7

I'm doing something like:

{% extends 'base.html' %}
{% url myapp.views.dashboard object as object_url %}
{% block sidebar %}
... {{ object_url }} ...
{% endblock %}
{% block content %}
... {{ object_url }} ...
{% endblock %}

Django documentation says url templatetag can define a variable in context, but I don't get any value for object_url in the following blocks.

If I put the url templatetag at the beginning of each block, it works, but I don't want to "repeat myself".

Anyone knows a better solution?

Achimnol
  • 1,551
  • 3
  • 19
  • 31

5 Answers5

8

If the URL is view specific, you could pass the URL from your view. If the URL needs to be truly global in your templates, you could put it in a context processor:

def object_url(request):
    return {'object_url': reverse('myapp.views.dashboard')}
SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
defrex
  • 15,735
  • 7
  • 34
  • 45
  • Um, many view uses this variable but not all. Also, I'm using the same pattern for another kind of variables defined by my custom templatetags. The case above is just simplified, so I think it's not appropriate to adopt your solution as it is. – Achimnol Jun 23 '09 at 03:12
  • 3
    Even if it's not used in every template it doesn't hurt anything to put it into a context processor... unless it's doing a database lookup of course, in which case it can impact site performance. – Van Gale Jun 23 '09 at 04:39
5

You could write a custom template tag:

@register.simple_tag(takes_context=True)
def set_global_context(context, key, value):
    """
    Sets a value to the global template context, so it can
    be accessible across blocks.

    Note that the block where the global context variable is set must appear
    before the other blocks using the variable IN THE BASE TEMPLATE.  The order
    of the blocks in the extending template is not important. 

    Usage::
        {% extends 'base.html' %}

        {% block first %}
            {% set_global_context 'foo' 'bar' %}
        {% endblock %}

        {% block second %}
            {{ foo }}
        {% endblock %}
    """
    context.dicts[0][key] = value
    return ''
seddonym
  • 16,304
  • 6
  • 66
  • 71
  • I've used this approach myself and it works great for code and readability. A global variable edited randomly throughout the templates can be very hard to debug. Creating a state machine using a template keeps the logic in one place, this has many benefits. – Paul Kenjora Mar 14 '16 at 15:54
  • Great solution. I found it useful combining this with "getattr(settings, name, "")", to retrieve and store settings as in "from django.conf import settings". – gornvix Sep 10 '20 at 16:52
2

Looks like this was answered before, but there is an alternative. It's one thing to use a context processor to keep track of something defined from outside the template, but sometimes you want to count the number of times two loops go through, or something like that. There is another way:

class GlobalVariable(object):
    def __init__(self, varname, varval):
        self.varname = varname
        self.varval = varval

    def name(self):
        return self.varname

    def value(self):
        return self.varval

    def set(self, newval):
        self.varval = newval


class GlobalVariableSetNode(template.Node):
    def __init__(self, varname, varval):
        self.varname = varname
        self.varval = varval

    def render(self, context):
        gv = context.get(self.varname, None)
        if gv:
            gv.set(self.varval)
        else:
            gv = context[self.varname] = GlobalVariable(
                self.varname, self.varval)
        return ''


def setglobal(parser, token):
    try:
        tag_name, varname, varval = token.contents.split(None, 2)
    except ValueError:
        raise template.TemplateSyntaxError(
            "%r tag requires 2 arguments" % token.contents.split()[0])
    return GlobalVariableSetNode(varname, varval)


register.tag('setglobal', setglobal)


class GlobalVariableGetNode(template.Node):
    def __init__(self, varname):
        self.varname = varname

    def render(self, context):
        try:
            return context[self.varname].value()
        except AttributeError:
            return ''


def getglobal(parser, token):
    try:
        tag_name, varname = token.contents.split(None, 1)
    except ValueError:
        raise template.TemplateSyntaxError(
            "%r tag requires arguments" % token.contents.split()[0])
    return GlobalVariableGetNode(varname)


register.tag('getglobal', getglobal)


class GlobalVariableIncrementNode(template.Node):
    def __init__(self, varname):
        self.varname = varname

    def render(self, context):
        gv = context.get(self.varname, None)
        if gv is None:
            return ''
        gv.set(int(gv.value()) + 1)
        return ''


def incrementglobal(parser, token):
    try:
        tag_name, varname = token.contents.split(None, 1)
    except ValueError:
        raise template.TemplateSyntaxError(
            "%r tag requires arguments" % token.contents.split()[0])
    return GlobalVariableIncrementNode(varname)


register.tag('incrementglobal', incrementglobal)

This allows you to use it in a template like this:

{% setglobal ii 0 %}
...
{% for ... %}
  {% incrementglobal ii %}
  current={% getglobal ii %}
{% endfor %}
...
{% for ... %}
  {% incrementglobal ii %}
  current={% getglobal ii %}
{% endfor %}
...
total of 2 loops={% getglobal ii %}
...
{% setglobal ii 0 %}
...
do something else now that {% getglobal ii %} is back to 0
Solomon Ucko
  • 5,724
  • 3
  • 24
  • 45
eruciform
  • 7,680
  • 1
  • 35
  • 47
0

Well, this is kind of abusive of template inheritance, but you could use {{block.super}} to put object_url into your blocks.

In other words, in your mid-level template do:

{% block sidebar %}{{ object_url }}{% endblock %}
{% block content %}{{ object_url }}{% endblock %}

And then in your block templates use:

{% block sidebar %}
... {{ block.super }}...
{% endblock %}

It's not a great idea because it prevents you from putting anything besides {{ object_url }} into your block... but it works. Just don't tell anyone you got it from me!

Van Gale
  • 43,536
  • 9
  • 71
  • 81
  • I should add that personally I prefer being explicit and doing the load in each template. Makes it easier for me to see exactly where my data is coming from. – Van Gale Jun 23 '09 at 04:37
0

In every inherited template any code outside blocks redefinitions is not executed. So in your example you have to call {% url %} tag inside each block or use context processor for setting "global" variable.

Alex Koshelev
  • 16,879
  • 2
  • 34
  • 28