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
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…