Django, list_display, and ManyToMany fields

Take the following models:

from django.db import models

class SomeOtherModel(models.Model):
    title = models.CharField(max_length=50)

class MyModel(models.Model):
    others = models.ManyToManyField('SomeOtherModel')

Since Django admin classes do not support rendering ManyToMany fields placed in an admin class' list_display property, we have to improvise. Let's take a look at MyModel again:

class MyModel(models.Model):
    others = models.ManyToManyField('SomeOtherModel')

    def display_others(self):
        return ', '.join([ other.title for other in self.others.all() ])
    display_others.short_description = 'Others'
    display_others.allow_tags = True

Notice the class method definition, display_others. This method is what we will use in our MyModel admin class' list_display property. The list_display property can take several types in order to display, the name of a model attribute, a callable (i.e. a function), a method on the model, or a method on the model admin. In this case we are using a method declared on our model (See Django's documentation for other examples).

A quick explanation of what is going on in the display_others method:

  • Line 5: We return a simple string of all the SomeOtherModels' titles assigned to MyModel
  • Line 6: We set an attribute called short_description on the class method. This is what will be displayed as the column header on the change list page.
  • Line 7: If SomeOtherModel titles might include HTML, this allows the HTML to be rendered instead of being escaped automatically which is Django's default behaviour.

Now, we have to hook this special list_display option into our model admin for MyModel. Its very easy!

from django.contrib import admin

from myapp.models import MyModel

class MyModelAdmin(admin.ModelAdmin):
    list_display = ('display_others',)
admin.site.register(MyModel, MyModelAdmin)

All that needs to be done to get this working is on line #6. Just add the name of the method you made on your model to the list_display property of that model's admin, and you're done!

There is one thing to look out for if you have a very large amount of related objects for many of your objects listed on a single change list page: you will end up making your database server really chug under the load. In those types of situations it would behove you to limit the number of related objects that you display in your list_display column. Either way, displaying the title of too many SomeOtherModels would make your change list page nearly unreadable. So, do the following:

class MyModel(models.Model):
    others = models.ManyToManyField('SomeOtherModel')
    def display_others(self):
        return ', '.join([ other.title for other in self.others.all()[:5] ])
    display_others.short_description = 'Others'
    display_others.allow_tags = True

Notice the "[:5]" towards the end of line #4, this causes Django to append a LIMIT clause to the SQL query that fetches the SomeOtherModel objects.

You could further improve the change list page load time by caching your display method's return value using something like memcached. Just make sure you clear the cache when you save the object (you could even pre-cache the return value in your model's save method if you wanted to get super-duper fancy)!

Questions? Comments? Let me know below!

Comments

Add comment

Gracias
muy bien explicado
me resolvio el problema .

By: fico chiossone on 18th of June, 2018 5:14:19 pm

Great, thanks a lot!

By: Rik Schoonbeek on 28th of February, 2018 9:52:10 am

Fantastic! Thank you for posting this

By: jfs on 11th of November, 2017 1:37:23 am

This was quite simple and working. Thanks

By: Shikha Jain on 8th of September, 2017 6:34:49 am