Skip to content

Instantly share code, notes, and snippets.

@treyhunner

treyhunner/autoescape.py

Last active Feb 15, 2021
Embed
What would you like to do?
# django.template.defaulttags.autoescape rewritten using Python 3.10's match/case statement
@register.tag
def autoescape(parser, token):
"""
Force autoescape behavior for this block.
"""
# token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
match token.contents.split():
case [_, ('on' | 'off') as arg]:
nodelist = parser.parse(('endautoescape',))
parser.delete_first_token()
return AutoEscapeControlNode((arg == 'on'), nodelist)
case [_, _]:
raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'")
case _:
raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.")
# django.template.defaulttags.cycle rewritten using Python 3.10's match/case statement
@register.tag
def cycle(parser, token):
"""
Cycle among the given strings each time this tag is encountered.
Within a loop, cycles among the given strings each time through
the loop::
{% for o in some_list %}
<tr class="{% cycle 'row1' 'row2' %}">
...
</tr>
{% endfor %}
Outside of a loop, give the values a unique name the first time you call
it, then use that name each successive time through::
<tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
You can use any number of values, separated by spaces. Commas can also
be used to separate values; if a comma is used, the cycle values are
interpreted as literal strings.
The optional flag "silent" can be used to prevent the cycle declaration
from returning any value::
{% for o in some_list %}
{% cycle 'row1' 'row2' as rowcolors silent %}
<tr class="{{ rowcolors }}">{% include "subtemplate.html " %}</tr>
{% endfor %}
"""
# Note: This returns the exact same node on each {% cycle name %} call;
# that is, the node object returned from {% cycle a b c as name %} and the
# one returned from {% cycle name %} are the exact same object. This
# shouldn't cause problems (heh), but if it does, now you know.
#
# Ugly hack warning: This stuffs the named template dict into parser so
# that names are only unique within each template (as opposed to using
# a global variable, which would make cycle names have to be unique across
# *all* templates.
#
# It keeps the last node in the parser to be able to reset it with
# {% resetcycle %}.
match token.split_contents():
case [_]:
raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
# {% cycle foo %} case.
case [_, name]:
if not hasattr(parser, '_named_cycle_nodes'):
raise TemplateSyntaxError(f"No named cycles in template. '{name}' is not defined")
if name not in parser._named_cycle_nodes:
raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
return parser._named_cycle_nodes[name]
# {% cycle ... as foo [silent] %} case.
case [_, *args, "as", name, flag]:
raise TemplateSyntaxError(f"Only 'silent' flag is allowed after cycle's name, not '{flag}'.")
case [_, *args, "as", name]:
as_form = True
silent = False
case [_, *args, "as", name, "silent"]:
as_form = True
silent = True
# {% cycle foo bar ... %} case.
case [_, *args]:
as_form = False
if as_form:
values = [parser.compile_filter(arg) for arg in args]
node = CycleNode(values, name, silent=silent)
if not hasattr(parser, '_named_cycle_nodes'):
parser._named_cycle_nodes = {}
parser._named_cycle_nodes[name] = node
else:
values = [parser.compile_filter(arg) for arg in args]
node = CycleNode(values)
parser._last_cycle_node = node
return node
# django.templatetags.i18n.do_get_language_info rewritten using Python 3.10's match/case statement
@register.tag("get_language_info")
def do_get_language_info(parser, token):
"""
Store the language information dictionary for the given language code in a
context variable.
Usage::
{% get_language_info for LANGUAGE_CODE as l %}
{{ l.code }}
{{ l.name }}
{{ l.name_translated }}
{{ l.name_local }}
{{ l.bidi|yesno:"bi-directional,uni-directional" }}
"""
match token.split_contents():
case [name, "for", code "as" info]:
return GetLanguageInfoNode(parser.compile_filter(code), info)
case [name, *rest]:
raise TemplateSyntaxError(f"'{name}' requires 'for string as variable' (got {rest!r})")
# django.utils.formats.localize rewritten using Python 3.10's match/case statement
def localize(value, use_l10n=None):
"""
Check if value is a localizable type (date, number...) and return it
formatted as a string using current locale format.
If use_l10n is provided and is not None, it forces the value to
be localized (or not), overriding the value of settings.USE_L10N.
"""
match value:
case str(): # Handle strings first for performance reasons.
return value
case bool(): # Make sure booleans don't get treated as numbers
return str(value)
case decimal.Decimal() | float() | int():
if use_l10n is False:
return str(value)
return number_format(value, use_l10n=use_l10n)
case datetime.datetime():
return date_format(value, 'DATETIME_FORMAT', use_l10n=use_l10n)
case datetime.date():
return date_format(value, use_l10n=use_l10n)
case datetime.time():
return time_format(value, 'TIME_FORMAT', use_l10n=use_l10n)
case _:
return value
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment