123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- =====================================
- Writing your first Django app, part 3
- =====================================
- This tutorial begins where :doc:`Tutorial 2 </intro/tutorial02>` left off. We're
- continuing the Web-poll application and will focus on creating the public
- interface -- "views."
- Overview
- ========
- A view is a "type" of Web page in your Django application that generally serves
- a specific function and has a specific template. For example, in a blog
- application, you might have the following views:
- * Blog homepage -- displays the latest few entries.
- * Entry "detail" page -- permalink page for a single entry.
- * Year-based archive page -- displays all months with entries in the
- given year.
- * Month-based archive page -- displays all days with entries in the
- given month.
- * Day-based archive page -- displays all entries in the given day.
- * Comment action -- handles posting comments to a given entry.
- In our poll application, we'll have the following four views:
- * Question "index" page -- displays the latest few questions.
- * Question "detail" page -- displays a question text, with no results but
- with a form to vote.
- * Question "results" page -- displays results for a particular question.
- * Vote action -- handles voting for a particular choice in a particular
- question.
- In Django, web pages and other content are delivered by views. Each view is
- represented by a simple Python function (or method, in the case of class-based
- views). Django will choose a view by examining the URL that's requested (to be
- precise, the part of the URL after the domain name).
- Now in your time on the web you may have come across such beauties as
- "ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B".
- You will be pleased to know that Django allows us much more elegant
- *URL patterns* than that.
- A URL pattern is simply the general form of a URL - for example:
- ``/newsarchive/<year>/<month>/``.
- To get from a URL to a view, Django uses what are known as 'URLconfs'. A
- URLconf maps URL patterns (described as regular expressions) to views.
- This tutorial provides basic instruction in the use of URLconfs, and you can
- refer to :mod:`django.core.urlresolvers` for more information.
- Writing more views
- ==================
- Now let's add a few more views to ``polls/views.py``. These views are
- slightly different, because they take an argument:
- .. snippet::
- :filename: polls/views.py
- def detail(request, question_id):
- return HttpResponse("You're looking at question %s." % question_id)
- def results(request, question_id):
- response = "You're looking at the results of question %s."
- return HttpResponse(response % question_id)
- def vote(request, question_id):
- return HttpResponse("You're voting on question %s." % question_id)
- Wire these new views into the ``polls.urls`` module by adding the following
- :func:`~django.conf.urls.url` calls:
- .. snippet::
- :filename: polls/urls.py
- from django.conf.urls import url
- from . import views
- urlpatterns = [
- # ex: /polls/
- url(r'^$', views.index, name='index'),
- # ex: /polls/5/
- url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
- # ex: /polls/5/results/
- url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
- # ex: /polls/5/vote/
- url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
- ]
- Take a look in your browser, at "/polls/34/". It'll run the ``detail()``
- method and display whatever ID you provide in the URL. Try
- "/polls/34/results/" and "/polls/34/vote/" too -- these will display the
- placeholder results and voting pages.
- When somebody requests a page from your Web site -- say, "/polls/34/", Django
- will load the ``mysite.urls`` Python module because it's pointed to by the
- :setting:`ROOT_URLCONF` setting. It finds the variable named ``urlpatterns``
- and traverses the regular expressions in order. The
- :func:`~django.conf.urls.include` functions we are using simply reference
- other URLconfs. Note that the regular expressions for the
- :func:`~django.conf.urls.include` functions don't have a ``$`` (end-of-string
- match character) but rather a trailing slash. Whenever Django encounters
- :func:`~django.conf.urls.include`, it chops off whatever part of the URL
- matched up to that point and sends the remaining string to the included
- URLconf for further processing.
- The idea behind :func:`~django.conf.urls.include` is to make it easy to
- plug-and-play URLs. Since polls are in their own URLconf
- (``polls/urls.py``), they can be placed under "/polls/", or under
- "/fun_polls/", or under "/content/polls/", or any other path root, and the
- app will still work.
- Here's what happens if a user goes to "/polls/34/" in this system:
- * Django will find the match at ``'^polls/'``
- * Then, Django will strip off the matching text (``"polls/"``) and send the
- remaining text -- ``"34/"`` -- to the 'polls.urls' URLconf for
- further processing which matches ``r'^(?P<question_id>[0-9]+)/$'`` resulting in a
- call to the ``detail()`` view like so::
- detail(request=<HttpRequest object>, question_id='34')
- The ``question_id='34'`` part comes from ``(?P<question_id>[0-9]+)``. Using parentheses
- around a pattern "captures" the text matched by that pattern and sends it as an
- argument to the view function; ``?P<question_id>`` defines the name that will
- be used to identify the matched pattern; and ``[0-9]+`` is a regular expression to
- match a sequence of digits (i.e., a number).
- Because the URL patterns are regular expressions, there really is no limit on
- what you can do with them. And there's no need to add URL cruft such as
- ``.html`` -- unless you want to, in which case you can do something like
- this::
- url(r'^polls/latest\.html$', views.index),
- But, don't do that. It's silly.
- Write views that actually do something
- ======================================
- Each view is responsible for doing one of two things: returning an
- :class:`~django.http.HttpResponse` object containing the content for the
- requested page, or raising an exception such as :exc:`~django.http.Http404`. The
- rest is up to you.
- Your view can read records from a database, or not. It can use a template
- system such as Django's -- or a third-party Python template system -- or not.
- It can generate a PDF file, output XML, create a ZIP file on the fly, anything
- you want, using whatever Python libraries you want.
- All Django wants is that :class:`~django.http.HttpResponse`. Or an exception.
- Because it's convenient, let's use Django's own database API, which we covered
- in :doc:`Tutorial 2 </intro/tutorial02>`. Here's one stab at a new ``index()``
- view, which displays the latest 5 poll questions in the system, separated by
- commas, according to publication date:
- .. snippet::
- :filename: polls/views.py
- from django.http import HttpResponse
- from .models import Question
- def index(request):
- latest_question_list = Question.objects.order_by('-pub_date')[:5]
- output = ', '.join([p.question_text for p in latest_question_list])
- return HttpResponse(output)
- # Leave the rest of the views (detail, results, vote) unchanged
- There's a problem here, though: the page's design is hard-coded in the view. If
- you want to change the way the page looks, you'll have to edit this Python code.
- So let's use Django's template system to separate the design from Python by
- creating a template that the view can use.
- First, create a directory called ``templates`` in your ``polls`` directory.
- Django will look for templates in there.
- Your project's :setting:`TEMPLATES` setting describes how Django will load and
- render templates. The default settings file configures a ``DjangoTemplates``
- backend whose :setting:`APP_DIRS <TEMPLATES-APP_DIRS>` option is set to
- ``True``. By convention ``DjangoTemplates`` looks for a "templates"
- subdirectory in each of the :setting:`INSTALLED_APPS`.
- Within the ``templates`` directory you have just created, create another
- directory called ``polls``, and within that create a file called
- ``index.html``. In other words, your template should be at
- ``polls/templates/polls/index.html``. Because of how the ``app_directories``
- template loader works as described above, you can refer to this template within
- Django simply as ``polls/index.html``.
- .. admonition:: Template namespacing
- Now we *might* be able to get away with putting our templates directly in
- ``polls/templates`` (rather than creating another ``polls`` subdirectory),
- but it would actually be a bad idea. Django will choose the first template
- it finds whose name matches, and if you had a template with the same name
- in a *different* application, Django would be unable to distinguish between
- them. We need to be able to point Django at the right one, and the easiest
- way to ensure this is by *namespacing* them. That is, by putting those
- templates inside *another* directory named for the application itself.
- Put the following code in that template:
- .. snippet:: html+django
- :filename: polls/templates/polls/index.html
- {% if latest_question_list %}
- <ul>
- {% for question in latest_question_list %}
- <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
- {% endfor %}
- </ul>
- {% else %}
- <p>No polls are available.</p>
- {% endif %}
- Now let's update our ``index`` view in ``polls/views.py`` to use the template:
- .. snippet::
- :filename: polls/views.py
- from django.http import HttpResponse
- from django.template import RequestContext, loader
- from .models import Question
- def index(request):
- latest_question_list = Question.objects.order_by('-pub_date')[:5]
- template = loader.get_template('polls/index.html')
- context = RequestContext(request, {
- 'latest_question_list': latest_question_list,
- })
- return HttpResponse(template.render(context))
- That code loads the template called ``polls/index.html`` and passes it a
- context. The context is a dictionary mapping template variable names to Python
- objects.
- Load the page by pointing your browser at "/polls/", and you should see a
- bulleted-list containing the "What's up" question from :doc:`Tutorial 2
- </intro/tutorial02>`. The link points to the question's detail page.
- A shortcut: :func:`~django.shortcuts.render`
- --------------------------------------------
- It's a very common idiom to load a template, fill a context and return an
- :class:`~django.http.HttpResponse` object with the result of the rendered
- template. Django provides a shortcut. Here's the full ``index()`` view,
- rewritten:
- .. snippet::
- :filename: polls/views.py
- from django.shortcuts import render
- from .models import Question
- def index(request):
- latest_question_list = Question.objects.order_by('-pub_date')[:5]
- context = {'latest_question_list': latest_question_list}
- return render(request, 'polls/index.html', context)
- Note that once we've done this in all these views, we no longer need to import
- :mod:`~django.template.loader`, :class:`~django.template.RequestContext` and
- :class:`~django.http.HttpResponse` (you'll want to keep ``HttpResponse`` if you
- still have the stub methods for ``detail``, ``results``, and ``vote``).
- The :func:`~django.shortcuts.render` function takes the request object as its
- first argument, a template name as its second argument and a dictionary as its
- optional third argument. It returns an :class:`~django.http.HttpResponse`
- object of the given template rendered with the given context.
- Raising a 404 error
- ===================
- Now, let's tackle the question detail view -- the page that displays the question text
- for a given poll. Here's the view:
- .. snippet::
- :filename: polls/views.py
- from django.http import Http404
- from django.shortcuts import render
- from .models import Question
- # ...
- def detail(request, question_id):
- try:
- question = Question.objects.get(pk=question_id)
- except Question.DoesNotExist:
- raise Http404("Question does not exist")
- return render(request, 'polls/detail.html', {'question': question})
- The new concept here: The view raises the :exc:`~django.http.Http404` exception
- if a question with the requested ID doesn't exist.
- We'll discuss what you could put in that ``polls/detail.html`` template a bit
- later, but if you'd like to quickly get the above example working, a file
- containing just:
- .. snippet:: html+django
- :filename: polls/templates/polls/detail.html
- {{ question }}
- will get you started for now.
- A shortcut: :func:`~django.shortcuts.get_object_or_404`
- -------------------------------------------------------
- It's a very common idiom to use :meth:`~django.db.models.query.QuerySet.get`
- and raise :exc:`~django.http.Http404` if the object doesn't exist. Django
- provides a shortcut. Here's the ``detail()`` view, rewritten:
- .. snippet::
- :filename: polls/views.py
- from django.shortcuts import get_object_or_404, render
- from .models import Question
- # ...
- def detail(request, question_id):
- question = get_object_or_404(Question, pk=question_id)
- return render(request, 'polls/detail.html', {'question': question})
- The :func:`~django.shortcuts.get_object_or_404` function takes a Django model
- as its first argument and an arbitrary number of keyword arguments, which it
- passes to the :meth:`~django.db.models.query.QuerySet.get` function of the
- model's manager. It raises :exc:`~django.http.Http404` if the object doesn't
- exist.
- .. admonition:: Philosophy
- Why do we use a helper function :func:`~django.shortcuts.get_object_or_404`
- instead of automatically catching the
- :exc:`~django.core.exceptions.ObjectDoesNotExist` exceptions at a higher
- level, or having the model API raise :exc:`~django.http.Http404` instead of
- :exc:`~django.core.exceptions.ObjectDoesNotExist`?
- Because that would couple the model layer to the view layer. One of the
- foremost design goals of Django is to maintain loose coupling. Some
- controlled coupling is introduced in the :mod:`django.shortcuts` module.
- There's also a :func:`~django.shortcuts.get_list_or_404` function, which works
- just as :func:`~django.shortcuts.get_object_or_404` -- except using
- :meth:`~django.db.models.query.QuerySet.filter` instead of
- :meth:`~django.db.models.query.QuerySet.get`. It raises
- :exc:`~django.http.Http404` if the list is empty.
- Use the template system
- =======================
- Back to the ``detail()`` view for our poll application. Given the context
- variable ``question``, here's what the ``polls/detail.html`` template might look
- like:
- .. snippet:: html+django
- :filename: polls/templates/polls/detail.html
- <h1>{{ question.question_text }}</h1>
- <ul>
- {% for choice in question.choice_set.all %}
- <li>{{ choice.choice_text }}</li>
- {% endfor %}
- </ul>
- The template system uses dot-lookup syntax to access variable attributes. In
- the example of ``{{ question.question_text }}``, first Django does a dictionary lookup
- on the object ``question``. Failing that, it tries an attribute lookup -- which
- works, in this case. If attribute lookup had failed, it would've tried a
- list-index lookup.
- Method-calling happens in the :ttag:`{% for %}<for>` loop:
- ``question.choice_set.all`` is interpreted as the Python code
- ``question.choice_set.all()``, which returns an iterable of ``Choice`` objects and is
- suitable for use in the :ttag:`{% for %}<for>` tag.
- See the :doc:`template guide </topics/templates>` for more about templates.
- Removing hardcoded URLs in templates
- ====================================
- Remember, when we wrote the link to a question in the ``polls/index.html``
- template, the link was partially hardcoded like this:
- .. code-block:: html+django
- <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
- The problem with this hardcoded, tightly-coupled approach is that it becomes
- challenging to change URLs on projects with a lot of templates. However, since
- you defined the name argument in the :func:`~django.conf.urls.url` functions in
- the ``polls.urls`` module, you can remove a reliance on specific URL paths
- defined in your url configurations by using the ``{% url %}`` template tag:
- .. code-block:: html+django
- <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
- The way this works is by looking up the URL definition as specified in the
- ``polls.urls`` module. You can see exactly where the URL name of 'detail' is
- defined below::
- ...
- # the 'name' value as called by the {% url %} template tag
- url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
- ...
- If you want to change the URL of the polls detail view to something else,
- perhaps to something like ``polls/specifics/12/`` instead of doing it in the
- template (or templates) you would change it in ``polls/urls.py``::
- ...
- # added the word 'specifics'
- url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
- ...
- Namespacing URL names
- ======================
- The tutorial project has just one app, ``polls``. In real Django projects,
- there might be five, ten, twenty apps or more. How does Django differentiate
- the URL names between them? For example, the ``polls`` app has a ``detail``
- view, and so might an app on the same project that is for a blog. How does one
- make it so that Django knows which app view to create for a url when using the
- ``{% url %}`` template tag?
- The answer is to add namespaces to your URLconf. In the ``polls/urls.py``
- file, go ahead and add an ``app_name`` to set the application namespace:
- .. snippet::
- :filename: polls/urls.py
- from django.conf.urls import url
- app_name = 'polls'
- urlpatterns = [
- url(r'^$', views.index, name='index'),
- url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
- url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
- url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
- ]
- Now change your ``polls/index.html`` template from:
- .. snippet:: html+django
- :filename: polls/templates/polls/index.html
- <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
- to point at the namespaced detail view:
- .. snippet:: html+django
- :filename: polls/templates/polls/index.html
- <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
- When you're comfortable with writing views, read :doc:`part 4 of this tutorial
- </intro/tutorial04>` to learn about simple form processing and generic views.
|