from django import template from django.template.loader import render_to_string from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.contrib import comments from django.utils.encoding import smart_unicode register = template.Library() class BaseCommentNode(template.Node): """ Base helper class (abstract) for handling the get_comment_* template tags. Looks a bit strange, but the subclasses below should make this a bit more obvious. """ #@classmethod def handle_token(cls, parser, token): """Class method to parse get_comment_list/count/form and return a Node.""" tokens = token.contents.split() if tokens[1] != 'for': raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0]) # {% get_whatever for obj as varname %} if len(tokens) == 5: if tokens[3] != 'as': raise template.TemplateSyntaxError("Third argument in %r must be 'as'" % tokens[0]) return cls( object_expr = parser.compile_filter(tokens[2]), as_varname = tokens[4], ) # {% get_whatever for app.model pk as varname %} elif len(tokens) == 6: if tokens[4] != 'as': raise template.TemplateSyntaxError("Fourth argument in %r must be 'as'" % tokens[0]) return cls( ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]), object_pk_expr = parser.compile_filter(tokens[3]), as_varname = tokens[5] ) else: raise template.TemplateSyntaxError("%r tag requires 4 or 5 arguments" % tokens[0]) handle_token = classmethod(handle_token) #@staticmethod def lookup_content_type(token, tagname): try: app, model = token.split('.') return ContentType.objects.get(app_label=app, model=model) except ValueError: raise template.TemplateSyntaxError("Third argument in %r must be in the format 'app.model'" % tagname) except ContentType.DoesNotExist: raise template.TemplateSyntaxError("%r tag has non-existant content-type: '%s.%s'" % (tagname, app, model)) lookup_content_type = staticmethod(lookup_content_type) def __init__(self, ctype=None, object_pk_expr=None, object_expr=None, as_varname=None, comment=None): if ctype is None and object_expr is None: raise template.TemplateSyntaxError("Comment nodes must be given either a literal object or a ctype and object pk.") self.comment_model = comments.get_model() self.as_varname = as_varname self.ctype = ctype self.object_pk_expr = object_pk_expr self.object_expr = object_expr self.comment = comment def render(self, context): qs = self.get_query_set(context) context[self.as_varname] = self.get_context_value_from_queryset(context, qs) return '' def get_query_set(self, context): ctype, object_pk = self.get_target_ctype_pk(context) if not object_pk: return self.comment_model.objects.none() qs = self.comment_model.objects.filter( content_type = ctype, object_pk = smart_unicode(object_pk), site__pk = settings.SITE_ID, ) # The is_public and is_removed fields are implementation details of the # built-in comment model's spam filtering system, so they might not # be present on a custom comment model subclass. If they exist, we # should filter on them. field_names = [f.name for f in self.comment_model._meta.fields] if 'is_public' in field_names: qs = qs.filter(is_public=True) if getattr(settings, 'COMMENTS_HIDE_REMOVED', True) and 'is_removed' in field_names: qs = qs.filter(is_removed=False) return qs def get_target_ctype_pk(self, context): if self.object_expr: try: obj = self.object_expr.resolve(context) except template.VariableDoesNotExist: return None, None return ContentType.objects.get_for_model(obj), obj.pk else: return self.ctype, self.object_pk_expr.resolve(context, ignore_failures=True) def get_context_value_from_queryset(self, context, qs): """Subclasses should override this.""" raise NotImplementedError class CommentListNode(BaseCommentNode): """Insert a list of comments into the context.""" def get_context_value_from_queryset(self, context, qs): return list(qs) class CommentCountNode(BaseCommentNode): """Insert a count of comments into the context.""" def get_context_value_from_queryset(self, context, qs): return qs.count() class CommentFormNode(BaseCommentNode): """Insert a form for the comment model into the context.""" def get_form(self, context): ctype, object_pk = self.get_target_ctype_pk(context) if object_pk: return comments.get_form()(ctype.get_object_for_this_type(pk=object_pk)) else: return None def render(self, context): context[self.as_varname] = self.get_form(context) return '' class RenderCommentFormNode(CommentFormNode): """Render the comment form directly""" #@classmethod def handle_token(cls, parser, token): """Class method to parse render_comment_form and return a Node.""" tokens = token.contents.split() if tokens[1] != 'for': raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0]) # {% render_comment_form for obj %} if len(tokens) == 3: return cls(object_expr=parser.compile_filter(tokens[2])) # {% render_comment_form for app.models pk %} elif len(tokens) == 4: return cls( ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]), object_pk_expr = parser.compile_filter(tokens[3]) ) handle_token = classmethod(handle_token) def render(self, context): ctype, object_pk = self.get_target_ctype_pk(context) if object_pk: template_search_list = [ "comments/%s/%s/form.html" % (ctype.app_label, ctype.model), "comments/%s/form.html" % ctype.app_label, "comments/form.html" ] context.push() formstr = render_to_string(template_search_list, {"form" : self.get_form(context)}, context) context.pop() return formstr else: return '' class RenderCommentListNode(CommentListNode): """Render the comment list directly""" #@classmethod def handle_token(cls, parser, token): """Class method to parse render_comment_list and return a Node.""" tokens = token.contents.split() if tokens[1] != 'for': raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0]) # {% render_comment_list for obj %} if len(tokens) == 3: return cls(object_expr=parser.compile_filter(tokens[2])) # {% render_comment_list for app.models pk %} elif len(tokens) == 4: return cls( ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]), object_pk_expr = parser.compile_filter(tokens[3]) ) handle_token = classmethod(handle_token) def render(self, context): ctype, object_pk = self.get_target_ctype_pk(context) if object_pk: template_search_list = [ "comments/%s/%s/list.html" % (ctype.app_label, ctype.model), "comments/%s/list.html" % ctype.app_label, "comments/list.html" ] qs = self.get_query_set(context) context.push() liststr = render_to_string(template_search_list, { "comment_list" : self.get_context_value_from_queryset(context, qs) }, context) context.pop() return liststr else: return '' # We could just register each classmethod directly, but then we'd lose out on # the automagic docstrings-into-admin-docs tricks. So each node gets a cute # wrapper function that just exists to hold the docstring. #@register.tag def get_comment_count(parser, token): """ Gets the comment count for the given params and populates the template context with a variable containing that value, whose name is defined by the 'as' clause. Syntax:: {% get_comment_count for [object] as [varname] %} {% get_comment_count for [app].[model] [object_id] as [varname] %} Example usage:: {% get_comment_count for event as comment_count %} {% get_comment_count for calendar.event event.id as comment_count %} {% get_comment_count for calendar.event 17 as comment_count %} """ return CommentCountNode.handle_token(parser, token) #@register.tag def get_comment_list(parser, token): """ Gets the list of comments for the given params and populates the template context with a variable containing that value, whose name is defined by the 'as' clause. Syntax:: {% get_comment_list for [object] as [varname] %} {% get_comment_list for [app].[model] [object_id] as [varname] %} Example usage:: {% get_comment_list for event as comment_list %} {% for comment in comment_list %} ... {% endfor %} """ return CommentListNode.handle_token(parser, token) #@register.tag def render_comment_list(parser, token): """ Render the comment list (as returned by ``{% get_comment_list %}``) through the ``comments/list.html`` template Syntax:: {% render_comment_list for [object] %} {% render_comment_list for [app].[model] [object_id] %} Example usage:: {% render_comment_list for event %} """ return RenderCommentListNode.handle_token(parser, token) #@register.tag def get_comment_form(parser, token): """ Get a (new) form object to post a new comment. Syntax:: {% get_comment_form for [object] as [varname] %} {% get_comment_form for [app].[model] [object_id] as [varname] %} """ return CommentFormNode.handle_token(parser, token) #@register.tag def render_comment_form(parser, token): """ Render the comment form (as returned by ``{% render_comment_form %}``) through the ``comments/form.html`` template. Syntax:: {% render_comment_form for [object] %} {% render_comment_form for [app].[model] [object_id] %} """ return RenderCommentFormNode.handle_token(parser, token) #@register.simple_tag def comment_form_target(): """ Get the target URL for the comment form. Example::
""" return comments.get_form_target() #@register.simple_tag def get_comment_permalink(comment, anchor_pattern=None): """ Get the permalink for a comment, optionally specifying the format of the named anchor to be appended to the end of the URL. Example:: {{ get_comment_permalink comment "#c%(id)s-by-%(user_name)s" }} """ if anchor_pattern: return comment.get_absolute_url(anchor_pattern) return comment.get_absolute_url() register.tag(get_comment_count) register.tag(get_comment_list) register.tag(get_comment_form) register.tag(render_comment_form) register.simple_tag(comment_form_target) register.simple_tag(get_comment_permalink) register.tag(render_comment_list)