![Django formset handling with class based views, custom errors and validation (1) Django formset handling with class based views, custom errors and validation (1)](https://i0.wp.com/simpleit.rocks/python/django/forms/using-formsets-with-django-cbv-generic-views/cover.jpg)
How to use formsets taking benefits of generic views
Published:TagsDjango, Forms
Overview
In Django, forms and formsets are very similar, but by default itsupports just regular forms in class based views, this is a guide toeasily use Formsets with Class Based Views (CBVs).
Basic
The most basic view for handling forms is one that:
- displays a form.1.1. On error, redisplays the form with validation errors;1.2. on success, redirects to a new URL.
That behaviour is already handled atdjango.views.generic.edit.FormView.
What if we want to take advantage of the above view but display anarbitrary number of forms for the same model?
Let’s assume we have the following in myapp/models.py
:
from django.db import modelsfrom django.urls import reverseclass Author(models.Model): name = models.CharField(max_length=200) def get_absolute_url(self): return reverse('author-detail', kwargs={'pk': self.pk})
To use a regular FormView CBV that displays a single form, we wouldhave to define it with a form in myapp/forms.py
:
from django import formsclass AuthorForm(forms.Form): name = forms.CharField()
and a view at myapp/views.py
using the above form specifying in atform_class
attribute:
from myapp.forms import AuthorFormfrom django.views.generic.edit import FormViewclass AuthorFormView(FormView): template_name = author.html form_class = AuthorForm success_url = '/'
with a template like:
<form method="post">{% csrf_token %} {{ form.as_p }} <input type="submit" value="Add"></form>
Using a formset
To display two author forms in our template with a single submitbutton, we slightly change the above code.
Create formset
First of all, we create a new formset in myapp/forms.py
:
AuthorFormSet = formset_factory( AuthorForm, extra=2, max_num=2, min_num=1)
We are using formset_factory
which isdefined at from django.forms import formset_factory
like:
def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, can_delete=False, max_num=None, validate_max=False, min_num=None, validate_min=False): ...
Use formset in view
Then use the above formset in the view, instead of a singleAuthorForm
we use AuthorFormSet
:
from myapp.forms import AuthorFormSetfrom django.views.generic.edit import FormViewclass AuthorFormView(FormView): template_name = author.html form_class = AuthorFormSet success_url = '/'
Now the template would display two forms, a simple test to be surethat the name is required (this test isn’t complete but I left hereto give an idea how to use TestCase.assertFormsetError
):
from django.test import TestCasefrom django.urls import reversefrom myapp.views import class AuthorFormView(TestCase): def test_language_is_required(self): name = 'Richard Morales' data = { 'form-TOTAL_FORMS': '2', 'form-INITIAL_FORMS': '1', 'form-MAX_NUM_FORMS': '2', #'form-0-name':name, not sent to raise the error } response = self.client.post(reverse('create-two-authors'), data) self.assertFormsetError(response, formset='form', form_index=0, field='name', errors='Please specify the name of theauthor.')
Adding custom validation to formset
To further customize the validation process, i.e.: overridingfull_clean()
, we need to inherit from BaseFormSet
instead of justdefining the method in the view, because it is the formset who doesthe proper validation for each of its forms and the collect all ofthose forms cleaned data at self.cleaned_data
.
Then in the myapp/forms.py
we explicitely create a newformset=BaseFormSet
to extend from, called AuthorBaseFormSet
andthen use it in formset_factory
.
When defining our custom full_clean
function, we can access allforms data in the self.cleaned_data
list, which contains the datafor each form in a dictionary like: [{'name': 'entered name in first form'},{'name': 'entered name in second form'}]
.
Finally, we add a specific error message and attach it to the rightform field like self.forms[0].add_error('name', "the name of the first form should contain vowels")
.
Summarizing:
class AuthorBaseFormSet(BaseFormSet): def full_clean(self, *args, **kwargs): """ Cleaning and validating fields that depend on each other""" super().full_clean(*args, **kwargs) # if basic clean has any problem stop further processing # if not is_valid() it won't have cleaned_data if(not self.is_valid()): return # now we can have access to self.cleaned_data# and be able to raise any error or attach an error to a form field# after some validation we add the errors ... msg0 = "First author name should contain vowels" msg1 = "Second author name should contain consonants" self.forms[0].add_error('rawtext', msg0) self.forms[1].add_error('rawtext', msg1)AuthorFormSet = formset_factory( AuthorForm, formset=AuthorBaseFormSet, # added to handle validation and error management extra=2, max_num=2, min_num=1)
Now to test the above expected behaviour:
from django.test import TestCasefrom django.urls import reversefrom myapp.views import class AuthorFormView(TestCase): def test_view_names_have_vowels_and_consonants(self): name0 = 'ZZZZZ' name1 = 'AAAAA' data = { 'form-TOTAL_FORMS': '2', 'form-INITIAL_FORMS': '2', 'form-MIN_NUM_FORMS': '1', 'form-MAX_NUM_FORMS': '2', 'form-0-name': name0, 'form-1-name': name1, } response = self.client.post(reverse('create-two-authors'), data) self.assertEqual(response.status_code, 200) # first form self.assertFormsetError(response, formset='form', form_index=0, field='name', errors='First author name should contain vowels') # second form self.assertFormsetError(response, formset='form', form_index=1, field='name', errors='Second author name should contain consonants')
Conclusion
In this way we can get all the benefits of Class Based Views usingthem with Formsets without having to repeat code for common taskalready well tested.
References
- Code example based in https://docs.djangoproject.com/en/2.2/ref/class-based-views/generic-editing/
- https://docs.djangoproject.com/en/2.1/topics/class-based-views/generic-editing/
- Adding a Cancel button in Django class-based views, editing views and formsJuly 15, 2019
- Using Django Model Primary Key in Custom Forms THE RIGHT WAYJuly 13, 2019
- Django formset handling with class based views, custom errors and validation
- How To Use Bootstrap 4 In Django FormsMay 25, 2018
- Understanding Django FormsApril 30, 2018
- How To Create A Form In DjangoJuly 29, 2016
Articles
Except as otherwise noted, the content of this page is licensed under CC BY-NC-ND 4.0 . Terms and Policy.
Powered by SimpleIT Hugo Theme
·