[Django]-How to get 'switch-case' statement functionality in Django templates?

23đź‘Ť

âś…

Unfortunately, this is not possible with the default Django template engine. You’ll have to write something ugly like this to emulate a switch.

{% if a %}
    {{ a }}
{% else %}
    {% if b %}
        {{ b }}
    {% else %}
        {% if c %}
            {{ c }}
        {% else %}
            {{ default }}
        {% endif %}
    {% endif %}
{% endif %}

or if only one if condition can be true and you don’t need a default.

{% if a %}
{{ a }}
{% endif %}
{% if b %}
{{ b }}
{% endif %}
{% if c %}
{{ c }}
{% endif %}

Usually, when the template engine is not powerful enough to accomplish what you want this is a sign that the code should be moved into Django view instead of in the template. For example:

# Django view
if a:
  val = a
elif b:
  val = b
elif c:
  val = c
else:
  val = default

# Template
{{ val }}
👤gerdemb

58đź‘Ť

As of Django 1.4, there is {% elif %}:

{% if a %}
  thing
{% elif b %}
  other thing
{% elif c %}
  another thing
{% endif %} 
👤Steve Bennett

23đź‘Ť

To the previous responders: Without understanding the use case, you’ve made assumptions and criticized the questioner. @Ber says “all over the place” which is certainly not implied by the questioner. Not fair.

I have a case where I would like to do a {% switch %} statement in exactly one place in my Django template. Not only is it not convenient to move the equivalent of the switch statement into Python code, but that would actually make both the view and the template harder to read and take simple conditional logic that belongs in one place and split it into two places.

In many cases where I could imagine a {% switch %} (or an {% if %}) being useful, not using one requires putting HTML in a view. That’s a far worse sin and is why {% if %} exists in the first place. {% switch %} is no different.

Fortunately, Django is extensible and multiple people have implemented switch. Check out:

Switch template tag

from django import template
from django.template import Library, Node, VariableDoesNotExist

register = Library()


@register.tag(name="switch")
def do_switch(parser, token):
    """
    The ``{% switch %}`` tag compares a variable against one or more values in
    ``{% case %}`` tags, and outputs the contents of the matching block.  An
    optional ``{% else %}`` tag sets off the default output if no matches
    could be found::

        {% switch result_count %}
            {% case 0 %}
                There are no search results.
            {% case 1 %}
                There is one search result.
            {% else %}
                Jackpot! Your search found {{ result_count }} results.
        {% endswitch %}

    Each ``{% case %}`` tag can take multiple values to compare the variable
    against::

        {% switch username %}
            {% case "Jim" "Bob" "Joe" %}
                Me old mate {{ username }}! How ya doin?
            {% else %}
                Hello {{ username }}
        {% endswitch %}
    """
    bits = token.contents.split()
    tag_name = bits[0]
    if len(bits) != 2:
        raise template.TemplateSyntaxError("'%s' tag requires one argument" % tag_name)
    variable = parser.compile_filter(bits[1])

    class BlockTagList(object):
        # This is a bit of a hack, as it embeds knowledge of the behaviour
        # of Parser.parse() relating to the "parse_until" argument.
        def __init__(self, *names):
            self.names = set(names)
        def __contains__(self, token_contents):
            name = token_contents.split()[0]
            return name in self.names

    # Skip over everything before the first {% case %} tag
    parser.parse(BlockTagList('case', 'endswitch'))

    cases = []
    token = parser.next_token()
    got_case = False
    got_else = False
    while token.contents != 'endswitch':
        nodelist = parser.parse(BlockTagList('case', 'else', 'endswitch'))

        if got_else:
            raise template.TemplateSyntaxError("'else' must be last tag in '%s'." % tag_name)

        contents = token.contents.split()
        token_name, token_args = contents[0], contents[1:]

        if token_name == 'case':
            tests = map(parser.compile_filter, token_args)
            case = (tests, nodelist)
            got_case = True
        else:
            # The {% else %} tag
            case = (None, nodelist)
            got_else = True
        cases.append(case)
        token = parser.next_token()

    if not got_case:
        raise template.TemplateSyntaxError("'%s' must have at least one 'case'." % tag_name)

    return SwitchNode(variable, cases)

class SwitchNode(Node):
    def __init__(self, variable, cases):
        self.variable = variable
        self.cases = cases

    def __repr__(self):
        return "<Switch node>"

    def __iter__(self):
        for tests, nodelist in self.cases:
            for node in nodelist:
                yield node

    def get_nodes_by_type(self, nodetype):
        nodes = []
        if isinstance(self, nodetype):
            nodes.append(self)
        for tests, nodelist in self.cases:
            nodes.extend(nodelist.get_nodes_by_type(nodetype))
        return nodes

    def render(self, context):
        try:
            value_missing = False
            value = self.variable.resolve(context, True)
        except VariableDoesNotExist:
            no_value = True
            value_missing = None

        for tests, nodelist in self.cases:
            if tests is None:
                return nodelist.render(context)
            elif not value_missing:
                for test in tests:
                    test_value = test.resolve(context, True)
                    if value == test_value:
                        return nodelist.render(context)
        else:
            return ""
👤Roy Leban

2đź‘Ť

In a very general view, the need for a switch statement is a sign that there is a need to create new classes and objects that capture the different “cases”.

Then, instead of “swtich”ing all over the place, you only need to call an object method or reference an object attribute and your done.

👤Ber

0đź‘Ť

This is a fixed version of the code Roy wrote:


@register.tag(name="case")
def case_tag(parser, token):
    pass


@register.tag(name="switch")
def switch_tag(parser, token):
    """
    The ``{% switch %}`` tag compares a variable against one or more values in
    ``{% case %}`` tags, and outputs the contents of the matching block.  An
    optional ``{% else %}`` tag sets off the default output if no matches
    could be found::

        {% switch result_count %}
            {% case 0 %}
                There are no search results.
            {% case 1 %}
                There is one search result.
            {% else %}
                Jackpot! Your search found {{ result_count }} results.
        {% endswitch %}

    Each ``{% case %}`` tag can take multiple values to compare the variable
    against::

        {% switch username %}
            {% case "Jim" "Bob" "Joe" %}
                Me old mate {{ username }}! How ya doin?
            {% else %}
                Hello {{ username }}
        {% endswitch %}
    """
    bits = token.contents.split()
    tag_name = bits[0]
    if len(bits) != 2:
        raise template.TemplateSyntaxError("'%s' tag requires one argument" % tag_name)
    variable = parser.compile_filter(bits[1])

    class BlockTagList(object):
        # This is a bit of a hack, as it embeds knowledge of the behaviour
        # of Parser.parse() relating to the "parse_until" argument.
        def __init__(self, *names):
            self.names = set(names)
        def __contains__(self, token_contents):
            name = token_contents.split()[0]
            return name in self.names

    # Skip over everything before the first {% case %} tag
    # TODO: error if there is anything here!
    parser.parse(BlockTagList('case', 'endswitch'))

    cases = []
    token = parser.next_token()
    got_case = False
    got_else = False
    while token.contents != 'endswitch':
        nodelist = parser.parse(BlockTagList('case', 'else', 'endswitch'))

        if got_else:
            raise template.TemplateSyntaxError("'else' must be last tag in '%s'." % tag_name)

        contents = token.split_contents()
        token_name, token_args = contents[0], contents[1:]

        if token_name == 'case':
            tests = map(parser.compile_filter, token_args)
            case = (tests, nodelist)
            got_case = True
        else:
            # The {% else %} tag
            assert token_name == 'else'
            case = (None, nodelist)
            got_else = True
        cases.append(case)
        token = parser.next_token()

    if not got_case:
        raise template.TemplateSyntaxError("'%s' must have at least one 'case'." % tag_name)

    return SwitchNode(variable, cases)


class SwitchNode(Node):
    def __init__(self, variable, cases):
        self.variable = variable
        self.cases = cases

    def __repr__(self):
        return "<Switch node>"

    def __iter__(self):
        for tests, nodelist in self.cases:
            for node in nodelist:
                yield node

    def get_nodes_by_type(self, nodetype):
        nodes = []
        if isinstance(self, nodetype):
            nodes.append(self)
        for tests, nodelist in self.cases:
            nodes.extend(nodelist.get_nodes_by_type(nodetype))
        return nodes

    def render(self, context):
        value_missing = False
        value = self.variable.resolve(context, True)

        for tests, nodelist in self.cases:
            if tests is None:
                return nodelist.render(context)
            elif not value_missing:
                for test in tests:
                    test_value = test.resolve(context, True)
                    if value == test_value:
                        return nodelist.render(context)

        assert False, f'No case hit for value {value}'

tests to verify:

import pytest
from django.template import (
    Context,
    Template,
)


def test_switch():
    assert Template('''
    {% load base %}
    {% switch foo %}
        {% case 1 %}
            foo        
    {% endswitch %}
    ''').render(Context(dict(foo=1))).strip() == 'foo'

    assert Template('''
    {% load base %}
    {% switch foo %}
        {% case 1 %}
            foo    
        {% else %}
            bar    
    {% endswitch %}
    ''').render(Context(dict(foo=2))).strip() == 'bar'

    assert Template('''
    {% load base %}
    {% switch foo %}
        {% case 1 %}
            foo    
        {% case 2 %}
            bar 
    {% endswitch %}
    ''').render(Context(dict(foo=2))).strip() == 'bar'

    assert Template('''
    {% load base %}
    {% switch foo %}
        {% case "hello world" %}
            foo    
        {% case "good bye world" %}
            bar 
    {% endswitch %}
    ''').render(Context(dict(foo="hello world"))).strip() == 'foo'


    with pytest.raises(AssertionError):
        Template('''
        {% load base %}
        {% switch foo %}
            {% case 1 %}
                foo
        {% endswitch %}
        ''').render(Context(dict(foo=0)))


    with pytest.raises(AssertionError):
        Template('''
        {% load base %}
        {% switch foo %}
            {% case "hello world" %}
                foo    
            {% case "good bye world" %}
                bar 
        {% endswitch %}
        ''').render(Context(dict(foo="something else"))).strip()
👤boxed

Leave a comment