views:

58

answers:

2

I don't know whether it's possible, but I'd like to be able to write something like the following:

{% with var1 var2 var3 as some_list %}
    {{ some_list|maximum }}
{% endwith %}

Creating a list on the fly from an arbitrary number of template variables and/or literals seems useful, so I'm hopeful that I've overlooked something simple.

Failing that, though, I'd like to know how to create a template tag which accepts an arbitrary number of arguments. (I've played around with simple_tag, which works well for tags which accept a fixed number of arguments.)

I don't want to go to the trouble of creating a parser and subclassing django.template.Node until I've ascertained that there's no simpler solution.

+2  A: 

If you want to add a new variable (ie some_list), you'll need access to the template's context, so simple_tag won't be enough.

For me, the first approach is to try to do this sort of work in the view, in order to keep the templates as simple as possible.

If that's not appropriate, you'll have to write the tag manually, like this:

@register.tag
def make_list(parser, token):
  bits = list(token.split_contents())
  if len(bits) >= 4 and bits[-2] == "as":
    varname = bits[-1]
    items = bits[1:-2]
    return MakeListNode(items, varname)
  else:
    raise template.TemplateSyntaxError("%r expected format is 'item [item ...] as varname'" % bits[0])

class MakeListNode(template.Node):
  def __init__(self, items, varname):
    self.items = map(template.Variable, items)
    self.varname = varname

  def render(self, context):
    context[self.varname] = [ i.resolve(context) for i in self.items ]
    return ""

And use it like this to create a new variable some_list:

{% make_list var1 var2 var3 as some_list %}

Feel free to give it a better name!

Will Hardy
+1. This solution lets you save the resulting list in another variable, allowing you to operate on it later.
Manoj Govindan
Thanks, Will. This fits the bill nicely. The reason that processing in the view is not an option is that I'm creating a template for a reusable Django app. The view provides a complete list of items. Within the template I'm setting the maximum number of items to display using `{% with 5 as max %}`. As well as outputting the appropriate number of items I'm including a display count in the form "displaying X of Y items". X is either `items|length` or `max`, whichever is smaller. So I need to find the smaller of two values, one of which is set within the template itself.
davidchambers
It should be `len(bits) > 3` rather than `len(bits) > 4` to allow a list with a single item to be created.
davidchambers
I've created a Django snippet for this code: http://djangosnippets.org/snippets/2196/.
davidchambers
Good catch! I've updated the code.
Will Hardy
A: 

I played around a bit and came up with a tag that can accept a variable number of arguments and convert them into a list.

@register.tag('to_list')
def to_list(_parser, token):
    try:
        parts = token.split_contents()
    except ValueError:
        raise template.TemplateSyntaxError, \
          "%r tag requires at least one argument" % token.contents.split()[0]

    return AsListNode(parts[1:])

class AsListNode(template.Node):
    def __init__(self, parts):
        self.parts = map(lambda p: template.Variable(p), parts)

    def render(self, context):
        resolved = []
        for each in self.parts:
            resolved.append(each.resolve(context))
        return resolved

Template:

<p>{% to_list var1 var2 var3 %}</p>

Update

@Will's solution is better. It lets you save the resulting list using another variable so that you can operate on it later.

Manoj Govindan