Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
Adrian Holovaty authored

Showing 1 changed file with 355 additions and 0 deletions. Show diff stats Hide diff stats

  1. 355  docs/templates_python.txt
355  docs/templates_python.txt
@@ -193,6 +193,93 @@ some things to keep in mind:
193 193
             self.database_record.delete()
194 194
         sensitive_function.alters_data = True
195 195
 
  196
+Playing with Context objects
  197
+----------------------------
  198
+
  199
+Most of the time, you'll instantiate ``Context`` objects by passing in a
  200
+fully-populated dictionary to ``Context()``. But you can add and delete items
  201
+from a ``Context`` object once it's been instantiated, too, using standard
  202
+dictionary syntax::
  203
+
  204
+    >>> c = Context({"foo": "bar"})
  205
+    >>> c['foo']
  206
+    'bar'
  207
+    >>> del c['foo']
  208
+    >>> c['foo']
  209
+    ''
  210
+    >>> c['newvariable'] = 'hello'
  211
+    >>> c['newvariable']
  212
+    'hello'
  213
+
  214
+A ``Context`` object is a stack. That is, you can ``push()`` and ``pop()`` it.
  215
+If you ``pop()`` too much, it'll raise
  216
+``django.core.template.ContextPopException``.
  217
+
  218
+    >>> c = Context()
  219
+    >>> c['foo'] = 'first level'
  220
+    >>> c.push()
  221
+    >>> c['foo'] = 'second level'
  222
+    >>> c['foo']
  223
+    'second level'
  224
+    >>> c.pop()
  225
+    >>> c['foo']
  226
+    'first level'
  227
+    >>> c['foo'] = 'overwritten'
  228
+    >>> c['foo']
  229
+    'overwritten'
  230
+    >>> c.pop()
  231
+    Traceback (most recent call last):
  232
+    ...
  233
+    django.core.template.ContextPopException
  234
+
  235
+Using a ``Context`` as a stack comes in handy in some custom template tags, as
  236
+you'll see below.
  237
+
  238
+Subclassing Context: DjangoContext
  239
+----------------------------------
  240
+
  241
+Django comes with a special ``Context`` class,
  242
+``django.core.extensions.DjangoContext``, that acts slightly differently than
  243
+the normal ``django.core.template.Context``. It takes an ``HttpRequest`` object
  244
+as its first argument, and it automatically populates the context with a few
  245
+variables:
  246
+
  247
+    * ``user`` -- An ``auth.User`` instance representing the currently
  248
+      logged-in user (or an ``AnonymousUser`` instance, if the client isn't
  249
+      logged in).
  250
+    * ``messages`` -- A list of ``auth.Message`` objects for the currently
  251
+      logged-in user.
  252
+    * ``perms`` -- An instance of ``django.core.extensions.PermWrapper``,
  253
+      representing the permissions that the currently logged-in user has.
  254
+
  255
+Also, if your ``DEBUG`` setting is set to ``True``, every ``DjangoContext``
  256
+instance has the following two extra variables:
  257
+
  258
+    * ``debug`` -- ``True``. You can use this in templates to test whether
  259
+      you're in ``DEBUG`` mode.
  260
+    * ``sql_queries`` -- A list of ``{'sql': ..., 'time': ...}`` dictionaries,
  261
+      representing every SQL query that has happened so far during the request.
  262
+      The list is in order by query.
  263
+
  264
+Feel free to subclass ``Context`` yourself if you find yourself wanting to give
  265
+each template something "automatically." For instance, if you want to give
  266
+every template automatic access to the current time, use something like this::
  267
+
  268
+    from django.core.template import Context
  269
+    import datetime
  270
+    class TimeContext(template.Context):
  271
+        def __init__(self, *args, **kwargs):
  272
+            Context.__init__(self, *args, **kwargs)
  273
+            self['current_time'] = datetime.datetime.now()
  274
+
  275
+This technique has two caveats:
  276
+
  277
+    * You'll have to remember to use ``TimeContext`` instead of ``Context`` in
  278
+      your template-loading code.
  279
+
  280
+    * You'll have to be careful not to set the variable ``current_time`` within
  281
+      your templates. If you do, you'll override the other one.
  282
+
196 283
 Loading templates
197 284
 -----------------
198 285
 
@@ -271,9 +358,277 @@ To load a template that's within a subdirectory, just use a slash, like so::
271 358
 Extending the template system
272 359
 =============================
273 360
 
  361
+Although the Django template language comes with several default tags and
  362
+filters, you might want to write your own. It's easy to do.
  363
+
  364
+First, create a ``templatetags`` package in the appropriate Django app's
  365
+package. It should be on the same level as ``models``, ``views``, etc. For
  366
+example::
  367
+
  368
+    polls/
  369
+        models/
  370
+        templatetags/
  371
+        views/
  372
+
  373
+Add two files to the ``templatetags`` package: an ``__init__.py`` file and a
  374
+file that will contain your custom tag/filter definitions. The name of the
  375
+latter file is the name you'll use to load the tags later. For example, if your
  376
+custom tags/filters are in a file called ``poll_extras.py``, you'd do the
  377
+following in a template::
  378
+
  379
+    {% load poll_extras %}
  380
+
  381
+The ``{% load %}`` tag looks at your ``INSTALLED_APPS`` setting and only allows
  382
+the loading of template libraries within installed Django apps. This is a
  383
+security feature: It allows you to host Python code for many template libraries
  384
+on a single computer without enabling access to all of them for every Django
  385
+installation.
  386
+
  387
+If you write a template library that isn't tied to any particular models/views,
  388
+it's perfectly OK to have a Django app package that only contains a
  389
+``templatetags`` package.
  390
+
  391
+There's no limit on how many modules you put in the ``templatetags`` package.
  392
+Just keep in mind that a ``{% load %}`` statement will load tags/filters for
  393
+the given Python module name, not the name of the app.
  394
+
  395
+Once you've created that Python module, you'll just have to write a bit of
  396
+Python code, depending on whether you're writing filters or tags.
  397
+
  398
+.. admonition:: Behind the scenes
  399
+
  400
+    For a ton of examples, read the source code for Django's default filters
  401
+    and tags. They're in ``django/core/defaultfilters.py`` and
  402
+    ``django/core/defaulttags.py``, respectively.
  403
+
274 404
 Writing custom template filters
275 405
 -------------------------------
276 406
 
  407
+Custom filters are just Python functions that take two arguments:
  408
+
  409
+    * The value of the variable (input) -- not necessarily a string
  410
+    * The value of the argument -- always a string
  411
+
  412
+Filter functions should always return something. They shouldn't raise
  413
+exceptions. They should fail silently. In case of error, they should return
  414
+either the original input or the empty string -- whichever makes more sense.
  415
+
  416
+Here's an example filter definition::
  417
+
  418
+    def cut(value, arg):
  419
+        "Removes all values of arg from the given string"
  420
+        return value.replace(arg, '')
  421
+
  422
+Most filters don't take arguments. For filters that don't take arguments, the
  423
+convention is to use a single underscore as the second argument to the filter
  424
+definition. Example::
  425
+
  426
+    def lower(value, _):
  427
+        "Converts a string into all lowercase"
  428
+        return value.lower()
  429
+
  430
+When you've written your filter definition, you need to register it, to make it
  431
+available to Django's template language.
  432
+
  433
+    from django.core import template
  434
+    template.register_filter('cut', cut, True)
  435
+    template.register_filter('lower', lower, False)
  436
+
  437
+``register_filter`` takes three arguments::
  438
+
  439
+    1. The name of the filter -- a string
  440
+    2. The Python function
  441
+    3. A boolean, designating whether the filter requires an argument
  442
+
  443
+The convention is to put all ``register_filter`` calls at the bottom of your
  444
+template-library module.
  445
+
277 446
 Writing custom template tags
278 447
 ----------------------------
279 448
 
  449
+Tags are more complex than filters, because tags can do anything.
  450
+
  451
+A quick overview
  452
+~~~~~~~~~~~~~~~~
  453
+
  454
+Above, this document explained that the template system works in a two-step
  455
+process: compiling and rendering. To define a custom template tag, you specify
  456
+how the compilation works and how the rendering works.
  457
+
  458
+When Django compiles a template, it splits the raw template text into
  459
+''nodes''. Each node is an instance of ``django.core.template.Node`` and has
  460
+a ``render()`` method. A compiled template is, simply, a list of ``Node``
  461
+objects. When you call ``render()`` on a compiled template object, the template
  462
+calls ``render()`` on each ``Node`` in its node list, with the given context.
  463
+
  464
+Thus, to define a custom template tag, you specify how the raw template tag is
  465
+converted into a ``Node`` (the compilation function), and what the node's
  466
+``render()`` method does.
  467
+
  468
+Writing the compilation function
  469
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  470
+
  471
+For each template tag the template parser encounters, it calls a Python
  472
+function with the tag contents and the parser object itself. This function is
  473
+responsible for returning a ``Node`` instance based on the contents of the tag.
  474
+
  475
+By convention, the name of each compilation function should start with ``do_``.
  476
+
  477
+For example, let's write a template tag that displays the current date/time,
  478
+formatted according to a parameter given in the tag, in `strftime syntax`_.
  479
+It's a good idea to decide the tag syntax before anything else. In our case,
  480
+let's say the tag should be used like this::
  481
+
  482
+    <p>The time is {% current_time "%Y-%M-%d %I:%M %p" %}.</p>
  483
+
  484
+.. _`strftime syntax`: http://www.python.org/doc/current/lib/module-time.html#l2h-1941
  485
+
  486
+The parser for this function should grab the parameter and create a ``Node``
  487
+object::
  488
+
  489
+    from django.core import template
  490
+    def do_current_time(parser, token):
  491
+        try:
  492
+            # Splitting by None == splitting by spaces.
  493
+            tag_name, format_string = token.contents.split(None, 1)
  494
+        except ValueError:
  495
+            raise template.TemplateSyntaxError, "%r tag requires an argument" % token.contents[0]
  496
+        if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
  497
+            raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
  498
+        return CurrentTimeNode(format_string[1:-1])
  499
+
  500
+Notes:
  501
+
  502
+    * ``parser`` is the template parser object. We don't need it in this
  503
+      example.
  504
+
  505
+    * ``token.contents`` is a string of the raw contents of the tag. In our
  506
+      example, it's ``'current_time "%Y-%M-%d %I:%M %p"'``
  507
+
  508
+    * This function raises ``django.core.template.TemplateSyntaxError``, with
  509
+      helpful messages, for any syntax error.
  510
+
  511
+    * The ``TemplateSyntaxError`` exceptions use the ``tag_name`` variable.
  512
+      Don't hard-code the tag's name in your error messages, because that
  513
+      couples the tag's name to your function. ``token.contents.split()[0]``
  514
+      will ''always'' be the name of your tag -- even when the tag has no
  515
+      arguments.
  516
+
  517
+    * The function returns a ``CurrentTimeNode`` with everything the node needs
  518
+      to know about this tag. In this case, it just passes the argument --
  519
+      ``"%Y-%M-%d %I:%M %p"``. The leading and trailing quotes from the
  520
+      template tag are removed in ``format_string[1:-1]``.
  521
+
  522
+    * The parsing is very low-level. The Django developers have experimented
  523
+      with writing small frameworks on top of this parsing system, using
  524
+      techniques such as EBNF grammars, but those experiments made the template
  525
+      engine too slow. It's low-level because that's fastest.
  526
+
  527
+Writing the renderer
  528
+~~~~~~~~~~~~~~~~~~~~
  529
+
  530
+The second step in writing custom tags is to define a ``Node`` subclass that
  531
+has a ``render()`` method.
  532
+
  533
+Continuing the above example, we need to define ``CurrentTimeNode``::
  534
+
  535
+    from django.core import template
  536
+    import datetime
  537
+    class CurrentTimeNode(template.Node):
  538
+        def __init__(self, format_string):
  539
+            self.format_string = format_string
  540
+        def render(self, context):
  541
+            return datetime.datetime.now().strftime(self.format_string)
  542
+
  543
+Notes:
  544
+
  545
+    * ``__init__()`` gets the ``format_string`` from ``do_current_time()``.
  546
+      Always pass any options/parameters/arguments to a ``Node`` via its
  547
+      ``__init__()``.
  548
+
  549
+    * The ``render()`` method is where the work actually happens.
  550
+
  551
+Ultimately, this decoupling of compilation and rendering results in an
  552
+efficient template system, because a template can render multiple context
  553
+without having to be parsed multiple times.
  554
+
  555
+Registering the tag
  556
+~~~~~~~~~~~~~~~~~~~
  557
+
  558
+Finally, use a ``register_tag`` call, as in ``register_filter`` above. Example::
  559
+
  560
+    from django.core import template
  561
+    template.register_tag('cycle', do_cycle)
  562
+
  563
+``register_tag`` takes two arguments:
  564
+
  565
+    * The name of the template tag -- a string
  566
+    * The compilation function -- a Python function (not the name of the
  567
+      function as a string)
  568
+
  569
+Setting a variable in the context
  570
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  571
+
  572
+The above example simply output a value. Generally, it's more flexible if your
  573
+template tags set template variables instead of outputting values. That way,
  574
+you allow template authors to reuse the values that your template tags create.
  575
+
  576
+To set a variable in the context, just use dictionary assignment on the context
  577
+object in the ``render()`` method. Here's an updated version of
  578
+``CurrentTimeNode`` that sets a template variable ``current_time`` instead of
  579
+outputting it.
  580
+
  581
+    class CurrentTimeNode2(template.Node):
  582
+        def __init__(self, format_string):
  583
+            self.format_string = format_string
  584
+        def render(self, context):
  585
+            context['current_time'] = datetime.datetime.now().strftime(self.format_string)
  586
+            return ''
  587
+
  588
+Note that ``render()`` returns the empty string. ``render()`` should always
  589
+return string output. If all the template tag does is set a variable,
  590
+``render()`` should return the empty string.
  591
+
  592
+Here's how you'd use this new version of the tag::
  593
+
  594
+    {% current_time "%Y-%M-%d %I:%M %p" %}<p>The time is {{ current_time }}.</p>
  595
+
  596
+But, there's a naive problem with ``CurrentTimeNode2``: The variable name
  597
+``current_time`` is hard-coded. This means you'll need to make sure your
  598
+template doesn't use ``{{ current_time }}`` anywhere else, because the
  599
+``{% current_time %}`` will blindly overwrite that variable's value. A cleaner
  600
+solution is to make the template tag specify the name of the output variable,
  601
+like so::
  602
+
  603
+    {% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}
  604
+    <p>The current time is {{ my_current_time }}.</p>
  605
+
  606
+To do that, you'll need to refactor both the compilation function and ``Node``
  607
+class, like so::
  608
+
  609
+    class CurrentTimeNode3(template.Node):
  610
+        def __init__(self, format_string, var_name):
  611
+            self.format_string = format_string
  612
+            self.var_name = var_name
  613
+        def render(self, context):
  614
+            context[self.var_name] = datetime.datetime.now().strftime(self.format_string)
  615
+            return ''
  616
+
  617
+    import re
  618
+    def do_current_time(parser, token):
  619
+        # This version uses a regular expression to parse tag contents.
  620
+        try:
  621
+            # Splitting by None == splitting by spaces.
  622
+            tag_name, arg = token.contents.split(None, 1)
  623
+        except ValueError:
  624
+            raise template.TemplateSyntaxError, "%r tag requires arguments" % token.contents[0]
  625
+        m = re.search(r'(.*?) as (\w+)', arg)
  626
+        if not m:
  627
+            raise template.TemplateSyntaxError, "%r tag had invalid arguments" % tag_name
  628
+        format_string, var_name = m.groups()
  629
+        if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
  630
+            raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
  631
+        return CurrentTimeNode3(format_string[1:-1], var_name)
  632
+
  633
+The difference here is that ``do_current_time()`` grabs the format string and
  634
+the variable name, passing both to ``CurrentTimeNode3``.

0 notes on commit f9f0ea9

Please sign in to comment.
Something went wrong with that request. Please try again.