mirror of
				https://github.com/django/django.git
				synced 2025-10-28 08:06:09 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			259 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			259 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| 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 :ref:`Using a form in a view<using-a-form-in-a-view>`). To help avoid
 | |
| this, Django provides a collection of generic class-based views for form
 | |
| processing.
 | |
| 
 | |
| 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(ContactView, self).form_valid(form)
 | |
| 
 | |
| Notes:
 | |
| 
 | |
| * FormView inherits
 | |
|   :class:`~django.views.generic.base.TemplateResponseMixin` so
 | |
|   :attr:`~django.views.generic.base.TemplateResponseMixin.template_name`
 | |
|   can be used here.
 | |
| * The default implementation for
 | |
|   :meth:`~django.views.generic.edit.FormMixin.form_valid` simply
 | |
|   redirects to the :attr:`~django.views.generic.edit.FormMixin.success_url`.
 | |
| 
 | |
| Model Forms
 | |
| -----------
 | |
| 
 | |
| Generic views really shine when working with models.  These generic
 | |
| views will automatically create a :class:`~django.forms.ModelForm`, so long as
 | |
| they can work out which model class to use:
 | |
| 
 | |
| * If the :attr:`~django.views.generic.edit.ModelFormMixin.model` attribute is
 | |
|   given, that model class will be used.
 | |
| * If :meth:`~django.views.generic.detail.SingleObjectMixin.get_object()`
 | |
|   returns an object, the class of that object will be used.
 | |
| * If a :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` is
 | |
|   given, the model for that queryset will be used.
 | |
| 
 | |
| Model form views provide a
 | |
| :meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` 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
 | |
| :class:`~django.views.generic.edit.CreateView` or
 | |
| :class:`~django.views.generic.edit.UpdateView` - they will use
 | |
| :meth:`~django.db.models.Model.get_absolute_url()` on the model object if available.
 | |
| 
 | |
| If you want to use a custom :class:`~django.forms.ModelForm` (for instance to
 | |
| add extra validation) simply set
 | |
| :attr:`~django.views.generic.edit.FormMixin.form_class` on your view.
 | |
| 
 | |
| .. note::
 | |
|     When specifying a custom form class, you must still specify the model,
 | |
|     even though the :attr:`~django.views.generic.edit.FormMixin.form_class` may
 | |
|     be a :class:`~django.forms.ModelForm`.
 | |
| 
 | |
| First we need to add :meth:`~django.db.models.Model.get_absolute_url()` to our
 | |
| ``Author`` class:
 | |
| 
 | |
| .. code-block:: python
 | |
| 
 | |
|     # models.py
 | |
|     from django.core.urlresolvers import reverse
 | |
|     from django.db import models
 | |
| 
 | |
|     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 :class:`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
 | |
|     from django.views.generic.edit import CreateView, UpdateView, DeleteView
 | |
|     from django.core.urlresolvers import reverse_lazy
 | |
|     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 :func:`~django.core.urlresolvers.reverse_lazy` here, not
 | |
|     just ``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 :class:`~django.forms.ModelForm`. Unless you define the
 | |
| form class in another way, the attribute is required and the view will raise
 | |
| an :exc:`~django.core.exceptions.ImproperlyConfigured` exception if it's not.
 | |
| 
 | |
| .. versionchanged:: 1.8
 | |
| 
 | |
|     Omitting the ``fields`` attribute was previously allowed and resulted in a
 | |
|     form with all of the model's fields.
 | |
| 
 | |
| Finally, we hook these new views into the URLconf::
 | |
| 
 | |
|     # urls.py
 | |
|     from django.conf.urls import url
 | |
|     from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete
 | |
| 
 | |
|     urlpatterns = [
 | |
|         # ...
 | |
|         url(r'author/add/$', AuthorCreate.as_view(), name='author_add'),
 | |
|         url(r'author/(?P<pk>[0-9]+)/$', AuthorUpdate.as_view(), name='author_update'),
 | |
|         url(r'author/(?P<pk>[0-9]+)/delete/$', AuthorDelete.as_view(), name='author_delete'),
 | |
|     ]
 | |
| 
 | |
| .. note::
 | |
| 
 | |
|     These views inherit
 | |
|     :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`
 | |
|     which uses
 | |
|     :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
 | |
|     to construct the
 | |
|     :attr:`~django.views.generic.base.TemplateResponseMixin.template_name`
 | |
|     based on the model.
 | |
| 
 | |
|     In this example:
 | |
| 
 | |
|     * :class:`CreateView` and :class:`UpdateView` use ``myapp/author_form.html``
 | |
|     * :class:`DeleteView` uses ``myapp/author_confirm_delete.html``
 | |
| 
 | |
|     If you wish to have separate templates for :class:`CreateView` and
 | |
|     :class:`UpdateView`, you can set either
 | |
|     :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` or
 | |
|     :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix`
 | |
|     on your view class.
 | |
| 
 | |
| Models and request.user
 | |
| -----------------------
 | |
| 
 | |
| To track the user that created an object using a :class:`CreateView`,
 | |
| you can use a custom :class:`~django.forms.ModelForm` to do this. First, add
 | |
| the 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)
 | |
| 
 | |
|         # ...
 | |
| 
 | |
| In the view, ensure that you don't include ``created_by`` in the list of fields
 | |
| to edit, and override
 | |
| :meth:`~django.views.generic.edit.ModelFormMixin.form_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(AuthorCreate, self).form_valid(form)
 | |
| 
 | |
| Note that you'll need to :ref:`decorate this
 | |
| view<decorating-class-based-views>` using
 | |
| :func:`~django.contrib.auth.decorators.login_required`, or
 | |
| alternatively handle unauthorized users in the
 | |
| :meth:`~django.views.generic.edit.ModelFormMixin.form_valid()`.
 | |
| 
 | |
| AJAX example
 | |
| ------------
 | |
| 
 | |
| Here is a simple example showing how you might go about implementing a form that
 | |
| works for AJAX requests as well as 'normal' form POSTs::
 | |
| 
 | |
|     import json
 | |
| 
 | |
|     from django.http import HttpResponse
 | |
|     from django.views.generic.edit import CreateView
 | |
|     from myapp.models import Author
 | |
| 
 | |
|     class AjaxableResponseMixin(object):
 | |
|         """
 | |
|         Mixin to add AJAX support to a form.
 | |
|         Must be used with an object-based FormView (e.g. CreateView)
 | |
|         """
 | |
|         def render_to_json_response(self, context, **response_kwargs):
 | |
|             data = json.dumps(context)
 | |
|             response_kwargs['content_type'] = 'application/json'
 | |
|             return HttpResponse(data, **response_kwargs)
 | |
| 
 | |
|         def form_invalid(self, form):
 | |
|             response = super(AjaxableResponseMixin, self).form_invalid(form)
 | |
|             if self.request.is_ajax():
 | |
|                 return self.render_to_json_response(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(AjaxableResponseMixin, self).form_valid(form)
 | |
|             if self.request.is_ajax():
 | |
|                 data = {
 | |
|                     'pk': self.object.pk,
 | |
|                 }
 | |
|                 return self.render_to_json_response(data)
 | |
|             else:
 | |
|                 return response
 | |
| 
 | |
|     class AuthorCreate(AjaxableResponseMixin, CreateView):
 | |
|         model = Author
 | |
|         fields = ['name']
 |