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.
- [Django]-Can't connect to local MySQL server through socket '/tmp/mysql.sock
- [Django]-Django CSRF Cookie Not Set
- [Django]-Django FileField: How to return filename only (in template)
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.
- [Django]-Setting default value for Foreign Key attribute in Django
- [Django]-Sending images using Http Post
- [Django]-How do you know if memcached is doing anything?
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 %}
- [Django]-List_display – boolean icons for methods
- [Django]-Django: Using F arguments in datetime.timedelta inside a query
- [Django]-Django: How to get related objects of a queryset?
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!
- [Django]-Strings won't be translated in Django using format function available in Python 2.7
- [Django]-Django-way for building a "News Feed" / "Status update" / "Activity Stream"
- [Django]-Convert seconds to hh:mm:ss in Python
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 %}
- [Django]-What is the best AJAX library for Django?
- [Django]-Django: Open uploaded file while still in memory; In the Form Clean method?
- [Django]-Adding to the "constructor" of a django model
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 }}
- [Django]-Best practice for Django project working directory structure
- [Django]-Django: How to get related objects of a queryset?
- [Django]-AWS: can't connect to RDS database from my machine
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.
- [Django]-Choose test database?
- [Django]-Django Rest Framework remove csrf
- [Django]-Detect mobile, tablet or Desktop on Django
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.
- [Django]-Django-celery: No result backend configured
- [Django]-Sorting related items in a Django template
- [Django]-Django Admin Form for Many to many relationship
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 }}
- [Django]-Numeric for loop in Django templates
- [Django]-Django test app error – Got an error creating the test database: permission denied to create database
- [Django]-Django-celery: No result backend configured
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" }}
- [Django]-How to access a dictionary element in a Django template?
- [Django]-Get all related Django model objects
- [Django]-How do you serialize a model instance in Django?
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)
- [Django]-IntegrityError duplicate key value violates unique constraint – django/postgres
- [Django]-How to migrate back from initial migration in Django 1.7?
- [Django]-Django class-based view: How do I pass additional parameters to the as_view method?