Search code examples
djangodjango-admindjango-querysetdjango-ormdjango-annotate

sorting in django admin list by a custom field alphabetically


I have a simple store / product relationship and I want to sort the products depending on the store name alphabetically.

models:

class Store(models.Model):
    name = models.CharField("name", max_length = 128)
    show_name = models.CharField("name", max_length = 128, null = True, blank = True)

class Product(models.Model):
    number = models.PositiveIntegerField()
    name = models.CharField("name", max_length = 128)
    store = models.ForeignKey(Store, on_delete = models.CASCADE)

and in the admin:

class ProductAdmin(admin.ModelAdmin):
    list_display = ["number", "name", "get_store_name",]
    
    def get_store_name(self, obj):
        if obj.store.show_name == None:
            return f"""{obj.store.name}"""
        else:
            return f"""{obj.store.show_name}"""

I know I cannot use order_by on custom fields. So I thought I need to override the get_queryset method probably? I found multiple examples to annotate by counted or calculated numbers, but never for strings.


Solution

  • You can annotate field with condition to queryset and then, set it as ordering in @admin.display decorator for your custom field.

    from django.db.models import Case, F, When
    
    
    class ProductAdmin(admin.ModelAdmin):
        list_display = ["number", "name", "get_store_name"]
    
        def get_queryset(self, request):
            queryset = super().get_queryset(request)
            queryset = queryset.annotate(
                _store_name=Case(
                    When(store__show_name__isnull=True, then=F('store__name')),
                    default=F('store__show_name')),
                ),
            )
            return queryset
    
        @admin.display(ordering='_store_name')
        def get_store_name(self, obj):
            if obj.store.show_name:
                return obj.store.show_name
            return obj.store.name
    

    With this implementation, standard django admin interface sorting will work for your column

    enter image description here

    If you want just default sorting, you can add it to queryset before return in get_queryset()

    queryset.order_by('_store_name')