~ubuntu-branches/ubuntu/saucy/python-django/saucy-updates

« back to all changes in this revision

Viewing changes to docs/topics/class-based-views/generic-editing.txt

  • Committer: Package Import Robot
  • Author(s): Luke Faraone, Jakub Wilk, Luke Faraone
  • Date: 2013-05-09 15:10:47 UTC
  • mfrom: (1.1.21) (4.4.27 sid)
  • Revision ID: package-import@ubuntu.com-20130509151047-aqv8d71oj9wvcv8c
Tags: 1.5.1-2
[ Jakub Wilk ]
* Use canonical URIs for Vcs-* fields.

[ Luke Faraone ]
* Upload to unstable.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
118
    class AuthorUpdate(UpdateView):
 
119
        model = Author
 
120
 
 
121
    class AuthorDelete(DeleteView):
 
122
        model = Author
 
123
        success_url = reverse_lazy('author-list')
 
124
 
 
125
.. note::
 
126
    We have to use :func:`~django.core.urlresolvers.reverse_lazy` here, not
 
127
    just ``reverse`` as the urls are not loaded when the file is imported.
 
128
 
 
129
Finally, we hook these new views into the URLconf::
 
130
 
 
131
    # urls.py
 
132
    from django.conf.urls import patterns, url
 
133
    from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete
 
134
 
 
135
    urlpatterns = patterns('',
 
136
        # ...
 
137
        url(r'author/add/$', AuthorCreate.as_view(), name='author_add'),
 
138
        url(r'author/(?P<pk>\d+)/$', AuthorUpdate.as_view(), name='author_update'),
 
139
        url(r'author/(?P<pk>\d+)/delete/$', AuthorDelete.as_view(), name='author_delete'),
 
140
    )
 
141
 
 
142
.. note::
 
143
 
 
144
    These views inherit
 
145
    :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`
 
146
    which uses
 
147
    :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
 
148
    to construct the
 
149
    :attr:`~django.views.generic.base.TemplateResponseMixin.template_name`
 
150
    based on the model.
 
151
 
 
152
    In this example:
 
153
 
 
154
    * :class:`CreateView` and :class:`UpdateView` use ``myapp/author_form.html``
 
155
    * :class:`DeleteView` uses ``myapp/author_confirm_delete.html``
 
156
 
 
157
    If you wish to have separate templates for :class:`CreateView` and
 
158
    :class:`UpdateView`, you can set either
 
159
    :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` or
 
160
    :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
 
161
    on your view class.
 
162
 
 
163
Models and request.user
 
164
-----------------------
 
165
 
 
166
To track the user that created an object using a :class:`CreateView`,
 
167
you can use a custom :class:`~django.forms.ModelForm` to do this. First, add
 
168
the foreign key relation to the model::
 
169
 
 
170
    # models.py
 
171
    from django.contrib.auth import User
 
172
    from django.db import models
 
173
 
 
174
    class Author(models.Model):
 
175
        name = models.CharField(max_length=200)
 
176
        created_by = models.ForeignKey(User)
 
177
 
 
178
        # ...
 
179
 
 
180
Create a custom :class:`~django.forms.ModelForm` in order to exclude the
 
181
``created_by`` field and prevent the user from editing it:
 
182
 
 
183
.. code-block:: python
 
184
 
 
185
    # forms.py
 
186
    from django import forms
 
187
    from myapp.models import Author
 
188
 
 
189
    class AuthorForm(forms.ModelForm):
 
190
        class Meta:
 
191
            model = Author
 
192
            exclude = ('created_by',)
 
193
 
 
194
In the view, use the custom
 
195
:attr:`~django.views.generic.edit.FormMixin.form_class` and override
 
196
:meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the
 
197
user::
 
198
 
 
199
    # views.py
 
200
    from django.views.generic.edit import CreateView
 
201
    from myapp.models import Author
 
202
    from myapp.forms import AuthorForm
 
203
 
 
204
    class AuthorCreate(CreateView):
 
205
        form_class = AuthorForm
 
206
        model = Author
 
207
 
 
208
        def form_valid(self, form):
 
209
            form.instance.created_by = self.request.user
 
210
            return super(AuthorCreate, self).form_valid(form)
 
211
 
 
212
Note that you'll need to :ref:`decorate this
 
213
view<decorating-class-based-views>` using
 
214
:func:`~django.contrib.auth.decorators.login_required`, or
 
215
alternatively handle unauthorized users in the
 
216
:meth:`~django.views.generic.edit.ModelFormMixin.form_valid()`.
 
217
 
 
218
AJAX example
 
219
------------
 
220
 
 
221
Here is a simple example showing how you might go about implementing a form that
 
222
works for AJAX requests as well as 'normal' form POSTs::
 
223
 
 
224
    import json
 
225
 
 
226
    from django.http import HttpResponse
 
227
    from django.views.generic.edit import CreateView
 
228
 
 
229
    class AjaxableResponseMixin(object):
 
230
        """
 
231
        Mixin to add AJAX support to a form.
 
232
        Must be used with an object-based FormView (e.g. CreateView)
 
233
        """
 
234
        def render_to_json_response(self, context, **response_kwargs):
 
235
            data = json.dumps(context)
 
236
            response_kwargs['content_type'] = 'application/json'
 
237
            return HttpResponse(data, **response_kwargs)
 
238
 
 
239
        def form_invalid(self, form):
 
240
            if self.request.is_ajax():
 
241
                return self.render_to_json_response(form.errors, status=400)
 
242
            else:
 
243
                return super(AjaxableResponseMixin, self).form_invalid(form)
 
244
 
 
245
        def form_valid(self, form):
 
246
            if self.request.is_ajax():
 
247
                data = {
 
248
                    'pk': form.instance.pk,
 
249
                }
 
250
                return self.render_to_json_response(data)
 
251
            else:
 
252
                return super(AjaxableResponseMixin, self).form_valid(form)
 
253
 
 
254
    class AuthorCreate(AjaxableResponseMixin, CreateView):
 
255
        model = Author