django :: list_display can’t sort on attribute of foreign key field…

I’m not the only one surprised by the fact that you can’t use the double-underscore-foreignkey-attribute-accessor syntax in list_display:

[python]
class MyModelOptions(admin.ModelAdmin):
list_display = [‘fk_field__fk_attribute’] # ILLEGAL
[/python]

There has been extensive discussions on the tracker (https://code.djangoproject.com/ticket/5863) and on the mailing list (http://groups.google.com/group/django-developers/browse_thread/thread/790484bfbe1b421f). It seems unlikely that this will be possible to do before hell freezes over (although someone commented that it works in Django 1.2 here: http://stackoverflow.com/questions/163823/can-list-display-in-a-django-modeladmin-display-attributes-of-foreignkey-field).

Luke Plant has suggested a solution using callables, which I personally find ugly, but YMMW:

[python]
def foreign_field(field_name):
def accessor(obj):
val = obj
for part in field_name.split(‘__’):
val = getattr(val, part)
return val
accessor.__name__ = field_name
return accessor

ff = foreign_field

class MyAdmin(ModelAdmin):

list_display = [ff(‘foreign_key__related_fieldname1’),
ff(‘foreign_key__related_fieldname2’)]
[/python]

The code from @lukeplant does all the work in the ModelAdmin, which keeps the Model class free of extra methods for the admin. This is generally a good idea, however I just had a use case where adding accessors made working with the model much easier.

The model in question looks like this:

[python]
class DailyEmployeeProjectHours(models.Model):
employee = models.ForeignKey(Employee)
empday = models.ForeignKey(EmployeeDay)
project = models.ForeignKey(Project)
seconds_worked = models.IntegerField(default=0)
[/python]

The code was littered with “deph.employee.user.username”, “deph.empday.date”, and “deph.project.name”…

We needed something that could define an accessor/property, that would automagically be sortable in Django’s admin interface… which sounds like the job for a descriptor:

[python]
class FkeyLookup(object):
def __init__(self, fkeydecl, short_description=None, admin_order_field=None):
self.fk, fkattrs = fkeydecl.split(‘__’, 1)
self.fkattrs = fkattrs.split(‘__’)

self.short_description = short_description or self.fkattrs[-1]
self.admin_order_field = admin_order_field or fkeydecl

def __get__(self, obj, klass):
if obj is None:
return self # hack required to make Django validate (if obj is None, then we’re a class, and classes are callable <wink>)

item = getattr(obj, self.fk)
for attr in self.fkattrs:
item = getattr(item, attr)
return item
[/python]

Usage:

[python]
class DailyEmployeeProjectHours(models.Model):
employee = models.ForeignKey(Employee)
username = FkeyLookup("employee__user__username")

empday = models.ForeignKey(EmployeeDay)
date = FkeyLookup("empday__date")

project = models.ForeignKey(Project)
name = FkeyLookup("project__name")

seconds_worked = models.IntegerField(default=0)
[/python]

and in admin.py::

[python]
class DailyEmployeeProjectHoursOptions(admin.ModelAdmin):
list_display = "username date name hours".split()

def hours(self, obj):
return ‘%.2f’ % round(obj.seconds_worked / 3600.0, 2)
hours.admin_order_field = ‘seconds_worked’
[/python]

The admin list for EmployeeProjectHours now has columns named “Username”, “Date”, “Name”, and “Hours” — and all of them will be sortable! (if you’re on an ancient version of Django, you’ll need to apply r9212 from Django trunk, only the changes in contrib/admin/views/main.py are needed if you’re lazy).

In addition the DailyEmployeeProjectHours class now has a number of new properties…

[python]
deph = DailyEmployeeProjectHours.objects.get(…)
assert deph.username == deph.employee.user.username
assert deph.date == deph.empday.date
assert deph.name == deph.project.name
[/python]

It’s cute and it uses descriptors… 🙂

This entry was posted in django and tagged . Bookmark the permalink.

2 Responses to django :: list_display can’t sort on attribute of foreign key field…

  1. Anika Dejong Butler says:

    I think the list_display needs to be a list, not a string…

Leave a Reply

Your email address will not be published. Required fields are marked *