Durch Kommas getrennte Listen in Django-Vorlagen

75

Wenn fruitsist die Liste ['apples', 'oranges', 'pears'],

Gibt es eine schnelle Möglichkeit, mit Django-Vorlagen-Tags "Äpfel, Orangen und Birnen" herzustellen?

Ich weiß, dass es nicht schwierig ist, dies mit einer Schleife und {% if counter.last %}Anweisungen zu tun , aber da ich dies wiederholt verwenden werde, denke ich, dass ich lernen muss, wie man benutzerdefinierte Anweisungen schreibtStichworte Filter, und ich möchte das Rad nicht neu erfinden, wenn es bereits getan wurde.

Als Erweiterung sind meine Versuche, das Oxford-Komma fallen zu lassen (dh "Äpfel, Orangen und Birnen" zurückzugeben), noch chaotischer.

Alasdair
quelle
4
Warum verwenden Sie nicht das vorhandene Join-Vorlagen-Tag?
S.Lott
1
@ S.Lott: Ich habe das Join-Vorlagen-Tag nicht entdeckt, als ich die Liste auf der Dokumentenseite durchgesehen habe. Hoppla. Der nächste Schritt besteht jedoch darin, jedes Element in der Liste in einen Hyperlink zu verpacken, für den ich einen Filter schreiben muss.
Alasdair
Wenn Sie Links zu Ihren Django-URLs verwenden, müssen Sie das {% url %}Tag verwenden. Die {% for %}Schleife sieht plötzlich viel ansprechender aus. "Wiederholt" bedeutet häufig, dass Ihre Vorlagen {% include %}gemeinsame Funktionen benötigen .
S.Lott

Antworten:

140

Erste Wahl: Verwenden Sie das vorhandene Tag für Verknüpfungsvorlagen.

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

Hier ist ihr Beispiel

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

Zweite Wahl: Mach es in der Ansicht.

fruits_text = ", ".join( fruits )

Stellen Sie fruits_textdie Vorlage zum Rendern bereit.

S.Lott
quelle
Ich benötige möglicherweise andere Listen (z. B. vegetables_text) und verwende diese Listen möglicherweise in vielen Ansichten. Daher hätte ich lieber eine Lösung, bei der ich nur die Vorlagen ändern muss. Einer der Gründe, warum ich über das Schreiben eines benutzerdefinierten Tags nachgedacht habe, ist, dass ich Python verwenden kann - es joinist definitiv eleganter als für Schleifen.
Alasdair
8
Dies fügt auch nicht das letzte "und" ein.
Meekohi
Gibt es eine bewährte Methode, um dies in der Vorlage oder in der Ansicht zu tun?
Rikki
69

Hier ist eine super einfache Lösung. Fügen Sie diesen Code in comma.html ein:

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

Und jetzt, wo immer Sie das Komma setzen würden, fügen Sie stattdessen "comma.html" hinzu:

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

Update: @ user3748764 gibt uns eine etwas kompaktere Version ohne die veraltete ifequal-Syntax:

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

Beachten Sie, dass es vor dem Element und nicht nach dem Element verwendet werden sollte.

Michael Matthew Toomim
quelle
3
Die beste Lösung, wenn Sie mehr als die Zeichenfolgen in einem Array verbinden müssen
BiAiB
Alles, was Sie tun müssen, um das Oxford-Komma hinzuzufügen, ist das Ersetzen anddurch , and.
Sardathrion - gegen SE Missbrauch
1
Eine etwas kompaktere Version ohne die veraltete ifequal-Syntax. {% if not forloop.first %}{% if forloop.last %} and {% else %}, {% endif %}{% endif %}Beachten Sie, dass es vor dem Element und nicht nach dem Element verwendet werden sollte.
user3748764
35

Ich würde eine benutzerdefinierte vorschlagen django Templating Filter anstelle eines benutzerdefinierten Tag - Filter ist handlicher und einfacher (gegebenenfalls wie hier). {{ fruits | joinby:", " }}sieht aus wie das, was ich für diesen Zweck haben möchte ... mit einem benutzerdefinierten joinbyFilter:

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

Was, wie Sie sehen, die Einfachheit selbst ist!

Alex Martelli
quelle
Mir war der Unterschied zwischen Tags und Filtern nicht bewusst. Während die benutzerdefinierten Tags in der Dokumentation etwas entmutigend wirken, scheinen Filter einfacher zu sein und genau das, was ich in diesem Fall benötige. Vielen Dank!
Alasdair
7
Das letzte "und" wird nicht eingefügt.
Meekohi
2
@Meekohi, also return arg.join(value[:-1]) + ' and ' + value[-1](für den AP-Stil, dh kein Komma zuvor and; für den "Oxford-Komma" -Stil ein + argRecht vor dem "+" und "" hinzufügen). Ich bevorzuge die Stärke des Asyndetons pro literarisches Gerät.net / asyndeton . Und keine dieser schönen Debatten über den englischen Stil gehört sowieso zu StackOverflow - bringen Sie es zu english.stackexchange.com! -)
Alex Martelli
+1 für die Rückkehr zu einer 6 Jahre alten Frage, um einen 3 Jahre alten Kommentar zu beantworten. Aber was macht Ihr Filter, wenn nur ein Element im Array vorhanden ist? :) Es scheint nicht so einfach zu sein, Dinge menschlich lesbar zu machen.
Meekohi
1
Mein ursprünglicher Asyndeton-Filter funktioniert einwandfrei. Die neuen Einfügungen 'and'sind im obigen Kommentar zum bedingungslosen Einfügen codiert 'and'. Alles, was man braucht, um sich für kurze Zeit anders zu verhalten, valueist Code [[as above] if len(value)>1 else value. Übrigens ist Asyndeton sehr gut lesbar - Aristoteles, Shakespeare, Joyce (natürlich unter vielen anderen) haben es alle effektiv genutzt, und meine Vorliebe dafür beruht in erster Linie darauf, dass ich Dichter bin.
Alex Martelli
32

Auf der Django-Vorlage ist dies alles, was Sie tun müssen, um nach jeder Frucht ein Komma zu setzen. Das Komma hört auf, sobald es die letzte Frucht erreicht hat.

{% if not forloop.last %}, {% endif %}
Maschinenpistole
quelle
2
Sehr saubere und einfache Lösung.
SaeX
8

Hier ist der Filter, den ich geschrieben habe, um mein Problem zu lösen (er enthält kein Oxford-Komma).

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

So verwenden Sie es in der Vorlage: {{ fruits|join_with_commas }}

Alasdair
quelle
4

Wenn Sie ein '.' Verwenden Sie am Ende der Antwort von Michael Matthew Toomim:

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

Alle Antworten hier schlagen eine oder mehrere der folgenden fehl:

  • Sie schreiben etwas (schlecht!) Um, das in der Standardvorlagenbibliothek enthalten ist (ack, top answer!)
  • Sie werden nicht andfür den letzten Artikel verwendet.
  • Ihnen fehlt ein serielles (Oxford) Komma.
  • Sie verwenden eine negative Indizierung, die für Django-Abfragesätze nicht funktioniert.
  • Sie handhaben normalerweise die Saitenhygiene nicht richtig.

Hier ist mein Eintrag in diesen Kanon. Zunächst die 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')

Und jetzt der Code. Ja, es wird ein bisschen chaotisch, aber Sie werden sehen, dass keine negative Indizierung verwendet wird:

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)

Sie können dies in einer Django-Vorlage verwenden mit:

{% load my_filters %}
{{ items|oxford_comma }}
mlissner
quelle
2

Ich würde es einfach verwenden, ', '.join(['apples', 'oranges', 'pears'])bevor ich es als Kontextdaten an die Vorlage sende.

AKTUALISIEREN:

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

Sie erhalten eine apples, oranges and pearsAusgabe.

Yigidix
quelle
Das gibt "apples, oranges, pears" . Die erforderliche Ausgabe ist "apples, oranges, and pears".
Alasdair
Oh, sieht so aus, als hätte ich es verpasst. Ich habe meine Antwort aktualisiert. Guck dir das mal bitte an. @Alasdair
Yigidix
1

Django hat keine Unterstützung für diesen Out-of-the-Box. Sie können hierfür einen benutzerdefinierten Filter definieren:

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

Wenn Sie sich jedoch mit etwas Komplexerem als nur Listen von Zeichenfolgen befassen möchten, müssen Sie {% for x in y %}in Ihrer Vorlage eine explizite Schleife verwenden.

Wilfred Hughes
quelle
0

Wenn Sie Einzeiler mögen:

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

und dann in der Vorlage:

{{ fruits|lineup }}
Fmalina
quelle
0

Ich denke, die einfachste Lösung könnte sein:

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

mannysz
quelle