| ===================================== |
| Writing your first Django app, part 4 |
| ===================================== |
| |
| This tutorial begins where :doc:`Tutorial 3 </intro/tutorial03>` left off. We're |
| continuing the Web-poll application and will focus on simple form processing and |
| cutting down our code. |
| |
| Write a simple form |
| =================== |
| |
| Let's update our poll detail template ("polls/detail.html") from the last |
| tutorial, so that the template contains an HTML ``<form>`` element: |
| |
| .. code-block:: html+django |
| |
| <h1>{{ poll.question }}</h1> |
| |
| {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} |
| |
| <form action="{% url 'polls:vote' poll.id %}" method="post"> |
| {% csrf_token %} |
| {% for choice in poll.choice_set.all %} |
| <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" /> |
| <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br /> |
| {% endfor %} |
| <input type="submit" value="Vote" /> |
| </form> |
| |
| A quick rundown: |
| |
| * The above template displays a radio button for each poll choice. The |
| ``value`` of each radio button is the associated poll choice's ID. The |
| ``name`` of each radio button is ``"choice"``. That means, when somebody |
| selects one of the radio buttons and submits the form, it'll send the |
| POST data ``choice=#`` where # is the ID of the selected choice. This is the |
| basic concept of HTML forms. |
| |
| * We set the form's ``action`` to ``{% url 'polls:vote' poll.id %}``, and we |
| set ``method="post"``. Using ``method="post"`` (as opposed to |
| ``method="get"``) is very important, because the act of submitting this |
| form will alter data server-side. Whenever you create a form that alters |
| data server-side, use ``method="post"``. This tip isn't specific to |
| Django; it's just good Web development practice. |
| |
| * ``forloop.counter`` indicates how many times the :ttag:`for` tag has gone |
| through its loop |
| |
| * Since we're creating a POST form (which can have the effect of modifying |
| data), we need to worry about Cross Site Request Forgeries. |
| Thankfully, you don't have to worry too hard, because Django comes with |
| a very easy-to-use system for protecting against it. In short, all POST |
| forms that are targeted at internal URLs should use the |
| :ttag:`{% csrf_token %}<csrf_token>` template tag. |
| |
| Now, let's create a Django view that handles the submitted data and does |
| something with it. Remember, in :doc:`Tutorial 3 </intro/tutorial03>`, we |
| created a URLconf for the polls application that includes this line:: |
| |
| url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='vote'), |
| |
| We also created a dummy implementation of the ``vote()`` function. Let's |
| create a real version. Add the following to ``polls/views.py``:: |
| |
| from django.shortcuts import get_object_or_404, render |
| from django.http import HttpResponseRedirect, HttpResponse |
| from django.core.urlresolvers import reverse |
| from polls.models import Choice, Poll |
| # ... |
| def vote(request, poll_id): |
| p = get_object_or_404(Poll, pk=poll_id) |
| try: |
| selected_choice = p.choice_set.get(pk=request.POST['choice']) |
| except (KeyError, Choice.DoesNotExist): |
| # Redisplay the poll voting form. |
| return render(request, 'polls/detail.html', { |
| 'poll': p, |
| 'error_message': "You didn't select a choice.", |
| }) |
| else: |
| selected_choice.votes += 1 |
| selected_choice.save() |
| # Always return an HttpResponseRedirect after successfully dealing |
| # with POST data. This prevents data from being posted twice if a |
| # user hits the Back button. |
| return HttpResponseRedirect(reverse('polls:results', args=(p.id,))) |
| |
| This code includes a few things we haven't covered yet in this tutorial: |
| |
| * :attr:`request.POST <django.http.HttpRequest.POST>` is a dictionary-like |
| object that lets you access submitted data by key name. In this case, |
| ``request.POST['choice']`` returns the ID of the selected choice, as a |
| string. :attr:`request.POST <django.http.HttpRequest.POST>` values are |
| always strings. |
| |
| Note that Django also provides :attr:`request.GET |
| <django.http.HttpRequest.GET>` for accessing GET data in the same way -- |
| but we're explicitly using :attr:`request.POST |
| <django.http.HttpRequest.POST>` in our code, to ensure that data is only |
| altered via a POST call. |
| |
| * ``request.POST['choice']`` will raise :exc:`~exceptions.KeyError` if |
| ``choice`` wasn't provided in POST data. The above code checks for |
| :exc:`~exceptions.KeyError` and redisplays the poll form with an error |
| message if ``choice`` isn't given. |
| |
| * After incrementing the choice count, the code returns an |
| :class:`~django.http.HttpResponseRedirect` rather than a normal |
| :class:`~django.http.HttpResponse`. |
| :class:`~django.http.HttpResponseRedirect` takes a single argument: the |
| URL to which the user will be redirected (see the following point for how |
| we construct the URL in this case). |
| |
| As the Python comment above points out, you should always return an |
| :class:`~django.http.HttpResponseRedirect` after successfully dealing with |
| POST data. This tip isn't specific to Django; it's just good Web |
| development practice. |
| |
| * We are using the :func:`~django.core.urlresolvers.reverse` function in the |
| :class:`~django.http.HttpResponseRedirect` constructor in this example. |
| This function helps avoid having to hardcode a URL in the view function. |
| It is given the name of the view that we want to pass control to and the |
| variable portion of the URL pattern that points to that view. In this |
| case, using the URLconf we set up in Tutorial 3, this |
| :func:`~django.core.urlresolvers.reverse` call will return a string like |
| :: |
| |
| '/polls/3/results/' |
| |
| ... where the ``3`` is the value of ``p.id``. This redirected URL will |
| then call the ``'results'`` view to display the final page. |
| |
| As mentioned in Tutorial 3, ``request`` is a :class:`~django.http.HttpRequest` |
| object. For more on :class:`~django.http.HttpRequest` objects, see the |
| :doc:`request and response documentation </ref/request-response>`. |
| |
| After somebody votes in a poll, the ``vote()`` view redirects to the results |
| page for the poll. Let's write that view:: |
| |
| def results(request, poll_id): |
| poll = get_object_or_404(Poll, pk=poll_id) |
| return render(request, 'polls/results.html', {'poll': poll}) |
| |
| This is almost exactly the same as the ``detail()`` view from :doc:`Tutorial 3 |
| </intro/tutorial03>`. The only difference is the template name. We'll fix this |
| redundancy later. |
| |
| Now, create a ``polls/results.html`` template: |
| |
| .. code-block:: html+django |
| |
| <h1>{{ poll.question }}</h1> |
| |
| <ul> |
| {% for choice in poll.choice_set.all %} |
| <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li> |
| {% endfor %} |
| </ul> |
| |
| <a href="{% url 'polls:detail' poll.id %}">Vote again?</a> |
| |
| Now, go to ``/polls/1/`` in your browser and vote in the poll. You should see a |
| results page that gets updated each time you vote. If you submit the form |
| without having chosen a choice, you should see the error message. |
| |
| Use generic views: Less code is better |
| ====================================== |
| |
| The ``detail()`` (from :doc:`Tutorial 3 </intro/tutorial03>`) and ``results()`` |
| views are stupidly simple -- and, as mentioned above, redundant. The ``index()`` |
| view (also from Tutorial 3), which displays a list of polls, is similar. |
| |
| These views represent a common case of basic Web development: getting data from |
| the database according to a parameter passed in the URL, loading a template and |
| returning the rendered template. Because this is so common, Django provides a |
| shortcut, called the "generic views" system. |
| |
| Generic views abstract common patterns to the point where you don't even need |
| to write Python code to write an app. |
| |
| Let's convert our poll app to use the generic views system, so we can delete a |
| bunch of our own code. We'll just have to take a few steps to make the |
| conversion. We will: |
| |
| 1. Convert the URLconf. |
| |
| 2. Delete some of the old, unneeded views. |
| |
| 3. Introduce new views based on Django's generic views. |
| |
| Read on for details. |
| |
| .. admonition:: Why the code-shuffle? |
| |
| Generally, when writing a Django app, you'll evaluate whether generic views |
| are a good fit for your problem, and you'll use them from the beginning, |
| rather than refactoring your code halfway through. But this tutorial |
| intentionally has focused on writing the views "the hard way" until now, to |
| focus on core concepts. |
| |
| You should know basic math before you start using a calculator. |
| |
| Amend URLconf |
| ------------- |
| |
| First, open the ``polls/urls.py`` URLconf and change it like so:: |
| |
| from django.conf.urls import patterns, url |
| |
| from polls import views |
| |
| urlpatterns = patterns('', |
| url(r'^$', views.IndexView.as_view(), name='index'), |
| url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'), |
| url(r'^(?P<pk>\d+)/results/$', views.ResultsView.as_view(), name='results'), |
| url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='vote'), |
| ) |
| |
| Amend views |
| ----------- |
| |
| Next, we're going to remove our old ``index``, ``detail``, and ``results`` |
| views and use Django's generic views instead. To do so, open the |
| ``polls/views.py`` file and change it like so:: |
| |
| from django.shortcuts import get_object_or_404, render |
| from django.http import HttpResponseRedirect |
| from django.core.urlresolvers import reverse |
| from django.views import generic |
| |
| from polls.models import Choice, Poll |
| |
| class IndexView(generic.ListView): |
| template_name = 'polls/index.html' |
| context_object_name = 'latest_poll_list' |
| |
| def get_queryset(self): |
| """Return the last five published polls.""" |
| return Poll.objects.order_by('-pub_date')[:5] |
| |
| |
| class DetailView(generic.DetailView): |
| model = Poll |
| template_name = 'polls/detail.html' |
| |
| |
| class ResultsView(generic.DetailView): |
| model = Poll |
| template_name = 'polls/results.html' |
| |
| def vote(request, poll_id): |
| .... |
| |
| We're using two generic views here: |
| :class:`~django.views.generic.list.ListView` and |
| :class:`~django.views.generic.detail.DetailView`. Respectively, those |
| two views abstract the concepts of "display a list of objects" and |
| "display a detail page for a particular type of object." |
| |
| * Each generic view needs to know what model it will be acting |
| upon. This is provided using the ``model`` attribute. |
| |
| * The :class:`~django.views.generic.detail.DetailView` generic view |
| expects the primary key value captured from the URL to be called |
| ``"pk"``, so we've changed ``poll_id`` to ``pk`` for the generic |
| views. |
| |
| By default, the :class:`~django.views.generic.detail.DetailView` generic |
| view uses a template called ``<app name>/<model name>_detail.html``. |
| In our case, it would use the template ``"polls/poll_detail.html"``. The |
| ``template_name`` attribute is used to tell Django to use a specific |
| template name instead of the autogenerated default template name. We |
| also specify the ``template_name`` for the ``results`` list view -- |
| this ensures that the results view and the detail view have a |
| different appearance when rendered, even though they're both a |
| :class:`~django.views.generic.detail.DetailView` behind the scenes. |
| |
| Similarly, the :class:`~django.views.generic.list.ListView` generic |
| view uses a default template called ``<app name>/<model |
| name>_list.html``; we use ``template_name`` to tell |
| :class:`~django.views.generic.list.ListView` to use our existing |
| ``"polls/index.html"`` template. |
| |
| In previous parts of the tutorial, the templates have been provided |
| with a context that contains the ``poll`` and ``latest_poll_list`` |
| context variables. For ``DetailView`` the ``poll`` variable is provided |
| automatically -- since we're using a Django model (``Poll``), Django |
| is able to determine an appropriate name for the context variable. |
| However, for ListView, the automatically generated context variable is |
| ``poll_list``. To override this we provide the ``context_object_name`` |
| attribute, specifying that we want to use ``latest_poll_list`` instead. |
| As an alternative approach, you could change your templates to match |
| the new default context variables -- but it's a lot easier to just |
| tell Django to use the variable you want. |
| |
| Run the server, and use your new polling app based on generic views. |
| |
| For full details on generic views, see the :doc:`generic views documentation |
| </topics/class-based-views/index>`. |
| |
| When you're comfortable with forms and generic views, read :doc:`part 5 of this |
| tutorial</intro/tutorial05>` to learn about testing our polls app. |