Skip to content

Commit

Permalink
Merge pull request #35 from jackson541/feat/use-serializer-context
Browse files Browse the repository at this point in the history
Use serializer context in virtual model
  • Loading branch information
fjsj authored Jan 18, 2024
2 parents b7c2220 + b561490 commit 9d8a8af
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 5 deletions.
2 changes: 1 addition & 1 deletion AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ django-virtual-models is maintained by [Vinta Software](https://www.vintasoftwar
Contributors
------------

None yet. Why not be the first?
* Jackson Alves Sousa (https://github.com/jackson541/)
2 changes: 1 addition & 1 deletion CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ this is how you set up your fork for local development::

4. Install the project and the dev requirements::

$ pip install -e .[doc,dev,test]
$ pip install -e ".[doc,dev,test]"

5. Install pre-commit checks::

Expand Down
4 changes: 3 additions & 1 deletion django_virtual_models/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ def get_optimized_queryset(self, initial_queryset):
"Using virtual models on %(cls_name)s. Finding lookup_list...",
{"cls_name": cls_name},
)
virtual_model_instance = virtual_model(user=self.get_request_user())
virtual_model_instance = virtual_model(
user=self.get_request_user(), serializer_context=self.context
)
lookup_list = serializer_optimization.LookupFinder(
serializer_instance=self,
virtual_model=virtual_model_instance,
Expand Down
49 changes: 47 additions & 2 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,8 @@ Imagine you have a `UserMovieRating` model that relates users with movies and ha

```python
class UserMovieRating(models.Model):
user = models.ForeignKey("User")
movie = models.ForeignKey("Movie", related_name="ratings")
user = models.ForeignKey("User", on_delete=models.CASCADE)
movie = models.ForeignKey("Movie", related_name="ratings", on_delete=models.CASCADE)
rating = models.DecimalField(max_digits=3, decimal_places=1)
```

Expand Down Expand Up @@ -367,6 +367,51 @@ class VirtualUserMovieRating(v.VirtualModel):
!!! warning
An advice: in general, you should avoid returning data relative to the current user in HTTP APIs, as this makes caching very hard or even impossible. Use this only if you really need it, as in a request that's only specific for users like user profile pages. Avoid nesting data related to the current user inside global data. Consider adding an additional request to fetch data relative to the current user, and then "hydrate" the previous request data on the frontend.


### Using Serializer's context
Similar to how `user` param works, it's possible to use in virtual models any data that the view passes to the serializer by context.

Using the previous example, suppose you want to get the vote count equal to a specific value that is defined in the view. To do this, use the serializer context:

```python
class MovieListView(v.VirtualModelListAPIView):
serializer_class = MovieSerializer
queryset = Movie.objects.all()

def get_serializer_context(self):
context = super().get_serializer_context()
vote_value = 5
return {**context, 'vote_value': vote_value}
```

Then, annotate the number of votes in the Virtual Model with the `serializer_context` param:

```python
class VirtualMovie(v.VirtualModel):
voting_count = v.Annotation(
lambda qs, user, serializer_context, **kwargs: qs.annotate(
voting_count=Count("ratings", filter=Q(ratings__rating=serializer_context['vote_value']))
)
)

class Meta:
model = Movie
```

And, to reflect the change, add the new field in `MovieSerializer`:

```python
class MovieSerializer(v.VirtualModelSerializer):
voting_count = serializers.IntegerField()

class Meta:
model = Movie
virtual_model = VirtualMovie
fields = ["name", "voting_count"]
```

Just like the `user` param, `serializer_context` can be used in the `get_prefetch_queryset` method.

### Ignoring a serializer field

If you have a serializer field that fetches data from somewhere other than your Django models, you cannot prefetch data for it with a Virtual Model. So you need to make the Virtual Model ignore that field.
Expand Down
Empty file.
45 changes: 45 additions & 0 deletions tests/virtual_model_serializers/test_virtual_model_serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from unittest.mock import MagicMock

from django.test import TestCase

from rest_framework import serializers

from model_bakery import baker

import django_virtual_models as v

from ..virtual_models.models import Course, User


class VirtualModelSerializerTest(TestCase):
def test_pass_serializer_context_to_virtual_model(self):
class MockedVirtualCourse(v.VirtualModel):
something = v.Annotation(
lambda qs, user, **kwargs: qs.annotate_something(user=user, **kwargs)
)

class Meta:
model = Course
deferred_fields = ["name", "description", "something"]

class CourseSerializer(v.VirtualModelSerializer):
something = serializers.CharField()

class Meta:
model = Course
virtual_model = MockedVirtualCourse
fields = ["name", "description", "something"]

user = baker.make(User)
user.is_anonymous = False
request = MagicMock()
request.method = "GET"
request.user = user
serializer_context = {"request": request, "value": 12345}
mock_qs = MagicMock()
virtual_course_serializer = CourseSerializer(instance=None, context=serializer_context)
virtual_course_serializer.get_optimized_queryset(mock_qs)

mock_qs.annotate_something.assert_called_once_with(
user=user, serializer_context=serializer_context
)

0 comments on commit 9d8a8af

Please sign in to comment.