| title | 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.
Strawberry Django provides two decorators for adding model properties:
@model_property: Similar to Python's@propertybut with optimization hints@cached_model_property: Similar to Django's@cached_propertybut with optimization hints
Both decorators accept the same optimization parameters as strawberry_django.field(), allowing the optimizer to properly prefetch or select related data.
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.quantityimport strawberry_django
from strawberry import auto
@strawberry_django.type(models.OrderItem)
class OrderItem:
price: auto
quantity: auto
total: auto # Automatically resolved with optimization hintsThe only parameter tells the optimizer to ensure price and quantity are fetched from the database when total is requested.
For expensive computations that should only be calculated once per instance, use @cached_model_property:
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.
Model properties accept the same optimization hints as strawberry_django.field(). See the Query Optimizer guide for complete details.
Model properties integrate seamlessly with strawberry.auto:
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}"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-
Always provide optimization hints: If your property accesses model fields or relations, specify them in the decorator parameters.
-
Use cached_model_property for expensive operations: If the calculation is expensive and doesn't depend on mutable data, use caching.
-
Keep properties focused: Complex business logic should be in separate service classes, not in model properties.
-
Type annotations are required: Always provide return type annotations for model properties.
-
Document your properties: Add clear docstrings that will appear in your GraphQL schema.
-
Test with the optimizer: Ensure your optimization hints actually work by checking the generated SQL queries.
-
Use
len()instead of.count()with prefetch_related: When accessing prefetched relationships, uselen()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: ignoreIf your model property is still causing N+1 queries:
- Check that optimization hints match the actual database access
- Ensure the Query Optimizer Extension is enabled
- Verify that
onlyincludes all accessed fields - Use
select_relatedfor foreign keys, notprefetch_related
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()- Query Optimizer - Understanding optimization hints
- Custom Resolvers - Alternative approaches for computed fields
- Fields - Basic field definition and customization