Wie führe ich eine Abfragefilterung in Django-Vorlagen durch?

82

Ich muss eine gefilterte Abfrage aus einer Django-Vorlage heraus ausführen, um eine Reihe von Objekten zu erhalten, die dem Python-Code in einer Ansicht entsprechen:

queryset = Modelclass.objects.filter(somekey=foo)

In meiner Vorlage möchte ich tun

{% for object in data.somekey_set.FILTER %}

aber ich kann einfach nicht herausfinden, wie man FILTER schreibt.

Ber
quelle

Antworten:

119

Sie können dies nicht tun, was beabsichtigt ist. Die Autoren des Django-Frameworks beabsichtigten eine strikte Trennung von Präsentationscode und Datenlogik. Das Filtern von Modellen ist Datenlogik, und die Ausgabe von HTML ist Präsentationslogik.

Sie haben also mehrere Möglichkeiten. Am einfachsten ist es, die Filterung durchzuführen und das Ergebnis an zu übergeben render_to_response. Oder Sie könnten eine Methode in Ihr Modell schreiben, damit Sie sagen können {% for object in data.filtered_set %}. Schließlich könnten Sie Ihr eigenes Template-Tag schreiben, obwohl ich in diesem speziellen Fall davon abraten würde.

Eli Courtwright
quelle
2
Hallo Leute ist jetzt 2014! Ungefähr 6 Jahre später hat die JS-Bibliothek enorme Fortschritte gemacht, und das Filtern nicht extrem großer Datenmengen sollte eher auf der Clientseite mit Unterstützung einer netten Java-Skriptbibliothek oder zumindest AJAX-ed erfolgen.
Andilabs
1
@andi: Ich stimme sicherlich auch für mäßig große Datenmengen zu, z. B. sogar für Tausende von Zeilen in einer Tabelle. Nachdem wir an Datenbanken mit Millionen von Zeilen gearbeitet haben, gibt es definitiv noch einen Platz für serverseitige Filterung :)
Eli Courtwright
sicher, aber ich wollte nur darauf hinweisen, dass Leute, die mit oft wenigen K Zeilen zu tun haben, dass eine nette Interaktionserfahrung für den Benutzer in seinem Browser passieren kann. Und für Leute, die sogar mit riesigen Datenmengen zu tun haben, kann ein hybrider Ansatz eine gute Lösung sein, z. B. Filter in Bezug auf wenige M bis wenige K auf der Serverseite und andere leichtere Mitarbeiter in diesen wenigen K auf der Clientseite.
Andilabs
9
@andi Außer in Situationen, in denen Sie Inhalte basierend auf Berechtigungen filtern, die auf der Clientseite niemals ausgeführt würden . Richtig?
38

Ich füge einfach ein zusätzliches Vorlagen-Tag wie das folgende hinzu:

@register.filter
def in_category(things, category):
    return things.filter(category=category)

Dann kann ich tun:

{% for category in categories %}
  {% for thing in things|in_category:category %}
    {{ thing }}
  {% endfor %}
{% endfor %}
tobych
quelle
Ich versuche diese Lösung, aber sie löst immer wieder einen Fehler aus : 'for' statements should use the format 'for x in y': for p in r | people_in_roll_department:d. Irgendwelche Ideen?
Diosney
12

Ich stoße regelmäßig auf dieses Problem und verwende häufig die Lösung "Methode hinzufügen". Es gibt jedoch definitiv Fälle, in denen "Methode hinzufügen" oder "In der Ansicht berechnen" nicht funktioniert (oder nicht gut funktioniert). Zum Beispiel, wenn Sie Vorlagenfragmente zwischenspeichern und eine nicht triviale DB-Berechnung benötigen, um sie zu erstellen. Sie möchten die DB-Arbeit nicht ausführen, es sei denn, Sie müssen, aber Sie werden nicht wissen, ob Sie dies tun müssen, bis Sie tief in der Vorlagenlogik sind.

Einige andere mögliche Lösungen:

  1. Verwenden Sie das Vorlagen-Tag {% expr <Ausdruck> als <var_name>%} unter http://www.djangosnippets.org/snippets/9/. Der Ausdruck ist ein beliebiger legaler Python-Ausdruck mit dem Kontext Ihrer Vorlage als lokalem Bereich.

  2. Ändern Sie Ihren Vorlagenprozessor. Jinja2 ( http://jinja.pocoo.org/2/ ) verfügt über eine Syntax, die fast identisch mit der Django-Vorlagensprache ist, jedoch über die volle Python-Leistung verfügt. Es ist auch schneller. Sie können dies im Großhandel tun oder die Verwendung auf Vorlagen beschränken, an denen Sie arbeiten, aber die "sichereren" Vorlagen von Django für vom Designer verwaltete Seiten verwenden.

Peter Rowell
quelle
9

Die andere Option besteht darin, dass Sie, wenn Sie einen Filter haben, den Sie immer anwenden möchten, dem betreffenden Modell einen benutzerdefinierten Manager hinzufügen , der den Filter immer auf die zurückgegebenen Ergebnisse anwendet.

Ein gutes Beispiel hierfür ist ein EventModell, bei dem Sie für 90% der Abfragen, die Sie an dem Modell durchführen, so etwas wie möchten Event.objects.filter(date__gte=now), dh Sie sind normalerweise daran interessiert Events. Das würde so aussehen:

class EventManager(models.Manager):
    def get_query_set(self):
        now = datetime.now()
        return super(EventManager,self).get_query_set().filter(date__gte=now)

Und im Modell:

class Event(models.Model):
    ...
    objects = EventManager()

Dies wendet jedoch wiederum denselben Filter auf alle Standardabfragen an, die für das EventModell ausgeführt werden, und ist daher bei einigen der oben beschriebenen Techniken nicht so flexibel.

mrmagooey
quelle
9

Dies kann mit einem Zuweisungs-Tag gelöst werden:

from django import template

register = template.Library()

@register.assignment_tag
def query(qs, **kwargs):
    """ template tag which allows queryset filtering. Usage:
          {% query books author=author as mybooks %}
          {% for book in mybooks %}
            ...
          {% endfor %}
    """
    return qs.filter(**kwargs)
chrisv
quelle
3
Zuweisung_Tag wurde entfernt in Django 2.0
Andreas Bergström
1

Für alle, die 2020 nach einer Antwort suchen. Das hat bei mir funktioniert.

In Ansichten:

 class InstancesView(generic.ListView):
        model = AlarmInstance
        context_object_name = 'settings_context'
        queryset = Group.objects.all()
        template_name = 'insta_list.html'

        @register.filter
        def filter_unknown(self, aVal):
            result = aVal.filter(is_known=False)
            return result

        @register.filter
        def filter_known(self, aVal):
            result = aVal.filter(is_known=True)
            return result

In Vorlage:

{% for instance in alarm.qar_alarm_instances|filter_unknown:alarm.qar_alarm_instances %}

Im Pseudocode:

For each in model.child_object|view_filter:filter_arg

Hoffentlich hilft das.

Krzysztof Szumko
quelle