| 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)
|
|---|