开发者

Is there a Django template tag that lets me set a context variable?

开发者 https://www.devze.com 2022-12-25 05:08 出处:网络
I want to be able to set variables in a template to string values.I wrote a tag, but it doesn\'t seem to change the context.The intended use is:

I want to be able to set variables in a template to string values. I wrote a tag, but it doesn't seem to change the context. The intended use is:

{% define "a string" as my_var %}

Update (solved):

class DefineNode(Node):
    def __init__(self, var, name):
        self.var = var
        self.name = name

    def __repr__(self):
        return "<DefineNode>"

    def render(self, context):
        context[self.开发者_运维知识库name] = self.var
        return ''

@register.tag
def define(parser, token):
    """
    Adds a name to the context for referencing an arbitrarily defined string.

    For example:

        {% define "my_string" as my_string %}

    Now anywhere in the template:

        {{ my_string }}
    """
    bits = list(token.split_contents())
    if (len(bits) != 4 or bits[2] != "as") or \
        not (bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]):
        raise TemplateSyntaxError("%r expected format is '\"string\" as name'" % bits[0])
    else:
        value = bits[1][1:-1]
    name = bits[3]
    return DefineNode(value, name)


Django already considered this particular case and provides the assigment tags, an special way of registering tags that set a variable in the context.

In this case, you don't need to care about retrieving, updating and saving the context. You simply do this:

@register.assignment_tag
def define(the_string):
  return the_string

And you can use it the very same way, but it is much cleaner:

{% define "a string" as my_var %}

This all the code you need.

EDIT: As Dirk Bergstrom pointed out, since version django 1.9 assignment_tag is deprecated. simple_tag is a perfect replacement.

@register.simple_tag
def define(the_string):
  return the_string


The answer is buried inside the more complex current_time example in the documentation.

Problem

You want to add a variable to the context. But you don't want to go back and add that variable to all the views which call all the templates which invoke the tag. You just want a tag which can add some data to the context wherever its wanted. I'm looking for this kind of thing when rendering those random distractions which get dropped into sidebars and aren't specifically related to the work of the main view, for example.

Method

To inject a variable to the context you need access to the context. To do that your custom tag will inject a node which added the data to the template context.

Example

This example adds a "coming_events" queryset to the context then loops over each result. It does that by declaring a custom tag which renders a node which adds a queryset to the context.

from django import template
from apps.events.models import Event
register = template.Library()

@register.tag
def coming_events(parser, token):
    return EventNode()

class EventNode(template.Node):
    def render(self, context):
        context['coming_events'] = Event.objects.all()
        return ''

You'd use it like this:

{% load events %}
{% coming_events %}
{% for event in coming_events %}
<div class="eventItem">
   <p>{{event.title}} {{event.data}}</p>
</div>
{% endfor %}

Extra Credit

If you're really keen to be able to name the variable arbitrarily eg {% coming_events as events %} then look closely at the example in the documentation and note how they split the token into what's before the ' as ' and what's after and use the latter part to name the context variable. You'd have to implement that.

Note that if I wound up putting the HTML for each event into its own dedicated template then I'd be better off just following the standard inclusion tag example in the documentation. This solution is suggested for when you want the data without any baggage.


If you want the variable to be available in other template blocks, you should look at http://od-eon.com/blogs/liviu/scope-variables-template-blocks/ . In particular, in the custom tag code, you should replace:

context[some_var_name] = some_val

with:

context.dicts[0][some_var_name] = some_val

That will do the trick (though it is possibly an ugly trick, and you should consider alternatives).


You don't need to write your own tag. The built-in {% with %} tag does this.


First of all, you generally want to set context variables within your view. Putting logic in the template is really a formula for added mess. That said, there does come a time when you want to use this, and the {% with %} tag makes a mess of things since you HAVE to end it with a {% endwith %}, losing the variable. The problem I ran into is that I can't include a template while passing it a value. I'd like to do:

{% if criteria %}
  {% define 'foo' as some_option %}
{% else %}
  {% define 'bar' as some_option %}
{% endif %}

{% include "some_template_partial.html" %}

This is impossible to do using {% with %} tags without having repeated code:

{% if criteria %}
  {% with 'foo' as some_option %}
    {% include "some_template_partial.html" %}
  {% endwith %}
{% else %}
  {% with 'bar' as some_option %}
    {% include "some_template_partial.html" %}
  {% endwith %}
{% endif %}

Fine as it is now, but this will degrade into a horrible mess as cases proliferate. Thus this code was written:

from django import template
from django.conf import settings
import logging
import re
register = template.Library()

NAMESPACE_PROTECTION = settings.DEBUG

class define_node(template.Node):
  def __init__(self, value, key, parse):
    self.value = value
    self.key = key
    self.parse = parse
  def render(self, context):
    if NAMESPACE_PROTECTION:
      if self.key in context:
        raise Exception("EPIC NAMESPACE FAIL, CONTEXT HAZ A %s" % self.key)
    if self.parse:
      context[self.key] = context[self.value]
    else:
      context[self.key] = self.value
    return ''

@register.tag
def define(parser, token):
  """Definition template tag. Use to define variables in your context within the template.
  Sorta like the {% with "blah" as blah %} tag, but without the {% endwith %} mess.

  Supports two modes:
  Literal mode: argument is encapsulated with quotes (e.g. "blah" or 'blah')
                variable, is set to the string literal, ex:
                {% define "fish" as foo %}
  Variable mode: argument is prefixed with a $ (e.g. $blah or $monkey)
                 variable is copied from another context variable, ex:
                 {% define $fish as foo %}

  Namespace protection is also provided if django.conf.settings.DEBUG is True.
  You will get an epic namespace fail if that occurs (please fix it before you deploy)

  TODO:
    * define override nomenclature if you REALLY want to overwrite a variable
      - should decide what nomeclature to use first
    * expand on variables so that {% define $array.blah as foo %} will work
      (this currently WILL NOT)
  """
  try:
    tag_name, arg = token.contents.split(None, 1)
  except ValueError:
    raise template.TemplateSyntaxError, "%r tag requires arguments" % token.contents.split()[0]
  m = re.search(r'(.*?) as (\w+)', arg)
  if not m:
    raise template.TemplateSyntaxError, "%r tag had invalid arguments" % tag_name
  value, key = m.groups()
  if (value[0] == value[-1] and value[0] in ('"', "'")):
    ret = value[1:-1]
    parse = False
  elif (value[0] == '$'):
    ret = value[1:]
    parse = True
  else:
    raise template.TemplateSyntaxError, "%r tag's first argument indeciperable" % tag_name
  return define_node(ret, key, parse)


You can use kiril's answer. It's quite simple. You can also use the set_context tag of django-libs.

Example:

{% set_context foo.bar|filter_foo as foobar %}


You can use custom template tags to set variables like so:

You create a file called set_var.py in your templatetags folder that contains the following code:

from django import template

register = template.Library()

class SetVarNode(template.Node):

    def __init__(self, var_name, var_value):
        self.var_name = var_name
        self.var_value = var_value

    def render(self, context):
        try:
            value = template.Variable(self.var_value).resolve(context)
        except template.VariableDoesNotExist:
            value = ""
        context[self.var_name] = value
        return u""

def set_var(parser, token):
    """
        {% set <var_name>  = <var_value> %}
    """
    parts = token.split_contents()
    if len(parts) < 4:
        raise template.TemplateSyntaxError("'set' tag must be of the form:  {% set <var_name>  = <var_value> %}")
    return SetVarNode(parts[1], parts[3])

register.tag('set', set_var)

Then to use this in your template, you just do the following:

{% load set_var %}

{% set a = 3 %}
{% set b = some_context_variable %}
{% set c = "some string" %}

Value of a is {{a}}
Value of b is {{b}}
Value of c is {{c}}


As of Django 4.0 this is much simpler:

from django import template

register = template.Library()

@register.simple_tag(takes_context=True)
def my_template_tag(context, any_var_or_object):
   result = None
   # do stuff that makes result not None
   context.update({'result': result})
   return ''

then to use just do this:

{% my_template_tag user %}
{# say my tag does an ORM query on the user and assigns the resulting queryset #}
{% for item in result %}
   {# do stuff #}
{% endfor %}

as the older answers state - you should try to define everything in your context in your view. Doing all of you context with template_tags will quickly become unmaintainable - but it is useful for one off things that come up or the exceedingly rare situation when you can't actually put it in the view

if you want to print a string or something - and not set a new variable in the page context, defer to kiril's answer

for further reference, see: https://docs.djangoproject.com/en/dev/howto/custom-template-tags/#simple-tags

0

精彩评论

暂无评论...
验证码 换一张
取 消