1 | from django import template
|
---|
2 | from django.template.loader import render_to_string
|
---|
3 | from django.conf import settings
|
---|
4 | from django.contrib.contenttypes.models import ContentType
|
---|
5 | from django.contrib import comments
|
---|
6 | from django.utils.encoding import smart_unicode
|
---|
7 |
|
---|
8 | register = template.Library()
|
---|
9 |
|
---|
10 | class BaseCommentNode(template.Node):
|
---|
11 | """
|
---|
12 | Base helper class (abstract) for handling the get_comment_* template tags.
|
---|
13 | Looks a bit strange, but the subclasses below should make this a bit more
|
---|
14 | obvious.
|
---|
15 | """
|
---|
16 |
|
---|
17 | #@classmethod
|
---|
18 | def handle_token(cls, parser, token):
|
---|
19 | """Class method to parse get_comment_list/count/form and return a Node."""
|
---|
20 | tokens = token.contents.split()
|
---|
21 | if tokens[1] != 'for':
|
---|
22 | raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0])
|
---|
23 |
|
---|
24 | # {% get_whatever for obj as varname %}
|
---|
25 | if len(tokens) == 5:
|
---|
26 | if tokens[3] != 'as':
|
---|
27 | raise template.TemplateSyntaxError("Third argument in %r must be 'as'" % tokens[0])
|
---|
28 | return cls(
|
---|
29 | object_expr = parser.compile_filter(tokens[2]),
|
---|
30 | as_varname = tokens[4],
|
---|
31 | )
|
---|
32 |
|
---|
33 | # {% get_whatever for app.model pk as varname %}
|
---|
34 | elif len(tokens) == 6:
|
---|
35 | if tokens[4] != 'as':
|
---|
36 | raise template.TemplateSyntaxError("Fourth argument in %r must be 'as'" % tokens[0])
|
---|
37 | return cls(
|
---|
38 | ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]),
|
---|
39 | object_pk_expr = parser.compile_filter(tokens[3]),
|
---|
40 | as_varname = tokens[5]
|
---|
41 | )
|
---|
42 |
|
---|
43 | else:
|
---|
44 | raise template.TemplateSyntaxError("%r tag requires 4 or 5 arguments" % tokens[0])
|
---|
45 |
|
---|
46 | handle_token = classmethod(handle_token)
|
---|
47 |
|
---|
48 | #@staticmethod
|
---|
49 | def lookup_content_type(token, tagname):
|
---|
50 | try:
|
---|
51 | app, model = token.split('.')
|
---|
52 | return ContentType.objects.get(app_label=app, model=model)
|
---|
53 | except ValueError:
|
---|
54 | raise template.TemplateSyntaxError("Third argument in %r must be in the format 'app.model'" % tagname)
|
---|
55 | except ContentType.DoesNotExist:
|
---|
56 | raise template.TemplateSyntaxError("%r tag has non-existant content-type: '%s.%s'" % (tagname, app, model))
|
---|
57 | lookup_content_type = staticmethod(lookup_content_type)
|
---|
58 |
|
---|
59 | def __init__(self, ctype=None, object_pk_expr=None, object_expr=None, as_varname=None, comment=None):
|
---|
60 | if ctype is None and object_expr is None:
|
---|
61 | raise template.TemplateSyntaxError("Comment nodes must be given either a literal object or a ctype and object pk.")
|
---|
62 | self.comment_model = comments.get_model()
|
---|
63 | self.as_varname = as_varname
|
---|
64 | self.ctype = ctype
|
---|
65 | self.object_pk_expr = object_pk_expr
|
---|
66 | self.object_expr = object_expr
|
---|
67 | self.comment = comment
|
---|
68 |
|
---|
69 | def render(self, context):
|
---|
70 | qs = self.get_query_set(context)
|
---|
71 | context[self.as_varname] = self.get_context_value_from_queryset(context, qs)
|
---|
72 | return ''
|
---|
73 |
|
---|
74 | def get_query_set(self, context):
|
---|
75 | ctype, object_pk = self.get_target_ctype_pk(context)
|
---|
76 | if not object_pk:
|
---|
77 | return self.comment_model.objects.none()
|
---|
78 |
|
---|
79 | qs = self.comment_model.objects.filter(
|
---|
80 | content_type = ctype,
|
---|
81 | object_pk = smart_unicode(object_pk),
|
---|
82 | site__pk = settings.SITE_ID,
|
---|
83 | is_public = True,
|
---|
84 | )
|
---|
85 | if getattr(settings, 'COMMENTS_HIDE_REMOVED', True):
|
---|
86 | qs = qs.filter(is_removed=False)
|
---|
87 |
|
---|
88 | return qs
|
---|
89 |
|
---|
90 | def get_target_ctype_pk(self, context):
|
---|
91 | if self.object_expr:
|
---|
92 | try:
|
---|
93 | obj = self.object_expr.resolve(context)
|
---|
94 | except template.VariableDoesNotExist:
|
---|
95 | return None, None
|
---|
96 | return ContentType.objects.get_for_model(obj), obj.pk
|
---|
97 | else:
|
---|
98 | return self.ctype, self.object_pk_expr.resolve(context, ignore_failures=True)
|
---|
99 |
|
---|
100 | def get_context_value_from_queryset(self, context, qs):
|
---|
101 | """Subclasses should override this."""
|
---|
102 | raise NotImplementedError
|
---|
103 |
|
---|
104 | class CommentListNode(BaseCommentNode):
|
---|
105 | """Insert a list of comments into the context."""
|
---|
106 | def get_context_value_from_queryset(self, context, qs):
|
---|
107 | return list(qs)
|
---|
108 |
|
---|
109 | class RenderCommentListNode(CommentListNode):
|
---|
110 | """ Render the comment list directly """
|
---|
111 |
|
---|
112 | #@classmethod
|
---|
113 | def handle_token(cls, parser, token):
|
---|
114 | """Class method to parse render_comment_list and return a Node."""
|
---|
115 | tokens = token.contents.split()
|
---|
116 | if tokens[1] != 'for':
|
---|
117 | raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0])
|
---|
118 |
|
---|
119 | # {% render_comment_list for obj %}
|
---|
120 | if len(tokens) == 3:
|
---|
121 | return cls(object_expr=parser.compile_filter(tokens[2]))
|
---|
122 |
|
---|
123 | # {% render_comment_list for app.models pk %}
|
---|
124 | elif len(tokens) == 4:
|
---|
125 | return cls(
|
---|
126 | ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]),
|
---|
127 | object_pk_expr = parser.compile_filter(tokens[3])
|
---|
128 | )
|
---|
129 | handle_token = classmethod(handle_token)
|
---|
130 |
|
---|
131 | def render(self, context):
|
---|
132 | ctype, object_pk = self.get_target_ctype_pk(context)
|
---|
133 | if object_pk:
|
---|
134 | template_search_list = [
|
---|
135 | "comments/%s/%s/list.html" % (ctype.app_label, ctype.model),
|
---|
136 | "comments/%s/list.html" % ctype.app_label,
|
---|
137 | "comments/list.html"
|
---|
138 | ]
|
---|
139 | qs = self.get_query_set(context)
|
---|
140 | context.push()
|
---|
141 | liststr = render_to_string(template_search_list, {"comment_list" : self.get_context_value_from_queryset(context, qs)}, context)
|
---|
142 | context.pop()
|
---|
143 | return liststr
|
---|
144 | else:
|
---|
145 | return ''
|
---|
146 |
|
---|
147 | class CommentCountNode(BaseCommentNode):
|
---|
148 | """Insert a count of comments into the context."""
|
---|
149 | def get_context_value_from_queryset(self, context, qs):
|
---|
150 | return qs.count()
|
---|
151 |
|
---|
152 | class CommentFormNode(BaseCommentNode):
|
---|
153 | """Insert a form for the comment model into the context."""
|
---|
154 |
|
---|
155 | def get_form(self, context):
|
---|
156 | ctype, object_pk = self.get_target_ctype_pk(context)
|
---|
157 | if object_pk:
|
---|
158 | return comments.get_form()(ctype.get_object_for_this_type(pk=object_pk))
|
---|
159 | else:
|
---|
160 | return None
|
---|
161 |
|
---|
162 | def render(self, context):
|
---|
163 | context[self.as_varname] = self.get_form(context)
|
---|
164 | return ''
|
---|
165 |
|
---|
166 | class RenderCommentFormNode(CommentFormNode):
|
---|
167 | """Render the comment form directly"""
|
---|
168 |
|
---|
169 | #@classmethod
|
---|
170 | def handle_token(cls, parser, token):
|
---|
171 | """Class method to parse render_comment_form and return a Node."""
|
---|
172 | tokens = token.contents.split()
|
---|
173 | if tokens[1] != 'for':
|
---|
174 | raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0])
|
---|
175 |
|
---|
176 | # {% render_comment_form for obj %}
|
---|
177 | if len(tokens) == 3:
|
---|
178 | return cls(object_expr=parser.compile_filter(tokens[2]))
|
---|
179 |
|
---|
180 | # {% render_comment_form for app.models pk %}
|
---|
181 | elif len(tokens) == 4:
|
---|
182 | return cls(
|
---|
183 | ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]),
|
---|
184 | object_pk_expr = parser.compile_filter(tokens[3])
|
---|
185 | )
|
---|
186 | handle_token = classmethod(handle_token)
|
---|
187 |
|
---|
188 | def render(self, context):
|
---|
189 | ctype, object_pk = self.get_target_ctype_pk(context)
|
---|
190 | if object_pk:
|
---|
191 | template_search_list = [
|
---|
192 | "comments/%s/%s/form.html" % (ctype.app_label, ctype.model),
|
---|
193 | "comments/%s/form.html" % ctype.app_label,
|
---|
194 | "comments/form.html"
|
---|
195 | ]
|
---|
196 | context.push()
|
---|
197 | formstr = render_to_string(template_search_list, {"form" : self.get_form(context)}, context)
|
---|
198 | context.pop()
|
---|
199 | return formstr
|
---|
200 | else:
|
---|
201 | return ''
|
---|
202 |
|
---|
203 | # We could just register each classmethod directly, but then we'd lose out on
|
---|
204 | # the automagic docstrings-into-admin-docs tricks. So each node gets a cute
|
---|
205 | # wrapper function that just exists to hold the docstring.
|
---|
206 |
|
---|
207 | #@register.tag
|
---|
208 | def get_comment_count(parser, token):
|
---|
209 | """
|
---|
210 | Gets the comment count for the given params and populates the template
|
---|
211 | context with a variable containing that value, whose name is defined by the
|
---|
212 | 'as' clause.
|
---|
213 |
|
---|
214 | Syntax::
|
---|
215 |
|
---|
216 | {% get_comment_count for [object] as [varname] %}
|
---|
217 | {% get_comment_count for [app].[model] [object_id] as [varname] %}
|
---|
218 |
|
---|
219 | Example usage::
|
---|
220 |
|
---|
221 | {% get_comment_count for event as comment_count %}
|
---|
222 | {% get_comment_count for calendar.event event.id as comment_count %}
|
---|
223 | {% get_comment_count for calendar.event 17 as comment_count %}
|
---|
224 |
|
---|
225 | """
|
---|
226 | return CommentCountNode.handle_token(parser, token)
|
---|
227 |
|
---|
228 | #@register.tag
|
---|
229 | def get_comment_list(parser, token):
|
---|
230 | """
|
---|
231 | Gets the list of comments for the given params and populates the template
|
---|
232 | context with a variable containing that value, whose name is defined by the
|
---|
233 | 'as' clause.
|
---|
234 |
|
---|
235 | Syntax::
|
---|
236 |
|
---|
237 | {% get_comment_list for [object] as [varname] %}
|
---|
238 | {% get_comment_list for [app].[model] [object_id] as [varname] %}
|
---|
239 |
|
---|
240 | Example usage::
|
---|
241 |
|
---|
242 | {% get_comment_list for event as comment_list %}
|
---|
243 | {% for comment in comment_list %}
|
---|
244 | ...
|
---|
245 | {% endfor %}
|
---|
246 |
|
---|
247 | """
|
---|
248 | return CommentListNode.handle_token(parser, token)
|
---|
249 |
|
---|
250 | #@register.tag
|
---|
251 | def render_comment_list(parser, token):
|
---|
252 | """
|
---|
253 | Render the comment list (as returned by ``{% render_comment_list %}``) through
|
---|
254 | the ``comments/list.html`` template
|
---|
255 |
|
---|
256 | Syntax::
|
---|
257 | {% render_comment_list for [object] %}
|
---|
258 | {% render_comment_list for [app].[model] [object_id] %}
|
---|
259 | """
|
---|
260 | return RenderCommentListNode.handle_token(parser, token)
|
---|
261 |
|
---|
262 | #@register.tag
|
---|
263 | def get_comment_form(parser, token):
|
---|
264 | """
|
---|
265 | Get a (new) form object to post a new comment.
|
---|
266 |
|
---|
267 | Syntax::
|
---|
268 |
|
---|
269 | {% get_comment_form for [object] as [varname] %}
|
---|
270 | {% get_comment_form for [app].[model] [object_id] as [varname] %}
|
---|
271 | """
|
---|
272 | return CommentFormNode.handle_token(parser, token)
|
---|
273 |
|
---|
274 | #@register.tag
|
---|
275 | def render_comment_form(parser, token):
|
---|
276 | """
|
---|
277 | Render the comment form (as returned by ``{% render_comment_form %}``) through
|
---|
278 | the ``comments/form.html`` template.
|
---|
279 |
|
---|
280 | Syntax::
|
---|
281 |
|
---|
282 | {% render_comment_form for [object] %}
|
---|
283 | {% render_comment_form for [app].[model] [object_id] %}
|
---|
284 | """
|
---|
285 | return RenderCommentFormNode.handle_token(parser, token)
|
---|
286 |
|
---|
287 | #@register.simple_tag
|
---|
288 | def comment_form_target():
|
---|
289 | """
|
---|
290 | Get the target URL for the comment form.
|
---|
291 |
|
---|
292 | Example::
|
---|
293 |
|
---|
294 | <form action="{% comment_form_target %}" method="POST">
|
---|
295 | """
|
---|
296 | return comments.get_form_target()
|
---|
297 |
|
---|
298 | register.tag(get_comment_count)
|
---|
299 | register.tag(get_comment_list)
|
---|
300 | register.tag(render_comment_list)
|
---|
301 | register.tag(get_comment_form)
|
---|
302 | register.tag(render_comment_form)
|
---|
303 | register.simple_tag(comment_form_target)
|
---|