Allow foreign key attributes in list_display with '__' - Django 1.5

 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
70
71
72
73
74
75
76
77
78
79
80
# -*- coding: utf-8 -*-
"""
Admin for related fields based on http://djangosnippets.org/snippets/2887/

for Django 1.5 where __metaclass__ is deprecated

"""

from __future__ import absolute_import
from six import with_metaclass
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(type(admin.ModelAdmin)):
    """
        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(with_metaclass(RelatedFieldAdminMetaclass,admin.ModelAdmin)):
    """
        Version of ModelAdmin that can use related fields in list_display, e.g.:
        list_display = ('address__city', 'address__country__country_code')
    """
    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. Allow foreign key attributes in list_display with '__' by jcushman 1 year, 2 months ago
  2. Show users' full names for foreign keys in admin by SmileyChris 4 years, 9 months ago
  3. Foreign Key list_filter wthout custom FilterSpec by haileris23 4 years, 2 months ago
  4. Admin list_display Ajax by whiteinge 6 years, 2 months ago
  5. ForeignKey filterspec by luc_j 3 years, 7 months ago

Comments

pdlouhy (on April 16, 2014):

Hi,

I have remade your snippet into form of an Django application (https://github.com/PetrDlouhy/django-related-admin), so it could be easily reused without need of code duplication. To finish this process, I would like to add some license and upload the application to Python Package Index.

Please, express yourself (e.g. whether you want to manage the application on Pip), what you think about this, and please choose license for your code.

#

(Forgotten your password?)