Ticket #626: 626_import_error.patch

File 626_import_error.patch, 64.7 KB (added by Adrian Holovaty, 19 years ago)

Patch that causes circular-import problem.

  • django/core/template_loader.py

     
    11"Wrapper for loading templates from storage of some sort (e.g. files or db)"
    2 import template
    3 from template_file import load_template_source
    42
     3from django.core import template
     4from django.core.template.loaders.filesystem import load_template_source
     5
    56class ExtendsError(Exception):
    67    pass
    78
     
    2425    Loads the given template_name and renders it with the given dictionary as
    2526    context. The template_name may be a string to load a single template using
    2627    get_template, or it may be a tuple to use select_template to find one of
    27     the templates in the list.  Returns a string. 
     28    the templates in the list.  Returns a string.
    2829    """
    2930    dictionary = dictionary or {}
    3031    if isinstance(template_name, (list, tuple)):
  • django/core/defaulttags.py

     
    1 "Default tags used by the template system, available to all templates."
    2 
    3 import sys
    4 import template
    5 
    6 class CommentNode(template.Node):
    7     def render(self, context):
    8         return ''
    9 
    10 class CycleNode(template.Node):
    11     def __init__(self, cyclevars):
    12         self.cyclevars = cyclevars
    13         self.cyclevars_len = len(cyclevars)
    14         self.counter = -1
    15 
    16     def render(self, context):
    17         self.counter += 1
    18         return self.cyclevars[self.counter % self.cyclevars_len]
    19 
    20 class DebugNode(template.Node):
    21     def render(self, context):
    22         from pprint import pformat
    23         output = [pformat(val) for val in context]
    24         output.append('\n\n')
    25         output.append(pformat(sys.modules))
    26         return ''.join(output)
    27 
    28 class FilterNode(template.Node):
    29     def __init__(self, filters, nodelist):
    30         self.filters, self.nodelist = filters, nodelist
    31 
    32     def render(self, context):
    33         output = self.nodelist.render(context)
    34         # apply filters
    35         for f in self.filters:
    36             output = template.registered_filters[f[0]][0](output, f[1])
    37         return output
    38 
    39 class FirstOfNode(template.Node):
    40     def __init__(self, vars):
    41         self.vars = vars
    42 
    43     def render(self, context):
    44         for var in self.vars:
    45             value = template.resolve_variable(var, context)
    46             if value:
    47                 return str(value)
    48         return ''
    49 
    50 class ForNode(template.Node):
    51     def __init__(self, loopvar, sequence, reversed, nodelist_loop):
    52         self.loopvar, self.sequence = loopvar, sequence
    53         self.reversed = reversed
    54         self.nodelist_loop = nodelist_loop
    55 
    56     def __repr__(self):
    57         if self.reversed:
    58             reversed = ' reversed'
    59         else:
    60             reversed = ''
    61         return "<For Node: for %s in %s, tail_len: %d%s>" % \
    62             (self.loopvar, self.sequence, len(self.nodelist_loop), reversed)
    63 
    64     def __iter__(self):
    65         for node in self.nodelist_loop:
    66             yield node
    67 
    68     def get_nodes_by_type(self, nodetype):
    69         nodes = []
    70         if isinstance(self, nodetype):
    71             nodes.append(self)
    72         nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
    73         return nodes
    74 
    75     def render(self, context):
    76         nodelist = template.NodeList()
    77         if context.has_key('forloop'):
    78             parentloop = context['forloop']
    79         else:
    80             parentloop = {}
    81         context.push()
    82         try:
    83             values = template.resolve_variable_with_filters(self.sequence, context)
    84         except template.VariableDoesNotExist:
    85             values = []
    86         if values is None:
    87             values = []
    88         len_values = len(values)
    89         if self.reversed:
    90             # From http://www.python.org/doc/current/tut/node11.html
    91             def reverse(data):
    92                 for index in range(len(data)-1, -1, -1):
    93                     yield data[index]
    94             values = reverse(values)
    95         for i, item in enumerate(values):
    96             context['forloop'] = {
    97                 # shortcuts for current loop iteration number
    98                 'counter0': i,
    99                 'counter': i+1,
    100                 # reverse counter iteration numbers
    101                 'revcounter': len_values - i,
    102                 'revcounter0': len_values - i - 1,
    103                 # boolean values designating first and last times through loop
    104                 'first': (i == 0),
    105                 'last': (i == len_values - 1),
    106                 'parentloop': parentloop,
    107             }
    108             context[self.loopvar] = item
    109             for node in self.nodelist_loop:
    110                 nodelist.append(node.render(context))
    111         context.pop()
    112         return nodelist.render(context)
    113 
    114 class IfChangedNode(template.Node):
    115     def __init__(self, nodelist):
    116         self.nodelist = nodelist
    117         self._last_seen = None
    118 
    119     def render(self, context):
    120         content = self.nodelist.render(context)
    121         if content != self._last_seen:
    122             firstloop = (self._last_seen == None)
    123             self._last_seen = content
    124             context.push()
    125             context['ifchanged'] = {'firstloop': firstloop}
    126             content = self.nodelist.render(context)
    127             context.pop()
    128             return content
    129         else:
    130             return ''
    131 
    132 class IfEqualNode(template.Node):
    133     def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
    134         self.var1, self.var2 = var1, var2
    135         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    136         self.negate = negate
    137 
    138     def __repr__(self):
    139         return "<IfEqualNode>"
    140 
    141     def render(self, context):
    142         val1 = template.resolve_variable(self.var1, context)
    143         val2 = template.resolve_variable(self.var2, context)
    144         if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
    145             return self.nodelist_true.render(context)
    146         return self.nodelist_false.render(context)
    147 
    148 class IfNode(template.Node):
    149     def __init__(self, boolvars, nodelist_true, nodelist_false):
    150         self.boolvars = boolvars
    151         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    152 
    153     def __repr__(self):
    154         return "<If node>"
    155 
    156     def __iter__(self):
    157         for node in self.nodelist_true:
    158             yield node
    159         for node in self.nodelist_false:
    160             yield node
    161 
    162     def get_nodes_by_type(self, nodetype):
    163         nodes = []
    164         if isinstance(self, nodetype):
    165             nodes.append(self)
    166         nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
    167         nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
    168         return nodes
    169 
    170     def render(self, context):
    171         for ifnot, boolvar in self.boolvars:
    172             try:
    173                 value = template.resolve_variable_with_filters(boolvar, context)
    174             except template.VariableDoesNotExist:
    175                 value = None
    176             if (value and not ifnot) or (ifnot and not value):
    177                 return self.nodelist_true.render(context)
    178         return self.nodelist_false.render(context)
    179 
    180 class RegroupNode(template.Node):
    181     def __init__(self, target_var, expression, var_name):
    182         self.target_var, self.expression = target_var, expression
    183         self.var_name = var_name
    184 
    185     def render(self, context):
    186         obj_list = template.resolve_variable_with_filters(self.target_var, context)
    187         if obj_list == '': # target_var wasn't found in context; fail silently
    188             context[self.var_name] = []
    189             return ''
    190         output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]}
    191         for obj in obj_list:
    192             grouper = template.resolve_variable_with_filters('var.%s' % self.expression, \
    193                 template.Context({'var': obj}))
    194             if output and repr(output[-1]['grouper']) == repr(grouper):
    195                 output[-1]['list'].append(obj)
    196             else:
    197                 output.append({'grouper': grouper, 'list': [obj]})
    198         context[self.var_name] = output
    199         return ''
    200 
    201 def include_is_allowed(filepath):
    202     from django.conf.settings import ALLOWED_INCLUDE_ROOTS
    203     for root in ALLOWED_INCLUDE_ROOTS:
    204         if filepath.startswith(root):
    205             return True
    206     return False
    207 
    208 class SsiNode(template.Node):
    209     def __init__(self, filepath, parsed):
    210         self.filepath, self.parsed = filepath, parsed
    211 
    212     def render(self, context):
    213         if not include_is_allowed(self.filepath):
    214             return '' # Fail silently for invalid includes.
    215         try:
    216             fp = open(self.filepath, 'r')
    217             output = fp.read()
    218             fp.close()
    219         except IOError:
    220             output = ''
    221         if self.parsed:
    222             try:
    223                 t = template.Template(output)
    224                 return t.render(context)
    225             except template.TemplateSyntaxError:
    226                 return '' # Fail silently for invalid included templates.
    227         return output
    228 
    229 class LoadNode(template.Node):
    230     def __init__(self, taglib):
    231         self.taglib = taglib
    232 
    233     def load_taglib(taglib):
    234         mod = __import__("django.templatetags.%s" % taglib.split('.')[-1], '', '', [''])
    235         reload(mod)
    236         return mod
    237     load_taglib = staticmethod(load_taglib)
    238 
    239     def render(self, context):
    240         "Import the relevant module"
    241         try:
    242             self.__class__.load_taglib(self.taglib)
    243         except ImportError:
    244             pass # Fail silently for invalid loads.
    245         return ''
    246 
    247 class NowNode(template.Node):
    248     def __init__(self, format_string):
    249         self.format_string = format_string
    250 
    251     def render(self, context):
    252         from datetime import datetime
    253         from django.utils.dateformat import DateFormat
    254         df = DateFormat(datetime.now())
    255         return df.format(self.format_string)
    256 
    257 class TemplateTagNode(template.Node):
    258     mapping = {'openblock': template.BLOCK_TAG_START,
    259                'closeblock': template.BLOCK_TAG_END,
    260                'openvariable': template.VARIABLE_TAG_START,
    261                'closevariable': template.VARIABLE_TAG_END}
    262 
    263     def __init__(self, tagtype):
    264         self.tagtype = tagtype
    265 
    266     def render(self, context):
    267         return self.mapping.get(self.tagtype, '')
    268 
    269 class WidthRatioNode(template.Node):
    270     def __init__(self, val_var, max_var, max_width):
    271         self.val_var = val_var
    272         self.max_var = max_var
    273         self.max_width = max_width
    274 
    275     def render(self, context):
    276         try:
    277             value = template.resolve_variable_with_filters(self.val_var, context)
    278             maxvalue = template.resolve_variable_with_filters(self.max_var, context)
    279         except template.VariableDoesNotExist:
    280             return ''
    281         try:
    282             value = float(value)
    283             maxvalue = float(maxvalue)
    284             ratio = (value / maxvalue) * int(self.max_width)
    285         except (ValueError, ZeroDivisionError):
    286             return ''
    287         return str(int(round(ratio)))
    288 
    289 def do_comment(parser, token):
    290     """
    291     Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
    292     """
    293     nodelist = parser.parse(('endcomment',))
    294     parser.delete_first_token()
    295     return CommentNode()
    296 
    297 def do_cycle(parser, token):
    298     """
    299     Cycle among the given strings each time this tag is encountered
    300 
    301     Within a loop, cycles among the given strings each time through
    302     the loop::
    303 
    304         {% for o in some_list %}
    305             <tr class="{% cycle row1,row2 %}">
    306                 ...
    307             </tr>
    308         {% endfor %}
    309 
    310     Outside of a loop, give the values a unique name the first time you call
    311     it, then use that name each sucessive time through::
    312 
    313             <tr class="{% cycle row1,row2,row3 as rowcolors %}">...</tr>
    314             <tr class="{% cycle rowcolors %}">...</tr>
    315             <tr class="{% cycle rowcolors %}">...</tr>
    316 
    317     You can use any number of values, seperated by commas. Make sure not to
    318     put spaces between the values -- only commas.
    319     """
    320 
    321     # Note: This returns the exact same node on each {% cycle name %} call; that
    322     # is, the node object returned from {% cycle a,b,c as name %} and the one
    323     # returned from {% cycle name %} are the exact same object.  This shouldn't
    324     # cause problems (heh), but if it does, now you know.
    325     #
    326     # Ugly hack warning: this stuffs the named template dict into parser so
    327     # that names are only unique within each template (as opposed to using
    328     # a global variable, which would make cycle names have to be unique across
    329     # *all* templates.
    330 
    331     args = token.contents.split()
    332     if len(args) < 2:
    333         raise template.TemplateSyntaxError("'Cycle' statement requires at least two arguments")
    334 
    335     elif len(args) == 2 and "," in args[1]:
    336         # {% cycle a,b,c %}
    337         cyclevars = [v for v in args[1].split(",") if v]    # split and kill blanks
    338         return CycleNode(cyclevars)
    339         # {% cycle name %}
    340 
    341     elif len(args) == 2:
    342         name = args[1]
    343         if not parser._namedCycleNodes.has_key(name):
    344             raise template.TemplateSyntaxError("Named cycle '%s' does not exist" % name)
    345         return parser._namedCycleNodes[name]
    346 
    347     elif len(args) == 4:
    348         # {% cycle a,b,c as name %}
    349         if args[2] != 'as':
    350             raise template.TemplateSyntaxError("Second 'cycle' argument must be 'as'")
    351         cyclevars = [v for v in args[1].split(",") if v]    # split and kill blanks
    352         name = args[3]
    353         node = CycleNode(cyclevars)
    354 
    355         if not hasattr(parser, '_namedCycleNodes'):
    356             parser._namedCycleNodes = {}
    357 
    358         parser._namedCycleNodes[name] = node
    359         return node
    360 
    361     else:
    362         raise template.TemplateSyntaxError("Invalid arguments to 'cycle': %s" % args)
    363 
    364 def do_debug(parser, token):
    365     "Print a whole load of debugging information, including the context and imported modules"
    366     return DebugNode()
    367 
    368 def do_filter(parser, token):
    369     """
    370     Filter the contents of the blog through variable filters.
    371 
    372     Filters can also be piped through each other, and they can have
    373     arguments -- just like in variable syntax.
    374 
    375     Sample usage::
    376 
    377         {% filter escape|lower %}
    378             This text will be HTML-escaped, and will appear in lowercase.
    379         {% endfilter %}
    380     """
    381     _, rest = token.contents.split(None, 1)
    382     _, filters = template.get_filters_from_token('var|%s' % rest)
    383     nodelist = parser.parse(('endfilter',))
    384     parser.delete_first_token()
    385     return FilterNode(filters, nodelist)
    386 
    387 def do_firstof(parser, token):
    388     """
    389     Outputs the first variable passed that is not False.
    390 
    391     Outputs nothing if all the passed variables are False.
    392 
    393     Sample usage::
    394 
    395         {% firstof var1 var2 var3 %}
    396 
    397     This is equivalent to::
    398 
    399         {% if var1 %}
    400             {{ var1 }}
    401         {% else %}{% if var2 %}
    402             {{ var2 }}
    403         {% else %}{% if var3 %}
    404             {{ var3 }}
    405         {% endif %}{% endif %}{% endif %}
    406 
    407     but obviously much cleaner!
    408     """
    409     bits = token.contents.split()[1:]
    410     if len(bits) < 1:
    411         raise template.TemplateSyntaxError, "'firstof' statement requires at least one argument"
    412     return FirstOfNode(bits)
    413 
    414 
    415 def do_for(parser, token):
    416     """
    417     Loop over each item in an array.
    418 
    419     For example, to display a list of athletes given ``athlete_list``::
    420 
    421         <ul>
    422         {% for athlete in athlete_list %}
    423             <li>{{ athlete.name }}</li>
    424         {% endfor %}
    425         </ul>
    426 
    427     You can also loop over a list in reverse by using
    428     ``{% for obj in list reversed %}``.
    429 
    430     The for loop sets a number of variables available within the loop:
    431 
    432         ==========================  ================================================
    433         Variable                    Description
    434         ==========================  ================================================
    435         ``forloop.counter``         The current iteration of the loop (1-indexed)
    436         ``forloop.counter0``        The current iteration of the loop (0-indexed)
    437         ``forloop.revcounter``      The number of iterations from the end of the
    438                                     loop (1-indexed)
    439         ``forloop.revcounter0``     The number of iterations from the end of the
    440                                     loop (0-indexed)
    441         ``forloop.first``           True if this is the first time through the loop
    442         ``forloop.last``            True if this is the last time through the loop
    443         ``forloop.parentloop``      For nested loops, this is the loop "above" the
    444                                     current one
    445         ==========================  ================================================
    446 
    447     """
    448     bits = token.contents.split()
    449     if len(bits) == 5 and bits[4] != 'reversed':
    450         raise template.TemplateSyntaxError, "'for' statements with five words should end in 'reversed': %s" % token.contents
    451     if len(bits) not in (4, 5):
    452         raise template.TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents
    453     if bits[2] != 'in':
    454         raise template.TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents
    455     loopvar = bits[1]
    456     sequence = bits[3]
    457     reversed = (len(bits) == 5)
    458     nodelist_loop = parser.parse(('endfor',))
    459     parser.delete_first_token()
    460     return ForNode(loopvar, sequence, reversed, nodelist_loop)
    461 
    462 def do_ifequal(parser, token, negate):
    463     """
    464     Output the contents of the block if the two arguments equal/don't equal each other.
    465 
    466     Examples::
    467 
    468         {% ifequal user.id comment.user_id %}
    469             ...
    470         {% endifequal %}
    471 
    472         {% ifnotequal user.id comment.user_id %}
    473             ...
    474         {% else %}
    475             ...
    476         {% endifnotequal %}
    477     """
    478     bits = token.contents.split()
    479     if len(bits) != 3:
    480         raise template.TemplateSyntaxError, "%r takes two arguments" % bits[0]
    481     end_tag = 'end' + bits[0]
    482     nodelist_true = parser.parse(('else', end_tag))
    483     token = parser.next_token()
    484     if token.contents == 'else':
    485         nodelist_false = parser.parse((end_tag,))
    486         parser.delete_first_token()
    487     else:
    488         nodelist_false = template.NodeList()
    489     return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
    490 
    491 def do_if(parser, token):
    492     """
    493     The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
    494     (i.e. exists, is not empty, and is not a false boolean value) the contents
    495     of the block are output:
    496 
    497     ::
    498 
    499         {% if althlete_list %}
    500             Number of athletes: {{ althete_list|count }}
    501         {% else %}
    502             No athletes.
    503         {% endif %}
    504 
    505     In the above, if ``athlete_list`` is not empty, the number of athletes will
    506     be displayed by the ``{{ athlete_list|count }}`` variable.
    507 
    508     As you can see, the ``if`` tag can take an option ``{% else %}`` clause that
    509     will be displayed if the test fails.
    510 
    511     ``if`` tags may use ``or`` or ``not`` to test a number of variables or to
    512     negate a given variable::
    513 
    514         {% if not athlete_list %}
    515             There are no athletes.
    516         {% endif %}
    517 
    518         {% if athlete_list or coach_list %}
    519             There are some athletes or some coaches.
    520         {% endif %}
    521 
    522         {% if not athlete_list or coach_list %}
    523             There are no athletes or there are some coaches (OK, so
    524             writing English translations of boolean logic sounds
    525             stupid; it's not my fault).
    526         {% endif %}
    527 
    528     For simplicity, ``if`` tags do not allow ``and`` clauses; use nested ``if``
    529     tags instead::
    530 
    531         {% if athlete_list %}
    532             {% if coach_list %}
    533                 Number of athletes: {{ athlete_list|count }}.
    534                 Number of coaches: {{ coach_list|count }}.
    535             {% endif %}
    536         {% endif %}
    537     """
    538     bits = token.contents.split()
    539     del bits[0]
    540     if not bits:
    541         raise template.TemplateSyntaxError, "'if' statement requires at least one argument"
    542     # bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
    543     boolpairs = ' '.join(bits).split(' or ')
    544     boolvars = []
    545     for boolpair in boolpairs:
    546         if ' ' in boolpair:
    547             not_, boolvar = boolpair.split()
    548             if not_ != 'not':
    549                 raise template.TemplateSyntaxError, "Expected 'not' in if statement"
    550             boolvars.append((True, boolvar))
    551         else:
    552             boolvars.append((False, boolpair))
    553     nodelist_true = parser.parse(('else', 'endif'))
    554     token = parser.next_token()
    555     if token.contents == 'else':
    556         nodelist_false = parser.parse(('endif',))
    557         parser.delete_first_token()
    558     else:
    559         nodelist_false = template.NodeList()
    560     return IfNode(boolvars, nodelist_true, nodelist_false)
    561 
    562 def do_ifchanged(parser, token):
    563     """
    564     Check if a value has changed from the last iteration of a loop.
    565 
    566     The 'ifchanged' block tag is used within a loop. It checks its own rendered
    567     contents against its previous state and only displays its content if the
    568     value has changed::
    569 
    570         <h1>Archive for {{ year }}</h1>
    571 
    572         {% for date in days %}
    573         {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
    574         <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
    575         {% endfor %}
    576     """
    577     bits = token.contents.split()
    578     if len(bits) != 1:
    579         raise template.TemplateSyntaxError, "'ifchanged' tag takes no arguments"
    580     nodelist = parser.parse(('endifchanged',))
    581     parser.delete_first_token()
    582     return IfChangedNode(nodelist)
    583 
    584 def do_ssi(parser, token):
    585     """
    586     Output the contents of a given file into the page.
    587 
    588     Like a simple "include" tag, the ``ssi`` tag includes the contents
    589     of another file -- which must be specified using an absolute page --
    590     in the current page::
    591 
    592         {% ssi /home/html/ljworld.com/includes/right_generic.html %}
    593 
    594     If the optional "parsed" parameter is given, the contents of the included
    595     file are evaluated as template code, with the current context::
    596 
    597         {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
    598     """
    599     bits = token.contents.split()
    600     parsed = False
    601     if len(bits) not in (2, 3):
    602         raise template.TemplateSyntaxError, "'ssi' tag takes one argument: the path to the file to be included"
    603     if len(bits) == 3:
    604         if bits[2] == 'parsed':
    605             parsed = True
    606         else:
    607             raise template.TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0]
    608     return SsiNode(bits[1], parsed)
    609 
    610 def do_load(parser, token):
    611     """
    612     Load a custom template tag set.
    613 
    614     For example, to load the template tags in ``django/templatetags/news/photos.py``::
    615 
    616         {% load news.photos %}
    617     """
    618     bits = token.contents.split()
    619     if len(bits) != 2:
    620         raise template.TemplateSyntaxError, "'load' statement takes one argument"
    621     taglib = bits[1]
    622     # check at compile time that the module can be imported
    623     try:
    624         LoadNode.load_taglib(taglib)
    625     except ImportError:
    626         raise template.TemplateSyntaxError, "'%s' is not a valid tag library" % taglib
    627     return LoadNode(taglib)
    628 
    629 def do_now(parser, token):
    630     """
    631     Display the date, formatted according to the given string.
    632 
    633     Uses the same format as PHP's ``date()`` function; see http://php.net/date
    634     for all the possible values.
    635 
    636     Sample usage::
    637 
    638         It is {% now "jS F Y H:i" %}
    639     """
    640     bits = token.contents.split('"')
    641     if len(bits) != 3:
    642         raise template.TemplateSyntaxError, "'now' statement takes one argument"
    643     format_string = bits[1]
    644     return NowNode(format_string)
    645 
    646 def do_regroup(parser, token):
    647     """
    648     Regroup a list of alike objects by a common attribute.
    649 
    650     This complex tag is best illustrated by use of an example:  say that
    651     ``people`` is a list of ``Person`` objects that have ``first_name``,
    652     ``last_name``, and ``gender`` attributes, and you'd like to display a list
    653     that looks like:
    654 
    655         * Male:
    656             * George Bush
    657             * Bill Clinton
    658         * Female:
    659             * Margaret Thatcher
    660             * Colendeeza Rice
    661         * Unknown:
    662             * Pat Smith
    663 
    664     The following snippet of template code would accomplish this dubious task::
    665 
    666         {% regroup people by gender as grouped %}
    667         <ul>
    668         {% for group in grouped %}
    669             <li>{{ group.grouper }}
    670             <ul>
    671                 {% for item in group.list %}
    672                 <li>{{ item }}</li>
    673                 {% endfor %}
    674             </ul>
    675         {% endfor %}
    676         </ul>
    677 
    678     As you can see, ``{% regroup %}`` populates a variable with a list of
    679     objects with ``grouper`` and ``list`` attributes.  ``grouper`` contains the
    680     item that was grouped by; ``list`` contains the list of objects that share
    681     that ``grouper``.  In this case, ``grouper`` would be ``Male``, ``Female``
    682     and ``Unknown``, and ``list`` is the list of people with those genders.
    683 
    684     Note that `{% regroup %}`` does not work when the list to be grouped is not
    685     sorted by the key you are grouping by!  This means that if your list of
    686     people was not sorted by gender, you'd need to make sure it is sorted before
    687     using it, i.e.::
    688 
    689         {% regroup people|dictsort:"gender" by gender as grouped %}
    690 
    691     """
    692     firstbits = token.contents.split(None, 3)
    693     if len(firstbits) != 4:
    694         raise template.TemplateSyntaxError, "'regroup' tag takes five arguments"
    695     target_var = firstbits[1]
    696     if firstbits[2] != 'by':
    697         raise template.TemplateSyntaxError, "second argument to 'regroup' tag must be 'by'"
    698     lastbits_reversed = firstbits[3][::-1].split(None, 2)
    699     if lastbits_reversed[1][::-1] != 'as':
    700         raise template.TemplateSyntaxError, "next-to-last argument to 'regroup' tag must be 'as'"
    701     expression = lastbits_reversed[2][::-1]
    702     var_name = lastbits_reversed[0][::-1]
    703     return RegroupNode(target_var, expression, var_name)
    704 
    705 def do_templatetag(parser, token):
    706     """
    707     Output one of the bits used to compose template tags.
    708 
    709     Since the template system has no concept of "escaping", to display one of
    710     the bits used in template tags, you must use the ``{% templatetag %}`` tag.
    711 
    712     The argument tells which template bit to output:
    713 
    714         ==================  =======
    715         Argument            Outputs
    716         ==================  =======
    717         ``openblock``       ``{%``
    718         ``closeblock``      ``%}``
    719         ``openvariable``    ``{{``
    720         ``closevariable``   ``}}``
    721         ==================  =======
    722     """
    723     bits = token.contents.split()
    724     if len(bits) != 2:
    725         raise template.TemplateSyntaxError, "'templatetag' statement takes one argument"
    726     tag = bits[1]
    727     if not TemplateTagNode.mapping.has_key(tag):
    728         raise template.TemplateSyntaxError, "Invalid templatetag argument: '%s'. Must be one of: %s" % \
    729             (tag, TemplateTagNode.mapping.keys())
    730     return TemplateTagNode(tag)
    731 
    732 def do_widthratio(parser, token):
    733     """
    734     For creating bar charts and such, this tag calculates the ratio of a given
    735     value to a maximum value, and then applies that ratio to a constant.
    736 
    737     For example::
    738 
    739         <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
    740 
    741     Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
    742     the above example will be 88 pixels wide (because 175/200 = .875; .875 *
    743     100 = 87.5 which is rounded up to 88).
    744     """
    745     bits = token.contents.split()
    746     if len(bits) != 4:
    747         raise template.TemplateSyntaxError("widthratio takes three arguments")
    748     tag, this_value_var, max_value_var, max_width = bits
    749     try:
    750         max_width = int(max_width)
    751     except ValueError:
    752         raise template.TemplateSyntaxError("widthratio final argument must be an integer")
    753     return WidthRatioNode(this_value_var, max_value_var, max_width)
    754 
    755 template.register_tag('comment', do_comment)
    756 template.register_tag('cycle', do_cycle)
    757 template.register_tag('debug', do_debug)
    758 template.register_tag('filter', do_filter)
    759 template.register_tag('firstof', do_firstof)
    760 template.register_tag('for', do_for)
    761 template.register_tag('ifequal', lambda parser, token: do_ifequal(parser, token, False))
    762 template.register_tag('ifnotequal', lambda parser, token: do_ifequal(parser, token, True))
    763 template.register_tag('if', do_if)
    764 template.register_tag('ifchanged', do_ifchanged)
    765 template.register_tag('regroup', do_regroup)
    766 template.register_tag('ssi', do_ssi)
    767 template.register_tag('load', do_load)
    768 template.register_tag('now', do_now)
    769 template.register_tag('templatetag', do_templatetag)
    770 template.register_tag('widthratio', do_widthratio)
  • django/core/template_file.py

     
    1 # Wrapper for loading templates from files
    2 
    3 from django.conf.settings import TEMPLATE_DIRS, TEMPLATE_FILE_EXTENSION
    4 from django.core.template import TemplateDoesNotExist
    5 import os
    6 
    7 def load_template_source(template_name, template_dirs=None):
    8     if not template_dirs:
    9         template_dirs = TEMPLATE_DIRS
    10     tried = []
    11     for template_dir in template_dirs:
    12         filepath = os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION
    13         try:
    14             return open(filepath).read()
    15         except IOError:
    16             tried.append(filepath)
    17     if template_dirs:
    18         error_msg = "Tried %s" % tried
    19     else:
    20         error_msg = "Your TEMPLATE_DIRS settings is empty. Change it to point to at least one template directory."
    21     raise TemplateDoesNotExist, error_msg
  • django/core/template.py

     
    1 """
    2 This is the Django template system.
    3 
    4 How it works:
    5 
    6 The tokenize() function converts a template string (i.e., a string containing
    7 markup with custom template tags) to tokens, which can be either plain text
    8 (TOKEN_TEXT), variables (TOKEN_VAR) or block statements (TOKEN_BLOCK).
    9 
    10 The Parser() class takes a list of tokens in its constructor, and its parse()
    11 method returns a compiled template -- which is, under the hood, a list of
    12 Node objects.
    13 
    14 Each Node is responsible for creating some sort of output -- e.g. simple text
    15 (TextNode), variable values in a given context (VariableNode), results of basic
    16 logic (IfNode), results of looping (ForNode), or anything else. The core Node
    17 types are TextNode, VariableNode, IfNode and ForNode, but plugin modules can
    18 define their own custom node types.
    19 
    20 Each Node has a render() method, which takes a Context and returns a string of
    21 the rendered node. For example, the render() method of a Variable Node returns
    22 the variable's value as a string. The render() method of an IfNode returns the
    23 rendered output of whatever was inside the loop, recursively.
    24 
    25 The Template class is a convenient wrapper that takes care of template
    26 compilation and rendering.
    27 
    28 Usage:
    29 
    30 The only thing you should ever use directly in this file is the Template class.
    31 Create a compiled template object with a template_string, then call render()
    32 with a context. In the compilation stage, the TemplateSyntaxError exception
    33 will be raised if the template doesn't have proper syntax.
    34 
    35 Sample code:
    36 
    37 >>> import template
    38 >>> s = '''
    39 ... <html>
    40 ... {% if test %}
    41 ...     <h1>{{ varvalue }}</h1>
    42 ... {% endif %}
    43 ... </html>
    44 ... '''
    45 >>> t = template.Template(s)
    46 
    47 (t is now a compiled template, and its render() method can be called multiple
    48 times with multiple contexts)
    49 
    50 >>> c = template.Context({'test':True, 'varvalue': 'Hello'})
    51 >>> t.render(c)
    52 '\n<html>\n\n    <h1>Hello</h1>\n\n</html>\n'
    53 >>> c = template.Context({'test':False, 'varvalue': 'Hello'})
    54 >>> t.render(c)
    55 '\n<html>\n\n</html>\n'
    56 """
    57 import re
    58 from django.conf.settings import DEFAULT_CHARSET
    59 
    60 __all__ = ('Template','Context','compile_string')
    61 
    62 TOKEN_TEXT = 0
    63 TOKEN_VAR = 1
    64 TOKEN_BLOCK = 2
    65 
    66 # template syntax constants
    67 FILTER_SEPARATOR = '|'
    68 FILTER_ARGUMENT_SEPARATOR = ':'
    69 VARIABLE_ATTRIBUTE_SEPARATOR = '.'
    70 BLOCK_TAG_START = '{%'
    71 BLOCK_TAG_END = '%}'
    72 VARIABLE_TAG_START = '{{'
    73 VARIABLE_TAG_END = '}}'
    74 
    75 ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
    76 
    77 # match a variable or block tag and capture the entire tag, including start/end delimiters
    78 tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
    79                                           re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END)))
    80 
    81 # global dict used by register_tag; maps custom tags to callback functions
    82 registered_tags = {}
    83 
    84 # global dict used by register_filter; maps custom filters to callback functions
    85 registered_filters = {}
    86 
    87 class TemplateSyntaxError(Exception):
    88     pass
    89 
    90 class ContextPopException(Exception):
    91     "pop() has been called more times than push()"
    92     pass
    93 
    94 class TemplateDoesNotExist(Exception):
    95     pass
    96 
    97 class VariableDoesNotExist(Exception):
    98     pass
    99 
    100 class SilentVariableFailure(Exception):
    101     "Any function raising this exception will be ignored by resolve_variable"
    102     pass
    103 
    104 class Template:
    105     def __init__(self, template_string):
    106         "Compilation stage"
    107         self.nodelist = compile_string(template_string)
    108 
    109     def __iter__(self):
    110         for node in self.nodelist:
    111             for subnode in node:
    112                 yield subnode
    113 
    114     def render(self, context):
    115         "Display stage -- can be called many times"
    116         return self.nodelist.render(context)
    117 
    118 def compile_string(template_string):
    119     "Compiles template_string into NodeList ready for rendering"
    120     tokens = tokenize(template_string)
    121     parser = Parser(tokens)
    122     return parser.parse()
    123 
    124 class Context:
    125     "A stack container for variable context"
    126     def __init__(self, dict=None):
    127         dict = dict or {}
    128         self.dicts = [dict]
    129 
    130     def __repr__(self):
    131         return repr(self.dicts)
    132 
    133     def __iter__(self):
    134         for d in self.dicts:
    135             yield d
    136 
    137     def push(self):
    138         self.dicts = [{}] + self.dicts
    139 
    140     def pop(self):
    141         if len(self.dicts) == 1:
    142             raise ContextPopException
    143         del self.dicts[0]
    144 
    145     def __setitem__(self, key, value):
    146         "Set a variable in the current context"
    147         self.dicts[0][key] = value
    148 
    149     def __getitem__(self, key):
    150         "Get a variable's value, starting at the current context and going upward"
    151         for dict in self.dicts:
    152             if dict.has_key(key):
    153                 return dict[key]
    154         return ''
    155 
    156     def __delitem__(self, key):
    157         "Delete a variable from the current context"
    158         del self.dicts[0][key]
    159 
    160     def has_key(self, key):
    161         for dict in self.dicts:
    162             if dict.has_key(key):
    163                 return True
    164         return False
    165 
    166     def update(self, other_dict):
    167         "Like dict.update(). Pushes an entire dictionary's keys and values onto the context."
    168         self.dicts = [other_dict] + self.dicts
    169 
    170 class Token:
    171     def __init__(self, token_type, contents):
    172         "The token_type must be TOKEN_TEXT, TOKEN_VAR or TOKEN_BLOCK"
    173         self.token_type, self.contents = token_type, contents
    174 
    175     def __str__(self):
    176         return '<%s token: "%s...">' % (
    177             {TOKEN_TEXT:'Text', TOKEN_VAR:'Var', TOKEN_BLOCK:'Block'}[self.token_type],
    178             self.contents[:20].replace('\n', '')
    179             )
    180 
    181 def tokenize(template_string):
    182     "Return a list of tokens from a given template_string"
    183     # remove all empty strings, because the regex has a tendency to add them
    184     bits = filter(None, tag_re.split(template_string))
    185     return map(create_token, bits)
    186 
    187 def create_token(token_string):
    188     "Convert the given token string into a new Token object and return it"
    189     if token_string.startswith(VARIABLE_TAG_START):
    190         return Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
    191     elif token_string.startswith(BLOCK_TAG_START):
    192         return Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
    193     else:
    194         return Token(TOKEN_TEXT, token_string)
    195 
    196 class Parser:
    197     def __init__(self, tokens):
    198         self.tokens = tokens
    199 
    200     def parse(self, parse_until=[]):
    201         nodelist = NodeList()
    202         while self.tokens:
    203             token = self.next_token()
    204             if token.token_type == TOKEN_TEXT:
    205                 nodelist.append(TextNode(token.contents))
    206             elif token.token_type == TOKEN_VAR:
    207                 if not token.contents:
    208                     raise TemplateSyntaxError, "Empty variable tag"
    209                 nodelist.append(VariableNode(token.contents))
    210             elif token.token_type == TOKEN_BLOCK:
    211                 if token.contents in parse_until:
    212                     # put token back on token list so calling code knows why it terminated
    213                     self.prepend_token(token)
    214                     return nodelist
    215                 try:
    216                     command = token.contents.split()[0]
    217                 except IndexError:
    218                     raise TemplateSyntaxError, "Empty block tag"
    219                 try:
    220                     # execute callback function for this tag and append resulting node
    221                     nodelist.append(registered_tags[command](self, token))
    222                 except KeyError:
    223                     raise TemplateSyntaxError, "Invalid block tag: '%s'" % command
    224         if parse_until:
    225             raise TemplateSyntaxError, "Unclosed tag(s): '%s'" % ', '.join(parse_until)
    226         return nodelist
    227 
    228     def next_token(self):
    229         return self.tokens.pop(0)
    230 
    231     def prepend_token(self, token):
    232         self.tokens.insert(0, token)
    233 
    234     def delete_first_token(self):
    235         del self.tokens[0]
    236 
    237 class FilterParser:
    238     """Parse a variable token and its optional filters (all as a single string),
    239        and return a list of tuples of the filter name and arguments.
    240        Sample:
    241             >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
    242             >>> p = FilterParser(token)
    243             >>> p.filters
    244             [('default', 'Default value'), ('date', 'Y-m-d')]
    245             >>> p.var
    246             'variable'
    247 
    248         This class should never be instantiated outside of the
    249         get_filters_from_token helper function.
    250     """
    251     def __init__(self, s):
    252         self.s = s
    253         self.i = -1
    254         self.current = ''
    255         self.filters = []
    256         self.current_filter_name = None
    257         self.current_filter_arg = None
    258         # First read the variable part
    259         self.var = self.read_alphanumeric_token()
    260         if not self.var:
    261             raise TemplateSyntaxError, "Could not read variable name: '%s'" % self.s
    262         if self.var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or self.var[0] == '_':
    263             raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % self.var
    264         # Have we reached the end?
    265         if self.current is None:
    266             return
    267         if self.current != FILTER_SEPARATOR:
    268             raise TemplateSyntaxError, "Bad character (expecting '%s') '%s'" % (FILTER_SEPARATOR, self.current)
    269         # We have a filter separator; start reading the filters
    270         self.read_filters()
    271 
    272     def next_char(self):
    273         self.i = self.i + 1
    274         try:
    275             self.current = self.s[self.i]
    276         except IndexError:
    277             self.current = None
    278 
    279     def read_alphanumeric_token(self):
    280         """Read a variable name or filter name, which are continuous strings of
    281         alphanumeric characters + the underscore"""
    282         var = ''
    283         while 1:
    284             self.next_char()
    285             if self.current is None:
    286                 break
    287             if self.current not in ALLOWED_VARIABLE_CHARS:
    288                 break
    289             var += self.current
    290         return var
    291 
    292     def read_filters(self):
    293         while 1:
    294             filter_name, arg = self.read_filter()
    295             if not registered_filters.has_key(filter_name):
    296                 raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name
    297             if registered_filters[filter_name][1] == True and arg is None:
    298                 raise TemplateSyntaxError, "Filter '%s' requires an argument" % filter_name
    299             if registered_filters[filter_name][1] == False and arg is not None:
    300                 raise TemplateSyntaxError, "Filter '%s' should not have an argument (argument is %r)" % (filter_name, arg)
    301             self.filters.append((filter_name, arg))
    302             if self.current is None:
    303                 break
    304 
    305     def read_filter(self):
    306         self.current_filter_name = self.read_alphanumeric_token()
    307         self.current_filter_arg = None
    308         # Have we reached the end?
    309         if self.current is None:
    310             return (self.current_filter_name, None)
    311         # Does the filter have an argument?
    312         if self.current == FILTER_ARGUMENT_SEPARATOR:
    313             self.current_filter_arg = self.read_arg()
    314             return (self.current_filter_name, self.current_filter_arg)
    315         # Next thing MUST be a pipe
    316         if self.current != FILTER_SEPARATOR:
    317             raise TemplateSyntaxError, "Bad character (expecting '%s') '%s'" % (FILTER_SEPARATOR, self.current)
    318         return (self.current_filter_name, self.current_filter_arg)
    319 
    320     def read_arg(self):
    321         # First read a "
    322         self.next_char()
    323         if self.current != '"':
    324             raise TemplateSyntaxError, "Bad character (expecting '\"') '%s'" % self.current
    325         self.escaped = False
    326         arg = ''
    327         while 1:
    328             self.next_char()
    329             if self.current == '"' and not self.escaped:
    330                 break
    331             if self.current == '\\' and not self.escaped:
    332                 self.escaped = True
    333                 continue
    334             if self.current == '\\' and self.escaped:
    335                 arg += '\\'
    336                 self.escaped = False
    337                 continue
    338             if self.current == '"' and self.escaped:
    339                 arg += '"'
    340                 self.escaped = False
    341                 continue
    342             if self.escaped and self.current not in '\\"':
    343                 raise TemplateSyntaxError, "Unescaped backslash in '%s'" % self.s
    344             if self.current is None:
    345                 raise TemplateSyntaxError, "Unexpected end of argument in '%s'" % self.s
    346             arg += self.current
    347         # self.current must now be '"'
    348         self.next_char()
    349         return arg
    350 
    351 def get_filters_from_token(token):
    352     "Convenient wrapper for FilterParser"
    353     p = FilterParser(token)
    354     return (p.var, p.filters)
    355 
    356 def resolve_variable(path, context):
    357     """
    358     Returns the resolved variable, which may contain attribute syntax, within
    359     the given context. The variable may be a hard-coded string (if it begins
    360     and ends with single or double quote marks).
    361 
    362     >>> c = {'article': {'section':'News'}}
    363     >>> resolve_variable('article.section', c)
    364     'News'
    365     >>> resolve_variable('article', c)
    366     {'section': 'News'}
    367     >>> class AClass: pass
    368     >>> c = AClass()
    369     >>> c.article = AClass()
    370     >>> c.article.section = 'News'
    371     >>> resolve_variable('article.section', c)
    372     'News'
    373 
    374     (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
    375     """
    376     if path[0] in ('"', "'") and path[0] == path[-1]:
    377         current = path[1:-1]
    378     else:
    379         current = context
    380         bits = path.split(VARIABLE_ATTRIBUTE_SEPARATOR)
    381         while bits:
    382             try: # dictionary lookup
    383                 current = current[bits[0]]
    384             except (TypeError, AttributeError, KeyError):
    385                 try: # attribute lookup
    386                     current = getattr(current, bits[0])
    387                     if callable(current):
    388                         if getattr(current, 'alters_data', False):
    389                             current = ''
    390                         else:
    391                             try: # method call (assuming no args required)
    392                                 current = current()
    393                             except SilentVariableFailure:
    394                                 current = ''
    395                             except TypeError: # arguments *were* required
    396                                 current = '' # invalid method call
    397                 except (TypeError, AttributeError):
    398                     try: # list-index lookup
    399                         current = current[int(bits[0])]
    400                     except (IndexError, ValueError, KeyError):
    401                         raise VariableDoesNotExist, "Failed lookup for key [%s] in %r" % (bits[0], current) # missing attribute
    402             del bits[0]
    403     return current
    404 
    405 def resolve_variable_with_filters(var_string, context):
    406     """
    407     var_string is a full variable expression with optional filters, like:
    408         a.b.c|lower|date:"y/m/d"
    409     This function resolves the variable in the context, applies all filters and
    410     returns the object.
    411     """
    412     var, filters = get_filters_from_token(var_string)
    413     try:
    414         obj = resolve_variable(var, context)
    415     except VariableDoesNotExist:
    416         obj = ''
    417     for name, arg in filters:
    418         obj = registered_filters[name][0](obj, arg)
    419     return obj
    420 
    421 class Node:
    422     def render(self, context):
    423         "Return the node rendered as a string"
    424         pass
    425 
    426     def __iter__(self):
    427         yield self
    428 
    429     def get_nodes_by_type(self, nodetype):
    430         "Return a list of all nodes (within this node and its nodelist) of the given type"
    431         nodes = []
    432         if isinstance(self, nodetype):
    433             nodes.append(self)
    434         if hasattr(self, 'nodelist'):
    435             nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
    436         return nodes
    437 
    438 class NodeList(list):
    439     def render(self, context):
    440         bits = []
    441         for node in self:
    442             if isinstance(node, Node):
    443                 bits.append(node.render(context))
    444             else:
    445                 bits.append(node)
    446         return ''.join(bits)
    447 
    448     def get_nodes_by_type(self, nodetype):
    449         "Return a list of all nodes of the given type"
    450         nodes = []
    451         for node in self:
    452             nodes.extend(node.get_nodes_by_type(nodetype))
    453         return nodes
    454 
    455 class TextNode(Node):
    456     def __init__(self, s):
    457         self.s = s
    458 
    459     def __repr__(self):
    460         return "<Text Node: '%s'>" % self.s[:25]
    461 
    462     def render(self, context):
    463         return self.s
    464 
    465 class VariableNode(Node):
    466     def __init__(self, var_string):
    467         self.var_string = var_string
    468 
    469     def __repr__(self):
    470         return "<Variable Node: %s>" % self.var_string
    471 
    472     def render(self, context):
    473         output = resolve_variable_with_filters(self.var_string, context)
    474         # Check type so that we don't run str() on a Unicode object
    475         if not isinstance(output, basestring):
    476             output = str(output)
    477         elif isinstance(output, unicode):
    478             output = output.encode(DEFAULT_CHARSET)
    479         return output
    480 
    481 def register_tag(token_command, callback_function):
    482     registered_tags[token_command] = callback_function
    483 
    484 def unregister_tag(token_command):
    485     del registered_tags[token_command]
    486 
    487 def register_filter(filter_name, callback_function, has_arg):
    488     registered_filters[filter_name] = (callback_function, has_arg)
    489 
    490 def unregister_filter(filter_name):
    491     del registered_filters[filter_name]
    492 
    493 import defaulttags
    494 import defaultfilters
  • django/core/defaultfilters.py

     
    1 "Default variable filters"
    2 
    3 import template, re
    4 import random as random_module
    5 
    6 ###################
    7 # STRINGS         #
    8 ###################
    9 
    10 def addslashes(value, _):
    11     "Adds slashes - useful for passing strings to JavaScript, for example."
    12     return value.replace('"', '\\"').replace("'", "\\'")
    13 
    14 def capfirst(value, _):
    15     "Capitalizes the first character of the value"
    16     value = str(value)
    17     return value and value[0].upper() + value[1:]
    18 
    19 def fix_ampersands(value, _):
    20     "Replaces ampersands with ``&amp;`` entities"
    21     from django.utils.html import fix_ampersands
    22     return fix_ampersands(value)
    23 
    24 def floatformat(text, _):
    25     """
    26     Displays a floating point number as 34.2 (with one decimal place) - but
    27     only if there's a point to be displayed
    28     """
    29     from math import modf
    30     if not text:
    31         return ''
    32     if modf(float(text))[0] < 0.1:
    33         return text
    34     return "%.1f" % float(text)
    35 
    36 def linenumbers(value, _):
    37     "Displays text with line numbers"
    38     from django.utils.html import escape
    39     lines = value.split('\n')
    40     # Find the maximum width of the line count, for use with zero padding string format command
    41     width = str(len(str(len(lines))))
    42     for i, line in enumerate(lines):
    43         lines[i] = ("%0" + width  + "d. %s") % (i + 1, escape(line))
    44     return '\n'.join(lines)
    45 
    46 def lower(value, _):
    47     "Converts a string into all lowercase"
    48     return value.lower()
    49 
    50 def make_list(value, _):
    51     """
    52     Returns the value turned into a list. For an integer, it's a list of
    53     digits. For a string, it's a list of characters.
    54     """
    55     return list(str(value))
    56 
    57 def slugify(value, _):
    58     "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
    59     value = re.sub('[^\w\s-]', '', value).strip().lower()
    60     return re.sub('\s+', '-', value)
    61 
    62 def stringformat(value, arg):
    63     """
    64     Formats the variable according to the argument, a string formatting specifier.
    65     This specifier uses Python string formating syntax, with the exception that
    66     the leading "%" is dropped.
    67 
    68     See http://docs.python.org/lib/typesseq-strings.html for documentation
    69     of Python string formatting
    70     """
    71     try:
    72         return ("%" + arg) % value
    73     except (ValueError, TypeError):
    74         return ""
    75 
    76 def title(value, _):
    77     "Converts a string into titlecase"
    78     return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
    79 
    80 def truncatewords(value, arg):
    81     """
    82     Truncates a string after a certain number of words
    83 
    84     Argument: Number of words to truncate after
    85     """
    86     from django.utils.text import truncate_words
    87     try:
    88         length = int(arg)
    89     except ValueError: # invalid literal for int()
    90         return value # Fail silently.
    91     if not isinstance(value, basestring):
    92         value = str(value)
    93     return truncate_words(value, length)
    94 
    95 def upper(value, _):
    96     "Converts a string into all uppercase"
    97     return value.upper()
    98 
    99 def urlencode(value, _):
    100     "Escapes a value for use in a URL"
    101     import urllib
    102     return urllib.quote(value)
    103 
    104 def urlize(value, _):
    105     "Converts URLs in plain text into clickable links"
    106     from django.utils.html import urlize
    107     return urlize(value, nofollow=True)
    108 
    109 def urlizetrunc(value, limit):
    110     """
    111     Converts URLs into clickable links, truncating URLs to the given character limit
    112 
    113     Argument: Length to truncate URLs to.
    114     """
    115     from django.utils.html import urlize
    116     return urlize(value, trim_url_limit=int(limit), nofollow=True)
    117 
    118 def wordcount(value, _):
    119     "Returns the number of words"
    120     return len(value.split())
    121 
    122 def wordwrap(value, arg):
    123     """
    124     Wraps words at specified line length
    125 
    126     Argument: number of words to wrap the text at.
    127     """
    128     from django.utils.text import wrap
    129     return wrap(value, int(arg))
    130 
    131 def ljust(value, arg):
    132     """
    133     Left-aligns the value in a field of a given width
    134 
    135     Argument: field size
    136     """
    137     return str(value).ljust(int(arg))
    138 
    139 def rjust(value, arg):
    140     """
    141     Right-aligns the value in a field of a given width
    142 
    143     Argument: field size
    144     """
    145     return str(value).rjust(int(arg))
    146 
    147 def center(value, arg):
    148     "Centers the value in a field of a given width"
    149     return str(value).center(int(arg))
    150 
    151 def cut(value, arg):
    152     "Removes all values of arg from the given string"
    153     return value.replace(arg, '')
    154 
    155 ###################
    156 # HTML STRINGS    #
    157 ###################
    158 
    159 def escape(value, _):
    160     "Escapes a string's HTML"
    161     from django.utils.html import escape
    162     return escape(value)
    163 
    164 def linebreaks(value, _):
    165     "Converts newlines into <p> and <br />s"
    166     from django.utils.html import linebreaks
    167     return linebreaks(value)
    168 
    169 def linebreaksbr(value, _):
    170     "Converts newlines into <br />s"
    171     return value.replace('\n', '<br />')
    172 
    173 def removetags(value, tags):
    174     "Removes a space separated list of [X]HTML tags from the output"
    175     tags = [re.escape(tag) for tag in tags.split()]
    176     tags_re = '(%s)' % '|'.join(tags)
    177     starttag_re = re.compile('<%s(>|(\s+[^>]*>))' % tags_re)
    178     endtag_re = re.compile('</%s>' % tags_re)
    179     value = starttag_re.sub('', value)
    180     value = endtag_re.sub('', value)
    181     return value
    182 
    183 def striptags(value, _):
    184     "Strips all [X]HTML tags"
    185     from django.utils.html import strip_tags
    186     if not isinstance(value, basestring):
    187         value = str(value)
    188     return strip_tags(value)
    189 
    190 ###################
    191 # LISTS           #
    192 ###################
    193 
    194 def dictsort(value, arg):
    195     """
    196     Takes a list of dicts, returns that list sorted by the property given in
    197     the argument.
    198     """
    199     decorated = [(template.resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
    200     decorated.sort()
    201     return [item[1] for item in decorated]
    202 
    203 def dictsortreversed(value, arg):
    204     """
    205     Takes a list of dicts, returns that list sorted in reverse order by the
    206     property given in the argument.
    207     """
    208     decorated = [(template.resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
    209     decorated.sort()
    210     decorated.reverse()
    211     return [item[1] for item in decorated]
    212 
    213 def first(value, _):
    214     "Returns the first item in a list"
    215     try:
    216         return value[0]
    217     except IndexError:
    218         return ''
    219 
    220 def join(value, arg):
    221     "Joins a list with a string, like Python's ``str.join(list)``"
    222     try:
    223         return arg.join(map(str, value))
    224     except AttributeError: # fail silently but nicely
    225         return value
    226 
    227 def length(value, _):
    228     "Returns the length of the value - useful for lists"
    229     return len(value)
    230 
    231 def length_is(value, arg):
    232     "Returns a boolean of whether the value's length is the argument"
    233     return len(value) == int(arg)
    234 
    235 def random(value, _):
    236     "Returns a random item from the list"
    237     return random_module.choice(value)
    238 
    239 def slice_(value, arg):
    240     """
    241     Returns a slice of the list.
    242 
    243     Uses the same syntax as Python's list slicing; see
    244     http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
    245     for an introduction.
    246     """
    247     try:
    248         return value[slice(*[x and int(x) or None for x in arg.split(':')])]
    249     except (ValueError, TypeError):
    250         return value # Fail silently.
    251 
    252 def unordered_list(value, _):
    253     """
    254     Recursively takes a self-nested list and returns an HTML unordered list --
    255     WITHOUT opening and closing <ul> tags.
    256 
    257     The list is assumed to be in the proper format. For example, if ``var`` contains
    258     ``['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]``,
    259     then ``{{ var|unordered_list }}`` would return::
    260 
    261         <li>States
    262         <ul>
    263                 <li>Kansas
    264                 <ul>
    265                         <li>Lawrence</li>
    266                         <li>Topeka</li>
    267                 </ul>
    268                 </li>
    269                 <li>Illinois</li>
    270         </ul>
    271         </li>
    272     """
    273     def _helper(value, tabs):
    274         indent = '\t' * tabs
    275         if value[1]:
    276             return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,
    277                 '\n'.join([unordered_list(v, tabs+1) for v in value[1]]), indent, indent)
    278         else:
    279             return '%s<li>%s</li>' % (indent, value[0])
    280     return _helper(value, 1)
    281 
    282 ###################
    283 # INTEGERS        #
    284 ###################
    285 
    286 def add(value, arg):
    287     "Adds the arg to the value"
    288     return int(value) + int(arg)
    289 
    290 def get_digit(value, arg):
    291     """
    292     Given a whole number, returns the requested digit of it, where 1 is the
    293     right-most digit, 2 is the second-right-most digit, etc. Returns the
    294     original value for invalid input (if input or argument is not an integer,
    295     or if argument is less than 1). Otherwise, output is always an integer.
    296     """
    297     try:
    298         arg = int(arg)
    299         value = int(value)
    300     except ValueError:
    301         return value # Fail silently for an invalid argument
    302     if arg < 1:
    303         return value
    304     try:
    305         return int(str(value)[-arg])
    306     except IndexError:
    307         return 0
    308 
    309 ###################
    310 # DATES           #
    311 ###################
    312 
    313 def date(value, arg):
    314     "Formats a date according to the given format"
    315     from django.utils.dateformat import format
    316     return format(value, arg)
    317 
    318 def time(value, arg):
    319     "Formats a time according to the given format"
    320     from django.utils.dateformat import time_format
    321     return time_format(value, arg)
    322 
    323 def timesince(value, _):
    324     'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
    325     from django.utils.timesince import timesince
    326     return timesince(value)
    327 
    328 ###################
    329 # LOGIC           #
    330 ###################
    331 
    332 def default(value, arg):
    333     "If value is unavailable, use given default"
    334     return value or arg
    335 
    336 def default_if_none(value, arg):
    337     "If value is None, use given default"
    338     if value is None:
    339         return arg
    340     return value
    341 
    342 def divisibleby(value, arg):
    343     "Returns true if the value is devisible by the argument"
    344     return int(value) % int(arg) == 0
    345 
    346 def yesno(value, arg):
    347     """
    348     Given a string mapping values for true, false and (optionally) None,
    349     returns one of those strings accoding to the value:
    350 
    351     ==========  ======================  ==================================
    352     Value       Argument                Outputs
    353     ==========  ======================  ==================================
    354     ``True``    ``"yeah,no,maybe"``     ``yeah``
    355     ``False``   ``"yeah,no,maybe"``     ``no``
    356     ``None``    ``"yeah,no,maybe"``     ``maybe``
    357     ``None``    ``"yeah,no"``           ``"no"`` (converts None to False
    358                                         if no mapping for None is given.
    359     ==========  ======================  ==================================
    360    """
    361     bits = arg.split(',')
    362     if len(bits) < 2:
    363         return value # Invalid arg.
    364     try:
    365         yes, no, maybe = bits
    366     except ValueError: # unpack list of wrong size (no "maybe" value provided)
    367         yes, no, maybe = bits[0], bits[1], bits[1]
    368     if value is None:
    369         return maybe
    370     if value:
    371         return yes
    372     return no
    373 
    374 ###################
    375 # MISC            #
    376 ###################
    377 
    378 def filesizeformat(bytes, _):
    379     """
    380     Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
    381     bytes, etc).
    382     """
    383     bytes = float(bytes)
    384     if bytes < 1024:
    385         return "%d byte%s" % (bytes, bytes != 1 and 's' or '')
    386     if bytes < 1024 * 1024:
    387         return "%.1f KB" % (bytes / 1024)
    388     if bytes < 1024 * 1024 * 1024:
    389         return "%.1f MB" % (bytes / (1024 * 1024))
    390     return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
    391 
    392 def pluralize(value, _):
    393     "Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'"
    394     try:
    395         if int(value) != 1:
    396             return 's'
    397     except ValueError: # invalid string that's not a number
    398         pass
    399     except TypeError: # value isn't a string or a number; maybe it's a list?
    400         try:
    401             if len(value) != 1:
    402                 return 's'
    403         except TypeError: # len() of unsized object
    404             pass
    405     return ''
    406 
    407 def phone2numeric(value, _):
    408     "Takes a phone number and converts it in to its numerical equivalent"
    409     from django.utils.text import phone2numeric
    410     return phone2numeric(value)
    411 
    412 def pprint(value, _):
    413     "A wrapper around pprint.pprint -- for debugging, really"
    414     from pprint import pformat
    415     return pformat(value)
    416 
    417 # Syntax: template.register_filter(name of filter, callback, has_argument)
    418 template.register_filter('add', add, True)
    419 template.register_filter('addslashes', addslashes, False)
    420 template.register_filter('capfirst', capfirst, False)
    421 template.register_filter('center', center, True)
    422 template.register_filter('cut', cut, True)
    423 template.register_filter('date', date, True)
    424 template.register_filter('default', default, True)
    425 template.register_filter('dictsort', dictsort, True)
    426 template.register_filter('dictsortreversed', dictsortreversed, True)
    427 template.register_filter('divisibleby', divisibleby, True)
    428 template.register_filter('escape', escape, False)
    429 template.register_filter('filesizeformat', filesizeformat, False)
    430 template.register_filter('first', first, False)
    431 template.register_filter('fix_ampersands', fix_ampersands, False)
    432 template.register_filter('floatformat', floatformat, False)
    433 template.register_filter('get_digit', get_digit, True)
    434 template.register_filter('join', join, True)
    435 template.register_filter('length', length, False)
    436 template.register_filter('length_is', length_is, True)
    437 template.register_filter('linebreaks', linebreaks, False)
    438 template.register_filter('linebreaksbr', linebreaksbr, False)
    439 template.register_filter('linenumbers', linenumbers, False)
    440 template.register_filter('ljust', ljust, True)
    441 template.register_filter('lower', lower, False)
    442 template.register_filter('make_list', make_list, False)
    443 template.register_filter('phone2numeric', phone2numeric, False)
    444 template.register_filter('pluralize', pluralize, False)
    445 template.register_filter('pprint', pprint, False)
    446 template.register_filter('removetags', removetags, True)
    447 template.register_filter('random', random, False)
    448 template.register_filter('rjust', rjust, True)
    449 template.register_filter('slice', slice_, True)
    450 template.register_filter('slugify', slugify, False)
    451 template.register_filter('stringformat', stringformat, True)
    452 template.register_filter('striptags', striptags, False)
    453 template.register_filter('time', time, True)
    454 template.register_filter('timesince', timesince, False)
    455 template.register_filter('title', title, False)
    456 template.register_filter('truncatewords', truncatewords, True)
    457 template.register_filter('unordered_list', unordered_list, False)
    458 template.register_filter('upper', upper, False)
    459 template.register_filter('urlencode', urlencode, False)
    460 template.register_filter('urlize', urlize, False)
    461 template.register_filter('urlizetrunc', urlizetrunc, True)
    462 template.register_filter('wordcount', wordcount, False)
    463 template.register_filter('wordwrap', wordwrap, True)
    464 template.register_filter('yesno', yesno, True)
  • django/core/template/defaultfilters.py

     
    11"Default variable filters"
    22
    3 import template, re
     3from django.core.template import resolve_variable
     4# from django.core import template
     5import re
    46import random as random_module
    57
    68###################
  • django/core/template/defaulttags.py

     
    11"Default tags used by the template system, available to all templates."
    22
     3from django.core import template
    34import sys
    4 import template
    55
    66class CommentNode(template.Node):
    77    def render(self, context):
     
    434434        ==========================  ================================================
    435435        ``forloop.counter``         The current iteration of the loop (1-indexed)
    436436        ``forloop.counter0``        The current iteration of the loop (0-indexed)
    437         ``forloop.revcounter``      The number of iterations from the end of the 
     437        ``forloop.revcounter``      The number of iterations from the end of the
    438438                                    loop (1-indexed)
    439         ``forloop.revcounter0``     The number of iterations from the end of the 
     439        ``forloop.revcounter0``     The number of iterations from the end of the
    440440                                    loop (0-indexed)
    441441        ``forloop.first``           True if this is the first time through the loop
    442442        ``forloop.last``            True if this is the last time through the loop
Back to Top