Skip to content
Advertisement

Django list_display highest value ManyToMany Field

I want to know how I can display the “highest value” from my ManyToMany Field in the admin. This is my models.py file:

class Personal(models.Model):
    lastname  = models.CharField(max_length = 100)
    firstname = models.CharField(max_length = 100)
    degree = models.ManyToManyField('Degree')
    
    def __str__(self):
        return self.lastname

class Degree(models.Model):
    name_degree = models.CharField(verbose_name = 'Degree', max_length = 200, blank = False)
    rank = models.PositiveIntegerField(default = 0)

    def __str__(self):
        return self.name_degree

In my backend, I have created different types of Degree’s, all with a “ranking”. In my case, the highest degree you can have is “Doctoral Degree”, with a rank of “6”. Backed Admin List for Education

So if a User is creating himself in “Personal” he will have the option to select all the Degree’s he has achieved. But for my Personal list, I just to want to see the highest one, e.g. if the User selects “Bachelor’s Degree” and “Master’s Degree”, the Personal list should only contain “Master’s Degree”, because 5 > 4.

Someone with an idea on how my admin.py file should look like?

class PersonalAdmin(admin.ModelAdmin):
    list_display = ('lastname', 'firstname', ) # 'degree'

Big thanks in advance!

Advertisement

Answer

You can define a method in your ModelAdmin to calculate the maximum degree:

class PersonalAdmin(admin.ModelAdmin):
    list_display = ('lastname', 'firstname', 'highest_degree')

    @admin.display(description='Highest degree')
    def highest_degree(self, obj):
        deg = Degree.objects.filter(personal=obj).first()
        if deg:
            return deg.name_degree
        return None

This is however quite inefficient if we want to render a lot of Personals, since that will require an extra query per item.

We can let the database do the work for us with a subquery expression:

from django.db.models import OuterRef, Subquery

class PersonalAdmin(admin.ModelAdmin):
    list_display = ('lastname', 'firstname', 'highest_degree')

    def get_queryset(self, request):
        return Personal.objects.annotate(
            highest_degree=Subquery(
                Degree.objects.filter(
                    personal=OuterRef('pk')
                ).order_by('-rank').values('name_degree')[:1]
            )
        )

    @admin.display(description='Highest degree')
    def highest_degree(self, obj):
        return obj.highest_degree

This will calculate the name of the degrees in bulk.

Advertisement