[Django]-How overwrite Response class in django rest framework ( DRF )?

25πŸ‘

βœ…

To resolve this, best practice (that DRF has proposed) is to use β€˜renderer’ classes. A renderer manipulates and returns structured response.

Django uses renderers like Template Renderer and DRF benefits this feature and provides API Renderers.

To do so, you could provide such this renderer in a package (e.g. app_name.renderers.ApiRenderer):

from rest_framework.renderers import BaseRenderer
from rest_framework.utils import json


class ApiRenderer(BaseRenderer):

    def render(self, data, accepted_media_type=None, renderer_context=None):
        response_dict = {
            'status': 'failure',
            'data': {},
            'message': '',
        }
        if data.get('data'):
            response_dict['data'] = data.get('data')
        if data.get('status'):
            response_dict['status'] = data.get('status')
        if data.get('message'):
            response_dict['message'] = data.get('message')
        data = response_dict
        return json.dumps(data)

And then in your settings file:

REST_FRAMEWORK = {
    ...
    'DEFAULT_RENDERER_CLASSES': (
        'app_name.renderers.ApiRenderer',
    ),
    ...
}

By this action all views that extend DRF generic views will use renderer. If you needed to override setting you can use renderer_classes attribute for generic view classes and @renderer_classes decorator for api view functions.

A comprehensive renderer class to override is available at <virtualenv_dir>/lib/python3.6/site-packages/rest_framework/renderers.py.

πŸ‘€Ali Farhoudi

12πŸ‘

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

In case of Generic views, the data argument we receive in the render() method is sent automatically by the generic view itself (if not overriding the methods, which will be against DRY), so we cannot handle it, as it does in the accepted answer.

Also, the checks in render() can be easily altered as per the needs (Eg., handling no-2XX status codes 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)


πŸ‘€Code With Me

5πŸ‘

Just an addition: I prefer to inherit from JSONRenderer. That way you get the nice formatting and indentation out of the box

    from rest_framework.renderers import JSONRenderer
    
    class CustomRenderer(JSONRenderer):
          
          def render(self, data, accepted_media_type=None, renderer_context=None):
              response = {
                 'error': False,
                 'message': 'Success',
                 'data': data
              }

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

Then in your views:

    from rest_framework.renderers import BrowsableAPIRenderer
    from api.renderers import CustomRenderer

    class MyViewSet(viewsets.ModelViewSet):
          renderer_classes = [CustomRenderer, BrowsableAPIRenderer]
          
          ...

When used with the BrowsableAPIRenderer as shown above, you get your nicely formatted custom response rendered in DRF’s Browsable API

1πŸ‘

Did you try to write custom Response middleware:

class ResponseCustomMiddleware(MiddlewareMixin):
    def __init__(self, *args, **kwargs):
        super(ResponseCustomMiddleware, self).__init__(*args, **kwargs)

    def process_template_response(self, request, response):

        if not response.is_rendered and isinstance(response, Response):
            if isinstance(response.data, dict):
                message = response.data.get('message', 'Some error occurred')
                if 'data' not in response.data:
                    response.data = {'data': response.data}
                response.data.setdefault('message', message)
                # you can add you logic for checking in status code is 2** or 4**.
                data_status = 'unknown'
                if response.status_code // 100 == 2:
                    data_status = 'success'
                elif response.status_code // 100 == 4:
                    data_status = 'failure'
                response.data.setdefault('data_status', data_status)
        return response

Add middleware in settings:

MIDDLEWARE = [
    # you all middleware here,
    'common.middleware.ResponseCustomMiddleware',
]

So you can return Response like this:

data = {'var1': 1, 'var2': 2}
return Response({'data': data, 'message': 'This is my message'}, status=status.HTTP_201_CREATED)

Response will be like:

{
  "data": [
    {
        "var1": 1,
        "var2": 2
    }
  ],
  "message": "This is my message",
  "data_status": "success"
}
πŸ‘€Sergey Pugach

0πŸ‘

This is how I solve the problem. I hope it helps

    def custom_response(data, code=None, message=None):
      if not code and not message:
        code = SUCCESSFUL_CODE
        message = SUCCESSFUL_MESSAGE
      return Response(OrderedDict([
        ('code', code),
        ('message', message),
        ('results', data)
    ]))

Now in your views function. You can custom the response however you want pretty easy return custom_response(data=..,message=...,code=...)

πŸ‘€Quang VΓ΅

Leave a comment