Managing Model Relationships¶
Autocompletion For Related Fields¶
Lets us go to BookAdmin and try to add a new book.
from book.models import Book class BookAdmin(admin.ModelAdmin): list_display = ('id', 'name', 'author') admin.site.register(Book, BookAdmin)
By default, this will show a select box with entire authors list. Navigating this select list and finding the required author is difficult.
To make this easier, we can provide autocomplete option for author field so that users can search and select the required author.
from book.models import Book class AuthorAdmin(admin.ModelAdmin): search_fields = ('name',) class BookAdmin(admin.ModelAdmin): list_display = ('id', 'name', 'author') autocomplete_fields = ('author',) admin.site.register(Book, BookAdmin)
For this, ModelAdmin provides autocomplete_fields option to change to select2 autocomplete input. We should also define search_fields on the related admin so that search is performed on these fields.
Related Fields In Admin List¶
Django admin has ModelAdmin class which provides options and functionality for the models in admin interface. It has options like list_display, list_filter, search_fields to specify fields for corresponding actions.
search_fields, list_filter and other options allow to include a ForeignKey or ManyToMany field with lookup API follow notation. For example, to search by book name in Bestselleradmin, we can specify book__name in search fields.
from django.contrib import admin from book.models import BestSeller class BestSellerAdmin(RelatedFieldAdmin): search_fields = ('book__name', ) list_display = ('id', 'year', 'rank', 'book') admin.site.register(Bestseller, BestsellerAdmin)
However Django doesn’t allow the same follow notation in list_display. To include ForeignKey field or ManyToMany field in the list display, we have to write a custom method and add this method in list display.
from django.contrib import admin from book.models import BestSeller class BestSellerAdmin(RelatedFieldAdmin): list_display = ('id', 'rank', 'year', 'book', 'author') search_fields = ('book__name', ) def author(self, obj): return obj.book.author author.description = 'Author' admin.site.register(Bestseller, BestsellerAdmin)
This way of adding foreignkeys in list_display becomes tedious when there are lots of models with foreignkey fields.
We can write a custom admin class to dynamically set the methods as attributes so that we can use the ForeignKey fields in list_display.
def get_related_field(name, admin_order_field=None, short_description=None): related_names = name.split('__') def dynamic_attribute(obj): for related_name in related_names: obj = getattr(obj, related_name) return obj dynamic_attribute.admin_order_field = admin_order_field or name dynamic_attribute.short_description = short_description or related_names[-1].title().replace('_', ' ') return dynamic_attribute class RelatedFieldAdmin(admin.ModelAdmin): def __getattr__(self, attr): if '__' in attr: return get_related_field(attr) # not dynamic lookup, default behaviour return self.__getattribute__(attr) class BestSellerAdmin(RelatedFieldAdmin): list_display = ('id', 'rank', 'year', 'book', 'book__author')
By sublcassing RelatedFieldAdmin, we can directly use foreignkey fields in list display.
However, this will lead to N+1 problem. We will discuss more about this and how to fix this in orm optimizations chapter.