Added a couple more sections to docs/templates_python.txt
git-svn-id: http://code.djangoproject.com/svn/django/trunk@625 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
e131e400e0
commit
f9f0ea9308
|
@ -193,6 +193,93 @@ some things to keep in mind:
|
||||||
self.database_record.delete()
|
self.database_record.delete()
|
||||||
sensitive_function.alters_data = True
|
sensitive_function.alters_data = True
|
||||||
|
|
||||||
|
Playing with Context objects
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Most of the time, you'll instantiate ``Context`` objects by passing in a
|
||||||
|
fully-populated dictionary to ``Context()``. But you can add and delete items
|
||||||
|
from a ``Context`` object once it's been instantiated, too, using standard
|
||||||
|
dictionary syntax::
|
||||||
|
|
||||||
|
>>> c = Context({"foo": "bar"})
|
||||||
|
>>> c['foo']
|
||||||
|
'bar'
|
||||||
|
>>> del c['foo']
|
||||||
|
>>> c['foo']
|
||||||
|
''
|
||||||
|
>>> c['newvariable'] = 'hello'
|
||||||
|
>>> c['newvariable']
|
||||||
|
'hello'
|
||||||
|
|
||||||
|
A ``Context`` object is a stack. That is, you can ``push()`` and ``pop()`` it.
|
||||||
|
If you ``pop()`` too much, it'll raise
|
||||||
|
``django.core.template.ContextPopException``.
|
||||||
|
|
||||||
|
>>> c = Context()
|
||||||
|
>>> c['foo'] = 'first level'
|
||||||
|
>>> c.push()
|
||||||
|
>>> c['foo'] = 'second level'
|
||||||
|
>>> c['foo']
|
||||||
|
'second level'
|
||||||
|
>>> c.pop()
|
||||||
|
>>> c['foo']
|
||||||
|
'first level'
|
||||||
|
>>> c['foo'] = 'overwritten'
|
||||||
|
>>> c['foo']
|
||||||
|
'overwritten'
|
||||||
|
>>> c.pop()
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
django.core.template.ContextPopException
|
||||||
|
|
||||||
|
Using a ``Context`` as a stack comes in handy in some custom template tags, as
|
||||||
|
you'll see below.
|
||||||
|
|
||||||
|
Subclassing Context: DjangoContext
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
Django comes with a special ``Context`` class,
|
||||||
|
``django.core.extensions.DjangoContext``, that acts slightly differently than
|
||||||
|
the normal ``django.core.template.Context``. It takes an ``HttpRequest`` object
|
||||||
|
as its first argument, and it automatically populates the context with a few
|
||||||
|
variables:
|
||||||
|
|
||||||
|
* ``user`` -- An ``auth.User`` instance representing the currently
|
||||||
|
logged-in user (or an ``AnonymousUser`` instance, if the client isn't
|
||||||
|
logged in).
|
||||||
|
* ``messages`` -- A list of ``auth.Message`` objects for the currently
|
||||||
|
logged-in user.
|
||||||
|
* ``perms`` -- An instance of ``django.core.extensions.PermWrapper``,
|
||||||
|
representing the permissions that the currently logged-in user has.
|
||||||
|
|
||||||
|
Also, if your ``DEBUG`` setting is set to ``True``, every ``DjangoContext``
|
||||||
|
instance has the following two extra variables:
|
||||||
|
|
||||||
|
* ``debug`` -- ``True``. You can use this in templates to test whether
|
||||||
|
you're in ``DEBUG`` mode.
|
||||||
|
* ``sql_queries`` -- A list of ``{'sql': ..., 'time': ...}`` dictionaries,
|
||||||
|
representing every SQL query that has happened so far during the request.
|
||||||
|
The list is in order by query.
|
||||||
|
|
||||||
|
Feel free to subclass ``Context`` yourself if you find yourself wanting to give
|
||||||
|
each template something "automatically." For instance, if you want to give
|
||||||
|
every template automatic access to the current time, use something like this::
|
||||||
|
|
||||||
|
from django.core.template import Context
|
||||||
|
import datetime
|
||||||
|
class TimeContext(template.Context):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
Context.__init__(self, *args, **kwargs)
|
||||||
|
self['current_time'] = datetime.datetime.now()
|
||||||
|
|
||||||
|
This technique has two caveats:
|
||||||
|
|
||||||
|
* You'll have to remember to use ``TimeContext`` instead of ``Context`` in
|
||||||
|
your template-loading code.
|
||||||
|
|
||||||
|
* You'll have to be careful not to set the variable ``current_time`` within
|
||||||
|
your templates. If you do, you'll override the other one.
|
||||||
|
|
||||||
Loading templates
|
Loading templates
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
@ -271,9 +358,277 @@ To load a template that's within a subdirectory, just use a slash, like so::
|
||||||
Extending the template system
|
Extending the template system
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
|
Although the Django template language comes with several default tags and
|
||||||
|
filters, you might want to write your own. It's easy to do.
|
||||||
|
|
||||||
|
First, create a ``templatetags`` package in the appropriate Django app's
|
||||||
|
package. It should be on the same level as ``models``, ``views``, etc. For
|
||||||
|
example::
|
||||||
|
|
||||||
|
polls/
|
||||||
|
models/
|
||||||
|
templatetags/
|
||||||
|
views/
|
||||||
|
|
||||||
|
Add two files to the ``templatetags`` package: an ``__init__.py`` file and a
|
||||||
|
file that will contain your custom tag/filter definitions. The name of the
|
||||||
|
latter file is the name you'll use to load the tags later. For example, if your
|
||||||
|
custom tags/filters are in a file called ``poll_extras.py``, you'd do the
|
||||||
|
following in a template::
|
||||||
|
|
||||||
|
{% load poll_extras %}
|
||||||
|
|
||||||
|
The ``{% load %}`` tag looks at your ``INSTALLED_APPS`` setting and only allows
|
||||||
|
the loading of template libraries within installed Django apps. This is a
|
||||||
|
security feature: It allows you to host Python code for many template libraries
|
||||||
|
on a single computer without enabling access to all of them for every Django
|
||||||
|
installation.
|
||||||
|
|
||||||
|
If you write a template library that isn't tied to any particular models/views,
|
||||||
|
it's perfectly OK to have a Django app package that only contains a
|
||||||
|
``templatetags`` package.
|
||||||
|
|
||||||
|
There's no limit on how many modules you put in the ``templatetags`` package.
|
||||||
|
Just keep in mind that a ``{% load %}`` statement will load tags/filters for
|
||||||
|
the given Python module name, not the name of the app.
|
||||||
|
|
||||||
|
Once you've created that Python module, you'll just have to write a bit of
|
||||||
|
Python code, depending on whether you're writing filters or tags.
|
||||||
|
|
||||||
|
.. admonition:: Behind the scenes
|
||||||
|
|
||||||
|
For a ton of examples, read the source code for Django's default filters
|
||||||
|
and tags. They're in ``django/core/defaultfilters.py`` and
|
||||||
|
``django/core/defaulttags.py``, respectively.
|
||||||
|
|
||||||
Writing custom template filters
|
Writing custom template filters
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
|
Custom filters are just Python functions that take two arguments:
|
||||||
|
|
||||||
|
* The value of the variable (input) -- not necessarily a string
|
||||||
|
* The value of the argument -- always a string
|
||||||
|
|
||||||
|
Filter functions should always return something. They shouldn't raise
|
||||||
|
exceptions. They should fail silently. In case of error, they should return
|
||||||
|
either the original input or the empty string -- whichever makes more sense.
|
||||||
|
|
||||||
|
Here's an example filter definition::
|
||||||
|
|
||||||
|
def cut(value, arg):
|
||||||
|
"Removes all values of arg from the given string"
|
||||||
|
return value.replace(arg, '')
|
||||||
|
|
||||||
|
Most filters don't take arguments. For filters that don't take arguments, the
|
||||||
|
convention is to use a single underscore as the second argument to the filter
|
||||||
|
definition. Example::
|
||||||
|
|
||||||
|
def lower(value, _):
|
||||||
|
"Converts a string into all lowercase"
|
||||||
|
return value.lower()
|
||||||
|
|
||||||
|
When you've written your filter definition, you need to register it, to make it
|
||||||
|
available to Django's template language.
|
||||||
|
|
||||||
|
from django.core import template
|
||||||
|
template.register_filter('cut', cut, True)
|
||||||
|
template.register_filter('lower', lower, False)
|
||||||
|
|
||||||
|
``register_filter`` takes three arguments::
|
||||||
|
|
||||||
|
1. The name of the filter -- a string
|
||||||
|
2. The Python function
|
||||||
|
3. A boolean, designating whether the filter requires an argument
|
||||||
|
|
||||||
|
The convention is to put all ``register_filter`` calls at the bottom of your
|
||||||
|
template-library module.
|
||||||
|
|
||||||
Writing custom template tags
|
Writing custom template tags
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
Tags are more complex than filters, because tags can do anything.
|
||||||
|
|
||||||
|
A quick overview
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Above, this document explained that the template system works in a two-step
|
||||||
|
process: compiling and rendering. To define a custom template tag, you specify
|
||||||
|
how the compilation works and how the rendering works.
|
||||||
|
|
||||||
|
When Django compiles a template, it splits the raw template text into
|
||||||
|
''nodes''. Each node is an instance of ``django.core.template.Node`` and has
|
||||||
|
a ``render()`` method. A compiled template is, simply, a list of ``Node``
|
||||||
|
objects. When you call ``render()`` on a compiled template object, the template
|
||||||
|
calls ``render()`` on each ``Node`` in its node list, with the given context.
|
||||||
|
|
||||||
|
Thus, to define a custom template tag, you specify how the raw template tag is
|
||||||
|
converted into a ``Node`` (the compilation function), and what the node's
|
||||||
|
``render()`` method does.
|
||||||
|
|
||||||
|
Writing the compilation function
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
For each template tag the template parser encounters, it calls a Python
|
||||||
|
function with the tag contents and the parser object itself. This function is
|
||||||
|
responsible for returning a ``Node`` instance based on the contents of the tag.
|
||||||
|
|
||||||
|
By convention, the name of each compilation function should start with ``do_``.
|
||||||
|
|
||||||
|
For example, let's write a template tag that displays the current date/time,
|
||||||
|
formatted according to a parameter given in the tag, in `strftime syntax`_.
|
||||||
|
It's a good idea to decide the tag syntax before anything else. In our case,
|
||||||
|
let's say the tag should be used like this::
|
||||||
|
|
||||||
|
<p>The time is {% current_time "%Y-%M-%d %I:%M %p" %}.</p>
|
||||||
|
|
||||||
|
.. _`strftime syntax`: http://www.python.org/doc/current/lib/module-time.html#l2h-1941
|
||||||
|
|
||||||
|
The parser for this function should grab the parameter and create a ``Node``
|
||||||
|
object::
|
||||||
|
|
||||||
|
from django.core import template
|
||||||
|
def do_current_time(parser, token):
|
||||||
|
try:
|
||||||
|
# Splitting by None == splitting by spaces.
|
||||||
|
tag_name, format_string = token.contents.split(None, 1)
|
||||||
|
except ValueError:
|
||||||
|
raise template.TemplateSyntaxError, "%r tag requires an argument" % token.contents[0]
|
||||||
|
if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
|
||||||
|
raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
|
||||||
|
return CurrentTimeNode(format_string[1:-1])
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
* ``parser`` is the template parser object. We don't need it in this
|
||||||
|
example.
|
||||||
|
|
||||||
|
* ``token.contents`` is a string of the raw contents of the tag. In our
|
||||||
|
example, it's ``'current_time "%Y-%M-%d %I:%M %p"'``
|
||||||
|
|
||||||
|
* This function raises ``django.core.template.TemplateSyntaxError``, with
|
||||||
|
helpful messages, for any syntax error.
|
||||||
|
|
||||||
|
* The ``TemplateSyntaxError`` exceptions use the ``tag_name`` variable.
|
||||||
|
Don't hard-code the tag's name in your error messages, because that
|
||||||
|
couples the tag's name to your function. ``token.contents.split()[0]``
|
||||||
|
will ''always'' be the name of your tag -- even when the tag has no
|
||||||
|
arguments.
|
||||||
|
|
||||||
|
* The function returns a ``CurrentTimeNode`` with everything the node needs
|
||||||
|
to know about this tag. In this case, it just passes the argument --
|
||||||
|
``"%Y-%M-%d %I:%M %p"``. The leading and trailing quotes from the
|
||||||
|
template tag are removed in ``format_string[1:-1]``.
|
||||||
|
|
||||||
|
* The parsing is very low-level. The Django developers have experimented
|
||||||
|
with writing small frameworks on top of this parsing system, using
|
||||||
|
techniques such as EBNF grammars, but those experiments made the template
|
||||||
|
engine too slow. It's low-level because that's fastest.
|
||||||
|
|
||||||
|
Writing the renderer
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The second step in writing custom tags is to define a ``Node`` subclass that
|
||||||
|
has a ``render()`` method.
|
||||||
|
|
||||||
|
Continuing the above example, we need to define ``CurrentTimeNode``::
|
||||||
|
|
||||||
|
from django.core import template
|
||||||
|
import datetime
|
||||||
|
class CurrentTimeNode(template.Node):
|
||||||
|
def __init__(self, format_string):
|
||||||
|
self.format_string = format_string
|
||||||
|
def render(self, context):
|
||||||
|
return datetime.datetime.now().strftime(self.format_string)
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
* ``__init__()`` gets the ``format_string`` from ``do_current_time()``.
|
||||||
|
Always pass any options/parameters/arguments to a ``Node`` via its
|
||||||
|
``__init__()``.
|
||||||
|
|
||||||
|
* The ``render()`` method is where the work actually happens.
|
||||||
|
|
||||||
|
Ultimately, this decoupling of compilation and rendering results in an
|
||||||
|
efficient template system, because a template can render multiple context
|
||||||
|
without having to be parsed multiple times.
|
||||||
|
|
||||||
|
Registering the tag
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Finally, use a ``register_tag`` call, as in ``register_filter`` above. Example::
|
||||||
|
|
||||||
|
from django.core import template
|
||||||
|
template.register_tag('cycle', do_cycle)
|
||||||
|
|
||||||
|
``register_tag`` takes two arguments:
|
||||||
|
|
||||||
|
* The name of the template tag -- a string
|
||||||
|
* The compilation function -- a Python function (not the name of the
|
||||||
|
function as a string)
|
||||||
|
|
||||||
|
Setting a variable in the context
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The above example simply output a value. Generally, it's more flexible if your
|
||||||
|
template tags set template variables instead of outputting values. That way,
|
||||||
|
you allow template authors to reuse the values that your template tags create.
|
||||||
|
|
||||||
|
To set a variable in the context, just use dictionary assignment on the context
|
||||||
|
object in the ``render()`` method. Here's an updated version of
|
||||||
|
``CurrentTimeNode`` that sets a template variable ``current_time`` instead of
|
||||||
|
outputting it.
|
||||||
|
|
||||||
|
class CurrentTimeNode2(template.Node):
|
||||||
|
def __init__(self, format_string):
|
||||||
|
self.format_string = format_string
|
||||||
|
def render(self, context):
|
||||||
|
context['current_time'] = datetime.datetime.now().strftime(self.format_string)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
Note that ``render()`` returns the empty string. ``render()`` should always
|
||||||
|
return string output. If all the template tag does is set a variable,
|
||||||
|
``render()`` should return the empty string.
|
||||||
|
|
||||||
|
Here's how you'd use this new version of the tag::
|
||||||
|
|
||||||
|
{% current_time "%Y-%M-%d %I:%M %p" %}<p>The time is {{ current_time }}.</p>
|
||||||
|
|
||||||
|
But, there's a naive problem with ``CurrentTimeNode2``: The variable name
|
||||||
|
``current_time`` is hard-coded. This means you'll need to make sure your
|
||||||
|
template doesn't use ``{{ current_time }}`` anywhere else, because the
|
||||||
|
``{% current_time %}`` will blindly overwrite that variable's value. A cleaner
|
||||||
|
solution is to make the template tag specify the name of the output variable,
|
||||||
|
like so::
|
||||||
|
|
||||||
|
{% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}
|
||||||
|
<p>The current time is {{ my_current_time }}.</p>
|
||||||
|
|
||||||
|
To do that, you'll need to refactor both the compilation function and ``Node``
|
||||||
|
class, like so::
|
||||||
|
|
||||||
|
class CurrentTimeNode3(template.Node):
|
||||||
|
def __init__(self, format_string, var_name):
|
||||||
|
self.format_string = format_string
|
||||||
|
self.var_name = var_name
|
||||||
|
def render(self, context):
|
||||||
|
context[self.var_name] = datetime.datetime.now().strftime(self.format_string)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
import re
|
||||||
|
def do_current_time(parser, token):
|
||||||
|
# This version uses a regular expression to parse tag contents.
|
||||||
|
try:
|
||||||
|
# Splitting by None == splitting by spaces.
|
||||||
|
tag_name, arg = token.contents.split(None, 1)
|
||||||
|
except ValueError:
|
||||||
|
raise template.TemplateSyntaxError, "%r tag requires arguments" % token.contents[0]
|
||||||
|
m = re.search(r'(.*?) as (\w+)', arg)
|
||||||
|
if not m:
|
||||||
|
raise template.TemplateSyntaxError, "%r tag had invalid arguments" % tag_name
|
||||||
|
format_string, var_name = m.groups()
|
||||||
|
if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
|
||||||
|
raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
|
||||||
|
return CurrentTimeNode3(format_string[1:-1], var_name)
|
||||||
|
|
||||||
|
The difference here is that ``do_current_time()`` grabs the format string and
|
||||||
|
the variable name, passing both to ``CurrentTimeNode3``.
|
||||||
|
|
Loading…
Reference in New Issue