Search code examples
djangorestdjango-rest-frameworkresponse

Where to pass context data in Response (Django)


I am wondering how to pass some context data to response in Django.

I have this endpoint:

/api/facebook/user/

which returns data like this:

HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

[
    {
        "facebook_account_id": "176890406129409",
        "first_name": "Ivan",
        "last_name": "K",
        "token_status": "active",
        "facebook_access_token": 1
    },
    {
        "facebook_account_id": "123123123",
        "first_name": "Ivan",
        "last_name": "FFFF",
        "token_status": null,
        "facebook_access_token": null
    },
    {
        "facebook_account_id": "123123",
        "first_name": "Test",
        "last_name": "test",
        "token_status": null,
        "facebook_access_token": null
    }
]

Here is my list serializer:

class FacebookUserListSerializer(ModelSerializer):
    token_status = SerializerMethodField()

    class Meta:
        model = FacebookUser
        fields = ["facebook_account_id", "first_name", "last_name", "token_status", "facebook_access_token"]

    def get_token_status(self, obj):
        try:
            facebook_token = obj.facebook_access_token
            if facebook_token:
                return "active" if facebook_token.is_active else "inactive"
        except FacebookToken.DoesNotExist:
            return None

Here is my list view:

def list(self, request, *args, **kwargs):
    response = super().list(request, *args, **kwargs)
    data = response.data
    inactive_accounts = sum(1 for account in data if account.get("token_status") == "inactive")
    accounts_without_token = sum(1 for account in data if account.get("token_status") is None)
    context = {"inactive_accounts": inactive_accounts, "accounts_without_token": accounts_without_token} # Is this the correct way of passing context data to a response?
    response.context_data = context
    return response

In list view, in the "context" variable, I am attempting to pass data to the frontend, but I am sure that I am doing this incorrectly, I must first convert it to JSON, or make this somewhere else.

So the question is - where (serializer, list method, or somewhere else?) and how should I put this context data according to REST conventions, so that data can be accessed in frontend without iterating over list items?


Solution

  • Do not loop through serialized data to find the information you want, use the view queryset instead. Also, there is no need to convert your response, DRF Response handles it automatically, given the correct content-type.

    Since the extra information is not directly related to your object, doing it inside the list method is just fine. Else, you would use get_serializer_context method.

    Here is a more "advanced" way on how to extract this information, using aggregation:

    views.py

    class FacebookUserList(ListAPIView):
        queryset = FacebookUser.objects.all()
        serializer_class = FacebookUserListSerializer
    
        def list(self, request):
            queryset = self.get_queryset()
    
            accounts_without_token = Q(facebook_access_token__isnull=True)
            inactive_accounts = (
                Q(facebook_access_token__is_active=False) | 
                Q(facebook_access_token__isnull=True)
            )
    
            metadata = queryset.aggregate(
                accounts_without_token=Count("pk", filter=accounts_without_token),
                inactive_accounts=Count("pk", filter=inactive_accounts),
            )
    
            serializer = self.get_serializer(
                queryset,
                many=True,
            )
    
            context = {
                "metadata": metadata,
                "users": serializer.data,
            }
            return Response(context)