Jak przeprowadzić filtrowanie zapytań w szablonach django

83

Muszę wykonać filtrowane zapytanie z poziomu szablonu django, aby uzyskać zestaw obiektów odpowiadający kodowi Pythona w widoku:

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

W moim szablonie chciałbym to zrobić

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

ale po prostu nie mogę się dowiedzieć, jak napisać FILTR.

Ber
źródło

Odpowiedzi:

121

Nie możesz tego zrobić, co jest zgodne z projektem. Autorzy frameworka Django zamierzali ściśle oddzielić kod prezentacji od logiki danych. Filtrowanie modeli to logika danych, a generowanie kodu HTML to logika prezentacji.

Masz więc kilka opcji. Najłatwiej jest przeprowadzić filtrowanie, a następnie przekazać wynik do render_to_response. Możesz też napisać metodę w swoim modelu, aby móc powiedzieć {% for object in data.filtered_set %}. Na koniec możesz napisać własny tag szablonu, chociaż w tym konkretnym przypadku odradzałbym to.

Eli Courtwright
źródło
2
Hello People to teraz rok 2014! Około 6 lat później biblioteki JS poczyniły ogromny postęp i filtrowanie niezbyt dużych ilości danych powinno być raczej wykonywane po stronie klienta przy wsparciu jakiejś fajnej biblioteki skryptów Java lub przynajmniej AJAX-ed.
andilabs
1
@andi: Z pewnością zgadzam się nawet na umiarkowanie duże zbiory danych, np. nawet tysiące wierszy w tabeli. Pracując nad bazami danych z milionami wierszy, wciąż jest miejsce na filtrowanie po stronie serwera :)
Eli Courtwright,
jasne, ale chciałem tylko wskazać na ludzi, którzy mają do czynienia z kilkoma tysiącami wierszy, że przyjemne doświadczenie interakcji dla użytkownika może się zdarzyć w przeglądarce. A dla ludzi, którzy mają nawet do czynienia z ogromnymi zbiorami danych, dobrym rozwiązaniem może być podejście hybrydowe, np. Filtrowanie w kategoriach od kilku M do kilku K po stronie serwera, a inni lżejsi pracownicy wewnątrz tych kilku K po stronie klienta.
andilabs
9
@andi Z wyjątkiem sytuacji, w których filtrujesz zawartość na podstawie uprawnień, które nigdy nie zostałyby wykonane po stronie klienta. Dobrze?
39

Po prostu dodaję dodatkowy tag szablonu, taki jak ten:

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

Wtedy mogę:

{% for category in categories %}
  {% for thing in things|in_category:category %}
    {{ thing }}
  {% endfor %}
{% endfor %}
tobych
źródło
Próbuję to rozwiązanie, ale to wciąż wyzwalania błąd: 'for' statements should use the format 'for x in y': for p in r | people_in_roll_department:d. Jakieś pomysły?
Diosney
@diosney prawdopodobnie dodasz „.all” w zdaniu rzeczy. Powinno być „things.all”
Enric Mieza
12

Regularnie napotykam ten problem i często korzystam z rozwiązania „dodaj metodę”. Jednak na pewno są przypadki, w których „dodaj metodę” lub „oblicz ją w widoku” nie działa (lub nie działa dobrze). Np. Kiedy buforujesz fragmenty szablonu i potrzebujesz trochę nietrywialnych obliczeń bazy danych, aby je stworzyć. Nie chcesz wykonywać pracy z bazą danych, chyba że musisz, ale nie będziesz wiedział, czy musisz, dopóki nie zagłębisz się w logikę szablonu.

Inne możliwe rozwiązania:

  1. Użyj tagu szablonu {% expr <expression> as <var_name>%} znajdującego się pod adresem http://www.djangosnippets.org/snippets/9/ Wyrażenie to dowolne zgodne z prawem wyrażenie Pythona z kontekstem szablonu jako zakresem lokalnym.

  2. Zmień procesor szablonów. Jinja2 ( http://jinja.pocoo.org/2/ ) ma składnię prawie identyczną z językiem szablonów Django, ale z pełną mocą Pythona. Jest też szybszy. Możesz to zrobić hurtowo lub ograniczyć jego użycie do szablonów, nad którymi pracujesz, ale użyj „bezpieczniejszych” szablonów Django dla stron obsługiwanych przez projektanta.

Peter Rowell
źródło
9

Inną opcją jest to, że jeśli masz filtr, który zawsze chcesz zastosować, możesz dodać niestandardowego menedżera do danego modelu, który zawsze stosuje filtr do zwracanych wyników.

Dobrym przykładem jest Eventmodel, w którym dla 90% zapytań, które wykonujesz na modelu, będziesz chciał czegoś takiego Event.objects.filter(date__gte=now), tj. Normalnie jesteś zainteresowany Eventstym, co nadchodzi. To wyglądałoby tak:

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

A w modelu:

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

Ale znowu, to stosuje ten sam filtr do wszystkich domyślnych zapytań wykonywanych w Eventmodelu, więc nie jest tak elastyczny w przypadku niektórych technik opisanych powyżej.

mrmagooey
źródło
9

Można to rozwiązać za pomocą tagu przypisania:

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
źródło
4
Przypisanie_tag zostało usunięte w Django 2.0
Andreas Bergström
1

Dla każdego, kto szuka odpowiedzi w 2020 roku. U mnie to zadziałało.

W widokach:

 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

W szablonie:

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

W pseudokodzie:

For each in model.child_object|view_filter:filter_arg

Mam nadzieję, że to pomoże.

Krzysztof Szumko
źródło