| 1 |
"""Default tags used by the template system, available to all templates.""" |
|---|
| 2 |
|
|---|
| 3 |
import sys |
|---|
| 4 |
import re |
|---|
| 5 |
from itertools import cycle as itertools_cycle |
|---|
| 6 |
try: |
|---|
| 7 |
reversed |
|---|
| 8 |
except NameError: |
|---|
| 9 |
from django.utils.itercompat import reversed # Python 2.3 fallback |
|---|
| 10 |
|
|---|
| 11 |
from django.template import Node, NodeList, Template, Context, Variable |
|---|
| 12 |
from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END |
|---|
| 13 |
from django.template import get_library, Library, InvalidTemplateLibrary |
|---|
| 14 |
from django.conf import settings |
|---|
| 15 |
from django.utils.encoding import smart_str, smart_unicode |
|---|
| 16 |
from django.utils.itercompat import groupby |
|---|
| 17 |
from django.utils.safestring import mark_safe |
|---|
| 18 |
|
|---|
| 19 |
register = Library() |
|---|
| 20 |
|
|---|
| 21 |
class AutoEscapeControlNode(Node): |
|---|
| 22 |
"""Implements the actions of the autoescape tag.""" |
|---|
| 23 |
def __init__(self, setting, nodelist): |
|---|
| 24 |
self.setting, self.nodelist = setting, nodelist |
|---|
| 25 |
|
|---|
| 26 |
def render(self, context): |
|---|
| 27 |
old_setting = context.autoescape |
|---|
| 28 |
context.autoescape = self.setting |
|---|
| 29 |
output = self.nodelist.render(context) |
|---|
| 30 |
context.autoescape = old_setting |
|---|
| 31 |
if self.setting: |
|---|
| 32 |
return mark_safe(output) |
|---|
| 33 |
else: |
|---|
| 34 |
return output |
|---|
| 35 |
|
|---|
| 36 |
class CommentNode(Node): |
|---|
| 37 |
def render(self, context): |
|---|
| 38 |
return '' |
|---|
| 39 |
|
|---|
| 40 |
class CycleNode(Node): |
|---|
| 41 |
def __init__(self, cyclevars, variable_name=None): |
|---|
| 42 |
self.cycle_iter = itertools_cycle(cyclevars) |
|---|
| 43 |
self.variable_name = variable_name |
|---|
| 44 |
|
|---|
| 45 |
def render(self, context): |
|---|
| 46 |
value = self.cycle_iter.next().resolve(context) |
|---|
| 47 |
if self.variable_name: |
|---|
| 48 |
context[self.variable_name] = value |
|---|
| 49 |
return value |
|---|
| 50 |
|
|---|
| 51 |
class DebugNode(Node): |
|---|
| 52 |
def render(self, context): |
|---|
| 53 |
from pprint import pformat |
|---|
| 54 |
output = [pformat(val) for val in context] |
|---|
| 55 |
output.append('\n\n') |
|---|
| 56 |
output.append(pformat(sys.modules)) |
|---|
| 57 |
return ''.join(output) |
|---|
| 58 |
|
|---|
| 59 |
class FilterNode(Node): |
|---|
| 60 |
def __init__(self, filter_expr, nodelist): |
|---|
| 61 |
self.filter_expr, self.nodelist = filter_expr, nodelist |
|---|
| 62 |
|
|---|
| 63 |
def render(self, context): |
|---|
| 64 |
output = self.nodelist.render(context) |
|---|
| 65 |
# Apply filters. |
|---|
| 66 |
context.update({'var': output}) |
|---|
| 67 |
filtered = self.filter_expr.resolve(context) |
|---|
| 68 |
context.pop() |
|---|
| 69 |
return filtered |
|---|
| 70 |
|
|---|
| 71 |
class FirstOfNode(Node): |
|---|
| 72 |
def __init__(self, vars): |
|---|
| 73 |
self.vars = vars |
|---|
| 74 |
|
|---|
| 75 |
def render(self, context): |
|---|
| 76 |
for var in self.vars: |
|---|
| 77 |
value = var.resolve(context, True) |
|---|
| 78 |
if value: |
|---|
| 79 |
return smart_unicode(value) |
|---|
| 80 |
return u'' |
|---|
| 81 |
|
|---|
| 82 |
class ForNode(Node): |
|---|
| 83 |
def __init__(self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None): |
|---|
| 84 |
self.loopvars, self.sequence = loopvars, sequence |
|---|
| 85 |
self.is_reversed = is_reversed |
|---|
| 86 |
self.nodelist_loop = nodelist_loop |
|---|
| 87 |
if nodelist_empty is None: |
|---|
| 88 |
self.nodelist_empty = NodeList() |
|---|
| 89 |
else: |
|---|
| 90 |
self.nodelist_empty = nodelist_empty |
|---|
| 91 |
|
|---|
| 92 |
def __repr__(self): |
|---|
| 93 |
reversed_text = self.is_reversed and ' reversed' or '' |
|---|
| 94 |
return "<For Node: for %s in %s, tail_len: %d%s>" % \ |
|---|
| 95 |
(', '.join(self.loopvars), self.sequence, len(self.nodelist_loop), |
|---|
| 96 |
reversed_text) |
|---|
| 97 |
|
|---|
| 98 |
def __iter__(self): |
|---|
| 99 |
for node in self.nodelist_loop: |
|---|
| 100 |
yield node |
|---|
| 101 |
for node in self.nodelist_empty: |
|---|
| 102 |
yield node |
|---|
| 103 |
|
|---|
| 104 |
def get_nodes_by_type(self, nodetype): |
|---|
| 105 |
nodes = [] |
|---|
| 106 |
if isinstance(self, nodetype): |
|---|
| 107 |
nodes.append(self) |
|---|
| 108 |
nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype)) |
|---|
| 109 |
nodes.extend(self.nodelist_empty.get_nodes_by_type(nodetype)) |
|---|
| 110 |
return nodes |
|---|
| 111 |
|
|---|
| 112 |
def render(self, context): |
|---|
| 113 |
if 'forloop' in context: |
|---|
| 114 |
parentloop = context['forloop'] |
|---|
| 115 |
else: |
|---|
| 116 |
parentloop = {} |
|---|
| 117 |
context.push() |
|---|
| 118 |
try: |
|---|
| 119 |
values = self.sequence.resolve(context, True) |
|---|
| 120 |
except VariableDoesNotExist: |
|---|
| 121 |
values = [] |
|---|
| 122 |
if values is None: |
|---|
| 123 |
values = [] |
|---|
| 124 |
if not hasattr(values, '__len__'): |
|---|
| 125 |
values = list(values) |
|---|
| 126 |
len_values = len(values) |
|---|
| 127 |
if len_values < 1: |
|---|
| 128 |
context.pop() |
|---|
| 129 |
return self.nodelist_empty.render(context) |
|---|
| 130 |
nodelist = NodeList() |
|---|
| 131 |
if self.is_reversed: |
|---|
| 132 |
values = reversed(values) |
|---|
| 133 |
unpack = len(self.loopvars) > 1 |
|---|
| 134 |
# Create a forloop value in the context. We'll update counters on each |
|---|
| 135 |
# iteration just below. |
|---|
| 136 |
loop_dict = context['forloop'] = {'parentloop': parentloop} |
|---|
| 137 |
for i, item in enumerate(values): |
|---|
| 138 |
# Shortcuts for current loop iteration number. |
|---|
| 139 |
loop_dict['counter0'] = i |
|---|
| 140 |
loop_dict['counter'] = i+1 |
|---|
| 141 |
# Reverse counter iteration numbers. |
|---|
| 142 |
loop_dict['revcounter'] = len_values - i |
|---|
| 143 |
loop_dict['revcounter0'] = len_values - i - 1 |
|---|
| 144 |
# Boolean values designating first and last times through loop. |
|---|
| 145 |
loop_dict['first'] = (i == 0) |
|---|
| 146 |
loop_dict['last'] = (i == len_values - 1) |
|---|
| 147 |
|
|---|
| 148 |
if unpack: |
|---|
| 149 |
# If there are multiple loop variables, unpack the item into |
|---|
| 150 |
# them. |
|---|
| 151 |
context.update(dict(zip(self.loopvars, item))) |
|---|
| 152 |
else: |
|---|
| 153 |
context[self.loopvars[0]] = item |
|---|
| 154 |
for node in self.nodelist_loop: |
|---|
| 155 |
nodelist.append(node.render(context)) |
|---|
| 156 |
if unpack: |
|---|
| 157 |
# The loop variables were pushed on to the context so pop them |
|---|
| 158 |
# off again. This is necessary because the tag lets the length |
|---|
| 159 |
# of loopvars differ to the length of each set of items and we |
|---|
| 160 |
# don't want to leave any vars from the previous loop on the |
|---|
| 161 |
# context. |
|---|
| 162 |
context.pop() |
|---|
| 163 |
context.pop() |
|---|
| 164 |
return nodelist.render(context) |
|---|
| 165 |
|
|---|
| 166 |
class IfChangedNode(Node): |
|---|
| 167 |
def __init__(self, nodelist_true, nodelist_false, *varlist): |
|---|
| 168 |
self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false |
|---|
| 169 |
self._last_seen = None |
|---|
| 170 |
self._varlist = varlist |
|---|
| 171 |
self._id = str(id(self)) |
|---|
| 172 |
|
|---|
| 173 |
def render(self, context): |
|---|
| 174 |
if 'forloop' in context and self._id not in context['forloop']: |
|---|
| 175 |
self._last_seen = None |
|---|
| 176 |
context['forloop'][self._id] = 1 |
|---|
| 177 |
try: |
|---|
| 178 |
if self._varlist: |
|---|
| 179 |
# Consider multiple parameters. This automatically behaves |
|---|
| 180 |
# like an OR evaluation of the multiple variables. |
|---|
| 181 |
compare_to = [var.resolve(context, True) for var in self._varlist] |
|---|
| 182 |
else: |
|---|
| 183 |
compare_to = self.nodelist_true.render(context) |
|---|
| 184 |
except VariableDoesNotExist: |
|---|
| 185 |
compare_to = None |
|---|
| 186 |
|
|---|
| 187 |
if compare_to != self._last_seen: |
|---|
| 188 |
firstloop = (self._last_seen == None) |
|---|
| 189 |
self._last_seen = compare_to |
|---|
| 190 |
content = self.nodelist_true.render(context) |
|---|
| 191 |
return content |
|---|
| 192 |
elif self.nodelist_false: |
|---|
| 193 |
return self.nodelist_false.render(context) |
|---|
| 194 |
return '' |
|---|
| 195 |
|
|---|
| 196 |
class IfEqualNode(Node): |
|---|
| 197 |
def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): |
|---|
| 198 |
self.var1, self.var2 = var1, var2 |
|---|
| 199 |
self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false |
|---|
| 200 |
self.negate = negate |
|---|
| 201 |
|
|---|
| 202 |
def __repr__(self): |
|---|
| 203 |
return "<IfEqualNode>" |
|---|
| 204 |
|
|---|
| 205 |
def render(self, context): |
|---|
| 206 |
val1 = self.var1.resolve(context, True) |
|---|
| 207 |
val2 = self.var2.resolve(context, True) |
|---|
| 208 |
if (self.negate and val1 != val2) or (not self.negate and val1 == val2): |
|---|
| 209 |
return self.nodelist_true.render(context) |
|---|
| 210 |
return self.nodelist_false.render(context) |
|---|
| 211 |
|
|---|
| 212 |
class IfNode(Node): |
|---|
| 213 |
def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type): |
|---|
| 214 |
self.bool_exprs = bool_exprs |
|---|
| 215 |
self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false |
|---|
| 216 |
self.link_type = link_type |
|---|
| 217 |
|
|---|
| 218 |
def __repr__(self): |
|---|
| 219 |
return "<If node>" |
|---|
| 220 |
|
|---|
| 221 |
def __iter__(self): |
|---|
| 222 |
for node in self.nodelist_true: |
|---|
| 223 |
yield node |
|---|
| 224 |
for node in self.nodelist_false: |
|---|
| 225 |
yield node |
|---|
| 226 |
|
|---|
| 227 |
def get_nodes_by_type(self, nodetype): |
|---|
| 228 |
nodes = [] |
|---|
| 229 |
if isinstance(self, nodetype): |
|---|
| 230 |
nodes.append(self) |
|---|
| 231 |
nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype)) |
|---|
| 232 |
nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype)) |
|---|
| 233 |
return nodes |
|---|
| 234 |
|
|---|
| 235 |
def render(self, context): |
|---|
| 236 |
if self.link_type == IfNode.LinkTypes.or_: |
|---|
| 237 |
for ifnot, bool_expr in self.bool_exprs: |
|---|
| 238 |
try: |
|---|
| 239 |
value = bool_expr.resolve(context, True) |
|---|
| 240 |
except VariableDoesNotExist: |
|---|
| 241 |
value = None |
|---|
| 242 |
if (value and not ifnot) or (ifnot and not value): |
|---|
| 243 |
return self.nodelist_true.render(context) |
|---|
| 244 |
return self.nodelist_false.render(context) |
|---|
| 245 |
else: |
|---|
| 246 |
for ifnot, bool_expr in self.bool_exprs: |
|---|
| 247 |
try: |
|---|
| 248 |
value = bool_expr.resolve(context, True) |
|---|
| 249 |
except VariableDoesNotExist: |
|---|
| 250 |
value = None |
|---|
| 251 |
if not ((value and not ifnot) or (ifnot and not value)): |
|---|
| 252 |
return self.nodelist_false.render(context) |
|---|
| 253 |
return self.nodelist_true.render(context) |
|---|
| 254 |
|
|---|
| 255 |
class LinkTypes: |
|---|
| 256 |
and_ = 0, |
|---|
| 257 |
or_ = 1 |
|---|
| 258 |
|
|---|
| 259 |
class RegroupNode(Node): |
|---|
| 260 |
def __init__(self, target, expression, var_name): |
|---|
| 261 |
self.target, self.expression = target, expression |
|---|
| 262 |
self.var_name = var_name |
|---|
| 263 |
|
|---|
| 264 |
def render(self, context): |
|---|
| 265 |
obj_list = self.target.resolve(context, True) |
|---|
| 266 |
if obj_list == None: |
|---|
| 267 |
# target variable wasn't found in context; fail silently. |
|---|
| 268 |
context[self.var_name] = [] |
|---|
| 269 |
return '' |
|---|
| 270 |
# List of dictionaries in the format: |
|---|
| 271 |
# {'grouper': 'key', 'list': [list of contents]}. |
|---|
| 272 |
context[self.var_name] = [ |
|---|
| 273 |
{'grouper': key, 'list': list(val)} |
|---|
| 274 |
for key, val in |
|---|
| 275 |
groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True)) |
|---|
| 276 |
] |
|---|
| 277 |
return '' |
|---|
| 278 |
|
|---|
| 279 |
def include_is_allowed(filepath): |
|---|
| 280 |
for root in settings.ALLOWED_INCLUDE_ROOTS: |
|---|
| 281 |
if filepath.startswith(root): |
|---|
| 282 |
return True |
|---|
| 283 |
return False |
|---|
| 284 |
|
|---|
| 285 |
class SsiNode(Node): |
|---|
| 286 |
def __init__(self, filepath, parsed): |
|---|
| 287 |
self.filepath, self.parsed = filepath, parsed |
|---|
| 288 |
|
|---|
| 289 |
def render(self, context): |
|---|
| 290 |
if not include_is_allowed(self.filepath): |
|---|
| 291 |
if settings.DEBUG: |
|---|
| 292 |
return "[Didn't have permission to include file]" |
|---|
| 293 |
else: |
|---|
| 294 |
return '' # Fail silently for invalid includes. |
|---|
| 295 |
try: |
|---|
| 296 |
fp = open(self.filepath, 'r') |
|---|
| 297 |
output = fp.read() |
|---|
| 298 |
fp.close() |
|---|
| 299 |
except IOError: |
|---|
| 300 |
output = '' |
|---|
| 301 |
if self.parsed: |
|---|
| 302 |
try: |
|---|
| 303 |
t = Template(output, name=self.filepath) |
|---|
| 304 |
return t.render(context) |
|---|
| 305 |
except TemplateSyntaxError, e: |
|---|
| 306 |
if settings.DEBUG: |
|---|
| 307 |
return "[Included template had syntax error: %s]" % e |
|---|
| 308 |
else: |
|---|
| 309 |
return '' # Fail silently for invalid included templates. |
|---|
| 310 |
return output |
|---|
| 311 |
|
|---|
| 312 |
class LoadNode(Node): |
|---|
| 313 |
def render(self, context): |
|---|
| 314 |
return '' |
|---|
| 315 |
|
|---|
| 316 |
class NowNode(Node): |
|---|
| 317 |
def __init__(self, format_string): |
|---|
| 318 |
self.format_string = format_string |
|---|
| 319 |
|
|---|
| 320 |
def render(self, context): |
|---|
| 321 |
from datetime import datetime |
|---|
| 322 |
from django.utils.dateformat import DateFormat |
|---|
| 323 |
df = DateFormat(datetime.now()) |
|---|
| 324 |
return df.format(self.format_string) |
|---|
| 325 |
|
|---|
| 326 |
class SpacelessNode(Node): |
|---|
| 327 |
def __init__(self, nodelist): |
|---|
| 328 |
self.nodelist = nodelist |
|---|
| 329 |
|
|---|
| 330 |
def render(self, context): |
|---|
| 331 |
from django.utils.html import strip_spaces_between_tags |
|---|
| 332 |
return strip_spaces_between_tags(self.nodelist.render(context).strip()) |
|---|
| 333 |
|
|---|
| 334 |
class TemplateTagNode(Node): |
|---|
| 335 |
mapping = {'openblock': BLOCK_TAG_START, |
|---|
| 336 |
'closeblock': BLOCK_TAG_END, |
|---|
| 337 |
'openvariable': VARIABLE_TAG_START, |
|---|
| 338 |
'closevariable': VARIABLE_TAG_END, |
|---|
| 339 |
'openbrace': SINGLE_BRACE_START, |
|---|
| 340 |
'closebrace': SINGLE_BRACE_END, |
|---|
| 341 |
'opencomment': COMMENT_TAG_START, |
|---|
| 342 |
'closecomment': COMMENT_TAG_END, |
|---|
| 343 |
} |
|---|
| 344 |
|
|---|
| 345 |
def __init__(self, tagtype): |
|---|
| 346 |
self.tagtype = tagtype |
|---|
| 347 |
|
|---|
| 348 |
def render(self, context): |
|---|
| 349 |
return self.mapping.get(self.tagtype, '') |
|---|
| 350 |
|
|---|
| 351 |
class URLNode(Node): |
|---|
| 352 |
def __init__(self, view_name, args, kwargs, asvar): |
|---|
| 353 |
self.view_name = view_name |
|---|
| 354 |
self.args = args |
|---|
| 355 |
self.kwargs = kwargs |
|---|
| 356 |
self.asvar = asvar |
|---|
| 357 |
|
|---|
| 358 |
def render(self, context): |
|---|
| 359 |
from django.core.urlresolvers import reverse, NoReverseMatch |
|---|
| 360 |
args = [arg.resolve(context) for arg in self.args] |
|---|
| 361 |
kwargs = dict([(smart_str(k,'ascii'), v.resolve(context)) |
|---|
| 362 |
for k, v in self.kwargs.items()]) |
|---|
| 363 |
|
|---|
| 364 |
# Try to look up the URL twice: once given the view name, and again |
|---|
| 365 |
# relative to what we guess is the "main" app. If they both fail, |
|---|
| 366 |
# re-raise the NoReverseMatch unless we're using the |
|---|
| 367 |
# {% url ... as var %} construct in which cause return nothing. |
|---|
| 368 |
url = '' |
|---|
| 369 |
try: |
|---|
| 370 |
url = reverse(self.view_name, args=args, kwargs=kwargs) |
|---|
| 371 |
except NoReverseMatch, e: |
|---|
| 372 |
if settings.SETTINGS_MODULE: |
|---|
| 373 |
project_name = settings.SETTINGS_MODULE.split('.')[0] |
|---|
| 374 |
try: |
|---|
| 375 |
url = reverse(project_name + '.' + self.view_name, |
|---|
| 376 |
args=args, kwargs=kwargs) |
|---|
| 377 |
except NoReverseMatch: |
|---|
| 378 |
if self.asvar is None: |
|---|
| 379 |
# Re-raise the original exception, not the one with |
|---|
| 380 |
# the path relative to the project. This makes a |
|---|
| 381 |
# better error message. |
|---|
| 382 |
raise e |
|---|
| 383 |
else: |
|---|
| 384 |
if self.asvar is None: |
|---|
| 385 |
raise e |
|---|
| 386 |
|
|---|
| 387 |
if self.asvar: |
|---|
| 388 |
context[self.asvar] = url |
|---|
| 389 |
return '' |
|---|
| 390 |
else: |
|---|
| 391 |
return url |
|---|
| 392 |
|
|---|
| 393 |
class WidthRatioNode(Node): |
|---|
| 394 |
def __init__(self, val_expr, max_expr, max_width): |
|---|
| 395 |
self.val_expr = val_expr |
|---|
| 396 |
self.max_expr = max_expr |
|---|
| 397 |
self.max_width = max_width |
|---|
| 398 |
|
|---|
| 399 |
def render(self, context): |
|---|
| 400 |
try: |
|---|
| 401 |
value = self.val_expr.resolve(context) |
|---|
| 402 |
maxvalue = self.max_expr.resolve(context) |
|---|
| 403 |
max_width = int(self.max_width.resolve(context)) |
|---|
| 404 |
except VariableDoesNotExist: |
|---|
| 405 |
return '' |
|---|
| 406 |
except ValueError: |
|---|
| 407 |
raise TemplateSyntaxError("widthratio final argument must be an number") |
|---|
| 408 |
try: |
|---|
| 409 |
value = float(value) |
|---|
| 410 |
maxvalue = float(maxvalue) |
|---|
| 411 |
ratio = (value / maxvalue) * max_width |
|---|
| 412 |
except (ValueError, ZeroDivisionError): |
|---|
| 413 |
return '' |
|---|
| 414 |
return str(int(round(ratio))) |
|---|
| 415 |
|
|---|
| 416 |
class WithNode(Node): |
|---|
| 417 |
def __init__(self, var, name, nodelist): |
|---|
| 418 |
self.var = var |
|---|
| 419 |
self.name = name |
|---|
| 420 |
self.nodelist = nodelist |
|---|
| 421 |
|
|---|
| 422 |
def __repr__(self): |
|---|
| 423 |
return "<WithNode>" |
|---|
| 424 |
|
|---|
| 425 |
def render(self, context): |
|---|
| 426 |
val = self.var.resolve(context) |
|---|
| 427 |
context.push() |
|---|
| 428 |
context[self.name] = val |
|---|
| 429 |
output = self.nodelist.render(context) |
|---|
| 430 |
context.pop() |
|---|
| 431 |
return output |
|---|
| 432 |
|
|---|
| 433 |
#@register.tag |
|---|
| 434 |
def autoescape(parser, token): |
|---|
| 435 |
""" |
|---|
| 436 |
Force autoescape behaviour for this block. |
|---|
| 437 |
""" |
|---|
| 438 |
args = token.contents.split() |
|---|
| 439 |
if len(args) != 2: |
|---|
| 440 |
raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.") |
|---|
| 441 |
arg = args[1] |
|---|
| 442 |
if arg not in (u'on', u'off'): |
|---|
| 443 |
raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'") |
|---|
| 444 |
nodelist = parser.parse(('endautoescape',)) |
|---|
| 445 |
parser.delete_first_token() |
|---|
| 446 |
return AutoEscapeControlNode((arg == 'on'), nodelist) |
|---|
| 447 |
autoescape = register.tag(autoescape) |
|---|
| 448 |
|
|---|
| 449 |
#@register.tag |
|---|
| 450 |
def comment(parser, token): |
|---|
| 451 |
""" |
|---|
| 452 |
Ignores everything between ``{% comment %}`` and ``{% endcomment %}``. |
|---|
| 453 |
""" |
|---|
| 454 |
parser.skip_past('endcomment') |
|---|
| 455 |
return CommentNode() |
|---|
| 456 |
comment = register.tag(comment) |
|---|
| 457 |
|
|---|
| 458 |
#@register.tag |
|---|
| 459 |
def cycle(parser, token): |
|---|
| 460 |
""" |
|---|
| 461 |
Cycles among the given strings each time this tag is encountered. |
|---|
| 462 |
|
|---|
| 463 |
Within a loop, cycles among the given strings each time through |
|---|
| 464 |
the loop:: |
|---|
| 465 |
|
|---|
| 466 |
{% for o in some_list %} |
|---|
| 467 |
<tr class="{% cycle 'row1' 'row2' %}"> |
|---|
| 468 |
... |
|---|
| 469 |
</tr> |
|---|
| 470 |
{% endfor %} |
|---|
| 471 |
|
|---|
| 472 |
Outside of a loop, give the values a unique name the first time you call |
|---|
| 473 |
it, then use that name each sucessive time through:: |
|---|
| 474 |
|
|---|
| 475 |
<tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr> |
|---|
| 476 |
<tr class="{% cycle rowcolors %}">...</tr> |
|---|
| 477 |
<tr class="{% cycle rowcolors %}">...</tr> |
|---|
| 478 |
|
|---|
| 479 |
You can use any number of values, separated by spaces. Commas can also |
|---|
| 480 |
be used to separate values; if a comma is used, the cycle values are |
|---|
| 481 |
interpreted as literal strings. |
|---|
| 482 |
""" |
|---|
| 483 |
|
|---|
| 484 |
# Note: This returns the exact same node on each {% cycle name %} call; |
|---|
| 485 |
# that is, the node object returned from {% cycle a b c as name %} and the |
|---|
| 486 |
# one returned from {% cycle name %} are the exact same object. This |
|---|
| 487 |
# shouldn't cause problems (heh), but if it does, now you know. |
|---|
| 488 |
# |
|---|
| 489 |
# Ugly hack warning: This stuffs the named template dict into parser so |
|---|
| 490 |
# that names are only unique within each template (as opposed to using |
|---|
| 491 |
# a global variable, which would make cycle names have to be unique across |
|---|
| 492 |
# *all* templates. |
|---|
| 493 |
|
|---|
| 494 |
args = token.split_contents() |
|---|
| 495 |
|
|---|
| 496 |
if len(args) < 2: |
|---|
| 497 |
raise TemplateSyntaxError("'cycle' tag requires at least two arguments") |
|---|
| 498 |
|
|---|
| 499 |
if ',' in args[1]: |
|---|
| 500 |
# Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %} |
|---|
| 501 |
# case. |
|---|
| 502 |
args[1:2] = ['"%s"' % arg for arg in args[1].split(",")] |
|---|
| 503 |
|
|---|
| 504 |
if len(args) == 2: |
|---|
| 505 |
# {% cycle foo %} case. |
|---|
| 506 |
name = args[1] |
|---|
| 507 |
if not hasattr(parser, '_namedCycleNodes'): |
|---|
| 508 |
raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name) |
|---|
| 509 |
if not name in parser._namedCycleNodes: |
|---|
| 510 |
raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) |
|---|
| 511 |
return parser._namedCycleNodes[name] |
|---|
| 512 |
|
|---|
| 513 |
if len(args) > 4 and args[-2] == 'as': |
|---|
| 514 |
name = args[-1] |
|---|
| 515 |
values = [parser.compile_filter(arg) for arg in args[1:-2]] |
|---|
| 516 |
node = CycleNode(values, name) |
|---|
| 517 |
if not hasattr(parser, '_namedCycleNodes'): |
|---|
| 518 |
parser._namedCycleNodes = {} |
|---|
| 519 |
parser._namedCycleNodes[name] = node |
|---|
| 520 |
else: |
|---|
| 521 |
values = [parser.compile_filter(arg) for arg in args[1:]] |
|---|
| 522 |
node = CycleNode(values) |
|---|
| 523 |
return node |
|---|
| 524 |
cycle = register.tag(cycle) |
|---|
| 525 |
|
|---|
| 526 |
def debug(parser, token): |
|---|
| 527 |
""" |
|---|
| 528 |
Outputs a whole load of debugging information, including the current |
|---|
| 529 |
context and imported modules. |
|---|
| 530 |
|
|---|
| 531 |
Sample usage:: |
|---|
| 532 |
|
|---|
| 533 |
<pre> |
|---|
| 534 |
{% debug %} |
|---|
| 535 |
</pre> |
|---|
| 536 |
""" |
|---|
| 537 |
return DebugNode() |
|---|
| 538 |
debug = register.tag(debug) |
|---|
| 539 |
|
|---|
| 540 |
#@register.tag(name="filter") |
|---|
| 541 |
def do_filter(parser, token): |
|---|
| 542 |
""" |
|---|
| 543 |
Filters the contents of the block through variable filters. |
|---|
| 544 |
|
|---|
| 545 |
Filters can also be piped through each other, and they can have |
|---|
| 546 |
arguments -- just like in variable syntax. |
|---|
| 547 |
|
|---|
| 548 |
Sample usage:: |
|---|
| 549 |
|
|---|
| 550 |
{% filter force_escape|lower %} |
|---|
| 551 |
This text will be HTML-escaped, and will appear in lowercase. |
|---|
| 552 |
{% endfilter %} |
|---|
| 553 |
""" |
|---|
| 554 |
_, rest = token.contents.split(None, 1) |
|---|
| 555 |
filter_expr = parser.compile_filter("var|%s" % (rest)) |
|---|
| 556 |
for func, unused in filter_expr.filters: |
|---|
| 557 |
if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'): |
|---|
| 558 |
raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__) |
|---|
| 559 |
nodelist = parser.parse(('endfilter',)) |
|---|
| 560 |
parser.delete_first_token() |
|---|
| 561 |
return FilterNode(filter_expr, nodelist) |
|---|
| 562 |
do_filter = register.tag("filter", do_filter) |
|---|
| 563 |
|
|---|
| 564 |
#@register.tag |
|---|
| 565 |
def firstof(parser, token): |
|---|
| 566 |
""" |
|---|
| 567 |
Outputs the first variable passed that is not False, without escaping. |
|---|
| 568 |
|
|---|
| 569 |
Outputs nothing if all the passed variables are False. |
|---|
| 570 |
|
|---|
| 571 |
Sample usage:: |
|---|
| 572 |
|
|---|
| 573 |
{% firstof var1 var2 var3 %} |
|---|
| 574 |
|
|---|
| 575 |
This is equivalent to:: |
|---|
| 576 |
|
|---|
| 577 |
{% if var1 %} |
|---|
| 578 |
{{ var1|safe }} |
|---|
| 579 |
{% else %}{% if var2 %} |
|---|
| 580 |
{{ var2|safe }} |
|---|
| 581 |
{% else %}{% if var3 %} |
|---|
| 582 |
{{ var3|safe }} |
|---|
| 583 |
{% endif %}{% endif %}{% endif %} |
|---|
| 584 |
|
|---|
| 585 |
but obviously much cleaner! |
|---|
| 586 |
|
|---|
| 587 |
You can also use a literal string as a fallback value in case all |
|---|
| 588 |
passed variables are False:: |
|---|
| 589 |
|
|---|
| 590 |
{% firstof var1 var2 var3 "fallback value" %} |
|---|
| 591 |
|
|---|
| 592 |
If you want to escape the output, use a filter tag:: |
|---|
| 593 |
|
|---|
| 594 |
{% filter force_escape %} |
|---|
| 595 |
{% firstof var1 var2 var3 "fallback value" %} |
|---|
| 596 |
{% endfilter %} |
|---|
| 597 |
|
|---|
| 598 |
""" |
|---|
| 599 |
bits = token.split_contents()[1:] |
|---|
| 600 |
if len(bits) < 1: |
|---|
| 601 |
raise TemplateSyntaxError("'firstof' statement requires at least one" |
|---|
| 602 |
" argument") |
|---|
| 603 |
return FirstOfNode([parser.compile_filter(bit) for bit in bits]) |
|---|
| 604 |
firstof = register.tag(firstof) |
|---|
| 605 |
|
|---|
| 606 |
#@register.tag(name="for") |
|---|
| 607 |
def do_for(parser, token): |
|---|
| 608 |
""" |
|---|
| 609 |
Loops over each item in an array. |
|---|
| 610 |
|
|---|
| 611 |
For example, to display a list of athletes given ``athlete_list``:: |
|---|
| 612 |
|
|---|
| 613 |
<ul> |
|---|
| 614 |
{% for athlete in athlete_list %} |
|---|
| 615 |
<li>{{ athlete.name }}</li> |
|---|
| 616 |
{% endfor %} |
|---|
| 617 |
</ul> |
|---|
| 618 |
|
|---|
| 619 |
You can loop over a list in reverse by using |
|---|
| 620 |
``{% for obj in list reversed %}``. |
|---|
| 621 |
|
|---|
| 622 |
You can also unpack multiple values from a two-dimensional array:: |
|---|
| 623 |
|
|---|
| 624 |
{% for key,value in dict.items %} |
|---|
| 625 |
{{ key }}: {{ value }} |
|---|
| 626 |
{% endfor %} |
|---|
| 627 |
|
|---|
| 628 |
The ``for`` tag can take an optional ``{% empty %}`` clause that will |
|---|
| 629 |
be displayed if the given array is empty or could not be found:: |
|---|
| 630 |
|
|---|
| 631 |
<ul> |
|---|
| 632 |
{% for athlete in athlete_list %} |
|---|
| 633 |
<li>{{ athlete.name }}</li> |
|---|
| 634 |
{% empty %} |
|---|
| 635 |
<li>Sorry, no athletes in this list.</li> |
|---|
| 636 |
{% endfor %} |
|---|
| 637 |
<ul> |
|---|
| 638 |
|
|---|
| 639 |
The above is equivalent to -- but shorter, cleaner, and possibly faster |
|---|
| 640 |
than -- the following:: |
|---|
| 641 |
|
|---|
| 642 |
<ul> |
|---|
| 643 |
{% if althete_list %} |
|---|
| 644 |
{% for athlete in athlete_list %} |
|---|
| 645 |
<li>{{ athlete.name }}</li> |
|---|
| 646 |
{% endfor %} |
|---|
| 647 |
{% else %} |
|---|
| 648 |
<li>Sorry, no athletes in this list.</li> |
|---|
| 649 |
{% endif %} |
|---|
| 650 |
</ul> |
|---|
| 651 |
|
|---|
| 652 |
The for loop sets a number of variables available within the loop: |
|---|
| 653 |
|
|---|
| 654 |
========================== ================================================ |
|---|
| 655 |
Variable Description |
|---|
| 656 |
========================== ================================================ |
|---|
| 657 |
``forloop.counter`` The current iteration of the loop (1-indexed) |
|---|
| 658 |
``forloop.counter0`` The current iteration of the loop (0-indexed) |
|---|
| 659 |
``forloop.revcounter`` The number of iterations from the end of the |
|---|
| 660 |
loop (1-indexed) |
|---|
| 661 |
``forloop.revcounter0`` The number of iterations from the end of the |
|---|
| 662 |
loop (0-indexed) |
|---|
| 663 |
``forloop.first`` True if this is the first time through the loop |
|---|
| 664 |
``forloop.last`` True if this is the last time through the loop |
|---|
| 665 |
``forloop.parentloop`` For nested loops, this is the loop "above" the |
|---|
| 666 |
current one |
|---|
| 667 |
========================== ================================================ |
|---|
| 668 |
|
|---|
| 669 |
""" |
|---|
| 670 |
bits = token.contents.split() |
|---|
| 671 |
if len(bits) < 4: |
|---|
| 672 |
raise TemplateSyntaxError("'for' statements should have at least four" |
|---|
| 673 |
" words: %s" % token.contents) |
|---|
| 674 |
|
|---|
| 675 |
is_reversed = bits[-1] == 'reversed' |
|---|
| 676 |
in_index = is_reversed and -3 or -2 |
|---|
| 677 |
if bits[in_index] != 'in': |
|---|
| 678 |
raise TemplateSyntaxError("'for' statements should use the format" |
|---|
| 679 |
" 'for x in y': %s" % token.contents) |
|---|
| 680 |
|
|---|
| 681 |
loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',') |
|---|
| 682 |
for var in loopvars: |
|---|
| 683 |
if not var or ' ' in var: |
|---|
| 684 |
raise TemplateSyntaxError("'for' tag received an invalid argument:" |
|---|
| 685 |
" %s" % token.contents) |
|---|
| 686 |
|
|---|
| 687 |
sequence = parser.compile_filter(bits[in_index+1]) |
|---|
| 688 |
nodelist_loop = parser.parse(('empty', 'endfor',)) |
|---|
| 689 |
token = parser.next_token() |
|---|
| 690 |
if token.contents == 'empty': |
|---|
| 691 |
nodelist_empty = parser.parse(('endfor',)) |
|---|
| 692 |
parser.delete_first_token() |
|---|
| 693 |
else: |
|---|
| 694 |
nodelist_empty = None |
|---|
| 695 |
return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty) |
|---|
| 696 |
do_for = register.tag("for", do_for) |
|---|
| 697 |
|
|---|
| 698 |
def do_ifequal(parser, token, negate): |
|---|
| 699 |
bits = list(token.split_contents()) |
|---|
| 700 |
if len(bits) != 3: |
|---|
| 701 |
raise TemplateSyntaxError, "%r takes two arguments" % bits[0] |
|---|
| 702 |
end_tag = 'end' + bits[0] |
|---|
| 703 |
nodelist_true = parser.parse(('else', end_tag)) |
|---|
| 704 |
token = parser.next_token() |
|---|
| 705 |
if token.contents == 'else': |
|---|
| 706 |
nodelist_false = parser.parse((end_tag,)) |
|---|
| 707 |
parser.delete_first_token() |
|---|
| 708 |
else: |
|---|
| 709 |
nodelist_false = NodeList() |
|---|
| 710 |
val1 = parser.compile_filter(bits[1]) |
|---|
| 711 |
val2 = parser.compile_filter(bits[2]) |
|---|
| 712 |
return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate) |
|---|
| 713 |
|
|---|
| 714 |
#@register.tag |
|---|
| 715 |
def ifequal(parser, token): |
|---|
| 716 |
""" |
|---|
| 717 |
Outputs the contents of the block if the two arguments equal each other. |
|---|
| 718 |
|
|---|
| 719 |
Examples:: |
|---|
| 720 |
|
|---|
| 721 |
{% ifequal user.id comment.user_id %} |
|---|
| 722 |
... |
|---|
| 723 |
{% endifequal %} |
|---|
| 724 |
|
|---|
| 725 |
{% ifnotequal user.id comment.user_id %} |
|---|
| 726 |
... |
|---|
| 727 |
{% else %} |
|---|
| 728 |
... |
|---|
| 729 |
{% endifnotequal %} |
|---|
| 730 |
""" |
|---|
| 731 |
return do_ifequal(parser, token, False) |
|---|
| 732 |
ifequal = register.tag(ifequal) |
|---|
| 733 |
|
|---|
| 734 |
#@register.tag |
|---|
| 735 |
def ifnotequal(parser, token): |
|---|
| 736 |
""" |
|---|
| 737 |
Outputs the contents of the block if the two arguments are not equal. |
|---|
| 738 |
See ifequal. |
|---|
| 739 |
""" |
|---|
| 740 |
return do_ifequal(parser, token, True) |
|---|
| 741 |
ifnotequal = register.tag(ifnotequal) |
|---|
| 742 |
|
|---|
| 743 |
#@register.tag(name="if") |
|---|
| 744 |
def do_if(parser, token): |
|---|
| 745 |
""" |
|---|
| 746 |
The ``{% if %}`` tag evaluates a variable, and if that variable is "true" |
|---|
| 747 |
(i.e., exists, is not empty, and is not a false boolean value), the |
|---|
| 748 |
contents of the block are output: |
|---|
| 749 |
|
|---|
| 750 |
:: |
|---|
| 751 |
|
|---|
| 752 |
{% if athlete_list %} |
|---|
| 753 |
Number of athletes: {{ athlete_list|count }} |
|---|
| 754 |
{% else %} |
|---|
| 755 |
No athletes. |
|---|
| 756 |
{% endif %} |
|---|
| 757 |
|
|---|
| 758 |
In the above, if ``athlete_list`` is not empty, the number of athletes will |
|---|
| 759 |
be displayed by the ``{{ athlete_list|count }}`` variable. |
|---|
| 760 |
|
|---|
| 761 |
As you can see, the ``if`` tag can take an option ``{% else %}`` clause |
|---|
| 762 |
that will be displayed if the test fails. |
|---|
| 763 |
|
|---|
| 764 |
``if`` tags may use ``or``, ``and`` or ``not`` to test a number of |
|---|
| 765 |
variables or to negate a given variable:: |
|---|
| 766 |
|
|---|
| 767 |
{% if not athlete_list %} |
|---|
| 768 |
There are no athletes. |
|---|
| 769 |
{% endif %} |
|---|
| 770 |
|
|---|
| 771 |
{% if athlete_list or coach_list %} |
|---|
| 772 |
There are some athletes or some coaches. |
|---|
| 773 |
{% endif %} |
|---|
| 774 |
|
|---|
| 775 |
{% if athlete_list and coach_list %} |
|---|
| 776 |
Both atheletes and coaches are available. |
|---|
| 777 |
{% endif %} |
|---|
| 778 |
|
|---|
| 779 |
{% if not athlete_list or coach_list %} |
|---|
| 780 |
There are no athletes, or there are some coaches. |
|---|
| 781 |
{% endif %} |
|---|
| 782 |
|
|---|
| 783 |
{% if athlete_list and not coach_list %} |
|---|
| 784 |
There are some athletes and absolutely no coaches. |
|---|
| 785 |
{% endif %} |
|---|
| 786 |
|
|---|
| 787 |
``if`` tags do not allow ``and`` and ``or`` clauses with the same tag, |
|---|
| 788 |
because the order of logic would be ambigous. For example, this is |
|---|
| 789 |
invalid:: |
|---|
| 790 |
|
|---|
| 791 |
{% if athlete_list and coach_list or cheerleader_list %} |
|---|
| 792 |
|
|---|
| 793 |
If you need to combine ``and`` and ``or`` to do advanced logic, just use |
|---|
| 794 |
nested if tags. For example:: |
|---|
| 795 |
|
|---|
| 796 |
{% if athlete_list %} |
|---|
| 797 |
{% if coach_list or cheerleader_list %} |
|---|
| 798 |
We have athletes, and either coaches or cheerleaders! |
|---|
| 799 |
{% endif %} |
|---|
| 800 |
{% endif %} |
|---|
| 801 |
""" |
|---|
| 802 |
bits = token.contents.split() |
|---|
| 803 |
del bits[0] |
|---|
| 804 |
if not bits: |
|---|
| 805 |
raise TemplateSyntaxError("'if' statement requires at least one argument") |
|---|
| 806 |
# Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d'] |
|---|
| 807 |
bitstr = ' '.join(bits) |
|---|
| 808 |
boolpairs = bitstr.split(' and ') |
|---|
| 809 |
boolvars = [] |
|---|
| 810 |
if len(boolpairs) == 1: |
|---|
| 811 |
link_type = IfNode.LinkTypes.or_ |
|---|
| 812 |
boolpairs = bitstr.split(' or ') |
|---|
| 813 |
else: |
|---|
| 814 |
link_type = IfNode.LinkTypes.and_ |
|---|
| 815 |
if ' or ' in bitstr: |
|---|
| 816 |
raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'" |
|---|
| 817 |
for boolpair in boolpairs: |
|---|
| 818 |
if ' ' in boolpair: |
|---|
| 819 |
try: |
|---|
| 820 |
not_, boolvar = boolpair.split() |
|---|
| 821 |
except ValueError: |
|---|
| 822 |
raise TemplateSyntaxError, "'if' statement improperly formatted" |
|---|
| 823 |
if not_ != 'not': |
|---|
| 824 |
raise TemplateSyntaxError, "Expected 'not' in if statement" |
|---|
| 825 |
boolvars.append((True, parser.compile_filter(boolvar))) |
|---|
| 826 |
else: |
|---|
| 827 |
boolvars.append((False, parser.compile_filter(boolpair))) |
|---|
| 828 |
nodelist_true = parser.parse(('else', 'endif')) |
|---|
| 829 |
token = parser.next_token() |
|---|
| 830 |
if token.contents == 'else': |
|---|
| 831 |
nodelist_false = parser.parse(('endif',)) |
|---|
| 832 |
parser.delete_first_token() |
|---|
| 833 |
else: |
|---|
| 834 |
nodelist_false = NodeList() |
|---|
| 835 |
return IfNode(boolvars, nodelist_true, nodelist_false, link_type) |
|---|
| 836 |
do_if = register.tag("if", do_if) |
|---|
| 837 |
|
|---|
| 838 |
#@register.tag |
|---|
| 839 |
def ifchanged(parser, token): |
|---|
| 840 |
""" |
|---|
| 841 |
Checks if a value has changed from the last iteration of a loop. |
|---|
| 842 |
|
|---|
| 843 |
The 'ifchanged' block tag is used within a loop. It has two possible uses. |
|---|
| 844 |
|
|---|
| 845 |
1. Checks its own rendered contents against its previous state and only |
|---|
| 846 |
displays the content if it has changed. For example, this displays a |
|---|
| 847 |
list of days, only displaying the month if it changes:: |
|---|
| 848 |
|
|---|
| 849 |
<h1>Archive for {{ year }}</h1> |
|---|
| 850 |
|
|---|
| 851 |
{% for date in days %} |
|---|
| 852 |
{% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %} |
|---|
| 853 |
<a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a> |
|---|
| 854 |
{% endfor %} |
|---|
| 855 |
|
|---|
| 856 |
2. If given a variable, check whether that variable has changed. |
|---|
| 857 |
For example, the following shows the date every time it changes, but |
|---|
| 858 |
only shows the hour if both the hour and the date have changed:: |
|---|
| 859 |
|
|---|
| 860 |
{% for date in days %} |
|---|
| 861 |
{% ifchanged date.date %} {{ date.date }} {% endifchanged %} |
|---|
| 862 |
{% ifchanged date.hour date.date %} |
|---|
| 863 |
{{ date.hour }} |
|---|
| 864 |
{% endifchanged %} |
|---|
| 865 |
{% endfor %} |
|---|
| 866 |
""" |
|---|
| 867 |
bits = token.contents.split() |
|---|
| 868 |
nodelist_true = parser.parse(('else', 'endifchanged')) |
|---|
| 869 |
token = parser.next_token() |
|---|
| 870 |
if token.contents == 'else': |
|---|
| 871 |
nodelist_false = parser.parse(('endifchanged',)) |
|---|
| 872 |
parser.delete_first_token() |
|---|
| 873 |
else: |
|---|
| 874 |
nodelist_false = NodeList() |
|---|
| 875 |
values = [parser.compile_filter(bit) for bit in bits[1:]] |
|---|
| 876 |
return IfChangedNode(nodelist_true, nodelist_false, *values) |
|---|
| 877 |
ifchanged = register.tag(ifchanged) |
|---|
| 878 |
|
|---|
| 879 |
#@register.tag |
|---|
| 880 |
def ssi(parser, token): |
|---|
| 881 |
""" |
|---|
| 882 |
Outputs the contents of a given file into the page. |
|---|
| 883 |
|
|---|
| 884 |
Like a simple "include" tag, the ``ssi`` tag includes the contents |
|---|
| 885 |
of another file -- which must be specified using an absolute path -- |
|---|
| 886 |
in the current page:: |
|---|
| 887 |
|
|---|
| 888 |
{% ssi /home/html/ljworld.com/includes/right_generic.html %} |
|---|
| 889 |
|
|---|
| 890 |
If the optional "parsed" parameter is given, the contents of the included |
|---|
| 891 |
file are evaluated as template code, with the current context:: |
|---|
| 892 |
|
|---|
| 893 |
{% ssi /home/html/ljworld.com/includes/right_generic.html parsed %} |
|---|
| 894 |
""" |
|---|
| 895 |
bits = token.contents.split() |
|---|
| 896 |
parsed = False |
|---|
| 897 |
if len(bits) not in (2, 3): |
|---|
| 898 |
raise TemplateSyntaxError("'ssi' tag takes one argument: the path to" |
|---|
| 899 |
" the file to be included") |
|---|
| 900 |
if len(bits) == 3: |
|---|
| 901 |
if bits[2] == 'parsed': |
|---|
| 902 |
parsed = True |
|---|
| 903 |
else: |
|---|
| 904 |
raise TemplateSyntaxError("Second (optional) argument to %s tag" |
|---|
| 905 |
" must be 'parsed'" % bits[0]) |
|---|
| 906 |
return SsiNode(bits[1], parsed) |
|---|
| 907 |
ssi = register.tag(ssi) |
|---|
| 908 |
|
|---|
| 909 |
#@register.tag |
|---|
| 910 |
def load(parser, token): |
|---|
| 911 |
""" |
|---|
| 912 |
Loads a custom template tag set. |
|---|
| 913 |
|
|---|
| 914 |
For example, to load the template tags in |
|---|
| 915 |
``django/templatetags/news/photos.py``:: |
|---|
| 916 |
|
|---|
| 917 |
{% load news.photos %} |
|---|
| 918 |
""" |
|---|
| 919 |
bits = token.contents.split() |
|---|
| 920 |
for taglib in bits[1:]: |
|---|
| 921 |
# add the library to the parser |
|---|
| 922 |
try: |
|---|
| 923 |
lib = get_library("django.templatetags.%s" % taglib) |
|---|
| 924 |
parser.add_library(lib) |
|---|
| 925 |
except InvalidTemplateLibrary, e: |
|---|
| 926 |
raise TemplateSyntaxError("'%s' is not a valid tag library: %s" % |
|---|
| 927 |
(taglib, e)) |
|---|
| 928 |
return LoadNode() |
|---|
| 929 |
load = register.tag(load) |
|---|
| 930 |
|
|---|
| 931 |
#@register.tag |
|---|
| 932 |
def now(parser, token): |
|---|
| 933 |
""" |
|---|
| 934 |
Displays the date, formatted according to the given string. |
|---|
| 935 |
|
|---|
| 936 |
Uses the same format as PHP's ``date()`` function; see http://php.net/date |
|---|
| 937 |
for all the possible values. |
|---|
| 938 |
|
|---|
| 939 |
Sample usage:: |
|---|
| 940 |
|
|---|
| 941 |
It is {% now "jS F Y H:i" %} |
|---|
| 942 |
""" |
|---|
| 943 |
bits = token.contents.split('"') |
|---|
| 944 |
if len(bits) != 3: |
|---|
| 945 |
raise TemplateSyntaxError, "'now' statement takes one argument" |
|---|
| 946 |
format_string = bits[1] |
|---|
| 947 |
return NowNode(format_string) |
|---|
| 948 |
now = register.tag(now) |
|---|
| 949 |
|
|---|
| 950 |
#@register.tag |
|---|
| 951 |
def regroup(parser, token): |
|---|
| 952 |
""" |
|---|
| 953 |
Regroups a list of alike objects by a common attribute. |
|---|
| 954 |
|
|---|
| 955 |
This complex tag is best illustrated by use of an example: say that |
|---|
| 956 |
``people`` is a list of ``Person`` objects that have ``first_name``, |
|---|
| 957 |
``last_name``, and ``gender`` attributes, and you'd like to display a list |
|---|
| 958 |
that looks like: |
|---|
| 959 |
|
|---|
| 960 |
* Male: |
|---|
| 961 |
* George Bush |
|---|
| 962 |
* Bill Clinton |
|---|
| 963 |
* Female: |
|---|
| 964 |
* Margaret Thatcher |
|---|
| 965 |
* Colendeeza Rice |
|---|
| 966 |
* Unknown: |
|---|
| 967 |
* Pat Smith |
|---|
| 968 |
|
|---|
| 969 |
The following snippet of template code would accomplish this dubious task:: |
|---|
| 970 |
|
|---|
| 971 |
{% regroup people by gender as grouped %} |
|---|
| 972 |
<ul> |
|---|
| 973 |
{% for group in grouped %} |
|---|
| 974 |
<li>{{ group.grouper }} |
|---|
| 975 |
<ul> |
|---|
| 976 |
{% for item in group.list %} |
|---|
| 977 |
<li>{{ item }}</li> |
|---|
| 978 |
{% endfor %} |
|---|
| 979 |
</ul> |
|---|
| 980 |
{% endfor %} |
|---|
| 981 |
</ul> |
|---|
| 982 |
|
|---|
| 983 |
As you can see, ``{% regroup %}`` populates a variable with a list of |
|---|
| 984 |
objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the |
|---|
| 985 |
item that was grouped by; ``list`` contains the list of objects that share |
|---|
| 986 |
that ``grouper``. In this case, ``grouper`` would be ``Male``, ``Female`` |
|---|
| 987 |
and ``Unknown``, and ``list`` is the list of people with those genders. |
|---|
| 988 |
|
|---|
| 989 |
Note that ``{% regroup %}`` does not work when the list to be grouped is not |
|---|
| 990 |
sorted by the key you are grouping by! This means that if your list of |
|---|
| 991 |
people was not sorted by gender, you'd need to make sure it is sorted |
|---|
| 992 |
before using it, i.e.:: |
|---|
| 993 |
|
|---|
| 994 |
{% regroup people|dictsort:"gender" by gender as grouped %} |
|---|
| 995 |
|
|---|
| 996 |
""" |
|---|
| 997 |
firstbits = token.contents.split(None, 3) |
|---|
| 998 |
if len(firstbits) != 4: |
|---|
| 999 |
raise TemplateSyntaxError, "'regroup' tag takes five arguments" |
|---|
| 1000 |
target = parser.compile_filter(firstbits[1]) |
|---|
| 1001 |
if firstbits[2] != 'by': |
|---|
| 1002 |
raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") |
|---|
| 1003 |
lastbits_reversed = firstbits[3][::-1].split(None, 2) |
|---|
| 1004 |
if lastbits_reversed[1][::-1] != 'as': |
|---|
| 1005 |
raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" |
|---|
| 1006 |
" be 'as'") |
|---|
| 1007 |
|
|---|
| 1008 |
expression = parser.compile_filter(lastbits_reversed[2][::-1]) |
|---|
| 1009 |
|
|---|
| 1010 |
var_name = lastbits_reversed[0][::-1] |
|---|
| 1011 |
return RegroupNode(target, expression, var_name) |
|---|
| 1012 |
regroup = register.tag(regroup) |
|---|
| 1013 |
|
|---|
| 1014 |
def spaceless(parser, token): |
|---|
| 1015 |
""" |
|---|
| 1016 |
Removes whitespace between HTML tags, including tab and newline characters. |
|---|
| 1017 |
|
|---|
| 1018 |
Example usage:: |
|---|
| 1019 |
|
|---|
| 1020 |
{% spaceless %} |
|---|
| 1021 |
<p> |
|---|
| 1022 |
<a href="foo/">Foo</a> |
|---|
| 1023 |
</p> |
|---|
| 1024 |
{% endspaceless %} |
|---|
| 1025 |
|
|---|
| 1026 |
This example would return this HTML:: |
|---|
| 1027 |
|
|---|
| 1028 |
<p><a href="foo/">Foo</a></p> |
|---|
| 1029 |
|
|---|
| 1030 |
Only space between *tags* is normalized -- not space between tags and text. |
|---|
| 1031 |
In this example, the space around ``Hello`` won't be stripped:: |
|---|
| 1032 |
|
|---|
| 1033 |
{% spaceless %} |
|---|
| 1034 |
<strong> |
|---|
| 1035 |
Hello |
|---|
| 1036 |
</strong> |
|---|
| 1037 |
{% endspaceless %} |
|---|
| 1038 |
""" |
|---|
| 1039 |
nodelist = parser.parse(('endspaceless',)) |
|---|
| 1040 |
parser.delete_first_token() |
|---|
| 1041 |
return SpacelessNode(nodelist) |
|---|
| 1042 |
spaceless = register.tag(spaceless) |
|---|
| 1043 |
|
|---|
| 1044 |
#@register.tag |
|---|
| 1045 |
def templatetag(parser, token): |
|---|
| 1046 |
""" |
|---|
| 1047 |
Outputs one of the bits used to compose template tags. |
|---|
| 1048 |
|
|---|
| 1049 |
Since the template system has no concept of "escaping", to display one of |
|---|
| 1050 |
the bits used in template tags, you must use the ``{% templatetag %}`` tag. |
|---|
| 1051 |
|
|---|
| 1052 |
The argument tells which template bit to output: |
|---|
| 1053 |
|
|---|
| 1054 |
================== ======= |
|---|
| 1055 |
Argument Outputs |
|---|
| 1056 |
================== ======= |
|---|
| 1057 |
``openblock`` ``{%`` |
|---|
| 1058 |
``closeblock`` ``%}`` |
|---|
| 1059 |
``openvariable`` ``{{`` |
|---|
| 1060 |
``closevariable`` ``}}`` |
|---|
| 1061 |
``openbrace`` ``{`` |
|---|
| 1062 |
``closebrace`` ``}`` |
|---|
| 1063 |
``opencomment`` ``{#`` |
|---|
| 1064 |
``closecomment`` ``#}`` |
|---|
| 1065 |
================== ======= |
|---|
| 1066 |
""" |
|---|
| 1067 |
bits = token.contents.split() |
|---|
| 1068 |
if len(bits) != 2: |
|---|
| 1069 |
raise TemplateSyntaxError, "'templatetag' statement takes one argument" |
|---|
| 1070 |
tag = bits[1] |
|---|
| 1071 |
if tag not in TemplateTagNode.mapping: |
|---|
| 1072 |
raise TemplateSyntaxError("Invalid templatetag argument: '%s'." |
|---|
| 1073 |
" Must be one of: %s" % |
|---|
| 1074 |
(tag, TemplateTagNode.mapping.keys())) |
|---|
| 1075 |
return TemplateTagNode(tag) |
|---|
| 1076 |
templatetag = register.tag(templatetag) |
|---|
| 1077 |
|
|---|
| 1078 |
def url(parser, token): |
|---|
| 1079 |
""" |
|---|
| 1080 |
Returns an absolute URL matching given view with its parameters. |
|---|
| 1081 |
|
|---|
| 1082 |
This is a way to define links that aren't tied to a particular URL |
|---|
| 1083 |
configuration:: |
|---|
| 1084 |
|
|---|
| 1085 |
{% url path.to.some_view arg1,arg2,name1=value1 %} |
|---|
| 1086 |
|
|---|
| 1087 |
The first argument is a path to a view. It can be an absolute python path |
|---|
| 1088 |
or just ``app_name.view_name`` without the project name if the view is |
|---|
| 1089 |
located inside the project. Other arguments are comma-separated values |
|---|
| 1090 |
that will be filled in place of positional and keyword arguments in the |
|---|
| 1091 |
URL. All arguments for the URL should be present. |
|---|
| 1092 |
|
|---|
| 1093 |
For example if you have a view ``app_name.client`` taking client's id and |
|---|
| 1094 |
the corresponding line in a URLconf looks like this:: |
|---|
| 1095 |
|
|---|
| 1096 |
('^client/(\d+)/$', 'app_name.client') |
|---|
| 1097 |
|
|---|
| 1098 |
and this app's URLconf is included into the project's URLconf under some |
|---|
| 1099 |
path:: |
|---|
| 1100 |
|
|---|
| 1101 |
('^clients/', include('project_name.app_name.urls')) |
|---|
| 1102 |
|
|---|
| 1103 |
then in a template you can create a link for a certain client like this:: |
|---|
| 1104 |
|
|---|
| 1105 |
{% url app_name.client client.id %} |
|---|
| 1106 |
|
|---|
| 1107 |
The URL will look like ``/clients/client/123/``. |
|---|
| 1108 |
""" |
|---|
| 1109 |
bits = token.split_contents() |
|---|
| 1110 |
if len(bits) < 2: |
|---|
| 1111 |
raise TemplateSyntaxError("'%s' takes at least one argument" |
|---|
| 1112 |
" (path to a view)" % bits[0]) |
|---|
| 1113 |
viewname = bits[1] |
|---|
| 1114 |
args = [] |
|---|
| 1115 |
kwargs = {} |
|---|
| 1116 |
asvar = None |
|---|
| 1117 |
|
|---|
| 1118 |
if len(bits) > 2: |
|---|
| 1119 |
bits = iter(bits[2:]) |
|---|
| 1120 |
for bit in bits: |
|---|
| 1121 |
if bit == 'as': |
|---|
| 1122 |
asvar = bits.next() |
|---|
| 1123 |
break |
|---|
| 1124 |
else: |
|---|
| 1125 |
for arg in bit.split(","): |
|---|
| 1126 |
if '=' in arg: |
|---|
| 1127 |
k, v = arg.split('=', 1) |
|---|
| 1128 |
k = k.strip() |
|---|
| 1129 |
kwargs[k] = parser.compile_filter(v) |
|---|
| 1130 |
elif arg: |
|---|
| 1131 |
args.append(parser.compile_filter(arg)) |
|---|
| 1132 |
return URLNode(viewname, args, kwargs, asvar) |
|---|
| 1133 |
url = register.tag(url) |
|---|
| 1134 |
|
|---|
| 1135 |
#@register.tag |
|---|
| 1136 |
def widthratio(parser, token): |
|---|
| 1137 |
""" |
|---|
| 1138 |
For creating bar charts and such, this tag calculates the ratio of a given |
|---|
| 1139 |
value to a maximum value, and then applies that ratio to a constant. |
|---|
| 1140 |
|
|---|
| 1141 |
For example:: |
|---|
| 1142 |
|
|---|
| 1143 |
<img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' /> |
|---|
| 1144 |
|
|---|
| 1145 |
Above, if ``this_value`` is 175 and ``max_value`` is 200, the image in |
|---|
| 1146 |
the above example will be 88 pixels wide (because 175/200 = .875; |
|---|
| 1147 |
.875 * 100 = 87.5 which is rounded up to 88). |
|---|
| 1148 |
""" |
|---|
| 1149 |
bits = token.contents.split() |
|---|
| 1150 |
if len(bits) != 4: |
|---|
| 1151 |
raise TemplateSyntaxError("widthratio takes three arguments") |
|---|
| 1152 |
tag, this_value_expr, max_value_expr, max_width = bits |
|---|
| 1153 |
|
|---|
| 1154 |
return WidthRatioNode(parser.compile_filter(this_value_expr), |
|---|
| 1155 |
parser.compile_filter(max_value_expr), |
|---|
| 1156 |
parser.compile_filter(max_width)) |
|---|
| 1157 |
widthratio = register.tag(widthratio) |
|---|
| 1158 |
|
|---|
| 1159 |
#@register.tag |
|---|
| 1160 |
def do_with(parser, token): |
|---|
| 1161 |
""" |
|---|
| 1162 |
Adds a value to the context (inside of this block) for caching and easy |
|---|
| 1163 |
access. |
|---|
| 1164 |
|
|---|
| 1165 |
For example:: |
|---|
| 1166 |
|
|---|
| 1167 |
{% with person.some_sql_method as total %} |
|---|
| 1168 |
{{ total }} object{{ total|pluralize }} |
|---|
| 1169 |
{% endwith %} |
|---|
| 1170 |
""" |
|---|
| 1171 |
bits = list(token.split_contents()) |
|---|
| 1172 |
if len(bits) != 4 or bits[2] != "as": |
|---|
| 1173 |
raise TemplateSyntaxError("%r expected format is 'value as name'" % |
|---|
| 1174 |
bits[0]) |
|---|
| 1175 |
var = parser.compile_filter(bits[1]) |
|---|
| 1176 |
name = bits[3] |
|---|
| 1177 |
nodelist = parser.parse(('endwith',)) |
|---|
| 1178 |
parser.delete_first_token() |
|---|
| 1179 |
return WithNode(var, name, nodelist) |
|---|
| 1180 |
do_with = register.tag('with', do_with) |
|---|