Comma separated lists in django templates
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!
Related videos on Youtube
Comments
-
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 customtagsfilters, 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 almost 15 yearsWhy aren't you using the existing join template tag?
-
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 almost 15 yearsIf 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 almost 15 yearsI 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 almost 15 yearsI 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 over 11 yearsThis doesn't insert the final "and".
-
Meekohi over 11 yearsThis also doesn't insert the final "and".
-
BiAiB almost 11 yearsthe best solution if you need to join more than the strings in an array
-
Sardathrion - against SE abuse over 8 years
-
Alex Martelli over 8 years@Meekohi, so
return arg.join(value[:-1]) + ' and ' + value[-1]
(for AP style, i.e, no comma beforeand
; 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 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 over 8 yearsMy 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 shortvalue
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 over 7 yearsVery clean & easy solution.
-
Rikki about 7 yearsIs there any best practice as to whether to do this in the template or in the view?
-
Alasdair almost 6 yearsThat gives
"apples, oranges, pears"
. The required output is"apples, oranges, and pears"
. -
yigidix almost 6 yearsOh, looks like I missed it. I've updated my answer. Please have a look. @Alasdair
-
user3748764 over 4 yearsA 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.