Merge branch 'master' into navigation
[philo.git] / templatetags / nodes.py
1 from django import template
2 from django.conf import settings
3 from django.contrib.sites.models import Site
4 from django.core.urlresolvers import reverse, NoReverseMatch
5 from django.template.defaulttags import kwarg_re
6 from django.utils.encoding import smart_str
7 from philo.exceptions import ViewCanNotProvideSubpath
8
9
10 register = template.Library()
11
12
13 class NodeURLNode(template.Node):
14         def __init__(self, node, as_var, with_obj=None, view_name=None, args=None, kwargs=None):
15                 self.as_var = as_var
16                 self.view_name = view_name
17                 
18                 # Because the following variables have already been compiled as filters if they exist, they don't need to be re-scanned as template variables.
19                 self.node = node
20                 self.with_obj = with_obj
21                 self.args = args
22                 self.kwargs = kwargs
23         
24         def render(self, context):
25                 if self.node:
26                         node = self.node.resolve(context)
27                 else:
28                         node = context.get('node', None)
29                 
30                 if not node:
31                         return settings.TEMPLATE_STRING_IF_INVALID
32                 
33                 if self.with_obj is None and self.view_name is None:
34                         url = node.get_absolute_url()
35                 else:
36                         if not node.view.accepts_subpath:
37                                 return settings.TEMPLATE_STRING_IF_INVALID
38                         
39                         if self.with_obj is not None:
40                                 try:
41                                         view_name, args, kwargs = node.view.get_reverse_params(self.with_obj.resolve(context))
42                                 except ViewCanNotProvideSubpath:
43                                         return settings.TEMPLATE_STRING_IF_INVALID
44                         else: # self.view_name is not None
45                                 view_name = self.view_name
46                                 args = [arg.resolve(context) for arg in self.args]
47                                 kwargs = dict([(smart_str(k, 'ascii'), v.resolve(context)) for k, v in self.kwargs.items()])
48                         
49                         url = ''
50                         try:
51                                 subpath = reverse(view_name, urlconf=node.view, args=args, kwargs=kwargs)
52                         except NoReverseMatch:
53                                 if self.as_var is None:
54                                         if settings.TEMPLATE_DEBUG:
55                                                 raise
56                                         return settings.TEMPLATE_STRING_IF_INVALID
57                         else:
58                                 if subpath[0] == '/':
59                                         subpath = subpath[1:]
60                                 
61                                 url = node.get_absolute_url() + subpath
62                 
63                 if self.as_var:
64                         context[self.as_var] = url
65                         return ''
66                 else:
67                         return url
68
69
70 @register.tag(name='node_url')
71 def do_node_url(parser, token):
72         """
73         {% node_url [for <node>] [as <var>] %}
74         {% node_url with <obj> [for <node>] [as <var>] %}
75         {% node_url <view_name> [<arg1> [<arg2> ...] ] [for <node>] [as <var>] %}
76         {% node_url <view_name> [<key1>=<value1> [<key2>=<value2> ...] ] [for <node>] [as <var>]%}
77         """
78         params = token.split_contents()
79         tag = params[0]
80         as_var = None
81         with_obj = None
82         node = None
83         params = params[1:]
84         
85         if len(params) >= 2 and params[-2] == 'as':
86                 as_var = params[-1]
87                 params = params[:-2]
88         
89         if len(params) >= 2 and params[-2] == 'for':
90                 node = parser.compile_filter(params[-1])
91                 params = params[:-2]
92         
93         if len(params) >= 2 and params[-2] == 'with':
94                 with_obj = parser.compile_filter(params[-1])
95                 params = params[:-2]
96         
97         if with_obj is not None:
98                 if params:
99                         raise template.TemplateSyntaxError('`%s` template tag accepts no arguments or keyword arguments if with <obj> is specified.' % tag)
100                 return NodeURLNode(with_obj=with_obj, node=node, as_var=as_var)
101         
102         if params:
103                 args = []
104                 kwargs = {}
105                 view_name = params.pop(0)
106                 for param in params:
107                         match = kwarg_re.match(param)
108                         if not match:
109                                 raise TemplateSyntaxError("Malformed arguments to `%s` tag" % tag)
110                         name, value = match.groups()
111                         if name:
112                                 kwargs[name] = parser.compile_filter(value)
113                         else:
114                                 args.append(parser.compile_filter(value))
115                 return NodeURLNode(view_name=view_name, args=args, kwargs=kwargs, node=node, as_var=as_var)
116         
117         return NodeURLNode(node=node, as_var=as_var)
118
119
120 class NavigationNode(template.Node):
121         def __init__(self, node=None, as_var=None):
122                 self.as_var = as_var
123                 self.node = node
124         
125         def render(self, context):
126                 if 'request' not in context:
127                         return settings.TEMPLATE_STRING_IF_INVALID
128                 
129                 if self.node:
130                         node = self.node.resolve(context)
131                 else:
132                         node = context.get('node', None)
133                 
134                 if not node:
135                         return settings.TEMPLATE_STRING_IF_INVALID
136                 
137                 try:
138                         nav_root = node.attributes['navigation_root']
139                 except KeyError:
140                         if settings.TEMPLATE_DEBUG:
141                                 raise
142                         return settings.TEMPLATE_STRING_IF_INVALID
143                 
144                 # Should I get its override and check for a max depth override there?
145                 navigation = nav_root.get_navigation()
146                 
147                 if self.as_var:
148                         context[self.as_var] = navigation
149                         return ''
150                 
151                 return self.compile(navigation, context['request'].path, nav_root.get_absolute_url(), nav_root.get_level(), nav_root.get_level() + 3)
152         
153         def compile(self, navigation, active_path, root_url, current_depth, max_depth):
154                 compiled = ""
155                 for item in navigation:
156                         if item['url'] in active_path and (item['url'] != root_url or root_url == active_path):
157                                 compiled += "<li class='active'>"
158                         else:
159                                 compiled += "<li>"
160                         
161                         if item['url']:
162                                 compiled += "<a href='%s'>" % item['url']
163                         
164                         compiled += item['title']
165                         
166                         if item['url']:
167                                 compiled += "</a>"
168                         
169                         if 'children' in item and current_depth < max_depth:
170                                 compiled += "<ul>%s</ul>" % self.compile(item['children'], active_path, root_url, current_depth + 1, max_depth)
171                         
172                         compiled += "</li>"
173                 return compiled
174
175
176 @register.tag(name='navigation')
177 def do_navigation(parser, token):
178         """
179         {% navigation [for <node>] [as <var>] %}
180         """
181         bits = token.split_contents()
182         tag = bits[0]
183         bits = bits[1:]
184         node = None
185         as_var = None
186         
187         if len(bits) >= 2 and bits[-2] == 'as':
188                 as_var = bits[-1]
189                 bits = bits[:-2]
190         
191         if len(bits) >= 2 and bits[-2] == 'for':
192                 node = parser.compile_filter(bits[-1])
193                 bits = bits[-2]
194         
195         if bits:
196                 raise template.TemplateSyntaxError('`%s` template tag expects the syntax {%% %s [for <node>] [as <var>] %}' % (tag, tag))
197         return NavigationNode(node, as_var)