Django

Django Admin show custom field in list_display

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>&nbsp;',
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>&nbsp;',
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

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>&nbsp;',
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

  1. For listing page
 reverse('admin:customerapp_foodorder_changelist' )
  1. 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>&nbsp;',
reverse('admin:customerapp_foodorder_changelist' ), obj.id
)

all_orders_of_this_customer.short_description = 'Customer Orders'
all_orders_of_this_customer.allow_tags = True



About author

Qaisar Ali Abbas

Senior QA Engineer & Software Engineer passionate about technology. Tech blogger sharing insights on the latest trends and innovations.


Load more
Scroll to Top