123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565 |
- =====================================
- 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."
- Philosophy
- ==========
- 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:
- * Poll "index" page -- displays the latest few polls.
- * Poll "detail" page -- displays a poll question, with no results but
- with a form to vote.
- * Poll "results" page -- displays results for a particular poll.
- * Vote action -- handles voting for a particular choice in a particular
- poll.
- In Django, each view is represented by a simple Python function.
- Write your first view
- =====================
- Let's write the first view. Open the file ``polls/views.py``
- and put the following Python code in it::
- from django.http import HttpResponse
- def index(request):
- return HttpResponse("Hello, world. You're at the poll index.")
- This is the simplest view possible in Django. Now we have a problem, how does
- this view get called? For that we need to map it to a URL, in Django this is
- done in a configuration file called a URLconf.
- .. admonition:: What is a URLconf?
- In Django, web pages and other content are delivered by views and
- determining which view is called is done by Python modules informally
- titled 'URLconfs'. These modules are pure Python code and are a simple
- mapping between URL patterns (as simple regular expressions) to Python
- callback functions (your views). This tutorial provides basic instruction
- in their use, and you can refer to :mod:`django.core.urlresolvers` for
- more information.
- To create a URLconf in the polls directory, create a file called ``urls.py``.
- Your app directory should now look like::
- polls/
- __init__.py
- admin.py
- models.py
- tests.py
- urls.py
- views.py
- In the ``polls/urls.py`` file include the following code::
- from django.conf.urls import patterns, url
- from polls import views
- urlpatterns = patterns('',
- url(r'^$', views.index, name='index')
- )
- The next step is to point the root URLconf at the ``polls.urls`` module. In
- ``mysite/urls.py`` insert an :func:`~django.conf.urls.include`, leaving you
- with::
- from django.conf.urls import patterns, include, url
- from django.contrib import admin
- admin.autodiscover()
- urlpatterns = patterns('',
- url(r'^polls/', include('polls.urls')),
- url(r'^admin/', include(admin.site.urls)),
- )
- You have now wired an `index` view into the URLconf. Go to
- http://localhost:8000/polls/ in your browser, and you should see the text
- "*Hello, world. You're at the poll index.*", which you defined in the
- ``index`` view.
- The :func:`~django.conf.urls.url` function is passed four arguments, two
- required: ``regex`` and ``view``, and two optional: ``kwargs``, and ``name``.
- At this point, it's worth reviewing what these arguments are for.
- :func:`~django.conf.urls.url` argument: regex
- ---------------------------------------------
- The term `regex` is a commonly used short form meaning `regular expression`,
- which is a syntax for matching patterns in strings, or in this case, url
- patterns. Django starts at the first regular expression and makes its way down
- the list, comparing the requested URL against each regular expression until it
- finds one that matches.
- Note that these regular expressions do not search GET and POST parameters, or
- the domain name. For example, in a request to
- ``http://www.example.com/myapp/``, the URLconf will look for ``myapp/``. In a
- request to ``http://www.example.com/myapp/?page=3``, the URLconf will also
- look for ``myapp/``.
- If you need help with regular expressions, see `Wikipedia's entry`_ and the
- documentation of the :mod:`re` module. Also, the O'Reilly book "Mastering
- Regular Expressions" by Jeffrey Friedl is fantastic. In practice, however,
- you don't need to be an expert on regular expressions, as you really only need
- to know how to capture simple patterns. In fact, complex regexes can have poor
- lookup performance, so you probably shouldn't rely on the full power of regexes.
- Finally, a performance note: these regular expressions are compiled the first
- time the URLconf module is loaded. They're super fast (as long as the lookups
- aren't too complex as noted above).
- .. _Wikipedia's entry: http://en.wikipedia.org/wiki/Regular_expression
- :func:`~django.conf.urls.url` argument: view
- --------------------------------------------
- When Django finds a regular expression match, Django calls the specified view
- function, with an :class:`~django.http.HttpRequest` object as the first
- argument and any “captured” values from the regular expression as other
- arguments. If the regex uses simple captures, values are passed as positional
- arguments; if it uses named captures, values are passed as keyword arguments.
- We'll give an example of this in a bit.
- :func:`~django.conf.urls.url` argument: kwargs
- ----------------------------------------------
- Arbitrary keyword arguments can be passed in a dictionary to the target view. We
- aren't going to use this feature of Django in the tutorial.
- :func:`~django.conf.urls.url` argument: name
- ---------------------------------------------
- Naming your URL lets you refer to it unambiguously from elsewhere in Django
- especially templates. This powerful feature allows you to make global changes
- to the url patterns of your project while only touching a single file.
- 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::
- def detail(request, poll_id):
- return HttpResponse("You're looking at poll %s." % poll_id)
- def results(request, poll_id):
- return HttpResponse("You're looking at the results of poll %s." % poll_id)
- def vote(request, poll_id):
- return HttpResponse("You're voting on poll %s." % poll_id)
- Wire these news views into the ``polls.urls`` module by adding the following
- :func:`~django.conf.urls.url` calls::
- from django.conf.urls import patterns, url
- from polls import views
- urlpatterns = patterns('',
- # ex: /polls/
- url(r'^$', views.index, name='index'),
- # ex: /polls/5/
- url(r'^(?P<poll_id>\d+)/$', views.detail, name='detail'),
- # ex: /polls/5/results/
- url(r'^(?P<poll_id>\d+)/results/$', views.results, name='results'),
- # ex: /polls/5/vote/
- url(r'^(?P<poll_id>\d+)/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<poll_id>\d+)/$'`` resulting in a
- call to the ``detail()`` view like so::
- detail(request=<HttpRequest object>, poll_id='34')
- The ``poll_id='34'`` part comes from ``(?P<poll_id>\d+)``. Using parentheses
- around a pattern "captures" the text matched by that pattern and sends it as an
- argument to the view function; ``?P<poll_id>`` defines the name that will
- be used to identify the matched pattern; and ``\d+`` 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::
- (r'^polls/latest\.html$', 'polls.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 1 </intro/tutorial01>`. Here's one stab at the ``index()``
- view, which displays the latest 5 poll questions in the system, separated by
- commas, according to publication date::
- from django.http import HttpResponse
- from polls.models import Poll
- def index(request):
- latest_poll_list = Poll.objects.order_by('-pub_date')[:5]
- output = ', '.join([p.question for p in latest_poll_list])
- return HttpResponse(output)
- 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.
- First, create a directory ``polls`` in your template directory you specified
- in setting:`TEMPLATE_DIRS`. Within that, create a file called ``index.html``.
- Put the following code in that template:
- .. code-block:: html+django
- {% if latest_poll_list %}
- <ul>
- {% for poll in latest_poll_list %}
- <li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
- {% endfor %}
- </ul>
- {% else %}
- <p>No polls are available.</p>
- {% endif %}
- Now let's use that html template in our index view::
- from django.http import HttpResponse
- from django.template import Context, loader
- from polls.models import Poll
- def index(request):
- latest_poll_list = Poll.objects.order_by('-pub_date')[:5]
- template = loader.get_template('polls/index.html')
- context = Context({
- 'latest_poll_list': latest_poll_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 in your Web browser, and you should see a bulleted-list
- containing the "What's up" poll from Tutorial 1. The link points to the poll's
- detail page.
- .. admonition:: Organizing Templates
- Rather than one big templates directory, you can also store templates
- within each app. We'll discuss this in more detail in the :doc:`reusable
- apps tutorial</intro/reusable-apps>`.
- 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::
- from django.shortcuts import render
- from polls.models import Poll
- def index(request):
- latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
- context = {'latest_poll_list': latest_poll_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.Context` 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 poll detail view -- the page that displays the question
- for a given poll. Here's the view::
- from django.http import Http404
- # ...
- def detail(request, poll_id):
- try:
- poll = Poll.objects.get(pk=poll_id)
- except Poll.DoesNotExist:
- raise Http404
- return render(request, 'polls/detail.html', {'poll': poll})
- The new concept here: The view raises the :exc:`~django.http.Http404` exception
- if a poll 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, just::
- {{ poll }}
- 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::
- from django.shortcuts import render, get_object_or_404
- # ...
- def detail(request, poll_id):
- poll = get_object_or_404(Poll, pk=poll_id)
- return render(request, 'polls/detail.html', {'poll': poll})
- 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.
- Write a 404 (page not found) view
- =================================
- When you raise :exc:`~django.http.Http404` from within a view, Django
- will load a special view devoted to handling 404 errors. It finds it
- by looking for the variable ``handler404`` in your root URLconf (and
- only in your root URLconf; setting ``handler404`` anywhere else will
- have no effect), which is a string in Python dotted syntax -- the same
- format the normal URLconf callbacks use. A 404 view itself has nothing
- special: It's just a normal view.
- You normally won't have to bother with writing 404 views. If you don't set
- ``handler404``, the built-in view :func:`django.views.defaults.page_not_found`
- is used by default. Optionally, you can create a ``404.html`` template
- in the root of your template directory. The default 404 view will then use that
- template for all 404 errors when :setting:`DEBUG` is set to ``False`` (in your
- settings module). If you do create the template, add at least some dummy
- content like "Page not found".
- A couple more things to note about 404 views:
- * If :setting:`DEBUG` is set to ``True`` (in your settings module) then your
- 404 view will never be used (and thus the ``404.html`` template will never
- be rendered) because the traceback will be displayed instead.
- * The 404 view is also called if Django doesn't find a match after checking
- every regular expression in the URLconf.
- Write a 500 (server error) view
- ===============================
- Similarly, your root URLconf may define a ``handler500``, which points
- to a view to call in case of server errors. Server errors happen when
- you have runtime errors in view code.
- Likewise, you should create a ``500.html`` template at the root of your
- template directory and add some content like "Something went wrong".
- Use the template system
- =======================
- Back to the ``detail()`` view for our poll application. Given the context
- variable ``poll``, here's what the ``polls/detail.html`` template might look
- like:
- .. code-block:: html+django
- <h1>{{ poll.question }}</h1>
- <ul>
- {% for choice in poll.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 ``{{ poll.question }}``, first Django does a dictionary lookup
- on the object ``poll``. 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:
- ``poll.choice_set.all`` is interpreted as the Python code
- ``poll.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 poll in the ``polls/index.html``
- template, the link was partially hardcoded like this:
- .. code-block:: html+django
- <li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</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' poll.id %}">{{ poll.question }}</a></li>
- .. note::
- If ``{% url 'detail' poll.id %}`` (with quotes) doesn't work, but
- ``{% url detail poll.id %}`` (without quotes) does, that means you're
- using a version of Django < 1.5. In this case, add the following
- declaration at the top of your template:
- .. code-block:: html+django
- {% load url from future %}
- 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<poll_id>\d+)/$', 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<poll_id>\d+)/$', 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 root URLconf. In the
- ``mysite/urls.py`` file, go ahead and change it to include namespacing::
- from django.conf.urls import patterns, include, url
- from django.contrib import admin
- admin.autodiscover()
- urlpatterns = patterns('',
- url(r'^polls/', include('polls.urls', namespace="polls")),
- url(r'^admin/', include(admin.site.urls)),
- )
- Now change your ``polls/index.html`` template from:
- .. code-block:: html+django
- <li><a href="{% url 'detail' poll.id %}">{{ poll.question }}</a></li>
- to point at the namespaced detail view:
- .. code-block:: html+django
- <li><a href="{% url 'polls:detail' poll.id %}">{{ poll.question }}</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.
|