To decrease the database queries inside of your view you can create an attribute for your view, so called property. With the creation of the attribute of the view you can make a database query once and use in later use of the attribute the cache to perform actions.
The @property
function has just self
as parameter, no other parameter.
In this example the property/attribute
of the view is static. But you can create a property to make this property/attribute dynamic. If you create this property with no condition, then every time the view is called, you will have every time a different value.
Whereas an attribute of the model is a static value, it is every time the same except you change the value in the database.
Examples and visualization of the creation of a property:
first example:
# terminal
>>> class A:
... foobar = timezone.now()
...
>>> a = A()
>>> a.foobar
datetime.datetime(2023, 11, 7, 10, 5, 54, 841005, tzinfo=datetime.timezone.utc)
>>> a.foobar
datetime.datetime(2023, 11, 7, 10, 5, 54, 841005, tzinfo=datetime.timezone.utc)
>>> a.foobar
datetime.datetime(2023, 11, 7, 10, 5, 54, 841005, tzinfo=datetime.timezone.utc)
As you can see every time we call a.foobar
the time is the same because we stored the time inside the value of foorbar
.
second example
# terminal
>>> class A:
... @property
... def foobar(self):
... return timezone.now()
...
>>> a = A()
>>> a.foobar
datetime.datetime(2023, 11, 7, 10, 9, 39, 384008, tzinfo=datetime.timezone.utc)
>>> a.foobar
datetime.datetime(2023, 11, 7, 10, 9, 48, 224372, tzinfo=datetime.timezone.utc)
>>> a.foobar
datetime.datetime(2023, 11, 7, 10, 9, 56, 199316, tzinfo=datetime.timezone.utc)
As you can see here the time is changing, every time we call foobar
we get the current time.
third example
# terminal
>>> class A:
... _foobar = None
... @property
... def foobar(self):
... if self._foobar is None:
... self._foobar = timezone.now()
... return self._foobar
...
>>> a = A()
>>> a.foobar
datetime.datetime(2023, 11, 7, 10, 23, 1, 962058, tzinfo=datetime.timezone.utc)
>>> a.foobar
datetime.datetime(2023, 11, 7, 10, 23, 1, 962058, tzinfo=datetime.timezone.utc)
>>> a.foobar
datetime.datetime(2023, 11, 7, 10, 23, 1, 962058, tzinfo=datetime.timezone.utc)
This time the value of the time is stored inside a value in the view. The time is created and stored inside the value _foobar
and every time we call the a.foobar
we receive the same return value.
An example to show the reduction of database queries in one view:
first example: several database queries
# views.py
class ContributorViewSet(ModelViewSet):
def get_queryset(self):
project = get_object_or_404(Project, pk=self.kwargs["project_pk"])
return project.contributors.all()
def perform_create(self, serializer):
project_id = self.kwargs["project_pk"]
project = (
Project.objects.filter(pk=project_id)
.prefetch_related("contributors")
.first()
)
project.contributors
.add(serializer.validated_data["user"])
def perform_destroy(self, instance):
project_id = self.kwargs["project_pk"]
project = get_object_or_404(Project, pk=project_id)
project.contributors.remove(instance)
In this above example I do in every function (get_queryset, perform_create and perform_destroy) a database query for the project model.
- get_queryset: project = get_object_or_404(Project, pk=self.kwargs["project_pk"])
- perform_create: project = Project.objects.filter(pk=project_id).prefetch_related("contributors")
- perform_destroy: project = get_object_or_404(Project, pk=project_id)
Let's see what a creation of a project
property/attribute of the view will change.
second example: creation of the property in view
# views.py
class ContributorViewSet(ModelViewSet):
# create this variable to avoid unnecessary database queries
_project = None
@property
def project(self):
"""create an attribute 'project' inside the ContributorViewSet
this attribute is available in the view and can be called/available in the serializer
"""
# if the view was never executed before, will make the database query
# otherwise _project will have a value and no database query will be performed
if self._project is None:
self._project = get_object_or_404(
Project.objects.all().prefetch_related("contributors"),
pk=self.kwargs["project_pk"],
)
return self._project
def get_queryset(self):
return self.project.contributors.all()
def perform_create(self, serializer):
self.project.contributors.add(serializer.validated_data["user"])
def perform_destroy(self, instance):
self.project.contributors.remove(instance)
vs
# views.py
class ContributorViewSet(ModelViewSet):
# create this variable to avoid unnecessary database queries
_project = None
@property
def project(self):
if not hasattr(self, '_project'):
self._project = get_object_or_404(
Project.objects.all().prefetch_related("contributors"),
pk=self.kwargs["project_pk"],
)
return self._project
def get_queryset(self):
return self.project.contributors.all()
def perform_create(self, serializer):
self.project.contributors.add(serializer.validated_data["user"])
def perform_destroy(self, instance):
self.project.contributors.remove(instance)
Now in this two examples in view, there is just one database query for the project and each time, I need to use the project instance I reference to the view attribute self.project
. With self.project
we can access the attribute of the view.
Even in the serializer of this view we can use the attribute 'project', like this:
serializers.py
class ContributorSerializer(serializers.ModelSerializer):
user = serializers.IntegerField(write_only=True)
class Meta:
model = UserModel
fields = ["id", "user"]
def validate_user(self, value):
if self.context["view"].project.contributors.filter(pk=value).exists():
raise serializers.ValidationError(
"This user is already a contributor of this project."
)
return user
To use the view attribute 'project' we can access it by self.context['view'].project
. The serializer contains all context of the view by this approach.
Top comments (0)