views:

54

answers:

3

I'm trying, in vain, to create a simple Django template tag to either show or hide a "delete" link next to a submitted comment on my site.

In a nutshell, I want to pass the comment object to the template tag, determine if the currently logged in user is authorized to delete the comment and then either show or not show the link.

The usage in my template would be like so:

{% load access_tags %}
{% if_authorized comment %}
   <a href="{% url delete_comment comment.id %}">Delete</a>
{% endif_authorized %}

Rest assured that I also check in the appropriate view if the user is authorized to delete the comment.

Does this type of tag have a specific name? It would certainly help me with my Google searches if it did. Thanks for your help!

UPDATE 1:

The way my site works, two people are potentially authorized to delete a comment: 1) the comment creator and 2) the owner of the post where the comment was left. Because of this, I need to determine, per comment, if one of those conditions is present.

I don't think I can use something like Django's built-in permission sytem, since it requires that permissions "be set globally per type of object, no per specific object instance".

In my case, user "Bob" may have permissions to delete a comment (if he wrote it or it is on a post he created), but he also may not be allowed to delete it (if he is looking at a comment on someone else's post).

UPDATE 2:

It appears that you can't pass objects to a template tag, only strings: "Although you can pass any number of arguments to a template tag using token.split_contents(), the arguments are all unpacked as string literals." I guess I'll pass the id of the comment object in question and pull it in the tag.

I was wrong about this, just have to access the passed in object like:

self.comment.resolve(context).user 

vs.

self.comment.user
+1  A: 

There already exists a project that aims to do what you would like to do.

django-authority allows for fine grain control over permissions in templates.

Django 1.2 also contains user permissions in templates, too.

Nick Presta
Thanks Nick, see my update above as to why Django's built-in permissions probably won't work in my case. At first blush, going with a full-blow application seems like overkill, but I'll check it out none-the-less. Thanks for your answer.
mitchf
+1  A: 

how about this... create a custom tag that writes a variable in the context, then test that variable using {% if %}

it'd be something like this:

{% check_access comment %}
{% if has_access %}
    <a href="{% url delete_comment comment.id %}">Delete</a>
{% endif %}

of course the "check_access" tag would write the "has_access" in the context.

Good Luck

pleasedontbelong
How would this work if there are 10 comments on the page of which the logged in user has delete rights to 3. Won't "has_access" keep overwriting and end up set to whatever the last value was?
mitchf
well you just have to call the {check _access} on each comment... but you already find your solution =) F'm always surprised of how a simple test like this one, becomes so hard to do in django... that's why php will never die!
pleasedontbelong
Thanks pleasedontbelong, I appreciate the help.
mitchf
A: 

OK, this is how I did it...

The tag is used like this in the template:

   {% load access_tags %}
   {% if_authorized comment.user object.user user %}
      <a href="{% url delete_comment comment.id %}">Delete</a>
   {% endif_authorized %}

The template tag file is called "access_tag.py" and is in my app's "templatetags" directory. This is the contents of "access_tag.py":

from django.template import Node, NodeList, TemplateSyntaxError
from django.template import Library, Variable, VariableDoesNotExist

register = Library()

def do_if_authorized(parser, token):
    """
    Outputs the contents of the block if the 'comment owner' or the 
    'page owner' is also the 'authenticated user'. As well, you can use
    an {% else %} tag to show text if the match fails.

    Takes three parameters:
      1) the comment owner
      2) the page owner
      3) the current authenticated user
    """
    bits = token.contents.split()
    if len(bits) != 4:
        raise TemplateSyntaxError("%s tag takes three arguments: \
                                   1) the comment owner \
                                   2) the page owner \
                                   3) the current authenticated user" % bits[0])
    nodelist_true = parser.parse(('else', 'endif_authorized'))
    token = parser.next_token()

    if token.contents == 'else':
        nodelist_false = parser.parse(('endif_authorized',))
        parser.delete_first_token()
    else:
        nodelist_false = NodeList()
    return IfAuthorizedNode(bits[1], bits[2], bits[3], nodelist_true, nodelist_false)

class IfAuthorizedNode(Node):
    def __init__(self, comment_owner, page_owner, authenticated_user, nodelist_true, nodelist_false):
        self.nodelist_true = nodelist_true
        self.nodelist_false = nodelist_false
        self.comment_owner = Variable(comment_owner)
        self.page_owner = Variable(page_owner)
        self.authenticated_user = Variable(authenticated_user)

    def render(self, context):
        try:
            comment_owner = self.comment_owner.resolve(context)
            page_owner = self.page_owner.resolve(context)
            authenticated_user = self.authenticated_user.resolve(context)
        except VariableDoesNotExist:
            return ''

        if comment_owner == authenticated_user or page_owner == authenticated_user:
            return self.nodelist_true.render(context)
        else:
            return self.nodelist_false.render(context)

register.tag('if_authorized', do_if_authorized)

Done. In the end, it would have been pretty easy to just use the built-in {% if %} tag to do this comparison, but since I'll have other per-object authorizations to do, I will continue to build out these custom "access_tags". Plus, the template code looks so much tidier :)

mitchf