summaryrefslogtreecommitdiff
path: root/parts/django/docs/intro/tutorial04.txt
diff options
context:
space:
mode:
Diffstat (limited to 'parts/django/docs/intro/tutorial04.txt')
-rw-r--r--parts/django/docs/intro/tutorial04.txt346
1 files changed, 346 insertions, 0 deletions
diff --git a/parts/django/docs/intro/tutorial04.txt b/parts/django/docs/intro/tutorial04.txt
new file mode 100644
index 0000000..dfbd82d
--- /dev/null
+++ b/parts/django/docs/intro/tutorial04.txt
@@ -0,0 +1,346 @@
+=====================================
+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="/polls/{{ poll.id }}/vote/" 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 }}</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=3``. This is HTML Forms 101.
+
+ * We set the form's ``action`` to ``/polls/{{ poll.id }}/vote/``, 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 ``{% csrf_token %}``
+ template tag.
+
+The ``{% csrf_token %}`` tag requires information from the request object, which
+is not normally accessible from within the template context. To fix this, a
+small adjustment needs to be made to the ``detail`` view, so that it looks like
+the following::
+
+ from django.template import RequestContext
+ # ...
+ def detail(request, poll_id):
+ p = get_object_or_404(Poll, pk=poll_id)
+ return render_to_response('polls/detail.html', {'poll': p},
+ context_instance=RequestContext(request))
+
+The details of how this works are explained in the documentation for
+:ref:`RequestContext <subclassing-context-requestcontext>`.
+
+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::
+
+ (r'^(?P<poll_id>\d+)/vote/$', '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_to_response
+ from django.http import HttpResponseRedirect, HttpResponse
+ from django.core.urlresolvers import reverse
+ from django.template import RequestContext
+ 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_to_response('polls/detail.html', {
+ 'poll': p,
+ 'error_message': "You didn't select a choice.",
+ }, context_instance=RequestContext(request))
+ 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.views.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:`KeyError` if ``choice`` wasn't
+ provided in POST data. The above code checks for :exc:`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. Note that you
+ need to use the full name of the view here (including the prefix).
+
+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):
+ p = get_object_or_404(Poll, pk=poll_id)
+ return render_to_response('polls/results.html', {'poll': p})
+
+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 ``results.html`` template:
+
+.. code-block:: html+django
+
+ <h1>{{ poll.question }}</h1>
+
+ <ul>
+ {% for choice in poll.choice_set.all %}
+ <li>{{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
+ {% endfor %}
+ </ul>
+
+ <a href="/polls/{{ 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. Rename a few templates.
+
+ 3. Delete some of the old, unneeded views.
+
+ 4. Fix up URL handling for the new 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.
+
+First, open the ``polls/urls.py`` URLconf. It looks like this, according to the
+tutorial so far::
+
+ from django.conf.urls.defaults import *
+
+ urlpatterns = patterns('polls.views',
+ (r'^$', 'index'),
+ (r'^(?P<poll_id>\d+)/$', 'detail'),
+ (r'^(?P<poll_id>\d+)/results/$', 'results'),
+ (r'^(?P<poll_id>\d+)/vote/$', 'vote'),
+ )
+
+Change it like so::
+
+ from django.conf.urls.defaults import *
+ from polls.models import Poll
+
+ info_dict = {
+ 'queryset': Poll.objects.all(),
+ }
+
+ urlpatterns = patterns('',
+ (r'^$', 'django.views.generic.list_detail.object_list', info_dict),
+ (r'^(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict),
+ url(r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html'), 'poll_results'),
+ (r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
+ )
+
+We're using two generic views here:
+:func:`~django.views.generic.list_detail.object_list` and
+:func:`~django.views.generic.list_detail.object_detail`. 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 data it will be acting upon. This
+ data is provided in a dictionary. The ``queryset`` key in this dictionary
+ points to the list of objects to be manipulated by the generic view.
+
+ * The :func:`~django.views.generic.list_detail.object_detail` generic view
+ expects the ID value captured from the URL to be called ``"object_id"``,
+ so we've changed ``poll_id`` to ``object_id`` for the generic views.
+
+ * We've added a name, ``poll_results``, to the results view so that we have
+ a way to refer to its URL later on (see the documentation about
+ :ref:`naming URL patterns <naming-url-patterns>` for information). We're
+ also using the :func:`~django.conf.urls.default.url` function from
+ :mod:`django.conf.urls.defaults` here. It's a good habit to use
+ :func:`~django.conf.urls.defaults.url` when you are providing a pattern
+ name like this.
+
+By default, the :func:`~django.views.generic.list_detail.object_detail` generic
+view uses a template called ``<app name>/<model name>_detail.html``. In our
+case, it'll use the template ``"polls/poll_detail.html"``. Thus, rename your
+``polls/detail.html`` template to ``polls/poll_detail.html``, and change the
+:func:`~django.shortcuts.render_to_response` line in ``vote()``.
+
+Similarly, the :func:`~django.views.generic.list_detail.object_list` generic
+view uses a template called ``<app name>/<model name>_list.html``. Thus, rename
+``polls/index.html`` to ``polls/poll_list.html``.
+
+Because we have more than one entry in the URLconf that uses
+:func:`~django.views.generic.list_detail.object_detail` for the polls app, we
+manually specify a template name for the results view:
+``template_name='polls/results.html'``. Otherwise, both views would use the same
+template. Note that we use ``dict()`` to return an altered dictionary in place.
+
+.. note:: :meth:`django.db.models.QuerySet.all` is lazy
+
+ It might look a little frightening to see ``Poll.objects.all()`` being used
+ in a detail view which only needs one ``Poll`` object, but don't worry;
+ ``Poll.objects.all()`` is actually a special object called a
+ :class:`~django.db.models.QuerySet`, which is "lazy" and doesn't hit your
+ database until it absolutely has to. By the time the database query happens,
+ the :func:`~django.views.generic.list_detail.object_detail` generic view
+ will have narrowed its scope down to a single object, so the eventual query
+ will only select one row from the database.
+
+ If you'd like to know more about how that works, The Django database API
+ documentation :ref:`explains the lazy nature of QuerySet objects
+ <querysets-are-lazy>`.
+
+In previous parts of the tutorial, the templates have been provided with a
+context that contains the ``poll`` and ``latest_poll_list`` context variables.
+However, the generic views provide the variables ``object`` and ``object_list``
+as context. Therefore, you need to change your templates to match the new
+context variables. Go through your templates, and modify any reference to
+``latest_poll_list`` to ``object_list``, and change any reference to ``poll``
+to ``object``.
+
+You can now delete the ``index()``, ``detail()`` and ``results()`` views
+from ``polls/views.py``. We don't need them anymore -- they have been replaced
+by generic views.
+
+The ``vote()`` view is still required. However, it must be modified to match the
+new context variables. In the :func:`~django.shortcuts.render_to_response` call,
+rename the ``poll`` context variable to ``object``.
+
+The last thing to do is fix the URL handling to account for the use of generic
+views. In the vote view above, we used the
+:func:`~django.core.urlresolvers.reverse` function to avoid hard-coding our
+URLs. Now that we've switched to a generic view, we'll need to change the
+:func:`~django.core.urlresolvers.reverse` call to point back to our new generic
+view. We can't simply use the view function anymore -- generic views can be (and
+are) used multiple times -- but we can use the name we've given::
+
+ return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))
+
+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/http/generic-views>`.
+
+Coming soon
+===========
+
+The tutorial ends here for the time being. Future installments of the tutorial
+will cover:
+
+ * Advanced form processing
+ * Using the RSS framework
+ * Using the cache framework
+ * Using the comments framework
+ * Advanced admin features: Permissions
+ * Advanced admin features: Custom JavaScript
+
+In the meantime, you might want to check out some pointers on :doc:`where to go
+from here </intro/whatsnext>`