Why `__str__()` on Models Can Kill Performance
Comments
Sign in to join the conversation
Sign in to join the conversation
It is one of the first methods you define on a Django model. It seems harmless. You just want your object to have a nice, readable name in the Django Admin.
But __str__() (or __unicode__ in older versions) is a synchronous function that runs every time Django needs to represent your object as text. If you are not careful, this single method can trigger thousands of silent database queries and bring your admin panel to a crawl.
Here is how a simple string representation turns into a performance bottleneck.
The most common error is including a related field (ForeignKey) inside the __str__ method.
Imagine an e-commerce app with Order and Customer.
class Customer(models.Model):
name = models.CharField(max_length=100)
class Order(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
amount = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
# DANGER: This accesses a related object!
return f"Order {self.id} by {self.customer.name}"
This looks helpful. Instead of "Order object (1)", you see "Order 1 by Alice".
The performance hit doesn't happen when you fetch a single order. It happens when you list them.
When you open the list view of Orders in the Django Admin, Django fetches, say, 100 orders to display on the page.
For each of those 100 orders, Django calls __str__() to display the title in the list or the header.
str(order_1) -> hits DB to get Customer 1str(order_2) -> hits DB to get Customer 2You just created an N+1 query problem strictly for string formatting.
If you have a form that allows you to select an Order (e.g., a ModelChoiceField), Django generates a <select> dropdown. To populate the <option> labels, it calls __str__() on every single order in your database.
If you have 10,000 orders, that page load will trigger 10,001 queries (1 for the list, 10,000 for the customers) and likely time out.
The safest __str__ method only uses fields that live on the model itself.
def __str__(self):
# Fast: No database lookups required
return f"Order #{self.id} - ${self.amount}"
If you must include the customer name, you need to tell the Admin to fetch it ahead of time.
# admin.py
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
# This tells Django to use a SQL JOIN when fetching the list
list_select_related = ['customer']
Now, when the Admin lists the orders, the customer data is already in memory. The __str__ method will run without hitting the database.
For forms, you must override the queryset to use select_related.
# forms.py
class RefundForm(forms.Form):
order = forms.ModelChoiceField(
# Optimize the queryset used for the dropdown
queryset=Order.objects.select_related('customer').all()
)
The __str__ method is called far more often than you think: in admin lists, debug logs, and form dropdowns. If it accesses a ForeignKey, it triggers a database query every time. To fix this, prefer using only local fields (like IDs or names) in your string representation, or ensure you use select_related in any view or form where a list of these objects is displayed.