Die Django-Vorlage kann das Standarddict nicht wiederholen

70
import collections

data = [
  {'firstname': 'John', 'lastname': 'Smith'}, 
  {'firstname': 'Samantha', 'lastname': 'Smith'}, 
  {'firstname': 'shawn', 'lastname': 'Spencer'}, 
]

new_data = collections.defaultdict(list)

for d in data:
    new_data[d['lastname']].append(d['firstname'])

print new_data

Hier ist die Ausgabe:

defaultdict(<type 'list'>, {'Smith': ['John', 'Samantha'], 'Spencer': ['shawn']})

und hier ist die Vorlage:

{% for lastname, firstname in data.items %}
  <h1> {{ lastname }} </h1>
  <p> {{ firstname|join:", " }} </p>
{% endfor %}

Aber die Schleife in meiner Vorlage funktioniert nicht. Es zeigt sich nichts. Es gibt mir nicht einmal einen Fehler. Wie kann ich das beheben? Es soll den Nachnamen zusammen mit dem Vornamen anzeigen, ungefähr so:

<h1> Smith </h1>
<p> John, Samantha </p>

<h1> Spencer </h1>
<p> shawn </p>
user216171
quelle
Sie haben den Code nicht angezeigt, mit dem das Wörterbuch in den Kontext für die Vorlage eingefügt wird. Bist du sicher, dass das richtig passiert?
Ned Batchelder
Ja, alles andere wird außerhalb der Schleife korrekt gerendert.
user216171

Antworten:

47

Versuchen:

dict(new_data)

und in Python 2 ist es besser, iteritemsanstatt zu verwenden items:)

virhilo
quelle
2
Ich kann nicht glauben, dass so etwas Einfaches funktioniert hat. Vielen Dank!
user216171
7
Es wurde tatsächlich als Fehler gegen django eingereicht: code.djangoproject.com/ticket/16335, aber es stellte sich heraus, dass die einzig gute Lösung darin bestand, es in ein Diktat umzuwandeln , wie es jetzt in den Dokumenten gezeigt wird und im Änderungssatz
Stefano
91

Sie können das Kopieren in ein neues Diktat vermeiden, indem Sie die Standardfunktion von defaultdict deaktivieren, sobald Sie neue Werte eingefügt haben:

new_data.default_factory = None

Erläuterung

Das Algorithmus zur variablen Auflösung von Vorlagen in Django versucht new_data.itemsals new_data['items']erstes aufzulösen , der bei Verwendung von defaultdict (Liste) in eine leere Liste aufgelöst wird .

Um die Standardeinstellung für eine leere Liste zu deaktivieren und Django auszuschalten und new_data['items']die Auflösungsversuche bis zum Aufruf fortzusetzen, kann new_data.items()das Attribut default_factory von defaultdict auf None festgelegt werden .

Sébastien Trottier
quelle
6
+1 - Dies ist viel effizienter als die ausgewählte Antwort, insbesondere bei einem großen Wörterbuchsatz.
Keithhackbarth
Tolle Antwort, Erklärung hat viel zum Verständnis des zugrunde liegenden Problems beigetragen!
Blackeagle52
Ich könnte hinzufügen, dass ein {% für k, v in detauldictvar%} es Ihnen ermöglicht, das Element des defaultdict zu iterieren, auch ohne die default_factory auf None zu setzen. Mit der Erklärung von Sebastien kann ich jetzt verstehen, warum :-)
RobM
1

Da das "Problem" noch Jahre später besteht und der Funktionsweise von Django-Vorlagen eigen ist, schreibe ich lieber eine neue Antwort mit den vollständigen Details, warum dieses Verhalten unverändert bleibt.

So beheben Sie den Fehler

Zunächst besteht die Lösung darin, das defaultdictin a dictumzuwandeln, bevor es an den Vorlagenkontext übergeben wird:

context = {
    'data': dict(new_data)
}

Sie sollten nicht verwenden defaultdict in Django Objekte im Vorlagenkontext verwenden.

Aber wieso?

Der Grund für diesen "Fehler" wird in der folgenden Django-Ausgabe Nr. 16335 beschrieben :

In der Tat läuft es darauf hinaus, dass die Vorlagensprache dieselbe Syntax für die Suche nach Wörterbüchern und Attributen verwendet.

... und aus den Dokumenten :

Wörterbuchsuche, Attributsuche und Listenindexsuche werden mit einer Punktnotation implementiert. [...] Wenn eine Variable in eine aufrufbare Variable aufgelöst wird, ruft das Vorlagensystem sie ohne Argumente auf und verwendet ihr Ergebnis anstelle der aufrufbaren.

Wenn Django Ihren Vorlagenausdruck auflöst , wird es zuerst versuchtdata['items'] . ABER dies ist ein gültiger Ausdruck, der automatisch einen neuen Eintrag itemsin Ihrem Standarddikt erstellt data, mit einer leeren Liste initialisiert (im Fall des ursprünglichen Autors) und die erstellte Liste zurückgibt (leer).

Die beabsichtigte Aktion wäre, die Methode itemsohne Argumente der Instanz aufzurufen data(kurz :) data.items(), aber da dies data['items']ein gültiger Ausdruck war, stoppt Django dort und erhält die gerade erstellte leere Liste.

Wenn Sie den gleichen Code versuchen, aber mit data = defaultdict(int), erhalten Sie einen TypeError: 'int' object is not iterable, da Django nicht in der Lage ist, über den Wert "0" zu iterieren, der durch die Erstellung des neuen Eintrags von zurückgegeben wird defaultdict.

Maxime Lorant
quelle