Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use serializer context in virtual model #35

Merged
merged 3 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
)
Loading