Integration with django-virtual-models
django-virtual-models
is a library that automatically optimizes querysets based on which serializer
fields are present. When combined with django-dynamic-serializer, the
prefetch strategy is driven entirely by your get_serializer_fields()
declaration — relations that are stripped from the serializer are never
prefetched.
Why it matters
Without virtual models you must manually keep querysets and serializer fields in
sync. If a view only needs ["id", "title"] but the queryset still calls
prefetch_related("reviews"), the database does extra work for data that is
never serialized.
With virtual models the queryset optimization is automatic: the library
inspects the serializer fields and generates only the necessary
select_related / prefetch_related calls.
How the integration works
The integration relies on two hooks:
DynamicSerializerView._get_empty_serializer()creates a serializer instance withinstance=Noneand thefieldskwarg applied. This produces a serializer whose.fieldsdict contains only the fields declared inget_serializer_fields().VirtualModelListAPIView.get_queryset()(from django-virtual-models) calls_get_empty_serializer()and passes the result to itsLookupFinder. The finder inspects the filtered fields and generates prefetch calls only for the relations that are still present.
The flow looks like this:
GET request
-> DynamicSerializerView._get_empty_serializer()
-> creates serializer with field selection applied
-> VirtualModelListAPIView.get_queryset()
-> LookupFinder inspects filtered serializer.fields
-> generates only the needed prefetch_related() calls
-> queryset is evaluated with minimal queries
Setup
Step 1: Install the dependency
pip install django-virtual-models>=0.1.4
Step 2: Define virtual models
Create a virtual model for each related model. The virtual model tells django-virtual-models how to prefetch the relation:
import django_virtual_models as v
from .models import Author, Book, Review
class VirtualAuthor(v.VirtualModel):
class Meta:
model = Author
class VirtualReview(v.VirtualModel):
class Meta:
model = Review
class VirtualBook(v.VirtualModel):
author = VirtualAuthor()
review_list = VirtualReview(lookup="reviews")
class Meta:
model = Book
VirtualBook.author tells the library to prefetch the author FK.
review_list uses lookup="reviews" to prefetch the reverse FK into a
to_attr="review_list" attribute, avoiding a conflict with Book’s default
reviews related manager.
Step 3: Create serializers with VirtualModelSerializer
Replace ModelSerializer with VirtualModelSerializer and point
Meta.virtual_model to the corresponding virtual model:
from django_virtual_models.serializers import VirtualModelSerializer
from django_dynamic_serializer import DynamicSerializerFieldsMixin
class AuthorVirtualSerializer(DynamicSerializerFieldsMixin, VirtualModelSerializer):
class Meta:
model = Author
fields = ["id", "name", "birth_year"]
virtual_model = VirtualAuthor
class ReviewVirtualSerializer(DynamicSerializerFieldsMixin, VirtualModelSerializer):
class Meta:
model = Review
fields = ["id", "rating", "comment"]
virtual_model = VirtualReview
class BookVirtualSerializer(DynamicSerializerFieldsMixin, VirtualModelSerializer):
author = AuthorVirtualSerializer()
reviews = ReviewVirtualSerializer(source="review_list", many=True)
class Meta:
model = Book
fields = ["id", "title", "isbn", "author", "reviews"]
virtual_model = VirtualBook
Note source="review_list" on the reviews field — this reads from the
to_attr set by the virtual model prefetch.
Step 4: Create views
Combine DynamicSerializerView with VirtualModelListAPIView (or
VirtualModelRetrieveAPIView). You do not need to write a custom
get_queryset() — the virtual model layer handles it automatically:
from django_virtual_models.generic_views import VirtualModelListAPIView
from django_dynamic_serializer import DynamicSerializerView
class BookVirtualModelListView(DynamicSerializerView, VirtualModelListAPIView):
serializer_class = BookVirtualSerializer
queryset = Book.objects.all()
def get_serializer_fields(self):
return [
"id",
"title",
{"object_name": "author", "fields": ["id", "name"]},
{"object_name": "reviews", "fields": ["id", "rating"]},
]
The queryset starts as a plain Book.objects.all(). The virtual model layer
inspects the field-filtered serializer and adds the necessary prefetches before
evaluation.
Performance comparison
The following table shows query counts for different field selections, all using
the same BookVirtualSerializer and VirtualModelListAPIView:
Fields requested |
Queries |
What happens |
|---|---|---|
|
3 |
1 query for books + 1 for authors (prefetch) + 1 for reviews (prefetch) |
|
2 |
1 query for books + 1 for authors (prefetch); reviews skipped |
|
1 |
1 query for books only (no prefetches) |
The key insight: query count scales with the number of requested relations, not
with the number of rows. Adding more books does not increase the query count
because prefetch_related batches all related objects in a single query per
relation.
Note
django-virtual-models uses prefetch_related for all relations,
including forward ForeignKey fields like Book.author. In plain Django you
would use select_related("author") to fetch the author via a JOIN in the
same query as books (1 query instead of 2). The trade-off is that virtual
models give you automatic optimization driven by serializer fields,
whereas select_related / prefetch_related must be maintained manually.
For comparison, without virtual models:
Fields requested |
Manual optimization |
Virtual models |
|---|---|---|
|
2 queries (JOIN + prefetch) |
3 queries (prefetch + prefetch) |
|
1 query (JOIN) |
2 queries (prefetch) |
|
1 query |
1 query |
Manual optimization uses select_related("author") for the FK (JOIN) and
prefetch_related("reviews") for the reverse FK. Virtual models use
prefetch_related for both, resulting in one extra query when the author
relation is included. The benefit is that you do not need to write or update
querysets when the field selection changes.
Without virtual models you would need to manually write a different queryset for
each view to achieve the same result. With this integration the optimization is
automatic — just change get_serializer_fields() and the queryset adapts.
Retrieve views
The same pattern works for single-object endpoints using
VirtualModelRetrieveAPIView:
from django_virtual_models.generic_views import VirtualModelRetrieveAPIView
class BookVirtualModelDetailView(DynamicSerializerView, VirtualModelRetrieveAPIView):
serializer_class = BookVirtualSerializer
queryset = Book.objects.all()
def get_serializer_fields(self):
return [
"id",
"title",
"isbn",
{"object_name": "author", "fields": ["id", "name", "birth_year"]},
{"object_name": "reviews", "fields": ["id", "rating", "comment"]},
]
This executes 3 queries (1 for the book + 1 for authors prefetch + 1 for reviews prefetch) regardless of how many reviews exist.