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.
- Fixed error when DEBUG=False.
- Broke out
getter_for_related_fieldso 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) 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
- Stuff by NixonDash 1 month ago
- Add custom fields to the built-in Group model by jmoppel 3 months, 1 week ago
- Month / Year SelectDateWidget based on django SelectDateWidget by pierreben 6 months, 3 weeks ago
- Python Django CRUD Example Tutorial by tuts_station 7 months, 1 week ago
- Browser-native date input field by kytta 8 months, 3 weeks ago
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:
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
To get this working in newer Django versions, replace
To get it working in versions since 1.8, replace:
Please login first before commenting.