Reputation: 10011
I have two models - Contract and Supplier. Each supplier supplies a type of commodity. These are defined as follows:
class CommodityType(models.Model):
name = models.CharField(max_length=64)
def __unicode__(self):
return self.name
class Supplier(models.Model):
name = models.CharField(max_length=64)
type = models.ForeignKey(CommodityType)
def __unicode__(self):
return self.name
class Meta:
ordering = ['type', 'name']
class Contract(models.Model):
supplier = models.ForeignKey(Supplier)
clientNumber = models.CharField(max_length=32)
def __unicode__(self):
return u'%s, %s' % (self.supplier, self.clientNumber)
I want to have a listing of the Contracts
in the Django Admin site. For each of the Contracts
, I want to have the type from the referenced Supplier
displayed. So, for example, if the associated supplier supplies Electricity, then I want to have that displayed in the listing of Contracts
.
However, I cannot seem to find how this is done. I found this answer, but trying that gives me an ImproperlyConfigured error.
How can this be done?
Upvotes: 1
Views: 1652
Reputation: 2098
Recently, a library called django-related-admin released, which allows you to use foreign key attributes in Django admin change list list_display
with '__' so easily, Specifically for this question, how to use this library in admin.py
module is as follows:
admin.py
from related_admin import RelatedFieldAdmin
from related_admin import getter_for_related_field
class ContractAdmin(RelatedFieldAdmin):
# these fields will work automatically (and boolean fields will display an icon):
list_display = ('clientNumber','supplier__type__name')
# or you can also define them manually if you need to override short_description or boolean parameter:
supplierType = getter_for_related_field('supplier__type__name', short_description='supplier type', boolean=False)
Upvotes: 0
Reputation: 99660
What you probably need is the list_display
class ContractAdmin(admin.ModelAdmin):
list_display('clientNumber', 'supplier')
admin.register(Contract, ContractAdmin)
To allow __
in Admin for foreign key, You can use this snippet
From the snippet:
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 __getattr__(self, name):
if '__' in name:
getter = getter_for_related_field(name)
setattr(self, name, getter) # cache so we don't have to do this again
return getter
raise AttributeError # let missing attribute be handled normally
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')
Upvotes: 1