Dynamic DRF serializers

Python

21 Jan 2021 | 4 minute read

I'm currently working a React Native/Django app that includes some social features, and I want to ensure that users only expose limited information to non-friend users and display the full information to the friends approved by the user.

After some investigation, I figured that the most convenient way to implement this would be to override the corresponding methods in the ViewSets and serialize the user data with the correct serializer on a per-object basis depending on if the conditions to display the full user information are fulfilled.

In the example below I use a utility function called userCanAccessFullUser to evaluate if the users are friends or not.

class UserViewSet(
    viewsets.GenericViewSet,
    mixins.RetrieveModelMixin,
):
    queryset = User.objects.all()
    serializer_class = MinimalUserSerializer
    permission_classes = [IsAuthenticated]

    def retrieve(self, request, *args, **kwargs):
        self.user = self.get_object()
        if userCanAccessFullUser(request.user, self.user):
            serializer = UserSerializer(self.user)
        else:
            serializer = MinimalUserSerializer(self.user)
        return Response(serializer.data)

Using the logic above, I'm able to respond with the correct data for each user depending on which users the request.user are friends with.

To test that your view is working as expected here's some test inspiration to get you started. 🌟

class UserViewSetTest(APITestCase):
    def setUp(self):
        self.user = User.objects.create_user("user", "[email protected]")
        self.user2 = User.objects.create_user("user2", "[email protected]")

    def test_get_non_friend_returns_minimal_info(self):
        self.client.force_authenticate(self.user)
        response = self.client.get(
            reverse("users:users-detail", args=[self.user2.id]),
            content_type="application/json",
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertTrue("field_only_available_for_friends" not in response.data)

    def test_get_friend_returns_full_information(self):
        self.client.force_authenticate(self.user)
        friendship_request = Friend.objects.add_friend(
            self.user, get_object_or_404(User, pk=self.user2.id)
        )
        friendship_request.accept()
        response = self.client.get(
            reverse("users:users-detail", args=[self.user2.id]),
            content_type="application/json",
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertTrue("field_only_available_for_friends" in response.data)