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:

class MyModelOptions(admin.ModelAdmin):
    list_display = ['fk_field__fk_attribute']  # ILLEGAL

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:

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')]

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:

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

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:

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

Usage:

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)

and in admin.py::

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'

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…

deph = DailyEmployeeProjectHours.objects.get(...)
assert deph.username == deph.employee.user.username
assert deph.date == deph.empday.date
assert deph.name == deph.project.name

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 *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>