Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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
  • Loading branch information...
commit f9f0ea93081e8afd2d2d5edc67b6a5b81aed197f 1 parent e131e40
@adrianholovaty adrianholovaty authored
Showing with 355 additions and 0 deletions.
  1. +355 −0 docs/templates_python.txt
View
355 docs/templates_python.txt
@@ -193,6 +193,93 @@ some things to keep in mind:
self.database_record.delete()
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
-----------------
@@ -271,9 +358,277 @@ To load a template that's within a subdirectory, just use a slash, like so::
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
-------------------------------
+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
----------------------------
+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``.
Please sign in to comment.
Something went wrong with that request. Please try again.