Sunday, December 23, 2012

Chapter 9 Advanced Template

context is a name -> value mapping (similar to a Python dictionary) that is passed to a template.

RequestContext and Context Processors



instance of django.template.Context, but Django also comes with a special subclass, django.template.RequestContext, that acts slightly differently. RequestContextadds a bunch of variables to your template context by default – things like the HttpRequest object or information about the currently logged-in user.

Use RequestContext when you don’t want to have to specify the same set of variables in a series of templates.
Avoid repeating the same values in the context dictionary 

Point: a context with many similar elements
DO NOT REPEAT YOURSELF

MOST LOW-LEVEL
 c1 = Context({
        'app': 'My app',
        'user': request.user,
        'ip_address': request.META['REMOTE_ADDR'],
        'message': 'I am view 1.'
    })
 c1 = Context({
        'app': 'My app',
        'user': request.user,
        'ip_address': request.META['REMOTE_ADDR'],
        'message': 'I am the second view.'
    })
from django.template import loader, RequestContext

def custom_proc(request):
    "A context processor that provides 'app', 'user' and 'ip_address'."
    return {
        'app': 'My app',
        'user': request.user,
        'ip_address': request.META['REMOTE_ADDR']
    }

def view_1(request):
    # ...
    t = loader.get_template('template1.html')
    c = RequestContext(request, {'message': 'I am view 1.'},
            processors=[custom_proc])
    return t.render(c)

def view_2(request):
    # ...
    t = loader.get_template('template2.html')
    c = RequestContext(request, {'message': 'I am the second view.'},
            processors=[custom_proc]) #list
    return t.render(c)
context processor: it takes an HttpRequest object and returns a dictionary of variables to use in the template context
# naming convention: processorName_proc

processors argument, which is a list or tuple of context processor functions to use. 

render_to_reponse() + RequestContext

def view_2(request):
    # ...
    return render_to_response('template2.html',
        {'message': 'I am the second view.'},
        context_instance=RequestContext(request, processors=[custom_proc]))
END MOST LOW-LEVEL

GLOBAL CONTEXT-LEVEL (Not really understood)
global context processors
This removes the need to specify processors each time you use RequestContext

For that reason, Django provides support for global context processors. The TEMPLATE_CONTEXT_PROCESSORSsetting (in your settings.py) designates which context processors should always be applied to RequestContext.

This setting is a tuple of callables that use the same interface as our custom_proc function above – functions that take a request object as their argument and return a dictionary of items to be merged into the context.
TEMPLATE_CONTEXT_PROCESSORS = ( #tuple
    'django.core.context_processors.auth',
    'django.core.context_processors.debug',
    'django.core.context_processors.i18n',
    'django.core.context_processors.media',
)

django.core.context_processors.auth

  • user
  • messages
  • perms

django.core.context_processors.debug

  • debug
  • sql_queries

django.core.context_processors.i18n

  • LANGUAGES
  • LANGUAGE_CODE

Auto escape
(template variable, variable from view functions)
{{ data|default:"3 < 2" }}
...rather than
{{ data|default:"3 < 2" }}  <-- Bad! Don't do this.
This doesn’t affect what happens to data coming from the variable itself. 
Generally, template authors don’t need to worry about auto-escaping very much. Developers on the Python side (people writing views and custom filters) need to think about the cases in which data shouldn’t be escaped, and mark data appropriately, so things work in the template.

Inside Template Loading

Generally, you’ll store templates in files on your filesystem, but you can use custom template loaders to load templates from other sources.

Django has two ways to load templates:
  1. django.template.loader.get_template(template_name)
  2. django.template.loader.select_template(template_name_list)
these two functions by default uses your TEMPLATE_DIRS setting to load templates. Internally, however, these functions actually delegate to a template loader for the heavy lifting.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)
TEMPLATE_DIRS = (
os.path.join(os.path.dirname(__file__), 'template').replace('\\','/'),
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)




django.template.loaders.filesystem.load_template_source: This loader loads templates from the filesystem, according to TEMPLATE_DIRS. It is enabled by default.


django.template.loaders.app_directories.load_template_source: This loader loads templates from Django applications on the filesystem. For each application in INSTALLED_APPS, the loader looks for a templates subdirectory. If the directory exists, Django looks for templates there.

This means you can store templates with your individual applications, making it easy to distribute Django applications with default templates. For example, if INSTALLED_APPS contains('myproject.polls', 'myproject.music'), then get_template('foo.html') will look for templates in this order:
/path/to/myproject/polls/templates/foo.html
/path/to/myproject/music/templates/foo.html


Note that the loader performs an optimization when it is first imported: it caches a list of which INSTALLED_APPS packages have a templates subdirectory.

This loader is enabled by default.

django.template.loaders.eggs.load_template_source: This loader is just like app_directories, except it loads templates from Python eggs rather than from the filesystem. This loader is disabled by default; you’ll need to enable it if you’re using eggs to distribute your application. (Python eggs are a way of compressing Python code into a single file.)

Django uses the template loaders in order according to the TEMPLATE_LOADERS setting. It uses each loader until a loader finds a match.

Extending the Template System

 custom template tags and/or filters

Creating a Template Library

to create a template library – a small bit of infrastructure Django can hook into.

Three steps of creating template library
  1. First, create another app solely for the template library.  because your filters might be useful to you in future projects, add the app to your INSTALLED_APPS setting
  2. Second, create a templatetags directory in the appropriate Django application’s package. It should be on the same level as models.py, views.py, and so forth. For example:
  3. __init__.py and then your custom tag/filter defintions

books/
    __init__.py
    models.py
    templatetags/ __init__.py; poll_extras.py etc.
    views.py

correspond to the poll_extras.py # your custom defined tag/filter
{% load poll_extras %} //later loading in the template

The {% load %} tag looks at your INSTALLED_APPS setting (new app for template library must be added in setting)and only allows the loading of template libraries within installed Django applications. 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.

first thing in poll_extras.py is registration: 
from django import template

register = template.Library()
Later:
register.filter('cut', cut)
register.filter('lower', lower)

Writing Custom Template Filters

  • __init__.py inside app folder, and inside templatetags folder (under yourApp folder)
  • app name in the INSTALLED_APPS section from settings.py
  • to use: {% load customizedfilter %}s

Custom filters are just Python functions that take one or two arguments:
  • The value of the variable (input)
  • The value of the argument, which can have a default value or be left out altogether
For example, in the filter {{ var|foo:"bar" }}, the filter foo would be passed the contents of the variable var and the argument "bar".

Rule for filter functions:
. They shouldn’t raise exceptions, and they should fail silently. If there’s an error, they should return either the original input or an empty string

def cut(value, arg):
    "Removes all values of arg from the given string"
    return value.replace(arg, '')
{{ somevariable|cut:" " }} 

def lower(value): # Only one argument.
    "Converts a string into all lowercase"
    return value.lower()
register.filter('cut', cut)
register.filter('lower', lower)
using decorator
from django import template

register = template.Library()

@register.filter #OR @register.filter(name='cut')
def cut(value, arg):
    return value.replace(arg, '')
done

Writing Custom Template Tags 


When Django compiles a template, it splits the raw template text into nodes. Each node is an instance of django.template.Node and has a render() method. Thus, a compiled template is simply a list of Node objects. For example, consider this template:

Hello, {{ person.name }}.

{% ifequal name.birthday today %}
    Happy birthday!
{% else %}
    Be sure to come back on your birthday
    for a splendid surprise message.
{% endifequal %}

In compiled template form, this template is represented as this list of nodes:
  • Text node: "Hello, "
  • Variable node: person.name
  • Text node: ".\n\n"
  • IfEqual node: name.birthday and today
When you call render() on a compiled template, the template calls render() on each Node in its node list
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.


1 Writing the Compilation Function

For each template tag the 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.
{% xxx %}  parser
User-defined {% current_time %} #actually available: {% now %}

<p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>

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"'.
from django import template

register = template.Library()

def do_current_time(parser, token):
    try:
        # split_contents() knows not to split quoted strings.
        tag_name, format_string = token.split_contents()
    except ValueError:
        msg = '%r tag requires a single argument' % token.split_contents()[0]
        raise template.TemplateSyntaxError(msg)
    return CurrentTimeNode(format_string[1:-1])

parser is the template parser object. We don’t use it in this example. token is the token currently being parsed by the parser.
  1. Don’t hard-code the tag’s name in your error messages, because that couples the tag’s name to your function. token.split_contents()[0] will always be the name of your tag – even when the tag has no arguments.
  2. The function returns a CurrentTimeNode (which we’ll create shortly) containing 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 with format_string[1:-1].
  3. Template tag compilation functions must return a Node subclass; any other return value is an error.

2 Writing the Template Node

import datetime

class CurrentTimeNode(template.Node): #extends
    def __init__(self, format_string):
        self.format_string = str(format_string)

    def render(self, context):
        now = datetime.datetime.now()
        return now.strftime(self.format_string)

3 Registering the Tag

register.tag('current_time', do_current_time)
As with filter registration, it is also possible to use register.tag as a decorator in Python 2.4 and above:
@register.tag(name="current_time")
def do_current_time(parser, token):
    # ...

@register.tag
def shout(parser, token):
    # ...

------------------------------------------------------------------------------------------------

Setting a Variable in the Context

The previous section’s example simply returned a value. Often it’s useful to set template variables instead of returning values.

return a value vs. set a value
dictionary assignment
class CurrentTimeNode2(template.Node):
    def __init__(self, format_string):
        self.format_string = str(format_string)

    def render(self, context):
        now = datetime.datetime.now()
        context['current_time'] = now.strftime(self.format_string)
        return ''
Note that render() returns an empty string. render() should always return a string, so if all the template tag does is set a variable, render() should return an empty string.

New version: setting template variable through context dictionary
(hard-coded)
{% current_time2 "%Y-%M-%d %I:%M %p" %}
<p>The time is {{ current_time }}.</p>
(non hard-coded)

{% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}
<p>The current time is {{ my_current_time }}.</p>

To do so:
import re

class CurrentTimeNode3(template.Node):
    def __init__(self, format_string, var_name):
        self.format_string = str(format_string)
        self.var_name = var_name

    def render(self, context):
        now = datetime.datetime.now()
        context[self.var_name] = now.strftime(self.format_string)
        return ''

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:
        msg = '%r tag requires arguments' % token.contents[0]
        raise template.TemplateSyntaxError(msg)

    m = re.search(r'(.*?) as (\w+)', arg)
    if m:
        fmt, var_name = m.groups()
    else:
        msg = '%r tag had invalid arguments' % tag_name
        raise template.TemplateSyntaxError(msg)

    if not (fmt[0] == fmt[-1] and fmt[0] in ('"', "'")):
        msg = "%r tag's argument should be in quotes" % tag_name
        raise template.TemplateSyntaxError(msg)

    return CurrentTimeNode3(fmt[1:-1], var_name)

Re: Regular expression operations

Parsing Until Another Template Tag

Template tags can work as blocks containing other tags (like {% if %}, {% for %}, etc.). To create a template tag like this, use parser.parse() in your compilation function.

Comment Tag:
def do_comment(parser, token):
    nodelist = parser.parse(('endcomment',)) #parse until; return nodeList
    parser.delete_first_token() # not counting {% comment %} and {% endcomment %}
    return CommentNode()

class CommentNode(template.Node):
    def render(self, context):
        return ''


parser.parse() takes a tuple of names of template tags to parse until. It returns an instance ofdjango.template.NodeList, which is a list of all Node objects that the parser encountered before it encountered any of the tags named in the tuple.

So in the preceding example, nodelist is a list of all nodes between {% comment %} and {% endcomment %}, not counting {% comment %} and {% endcomment %} themselves.

After parser.parse() is called, the parser (not nodeList from parser.parse) hasn’t yet “consumed” the {% endcomment %} tag, so the code needs to explicitly call parser.delete_first_token() to prevent that tag from being processed twice.

Parsing Until Another Template Tag and Saving Contents {% endXXXX %}

{% upper %}
    This will appear in uppercase, {{ user_name }}.
{% endupper %}

def do_upper(parser, token):
    nodelist = parser.parse(('endupper',))
    parser.delete_first_token()
    return UpperNode(nodelist)

class UpperNode(template.Node):
    def __init__(self, nodelist):
        self.nodelist = nodelist

    def render(self, context):
        output = self.nodelist.render(context) #calls render() one each node
        return output.upper()

The only new concept here is self.nodelist.render(context) in UpperNode.render(). This simply calls render() on eachNode in the node list.

Shortcut for Simple Tags

take a single argument, and return a string -> shortcut for it
def current_time(format_string):
    try:
        return datetime.datetime.now().strftime(str(format_string))
    except UnicodeEncodeError:
        return ''

register.simple_tag(current_time)

@register.simple_tag
def current_time(token):
    # ...

  • Single: Only the (single) argument is passed into our function.
  • Error Checking: Checking for the required number of arguments has already been done by the time our function is called, so we don’t need to do that.
  • Strip: The quotes around the argument (if any) have already been stripped away, so we receive a plain Unicode string.

Inclusion Tags

Another common template tag is the type that displays some data by rendering ANOTHER template
effect:
{% books_for_author author %}

result in
<ul>
    <li>The Cat In The Hat</li>
    <li>Hop On Pop</li>
    <li>Green Eggs And Ham</li>
</ul>

1. function

def books_for_author(author):
    books = Book.objects.filter(authors__id=author.id)
    return {'books': books}

2. Template

<ul>
{% for book in books %}
    <li>{{ book.title }}</li>
{% endfor %}
</ul>

3. Register

register.inclusion_tag('book_snippet.html')(books_for_author)
shortcut:
@register.inclusion_tag('book_snippet.html')
def books_for_author(author):
    # ...

>>>

takes_context: Sometimes, your inclusion tags need access to values from the parent template’s context
@register.inclusion_tag('link.html', takes_context=True)
def jump_link(context):
    return {
        'link': context['home_link'],
        'title': context['home_title'],
    }
link.html template:
Jump directly to <a href="{{ link }}">{{ title }}</a>.
use the inclusion tag:
{% jump_link %}

Writing Custom Template Loaders

special loading logic. For example, you could load templates from a database, or directly from a Subversion repository using Subversion’s Python bindings, or (as shown shortly) from a ZIP archive.
to be continued...
http://www.djangobook.com/en/2.0/chapter09.html





done

No comments:

Post a Comment