Search code examples
djangoimageformsinline-formset

Django inline-formset with an image field not updating


I have a listing model and a photo model:

class Listing(models.Model):
    title = models.CharField(max_length=255)
    <more fields>...

class Photo(models.Model):
    image = models.ImageField(upload_to=create_file_path)
    listing = models.ForeignKey(Listing, related_name='photos')

I am using a CBV, UpdateView, to edit a listing. I am using this form:

class ListingDetailForm(forms.ModelForm):
    class Meta:
    model = Listing
    exclude = []

and the inline formset in forms.py to make deleting/changing the image possible:

PhotoFormset = inlineformset_factory(Listing, Photo, fields='__all__', extra=1)

here is my view:

class ListingDetailView(UpdateView):
    model = Listing
    template_name = 'listing/listing_detail.html'
    form_class = ListingDetailForm
    success_url = '/store/'

    def get_context_data(self, **kwargs):
        self.object = self.get_object()
        context = super(ListingDetailView, self).get_context_data(**kwargs)
        if self.request.POST:
            context['form'] = ListingDetailForm(self.request.POST, instance=self.object)
            context['photo_form'] = PhotoFormset(self.request.POST, self.request.FILES, instance=self.object)
        else:
            context['form'] = ListingDetailForm(instance=self.object)
            context['photo_form'] = PhotoFormset(instance=self.object)
        return context

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        photo_form = PhotoFormset(self.request.POST)
        print photo_form.is_valid()
        if form.is_valid() and photo_form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form):
        print 'in form valid for update'
        context = self.get_context_data()
        base_form = context['form']
        photo_form = context['photo_form']
        # print base_form
        # print photo_form
        if base_form.is_valid() and photo_form.is_valid():
            print 'forms are valid for update'
            base_form.save()
            photo_form.save()
            return super(ListingDetailView, self).form_valid(form)
        else:
            return self.render_to_response(self)

and the relevant template section:

{% block body %}
<form action="" method="post">
    {% csrf_token %}
        {% for field in form %}
            {{ field.errors }}
            {{ field.label_tag }} {{ field }}<br><br>
        {% endfor %}
        {% for field in photo_form %}
            {{ field.errors }}
            {{ field.label_tag }} {{ field }}<br><br>
        {% endfor %}
    {{ photo_form.management_form }}
    <input type="submit" value="Update" />
</form>
{% endblock %}

The issues I am having are:

1) If there is a photo attached to the listing, through the admin, the photo form does not pass validation if I do nothing with the photo form, e.g. change only fields from the listing model. The photo form displays no errors when the page reloads after invalid.

2) selecting a new photo does not change the current photo, the photo form does not validate and displays no errors.

3) if there is currently no photo related to the listing trying to add one validates through the form but does not actually save a photo related to that listing.

Deleting an image, if there is one attached to the listing, works just fine. Deleting the image and updating some other field from the listing works. If there is no image updating only a listing field works. Adding a second photo to the listing through the form does not work and displays no form errors.


Solution

  • There are a few issues I noticed with your form.

    1. You need to include enctype="multipart/form-data" on your form attributes or else you won't be able to post file data to the server
    2. I would use the Django methods for rendering the form (form.as_p, form.as_table or form.as_ul) if you absolutely need to use manual rendering then follow the official guide: model formsets
    3. On the post method your formset is missing FILES and instance

    Once you implement these changes your formset should work just fine.