[Django]-Comma separated lists in django templates

9👍

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

158👍

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

77👍

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.

42👍

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

35👍

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!

4👍

If you want a ‘.’ on the end of Michael Matthew Toomim’s answer, then use:

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

4👍

All of the answers here fail one or more of the following:

  • They rewrite something (poorly!) that’s in the standard template library (ack, top answer!)
  • They don’t use and for the last item.
  • They lack a serial (oxford) comma.
  • They use negative indexing, which won’t work for django querysets.
  • They don’t usually handle string sanitation properly.

Here’s my entry into this canon. First, the tests:

class TestTextFilters(TestCase):

    def test_oxford_zero_items(self):
        self.assertEqual(oxford_comma([]), '')

    def test_oxford_one_item(self):
        self.assertEqual(oxford_comma(['a']), 'a')

    def test_oxford_two_items(self):
        self.assertEqual(oxford_comma(['a', 'b']), 'a and b')

    def test_oxford_three_items(self):
        self.assertEqual(oxford_comma(['a', 'b', 'c']), 'a, b, and c')

And now the code. Yes, it gets a bit messy, but you’ll see that it doesn’t use negative indexing:

from django.utils.encoding import force_text
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe

@register.filter(is_safe=True, needs_autoescape=True)
def oxford_comma(l, autoescape=True):
    """Join together items in a list, separating them with commas or ', and'"""
    l = map(force_text, l)
    if autoescape:
        l = map(conditional_escape, l)

    num_items = len(l)
    if num_items == 0:
        s = ''
    elif num_items == 1:
        s = l[0]
    elif num_items == 2:
        s = l[0] + ' and ' + l[1]
    elif num_items > 2:
        for i, item in enumerate(l):
            if i == 0:
                # First item
                s = item
            elif i == (num_items - 1):
                # Last item.
                s += ', and ' + item
            else:
                # Items in the middle
                s += ', ' + item

    return mark_safe(s)

You can use this in a django template with:

{% load my_filters %}
{{ items|oxford_comma }}

2👍

I would simply use ', '.join(['apples', 'oranges', 'pears']) before sending it to the template as a context data.

UPDATE:

data = ['apples', 'oranges', 'pears']
print(', '.join(data[0:-1]) + ' and ' + data[-1])

You will get apples, oranges and pears output.

1👍

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.

1👍

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

1👍

While it isn’t available as a template tag, there is an Django function, no other answer seems to have mentioned, that already does this get_text_list. This should be very easy to wrap with a template filter to make available in templates. Something like:

@register.filter
def text_list(value, conjunction):
    return get_text_list(value, conjunction)

{{ items | text_list:"and" }}

0👍

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)

Leave a comment