Classificando itens relacionados em um modelo Django

87

É possível classificar um conjunto de itens relacionados em um modelo DJango?

Isto é: este código (com tags HTML omitidas para maior clareza):

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

exibe quase exatamente o que eu quero. A única coisa que quero mudar é que a lista de participantes seja classificada pelo sobrenome. Tentei dizer algo assim:

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

Infelizmente, a sintaxe acima não funciona (ela produz uma lista vazia) e nem qualquer outra variação que eu tenha pensado (muitos erros de sintaxe relatados, mas nenhum prazer).

Eu poderia, é claro, produzir algum tipo de conjunto de listas classificadas de participantes, na minha opinião, mas essa é uma solução feia e frágil (e já mencionei feia).

Desnecessário dizer, mas direi mesmo assim, eu li os documentos on-line e pesquisei Stack Overflow e os arquivos do django-user sem encontrar nada útil (ah, se apenas um conjunto de consulta fosse um dicionário dictsort faria o trabalho, mas não é e não faz)

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

Editado para adicionar pensamentos adicionais após aceitar a resposta de Tawmas.


Tawmas abordou o problema exatamente como eu apresentei - embora a solução não fosse o que eu esperava. Como resultado, aprendi uma técnica útil que também pode ser usada em outras situações.

A resposta de Tom propôs uma abordagem que já havia mencionado em meu OP e rejeitei provisoriamente como sendo "feia".

O "feio" foi uma reação instintiva, e eu queria esclarecer o que havia de errado com ele. Ao fazer isso, percebi que a razão pela qual era uma abordagem feia era porque estava preso à ideia de passar um conjunto de consultas para o modelo a ser processado. Se eu relaxar essa exigência, há uma abordagem desagradável que deve funcionar.

Eu não tentei isso ainda, mas suponha que ao invés de passar o queryset, o código vista iterado através do conjunto de consulta produzindo uma lista de eventos, em seguida, decorado cada evento com um conjunto de consulta para os participantes correspondentes que FOI ordenados (ou filtrada, ou qualquer outra coisa) da maneira desejada. Algo assim:

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)

Agora o modelo se torna:

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

A desvantagem é que a visão precisa "atualizar" todos os eventos de uma vez, o que poderia ser um problema se houvesse um grande número de eventos. Claro que pode-se adicionar paginação, mas isso complica consideravelmente a visão.

A vantagem é que o código "preparar os dados a serem exibidos" está na visualização a que pertence, permitindo que o modelo se concentre na formatação dos dados fornecidos pela visualização para exibição. Isso é certo e adequado.

Portanto, meu plano é usar a técnica de Tawmas para tabelas grandes e a técnica acima para tabelas pequenas, com a definição de grande e pequeno deixada para o leitor (sorriso.)

Dale Wilson
fonte

Respostas:

135

Você precisa especificar a ordem no modelo de participante, assim. Por exemplo (supondo que sua classe de modelo seja chamada de Participante):

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

Consulte o manual para mais referências.

EDIT . Outra solução é adicionar uma propriedade ao seu modelo de evento, que você pode acessar a partir do seu modelo:

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

Você poderia definir mais destes conforme necessário ...

Tawmas
fonte
Obrigado pelo seu comentário, mas isso só funciona se eu quiser tornar a ordem de exibição uma propriedade permanente do participante, o que não é o caso. Eu poderia, por exemplo, desejar exibir os participantes classificados pela data em que seu registro foi recebido, para saber quais participantes deveriam ser notificados de que não havia lugar para eles.
Dale Wilson
Eu adicionei uma solução alternativa para você. Deve permitir a flexibilidade de que você precisa.
tawmas
@Mark Certamente que sim. Pelo que entendi, @propertyé um exagero aqui, já que não há getters ou setters envolvidos: stackoverflow.com/questions/1554546/…
Rick Westera
Embora não seja estritamente necessário para o modelo, acho que @property aqui torna o acesso mais limpo do código do aplicativo , embora haja uma pequena penalidade de desempenho envolvida (<30 ns no meu laptop). É claro que isso é uma questão de estilo e altamente subjetivo.
tawmas de
Caso você queira classificar o conjunto com relação a dois atributos, este é o comando: class Meta: ordering = ['last_name', 'first_name']
Tms91
135

Você pode usar o filtro de modelo dictsort https://docs.djangoproject.com/en/dev/ref/templates/builtins/#std:templatefilter-dictsort

Isso deve funcionar:

{% 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
fonte
22
Ótimo ! E para aqueles que se perguntam, há também dictsortreversed: docs.djangoproject.com/en/dev/ref/templates/builtins/…
Mickaël
1
Para citar minha postagem original: ah, se apenas um conjunto de consultas fosse um dicionário dictsort faria o trabalho, mas não é e não faz.
Dale Wilson
Acho que isso deveria ser menos prático e deixar o modelo lidar com a classificação antes de buscar os registros.
acpmasquerade
1
@DaleWilson, Na verdade, estou dictsorttrabalhando corretamente em um código quase igual ao seu. Curiosamente, parece funcionar bem em querysets.
mlissner
2
E apenas para maior clareza, os espaços são importantes: {% for attendee in event.attendee_set.all|dictsort:"last_name" %}classifica os participantes, mas {% for attendee in event.attendee_set.all | dictsort:"last_name" %}tenta classificar a saída do loop for e interrompe o for.
mattsl
3

Uma solução é fazer um templatag personalizado:

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

use assim:

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

O reagrupamento deve ser capaz de fazer o que você deseja, mas há um motivo pelo qual você não pode ordená-los da maneira que deseja de volta na exibição?

Tom
fonte
Para ordená-los na visualização, eu precisaria iterar pelos eventos e, para cada evento, criar um contêiner de algum tipo de participante relacionado a esse evento em uma ordem de classificação adequada e, em seguida, passar toda a coleção de contêineres para o modelo em um formulário que permitiria que o modelo encontrasse a coleção apropriada de participantes à medida que iterava pelos eventos. Como disse, poderia ser feito, mas não é uma boa solução. Requer muito acoplamento entre a visão e o modelo, iterações paralelas, etc. Eu esperava por uma solução melhor para o que deveria ser um problema comum.
Dale Wilson
Eu olhei para reagrupar e não pareceu fazer o que eu queria. em particular, a documentação diz: [quote] Observe que {% regroup%} não ordena sua entrada! Nosso exemplo se baseia no fato de que a lista de pessoas foi ordenada por gênero em primeiro lugar. [endquote]
Dale Wilson