1 | Form handling with class-based views
|
---|
2 | ====================================
|
---|
3 |
|
---|
4 | Form processing generally has 3 paths:
|
---|
5 |
|
---|
6 | * Initial GET (blank or prepopulated form)
|
---|
7 | * POST with invalid data (typically redisplay form with errors)
|
---|
8 | * POST with valid data (process the data and typically redirect)
|
---|
9 |
|
---|
10 | Implementing this yourself often results in a lot of repeated boilerplate code
|
---|
11 | (see :ref:`Using a form in a view<using-a-form-in-a-view>`). To help avoid
|
---|
12 | this, Django provides a collection of generic class-based views for form
|
---|
13 | processing.
|
---|
14 |
|
---|
15 | Basic Forms
|
---|
16 | -----------
|
---|
17 |
|
---|
18 | Given a simple contact form::
|
---|
19 |
|
---|
20 | # forms.py
|
---|
21 | from django import forms
|
---|
22 |
|
---|
23 | class ContactForm(forms.Form):
|
---|
24 | name = forms.CharField()
|
---|
25 | message = forms.CharField(widget=forms.Textarea)
|
---|
26 |
|
---|
27 | def send_email(self):
|
---|
28 | # send email using the self.cleaned_data dictionary
|
---|
29 | pass
|
---|
30 |
|
---|
31 | The view can be constructed using a ``FormView``::
|
---|
32 |
|
---|
33 | # views.py
|
---|
34 | from myapp.forms import ContactForm
|
---|
35 | from django.views.generic.edit import FormView
|
---|
36 |
|
---|
37 | class ContactView(FormView):
|
---|
38 | template_name = 'contact.html'
|
---|
39 | form_class = ContactForm
|
---|
40 | success_url = '/thanks/'
|
---|
41 |
|
---|
42 | def form_valid(self, form):
|
---|
43 | # This method is called when valid form data has been POSTed.
|
---|
44 | # It should return an HttpResponse.
|
---|
45 | form.send_email()
|
---|
46 | return super(ContactView, self).form_valid(form)
|
---|
47 |
|
---|
48 | Notes:
|
---|
49 |
|
---|
50 | * FormView inherits
|
---|
51 | :class:`~django.views.generic.base.TemplateResponseMixin` so
|
---|
52 | :attr:`~django.views.generic.base.TemplateResponseMixin.template_name`
|
---|
53 | can be used here.
|
---|
54 | * The default implementation for
|
---|
55 | :meth:`~django.views.generic.edit.FormMixin.form_valid` simply
|
---|
56 | redirects to the :attr:`~django.views.generic.edit.FormMixin.success_url`.
|
---|
57 |
|
---|
58 | Model Forms
|
---|
59 | -----------
|
---|
60 |
|
---|
61 | Generic views really shine when working with models. These generic
|
---|
62 | views will automatically create a :class:`~django.forms.ModelForm`, so long as
|
---|
63 | they can work out which model class to use:
|
---|
64 |
|
---|
65 | * If the :attr:`~django.views.generic.edit.ModelFormMixin.model` attribute is
|
---|
66 | given, that model class will be used.
|
---|
67 | * If :meth:`~django.views.generic.detail.SingleObjectMixin.get_object()`
|
---|
68 | returns an object, the class of that object will be used.
|
---|
69 | * If a :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` is
|
---|
70 | given, the model for that queryset will be used.
|
---|
71 |
|
---|
72 | Model form views provide a
|
---|
73 | :meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` implementation
|
---|
74 | that saves the model automatically. You can override this if you have any
|
---|
75 | special requirements; see below for examples.
|
---|
76 |
|
---|
77 | You don't even need to provide a ``success_url`` for
|
---|
78 | :class:`~django.views.generic.edit.CreateView` or
|
---|
79 | :class:`~django.views.generic.edit.UpdateView` - they will use
|
---|
80 | :meth:`~django.db.models.Model.get_absolute_url()` on the model object if available.
|
---|
81 |
|
---|
82 | If you want to use a custom :class:`~django.forms.ModelForm` (for instance to
|
---|
83 | add extra validation) simply set
|
---|
84 | :attr:`~django.views.generic.edit.FormMixin.form_class` on your view.
|
---|
85 |
|
---|
86 | .. note::
|
---|
87 | When specifying a custom form class, you must still specify the model,
|
---|
88 | even though the :attr:`~django.views.generic.edit.FormMixin.form_class` may
|
---|
89 | be a :class:`~django.forms.ModelForm`.
|
---|
90 |
|
---|
91 | First we need to add :meth:`~django.db.models.Model.get_absolute_url()` to our
|
---|
92 | ``Author`` class:
|
---|
93 |
|
---|
94 | .. code-block:: python
|
---|
95 |
|
---|
96 | # models.py
|
---|
97 | from django.core.urlresolvers import reverse
|
---|
98 | from django.db import models
|
---|
99 |
|
---|
100 | class Author(models.Model):
|
---|
101 | name = models.CharField(max_length=200)
|
---|
102 |
|
---|
103 | def get_absolute_url(self):
|
---|
104 | return reverse('author-detail', kwargs={'pk': self.pk})
|
---|
105 |
|
---|
106 | Then we can use :class:`CreateView` and friends to do the actual
|
---|
107 | work. Notice how we're just configuring the generic class-based views
|
---|
108 | here; we don't have to write any logic ourselves::
|
---|
109 |
|
---|
110 | # views.py
|
---|
111 | from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
---|
112 | from django.core.urlresolvers import reverse_lazy
|
---|
113 | from myapp.models import Author
|
---|
114 |
|
---|
115 | class AuthorCreate(CreateView):
|
---|
116 | model = Author
|
---|
117 | fields = ['name']
|
---|
118 |
|
---|
119 | class AuthorUpdate(UpdateView):
|
---|
120 | model = Author
|
---|
121 | fields = ['name']
|
---|
122 |
|
---|
123 | class AuthorDelete(DeleteView):
|
---|
124 | model = Author
|
---|
125 | success_url = reverse_lazy('author-list')
|
---|
126 |
|
---|
127 | .. note::
|
---|
128 | We have to use :func:`~django.core.urlresolvers.reverse_lazy` here, not
|
---|
129 | just ``reverse`` as the urls are not loaded when the file is imported.
|
---|
130 |
|
---|
131 | The ``fields`` attribute works the same way as the ``fields`` attribute on the
|
---|
132 | inner ``Meta`` class on :class:`~django.forms.ModelForm`. Unless you define the
|
---|
133 | form class in another way, the attribute is required and the view will raise
|
---|
134 | an :exc:`~django.core.exceptions.ImproperlyConfigured` exception if it's not.
|
---|
135 |
|
---|
136 | .. versionchanged:: 1.8
|
---|
137 |
|
---|
138 | Omitting the ``fields`` attribute was previously allowed and resulted in a
|
---|
139 | form with all of the model's fields.
|
---|
140 |
|
---|
141 | Finally, we hook these new views into the URLconf::
|
---|
142 |
|
---|
143 | # urls.py
|
---|
144 | from django.conf.urls import url
|
---|
145 | from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete
|
---|
146 |
|
---|
147 | urlpatterns = [
|
---|
148 | # ...
|
---|
149 | url(r'author/add/$', AuthorCreate.as_view(), name='author_add'),
|
---|
150 | url(r'author/(?P<pk>[0-9]+)/$', AuthorUpdate.as_view(), name='author_update'),
|
---|
151 | url(r'author/(?P<pk>[0-9]+)/delete/$', AuthorDelete.as_view(), name='author_delete'),
|
---|
152 | ]
|
---|
153 |
|
---|
154 | .. note::
|
---|
155 |
|
---|
156 | These views inherit
|
---|
157 | :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`
|
---|
158 | which uses
|
---|
159 | :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
|
---|
160 | to construct the
|
---|
161 | :attr:`~django.views.generic.base.TemplateResponseMixin.template_name`
|
---|
162 | based on the model.
|
---|
163 |
|
---|
164 | In this example:
|
---|
165 |
|
---|
166 | * :class:`CreateView` and :class:`UpdateView` use ``myapp/author_form.html``
|
---|
167 | * :class:`DeleteView` uses ``myapp/author_confirm_delete.html``
|
---|
168 |
|
---|
169 | If you wish to have separate templates for :class:`CreateView` and
|
---|
170 | :class:`UpdateView`, you can set either
|
---|
171 | :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` or
|
---|
172 | :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
|
---|
173 | on your view class.
|
---|
174 |
|
---|
175 | Models and request.user
|
---|
176 | -----------------------
|
---|
177 |
|
---|
178 | To track the user that created an object using a :class:`CreateView`,
|
---|
179 | you can use a custom :class:`~django.forms.ModelForm` to do this. First, add
|
---|
180 | the foreign key relation to the model::
|
---|
181 |
|
---|
182 | # models.py
|
---|
183 | from django.contrib.auth import get_user_model
|
---|
184 | from django.db import models
|
---|
185 |
|
---|
186 | class Author(models.Model):
|
---|
187 | name = models.CharField(max_length=200)
|
---|
188 | created_by = models.ForeignKey(get_user_model())
|
---|
189 |
|
---|
190 | # ...
|
---|
191 |
|
---|
192 | In the view, ensure that you don't include ``created_by`` in the list of fields
|
---|
193 | to edit, and override
|
---|
194 | :meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the user::
|
---|
195 |
|
---|
196 | # views.py
|
---|
197 | from django.views.generic.edit import CreateView
|
---|
198 | from myapp.models import Author
|
---|
199 |
|
---|
200 | class AuthorCreate(CreateView):
|
---|
201 | model = Author
|
---|
202 | fields = ['name']
|
---|
203 |
|
---|
204 | def form_valid(self, form):
|
---|
205 | form.instance.created_by = self.request.user
|
---|
206 | return super(AuthorCreate, self).form_valid(form)
|
---|
207 |
|
---|
208 | Note that you'll need to :ref:`decorate this
|
---|
209 | view<decorating-class-based-views>` using
|
---|
210 | :func:`~django.contrib.auth.decorators.login_required`, or
|
---|
211 | alternatively handle unauthorized users in the
|
---|
212 | :meth:`~django.views.generic.edit.ModelFormMixin.form_valid()`.
|
---|
213 |
|
---|
214 | AJAX example
|
---|
215 | ------------
|
---|
216 |
|
---|
217 | Here is a simple example showing how you might go about implementing a form that
|
---|
218 | works for AJAX requests as well as 'normal' form POSTs::
|
---|
219 |
|
---|
220 | import json
|
---|
221 |
|
---|
222 | from django.http import HttpResponse
|
---|
223 | from django.views.generic.edit import CreateView
|
---|
224 | from myapp.models import Author
|
---|
225 |
|
---|
226 | class AjaxableResponseMixin(object):
|
---|
227 | """
|
---|
228 | Mixin to add AJAX support to a form.
|
---|
229 | Must be used with an object-based FormView (e.g. CreateView)
|
---|
230 | """
|
---|
231 | def render_to_json_response(self, context, **response_kwargs):
|
---|
232 | data = json.dumps(context)
|
---|
233 | response_kwargs['content_type'] = 'application/json'
|
---|
234 | return HttpResponse(data, **response_kwargs)
|
---|
235 |
|
---|
236 | def form_invalid(self, form):
|
---|
237 | response = super(AjaxableResponseMixin, self).form_invalid(form)
|
---|
238 | if self.request.is_ajax():
|
---|
239 | return self.render_to_json_response(form.errors, status=400)
|
---|
240 | else:
|
---|
241 | return response
|
---|
242 |
|
---|
243 | def form_valid(self, form):
|
---|
244 | # We make sure to call the parent's form_valid() method because
|
---|
245 | # it might do some processing (in the case of CreateView, it will
|
---|
246 | # call form.save() for example).
|
---|
247 | response = super(AjaxableResponseMixin, self).form_valid(form)
|
---|
248 | if self.request.is_ajax():
|
---|
249 | data = {
|
---|
250 | 'pk': self.object.pk,
|
---|
251 | }
|
---|
252 | return self.render_to_json_response(data)
|
---|
253 | else:
|
---|
254 | return response
|
---|
255 |
|
---|
256 | class AuthorCreate(AjaxableResponseMixin, CreateView):
|
---|
257 | model = Author
|
---|
258 | fields = ['name']
|
---|