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
8 register
= template
.Library()
10 class BaseCommentNode(template
.Node
):
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
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])
24 # {% get_whatever for obj as varname %}
27 raise template
.TemplateSyntaxError("Third argument in %r must be 'as'" % tokens
[0])
29 object_expr
= parser
.compile_filter(tokens
[2]),
30 as_varname
= tokens
[4],
33 # {% get_whatever for app.model pk as varname %}
34 elif len(tokens
) == 6:
36 raise template
.TemplateSyntaxError("Fourth argument in %r must be 'as'" % tokens
[0])
38 ctype
= BaseCommentNode
.lookup_content_type(tokens
[2], tokens
[0]),
39 object_pk_expr
= parser
.compile_filter(tokens
[3]),
40 as_varname
= tokens
[5]
44 raise template
.TemplateSyntaxError("%r tag requires 4 or 5 arguments" % tokens
[0])
46 handle_token
= classmethod(handle_token
)
49 def lookup_content_type(token
, tagname
):
51 app
, model
= token
.split('.')
52 return ContentType
.objects
.get(app_label
=app
, model
=model
)
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
)
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
65 self
.object_pk_expr
= object_pk_expr
66 self
.object_expr
= object_expr
67 self
.comment
= comment
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
)
74 def get_query_set(self
, context
):
75 ctype
, object_pk
= self
.get_target_ctype_pk(context
)
77 return self
.comment_model
.objects
.none()
79 qs
= self
.comment_model
.objects
.filter(
81 object_pk
= smart_unicode(object_pk
),
82 site__pk
= settings
.SITE_ID
,
85 # The is_public and is_removed fields are implementation details of the
86 # built-in comment model's spam filtering system, so they might not
87 # be present on a custom comment model subclass. If they exist, we
88 # should filter on them.
89 field_names
= [f
.name
for f
in self
.comment_model
._meta
.fields
]
90 if 'is_public' in field_names
:
91 qs
= qs
.filter(is_public
=True)
92 if getattr(settings
, 'COMMENTS_HIDE_REMOVED', True) and 'is_removed' in field_names
:
93 qs
= qs
.filter(is_removed
=False)
97 def get_target_ctype_pk(self
, context
):
100 obj
= self
.object_expr
.resolve(context
)
101 except template
.VariableDoesNotExist
:
103 return ContentType
.objects
.get_for_model(obj
), obj
.pk
105 return self
.ctype
, self
.object_pk_expr
.resolve(context
, ignore_failures
=True)
107 def get_context_value_from_queryset(self
, context
, qs
):
108 """Subclasses should override this."""
109 raise NotImplementedError
111 class CommentListNode(BaseCommentNode
):
112 """Insert a list of comments into the context."""
113 def get_context_value_from_queryset(self
, context
, qs
):
116 class CommentCountNode(BaseCommentNode
):
117 """Insert a count of comments into the context."""
118 def get_context_value_from_queryset(self
, context
, qs
):
121 class CommentFormNode(BaseCommentNode
):
122 """Insert a form for the comment model into the context."""
124 def get_form(self
, context
):
125 ctype
, object_pk
= self
.get_target_ctype_pk(context
)
127 return comments
.get_form()(ctype
.get_object_for_this_type(pk
=object_pk
))
131 def render(self
, context
):
132 context
[self
.as_varname
] = self
.get_form(context
)
135 class RenderCommentFormNode(CommentFormNode
):
136 """Render the comment form directly"""
139 def handle_token(cls
, parser
, token
):
140 """Class method to parse render_comment_form and return a Node."""
141 tokens
= token
.contents
.split()
142 if tokens
[1] != 'for':
143 raise template
.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens
[0])
145 # {% render_comment_form for obj %}
147 return cls(object_expr
=parser
.compile_filter(tokens
[2]))
149 # {% render_comment_form for app.models pk %}
150 elif len(tokens
) == 4:
152 ctype
= BaseCommentNode
.lookup_content_type(tokens
[2], tokens
[0]),
153 object_pk_expr
= parser
.compile_filter(tokens
[3])
155 handle_token
= classmethod(handle_token
)
157 def render(self
, context
):
158 ctype
, object_pk
= self
.get_target_ctype_pk(context
)
160 template_search_list
= [
161 "comments/%s/%s/form.html" % (ctype
.app_label
, ctype
.model
),
162 "comments/%s/form.html" % ctype
.app_label
,
166 formstr
= render_to_string(template_search_list
, {"form" : self
.get_form(context
)}, context
)
172 class RenderCommentListNode(CommentListNode
):
173 """Render the comment list directly"""
176 def handle_token(cls
, parser
, token
):
177 """Class method to parse render_comment_list and return a Node."""
178 tokens
= token
.contents
.split()
179 if tokens
[1] != 'for':
180 raise template
.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens
[0])
182 # {% render_comment_list for obj %}
184 return cls(object_expr
=parser
.compile_filter(tokens
[2]))
186 # {% render_comment_list for app.models pk %}
187 elif len(tokens
) == 4:
189 ctype
= BaseCommentNode
.lookup_content_type(tokens
[2], tokens
[0]),
190 object_pk_expr
= parser
.compile_filter(tokens
[3])
192 handle_token
= classmethod(handle_token
)
194 def render(self
, context
):
195 ctype
, object_pk
= self
.get_target_ctype_pk(context
)
197 template_search_list
= [
198 "comments/%s/%s/list.html" % (ctype
.app_label
, ctype
.model
),
199 "comments/%s/list.html" % ctype
.app_label
,
202 qs
= self
.get_query_set(context
)
204 liststr
= render_to_string(template_search_list
, {
205 "comment_list" : self
.get_context_value_from_queryset(context
, qs
)
212 # We could just register each classmethod directly, but then we'd lose out on
213 # the automagic docstrings-into-admin-docs tricks. So each node gets a cute
214 # wrapper function that just exists to hold the docstring.
217 def get_comment_count(parser
, token
):
219 Gets the comment count for the given params and populates the template
220 context with a variable containing that value, whose name is defined by the
225 {% get_comment_count for [object] as [varname] %}
226 {% get_comment_count for [app].[model] [object_id] as [varname] %}
230 {% get_comment_count for event as comment_count %}
231 {% get_comment_count for calendar.event event.id as comment_count %}
232 {% get_comment_count for calendar.event 17 as comment_count %}
235 return CommentCountNode
.handle_token(parser
, token
)
238 def get_comment_list(parser
, token
):
240 Gets the list of comments for the given params and populates the template
241 context with a variable containing that value, whose name is defined by the
246 {% get_comment_list for [object] as [varname] %}
247 {% get_comment_list for [app].[model] [object_id] as [varname] %}
251 {% get_comment_list for event as comment_list %}
252 {% for comment in comment_list %}
257 return CommentListNode
.handle_token(parser
, token
)
260 def render_comment_list(parser
, token
):
262 Render the comment list (as returned by ``{% get_comment_list %}``)
263 through the ``comments/list.html`` template
267 {% render_comment_list for [object] %}
268 {% render_comment_list for [app].[model] [object_id] %}
272 {% render_comment_list for event %}
275 return RenderCommentListNode
.handle_token(parser
, token
)
278 def get_comment_form(parser
, token
):
280 Get a (new) form object to post a new comment.
284 {% get_comment_form for [object] as [varname] %}
285 {% get_comment_form for [app].[model] [object_id] as [varname] %}
287 return CommentFormNode
.handle_token(parser
, token
)
290 def render_comment_form(parser
, token
):
292 Render the comment form (as returned by ``{% render_comment_form %}``) through
293 the ``comments/form.html`` template.
297 {% render_comment_form for [object] %}
298 {% render_comment_form for [app].[model] [object_id] %}
300 return RenderCommentFormNode
.handle_token(parser
, token
)
302 #@register.simple_tag
303 def comment_form_target():
305 Get the target URL for the comment form.
309 <form action="{% comment_form_target %}" method="post">
311 return comments
.get_form_target()
313 #@register.simple_tag
314 def get_comment_permalink(comment
, anchor_pattern
=None):
316 Get the permalink for a comment, optionally specifying the format of the
317 named anchor to be appended to the end of the URL.
320 {{ get_comment_permalink comment "#c%(id)s-by-%(user_name)s" }}
324 return comment
.get_absolute_url(anchor_pattern
)
325 return comment
.get_absolute_url()
327 register
.tag(get_comment_count
)
328 register
.tag(get_comment_list
)
329 register
.tag(get_comment_form
)
330 register
.tag(render_comment_form
)
331 register
.simple_tag(comment_form_target
)
332 register
.simple_tag(get_comment_permalink
)
333 register
.tag(render_comment_list
)