Ticket #10285: comments.py

File comments.py, 10.6 KB (added by Kyle Fuller, 15 years ago)

django/contrib/comments/templatestags/comments.py

Line 
1from django import template
2from django.template.loader import render_to_string
3from django.conf import settings
4from django.contrib.contenttypes.models import ContentType
5from django.contrib import comments
6from django.utils.encoding import smart_unicode
7
8register = template.Library()
9
10class 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
104class 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
109class 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
147class 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
152class 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
166class 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
208def 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
229def 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
251def 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
263def 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
275def 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
288def 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
298register.tag(get_comment_count)
299register.tag(get_comment_list)
300register.tag(render_comment_list)
301register.tag(get_comment_form)
302register.tag(render_comment_form)
303register.simple_tag(comment_form_target)
Back to Top