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
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 3 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
Please login first before commenting.