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