Login

a template tag to invoke a method on an object with a variable

Author:
Scanner
Posted:
May 10, 2007
Language:
Python
Version:
.96
Tags:
tags
Score:
1 (after 1 ratings)

The django templating language is quite nice, and specifically limited to guide people in to making their business logic in the view, not in the template itself.

Sometimes it can be difficult to do certain things in the template even though it seems like the most appropriate place to do this.

I have an application that is heavily influenced by the user that is logged in in several ways.

For example let us say I have a list of forums. Each forum has several discussions full of posts. This system of forums and discussions has permissions such that some users can not see some entities. Now, I can produce in the view the query set of what forums a user is allowed to see, and I want this list to display the latest post in each of those forums, but I have to restrict that to the posts that they can see.

Easy enough, I have a method on the Forum object that takes a user object and does the appropriate filter to return the latest post that the user can see. The trick is the template is passed the query set of forums, and then iterates through them using the template language. How can it invoke the method on the forum for the latest post that needs the 'user' variable from the template context? The template language lets me say 'forum.latest_post' but I need to pass in 'user'. 'forum.latest_post(user)' does not work because the template language does not allow it.

This tag lets me specify an object, the method on that object to call, and the variable to pass to that method.

It is not the prettiest thing but with this add on you can do:

{% method_arg forum latest_post user as post %}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
##############################################################################
#
@register.tag(name = 'method_arg')
def method_arg(parser, token):
    """
    This tag allows us to call the given method with the given
    argument and set the resultant value to a variable in our context.
    ie: {% method_arg foo bar user as post %} would result in the
    variable 'post' getting the result of evaluating foo.bar(user).
    If the argument is surrounded by quotes, then it is considered a
    string and not a variable to be resolved.
    """
    try:
        tag_name, var, method_name, arg, ign, dst = token.split_contents()
    except ValueError:
        raise template.TemplateSyntaxError, "%r requires arguments in the " \
              "format of <variable> <methodname> <argument> as <variable>."
    if ign.lower() != "as":
        raise template.TemplateSyntaxError, "%r requires arguments in the " \
              "format of <variable> <methodname> <argument> as <variable>."
    return MethodArgNode(var, method_name, arg, dst)

class MethodArgNode(template.Node):
    """
    The template node sub-class that does the work of looking up the
    method you wish to invoke in your template, resolving the variable
    to pass to it and setting the resultant value in the template's
    context.
    """
    def __init__(self, var, method_name, arg, dst):
        self.var = var
        self.method_name = method_name
        self.arg = arg
        self.dest = dst

    def render(self, context):
        try:
            obj = resolve_variable(self.var, context)
            if hasattr(obj, self.method_name) and \
               isinstance(getattr(obj, self.method_name), MethodType):
                if self.arg[0] == self.arg[-1] and self.arg[0] in ('"', "'"):
                    context[self.dest] = \
                                getattr(obj, self.method_name)(self.arg[1:-1])
                else:
                    context[self.dest] = getattr(obj, self.method_name)\
                                         (resolve_variable(self.arg, context))
        except:
            # render() should never raise any exception. If something goes
            # wrong we need to log it somewhere else, not chuck it up the
            # call stack.
            #
            raise
            pass
        return ""

More like this

  1. Per-Instance On-Model M2M Caching by bryanhelmig 4 years, 10 months ago
  2. Complex Formsets, Redux by smagala 5 years, 2 months ago
  3. fancy_if by Scanner 8 years, 1 month ago
  4. Functional Filters by waterson 7 years, 8 months ago
  5. Subdirectory and subcontext include template tag with examples by t_rybik 5 years, 2 months ago

Comments

Please login first before commenting.