Admin for related fields based on: http://djangosnippets.org/snippets/2887/
for Django 1.5 where metaclass is deprecated
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
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
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.
#
Please login first before commenting.