Django admin: como classificar por um dos campos customizados list_display que não possuem campo de banco de dados


122
# admin.py
class CustomerAdmin(admin.ModelAdmin):  
    list_display = ('foo', 'number_of_orders')

# models.py
class Order(models.Model):
    bar = models.CharField[...]
    customer = models.ForeignKey(Customer)

class Customer(models.Model):
    foo = models.CharField[...]
    def number_of_orders(self):
        return u'%s' % Order.objects.filter(customer=self).count()  

Como eu poderia classificar os clientes, dependendo number_of_ordersdeles?

admin_order_fieldA propriedade não pode ser usada aqui, pois requer um campo de banco de dados para classificação. É possível, já que o Django depende do banco de dados subjacente para realizar a classificação? Criar um campo agregado para conter o número de pedidos parece um exagero aqui.

O mais divertido: se você alterar o URL manualmente no navegador para classificar nesta coluna - ele funcionará conforme o esperado!


"O mais divertido: se você alterar o URL manualmente no navegador para classificar nesta coluna - ele funciona como esperado!" Você quer dizer: / admin / myapp / customer /? Ot = asc & o = 2 Tem certeza?
Andy Baker

sim, tanto asc e dsc. Talvez apenas funcione com decimais.
precisa saber é o seguinte

Eu não acho que funcionaria com várias páginas.
Chase Seibert 27/02

Respostas:


159

Adorei a solução de Greg para esse problema, mas gostaria de salientar que você pode fazer a mesma coisa diretamente no administrador:

from django.db import models

class CustomerAdmin(admin.ModelAdmin):
    list_display = ('number_of_orders',)

    def get_queryset(self, request):
    # def queryset(self, request): # For Django <1.6
        qs = super(CustomerAdmin, self).get_queryset(request)
        # qs = super(CustomerAdmin, self).queryset(request) # For Django <1.6
        qs = qs.annotate(models.Count('order'))
        return qs

    def number_of_orders(self, obj):
        return obj.order__count
    number_of_orders.admin_order_field = 'order__count'

Dessa forma, você apenas faz anotações dentro da interface de administração. Não com todas as consultas que você faz.


5
Sim, esta é uma maneira muito melhor. :)
Greg

2
Há uma edição sugerida nesta resposta. Votei em rejeitá-lo porque ele removeu muito texto. Não conheço o Django, não faço ideia se vale a pena mencionar a alteração proposta no código.
Gilles 'SO- stop be evil'

1
@Gilles, a edição sugerida está correta em relação a uma definição mais simples de number_of_orders. Isso funciona: def number_of_orders(self, obj): return obj.order__count
Nils

1
Não deveria ser isso em get_queryset()vez de queryset()?
Mariusz Jamro

2
deve ser get_queryset (self, request): ... para Django 1.6+
michael

50

Eu não testei isso (eu estaria interessado em saber se funciona), mas que tal definir um gerente personalizado para o Customerqual inclua o número de pedidos agregados e depois definir admin_order_fieldesse agregado, ou seja,

from django.db import models 


class CustomerManager(models.Manager):
    def get_query_set(self):
        return super(CustomerManager, self).get_query_set().annotate(models.Count('order'))

class Customer(models.Model):
    foo = models.CharField[...]

    objects = CustomerManager()

    def number_of_orders(self):
        return u'%s' % Order.objects.filter(customer=self).count()
    number_of_orders.admin_order_field = 'order__count'

EDIT: Acabei de testar essa idéia e ela funciona perfeitamente - não é necessária nenhuma subclasse de administrador do django!


1
Esta é uma resposta melhor em comparação com a aceita. O problema que tive ao aplicar o aceito é quando você pesquisa algo junto com o conjunto de consultas atualizado no nível do administrador, leva muito tempo e também apresenta uma contagem incorreta dos resultados encontrados.
Mutant

0

A única maneira de pensar é desnormalizar o campo. Ou seja - crie um campo real que seja atualizado para permanecer sincronizado com os campos dos quais é derivado. Eu costumo fazer isso substituindo save on eith o modelo pelos campos desnormalizados ou o modelo do qual deriva:

# models.py
class Order(models.Model):
    bar = models.CharField[...]
    customer = models.ForeignKey(Customer)
    def save(self):
        super(Order, self).save()
        self.customer.number_of_orders = Order.objects.filter(customer=self.customer).count()
        self.customer.save()

class Customer(models.Model):
    foo = models.CharField[...]
    number_of_orders = models.IntegerField[...]

1
Isso certamente deve funcionar, mas não pode marcá-lo como aceito devido ao campo extra do banco de dados envolvido. Observe também .count () ausente no final da linha do conjunto de consultas.
Mike_k

corrigiu a contagem (). A única outra solução (menos a subclasse de grandes partes de contrib.admin) seria um hack Jquery / Ajaxy.
Andy Baker
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.