/django/contrib/comments/templatetags/comments.py

https://code.google.com/p/mango-py/ · Python · 333 lines · 265 code · 33 blank · 35 comment · 32 complexity · 42faa5e0f2a4d1dc5f2e6ff762395111 MD5 · raw file

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