K. Jones
K. Jones

Reputation: 35

Django Dynamic Formset UpdateView Not Updating

I used this tutorial to successfully set up a dynamic inline formset using Django. The CreateView works great, but I cannot get the UpdateView to actually update the related fields. There are no errors thrown, but the items will not update. I believe I have isolated the error to the form_valid function. The code is as follows. Thank you for any assistance.

class ApplicantCreate(CreateView):
    model = Applicant
    success_message = 'Your application was submitted successfully.'
    form_class = forms.ApplicantForm
    template_name = 'careers/add_applicant.html'
    success_url = reverse_lazy('careers:thanks')

    def get_context_data(self, **kwargs):
        data = super(ApplicantCreate, self).get_context_data(**kwargs)
        positions = super(ApplicantCreate, self).get_context_data(**kwargs)
        if self.request.POST:
            data['employer'] = forms.ApplicantEmployerFormSet(
                                    self.request.POST,
                                    prefix='employer')
            data['education'] = forms.ApplicantEducationFormSet(
                                     self.request.POST,
                                     prefix='education')
        else:
            data['employer'] = forms.ApplicantEmployerFormSet(prefix='employer')
            data['education'] = forms.ApplicantEducationFormSet(prefix='education')
        return data
        context['unfilled_positions'] = Position.objects.filter(filled=False)
        return positions

    def form_valid(self, form):
        context = self.get_context_data()
        employer = context['employer']
        education = context['education']
        with transaction.atomic():
            form.instance.created_by = self.request.user
            self.object = form.save()
            if employer.is_valid():
                employer.instance = self.object
                employer.save()
            if education.is_valid():
                education.instance = self.object
                education.save()
        return super(ApplicantCreate, self).form_valid(form)

    def get_success_url(self):
        return reverse_lazy('careers:thanks')

class ApplicantUpdate(SuccessMessageMixin,LoginRequiredMixin,GroupRequiredMixin,UpdateView):
    group_required = [u'careers-admin',u'careers']
    model = Applicant
    success_message = '%(first_name)s %(last_name)s was updated successfully.'
    form_class = forms.ApplicantUpdateForm
    template_name = 'careers/edit_applicant.html'

    def get_context_data(self, **kwargs):
        data = super(ApplicantUpdate, self).get_context_data(**kwargs)
        positions = super(ApplicantUpdate, self).get_context_data(**kwargs)
        if self.request.POST:
            data['employer'] = forms.ApplicantEmployerFormSet(
                                    self.request.POST,
                                    instance=self.object,
                                    prefix='employer')
            data['education'] = forms.ApplicantEducationFormSet(
                                    self.request.POST,
                                    instance=self.object,
                                    prefix='education')
        else:
            data['employer'] = forms.ApplicantEmployerFormSet(
                                    instance=self.object,
                                    prefix='employer')
            data['education'] = forms.ApplicantEducationFormSet(
                                    instance=self.object,
                                    prefix='education')
        return data
        context['unfilled_positions'] = Position.objects.filter(filled=False)
        return positions

    def form_valid(self, form):
        context = self.get_context_data()
        employer = context['employer']
        education = context['education']
        with transaction.atomic():
            form.instance.created_by = self.request.user
            self.object = form.save()
            if employer.is_valid():
                employer.instance = self.object
                employer.save()
            if education.is_valid():
                education.instance = self.object
                education.save()
        return super(ApplicantUpdate, self).form_valid(form)

    def get_success_url(self):
        return reverse_lazy('careers:applicant_detail',kwargs={'pk': self.object.pk})

Upvotes: 2

Views: 771

Answers (1)

dirkgroten
dirkgroten

Reputation: 20702

This is not a good blog post, because no errors are shown if any of the inline forms aren't valid. As a user, I wouldn't expect the view to just silently ignore errors in the inline forms, saving my main instance successfully and not reporting back those errors.

Note that I don't know what the errors are, maybe it's just an issue with the management form. But in any case, formset errors should be handled before the main object is actually saved.

In general, if you need to write a view with multiple forms (including a formset), it's better to use a function-based view or a View where you write the get and post than trying to force this into a generic class-based view, which is not meant for this.

def multiple_forms_view(request, object_id):
    # if this is a POST request we need to process the form data
    obj = get_object_or_404(MyModel, pk=object_id)
    if request.method == 'POST':
        # create a form instance and populate it with data from the request:
        form = MyForm(request.POST, instance=obj)
        formset = MyFormSet(request.POST, instance=obj, ...)
        # check whether it's valid:
        if form.is_valid() and formset.is_valid():
            # process the data in form.cleaned_data as required
            # ...
            # redirect to a new URL:
            return HttpResponseRedirect('/thanks/')

    # if a GET (or any other method) we'll create a blank form
    else:
        form = MyForm(instance=obj)
        formset = MyFormSet(instance=obj, ...)

    return render(request, 'name.html', {'form': form, 'formset': formset})

That way your template can render the errors of each form, including those in the formset. As mentioned before, you could also do this in a View (so your mixins will work), just write the get and post methods and return the same as in the function-based view above.

Upvotes: 4

Related Questions