Feel free to contact.
Qaisar Ali Abbas
12 Oct 2019
In this blog, I will be showing you guys how to show some fields in Django admin page record listing.
There can be two types of data that you might want to show in your record listing page
Fields that exist in Database Models
For that field that exists in Django model, it is very simple to show them on the django admin page.
considering your customerapp/models.py looks something like
from django.contrib.auth.models import User
from django.db import models
class Customer(models.Model):
user = models.OneToOneField(User, related_name="customer")
phone_number = models.CharField(max_length=256, default=None, null=True, blank=True)
github_profile_url = models.CharField(max_length=256, default=None, null=True, blank=True)
is_verified = models.BooleanField(default=False)
....
class FoodOrder(models.Model):
customer = models.ForeignKey(Customer)
....
in admin.py file
from django.contrib import admin
from customerapp.models import Customer
@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
list_display = ("id", "user", "phone_number", "is_verified")
First one was straight forward but what if you want to show some column from a
Problem Statement
- Show a field from a foreign model
A foreign model like in this case user model which is Abstract User and contains 'first_name', 'last_name', 'email' etc.. but I cannot show something like "user__first_name", the list_display does not accept that.
Django Interview Question Answers
For this we can create a model property and use that property in list_display:
from django.contrib.auth.models import User
from django.db import models
class Customer(models.Model):
....
@property
def full_name(self):
return "%s %s"%(self.user.first_name, self.user.last_name)
def __str__(self):
return self.full_name
now in admin.py, we can add "full_name" in list_display
from django.contrib import admin
from customerapp.models import Customer
@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
list_display = ("id", "full_name", "phone_number", "is_verified")
Fields that doesn't exist in Database Models
But if we don't want to add a model property or the data we want to show is completely separate and has no direct link to the model and we are showing a field that does not exist in the models.
Problem Statement
- Count the number of orders placed by each customer and show it in list_display of the customer.
NOTICE: This count does not exist in DB we have to calculate it on the go.
Possible Solution:
for that, we can update the queryset to have our custom field in it and we can add that newly added field in list_display by creating a function and returning that object.
so in admin.py
from django.contrib import admin
from customerapp.models import Customer
from django.db.models import Count
@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
list_display = ("id", "order_count", "full_name", "phone_number")
def order_count(self, obj):
return obj._order_count
def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(
_order_count=Count("foodorder", distinct=True),
)
return queryset
Here order_count did not exist anywhere in the model we calculated it in get_queryset right before showing it.
Show custom button/link in list_display
Not only we can show the custom fields we can change the look and feel of the outputted value to further improve the user experience.
For example in our model we have "github_profile_url" if we want to not only show this in list_display but we also want to output it as anchor link to easily navigate to the profile
For that we do something like this
in customerapp/admin.py
from django.utils.html import format_html
@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
list_display = ("id", "full_name", "custom_github_profile_url", "phone_number")
def custom_github_profile_url(self, obj):
return format_html(
'<a href="{0}" >{0}</a> ',
obj.github_profile_url
)
custom_github_profile_url.short_description = 'Github Profile'
custom_github_profile_url.allow_tags = True
This is just a simple example you can do so much with this ability to return HTML
some of the classes that come with default django can be helpful like
def custom_github_profile_url(self, obj):
return format_html(
'<a href="{0}" class="button">Profile Link</a> ',
obj.github_profile_url
)
Show custom button in list_display linking other admin pages
This is probably a different topic where we can leverage Django admin internal URL schemes for easy navigation to the Django admin pages.
like, in this case, we have FoodOrder linking to the customer and has also been added to django admin this is important that those pages we want to navigate to should be added/registered to Django admin.
Problem Statement
- add a button in list_display of Customer which takes us to the FoodOrder listing page with the filter of customer added resulting "listing orders of only this customer"
NOTE:- Assuming we have filter enabled (which are enabled by default)
/admin/customerapp/foodorder/?customer=1 - Notice that in Query paramers we have "customer" setting its value to 1 will return orders of only that customer
- Assuming we have filter enabled (which are enabled by default)
Solution
in customerapp/admin.py
@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
list_display = ("id", "full_name", "all_orders_of_this_customer", "phone_number")
def all_orders_of_this_customer(self, obj):
return format_html(
'<a href="{0}?customer={1}">Orders list of this customer</a> ',
reverse('admin:customerapp_foodorder_changelist' ), obj.id
)
all_orders_of_this_customer.short_description = 'Customer Orders'
all_orders_of_this_customer.allow_tags = True
Notice that this link will redirect to the FoodOrder Django admin page with the customer filter added by default.
Touching a bit on reverse URLs schemes for Django admin
- For listing page
reverse('admin:customerapp_foodorder_changelist' )
- For details / Edit page
reverse('admin:customerapp_foodorder_change', obj.id )
Now, adding iterative improvement to this what we can do is instead of on click going to the page and then coming back which can be a bit cumbersome one solution is that we can add target="_blank" to the anchor tag and so that it opens in next tab but we can do even better.
Django admin open new window on a custom link
This can simply be achieved by adding a class 'related-widget-wrapper-link' to the anchor tag and Django will take care of the rest.
NOTE: this simply adding of the class will work for only the listing page. but if you are opening let's say some other admin page which has a form in it for or example edit page then we need some extra fields as well.
@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
list_display = ("id", "full_name", "all_orders_of_this_customer", "phone_number")
def all_orders_of_this_customer(self, obj):
return format_html(
'<a class="related-widget-wrapper-link" href="{0}?customer={1}">Orders list of this customer</a> ',
reverse('admin:customerapp_foodorder_changelist' ), obj.id
)
all_orders_of_this_customer.short_description = 'Customer Orders'
all_orders_of_this_customer.allow_tags = True