Eliminated (Generic)ForeignKey evaluations in shipherd's NavigationManager.update_cac...
[philo.git] / philo / templatetags / nodes.py
1 """
2 The node template tags are automatically included as builtins if :mod:`philo` is an installed app.
3
4 """
5
6 from django import template
7 from django.conf import settings
8 from django.contrib.sites.models import Site
9 from django.core.urlresolvers import reverse, NoReverseMatch
10 from django.template.defaulttags import kwarg_re
11 from django.utils.encoding import smart_str
12
13 from philo.exceptions import ViewCanNotProvideSubpath
14
15
16 register = template.Library()
17
18
19 class NodeURLNode(template.Node):
20         def __init__(self, node, as_var, with_obj=None, view_name=None, args=None, kwargs=None):
21                 self.as_var = as_var
22                 self.view_name = view_name
23                 
24                 # Because the following variables have already been compiled as filters if they exist, they don't need to be re-scanned as template variables.
25                 self.node = node
26                 self.with_obj = with_obj
27                 self.args = args
28                 self.kwargs = kwargs
29         
30         def render(self, context):
31                 if self.node:
32                         node = self.node.resolve(context)
33                 else:
34                         node = context.get('node', None)
35                 
36                 if not node:
37                         return settings.TEMPLATE_STRING_IF_INVALID
38                 
39                 if self.with_obj is None and self.view_name is None:
40                         url = node.get_absolute_url()
41                 else:
42                         if not node.accepts_subpath:
43                                 return settings.TEMPLATE_STRING_IF_INVALID
44                         
45                         if self.with_obj is not None:
46                                 try:
47                                         view_name, args, kwargs = node.view.get_reverse_params(self.with_obj.resolve(context))
48                                 except ViewCanNotProvideSubpath:
49                                         return settings.TEMPLATE_STRING_IF_INVALID
50                         else: # self.view_name is not None
51                                 view_name = self.view_name
52                                 args = [arg.resolve(context) for arg in self.args]
53                                 kwargs = dict([(smart_str(k, 'ascii'), v.resolve(context)) for k, v in self.kwargs.items()])
54                         
55                         url = ''
56                         try:
57                                 subpath = reverse(view_name, urlconf=node.view, args=args, kwargs=kwargs)
58                         except NoReverseMatch:
59                                 if self.as_var is None:
60                                         if settings.TEMPLATE_DEBUG:
61                                                 raise
62                                         return settings.TEMPLATE_STRING_IF_INVALID
63                         else:
64                                 url = node.construct_url(subpath)
65                 
66                 if self.as_var:
67                         context[self.as_var] = url
68                         return ''
69                 else:
70                         return url
71
72
73 @register.tag
74 def node_url(parser, token):
75         """
76         The :ttag:`node_url` tag allows access to :meth:`.View.reverse` from a template for a :class:`.Node`. By default, the :class:`.Node` that is used for the call is pulled from the context variable ``node``; however, this can be overridden with the ``[for <node>]`` option.
77         
78         Usage::
79         
80                 {% node_url [for <node>] [as <var>] %}
81                 {% node_url with <obj> [for <node>] [as <var>] %}
82                 {% node_url <view_name> [<arg1> [<arg2> ...] ] [for <node>] [as <var>] %}
83                 {% node_url <view_name> [<key1>=<value1> [<key2>=<value2> ...] ] [for <node>] [as <var>] %}
84         
85         """
86         params = token.split_contents()
87         tag = params[0]
88         as_var = None
89         with_obj = None
90         node = None
91         params = params[1:]
92         
93         if len(params) >= 2 and params[-2] == 'as':
94                 as_var = params[-1]
95                 params = params[:-2]
96         
97         if len(params) >= 2 and params[-2] == 'for':
98                 node = parser.compile_filter(params[-1])
99                 params = params[:-2]
100         
101         if len(params) >= 2 and params[-2] == 'with':
102                 with_obj = parser.compile_filter(params[-1])
103                 params = params[:-2]
104         
105         if with_obj is not None:
106                 if params:
107                         raise template.TemplateSyntaxError('`%s` template tag accepts no arguments or keyword arguments if with <obj> is specified.' % tag)
108                 return NodeURLNode(with_obj=with_obj, node=node, as_var=as_var)
109         
110         if params:
111                 args = []
112                 kwargs = {}
113                 view_name = params.pop(0)
114                 for param in params:
115                         match = kwarg_re.match(param)
116                         if not match:
117                                 raise TemplateSyntaxError("Malformed arguments to `%s` tag" % tag)
118                         name, value = match.groups()
119                         if name:
120                                 kwargs[name] = parser.compile_filter(value)
121                         else:
122                                 args.append(parser.compile_filter(value))
123                 return NodeURLNode(view_name=view_name, args=args, kwargs=kwargs, node=node, as_var=as_var)
124         
125         return NodeURLNode(node=node, as_var=as_var)