DRF Excel Renderer

raw JSON →
2.5.3 verified Mon Apr 27 auth: no python

Django REST Framework renderer for generating Excel spreadsheet (.xlsx) files from DRF responses using OpenPyXL. Current version 2.5.3, requires Python >=3.9, DRF >=3.14. Release cadence: irregular, with maintenance updates and occasional bug fixes.

pip install drf-excel
error 'NoneType' object has no attribute 'sheet'
cause Attempting to access sheet properties after rendering without creating the workbook properly.
fix
Ensure the view correctly returns an XLSX response; use XLSXMixin and renderer_classes correctly.
error ImportError: cannot import name 'XLSXFileRenderer' from 'drf_excel'
cause Importing from wrong module; correct import path is from drf_excel.renderers.
fix
Replace 'from drf_excel import XLSXFileRenderer' with 'from drf_excel.renderers import XLSXFileRenderer'.
breaking Version 2.2.0 removed NullBooleanField and requires DRF >=3.14. Upgrade from older versions may break if using NullBooleanField or older DRF.
fix Upgrade to DRF >=3.14 and replace NullBooleanField with BooleanField(null=True).
gotcha Pagination can break the renderer if your view uses pagination. The renderer expects the full list, not a paginated response.
fix Disable pagination for the export endpoint, or override list() to return the entire queryset.
gotcha If you use `_lazy` translations in serializer fields, they may not be rendered correctly in Excel without special handling.
fix Upgrade to drf-excel >=2.4.0 for lazy translation support.

Basic usage: mixin + renderer, minimal setup.

from rest_framework import viewsets
from drf_excel.renderers import XLSXFileRenderer
from drf_excel.mixins import XLSXMixin
from myapp.models import MyModel
from myapp.serializers import MySerializer

class MyViewSet(XLSXMixin, viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MySerializer
    renderer_classes = [XLSXFileRenderer]
    filename = 'my_export.xlsx'
    
    def get_queryset(self):
        # Add filtering if needed
        return super().get_queryset().filter(is_active=True)