Django模板中的逗号分隔列表


75

如果fruits是清单['apples', 'oranges', 'pears']

有没有一种使用Django模板标签生成“苹果,橘子和梨”的快速方法?

我知道使用循环和{% if counter.last %}语句执行此操作并不难,但是由于我将反复使用它,所以我认为我将必须学习如何编写自定义标签 过滤器,如果已经完成,我不想重新发明轮子。

作为扩展,我尝试删除牛津逗号(即返回“苹果,橘子和梨”)的操作更加混乱。


4
为什么不使用现有的联接模板标记?
S.Lott

1
@ S.Lott:在查看文档页面上的列表时,我没有发现连接模板标记。哎呀。话虽如此,下一步是将列表中的每个项目包装在一个超链接中,我认为我需要为此编写一个过滤器。
阿拉斯代尔

如果您使用的是指向Django URL的链接,则需要使用{% url %}标记。该{% for %}回路突然看起来更吸引人。“重复”通常意味着您的模板需要{% include %}通用功能。
S.Lott

Answers:


140

首选:使用现有的联接模板标记。

http://docs.djangoproject.com/en/dev/ref/templates/builtins/#join

这是他们的例子

{{ value|join:" // " }}

第二选择:在视图中进行操作。

fruits_text = ", ".join( fruits )

提供fruits_text给模板进行渲染。


我可能需要其他列表(例如vegetables_text),并且可能在很多视图中使用这些列表,所以我宁愿有一个只需要更改模板的解决方案。我考虑编写自定义标签的原因之一是我可以使用Python-join绝对比for循环更优雅。
阿拉斯戴尔

8
这也不会插入最后的“和”。
Meekohi,2012年

是否在模板或视图中执行任何最佳实践?

69

这是一个超级简单的解决方案。将此代码放入comma.html:

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

现在,无论您将逗号放在何处,都应添加“ comma.html”:

{% for cat in cats %}
Kitty {{cat.name}}{% include "comma.html" %}
{% endfor %}

更新:@ user3748764为我们提供了一个更紧凑的版本,没有不赞成使用的ifequal语法:

{% if not forloop.first %}{% if forloop.last %} and {% else %}, {% endif %}{% endif %}

请注意,应在元素之前而不是之后使用它。


3
最好的解决方案,如果您需要在数组中连接多个字符串
BiAiB 2013年

添加牛津逗号所需要做的就是替换and, and
Sardathrion-反对SE滥用

1
一个稍微紧凑的版本,没有不赞成使用的eququal语法。{% if not forloop.first %}{% if forloop.last %} and {% else %}, {% endif %}{% endif %}请注意,应在元素之前而不是之后使用它。
user3748764

35

我建议使用自定义django模板过滤器,而不是自定义标签-过滤器更方便,更简单(在适当的地方,例如这里)。{{ fruits | joinby:", " }}看起来像我想要的...使用自定义joinby过滤器:

def joinby(value, arg):
    return arg.join(value)

如您所见,这本身就是简单性!


我不知道标签和过滤器之间的区别。当我查看文档时,自定义标签似乎有些令人生畏,而过滤器似乎更简单,而且在这种情况下正是我所需要的。谢谢!
阿拉斯戴尔

7
这不会插入最后的“和”。
Meekohi,2012年

2
@Meekohi,因此return arg.join(value[:-1]) + ' and ' + value[-1](对于AP样式,即,之前没有逗号and;对于“ Oxford逗号”样式,请+ arg在“ +”和“`”之前添加一个权利)。我,我更喜欢asyndeton的力量,请参阅literarydevices.net/asyndeton。无论如何,关于英语风格的这场激烈辩论都没有落在StackOverflow上–将其转到english.stackexchange.com!-)
Alex Martelli 2015年

+1返回6岁的问题以回答3岁的评论,但是当数组中只有一项时,您的过滤器会做什么?:)看起来让人很难理解。
Meekohi 2015年

1
我原来的asyndeton过滤器工作正常;新的插入'and'代码在上面的注释中编码为无条件插入'and'。在很短的时间内表现不同所需要做的value就是编写代码[[as above] if len(value)>1 else value。BTW asyndeton具有很高的可读性-亚里斯多德,莎士比亚,乔伊斯(当然还有很多人)都有效地使用了它,而我对它的偏爱首先源于我是诗人。
Alex Martelli 2015年

32

在Django模板上,您需要做的所有工作都是在每个水果之后建立一个逗号。一旦到达最后一个结果,逗号将停止。

{% if not forloop.last %}, {% endif %}

2
非常干净和容易的解决方案。
SaeX

8

这是我为解决问题而写的过滤器(不包括牛津逗号)

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])

要在模板中使用它: {{ fruits|join_with_commas }}


4

如果您想要一个“。” 在Michael Matthew Toomim的答案结尾处,然后使用:

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

4

此处的所有答案均不满足以下一项或多项要求:

  • 他们重写标准模板库中的内容(可怜!)(糟糕,最重要的答案!)
  • 它们不and用于最后一项。
  • 他们缺少连续的(牛津)逗号。
  • 他们使用负索引,不适用于Django查询集。
  • 他们通常不能正确处理字符串卫生。

这是我进入该经典的内容。一,测试:

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')

现在是代码。是的,它有点混乱,但是您会发现它使用负索引:

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)

您可以在django模板中使用以下命令:

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

2

我会', '.join(['apples', 'oranges', 'pears'])在将其作为上下文数据发送到模板之前简单地使用。

更新:

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

您将获得apples, oranges and pears输出。


那给"apples, oranges, pears"。所需的输出是"apples, oranges, and pears"
阿拉斯代尔

哦,好像我错过了。我已经更新了答案。请看一看。@Alasdair
yigidix '18

1

Django没有对此开箱即用的支持。您可以为此定义一个自定义过滤器:

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])

但是,如果您要处理的不仅仅是字符串列表,那么就必须{% for x in y %}在模板中使用显式循环。


0

如果您喜欢单线:

@register.filter
def lineup(ls): return ', '.join(ls[:-1])+' and '+ls[-1] if len(ls)>1 else ls[0]

然后在模板中:

{{ fruits|lineup }}

0

我认为最简单的解决方案可能是:

@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)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.