Reputation: 35
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
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