Model Properties

Model properties allow you to add computed fields directly to your Django models while providing optimization hints for the GraphQL query optimizer. This feature is particularly useful when you want to expose derived data in your GraphQL schema without triggering N+1 queries or deferred attribute issues.

Overview

Strawberry Django provides two decorators for adding model properties:

Both decorators accept the same optimization parameters as strawberry_django.field() , allowing the optimizer to properly prefetch or select related data.

Basic Usage

Basic Model Property

models.py
from decimal import Decimal
from django.db import models
from strawberry_django.descriptors import model_property
class OrderItem(models.Model):
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.IntegerField()
@model_property(only=["price", "quantity"])
def total(self) -> Decimal:
"""Calculate the total price for this order item."""
return self.price * self.quantity
types.py
import strawberry_django
from strawberry import auto
@strawberry_django.type(models.OrderItem)
class OrderItem:
price: auto
quantity: auto
total: auto # Automatically resolved with optimization hints

The only parameter tells the optimizer to ensure price and quantity are fetched from the database when total is requested.

Cached Model Property

For expensive computations that should only be calculated once per instance, use @cached_model_property :

models.py
from django.db import models
from strawberry_django.descriptors import cached_model_property
class Product(models.Model):
name = models.CharField(max_length=100)
@cached_model_property(
prefetch_related=["reviews"]
)
def average_rating(self) -> float:
"""Calculate average rating from all reviews."""
reviews = list(self.reviews.all())
if not reviews:
return 0.0
return sum(r.rating for r in reviews) / len(reviews)

The computed value is cached on the instance after the first access, avoiding redundant calculations.

Optimization Parameters

Model properties accept the same optimization hints as strawberry_django.field() . See the Query Optimizer guide for complete details.

Combining with GraphQL Types

Model properties integrate seamlessly with strawberry.auto :

models.py
from decimal import Decimal
from django.db import models
from django.db.models import Sum
from strawberry_django.descriptors import model_property, cached_model_property
class Order(models.Model):
customer = models.ForeignKey("Customer", on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=20)
@model_property(
prefetch_related=["items"],
annotate={"_total": Sum("items__total")}
)
def total_amount(self) -> Decimal:
"""Calculate total order amount."""
return self._total or Decimal(0) # type: ignore
@cached_model_property(select_related=["customer"])
def customer_name(self) -> str:
"""Get the customer's full name."""
return f"{self.customer.first_name} {self.customer.last_name}"
types.py
import strawberry_django
from strawberry import auto
@strawberry_django.type(models.Order)
class Order:
created_at: auto
status: auto
total_amount: auto # Uses model_property optimization hints
customer_name: auto # Uses cached_model_property hints

Best Practices

  1. Always provide optimization hints: If your property accesses model fields or relations, specify them in the decorator parameters.

  2. Use cached_model_property for expensive operations: If the calculation is expensive and doesn’t depend on mutable data, use caching.

  3. Keep properties focused: Complex business logic should be in separate service classes, not in model properties.

  4. Type annotations are required: Always provide return type annotations for model properties.

  5. Document your properties: Add clear docstrings that will appear in your GraphQL schema.

  6. Test with the optimizer: Ensure your optimization hints actually work by checking the generated SQL queries.

  7. Use len() instead of .count() with prefetch_related: When accessing prefetched relationships, use len() to avoid bypassing the prefetch cache:

# ❌ Bad: .count() bypasses prefetch cache and hits database
@model_property(prefetch_related=["books"])
def book_count(self) -> int:
return self.books.count() # Issues a COUNT(*) query!
# βœ… Good: len() uses prefetch cache
@model_property(prefetch_related=["books"])
def book_count(self) -> int:
return len(self.books.all()) # Uses prefetched data
# βœ… Best: Use database annotation when prefetch not needed
@model_property(annotate={"_book_count": Count("books")})
def book_count(self) -> int:
return self._book_count # type: ignore

Troubleshooting

Property triggers extra queries

If your model property is still causing N+1 queries:

  1. Check that optimization hints match the actual database access
  2. Ensure the Query Optimizer Extension is enabled
  3. Verify that only includes all accessed fields
  4. Use select_related for foreign keys, not prefetch_related

Type resolution errors

If Strawberry can’t resolve the type:

# ❌ Missing return type annotation
@model_property(only=["name"])
def display_name(self):
return self.name.upper()
# βœ… With return type annotation
@model_property(only=["name"])
def display_name(self) -> str:
return self.name.upper()

See Also