Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django REST framework custom format for all out responses

In my project I use DRF as backend and Angular as frontend.

Django==1.10 djangorestframework==3.7.1

I need all responses from DRF to be in the following format.

{
 "status": "",  //  200,400,.....etc
 "error": "",   //  True, False
 "data": [],    //  data
 "message": ""  //  Success messages
}

Currently it is in

[
    {
        "id": 1,
        "name": ""
    },
    {
        "id": 2,
        "name": ""
    }
]

it should be

{
     "status": "200",   
     "error": "False",      
     "data": [
               {
                   "id": 1,
                   "name": ""
                },
                {
                   "id": 2,
                   "name": ""
                }
            ],    
     "message": "Success"  
}

for this i have written a custom viewset and overridden the functions list, detail, create, update

class ResponseModelViewSet(viewsets.ModelViewSet):
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        custom_data = {
            "status": True,
            "error": False,
            "message": 'message',
            "data": serializer.data
        }
        return Response(custom_data)

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)

        custom_data = {
            "status": True,
            "error": False,
            "message": 'message',
            "data": serializer.data
        }

        return Response(custom_data, status=status.HTTP_201_CREATED, headers=headers)

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        custom_data = {
            "status": True,
            "error": False,
            "message": 'message',
            "data": serializer.data
        }
        return Response(custom_data)

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        custom_data = {
            "status": True,
            "error": False,
            "message": 'message',
            "data": serializer.data
        }
        return Response(custom_data)

and in views i use my custom viewset

from common.baseview import ResponseModelViewSet

class PositionViewsets(ResponseModelViewSet):
    serializer_class = PositionSerializer
    permission_classes = (IsAuthenticated,)
    model = Position

    def get_queryset(self):
        return Position.objects.filter(order__user=self.request.user)

I am not sure if this is the correct way of doing it or there is some other efficient way to do it. Anyway this works for my custom apps but not for the Authentication app i used the default rest apps

'rest_framework.authtoken',
'rest_auth',

For login using username and password and get the success response as follows.

{
    "key": "e642efd0b78e08b57bf34fa999f49b70a7bfe21a"
}

Instead i need this.

{
 "status": "200",   
 "error": "False",      
 "data": [
          {
           "token":{ 
                     "key":"e642efd0b78e08b57bf34fa999f49b70a7bfe21a"
                    }
           }
         ],     
 "message": "Login Sucess"  
}

for error

{
 "status": "error",     
 "error": "True",   
 "data": [
           {
            "email": ["Enter a valid email address."]
            }
         ],     
 "message": "Login Failed"  
}
like image 672
Christo Jose Avatar asked Nov 08 '17 07:11

Christo Jose


2 Answers

After some research I found a way to do this. I had to override the default behaviour of the ModelViewSet to output a different response.

I created a custom Response format initially:

class ResponseInfo(object):
    def __init__(self, user=None, **args):
        self.response = {
            "status": args.get('status', True),
            "error": args.get('error', 200),
            "data": args.get('data', []),
            "message": args.get('message', 'success')
        }

Then use this custom format in every method of the ModelViewSet:

class ResponseModelViewSet(viewsets.ModelViewSet):
    def __init__(self, **kwargs):
        self.response_format = ResponseInfo().response
        super(ResponseModelViewSet, self).__init__(**kwargs)

    def list(self, request, *args, **kwargs):
        response_data = super(ResponseModelViewSet, self).list(request, *args, **kwargs)
        self.response_format["data"] = response_data.data
        self.response_format["status"] = True
        if not response_data.data:
            self.response_format["message"] = "List empty"
        return Response(self.response_format)

    def create(self, request, *args, **kwargs):
        response_data = super(ResponseModelViewSet, self).create(request, *args, **kwargs)
        self.response_format["data"] = response_data.data
        self.response_format["status"] = True
        return Response(self.response_format)

    def retrieve(self, request, *args, **kwargs):
        response_data = super(ResponseModelViewSet, self).retrieve(request, *args, **kwargs)
        self.response_format["data"] = response_data.data
        self.response_format["status"] = True
        if not response_data.data:
            self.response_format["message"] = "Empty"
        return Response(self.response_format)

    def update(self, request, *args, **kwargs):
        response_data = super(ResponseModelViewSet, self).update(request, *args, **kwargs)
        self.response_format["data"] = response_data.data
        self.response_format["status"] = True

        return Response(self.response_format)

    def destroy(self, request, *args, **kwargs):
        response_data = super(ResponseModelViewSet, self).destroy(request, *args, **kwargs)
        self.response_format["data"] = response_data.data
        self.response_format["status"] = True
        return Response(self.response_format)
like image 71
Christo Jose Avatar answered Oct 30 '22 19:10

Christo Jose


This would be more a more robust solution, as it can be used with Generic Views hassle free.

Also, the checks in render() can be easily altered as per the needs (Eg., handling no-2XX in this solution).

from rest_framework.renderers import JSONRenderer


class CustomRenderer(JSONRenderer):

    def render(self, data, accepted_media_type=None, renderer_context=None):
        status_code = renderer_context['response'].status_code
        response = {
          "status": "success",
          "code": status_code,
          "data": data,
          "message": None
        }

        if not str(status_code).startswith('2'):
            response["status"] = "error"
            response["data"] = None
            try:
                response["message"] = data["detail"]
            except KeyError:
                response["data"] = data

        return super(CustomRenderer, self).render(response, accepted_media_type, renderer_context)

like image 25
Code With Me Avatar answered Oct 30 '22 19:10

Code With Me