views:

790

answers:

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

How could I sort Customers, depending on number_of_orders they have?

admin_order_field property can't be used here, as it requires a database field to sort on. Is it possible at all, as Django relies on the underlying DB to perform sorting? Creating an aggregate field to contain the number of orders seems like an overkill here.

The fun thing: if you change url by hand in the browser to sort on this column - it works as expected!

A: 

The only way I can think of is to denormalize the field. That is - create a real field that get's updated to stay in sync with the fields it is derived from. I usually do this by overriding save on eith the model with the denormalized fields or the model it derives from:

# 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[...]
andybak
This certainly should work, but can't mark it as accepted due to extra DB field involved. Also note missing .count() at the end of query-set line.
mike_k
fixed the count(). The only other solution (short of subclassing large chunks of contrib.admin) would be a Jquery/Ajaxy hack.
andybak
+8  A: 

I haven't tested this out (I'd be interested to know if it works) but what about defining a custom manager for Customer which includes the number of orders aggregated, and then setting admin_order_field to that aggregate, ie

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: I've just tested this idea and it works perfectly - no django admin subclassing required!

Greg
You are my hero!
mike_k