Skip to content

aprendía

(I was learning)

Pretty comma-separated lists

Here is a quick tip for outputting lists. Often times you’ll have a short list of items that you want to output as a comma separated list. Things like a couple of contributing authors, the categories your entry is filed under, or the tags that a photo is tagged with. The first thought might be to try this:

(I am going to add line breaks and indentation to all of these for the sake of readability. However, in production you must remove all line breaks in these snippets or you will end up with spaces in places you don’t want them, like before a comma. (No, the {% spaceless %} tag won’t help.))

{% for tag in entry.tags.all %}
  {{ tag.name }},
{% endfor %}

There is a problem with that method — even the last tag would have a comma after it. That feels tacky to me. We could improve it like so:

{% for tag in entry.tags.all %}
  {{ tag.name }}
  {% if not forloop.last %},{% endif %}
{% endfor %}

But what if you want a comma-separated list that uses an ampersand for the final item, like so:

foo, bar & baz

We need to change things up a bit to do that. We need to put the if statement before the list items. The following would achieve the same result as above.

{% for tag in entry.tags.all %}
  {% if not forloop.first %}, {% endif %}
  {{ tag.name }}
{% endfor %}

Now let’s throw in our ampersand.

{% for tag in entry.tags.all %}
  {% if not forloop.first %}
    {% if forloop.last %} & {% else %}, {% endif %}
  {% endif %}
  {{ tag.name }}
{% endfor %}

Beyond the {% if %} tag

That is syntax is what most people would use. The {% if %} tag is one of our most used tools, but there are other filters that can often be used instead and they may make our code more readable. The the same outcome could be achieved with:

{% for tag in entry.tags.all %}
  {% if not forloop.first %}
    {{ forloop.last|yesno:" & ,, "|safe }}
  {% endif %}
  {{ tag.name }}
{% endfor %}

Ah, the yesno filter. It is a beautiful thing. Two things two note about the following example:

  1. yesno cannot output raw commas because they are used as the separator in the argument; but we can use an entity.
  2. Our entities will be escaped unless we mark them with the safe filter.

Now one may argue that the yesno filter didn’t do much for us in this example, except make us look up the comma’s decimal entity. But the yesno filter can really shine. What if we wanted to typogrify our ampersand, wrapping it in a span element so we can style it? Using the if statement our snippet would look like so:

{% for tag in entry.tags.all %}
  {% if not forloop.first %}
    {% if forloop.last %} <span class="amp">&amp;</span> {% else %}, {% endif %}
  {% endif %}
  {{ tag.name }}
{% endfor %}

But if we use the yesno and amp filters our snippet is much shorter.

{% for tag in entry.tags.all %}
  {% if not forloop.first %}
    {{ forloop.last|yesno:" &amp; ,&#44; "|amp }}
  {% endif %}
  {{ tag.name }}
{% endfor %}

Also note that we did not need to use the safe filter because typogrify filters mark output as safe (see Django docs).

Conclusion

Pull out all of the line breaks and our final snippet:

{% for tag in entry.tags.all %}{% if not forloop.first %}{{ forloop.last|yesno:" &amp; ,&#44; "|amp }}{% endif %}{{ tag.name }}{% endfor %}

Output:

foo, bar & baz

If you like the yesno filter, you may also find default and default_if_none to be useful.