Fix Django Rest Framework Tracking (drf-tracking) errors with Django model FileField

I was working recently on logging Django Rest Framework (DRF) requests and responses and also creating a download endpoint so I can protect files from being accessed by other users with the ability log requests to see who's downloading what.

So I came across this awesome DRF plugin called drf-tracking.

It so easy to use that you just need to add rest_framework_tracking.mixins.LoggingMixin to your existing DRF views like the code below:

from rest_framework import generics
from rest_framework.response import Response
from rest_framework_tracking.mixins import LoggingMixin

class LoggingView(LoggingMixin, generics.GenericAPIView):
    def get(self, request):
        return Response('with logging')

And you should see the logs in your django admin page.

Upon testing, I noticed that I get an exception when I'm sending a POST request with a file and also have the decoding error on my download api.

So I have to override the mixin methods.

from rest_framework import viewsets, renderers
from rest_framework.response import Response
from rest_framework.decorators import detail_route
from django.http import FileResponse, HttpResponse
from rest_framework_tracking.mixins import LoggingMixin
from .models import File

class BinaryFileRenderer(renderers.BaseRenderer):
    media_type = 'application/octet-stream'
    format = None
    charset = None
    render_style = 'binary'

    def render(self, data, media_type=None, renderer_context=None):
        return data

class FileViewSet(LoggingMixin, viewsets.ModelViewSet):
    queryset = File.objects.all()

    # Override finalize_response to check if response is FileResponse then I will return the rendered_content response without
    # processing it to avoid decode exceptions
    def finalize_response(self, request, response, *args, **kwargs):
        if response.__class__.__name__ == 'FileResponse':
            mixinResponse = super(FileViewSet, self).finalize_response(request, response, *args, **kwargs)
            if hasattr(response, 'rendered_content'):
                rendered_content = response.rendered_content
            else:
                rendered_content = response.getvalue()

            return HttpResponse(rendered_content)

        return super(FileViewSet, self).finalize_response(request, response, *args, **kwargs)

    # Override _clean_data to decode data with ignore instead of replace to ignore
    # errors in decode so when the logger inserts the data to db, it will not hit
    # any decoding/encoding issues
    def _clean_data(self, data):
        if isinstance(data, bytes):
            data = data.decode(errors='ignore')
        return super(FileViewSet, self)._clean_data(data)

    @detail_route(methods=['get'], renderer_classes=(BinaryFileRenderer,))
    def download(self, request, pk=None):
        queryset = File.objects.get(id=pk)
        documentFile = queryset.document
        file_handle = documentFile.open()
        # send file
        response = FileResponse(file_handle)
        return response

 

Add new comment

This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.