Comma separated lists in django templates

54,314

Solution 1

Here's the filter I wrote to solve my problem (it doesn't include the Oxford comma)

def join_with_commas(obj_list):
    """Takes a list of objects and returns their string representations,
    separated 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'
    """
    if not obj_list:
        return ""
    l=len(obj_list)
    if l==1:
        return u"%s" % obj_list[0]
    else:    
        return ", ".join(str(obj) for obj in obj_list[:l-1]) \
                + " and " + str(obj_list[l-1])

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

Solution 2

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.

Solution 3

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 %}

Update: @user3748764 gives us a slightly more compact version, without the deprecated ifequal syntax:

{% if not forloop.first %}{% if forloop.last %} and {% else %}, {% endif %}{% endif %}

Note that it should be used before the element, not after.

Solution 4

On the Django template this all you need to do for establishing a comma after each fruit. The comma will stop once its reached the last fruit.

{% if not forloop.last %}, {% endif %}

Solution 5

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!

Share:
54,314

Related videos on Youtube

Alasdair
Author by

Alasdair

Pythonista/Djangonaut

Updated on September 14, 2020

Comments

  • Alasdair
    Alasdair over 3 years

    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.

    • user1066101
      user1066101 almost 15 years
      Why aren't you using the existing join template tag?
    • Alasdair
      Alasdair almost 15 years
      @S.Lott: I didn't spot the join template tag when I looked through the list on the docs page. Oops. Having said that, the next stage is to wrap each item in the list in a hyperlink, for which I think I'll need to write a filter.
    • user1066101
      user1066101 almost 15 years
      If you're using links to your Django URL's, you'll need to use the {% url %} tag. The {% for %} loop suddenly looks much more appealing. "Repeatedly" often means your templates need to {% include %} common features.
  • Alasdair
    Alasdair almost 15 years
    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
    Alasdair almost 15 years
    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!
  • Meekohi
    Meekohi over 11 years
    This doesn't insert the final "and".
  • Meekohi
    Meekohi over 11 years
    This also doesn't insert the final "and".
  • BiAiB
    BiAiB almost 11 years
    the best solution if you need to join more than the strings in an array
  • Sardathrion - against SE abuse
    Sardathrion - against SE abuse over 8 years
    All you need to do to add the Oxford comma is to replace and with , and.
  • Alex Martelli
    Alex Martelli over 8 years
    @Meekohi, so return arg.join(value[:-1]) + ' and ' + value[-1] (for AP style, i.e, no comma before and; for "Oxford comma" style, add a + arg right before the ` + 'and'`). Me, I prefer the strength of the asyndeton, per literarydevices.net/asyndeton . And none of this fine debate on English style belongs on StackOverflow anyway -- take it to english.stackexchange.com!-)
  • Meekohi
    Meekohi over 8 years
    +1 for coming back to a 6 year old question to answer a 3 year old comment, but what does your filter do when there is only one item in the array? :) Making things human readable is not so easy it seems.
  • Alex Martelli
    Alex Martelli over 8 years
    My original asyndeton filter works fine; the new ones inserting 'and' are coded in the above comment to unconditionally insert 'and'. All it takes to behave differently for very short value is to code [[as above] if len(value)>1 else value. And BTW asyndeton is HIGHLY human readable -- Aristotle, Shakespeare, Joyce (among many others of course) all used it effectively, and my preference for it is rooted in my being a poet, first and foremost.
  • SaeX
    SaeX over 7 years
    Very clean & easy solution.
  • Rikki
    Rikki about 7 years
    Is there any best practice as to whether to do this in the template or in the view?
  • Alasdair
    Alasdair almost 6 years
    That gives "apples, oranges, pears". The required output is "apples, oranges, and pears".
  • yigidix
    yigidix almost 6 years
    Oh, looks like I missed it. I've updated my answer. Please have a look. @Alasdair
  • user3748764
    user3748764 over 4 years
    A slightly more compact version without the deprecated ifequal syntax. {% if not forloop.first %}{% if forloop.last %} and {% else %}, {% endif %}{% endif %} Note that it should be used before the element, not after.