views:

601

answers:

4

If fruits is the list ['apples', 'oranges', 'pears'],

is there a quick way using django template tags to produce "apples, oranges, and pears"?

I know it's not difficult to do this using a loop and {% if counter.last %} statements, but because I'm going to use this repeatedly I think I'm going to have to learn how to write custom tags filters, and I don't want to reinvent the wheel if it's already been done.

As an extension, my attempts to drop the Oxford Comma (ie return "apples, oranges and pears") are even messier.

+3  A: 

First choice: use the existing join template tag.

http://docs.djangoproject.com/en/dev/ref/templates/builtins/#join

Here's their example

{{ value|join:" // " }}

Second choice: do it in the view.

fruits_text = ", ".join( fruits )

Provide fruits_text to the template for rendering.

S.Lott
I might require other lists (eg `vegetables_text`), and I may use these lists in lots of views, so I'd rather have a solution that only requires me to alter the templates. One of the reasons I was thinking about writing a custom tag is that I can use Python - `join` is definitely more elegant than for loops.
Alasdair
+3  A: 

I would suggest a custom django templating filter rather than a custom tag -- filter is handier and simpler (where appropriate, like here). {{ fruits | joinby:", " }} looks like what I'd want to have for the purpose... with a custom joinby filter:

def joinby(value, arg):
    return arg.join(value)

which as you see is simplicity itself!

Alex Martelli
I wasn't aware of the distinction between tags and filters. Whereas the custom tags seem slightly daunting when I look at the documentation, filters appear to be simpler, and exactly what I need in this case. Thanks!
Alasdair
A: 

Here's the filter I wrote to solve my problem:

def join_with_commas(obj_list):
    """Takes a list of objects and returns their unicode representations,
    seperated by commas and with 'and' between the penultimate and final items
    For example, for a list of fruit objects:
    [<Fruit: apples>,<Fruit: oranges>,<Fruit: pears>] -> 'apples, oranges and pears'
    """

    l=len(obj_list)
    if l==1:
        return obj_list[0].__unicode__()
    else:    
        return ", ".join(_.__unicode__() for _ in obj_list[:l-1]) \
                + " and " + obj_list[l-1].__unicode__()

To use it in the template: {{ fruits | join_with_commas }}

Alasdair
+2  A: 

Here's a super simple solution. Put this code into comma.html:

{% if not forloop.last %}{% ifequal forloop.revcounter 2 %} and {% else %}, {% endifequal %}{% else %}{% endif %}

And now wherever you'd put the comma, include "comma.html" instead:

{% for cat in cats %}
Kitty {{cat.name}}{% include "comma.html" %}
{% endfor %}
Michael Matthew Toomim