1

I have a custom Django template tag that acts as a conditional block:

{% if_has_permission request "some_permission" %}
<div>
    <input type="text" name="sample_1">
    <label><input type="checkbox" name="enable_it"> Enable</label>
</div>
{% endif_has_permission %}

In this example, if the request object doesn't have the appropriate permission (some_permission in this case), the block doesn't get rendered. However, as soon as I inject a conditional into this block (using the {% if %} template tag), I get a TemplateSyntaxError:

{% if_has_permission request "some_permission" %}
<div>
    <input type="text" name="sample_1">
    <label><input type="checkbox" name="enable_it" {% if isChecked %}checked="checked"{% endif %}> Enable</label>
</div>
{% endif_has_permission %}

The error I see is:

Invalid block tag: 'endif', expected 'endblock'

What, if anything, can I do to allow conditional expressions within my custom tag? I'm pretty sure that {% if %} is the only case I'll ever need to allow, though the occasional {% for %} might also be useful.

Here's my custom template tag code:

@register.tag
def if_has_permission(parser, token):
    try:
        args = token.split_contents()
        tag_name, request, to_check = args[0], args[1], args[2]
        opts = None
        if(len(args) > 3):
            opts = args[3:]
    except IndexError:
        raise template.TemplateSyntaxError("Tag %r requires at least two arguments" % tag_name)

    if(not (to_check[0] == to_check[-1] and to_check[0] in ('"', "'"))):
        raise template.TemplateSyntaxError("The second argument to tag %r must be in quotes" % tag_name)

    nodelist_true = parser.parse(('endif_has_permission'),)
    parser.delete_first_token()
    return CheckPermissionNode(request, to_check[1:-1], opts, nodelist_true)

class CheckPermissionNode(template.Node):
    def __init__(self, request, to_check, opts, nodelist_true):
        self.request = template.Variable(request)
        self.to_check = to_check
        self.opts = opts
        self.nodelist_true = nodelist_true

    def render(self, context):
        rq = self.request.resolve(context)

        # Admins can always see everything
        if(rq.session['is_admin']):
            return self.nodelist_true.render(context)

        # Check to see if any of the necessary permissions are present
        hasPerm = False
        checkList = self.to_check.split('|')
        for c in checkList:
            if(c in rq.session['perms']):
                hasPerm = True
                break

        if(hasPerm):
            return self.nodelist_true.render(context)
        else:
            return ''
Jonah Bishop
  • 12,279
  • 6
  • 49
  • 74
  • Why do you have to extract the 'if' condition to a template tag? (I'm wondering). Couldn't you move the logic to a regular 'if' would do it's job? (Haven't used django in a while) – phenxd Feb 26 '16 at 15:48
  • I'm thinking about something kind of like this answer http://stackoverflow.com/questions/25645219/syntax-to-supply-a-parameter-to-a-custom-template-tag-in-if-block?lq=1 – phenxd Feb 26 '16 at 15:51
  • 1
    Did you think about using [filters](https://docs.djangoproject.com/es/1.9/ref/templates/language/#filters) instead of tags? For me it seems more appropiate. Check this answer which solves a problem similar to yours http://stackoverflow.com/a/7791823/1345165 – dnaranjo Feb 26 '16 at 15:52
  • My knowledge may be outdated, but I'm wondering if you need an extra "if" in there ... `{% if if_has_permission request "some_permission" %}` – jcfollower Feb 26 '16 at 15:59
  • 1
    This might help ... http://stackoverflow.com/questions/22556052/django-how-to-use-custom-template-tag-with-if-and-else-checks – jcfollower Feb 26 '16 at 16:01
  • I originally thought about filters, but I need to check more than just one variable. Note that I check if the user is an admin down in the code; I also do some other processing not listed here. – Jonah Bishop Feb 26 '16 at 16:26

2 Answers2

1

Templatetags are not like blocks - think about them more like as methods. The error you are getting is due to bad syntax then.

To implement something like this just create a filter that will check a condition (exactly like your tag is doing now) and will return True or False then use it like

    {% if request|your_filter_name:"condition" %}
        <p> do_sth </p>
    {% endif %}

by using default django-template if block


Please notice that you cannot use tags in if blocks - that's why you need to change it to being filter (by adding register.filter instead of register.tag). Nothing will change but syntax:

    request|your_filter:"condition"

instead of

    your_tag request "condition"
m.antkowicz
  • 13,268
  • 18
  • 37
1

As it turns out, this is indeed possible. There's a typo in the if_has_permission routine:

nodelist_true = parser.parse(('endif_has_permission'),)

should instead become:

nodelist_true = parser.parse(('endif_has_permission',))

Note that the comma was in the wrong place! The parse function expects a tuple. Fixing this typo prevents things from going awry.

As an aside, I stumbled upon this question today after running into the exact same problem. Imagine my surprise when I found that I was the original asker, nearly five years ago; ha!

Jonah Bishop
  • 12,279
  • 6
  • 49
  • 74