Tri des éléments associés dans un modèle Django

87

Est-il possible de trier un ensemble d'éléments associés dans un modèle DJango?

C'est-à-dire: ce code (avec les balises HTML omises pour plus de clarté):

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

affiche presque exactement ce que je veux. La seule chose que je souhaite modifier est la liste des participants à trier par nom de famille. J'ai essayé de dire quelque chose comme ça:

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

Hélas, la syntaxe ci-dessus ne fonctionne pas (elle produit une liste vide) et aucune autre variante à laquelle j'ai pensé (beaucoup d'erreurs de syntaxe signalées, mais pas de joie).

Je pourrais, bien sûr, produire une sorte de tableau de listes de participants triées à mon avis, mais c'est une solution laide et fragile (et ai-je mentionné laide).

Inutile de dire, mais je le dirai quand même, j'ai parcouru la documentation en ligne et recherché Stack Overflow et les archives de django-user sans rien trouver d'utile (ah, si seulement un ensemble de requêtes était un dictionnaire dictsort ferait le travail, mais ce n'est pas et ça ne marche pas)

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

Modifié pour ajouter des pensées supplémentaires après avoir accepté la réponse de Tawmas.


Tawmas a abordé le problème exactement tel que je l'ai présenté - même si la solution n'était pas ce à quoi je m'attendais. En conséquence, j'ai appris une technique utile qui peut également être utilisée dans d'autres situations.

La réponse de Tom proposait une approche que j'avais déjà mentionnée dans mon OP et que j'avais provisoirement rejetée comme étant «moche».

Le "laid" était une réaction instinctive, et je voulais clarifier ce qui n'allait pas. Ce faisant, j'ai réalisé que la raison pour laquelle c'était une approche laide était que j'étais accroché à l'idée de transmettre un ensemble de requêtes au modèle à rendre. Si je relâche cette exigence, il y a une approche laide qui devrait fonctionner.

Je ne l' ai pas encore essayé, mais supposons que , plutôt que de passer le queryset, le code de la vue itérer à travers l'ensemble des requêtes produisant une liste d'événements, puis décoré chaque événement avec une série de requêtes pour les participants correspondants qui WAS triés (ou filtré, ou autre) de la manière souhaitée. Quelque chose comme ça:

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)

Maintenant, le modèle devient:

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

L'inconvénient est que la vue doit «actualiser» tous les événements à la fois, ce qui pourrait être un problème s'il y avait un grand nombre d'événements. Bien sûr, on pourrait ajouter une pagination, mais cela complique considérablement la vue.

L'avantage est que le code "préparer les données à afficher" se trouve dans la vue à laquelle il appartient, ce qui permet au modèle de se concentrer sur le formatage des données fournies par la vue pour l'affichage. C'est juste et approprié.

Mon plan est donc d'utiliser la technique de Tawmas pour les grandes tables et la technique ci-dessus pour les petites tables, avec la définition du grand et du petit laissée au lecteur (sourire.)

Dale Wilson
la source

Réponses:

135

Vous devez spécifier l'ordre dans le modèle de participant, comme ceci. Par exemple (en supposant que votre classe de modèle s'appelle Participant):

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

Consultez le manuel pour plus d'informations.

MODIFIER . Une autre solution consiste à ajouter une propriété à votre modèle d'événement, à laquelle vous pouvez accéder depuis votre modèle:

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

Vous pouvez en définir plus selon vos besoins ...

tawmas
la source
Merci pour votre commentaire, mais cela ne fonctionne que si je veux faire de la commande d'affichage une propriété permanente du participant, ce que je ne fais pas. Je pourrais, par exemple, souhaiter afficher les participants triés par date à laquelle leur inscription a été reçue, afin de savoir quels participants devraient être informés qu'il n'y avait pas de place pour eux.
Dale Wilson
J'ai ajouté une solution alternative pour vous. Cela devrait vous offrir la flexibilité dont vous avez besoin.
tawmas
@Mark En effet, c'est le cas. Autant que je @propertysache, c'est exagéré ici car il n'y a pas de getters ou de setters impliqués: stackoverflow.com/questions/1554546/…
Rick Westera
Bien que cela ne soit pas strictement nécessaire pour le modèle, je trouve que la propriété @ ici permet un accès plus propre à partir du code de l'application , bien qu'il y ait une petite pénalité de performance (<30 ns sur mon ordinateur portable). C'est bien sûr une question de style et très subjective.
tawmas
Dans le cas où vous souhaitez trier l'ensemble par rapport à deux attributs, voici la commande: class Meta: ordering = ['last_name', 'first_name']
Tms91
135

Vous pouvez utiliser le filtre de modèle dictsort https://docs.djangoproject.com/en/dev/ref/templates/builtins/#std:templatefilter-dictsort

Cela devrait fonctionner:

{% 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
la source
22
Génial ! Et pour ceux qui s'interrogent, il y a aussi dictsortreversed: docs.djangoproject.com/en/dev/ref/templates/builtins/…
Mickaël
1
Pour citer mon article d'origine: ah, si seulement un ensemble de requêtes était un dictionnaire, le tri ferait l'affaire, mais ce n'est pas le cas et ce n'est pas le cas.
Dale Wilson
Je pense que cela devrait être moins pratique en pratique et laisser le modèle gérer le tri avant de récupérer les enregistrements.
acpmasquerade
1
@DaleWilson, j'ai en fait dictsorttravaillé correctement sur du code presque exactement comme le vôtre. Fait intéressant, cela semble fonctionner correctement sur les ensembles de requêtes.
mlissner
2
Et juste pour plus de clarté, les espaces sont importants: {% for attendee in event.attendee_set.all|dictsort:"last_name" %}trie les participants, mais {% for attendee in event.attendee_set.all | dictsort:"last_name" %}tente de trier la sortie de la boucle for et interrompt le for.
mattsl
3

Une solution consiste à créer un gabarit personnalisé:

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

utiliser comme ceci:

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

regroup devrait être en mesure de faire ce que vous voulez, mais y a-t-il une raison pour laquelle vous ne pouvez pas les réorganiser comme vous le souhaitez dans la vue?

À M
la source
Pour les ordonner dans la vue, je devrais parcourir les événements et pour chaque événement créer un conteneur d'une sorte de participants liés à cet événement dans un ordre correctement trié, puis transmettre toute cette collection de conteneurs au modèle sous une forme qui laisserait le modèle trouver la collection appropriée de participants lors de son itération à travers les événements. Comme je l'ai dit, cela pourrait être fait, mais ce n'est pas une bonne solution. Cela nécessite beaucoup trop de couplage entre la vue et le modèle, des itérations parallèles, etc. J'espérais une meilleure solution à ce qui devrait être un problème courant.
Dale Wilson
J'ai regardé le regroupement et il n'a pas semblé faire ce que je voulais. en particulier la documentation dit: [quote] Notez que {% regroup%} ne commande pas son entrée! Notre exemple repose sur le fait que la liste des personnes a été classée par sexe en premier lieu. [endquote]
Dale Wilson