1
0
mirror of https://github.com/django/django.git synced 2025-01-18 22:33:44 +00:00

Fixed #20910 -- Added a "snippet" sphinx directive to allow prefixing a filename.

Thanks Marc Tamlyn for the suggestion.
This commit is contained in:
M Nasimul Haque 2013-09-23 23:23:47 +01:00 committed by Tim Graham
parent e077224f4a
commit d07d6ae116
10 changed files with 403 additions and 124 deletions

View File

@ -282,6 +282,7 @@ answer newbie questions, and generally made Django that much better:
Scot Hacker <shacker@birdhouse.org> Scot Hacker <shacker@birdhouse.org>
dAniel hAhler dAniel hAhler
hambaloney hambaloney
Nasimul Haque <nasim.haque@gmail.com>
Will Hardy <django@willhardy.com.au> Will Hardy <django@willhardy.com.au>
Brian Harring <ferringb@gmail.com> Brian Harring <ferringb@gmail.com>
Brant Harris Brant Harris

View File

@ -5,11 +5,15 @@ import json
import os import os
import re import re
from docutils import nodes
from docutils.parsers.rst import directives
from sphinx import addnodes, __version__ as sphinx_ver from sphinx import addnodes, __version__ as sphinx_ver
from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.writers.html import SmartyPantsHTMLTranslator from sphinx.writers.html import SmartyPantsHTMLTranslator
from sphinx.util.console import bold from sphinx.util.console import bold
from sphinx.util.compat import Directive from sphinx.util.compat import Directive
from sphinx.util.nodes import set_source_info
# RE for option descriptions without a '--' prefix # RE for option descriptions without a '--' prefix
simple_option_desc_re = re.compile( simple_option_desc_re = re.compile(
@ -53,6 +57,136 @@ def setup(app):
app.add_directive('versionchanged', VersionDirective) app.add_directive('versionchanged', VersionDirective)
app.add_builder(DjangoStandaloneHTMLBuilder) app.add_builder(DjangoStandaloneHTMLBuilder)
# register the snippet directive
app.add_directive('snippet', SnippetWithFilename)
# register a node for snippet directive so that the xml parser
# knows how to handle the enter/exit parsing event
app.add_node(snippet_with_filename,
html=(visit_snippet, depart_snippet_literal),
latex=(visit_snippet_latex, depart_snippet_latex),
man=(visit_snippet_literal, depart_snippet_literal),
text=(visit_snippet_literal, depart_snippet_literal),
texinfo=(visit_snippet_literal, depart_snippet_literal))
class snippet_with_filename(nodes.literal_block):
"""
Subclass the literal_block to override the visit/depart event handlers
"""
pass
def visit_snippet_literal(self, node):
"""
default literal block handler
"""
self.visit_literal_block(node)
def depart_snippet_literal(self, node):
"""
default literal block handler
"""
self.depart_literal_block(node)
def visit_snippet(self, node):
"""
HTML document generator visit handler
"""
lang = self.highlightlang
linenos = node.rawsource.count('\n') >= self.highlightlinenothreshold - 1
fname = node['filename']
highlight_args = node.get('highlight_args', {})
if node.has_key('language'):
# code-block directives
lang = node['language']
highlight_args['force'] = True
if node.has_key('linenos'):
linenos = node['linenos']
def warner(msg):
self.builder.warn(msg, (self.builder.current_docname, node.line))
highlighted = self.highlighter.highlight_block(node.rawsource, lang,
warn=warner,
linenos=linenos,
**highlight_args)
starttag = self.starttag(node, 'div', suffix='',
CLASS='highlight-%s' % lang)
self.body.append(starttag)
self.body.append('<div class="snippet-filename">%s</div>\n''' % (fname,))
self.body.append(highlighted)
self.body.append('</div>\n')
raise nodes.SkipNode
def visit_snippet_latex(self, node):
"""
Latex document generator visit handler
"""
self.verbatim = ''
def depart_snippet_latex(self, node):
"""
Latex document generator depart handler.
"""
code = self.verbatim.rstrip('\n')
lang = self.hlsettingstack[-1][0]
linenos = code.count('\n') >= self.hlsettingstack[-1][1] - 1
fname = node['filename']
highlight_args = node.get('highlight_args', {})
if 'language' in node:
# code-block directives
lang = node['language']
highlight_args['force'] = True
if 'linenos' in node:
linenos = node['linenos']
def warner(msg):
self.builder.warn(msg, (self.curfilestack[-1], node.line))
hlcode = self.highlighter.highlight_block(code, lang, warn=warner,
linenos=linenos,
**highlight_args)
self.body.append('\n{\\colorbox[rgb]{0.9,0.9,0.9}'
'{\\makebox[\\textwidth][l]'
'{\\small\\texttt{%s}}}}\n' % (fname,))
if self.table:
hlcode = hlcode.replace('\\begin{Verbatim}',
'\\begin{OriginalVerbatim}')
self.table.has_problematic = True
self.table.has_verbatim = True
hlcode = hlcode.rstrip()[:-14] # strip \end{Verbatim}
hlcode = hlcode.rstrip() + '\n'
self.body.append('\n' + hlcode + '\\end{%sVerbatim}\n' %
(self.table and 'Original' or ''))
self.verbatim = None
class SnippetWithFilename(Directive):
"""
The 'snippet' directive that allows to add the filename (optional)
of a code snippet in the document. This is modeled after CodeBlock.
"""
has_content = True
optional_arguments = 1
option_spec = {'filename': directives.unchanged_required}
def run(self):
code = u'\n'.join(self.content)
literal = snippet_with_filename(code, code)
if self.arguments:
literal['language'] = self.arguments[0]
literal['filename'] = self.options['filename']
set_source_info(self, literal)
return [literal]
class VersionDirective(Directive): class VersionDirective(Directive):
has_content = True has_content = True

View File

@ -100,6 +100,9 @@ pre { font-size:small; background:#E0FFB8; border:1px solid #94da3a; border-widt
dt .literal, table .literal { background:none; } dt .literal, table .literal { background:none; }
#bd a.reference { text-decoration: none; } #bd a.reference { text-decoration: none; }
#bd a.reference tt.literal { border-bottom: 1px #234f32 dotted; } #bd a.reference tt.literal { border-bottom: 1px #234f32 dotted; }
div.snippet-filename { color: white; background-color: #234F32; margin: 0; padding: 2px 5px; width: 100%; font-family: monospace; font-size: small; line-height: 1.3em; }
div.snippet-filename + div.highlight > pre { margin-top: 0; }
div.snippet-filename + pre { margin-top: 0; }
/* Restore colors of pygments hyperlinked code */ /* Restore colors of pygments hyperlinked code */
#bd .highlight .k a:link, #bd .highlight .k a:visited { color: #000000; text-decoration: none; border-bottom: 1px dotted #000000; } #bd .highlight .k a:link, #bd .highlight .k a:visited { color: #000000; text-decoration: none; border-bottom: 1px dotted #000000; }

View File

@ -120,112 +120,122 @@ this. For a small app like polls, this process isn't too difficult.
1. First, create a parent directory for ``polls``, outside of your Django 1. First, create a parent directory for ``polls``, outside of your Django
project. Call this directory ``django-polls``. project. Call this directory ``django-polls``.
.. admonition:: Choosing a name for your app .. admonition:: Choosing a name for your app
When choosing a name for your package, check resources like PyPI to avoid When choosing a name for your package, check resources like PyPI to avoid
naming conflicts with existing packages. It's often useful to prepend naming conflicts with existing packages. It's often useful to prepend
``django-`` to your module name when creating a package to distribute. ``django-`` to your module name when creating a package to distribute.
This helps others looking for Django apps identify your app as Django This helps others looking for Django apps identify your app as Django
specific. specific.
2. Move the ``polls`` directory into the ``django-polls`` directory. 2. Move the ``polls`` directory into the ``django-polls`` directory.
3. Create a file ``django-polls/README.rst`` with the following contents:: 3. Create a file ``django-polls/README.rst`` with the following contents:
===== .. snippet::
Polls :filename: django-polls/README.rst
=====
Polls is a simple Django app to conduct Web-based polls. For each =====
question, visitors can choose between a fixed number of answers. Polls
=====
Detailed documentation is in the "docs" directory. Polls is a simple Django app to conduct Web-based polls. For each
question, visitors can choose between a fixed number of answers.
Quick start Detailed documentation is in the "docs" directory.
-----------
1. Add "polls" to your INSTALLED_APPS setting like this:: Quick start
-----------
INSTALLED_APPS = ( 1. Add "polls" to your INSTALLED_APPS setting like this::
...
'polls',
)
2. Include the polls URLconf in your project urls.py like this:: INSTALLED_APPS = (
...
'polls',
)
url(r'^polls/', include('polls.urls')), 2. Include the polls URLconf in your project urls.py like this::
3. Run `python manage.py migrate` to create the polls models. url(r'^polls/', include('polls.urls')),
4. Start the development server and visit http://127.0.0.1:8000/admin/ 3. Run `python manage.py migrate` to create the polls models.
to create a poll (you'll need the Admin app enabled).
5. Visit http://127.0.0.1:8000/polls/ to participate in the poll. 4. Start the development server and visit http://127.0.0.1:8000/admin/
to create a poll (you'll need the Admin app enabled).
5. Visit http://127.0.0.1:8000/polls/ to participate in the poll.
4. Create a ``django-polls/LICENSE`` file. Choosing a license is beyond the 4. Create a ``django-polls/LICENSE`` file. Choosing a license is beyond the
scope of this tutorial, but suffice it to say that code released publicly scope of this tutorial, but suffice it to say that code released publicly
without a license is *useless*. Django and many Django-compatible apps are without a license is *useless*. Django and many Django-compatible apps are
distributed under the BSD license; however, you're free to pick your own distributed under the BSD license; however, you're free to pick your own
license. Just be aware that your licensing choice will affect who is able license. Just be aware that your licensing choice will affect who is able
to use your code. to use your code.
5. Next we'll create a ``setup.py`` file which provides details about how to 5. Next we'll create a ``setup.py`` file which provides details about how to
build and install the app. A full explanation of this file is beyond the build and install the app. A full explanation of this file is beyond the
scope of this tutorial, but the `distribute docs scope of this tutorial, but the `distribute docs
<http://packages.python.org/distribute/setuptools.html>`_ have a good explanation. <http://packages.python.org/distribute/setuptools.html>`_ have a good
Create a file ``django-polls/setup.py`` with the following contents:: explanation. Create a file ``django-polls/setup.py`` with the following
contents:
import os .. snippet::
from setuptools import setup :filename: django-polls/setup.py
README = open(os.path.join(os.path.dirname(__file__), 'README.rst')).read() import os
from setuptools import setup
# allow setup.py to be run from any path README = open(os.path.join(os.path.dirname(__file__), 'README.rst')).read()
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
setup( # allow setup.py to be run from any path
name='django-polls', os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
version='0.1',
packages=['polls'],
include_package_data=True,
license='BSD License', # example license
description='A simple Django app to conduct Web-based polls.',
long_description=README,
url='http://www.example.com/',
author='Your Name',
author_email='yourname@example.com',
classifiers=[
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License', # example license
'Operating System :: OS Independent',
'Programming Language :: Python',
# replace these appropriately if you are using Python 3
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
],
)
.. admonition:: I thought you said we were going to use ``distribute``? setup(
name='django-polls',
version='0.1',
packages=['polls'],
include_package_data=True,
license='BSD License', # example license
description='A simple Django app to conduct Web-based polls.',
long_description=README,
url='http://www.example.com/',
author='Your Name',
author_email='yourname@example.com',
classifiers=[
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License', # example license
'Operating System :: OS Independent',
'Programming Language :: Python',
# replace these appropriately if you are using Python 3
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
],
)
Distribute is a drop-in replacement for ``setuptools``. Even though we .. admonition:: I thought you said we were going to use ``distribute``?
appear to import from ``setuptools``, since we have ``distribute``
installed, it will override the import. Distribute is a drop-in replacement for ``setuptools``. Even though we
appear to import from ``setuptools``, since we have ``distribute``
installed, it will override the import.
6. Only Python modules and packages are included in the package by default. To 6. Only Python modules and packages are included in the package by default. To
include additional files, we'll need to create a ``MANIFEST.in`` file. The include additional files, we'll need to create a ``MANIFEST.in`` file. The
distribute docs referred to in the previous step discuss this file in more distribute docs referred to in the previous step discuss this file in more
details. To include the templates, the ``README.rst`` and our ``LICENSE`` details. To include the templates, the ``README.rst`` and our ``LICENSE``
file, create a file ``django-polls/MANIFEST.in`` with the following file, create a file ``django-polls/MANIFEST.in`` with the following
contents:: contents:
include LICENSE .. snippet::
include README.rst :filename: django-polls/MANIFEST.in
recursive-include polls/static *
recursive-include polls/templates * include LICENSE
include README.rst
recursive-include polls/static *
recursive-include polls/templates *
7. It's optional, but recommended, to include detailed documentation with your 7. It's optional, but recommended, to include detailed documentation with your
app. Create an empty directory ``django-polls/docs`` for future app. Create an empty directory ``django-polls/docs`` for future

View File

@ -344,7 +344,10 @@ the text of the choice and a vote tally. Each ``Choice`` is associated with a
``Question``. ``Question``.
These concepts are represented by simple Python classes. Edit the These concepts are represented by simple Python classes. Edit the
:file:`polls/models.py` file so it looks like this:: :file:`polls/models.py` file so it looks like this:
.. snippet::
:filename: polls/models.py
from django.db import models from django.db import models
@ -415,7 +418,10 @@ But first we need to tell our project that the ``polls`` app is installed.
Edit the :file:`mysite/settings.py` file again, and change the Edit the :file:`mysite/settings.py` file again, and change the
:setting:`INSTALLED_APPS` setting to include the string ``'polls'``. So it'll :setting:`INSTALLED_APPS` setting to include the string ``'polls'``. So it'll
look like this:: look like this:
.. snippet::
:filename: mysite/settings.py
INSTALLED_APPS = ( INSTALLED_APPS = (
'django.contrib.admin', 'django.contrib.admin',
@ -589,7 +595,10 @@ of this object. Let's fix that by editing the ``Question`` model (in the
``polls/models.py`` file) and adding a ``polls/models.py`` file) and adding a
:meth:`~django.db.models.Model.__unicode__` method to both ``Question`` and :meth:`~django.db.models.Model.__unicode__` method to both ``Question`` and
``Choice``. On Python 3, simply replace ``__unicode__`` by ``__str__`` in the ``Choice``. On Python 3, simply replace ``__unicode__`` by ``__str__`` in the
following example:: following example:
.. snippet::
:filename: polls/models.py
from django.db import models from django.db import models
@ -633,7 +642,10 @@ admin.
luck, things should Just Work for you. luck, things should Just Work for you.
Note these are normal Python methods. Let's add a custom method, just for Note these are normal Python methods. Let's add a custom method, just for
demonstration:: demonstration:
.. snippet::
:filename: polls/models.py
import datetime import datetime
from django.utils import timezone from django.utils import timezone

View File

@ -79,7 +79,10 @@ But where's our poll app? It's not displayed on the admin index page.
Just one thing to do: we need to tell the admin that ``Question`` Just one thing to do: we need to tell the admin that ``Question``
objects have an admin interface. To do this, open the :file:`polls/admin.py` objects have an admin interface. To do this, open the :file:`polls/admin.py`
file, and edit it to look like this:: file, and edit it to look like this:
.. snippet::
:filename: polls/admin.py
from django.contrib import admin from django.contrib import admin
from polls.models import Question from polls.models import Question
@ -156,7 +159,10 @@ to customize how the admin form looks and works. You'll do this by telling
Django the options you want when you register the object. Django the options you want when you register the object.
Let's see how this works by re-ordering the fields on the edit form. Replace Let's see how this works by re-ordering the fields on the edit form. Replace
the ``admin.site.register(Question)`` line with:: the ``admin.site.register(Question)`` line with:
.. snippet::
:filename: polls/admin.py
from django.contrib import admin from django.contrib import admin
from polls.models import Question from polls.models import Question
@ -181,7 +187,10 @@ This isn't impressive with only two fields, but for admin forms with dozens
of fields, choosing an intuitive order is an important usability detail. of fields, choosing an intuitive order is an important usability detail.
And speaking of forms with dozens of fields, you might want to split the form And speaking of forms with dozens of fields, you might want to split the form
up into fieldsets:: up into fieldsets:
.. snippet::
:filename: polls/admin.py
from django.contrib import admin from django.contrib import admin
from polls.models import Question from polls.models import Question
@ -204,7 +213,10 @@ Here's what our form looks like now:
You can assign arbitrary HTML classes to each fieldset. Django provides a You can assign arbitrary HTML classes to each fieldset. Django provides a
``"collapse"`` class that displays a particular fieldset initially collapsed. ``"collapse"`` class that displays a particular fieldset initially collapsed.
This is useful when you have a long form that contains a number of fields that This is useful when you have a long form that contains a number of fields that
aren't commonly used:: aren't commonly used:
.. snippet::
:filename: polls/admin.py
from django.contrib import admin from django.contrib import admin
from polls.models import Question from polls.models import Question
@ -228,7 +240,10 @@ the admin page doesn't display choices.
Yet. Yet.
There are two ways to solve this problem. The first is to register ``Choice`` There are two ways to solve this problem. The first is to register ``Choice``
with the admin just as we did with ``Question``. That's easy:: with the admin just as we did with ``Question``. That's easy:
.. snippet::
:filename: polls/admin.py
from django.contrib import admin from django.contrib import admin
from polls.models import Choice from polls.models import Choice
@ -258,7 +273,10 @@ It'd be better if you could add a bunch of Choices directly when you create the
``Question`` object. Let's make that happen. ``Question`` object. Let's make that happen.
Remove the ``register()`` call for the ``Choice`` model. Then, edit the ``Question`` Remove the ``register()`` call for the ``Choice`` model. Then, edit the ``Question``
registration code to read:: registration code to read:
.. snippet::
:filename: polls/admin.py
from django.contrib import admin from django.contrib import admin
from polls.models import Choice, Question from polls.models import Choice, Question
@ -302,7 +320,10 @@ that you can't remove the original three slots. This image shows an added slot:
One small problem, though. It takes a lot of screen space to display all the One small problem, though. It takes a lot of screen space to display all the
fields for entering related ``Choice`` objects. For that reason, Django offers a fields for entering related ``Choice`` objects. For that reason, Django offers a
tabular way of displaying inline related objects; you just need to change tabular way of displaying inline related objects; you just need to change
the ``ChoiceInline`` declaration to read:: the ``ChoiceInline`` declaration to read:
.. snippet::
:filename: polls/admin.py
class ChoiceInline(admin.TabularInline): class ChoiceInline(admin.TabularInline):
#... #...
@ -330,14 +351,20 @@ Here's what it looks like at this point:
By default, Django displays the ``str()`` of each object. But sometimes it'd be By default, Django displays the ``str()`` of each object. But sometimes it'd be
more helpful if we could display individual fields. To do that, use the more helpful if we could display individual fields. To do that, use the
``list_display`` admin option, which is a tuple of field names to display, as ``list_display`` admin option, which is a tuple of field names to display, as
columns, on the change list page for the object:: columns, on the change list page for the object:
.. snippet::
:filename: polls/admin.py
class QuestionAdmin(admin.ModelAdmin): class QuestionAdmin(admin.ModelAdmin):
# ... # ...
list_display = ('question_text', 'pub_date') list_display = ('question_text', 'pub_date')
Just for good measure, let's also include the ``was_published_recently`` custom Just for good measure, let's also include the ``was_published_recently`` custom
method from Tutorial 1:: method from Tutorial 1:
.. snippet::
:filename: polls/admin.py
class QuestionAdmin(admin.ModelAdmin): class QuestionAdmin(admin.ModelAdmin):
# ... # ...
@ -356,7 +383,10 @@ underscores replaced with spaces), and that each line contains the string
representation of the output. representation of the output.
You can improve that by giving that method (in :file:`polls/models.py`) a few You can improve that by giving that method (in :file:`polls/models.py`) a few
attributes, as follows:: attributes, as follows:
.. snippet::
:filename: polls/admin.py
class Question(models.Model): class Question(models.Model):
# ... # ...
@ -417,7 +447,10 @@ whatever user your server runs.) However, keeping your templates within the
project is a good convention to follow. project is a good convention to follow.
Open your settings file (:file:`mysite/settings.py`, remember) and add a Open your settings file (:file:`mysite/settings.py`, remember) and add a
:setting:`TEMPLATE_DIRS` setting:: :setting:`TEMPLATE_DIRS` setting:
.. snippet::
:filename: mysite/settings.py
TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')] TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')]

View File

@ -84,7 +84,10 @@ Your app directory should now look like::
urls.py urls.py
views.py views.py
In the ``polls/urls.py`` file include the following code:: In the ``polls/urls.py`` file include the following code:
.. snippet::
:filename: polls/urls.py
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
@ -96,7 +99,10 @@ In the ``polls/urls.py`` file include the following code::
The next step is to point the root URLconf at the ``polls.urls`` module. In 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 ``mysite/urls.py`` insert an :func:`~django.conf.urls.include`, leaving you
with:: with:
.. snippet::
:filename: mysite/urls.py
from django.conf.urls import patterns, include, url from django.conf.urls import patterns, include, url
@ -172,7 +178,10 @@ Writing more views
================== ==================
Now let's add a few more views to ``polls/views.py``. These views are Now let's add a few more views to ``polls/views.py``. These views are
slightly different, because they take an argument:: slightly different, because they take an argument:
.. snippet::
:filename: polls/views.py
def detail(request, question_id): def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id) return HttpResponse("You're looking at question %s." % question_id)
@ -185,7 +194,10 @@ slightly different, because they take an argument::
return HttpResponse("You're voting on question %s." % 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 Wire these new views into the ``polls.urls`` module by adding the following
:func:`~django.conf.urls.url` calls:: :func:`~django.conf.urls.url` calls:
.. snippet::
:filename: polls/urls.py
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
@ -269,7 +281,10 @@ 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 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()`` 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 view, which displays the latest 5 poll questions in the system, separated by
commas, according to publication date:: commas, according to publication date:
.. snippet::
:filename: polls/views.py
from django.http import HttpResponse from django.http import HttpResponse
@ -327,7 +342,8 @@ Django simply as ``polls/index.html``.
Put the following code in that template: Put the following code in that template:
.. code-block:: html+django .. snippet:: html+django
:filename: polls/templates/polls/index.html
{% if latest_question_list %} {% if latest_question_list %}
<ul> <ul>
@ -339,7 +355,10 @@ Put the following code in that template:
<p>No polls are available.</p> <p>No polls are available.</p>
{% endif %} {% endif %}
Now let's update our ``index`` view in ``polls/views.py`` to use the template:: 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.http import HttpResponse
from django.template import RequestContext, loader from django.template import RequestContext, loader
@ -369,7 +388,10 @@ A shortcut: :func:`~django.shortcuts.render`
It's a very common idiom to load a template, fill a context and return an 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 :class:`~django.http.HttpResponse` object with the result of the rendered
template. Django provides a shortcut. Here's the full ``index()`` view, template. Django provides a shortcut. Here's the full ``index()`` view,
rewritten:: rewritten:
.. snippet::
:filename: polls/views.py
from django.shortcuts import render from django.shortcuts import render
@ -395,7 +417,10 @@ Raising a 404 error
=================== ===================
Now, let's tackle the question detail view -- the page that displays the question text Now, let's tackle the question detail view -- the page that displays the question text
for a given poll. Here's the view:: for a given poll. Here's the view:
.. snippet::
:filename: polls/views.py
from django.http import Http404 from django.http import Http404
from django.shortcuts import render from django.shortcuts import render
@ -414,7 +439,10 @@ 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 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 later, but if you'd like to quickly get the above example working, a file
containing just:: containing just:
.. snippet:: html+django
:filename: polls/templates/polls/detail.html
{{ question }} {{ question }}
@ -425,7 +453,10 @@ A shortcut: :func:`~django.shortcuts.get_object_or_404`
It's a very common idiom to use :meth:`~django.db.models.query.QuerySet.get` 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 and raise :exc:`~django.http.Http404` if the object doesn't exist. Django
provides a shortcut. Here's the ``detail()`` view, rewritten:: provides a shortcut. Here's the ``detail()`` view, rewritten:
.. snippet::
:filename: polls/views.py
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
@ -466,7 +497,8 @@ Back to the ``detail()`` view for our poll application. Given the context
variable ``question``, here's what the ``polls/detail.html`` template might look variable ``question``, here's what the ``polls/detail.html`` template might look
like: like:
.. code-block:: html+django .. snippet:: html+django
:filename: polls/templates/polls/detail.html
<h1>{{ question.question_text }}</h1> <h1>{{ question.question_text }}</h1>
<ul> <ul>
@ -549,7 +581,10 @@ make it so that Django knows which app view to create for a url when using the
The answer is to add namespaces to your root URLconf. In the ``mysite/urls.py`` The answer is to add namespaces to your root URLconf. In the ``mysite/urls.py``
file (the project's ``urls.py``, not the application's), go ahead and change file (the project's ``urls.py``, not the application's), go ahead and change
it to include namespacing:: it to include namespacing:
.. snippet::
:filename: mysite/urls.py
from django.conf.urls import patterns, include, url from django.conf.urls import patterns, include, url
@ -563,13 +598,15 @@ it to include namespacing::
Now change your ``polls/index.html`` template from: Now change your ``polls/index.html`` template from:
.. code-block:: html+django .. snippet:: html+django
:filename: polls/templates/polls/index.html
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li> <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
to point at the namespaced detail view: to point at the namespaced detail view:
.. code-block:: html+django .. snippet:: html+django
:filename: polls/templates/polls/index.html
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li> <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

View File

@ -12,7 +12,8 @@ Write a simple form
Let's update our poll detail template ("polls/detail.html") from the last Let's update our poll detail template ("polls/detail.html") from the last
tutorial, so that the template contains an HTML ``<form>`` element: tutorial, so that the template contains an HTML ``<form>`` element:
.. code-block:: html+django .. snippet:: html+django
:filename: polls/templates/polls/detail.html
<h1>{{ question.question_text }}</h1> <h1>{{ question.question_text }}</h1>
@ -54,12 +55,18 @@ A quick rundown:
Now, let's create a Django view that handles the submitted data and does 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 something with it. Remember, in :doc:`Tutorial 3 </intro/tutorial03>`, we
created a URLconf for the polls application that includes this line:: created a URLconf for the polls application that includes this line:
.. snippet::
:filename: polls/urls.py
url(r'^(?P<question_id>\d+)/vote/$', views.vote, name='vote'), url(r'^(?P<question_id>\d+)/vote/$', views.vote, name='vote'),
We also created a dummy implementation of the ``vote()`` function. Let's We also created a dummy implementation of the ``vote()`` function. Let's
create a real version. Add the following to ``polls/views.py``:: create a real version. Add the following to ``polls/views.py``:
.. snippet::
:filename: polls/views.py
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse from django.http import HttpResponseRedirect, HttpResponse
@ -134,7 +141,10 @@ object. For more on :class:`~django.http.HttpRequest` objects, see the
:doc:`request and response documentation </ref/request-response>`. :doc:`request and response documentation </ref/request-response>`.
After somebody votes in a question, the ``vote()`` view redirects to the results After somebody votes in a question, the ``vote()`` view redirects to the results
page for the question. Let's write that view:: page for the question. Let's write that view:
.. snippet::
:filename: polls/views.py
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
@ -149,7 +159,8 @@ redundancy later.
Now, create a ``polls/results.html`` template: Now, create a ``polls/results.html`` template:
.. code-block:: html+django .. snippet:: html+django
:filename: polls/templates/polls/results.html
<h1>{{ question.question_text }}</h1> <h1>{{ question.question_text }}</h1>
@ -205,7 +216,10 @@ Read on for details.
Amend URLconf Amend URLconf
------------- -------------
First, open the ``polls/urls.py`` URLconf and change it like so:: First, open the ``polls/urls.py`` URLconf and change it like so:
.. snippet::
:filename: polls/urls.py
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
@ -223,7 +237,10 @@ Amend views
Next, we're going to remove our old ``index``, ``detail``, and ``results`` 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 views and use Django's generic views instead. To do so, open the
``polls/views.py`` file and change it like so:: ``polls/views.py`` file and change it like so:
.. snippet::
:filename: polls/views.py
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect

View File

@ -160,7 +160,10 @@ A conventional place for an application's tests is in the application's
``tests.py`` file; the testing system will automatically find tests in any file ``tests.py`` file; the testing system will automatically find tests in any file
whose name begins with ``test``. whose name begins with ``test``.
Put the following in the ``tests.py`` file in the ``polls`` application:: Put the following in the ``tests.py`` file in the ``polls`` application:
.. snippet::
:filename: polls/tests.py
import datetime import datetime
@ -236,7 +239,10 @@ Fixing the bug
We already know what the problem is: ``Question.was_published_recently()`` should We already know what the problem is: ``Question.was_published_recently()`` should
return ``False`` if its ``pub_date`` is in the future. Amend the method in return ``False`` if its ``pub_date`` is in the future. Amend the method in
``models.py``, so that it will only return ``True`` if the date is also in the ``models.py``, so that it will only return ``True`` if the date is also in the
past:: past:
.. snippet::
:filename: polls/models.py
def was_published_recently(self): def was_published_recently(self):
now = timezone.now() now = timezone.now()
@ -268,7 +274,10 @@ method; in fact, it would be positively embarrassing if in fixing one bug we had
introduced another. introduced another.
Add two more test methods to the same class, to test the behavior of the method Add two more test methods to the same class, to test the behavior of the method
more comprehensively:: more comprehensively:
.. snippet::
:filename: polls/tests.py
def test_was_published_recently_with_old_question(self): def test_was_published_recently_with_old_question(self):
""" """
@ -382,7 +391,10 @@ The list of polls shows polls that aren't published yet (i.e. those that have a
``pub_date`` in the future). Let's fix that. ``pub_date`` in the future). Let's fix that.
In :doc:`Tutorial 4 </intro/tutorial04>` we introduced a class-based view, In :doc:`Tutorial 4 </intro/tutorial04>` we introduced a class-based view,
based on :class:`~django.views.generic.list.ListView`:: based on :class:`~django.views.generic.list.ListView`:
.. snippet::
:filename: polls/views.py
class IndexView(generic.ListView): class IndexView(generic.ListView):
template_name = 'polls/index.html' template_name = 'polls/index.html'
@ -397,11 +409,17 @@ places into the context.
We need to amend the ``get_queryset`` method and change it so that it also We need to amend the ``get_queryset`` method and change it so that it also
checks the date by comparing it with ``timezone.now()``. First we need to add checks the date by comparing it with ``timezone.now()``. First we need to add
an import:: an import:
.. snippet::
:filename: polls/views.py
from django.utils import timezone from django.utils import timezone
and then we must amend the ``get_queryset`` method like so:: and then we must amend the ``get_queryset`` method like so:
.. snippet::
:filename: polls/views.py
def get_queryset(self): def get_queryset(self):
""" """
@ -426,12 +444,18 @@ are listed. You don't want to have to do that *every single time you make any
change that might affect this* - so let's also create a test, based on our change that might affect this* - so let's also create a test, based on our
:djadmin:`shell` session above. :djadmin:`shell` session above.
Add the following to ``polls/tests.py``:: Add the following to ``polls/tests.py``:
.. snippet::
:filename: polls/tests.py
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
and we'll create a factory method to create questions as well as a new test and we'll create a factory method to create questions as well as a new test
class:: class:
.. snippet::
:filename: polls/tests.py
def create_question(question_text, days): def create_question(question_text, days):
""" """
@ -532,8 +556,10 @@ Testing the ``DetailView``
What we have works well; however, even though future questions don't appear in What we have works well; however, even though future questions don't appear in
the *index*, users can still reach them if they know or guess the right URL. So the *index*, users can still reach them if they know or guess the right URL. So
we need to add a similar constraint to ``DetailView``:: we need to add a similar constraint to ``DetailView``:
.. snippet::
:filename: polls/views.py
class DetailView(generic.DetailView): class DetailView(generic.DetailView):
... ...
@ -545,7 +571,10 @@ we need to add a similar constraint to ``DetailView``::
And of course, we will add some tests, to check that a ``Question`` whose And of course, we will add some tests, to check that a ``Question`` whose
``pub_date`` is in the past can be displayed, and that one with a ``pub_date`` ``pub_date`` is in the past can be displayed, and that one with a ``pub_date``
in the future is not:: in the future is not:
.. snippet::
:filename: polls/tests.py
class QuestionIndexDetailTests(TestCase): class QuestionIndexDetailTests(TestCase):
def test_detail_view_with_a_future_question(self): def test_detail_view_with_a_future_question(self):

View File

@ -56,7 +56,8 @@ reference the path for templates.
Put the following code in that stylesheet (``polls/static/polls/style.css``): Put the following code in that stylesheet (``polls/static/polls/style.css``):
.. code-block:: css .. snippet:: css
:filename: polls/static/polls/style.css
li a { li a {
color: green; color: green;
@ -64,7 +65,8 @@ Put the following code in that stylesheet (``polls/static/polls/style.css``):
Next, add the following at the top of ``polls/templates/polls/index.html``: Next, add the following at the top of ``polls/templates/polls/index.html``:
.. code-block:: html+django .. snippet:: html+django
:filename: polls/templates/polls/index.html
{% load staticfiles %} {% load staticfiles %}
@ -88,7 +90,8 @@ called ``background.gif``. In other words, put your image in
Then, add to your stylesheet (``polls/static/polls/style.css``): Then, add to your stylesheet (``polls/static/polls/style.css``):
.. code-block:: css .. snippet:: css
:filename: polls/static/polls/style.css
body { body {
background: white url("images/background.gif") no-repeat right bottom; background: white url("images/background.gif") no-repeat right bottom;