Списки, розділені комами в шаблонах django


75

Якщо fruitsє список ['apples', 'oranges', 'pears'],

чи існує швидкий спосіб використання тегів шаблонів django для отримання "яблук, апельсинів та груш"?

Я знаю, що це не складно зробити за допомогою циклу та {% if counter.last %}операторів, але оскільки я буду використовувати це неодноразово, я думаю, що мені доведеться навчитися писати власнітеги фільтри, і я не хочу винаходити колесо, якщо це вже було зроблено.

Як продовження, мої спроби кинути оксфордську кому (тобто повернути "яблука, апельсини та груші") ще більш заплутані.


4
Чому ви не використовуєте існуючий тег шаблону об’єднання?
S.Lott

1
@ S.Lott: Я не помітив тегу шаблону приєднання, коли переглядав список на сторінці документів. На жаль Сказавши це, наступним етапом є обгортання кожного пункту у списку гіперпосиланням, для чого, я думаю, мені потрібно буде написати фільтр.
Alasdair,

Якщо ви використовуєте посилання на свої URL-адреси Django, вам потрібно буде використовувати {% url %}тег. {% for %}Петля несподівано виглядає набагато більш привабливим. "Неодноразово" часто означає, що ваші шаблони повинні мати {% include %}загальні риси.
S.Lott

Відповіді:


140

Перший вибір: використовувати існуючий тег шаблону об’єднання.

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

Ось їх приклад

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

Другий вибір: зробити це на вигляд.

fruits_text = ", ".join( fruits )

Надайте fruits_textу шаблоні для візуалізації.


Мені можуть знадобитися інші списки (наприклад vegetables_text), і я можу використовувати ці списки у безлічі переглядів, тому я волів би рішення, яке вимагає від мене лише зміни шаблонів. Однією з причин, чому я думав про написання власного тегу, є те, що я можу використовувати Python - joinце, безумовно, більш елегантно, ніж для циклів.
Alasdair

8
Це також не вставляє остаточне "і".
Meekohi

Чи існує найкраща практика щодо того, чи робити це у шаблоні чи у поданні?
Ріккі

70

Ось супер просте рішення. Помістіть цей код у запіс 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

Все, що вам потрібно зробити, щоб додати оксфордську кому, - це замінити andна , and.
Sardathrion - проти зловживання SE

1
Трохи більш компактна версія без застарілого синтаксису ifequal. {% if not forloop.first %}{% if forloop.last %} and {% else %}, {% endif %}{% endif %}Зверніть увагу, що його слід використовувати перед елементом, а не після.
користувач3748764

35

Я б запропонував користувальницький фільтр шаблонізації django, а не власний тег - фільтр зручніший та простіший (де це доречно, як тут). {{ fruits | joinby:", " }}схоже на те, що я хотів би мати з цією метою ... за допомогою спеціального joinbyфільтра:

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

що, як бачите, - це простота!


Я не знав про різницю між тегами та фільтрами. У той час як користувацькі теги здаються трохи лякаючими, коли я переглядаю документацію, фільтри здаються простішими, і саме те, що мені потрібно в цьому випадку. Дякую!
Alasdair

7
Це не вставляє остаточне "і".
Meekohi

2
@Meekohi, отже return arg.join(value[:-1]) + ' and ' + value[-1](для стилю АР, тобто перед комою немає and; для стилю "оксфордська кома" додайте + argправо перед знаками "+" і "). Мене, я вважаю за краще силу бессоюзіе, за literarydevices.net/asyndeton . І жодна з цих чудових дискусій щодо англійського стилю так і не належить StackOverflow - перенесіть це на english.stackexchange.com! -)
Алекс Мартеллі,

+1 за повернення до 6-річного запитання, щоб відповісти на 3-річний коментар, але що робить ваш фільтр, коли в масиві є лише один елемент? :) Зробити речі зрозумілими для людини не так просто, здається.
Meekohi

1
Мій оригінальний асиндетонний фільтр чудово працює; нові, що вставляються 'and', кодуються у наведеному коментарі, щоб безумовно вставити 'and'. Все, що потрібно, щоб поводитися по-різному дуже коротко, value- це кодування [[as above] if len(value)>1 else value. І до речі, асиндетон ВИСОКО читається людиною - Арістотель, Шекспір, Джойс (серед багатьох інших, звичайно) - усі ефективно використовували його, і моя перевага до нього пов’язана з моїм поетом, насамперед.
Алекс Мартеллі,

32

На шаблоні Django це все, що вам потрібно зробити для встановлення коми після кожного фрукта. Кома зупиниться, коли досягне останнього плоду.

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

2
Дуже чисте та просте рішення.
SaeX

9

Ось фільтр, який я написав для вирішення своєї проблеми (він не містить оксфордської коми)

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

Якщо ви хочете "." в кінці відповіді Майкла Метью Тооміма, а потім використовуйте:

{% 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,

О, схоже, я це пропустив. Я оновив свою відповідь. Будь ласка, подивіться. @Alasdair
yigidix

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 %}цикл у своєму шаблоні.


1

Якщо ви любите однокласники:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.