Skip to main content

Django Virtual Models Icon

Django Virtual Models

Improve performance and maintainability with a prefetching layer in your Django / Django REST Framework project

Test   Coverage Status   Package version   Supported Python versions


Documentation: https://vintasoftware.github.io/django-virtual-models/

Example project: https://github.com/vintasoftware/django-virtual-models/tree/main/example

Source Code: https://github.com/vintasoftware/django-virtual-models


Django Virtual Models introduces a new "prefetching layer" to Django codebases that assists developers to express complex read logic without sacrificing maintainability, composability and performance. A Virtual Model allows developers to declare all nesting they need along with all annotations, prefetches, and joins in a single declarative class.

When implementing Django REST Framework serializers, developers need to be careful to avoid causing the N+1 selects problem due to missing prefetch_related or select_related calls on the associated queryset. Additionaly, developers must not miss annotate calls for fields that are computed at queryset-level.

With Virtual Models integration with DRF, if you change a DRF Serializer, you won't forget to modify the associated queryset with additional annotations, prefetches, and joins. If you do forget to update the queryset, Django Virtual Models will guide you by raising friendly exceptions to assist you to write the correct Virtual Model for the serializer you're changing. This guidance will prevent N+1s and missing annotations in all serializers that use Virtual Models.

For example, imagine if you have following nested serializers starting from MovieSerializer:

from movies.models import Nomination, Person, Movie

class AwardSerializer(serializers.ModelSerializer):
    class Meta:
        model = Nomination
        fields = ["award", "category", "year", "is_winner"]

class PersonSerializer(serializers.ModelSerializer):
    awards = AwardSerializer(many=True)
    nomination_count = serializers.IntegerField(read_only=True)

    class Meta:
        model = Person
        fields = ["name", "awards", "nomination_count"]

class MovieSerializer(serializers.ModelSerializer):
    directors = PersonSerializer(many=True)

    class Meta:
        model = Movie
        fields = ["name", "directors"]

For good performance and correct functionality, all nested serializers must have a corresponding prefetch_related on the queryset used by MovieSerializer. Also, the nomination_count field should be annotated on it. Therefore, you'll need to write this complex chain of nested prefetches:

from django.db.models import Prefetch

awards_qs = Nomination.objects.filter(is_winner=True)

directors_qs = Person.objects.prefetch_related(
    Prefetch(
        "nominations",
        queryset=awards_qs,
        to_attr="awards"
    )
).annotate(
    nomination_count=Count("nominations")
).distinct()

qs = Movie.objects.prefetch_related(
    Prefetch(
        "directors",
        queryset=directors_qs
    )
)

Conversely, you can declare Virtual Models for this read logic to easily reuse and customize those classes in multiple places of the codebase:

import django_virtual_models as v

class VirtualAward(v.VirtualModel):
    class Meta:
        model = Nomination

    def get_prefetch_queryset(self, **kwargs):
        return Nomination.objects.filter(is_winner=True)


class VirtualPerson(v.VirtualModel):
    awards = VirtualAward(lookup="nominations")
    nomination_count = v.Annotation(
        lambda qs, **kwargs: qs.annotate(
            nomination_count=Count("nominations")
        ).distinct()
    )

    class Meta:
        model = Person


class VirtualMovie(v.VirtualModel):
    directors = VirtualPerson()

    class Meta:
        model = Movie

To configure your DRF view and serializer to use Virtual Models, inherit from the proper classes:

import django_virtual_models as v

class MovieSerializer(v.VirtualModelSerializer):
    ...

    class Meta:
        ...
        virtual_model = VirtualMovie

class MovieList(v.VirtualModelListAPIView):
    queryset = Movie.objects.all()
    serializer_class = MovieSerializer
    ...

Then the library will automatically do the right prefetches and annotations for you!

If, for example, you forget to add the nomination_count field on VirtualPerson, the following exception will appear when using MovieSerializer:

MissingVirtualModelFieldException exception

If you aren't using DRF serializers, you hydrate your queryset with virtual fields manually:

qs = VirtualMovie().get_optimized_queryset(
    Movie.objects.all(),
    lookup_list=[
        "directors__awards",
        "directors__nomination_count",
    ]
)

To learn more, check the Installation and the Tutorial. Or the example project.

Metadata

Release files for django-virtual-models 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-virtual-models 0.4.0
File Size Uploaded
django_virtual_models-0.4.0.tar.gz 54.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-virtual-models 0.4.0
File Interpreter ABI Platform
django_virtual_models-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 75.9 kB

Release files / django_virtual_models-0.4.0.tar.gz

Download URL django_virtual_models-0.4.0.tar.gz
Size 54.1 kB
Tags Source
SHA-256 checksum
How to use checksums
874643330c799ec38e79adf0b032ae5635352fbc84a04f198df9a4ba071fe854
BLAKE2b-256 checksum
How to use checksums
92bade3b1b7ca7c75dfff1c701cc10d37ab06418481874b51c458daba89ab56e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via python-requests/2.32.3

Release files / django_virtual_models-0.4.0-py3-none-any.whl

Download URL django_virtual_models-0.4.0-py3-none-any.whl
Size 21.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2ae100ef5c423921924480e57da45d4e6a58ab0e91e094cc694ebd4c2286f04c
BLAKE2b-256 checksum
How to use checksums
3a0e778cb79c45e6668a7c55dff9cd97fb8545343ae581caaa3c2f7895a1438c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via python-requests/2.32.3

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page