Ordering by model_set in @admin.display

Hello,
I have a small app of type “Countries => City” as reported below:

# models.py
class Country(models.Model):
	name = models.CharField(max_length=150, help_text="Country name")
	iso2 = models.CharField(max_length=2, help_text="Country ISO2")
	iso3 = models.CharField(max_length=3, help_text="Country ISO3")

class City(models.Model):
	name = models.CharField(max_length=150, help_text="City name")
	country = models.ForeignKey('Country', on_delete=models.SET_NULL, null=True, blank=True)

and in admin.py I set City @admin.display as below:

@admin.register(Country)
class CountryAdmin(admin.ModelAdmin):
	list_display = ('id', 'name', 'get_cities', 'iso2', 'iso3')

	@admin.display(description='Cities', ordering='city')
	def get_cities(self, obj):
		count_results = obj.city_set.count()
		return format_html('<a href="{0}?country__id={1}">{2}</a>'.format( reverse("admin:myapp_city_changelist"), obj.pk, count_results ) )	

It partially works since it shows duplicates countries (See “Brazil” and “France” in attached image):

I also tried to type @admin.display(description='Cities', ordering='city_set.count') but it fails.

Hey there!
Maybe you can get around this overriding the get_queryset method.

from django.db.models import Count

@admin.register(Country)
class CountryAdmin(admin.ModelAdmin):
	list_display = ('id', 'name', 'get_cities', 'iso2', 'iso3')

	@admin.display(description='Cities', ordering='city')
	def get_cities(self, obj):
		# count_results = obj.city_set.count()
        # this is no longer needed, since the value is annotated on the queryset
		return format_html('<a href="{0}?country__id={1}">{2}</a>'.format( reverse("admin:myapp_city_changelist"), obj.pk, obj.city_count ))
        
    def get_queryset(self, request):
        return super().get_queryset(request).annotate(city_count=Count("city_set")).order_by("-city_count")

Hi!

unfortunately it doesn’t work due to following errors:

  1. Cannot resolve keyword ‘city_set’ into field. Choices are: continent, id, iso2, iso3, iso_numeric, name, tld, city. If I edit city_set to city I obtain following error
  2. name ‘city_count’ is not defined

Oops, my bad.
What about:

def get_queryset(self, request):
        return super().get_queryset(request).annotate(city_count=Count("city_set__id")).order_by("-city_count")

unfortunately the issue remains and I have the same error:

Cannot resolve keyword ‘city_set’ into field. Choices are: continent, id, iso2, iso3, iso_numeric, name, tld, city

That’s strange, i used to do this a lot. Can you add this to your country fk?

class City(models.Model):
	name = models.CharField(max_length=150, help_text="City name")
	country = models.ForeignKey('Country', on_delete=models.SET_NULL, null=True, blank=True,
related_name="cities"  # <- This
)

# On the admin get_queryset
    def get_queryset(self, request):
        return super().get_queryset(request).annotate(city_count=Count("cities")).order_by("-city_count")

Now it works!
So, automatic ordering does not seems to work in @admin.display(description='Cities', ordering='cities_count'). Howewer, If I click on “cities” column 3 times it works.

Furthermore, I had to put cities_count in ordering since if I put cities, I have the duplicates.

Hi @leandrodesouzadev,
can you tell me how to automatically order by cities_count?

What do you mean by automatically?

When I go to admin/model/country/, I wish to have Countries by cities_count desc. I have to on “cities” column 3 times to have correct ordering.