How to print object's values with "annotate()" and "for loop" in ascending order?

I have Category and Product models below. *I use Django 3.2.16:

# "models.py"

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=20)

class Product(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    name = models.CharField(max_length=50)

Then, when running test view to print id, name and product__count with annotate() and index as shown below:

# "views.py"

from .models import Category
from django.http import HttpResponse
from django.db.models import Count

def test(request):
    qs = Category.objects.annotate(Count('product'))

    print(qs[0].id, qs[0].name, qs[0].product__count)
    print(qs[1].id, qs[1].name, qs[1].product__count)
    print(qs[2].id, qs[2].name, qs[2].product__count)
    print(qs[3].id, qs[3].name, qs[3].product__count)
    
    return HttpResponse("Test")

These are printed in ascending order properly as shown below:

1 Fruits 14
2 Vegetables 10
3 Meat 4
4 Fish 3

But, when running test view to print id, name and product__count with annotate() and for loop as shown below:

# "views.py"

# ...

def test(request):
    qs = Category.objects.annotate(Count('product'))

    for obj in qs:
        print(obj.id, obj.name, obj.product__count)
    
    return HttpResponse("Test")

These are printed in descending order improperly as shown below:

4 Fish 3
2 Vegetables 10
3 Meat 4
1 Fruits 14
[25/Jan/202

In addition, when running test view to print id and name with all() and index as shown below:

# "views.py"

# ...

def test(request):
    qs = Category.objects.all()

    print(qs[0].id, qs[0].name)
    print(qs[1].id, qs[1].name)
    print(qs[2].id, qs[2].name)
    print(qs[3].id, qs[3].name)
    
    return HttpResponse("Test")

And, when running test view to print id and name with all() and for loop as shown below:

# "views.py"

# ...

def test(request):
    qs = Category.objects.all()

    for obj in qs:
        print(obj.id, obj.name)
    
    return HttpResponse("Test")

These are printed in ascending order properly as shown below:

1 Fruits 14
2 Vegetables 10
3 Meat 4
4 Fish 3

So, how can I print object's values with annotate() and for loop in ascending order properly?

Without any order by, you can not make any assumptions on the order. When subscripting, and the queryset is not ordered, Django will order by primary key to prevent returning the same record multiple times. You can order with:

def test(request):
    qs = Category.objects.annotate(Count('product')).order_by('pk')

    for obj in qs:
        print(obj.id, obj.name, obj.product__count)

    return HttpResponse('Test')
Back to Top