Login

Allow foreign key attributes in list_display with '__'

Author:
jcushman
Posted:
February 3, 2013
Language:
Python
Version:
1.4
Score:
6 (after 6 ratings)

This snippet provides a subclass of admin.ModelAdmin that lets you span foreign key relationships in list_display using '__'. The foreign key columns are sortable and have pretty names, and select_related() is set appropriately so you don't need queries for each line.

EDITS:

  • Fixed error when DEBUG=False.
  • Broke out getter_for_related_field so you can override short_description manually (see example).
  • Added regular foreign key fields to select_related(), since this is overriding the code in ChangeList that usually does it.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
from django.contrib import admin
from django.db import models

def getter_for_related_field(name, admin_order_field=None, short_description=None):
    """
        Create a function that can be attached to a ModelAdmin to use as a list_display field, e.g:
        client__name = getter_for_related_field('client__name', short_description='Client')
    """
    related_names = name.split('__')
    def getter(self, obj):
        for related_name in related_names:
            obj = getattr(obj, related_name)
        return obj
    getter.admin_order_field = admin_order_field or name
    getter.short_description = short_description or related_names[-1].title().replace('_',' ')
    return getter

class RelatedFieldAdminMetaclass(admin.ModelAdmin.__metaclass__):
    """
        Metaclass used by RelatedFieldAdmin to handle fetching of related field values.
        We have to do this as a metaclass because Django checks that list_display fields are supported by the class.
    """
    def __new__(cls, name, bases, attrs):
        new_class = super(RelatedFieldAdminMetaclass, cls).__new__(cls, name, bases, attrs)

        for field in new_class.list_display:
            if '__' in field:
                setattr(new_class, field, getter_for_related_field(field))

        return new_class

class RelatedFieldAdmin(admin.ModelAdmin):
    """
        Version of ModelAdmin that can use related fields in list_display, e.g.:
        list_display = ('address__city', 'address__country__country_code')
    """
    __metaclass__ = RelatedFieldAdminMetaclass
    def queryset(self, request):
        qs = super(RelatedFieldAdmin, self).queryset(request)

        # include all related fields in queryset
        select_related = [field.rsplit('__',1)[0] for field in self.list_display if '__' in field]

        # Include all foreign key fields in queryset.
        # This is based on ChangeList.get_query_set().
        # We have to duplicate it here because select_related() only works once.
        # Can't just use list_select_related because we might have multiple__depth__fields it won't follow.
        model = qs.model
        for field_name in self.list_display:
            try:
                field = model._meta.get_field(field_name)
            except models.FieldDoesNotExist:
                continue
            if isinstance(field.rel, models.ManyToOneRel):
                select_related.append(field_name)

        return qs.select_related(*select_related)





#### USAGE ####
class FooAdmin(RelatedFieldAdmin):
    # these fields will work automatically:
    list_display = ('address__phone','address__country__country_code','address__foo')

    # ... but you can also define them manually if you need to override short_description:
    address__foo = getter_for_related_field('address__foo', short_description='Custom Name')

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

pdlouhy (on April 2, 2013):

Hi, thanks for usefull snippet.

I have problem running it in non-DEBUG mode. Although it is working perfectly when DEBUG=True is set, when I set it to False, I am always getting following error:

AttributeError: Unable to lookup 'subsidiary__city' on Team or TeamAdmin

Any suggestions?

#

kase (on August 5, 2013):

admin.ModelAdmin.metaclass not work in django 1.5+ .metaclass is deprecated

i replace for type(admin.ModelAdmin) and i think it work... but cant use this snippet like midlewareclass.. i need import in admin.py

sorry.. bad english

#

Wouter-M (on March 11, 2015):

To get this working in newer Django versions, replace

class RelatedFieldAdminMetaclass(admin.ModelAdmin.__metaclass__):

by

class RelatedFieldAdminMetaclass(type(admin.ModelAdmin)):

#

Wouter-M (on August 3, 2015):

To get it working in versions since 1.8, replace:

def get_queryset(self, request):
    qs = super(RelatedFieldAdmin, self).queryset(request)

by

def get_queryset(self, request):
    qs = super(RelatedFieldAdmin, self).get_queryset(request)

#

Please login first before commenting.