If fruits
is the list [\'apples\', \'oranges\', \'pears\']
,
is there a quick way using django template tags to produce \"apples, oranges, and p
Django doesn't have support for this out-of-the-box. You can define a custom filter for this:
from django import template
register = template.Library()
@register.filter
def join_and(value):
"""Given a list of strings, format them with commas and spaces, but
with 'and' at the end.
>>> join_and(['apples', 'oranges', 'pears'])
"apples, oranges, and pears"
"""
# convert numbers to strings
value = [str(item) for item in value]
if len(value) == 1:
return value[0]
# join all but the last element
all_but_last = ", ".join(value[:-1])
return "%s, and %s" % (all_but_last, value[-1])
However, if you want to deal with something more complex than just lists of strings, you'll have to use an explicit {% for x in y %}
loop in your template.
If you like one-liners:
@register.filter
def lineup(ls): return ', '.join(ls[:-1])+' and '+ls[-1] if len(ls)>1 else ls[0]
and then in the template:
{{ fruits|lineup }}
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.
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 }}
I think the simplest solution might be:
@register.filter
def comma_list(p_values: Iterable[str]) -> List[str]:
values = list(p_values)
if len(values) > 1:
values[-1] = u'and %s' % values[-1]
if len(values) > 2:
return u', '.join(values)
return u' '.join(values)