def parse_tag(token, parser): """Parses template tag for name, arguments and keyword arguments. :param token: Template token containing all the tag contents :type token: django.template.base.Token :param parser: Template parser :type parser: django.template.base.Parser :return: Tuple with tag name, arguments and keyword arguments :rtype: tuple """ # Split the tag content into words, respecting quoted strings. bits = token.split_contents() # Pull out the tag name. tag_name = bits.pop(0) # Parse the rest of the args, and build FilterExpressions from them so that # we can evaluate them later. args = [] kwargs = {} for bit in bits: # Is this a kwarg or an arg? match = kwarg_re.match(bit) kwarg_format = match and match.group(1) if kwarg_format: key, value = match.groups() kwargs[key] = FilterExpression(value, parser) else: args.append(FilterExpression(bit, parser)) return (tag_name, args, kwargs)
def test_filter_parsing(self): c = {"article": {"section": "News"}} p = Parser("", builtins=[filter_library]) def fe_test(s, val): self.assertEqual(FilterExpression(s, p).resolve(c), val) fe_test("article.section", "News") fe_test("article.section|upper", "NEWS") fe_test('"News"', "News") fe_test("'News'", "News") fe_test(r'"Some \"Good\" News"', 'Some "Good" News') fe_test(r'"Some \"Good\" News"', 'Some "Good" News') fe_test(r"'Some \'Bad\' News'", "Some 'Bad' News") fe = FilterExpression(r'"Some \"Good\" News"', p) self.assertEqual(fe.filters, []) self.assertEqual(fe.var, 'Some "Good" News') # Filtered variables should reject access of attributes beginning with # underscores. msg = ( "Variables and attributes may not begin with underscores: 'article._hidden'" ) with self.assertRaisesMessage(TemplateSyntaxError, msg): FilterExpression("article._hidden|upper", p)
def do_usemacro(parser, token): try: args = token.split_contents() macro_name, values = args[1], args[2:] except IndexError: m = ("'%s' tag requires at least one argument (macro name)" % token.contents.split()[0]) raise template.TemplateSyntaxError(m) try: macro = parser._macros[macro_name] except (AttributeError, KeyError): m = "Macro '%s' is not defined" % macro_name raise template.TemplateSyntaxError(m) fe_kwargs = {} fe_args = [] for val in values: if "=" in val: # kwarg name, value = val.split("=") fe_kwargs[name] = FilterExpression(value, parser) else: # arg # no validation, go for it ... fe_args.append(FilterExpression(val, parser)) macro.parser = parser return UseMacroNode(macro, fe_args, fe_kwargs)
class iffchangedNode(template.Node): def __init__(self, parser, condition, true_value, false_value, asvar=None): self.condition = FilterExpression(condition, parser) self.true_value = FilterExpression(true_value, parser) self.false_value = FilterExpression(false_value, parser) self.asvar = asvar def render(self, context): if self not in context.render_context: context.render_context[self] = {'condition': self.condition.resolve(context)} if self.asvar: context[self.asvar] = self.true_value.resolve(context) return self.true_value.resolve(context) else: if context.render_context[self]['condition'] == self.condition.resolve(context): if self.asvar: context[self.asvar] = self.false_value.resolve(context) else: return self.false_value.resolve(context) else: context.render_context[self]['condition'] = self.condition.resolve(context) if self.asvar: context[self.asvar] = self.true_value.resolve(context) else: return self.true_value.resolve(context) return ''
def interpolate(parser, tokens): ''' make django.template.Variable's from the input tokens ''' args = [] kwargs = {} for token in tokens: match = kwarg_re.match(token) if match: kwargs[match.group(1)] = FilterExpression(match.group(2), parser) else: args.append(FilterExpression(token, parser)) return (args, kwargs)
def parse_tag_args(parser, parts): args = [] kwargs = {} for part in parts: match = kwarg_re.match(part) kwarg_format = match and match.group(1) if kwarg_format: key, value = match.groups() kwargs[key] = FilterExpression(value, parser) else: args.append(FilterExpression(part, parser)) return args, kwargs
def do_get_latest_objects_by_category(parser, token): """ Get the latest objects by category {% get_latest_objects_by_category category app_name model_name set_name [date_field] [number] as [var_name] %} """ proper_form = "{% get_latest_objects_by_category category app_name model_name set_name [date_field] [number] as [var_name] %}" bits = token.split_contents() if bits[-2] != 'as': raise TemplateSyntaxError("%s tag shoud be in the form: %s" % (bits[0], proper_form)) if len(bits) < 7: raise TemplateSyntaxError("%s tag shoud be in the form: %s" % (bits[0], proper_form)) if len(bits) > 9: raise TemplateSyntaxError("%s tag shoud be in the form: %s" % (bits[0], proper_form)) category = FilterExpression(bits[1], parser) app_label = FilterExpression(bits[2], parser) model_name = FilterExpression(bits[3], parser) set_name = FilterExpression(bits[4], parser) var_name = bits[-1] if bits[5] != 'as': date_field = FilterExpression(bits[5], parser) else: date_field = FilterExpression(None, parser) if bits[6] != 'as': num = FilterExpression(bits[6], parser) else: num = FilterExpression(None, parser) return LatestObjectsNode(var_name, category, app_label, model_name, set_name, date_field, num)
def parse_args_kwargs(parser, bits): # Parse the rest of the args, and build FilterExpressions from them so that # we can evaluate them later. args = [] kwargs = {} for bit in bits: # Is this a kwarg or an arg? match = kwarg_re.match(bit) kwarg_format = match and match.group(1) if kwarg_format: key, value = match.groups() kwargs[key] = FilterExpression(value, parser) else: args.append(FilterExpression(bit, parser)) return args, kwargs
def parse_tag(token, parser): bits = token.split_contents() tag_name = bits.pop(0) args = [] kwargs = {} for bit in bits: match = kwarg_re.match(bit) kwarg_format = match and match.group(1) if kwarg_format: key, value = match.groups() kwargs[key] = FilterExpression(value, parser) else: args.append(FilterExpression(bit, parser)) return (tag_name, args, kwargs)
def recursetree(parser, token): """ Iterates over the nodes in the tree, and renders the contained block for each node. This tag will recursively render children into the template variable {{ children }}. Only one database query is required (children are cached for the whole tree) Usage: <ul> {% recursetree nodes %} <li> {{ node.name }} {% if not node.is_leaf_node %} <ul> {{ children }} </ul> {% endif %} </li> {% endrecursetree %} </ul> """ bits = token.contents.split() if len(bits) != 2: raise template.TemplateSyntaxError('%s tag requires a queryset' % bits[0]) queryset_var = FilterExpression(bits[1], parser) template_nodes = parser.parse(('endrecursetree', )) parser.delete_first_token() return RecurseTreeNode(template_nodes, queryset_var)
def render(self, context): for i, arg in enumerate(self.macro.args): try: fe = self.fe_args[i] context[arg] = fe.resolve(context) except IndexError: context[arg] = "" for name, default in self.macro.kwargs.items(): if name in self.fe_kwargs: context[name] = self.fe_kwargs[name].resolve(context) else: context[name] = FilterExpression(default, self.macro.parser ).resolve(context) # # Place output into context variable # context[self.macro.name] = self.macro.nodelist.render(context) # return '' if self.context_only else context[self.macro.name] string = self.macro.nodelists[0].render(context) for nl in self.macro.nodelists[1:]: # render macro body string += self.nodelist.render(context) string += nl.render(context) return string
def do_usemacro(parser, token): try: args = token.split_contents() tag_name, macro_name, values = args[0], args[1], args[2:] except IndexError: raise (template.TemplateSyntaxError, "'%s' tag requires at least one argument (macro name)" % token.contents.split()[0]) try: macro = parser._macros[macro_name] except (AttributeError, KeyError): raise (template.TemplateSyntaxError, "Macro '%s' is not defined" % macro_name) if (len(values) != len(macro.args)): raise ( template.TemplateSyntaxError, "Macro '%s' was declared with %d parameters and used with %d parameter" % (macro_name, len(macro.args), len(values))) filter_expressions = [] for val in values: if (val[0] == "'" or val[0] == '"') and (val[0] != val[-1]): raise (template.TemplateSyntaxError, "Non-terminated string argument: %s" % val[1:]) filter_expressions.append(FilterExpression(val, parser)) return UseMacroNode(macro, filter_expressions)
def pjaxr_extends(parser, token): bits = token.split_contents() if len(bits) != 4 and len(bits) != 3 and len(bits) != 2: raise TemplateSyntaxError("'%s' takes 1 - 3 arguments" % bits[0]) nodelist = parser.parse() if nodelist.get_nodes_by_type( PjaxrExtendsNode) or nodelist.get_nodes_by_type(ExtendsNode): raise TemplateSyntaxError( "'pjaxr_extends' and 'extends' cannot appear more than once in the same template!" ) if len(bits) > 2: try: # format DEFAULT_PJAXR_TEMPLATE string to fit into FilterExpression as token pjaxr_template = parser.compile_filter( bits[3]) if (len(bits) == 4) else FilterExpression( "'{0}'".format(settings.DEFAULT_PJAXR_TEMPLATE), parser) except AttributeError: raise TemplateSyntaxError( "No Pjaxr template set, even no default!") return PjaxrExtendsNode(nodelist, parser.compile_filter(bits[1]), parser.compile_filter(bits[2]), pjaxr_template) return ExtendsNode(nodelist, parser.compile_filter(bits[1]))
def do_math(parser, token): """ Syntax: {% math <argument, ..> "expression" as var_name %} Evaluates a math expression in the current context and saves the value into a variable with the given name. "$<number>" is a placeholder in the math expression. It will be replaced by the value of the argument at index <number> - 1. Arguments are static values or variables immediately after 'math' tag and before the expression (the third last token). Example usage, {% math a b "min($1, $2)" as result %} {% math a|length b|length 3 "($1 + $2) % $3" as result %} """ tokens = token.split_contents() if len(tokens) < 5: raise TemplateSyntaxError("'math' tag requires at least 4 arguments") expr, as_, var_name = tokens[-3:] # strip quote if found if re.match(r'^(\'|")', expr): expr = expr.strip(expr[0]) args = [] for a in tokens[1:-3]: if a.find('|') != -1: args.append(FilterExpression(a, parser)) else: args.append(Variable(a)) return MathNode(var_name, expr, args)
def token_value(bits, parser): """Parse ``bits`` string and return string or variable (FilterExpression).""" if bits[0] in ('"', "'"): # Parse a string. if not (bits[0] == bits[-1] or len(bits) < 2): raise template.TemplateSyntaxError("Malformed argument %r" % bits) return bits[1:-1] else: # Parse a variable. return FilterExpression(bits, parser)
def get_category_drilldown(parser, token): """ Retrieves the specified category, its ancestors and its immediate children as an Iterable. The basic syntax:: {% get_category_drilldown "category name" [using "app.Model"] as varname %} Example: Using a string for the category name:: {% get_category_drilldown "/Grandparent/Parent" as family %} or using a variable for the category:: {% get_category_drilldown category_obj as family %} Sets family to:: Grandparent, Parent, Child 1, Child 2, Child n Args: parser: The Django template parser. token: The tag contents Returns: The recursive tree node. Raises: TemplateSyntaxError: If the tag is malformed. """ bits = token.split_contents() error_str = ("%(tagname)s tag should be in the format {%% %(tagname)s " '"category name" [using "app.Model"] as varname %%} or ' "{%% %(tagname)s category_obj as varname %%}.") varname = model = "" if len(bits) == 4: if bits[2] != "as": raise template.TemplateSyntaxError(error_str % {"tagname": bits[0]}) if bits[2] == "as": varname = bits[3].strip("'\"") model = "categories.category" elif len(bits) == 6: if bits[2] not in ("using", "as") or bits[4] not in ("using", "as"): raise template.TemplateSyntaxError(error_str % {"tagname": bits[0]}) if bits[2] == "as": varname = bits[3].strip("'\"") model = bits[5].strip("'\"") if bits[2] == "using": varname = bits[5].strip("'\"") model = bits[3].strip("'\"") else: raise template.TemplateSyntaxError(error_str % {"tagname": bits[0]}) category = FilterExpression(bits[1], parser) return CategoryDrillDownNode(category, varname, model)
class varNode(template.Node): def __init__(self, parser, value, asvar): self.value = FilterExpression(value, parser) self.asvar = asvar def render(self, context): context[self.asvar] = self.value.resolve(context) return ''
def test_filter_args_count(self): p = Parser("") l = Library() @l.filter def no_arguments(value): pass @l.filter def one_argument(value, arg): pass @l.filter def one_opt_argument(value, arg=False): pass @l.filter def two_arguments(value, arg, arg2): pass @l.filter def two_one_opt_arg(value, arg, arg2=False): pass p.add_library(l) for expr in ( '1|no_arguments:"1"', '1|two_arguments', '1|two_arguments:"1"', '1|two_one_opt_arg', ): with self.assertRaises(TemplateSyntaxError): FilterExpression(expr, p) for expr in ( # Correct number of arguments '1|no_arguments', '1|one_argument:"1"', # One optional '1|one_opt_argument', '1|one_opt_argument:"1"', # Not supplying all '1|two_one_opt_arg:"1"', ): FilterExpression(expr, p)
def foldy(parser, token): tag, args, kwargs = parse_block_tag(parser, token) usage = '{{% {tag} name=<str> [expanded=<bool>] [class=<str>] %}}'\ '<foldyhead>'\ '<foldybody>'\ '{{% -{tag} %}}'.format(tag=tag) if 'name' not in kwargs.keys(): raise template.TemplateSyntaxError("Usage: "+usage) nodes = parser.parse(('-'+tag,)) parser.delete_first_token() return FoldyNode( nodes, name=kwargs['name'], expanded=kwargs.get('expanded', FilterExpression("False", parser)), klass=kwargs.get('class', FilterExpression("", parser)) )
def foldybody(parser, token): tag, args, kwargs = parse_block_tag(parser, token) usage = '{{% {tag} [class=<str>] %}}'\ '<content>'\ '{{% -{tag} %}}'.format(tag=tag) nodes = parser.parse(('-'+tag,)) parser.delete_first_token() return FoldybodyNode( nodes, klass=kwargs.get('class', FilterExpression("", parser)) )
def url_timeslot(parser, token): """ Wrapper around url templatetag which inserts a timeslot argument if a timeslot is present. :param parser: :param token: :return: """ node = url(parser, token) # default url parser ts = get_timeslot() if ts: node.args.append(FilterExpression(token=str(ts.pk), parser=parser)) return node
def do_get_latest_objects_by_category(parser, token): """ Get the latest objects by category. The basic syntax is:: {% get_latest_objects_by_category category app_name model_name set_name [date_field] [number] as [var_name] %} Args: parser: The Django template parser. token: The tag contents Returns: The latet objects node. Raises: TemplateSyntaxError: If the tag is malformed """ proper_form = ( "{% get_latest_objects_by_category category app_name model_name set_name " "[date_field] [number] as [var_name] %}") bits = token.split_contents() if bits[-2] != "as": raise TemplateSyntaxError("%s tag shoud be in the form: %s" % (bits[0], proper_form)) if len(bits) < 7: raise TemplateSyntaxError("%s tag shoud be in the form: %s" % (bits[0], proper_form)) if len(bits) > 9: raise TemplateSyntaxError("%s tag shoud be in the form: %s" % (bits[0], proper_form)) category = FilterExpression(bits[1], parser) app_label = FilterExpression(bits[2], parser) model_name = FilterExpression(bits[3], parser) set_name = FilterExpression(bits[4], parser) var_name = bits[-1] if bits[5] != "as": date_field = FilterExpression(bits[5], parser) else: date_field = FilterExpression(None, parser) if bits[6] != "as": num = FilterExpression(bits[6], parser) else: num = FilterExpression(None, parser) return LatestObjectsNode(var_name, category, app_label, model_name, set_name, date_field, num)
def parse_tag(token, parser): """ Generic template tag parser. Returns a three-tuple: (tag_name, args, kwargs) tag_name is a string, the name of the tag. args is a list of FilterExpressions, from all the arguments that didn't look like kwargs, in the order they occurred, including any that were mingled amongst kwargs. kwargs is a dictionary mapping kwarg names to FilterExpressions, for all the arguments that looked like kwargs, including any that were mingled amongst args. (At rendering time, a FilterExpression f can be evaluated by calling f.resolve(context).) """ # Split the tag content into words, respecting quoted strings. bits = token.split_contents() # Pull out the tag name. tag_name = bits.pop(0) # Parse the rest of the args, and build FilterExpressions from them so that # we can evaluate them later. args = [] kwargs = {} for bit in bits: bit = bit.replace('"', '"') # Is this a kwarg or an arg? match = kwarg_re.match(bit) kwarg_format = match and match.group(1) if kwarg_format: key, value = match.groups() kwargs[key] = FilterExpression(value, parser) else: args.append(FilterExpression(bit, parser)) return tag_name, args, kwargs
def render(self, context): for i, arg in enumerate(self.macro.args): try: fe = self.fe_args[i] context[arg] = fe.resolve(context) except IndexError: context[arg] = "" for name, default in iter(self.macro.kwargs.items()): if name in self.fe_kwargs: context[name] = self.fe_kwargs[name].resolve(context) else: context[name] = FilterExpression( default, self.macro.parser).resolve(context) return self.macro.nodelist.render(context)
def test_repr(self): token = Token(TokenType.BLOCK, 'some text') self.assertEqual(repr(token), '<Block token: "some text...">') parser = Parser([token], builtins=[filter_library]) self.assertEqual( repr(parser), '<Parser tokens=[<Block token: "some text...">]>', ) filter_expression = FilterExpression('news|upper', parser) self.assertEqual(repr(filter_expression), "<FilterExpression 'news|upper'>") lexer = Lexer('{% for i in 1 %}{{ a }}\n{% endfor %}') self.assertEqual( repr(lexer), '<Lexer template_string="{% for i in 1 %}{{ a...", verbatim=False>', )
def token_value(bits, parser): """Parse ``bits`` string and return string or variable (FilterExpression). >>> from django.template.base import Parser >>> parser = Parser('') >>> from django_genericfilters.templatetags.updateurl import token_value >>> token_value('"A"', parser) 'A' >>> token_value('a', parser).var <Variable: 'a'> """ if bits[0] in ('"', "'"): # Parse a string. if not (bits[0] == bits[-1] or len(bits) < 2): raise template.TemplateSyntaxError("Malformed argument %r" % bits) return bits[1:-1] else: # Parse a variable. return FilterExpression(bits, parser)
def do_usemacro(parser, token): try: args = token.split_contents() macro_name, values = args[1], args[2:] except IndexError: m = ("'%s' tag requires at least one argument (macro name)" % token.contents.split()[0]) raise template.TemplateSyntaxError(m) try: macro = parser._macros[macro_name] except (AttributeError, KeyError): m = "Macro '%s' is not defined" % macro_name raise template.TemplateSyntaxError(m) fe_args, fe_kwargs = parse_token_args( values, lambda value: FilterExpression(value, parser)) macro.parser = parser return UseMacroNode(macro, fe_args, fe_kwargs)
def get_category_drilldown(parser, token): """ Retrieves the specified category, its ancestors and its immediate children as an iterable. Syntax:: {% get_category_drilldown "category name" [using "app.Model"] as varname %} Example:: {% get_category_drilldown "/Grandparent/Parent" [using "app.Model"] as family %} or :: {% get_category_drilldown category_obj as family %} Sets family to:: Grandparent, Parent, Child 1, Child 2, Child n """ bits = token.split_contents() error_str = '%(tagname)s tag should be in the format {%% %(tagname)s ' \ '"category name" [using "app.Model"] as varname %%} or ' \ '{%% %(tagname)s category_obj as varname %%}.' if len(bits) == 4: if bits[2] != 'as': raise template.TemplateSyntaxError(error_str % {'tagname': bits[0]}) if bits[2] == 'as': varname = bits[3].strip("'\"") model = "categories.category" if len(bits) == 6: if bits[2] not in ('using', 'as') or bits[4] not in ('using', 'as'): raise template.TemplateSyntaxError(error_str % {'tagname': bits[0]}) if bits[2] == 'as': varname = bits[3].strip("'\"") model = bits[5].strip("'\"") if bits[2] == 'using': varname = bits[5].strip("'\"") model = bits[3].strip("'\"") category = FilterExpression(bits[1], parser) return CategoryDrillDownNode(category, varname, model)
def recursetree(parser, token): """ Iterates over the nodes in the tree, and renders the contained block for each node. This tag will recursively render children into the template variable {{ children }}. Only one database query is required (children are cached for the whole tree) Example: Basic usage example:: <ul> {% recursetree nodes %} <li> {{ node.name }} {% if not node.is_leaf_node %} <ul> {{ children }} </ul> {% endif %} </li> {% endrecursetree %} </ul> Args: parser: The Django template parser. token: The tag contents Returns: The recursive tree node. Raises: TemplateSyntaxError: If a queryset isn't provided. """ bits = token.contents.split() if len(bits) != 2: raise template.TemplateSyntaxError("%s tag requires a queryset" % bits[0]) queryset_var = FilterExpression(bits[1], parser) template_nodes = parser.parse(("endrecursetree", )) parser.delete_first_token() return RecurseTreeNode(template_nodes, queryset_var)
def test_filter_parsing(self): c = {"article": {"section": "News"}} p = Parser("") def fe_test(s, val): self.assertEqual(FilterExpression(s, p).resolve(c), val) fe_test("article.section", "News") fe_test("article.section|upper", "NEWS") fe_test('"News"', "News") fe_test("'News'", "News") fe_test(r'"Some \"Good\" News"', 'Some "Good" News') fe_test(r'"Some \"Good\" News"', 'Some "Good" News') fe_test(r"'Some \'Bad\' News'", "Some 'Bad' News") fe = FilterExpression(r'"Some \"Good\" News"', p) self.assertEqual(fe.filters, []) self.assertEqual(fe.var, 'Some "Good" News') # Filtered variables should reject access of attributes beginning with # underscores. self.assertRaises(TemplateSyntaxError, FilterExpression, "article._hidden|upper", p)
def do_tumbleposts(parser, token): ''' Iterates given block over tumblelog posts. {% tumbleposts as post tumblelog_id=1 **kwargs %} {{ post.title }} {% endtumbleposts %} ''' bits = token.contents.split() bits.reverse() bits = [bit.strip(',') for bit in bits] tag_name = bits.pop() as_name = bits.pop() var_name = bits.pop() order_by = None kwargs_list = [bit for bit in bits if '=' in bit] kwargs = {} #coerce kwargs to expected types for kwarg in kwargs_list: key, val = kwarg.split('=') try: val = int(val) except ValueError: pass if val in ['True', 'False']: val = bool(val) kwargs[key] = val # preflight kwargs before running any queries if not var_name or '=' in var_name or not kwargs.get('tumblelog_id'): raise template.TemplateSyntaxError("'%s' tag requires at a minimum a post variable name and tumblelog_id." % tag_name) order_by = None limit = None if 'order_by' in kwargs: order_by = kwargs['order_by'] del kwargs['order_by'] if 'limit' in kwargs: limit = kwargs['limit'] del kwargs['limit'] object_list = models.TumblelogPost.objects.filter(**kwargs) if order_by: object_list = object_list.order_by(order_by) if limit: object_list = object_list[:limit] # apply custom data for obj in object_list: # set permalink permalink = reverse('tumble_post', urlconf='tumbledore.urls', args=[obj.tumblelog.mount_on, obj.slug]) obj.__dict__.update(permalink=permalink) # override any custom variables if isinstance(obj.custom_data, dict): obj.__dict__.update(**obj.custom_data) # set up FilterExpression from object_list sequence = FilterExpression('', parser) sequence.filters = [] sequence.var = object_list # gather node lists out of the template parser nodelist_loop = parser.parse(('empty', 'endtumbleposts')) token = parser.next_token() if token.contents == 'empty': nodelist_empty = parser.parse(('endtumbleposts',)) parser.delete_first_token() else: nodelist_empty = None return ForNode([var_name], sequence, False, nodelist_loop, nodelist_empty)
def __init__(self, parser, value, asvar): self.value = FilterExpression(value, parser) self.asvar = asvar
def __init__(self, parser, condition, true_value, false_value, asvar=None): self.condition = FilterExpression(condition, parser) self.true_value = FilterExpression(true_value, parser) self.false_value = FilterExpression(false_value, parser) self.asvar = asvar