Search code examples
pythonexcelpandasdjango-rest-frameworkrenderer

Prevent Custom Renderer to render Exceptions in Django Rest Framework


I have written a custom Renderer that allows rendering nested json data as flat Excel files.

class ExcelRenderer(renderers.BaseRenderer):

    """Custom renderer for exporting XLS files"""

    media_type = "application/xlsx"
    format = "excel"
    render_style = 'binary'

    def render(self, data, accepted_media_type=None, renderer_context=None):

        query_params = renderer_context["request"].query_params
        query_params = "_".join([f"{v.split('T')[0]}" for k,v in query_params.items()])


        """
        Render `data` into XLSX workbook, returning a workbook.
        """
        
        try:
            df = json_into_flat_dataframe(data)
        except Exception as e:
            logger.error(e, exc_info=True)
            df = pd.DataFrame()

        if len(df.index) == 0:
            raise rest_exceptions.APIException("No data!")

        output = io.BytesIO()

        # Use a temp filename to keep pandas happy.
        writer = pd.ExcelWriter(output, engine='xlsxwriter')

        # Write the data frame to the StringIO object.
        df.to_excel(writer, sheet_name='Sheet1', index=False)
        writer.save()
        data = output.getvalue()

        #Enable direct download.
        renderer_context['response']["content-disposition"] = f"attachment; filename=vodepro_export_{query_params}.xlsx"

        return data

This works as expected as long as there is not exception (e.g. ValidationError) in the process before rendering. When a ValidationError is raised, my Custom Renderer tries to render it (and fails, since this was not even designed behaviour of Renderer).

I would like to implement a renderer, that would not try to parse ValidationErrors (or any other Exceptions in fact) into Excel, but would fallback to a normal DRF behaviour (json or BrowsableAPI renderer) in such case?


Solution

  • To be a complete answer here's the code:

    from rest_framework.renderers import JSONRenderer
    from rest_framework.views import exception_handler
    
    
    def custom_exception_handler(exc, context):
        """ Switch from PDFRenderer to JSONRenderer for exceptions """
        if context['request'].accepted_renderer.format == 'pdf':
            context['request'].accepted_renderer = JSONRenderer()
        return exception_handler(exc, context)
    

    If you already have a custom exception handler, replace it by the default one from rest_framework.views in the snippet above.

    You then need to set it on settings:

    REST_FRAMEWORK = {
        'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
    }
    

    You may refer to this answer.