generic-editing.txt 8.8 KB

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