Ordinamento degli elementi correlati in un modello Django


88

È possibile ordinare una serie di elementi correlati in un modello DJango?

Ovvero: questo codice (con tag HTML omessi per chiarezza):

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

mostra quasi esattamente quello che voglio. L'unica cosa che voglio cambiare è l'elenco dei partecipanti da ordinare per cognome. Ho provato a dire qualcosa del genere:

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

Purtroppo, la sintassi di cui sopra non funziona (produce un elenco vuoto) e nemmeno qualsiasi altra variazione a cui ho pensato (molti errori di sintassi segnalati, ma nessuna gioia).

Potrei, ovviamente, produrre una sorta di serie di elenchi di partecipanti ordinati a mio avviso, ma questa è una soluzione brutta e fragile (e ho menzionato brutta).

Inutile dirlo, ma lo dirò comunque, ho esaminato i documenti in linea e cercato Stack Overflow e gli archivi di django-user senza trovare nulla di utile (ah, se solo un set di query fosse un dizionario, dictsort farebbe il lavoro, ma non lo è e non lo è)

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

Modificato per aggiungere ulteriori pensieri dopo aver accettato la risposta di Tawmas.


Tawmas ha affrontato il problema esattamente come l'ho presentato, sebbene la soluzione non fosse quella che mi aspettavo. Di conseguenza ho imparato una tecnica utile che può essere utilizzata anche in altre situazioni.

La risposta di Tom proponeva un approccio che avevo già menzionato nel mio PO e rifiutato provvisoriamente come "brutto".

Il "brutto" è stata una reazione istintiva, e volevo chiarire cosa c'era di sbagliato in esso. In tal modo mi sono reso conto che il motivo per cui era un approccio brutto era perché ero bloccato all'idea di passare un set di query al modello da renderizzare. Se allento questo requisito, c'è un approccio sgradevole che dovrebbe funzionare.

Non l'ho ancora provato, ma supponiamo che invece di passare il set di query, il codice di visualizzazione abbia iterato attraverso il set di query producendo un elenco di eventi, quindi decorato ogni evento con un set di query per i partecipanti corrispondenti che È STATO ordinato (o filtrato, o qualsiasi altra cosa) nel modo desiderato. Qualcosa di simile:

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)

Ora il modello diventa:

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

Lo svantaggio è che la vista deve "attualizzare" tutti gli eventi contemporaneamente, il che potrebbe essere un problema se ci fosse un gran numero di eventi. Ovviamente si potrebbe aggiungere l'impaginazione, ma questo complica notevolmente la vista.

Il vantaggio è che il codice "prepara i dati da visualizzare" è nella vista a cui appartiene, lasciando che il modello si concentri sulla formattazione dei dati forniti dalla vista per la visualizzazione. Questo è giusto e corretto.

Quindi il mio piano è quello di utilizzare la tecnica di Tawmas per i tavoli grandi e la tecnica sopra per i tavoli piccoli, con la definizione di grande e piccolo lasciata al lettore (ghigno).

Risposte:


137

Devi specificare l'ordine nel modello del partecipante, in questo modo. Ad esempio (supponendo che la classe del modello si chiami Partecipante):

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

Vedere il manuale per ulteriori riferimenti.

MODIFICA . Un'altra soluzione è aggiungere una proprietà al tuo modello di evento, a cui puoi accedere dal tuo modello:

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

Potresti definirne di più in base alle tue esigenze ...


Grazie per il tuo commento, ma funziona solo se voglio rendere l'ordine di visualizzazione una proprietà permanente del partecipante, cosa che non faccio. Ad esempio, potrei voler visualizzare i partecipanti ordinati in base alla data in cui è stata ricevuta la registrazione, in modo da sapere quali partecipanti devono essere informati che non c'è spazio per loro.
Dale Wilson,

Ho aggiunto una soluzione alternativa per te. Dovrebbe darti la flessibilità di cui hai bisogno.
tawmas

@ Mark infatti lo fa. Per quanto ho capito @propertyè eccessivo anche qui non ci sono getter o setter coinvolti: stackoverflow.com/questions/1554546/...
Rick Westera

Sebbene non sia strettamente necessario per il modello, trovo che @property qui consente un accesso più pulito dal codice dell'applicazione , sebbene sia prevista una piccola penalizzazione delle prestazioni (<30 ns sul mio laptop). Questa è ovviamente una questione di stile e altamente soggettiva.
tawmas

Nel caso in cui desideri ordinare il set rispetto a due attributi, questo è il comando: class Meta: ordering = ['last_name', 'first_name']
Tms91

140

Puoi utilizzare il filtro modello dictsort https://docs.djangoproject.com/en/dev/ref/templates/builtins/#std:templatefilter-dictsort

Questo dovrebbe funzionare:

{% for event in eventsCollection %}
   {{ event.location }}
   {% for attendee in event.attendee_set.all|dictsort:"last_name" %}
     {{ attendee.first_name }} {{ attendee.last_name }}
   {% endfor %}
 {% endfor %}

23
Grande ! E per chi se lo chiede, c'è anche dictsortreversed: docs.djangoproject.com/en/dev/ref/templates/builtins/…
Mickaël

1
Per citare dal mio post originale: ah, se solo un set di query fosse un dizionario dictsort farebbe il lavoro, ma non lo è e non lo è.
Dale Wilson

Penso che questo dovrebbe essere meno nella pratica e lasciare che il modello gestisca l'ordinamento prima di recuperare i record.
acpmasquerade

1
@DaleWilson, ho effettivamente dictsortfunzionato correttamente su un codice quasi esattamente come il tuo. È interessante notare che sembra funzionare bene sui set di query.
mlissner

2
E solo per maggiore chiarezza, gli spazi contano: {% for attendee in event.attendee_set.all|dictsort:"last_name" %}ordina i partecipanti, ma {% for attendee in event.attendee_set.all | dictsort:"last_name" %}tenta di ordinare l'output del ciclo for e interrompe il file for.
mattsl

5

Una soluzione è creare un templatag personalizzato:

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

usa in questo modo:

{% for image in instance.folder.files|order_by:"original_filename" %}
   ...
{% endfor %}

0

il raggruppamento dovrebbe essere in grado di fare quello che vuoi, ma c'è una ragione per cui non puoi ordinarli nel modo in cui vuoi che siano visualizzati?


Per ordinarli nella vista, avrei bisogno di scorrere gli eventi e per ogni evento creare un contenitore di una sorta di partecipanti correlati a quell'evento in ordine correttamente ordinato, quindi passare l'intera raccolta di contenitori al modello in una forma che consentirebbe al modello di trovare la raccolta appropriata di partecipanti durante l' iterazione degli eventi. Come ho detto, potrebbe essere fatto, ma non è una buona soluzione. Richiede troppi accoppiamenti tra la vista e il modello, iterazioni parallele, ecc. Speravo in una soluzione migliore a quello che dovrebbe essere un problema comune.
Dale Wilson

Ho guardato il raggruppamento e non sembrava fare quello che volevo. in particolare la documentazione dice: [quote] Nota che {% regroup%} non ordina il suo input! Il nostro esempio si basa sul fatto che l'elenco delle persone è stato ordinato in primo luogo per sesso. [endquote]
Dale Wilson il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.