Sortowanie powiązanych elementów w szablonie Django

88

Czy można posortować zestaw powiązanych elementów w szablonie DJango?

To znaczy: ten kod (z pominiętymi tagami HTML dla przejrzystości):

{% for event in eventsCollection %}
   {{ event.location }}
   {% for attendee in event.attendee_set.all %}
     {{ attendee.first_name }} {{ attendee.last_name }}
   {% endfor %}
 {% endfor %}

wyświetla prawie dokładnie to, czego chcę. Jedyne, co chcę zmienić, to lista uczestników do posortowania według nazwiska. Próbowałem powiedzieć coś takiego:

{% for event in events %}
   {{ event.location }}
   {% for attendee in event.attendee_set.order_by__last_name %}
     {{ attendee.first_name }} {{ attendee.last_name }}
   {% endfor %}
 {% endfor %}

Niestety, powyższa składnia nie działa (tworzy pustą listę), podobnie jak żadna inna odmiana, o której myślałem (zgłoszono wiele błędów składniowych, ale bez radości).

Moim zdaniem mógłbym oczywiście stworzyć szereg posortowanych list uczestników, ale jest to brzydkie i kruche (i czy wspomniałem brzydkie) rozwiązanie.

Nie trzeba mówić, ale i tak powiem, przejrzałem dokumenty on-line i przeszukałem Stack Overflow oraz archiwa django-user, nie znajdując niczego pomocnego (ach, gdyby tylko zestaw zapytań był słownikiem, to dictsort zrobiłby praca, ale nie jest i nie działa)

==============================================

Zmieniono, aby dodać dodatkowe myśli po zaakceptowaniu odpowiedzi Tawmas.


Tawmas odniósł się do problemu dokładnie tak, jak go przedstawiłem - chociaż rozwiązanie nie było tym, czego się spodziewałem. W rezultacie nauczyłem się użytecznej techniki, którą można zastosować również w innych sytuacjach.

Odpowiedź Toma zaproponowała podejście, o którym wspomniałem już w moim OP i wstępnie odrzuciłem je jako „brzydkie”.

„Brzydka” była intuicyjną reakcją i chciałem wyjaśnić, co jest z nią nie tak. Robiąc to, zdałem sobie sprawę, że powodem, dla którego było to brzydkie podejście, było to, że ogarnął mnie pomysł przekazania zestawu zapytań do szablonu, który ma być renderowany. Jeśli złagodzę ten wymóg, istnieje brzydkie podejście, które powinno działać.

Nie próbowałem tego jeszcze, ale załóżmy, że zamiast przekazując queryset, kod widok powtórzyć przez zestaw zapytań produkujących listę zdarzeń, a następnie zdobione każde zdarzenie z zestawem zapytań do odpowiednich uczestników, który WAS posortowanych (lub sączy lub cokolwiek) w pożądany sposób. Coś takiego:

eventCollection = []   
events = Event.object.[filtered and sorted to taste]
for event in events:
   event.attendee_list = event.attendee_set.[filtered and sorted to taste]
   eventCollection.append(event)

Teraz szablon staje się:

{% for event in events %}
   {{ event.location }}
   {% for attendee in event.attendee_list %}
     {{ attendee.first_name }} {{ attendee.last_name }}
   {% endfor %}
 {% endfor %}

Wadą jest to, że pogląd musi „aktualizować” wszystkie zdarzenia naraz, co mogłoby stanowić problem, gdyby było ich dużo. Oczywiście można dodać paginację, ale to znacznie komplikuje widok.

Plusem jest to, że kod „przygotuj dane do wyświetlenia” jest w widoku, do którego należy, pozwalając szablonowi skupić się na formatowaniu danych dostarczonych przez widok do wyświetlenia. To jest słuszne i właściwe.

Dlatego planuję zastosować technikę Tawmasa do dużych stołów i powyższą technikę do małych stołów, z definicją dużego i małego pozostawionego czytelnikowi (uśmiech).

Dale Wilson
źródło

Odpowiedzi:

137

Musisz określić kolejność w modelu uczestnika, w ten sposób. Na przykład (zakładając, że klasa modelu nosi nazwę Uczestnik):

class Attendee(models.Model):
    class Meta:
        ordering = ['last_name']

Więcej informacji można znaleźć w instrukcji .

EDYTUJ . Innym rozwiązaniem jest dodanie właściwości do modelu zdarzenia, do której można uzyskać dostęp z poziomu szablonu:

class Event(models.Model):
# ...
@property
def sorted_attendee_set(self):
    return self.attendee_set.order_by('last_name')

Możesz zdefiniować ich więcej, gdy ich potrzebujesz ...

tawmas
źródło
Dziękuję za komentarz, ale działa to tylko wtedy, gdy chcę, aby zamówienie wyświetlania było stałą własnością uczestnika, czego nie robię. Mógłbym na przykład chcieć wyświetlić uczestników posortowanych według daty otrzymania ich rejestracji, aby wiedzieć, którzy z nich powinni zostać powiadomieni, że nie ma dla nich miejsca.
Dale Wilson
Dodałem dla Ciebie alternatywne rozwiązanie. Powinien zapewniać elastyczność, której potrzebujesz.
tawmas
@Mark Rzeczywiście tak. O ile rozumiem, @propertyjest to przesada, ponieważ nie ma tu
metod pobierających
Chociaż nie jest to ściśle potrzebne dla szablonu, stwierdzam, że @właściwość tutaj zapewnia czystszy dostęp z kodu aplikacji , chociaż wiąże się to z niewielkim spadkiem wydajności (<30 ns na moim laptopie). Jest to oczywiście kwestia stylu i wysoce subiektywna.
tawmas
W przypadku, gdy chcesz posortować zestaw pod kątem dwóch atrybutów, to jest polecenie: class Meta: ordering = ['last_name', 'first_name']
Tms91
140

Możesz użyć filtru szablonu dictsort https://docs.djangoproject.com/en/dev/ref/templates/builtins/#std:templatefilter-dictsort

To powinno działać:

{% for event in eventsCollection %}
   {{ event.location }}
   {% for attendee in event.attendee_set.all|dictsort:"last_name" %}
     {{ attendee.first_name }} {{ attendee.last_name }}
   {% endfor %}
 {% endfor %}
tariwan
źródło
23
Świetny ! A dla tych, którzy się zastanawiają, jest też dictsortreversed: docs.djangoproject.com/en/dev/ref/templates/builtins/ ...
Mickaël
1
Cytując z mojego oryginalnego postu: ah, gdyby tylko zbiór zapytań był słownikowy, dyktafon wykonałby zadanie, ale tak nie jest i nie działa.
Dale Wilson,
Myślę, że w praktyce powinno to być mniej i niech model zajmie się sortowaniem przed pobraniem rekordów.
acpmasquerade
1
@DaleWilson, właściwie dictsortpracowałem nad kodem prawie dokładnie takim, jak twój. Co ciekawe, wydaje się, że działa dobrze na zestawach zapytań.
mlissner
2
Dla większej przejrzystości spacje mają znaczenie: {% for attendee in event.attendee_set.all|dictsort:"last_name" %}sortuje uczestników, ale {% for attendee in event.attendee_set.all | dictsort:"last_name" %}próbuje posortować wyjście pętli for i przerywa for.
mattsl
5

Jednym z rozwiązań jest utworzenie niestandardowego szablonu:

@register.filter
def order_by(queryset, args):
    args = [x.strip() for x in args.split(',')]
    return queryset.order_by(*args)

użyj w ten sposób:

{% for image in instance.folder.files|order_by:"original_filename" %}
   ...
{% endfor %}
matinfo
źródło
0

przegrupowanie powinno być w stanie zrobić, co chcesz, ale czy istnieje powód, dla którego nie możesz zamówić ich tak, jak chcesz, z powrotem w widoku?

Tomek
źródło
Aby uporządkować je w widoku, musiałbym iterować po wydarzeniach i dla każdego wydarzenia utworzyć kontener jakiegoś rodzaju uczestników związanych z tym wydarzeniem w odpowiednio posortowanej kolejności, a następnie przekazać całą kolekcję kontenerów do szablonu w postaci, która pozwoliłby szablon znaleźć odpowiedni zbiór uczestników jak to powtórzyć poprzez wydarzenia. Jak powiedziałem, można to zrobić, ale to nie jest dobre rozwiązanie. Wymaga to zbyt dużego sprzężenia między widokiem a szablonem, równoległych iteracji itp. Liczyłem na lepsze rozwiązanie tego, co powinno być częstym problemem.
Dale Wilson
Spojrzałem na przegrupowanie i wyglądało na to, że nie zrobił tego, co chciałem. w szczególności dokumentacja mówi: [quote] Zauważ, że {% regroup%} nie porządkuje swoich danych wejściowych! Nasz przykład opiera się na fakcie, że lista osób została uporządkowana przede wszystkim według płci. [endquote]
Dale Wilson