Form handling with class-based views

    • Initial GET (blank or prepopulated form)
    • POST with invalid data (typically redisplay form with errors)
    • POST with valid data (process the data and typically redirect)

    Implementing this yourself often results in a lot of repeated boilerplate code (see Using a form in a view). To help avoid this, Django provides a collection of generic class-based views for form processing.

    Given a contact form:

    forms.py

    The view can be constructed using a FormView:

    views.py

    1. from myapp.forms import ContactForm
    2. from django.views.generic.edit import FormView
    3. class ContactFormView(FormView):
    4. template_name = 'contact.html'
    5. form_class = ContactForm
    6. success_url = '/thanks/'
    7. def form_valid(self, form):
    8. # This method is called when valid form data has been POSTed.
    9. # It should return an HttpResponse.
    10. form.send_email()
    11. return super().form_valid(form)

    Notes:

    • FormView inherits so template_name can be used here.
    • The default implementation for simply redirects to the success_url.

    Generic views really shine when working with models. These generic views will automatically create a , so long as they can work out which model class to use:

    • If the model attribute is given, that model class will be used.
    • If returns an object, the class of that object will be used.
    • If a queryset is given, the model for that queryset will be used.

    Model form views provide a implementation that saves the model automatically. You can override this if you have any special requirements; see below for examples.

    You don’t even need to provide a success_url for CreateView or - they will use get_absolute_url() on the model object if available.

    If you want to use a custom (for instance to add extra validation), set form_class on your view.

    When specifying a custom form class, you must still specify the model, even though the may be a ModelForm.

    First we need to add to our Author class:

    models.py

    Then we can use CreateView and friends to do the actual work. Notice how we’re just configuring the generic class-based views here; we don’t have to write any logic ourselves:

    views.py

    1. from django.urls import reverse_lazy
    2. from myapp.models import Author
    3. class AuthorCreateView(CreateView):
    4. model = Author
    5. fields = ['name']
    6. model = Author
    7. fields = ['name']
    8. class AuthorDeleteView(DeleteView):
    9. model = Author
    10. success_url = reverse_lazy('author-list')

    Note

    We have to use instead of reverse(), as the urls are not loaded when the file is imported.

    The fields attribute works the same way as the fields attribute on the inner Meta class on ModelForm. Unless you define the form class in another way, the attribute is required and the view will raise an exception if it’s not.

    If you specify both the fields and attributes, an ImproperlyConfigured exception will be raised.

    Finally, we hook these new views into the URLconf:

    Note

    These views inherit which uses template_name_suffix to construct the based on the model.

    In this example:

    If you wish to have separate templates for and UpdateView, you can set either or template_name_suffix on your view class.

    To track the user that created an object using a , you can use a custom ModelForm to do this. First, add the foreign key relation to the model:

    models.py

    1. from django.contrib.auth.models import User
    2. from django.db import models
    3. class Author(models.Model):
    4. name = models.CharField(max_length=200)
    5. created_by = models.ForeignKey(User, on_delete=models.CASCADE)
    6. # ...

    In the view, ensure that you don’t include created_by in the list of fields to edit, and override to add the user:

    views.py

    LoginRequiredMixin prevents users who aren’t logged in from accessing the form. If you omit that, you’ll need to handle unauthorized users in .

    Here is an example showing how you might go about implementing a form that works with an API-based workflow as well as ‘normal’ form POSTs:

    1. from django.http import JsonResponse
    2. from django.views.generic.edit import CreateView
    3. from myapp.models import Author
    4. class JsonableResponseMixin:
    5. """
    6. Mixin to add JSON support to a form.
    7. Must be used with an object-based FormView (e.g. CreateView)
    8. """
    9. def form_invalid(self, form):
    10. response = super().form_invalid(form)
    11. if self.request.accepts('text/html'):
    12. return response
    13. else:
    14. return JsonResponse(form.errors, status=400)
    15. def form_valid(self, form):
    16. # We make sure to call the parent's form_valid() method because
    17. # it might do some processing (in the case of CreateView, it will
    18. # call form.save() for example).
    19. response = super().form_valid(form)
    20. if self.request.accepts('text/html'):
    21. return response
    22. else:
    23. data = {
    24. 'pk': self.object.pk,
    25. }
    26. return JsonResponse(data)
    27. class AuthorCreateView(JsonableResponseMixin, CreateView):
    28. fields = ['name']