How do you order querysets in Django?
How to use order_by to sort in ascending and descending order, break ties with more than one field, ignore case, control where null values go and set a default order on the model.
- django
- python
I first published this article on dev.to in August 2024. In this version I fixed the tie-breaking example, which talked about different prices but used dates, and added case-insensitive ordering, null values, related fields and the model’s default order. The examples were tested with Django 6.1.
The examples use a Product model with a name, price, creation date and category.
Ascending order
Ascending order sorts items from smallest to largest: A before B, 1 before 2, the oldest date before the newest. It is the default for order_by:
Product.objects.order_by("name")
The .all() before order_by in the original version is not needed, because order_by is already available on the manager.
Descending order
Descending order does the opposite, from largest to smallest. Just put a - before the field name:
Product.objects.order_by("-name")
Breaking ties with more than one field
order_by accepts several fields. The second one is only used when the first ties, the third when the first two tie, and so on. Picture two products with the same name and different creation dates:
| Name | Created on |
|---|---|
| Product A | 2024-08-01 |
| Product A | 2024-08-02 |
| Product B | 2024-08-03 |
| Product C | 2024-08-04 |
| Product D | 2024-08-05 |
To list them by name and, among products with the same name, show the newest first:
Product.objects.order_by("name", "-created_at")
The result looks like this:
| Name | Created on |
|---|---|
| Product A | 2024-08-02 |
| Product A | 2024-08-01 |
| Product B | 2024-08-03 |
| Product C | 2024-08-04 |
| Product D | 2024-08-05 |
Without the second field, the order between the two “Product A” rows is up to the database and may change from one query to the next. That matters for pagination, where an item can show up on two pages while another disappears. When the first field can repeat, end the ordering with a unique field, such as id.
Calling order_by twice does not add up the criteria. The second call replaces the first, so order_by("name").order_by("-created_at") sorts by date only.
Ignoring case
Text comparison follows the database collation. In SQLite, for example, every uppercase letter comes before the lowercase ones, and “product a” shows up after “Product D”. To sort without telling uppercase from lowercase, use the Lower function:
from django.db.models.functions import Lower
Product.objects.order_by(Lower("name"), "-created_at")
For descending order, use Lower("name").desc().
Where null values go
If the field accepts null, each database decides where to put the nulls. PostgreSQL treats null as larger than any value, so order_by("-price") shows the products without a price first. SQLite and MySQL do the opposite. To get the same result on any database, say explicitly where the nulls go with F:
from django.db.models import F
Product.objects.order_by(F("price").desc(nulls_last=True))
There is also nulls_first=True, and both work with .asc().
Ordering by related fields
To sort by a field of another model, use __, just like in filters:
Product.objects.order_by("category__name", "name")
The query joins the categories table and sorts by the category name and then by the product name.
Default order on the model
If an order applies to almost every query, it can go in the model’s Meta:
class Product(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateField()
class Meta:
ordering = ["name", "-created_at"]
Any order_by in the query replaces this order, and order_by() with no arguments removes the ordering, which helps when you only need a count or an aggregation. To get only the newest or the oldest record, Product.objects.latest("created_at") and earliest("created_at") save you from writing the order_by and slicing the result.