Django admin: come ordinare in base a uno dei campi list_display personalizzati che non hanno un campo database


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()  

Come posso ordinare i clienti, a seconda di quelli number_of_ordersche hanno?

admin_order_fieldla proprietà non può essere utilizzata qui, poiché richiede un campo del database per l'ordinamento. È possibile, dato che Django si affida al DB sottostante per eseguire l'ordinamento? La creazione di un campo aggregato per contenere il numero di ordini sembra eccessivo qui.

La cosa divertente: se cambi l'URL a mano nel browser per ordinare su questa colonna, funziona come previsto!


"La cosa divertente: se modifichi l'URL manualmente nel browser per ordinare su questa colonna, funziona come previsto!" Intendi come: / admin / miaapp / cliente /? Ot = asc & o = 2 Sei sicuro?
— Andy Baker

sì, sia asc che dsc. Forse funziona solo con i decimali.
— mike_k

Non credo che funzionerebbe con più pagine.
— Chase Seibert,

Risposte:


159

Mi è piaciuta la soluzione di Greg a questo problema, ma vorrei sottolineare che puoi fare la stessa cosa direttamente nell'amministratore:

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'

In questo modo annoti solo all'interno dell'interfaccia di amministrazione. Non con tutte le domande che fai.


5
Sì, questo è un modo molto migliore. :)
— Greg

2
C'è una modifica suggerita su questa risposta. Ho votato per respingerla perché rimuoveva troppo testo. Non conosco Django, non ho idea se valga la pena menzionare la modifica del codice proposta.
— Gilles "SO- smettila di essere malvagio"

1
@Gilles la modifica suggerita è corretta per una più semplice definizione di number_of_orders. Funziona: def number_of_orders(self, obj): return obj.order__count
— Nils

1
Non dovrebbe essere al get_queryset()posto di queryset()?
— Mariusz Jamro

2
dovrebbe essere get_queryset (self, request): ... for Django 1.6+
— michael

50

Non l'ho testato (sarei interessato a sapere se funziona) ma per quanto riguarda la definizione di un gestore personalizzato per il Customerquale includa il numero di ordini aggregati e quindi l'impostazione admin_order_fielddi tale aggregato, ad es.

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: Ho appena testato questa idea e funziona perfettamente - non è richiesta alcuna sottoclasse di amministrazione di django!


1
Questa è una risposta migliore rispetto a quella accettata. Il problema che ho riscontrato nell'applicazione di quello accettato è che quando cerchi qualcosa insieme a quel set di query aggiornato a livello di amministratore, ci vuole troppo tempo e si presenta anche con un conteggio errato dei risultati trovati.
— Mutant

0

L'unico modo a cui riesco a pensare è denormalizzare il campo. Cioè: crea un campo reale che venga aggiornato per rimanere sincronizzato con i campi da cui deriva. Di solito lo faccio sovrascrivendo save on eith il modello con i campi denormalizzati o il modello da cui 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
Questo dovrebbe certamente funzionare, ma non è possibile contrassegnarlo come accettato a causa del campo DB aggiuntivo coinvolto. Notare anche la mancanza di .count () alla fine della riga di set di query.
— mike_k

corretto il conteggio (). L'unica altra soluzione (a parte la sottoclasse di grandi blocchi di contrib.admin) sarebbe un hack Jquery / Ajaxy.
— Andy Baker
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.