Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django check existence of template context variable

I am writing a django template and I want to differentiate between the existence of a context variable vs it being None, empty etc. I've done my homework and it seems surprisingly hard. Specifically, this is what I'm trying to do

view 1:
...
if some_condition = True:
    context['letters'] = ['a', 'b', 'c'] # The list might also be empty or None in some cases
else
    context['numbers'] = [1, 2, 3] #This list might be empty or None in some cases

Template
...
<ul>
{% if letters %}
    {% for x in letter %}
        <li>{{x}}</li>
    {%endfor%}
{% else %}
    {%for x in numbers%}
        <li>{{x}}</li>
    {%endfor%}
</ul>

Using the {% if %} is dicey because it is fails if letters doesnt exist or the list is empty. I want to use letters even if it is empty (but defined in the context)

I have the same problem with the built-in filters default and default_if_none How can I differentiate the existence of a context variable from it being other things like None or Empty

like image 509
sha Avatar asked Oct 04 '22 20:10

sha


1 Answers

I recently faced the same conundrum, and after looking into the way the {% if %} tag is structured here's what I came up with:

from django.template.base import VariableDoesNotExist
from django.template.defaulttags import IfNode
from django.template.smartif import IfParser, Literal

# Used as a value for ifdef and ifndef tags
undefined = object()

class IfDefLiteral(Literal):
    def eval(self, context):
        if not self.value in context:
            # Can't raise an exception here because Operator catches it
            return undefined

class IfDefParser(IfParser):
    def create_var(self, value):
        return IfDefLiteral(value)

class IfDefNode(IfNode):
    def __init__(self, defined=True, *args, **kwargs):
        self.defined = defined
        super(IfDefNode, self).__init__(*args, **kwargs)

    def __repr__(self):
        return "<%s>" % self.__class__.__name__

    def render(self, context):
        for condition, nodelist in self.conditions_nodelists:

            match = undefined
            if condition is not None:           # if / elif clause
                try:
                    match = condition.eval(context)
                except VariableDoesNotExist:
                    pass

            if condition is None or (  # else clause, always render
                (self.defined and match is not undefined) or
                (match is undefined and not self.defined)):
                return nodelist.render(context)

        return ''

def _gen_ifdef(parser, token, block_tokens, defined):
    # {% if ... %}
    bits = token.split_contents()[1:]
    condition = IfDefParser(bits).parse()
    nodelist = parser.parse(block_tokens)
    conditions_nodelists = [(condition, nodelist)]
    token = parser.next_token()

    # {% elif ... %} (repeatable)
    while token.contents.startswith(block_tokens[0]):
        bits = token.split_contents()[1:]
        condition = IfDefParser(bits).parse()
        nodelist = parser.parse(block_tokens)
        conditions_nodelists.append((condition, nodelist))
        token = parser.next_token()

    # {% else %} (optional)
    if token.contents == 'else':
        nodelist = parser.parse(block_tokens[-1:])
        conditions_nodelists.append((None, nodelist))
        token = parser.next_token()

    # {% endif %}
    assert token.contents == block_tokens[-1]

    return IfDefNode(defined, conditions_nodelists)

@register.tag
def ifdef(parser, token):
    """Check if variable is defined in the context

    Unlike the {% if %} tag, this renders the block if the variable(s)
    exist within the context, not only if they are truthy. That is, variables
    with None, 0 or [] values would also render the block.
    """
    return _gen_ifdef(parser, token, ('elifdef', 'else', 'endifdef'), True)

@register.tag
def ifndef(parser, token):
    """Check if variable is *not* defined in the context

    This is the opposite of {% ifdef %}.
    """
    return _gen_ifdef(parser, token, ('elifndef', 'else', 'endifndef'), False)

Then you would use it like an {% if %} tag in your template:

{% ifdef letters or numbers %}
    {# do something with letters or numbers #}
{% else %}
    {# variables are not defined here #}
{% endifdef %}

I'm not sure if there's a simpler way to accomplish this, and while I'm not too happy with the approach, it seems to work well in my use case. Hope this helps!

like image 179
imiric Avatar answered Oct 08 '22 02:10

imiric