Form handling with class-based views
Form processing generally has 3 paths:
- 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 avoidthis, Django provides a collection of generic class-based views for formprocessing.
Basic forms
Given a simple contact form:
forms.py
- from django import forms
- class ContactForm(forms.Form):
- name = forms.CharField()
- message = forms.CharField(widget=forms.Textarea)
- def send_email(self):
- # send email using the self.cleaned_data dictionary
- pass
The view can be constructed using a FormView
:
views.py
- from myapp.forms import ContactForm
- from django.views.generic.edit import FormView
- class ContactView(FormView):
- template_name = 'contact.html'
- form_class = ContactForm
- success_url = '/thanks/'
- def form_valid(self, form):
- # This method is called when valid form data has been POSTed.
- # It should return an HttpResponse.
- form.send_email()
- return super().form_valid(form)
注意:
- FormView inherits
TemplateResponseMixin
sotemplate_name
can be used here. - The default implementation for
form_valid()
simplyredirects to thesuccess_url
.
Model forms
Generic views really shine when working with models. These genericviews will automatically create a ModelForm
, so long asthey can work out which model class to use:
- If the
model
attribute isgiven, that model class will be used. - If
get_object()
returns an object, the class of that object will be used. - If a
queryset
isgiven, the model for that queryset will be used.Model form views provide aform_valid()
implementationthat saves the model automatically. You can override this if you have anyspecial requirements; see below for examples.
You don't even need to provide a success_url
forCreateView
orUpdateView
- they will useget_absolute_url()
on the model object if available.
If you want to use a custom ModelForm
(for instance toadd extra validation) simply setform_class
on your view.
Note
When specifying a custom form class, you must still specify the model,even though the form_class
maybe a ModelForm
.
First we need to add get_absolute_url()
to ourAuthor
class:
models.py
- from django.db import models
- from django.urls import reverse
- class Author(models.Model):
- name = models.CharField(max_length=200)
- def get_absolute_url(self):
- return reverse('author-detail', kwargs={'pk': self.pk})
Then we can use CreateView
and friends to do the actualwork. Notice how we're just configuring the generic class-based viewshere; we don't have to write any logic ourselves:
views.py
- from django.urls import reverse_lazy
- from django.views.generic.edit import CreateView, DeleteView, UpdateView
- from myapp.models import Author
- class AuthorCreate(CreateView):
- model = Author
- fields = ['name']
- class AuthorUpdate(UpdateView):
- model = Author
- fields = ['name']
- class AuthorDelete(DeleteView):
- model = Author
- success_url = reverse_lazy('author-list')
Note
We have to use reverse_lazy()
here, not justreverse()
as the urls are not loaded when the file is imported.
The fields
attribute works the same way as the fields
attribute on theinner Meta
class on ModelForm
. Unless you define theform class in another way, the attribute is required and the view will raisean ImproperlyConfigured
exception if it's not.
If you specify both the fields
and form_class
attributes, anImproperlyConfigured
exception will be raised.
Finally, we hook these new views into the URLconf:
urls.py
- from django.urls import path
- from myapp.views import AuthorCreate, AuthorDelete, AuthorUpdate
- urlpatterns = [
- # ...
- path('author/add/', AuthorCreate.as_view(), name='author-add'),
- path('author/<int:pk>/', AuthorUpdate.as_view(), name='author-update'),
- path('author/<int:pk>/delete/', AuthorDelete.as_view(), name='author-delete'),
- ]
Note
These views inheritSingleObjectTemplateResponseMixin
which usestemplate_name_suffix
to construct thetemplate_name
based on the model.
In this example:
CreateView
andUpdateView
usemyapp/author_form.html
DeleteView
usesmyapp/author_confirm_delete.html
If you wish to have separate templates forCreateView
andUpdateView
, you can set eithertemplate_name
ortemplate_name_suffix
on your view class.
Models and request.user
To track the user that created an object using a CreateView
,you can use a custom ModelForm
to do this. First, addthe foreign key relation to the model:
models.py
- from django.contrib.auth.models import User
- from django.db import models
- class Author(models.Model):
- name = models.CharField(max_length=200)
- created_by = models.ForeignKey(User, on_delete=models.CASCADE)
- # ...
In the view, ensure that you don't include created_by
in the list of fieldsto edit, and overrideform_valid()
to add the user:
views.py
- from django.views.generic.edit import CreateView
- from myapp.models import Author
- class AuthorCreate(CreateView):
- model = Author
- fields = ['name']
- def form_valid(self, form):
- form.instance.created_by = self.request.user
- return super().form_valid(form)
Note that you'll need to decorate thisview usinglogin_required()
, oralternatively handle unauthorized users in theform_valid()
.
AJAX example
Here is a simple example showing how you might go about implementing a form thatworks for AJAX requests as well as 'normal' form POSTs:
- from django.http import JsonResponse
- from django.views.generic.edit import CreateView
- from myapp.models import Author
- class AjaxableResponseMixin:
- """
- Mixin to add AJAX support to a form.
- Must be used with an object-based FormView (e.g. CreateView)
- """
- def form_invalid(self, form):
- response = super().form_invalid(form)
- if self.request.is_ajax():
- return JsonResponse(form.errors, status=400)
- else:
- return response
- def form_valid(self, form):
- # We make sure to call the parent's form_valid() method because
- # it might do some processing (in the case of CreateView, it will
- # call form.save() for example).
- response = super().form_valid(form)
- if self.request.is_ajax():
- data = {
- 'pk': self.object.pk,
- }
- return JsonResponse(data)
- else:
- return response
- class AuthorCreate(AjaxableResponseMixin, CreateView):
- model = Author
- fields = ['name']