Skip to main content

DRF Excel: Django REST Framework Excel Spreadsheet (xlsx) Renderer

PyPI - License Ruff PyPI version PyPI python versions PyPI django versions PyPI status CI codecov

drf-excel provides an Excel spreadsheet (xlsx) renderer for Django REST Framework. It uses OpenPyXL to create the spreadsheet and provide the file to the end user.

Requirements

We aim to support Django's currently supported versions, as well as:

  • Django REST Framework >= 3.14
  • OpenPyXL >= 2.4

Installation

pip install drf-excel

Then add the following to your REST_FRAMEWORK settings:

REST_FRAMEWORK = {
    ...

    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
        'drf_excel.renderers.XLSXRenderer',
    ),
}

To avoid having a file streamed without a filename (which the browser will often default to the filename "download", with no extension), we need to use a mixin to override the Content-Disposition header. If no filename is provided, it will default to export.xlsx. For example:

from rest_framework.viewsets import ReadOnlyModelViewSet
from drf_excel.mixins import XLSXFileMixin
from drf_excel.renderers import XLSXRenderer

from .models import MyExampleModel
from .serializers import MyExampleSerializer

class MyExampleViewSet(XLSXFileMixin, ReadOnlyModelViewSet):
    queryset = MyExampleModel.objects.all()
    serializer_class = MyExampleSerializer
    renderer_classes = (XLSXRenderer,)
    filename = 'my_export.xlsx'
    pagination_class = None  # Only required if custom pagination is used

The XLSXFileMixin also provides a get_filename() method which can be overridden, if you prefer to provide a filename programmatically instead of the filename attribute.

Upgrading to 2.0.0

To upgrade to drf_excel 2.0.0 from drf_renderer_xlsx, update your import paths:

  • from drf_renderer_xlsx.mixins import XLSXFileMixin becomes from drf_excel.mixins import XLSXFileMixin.
  • drf_renderer_xlsx.renderers.XLSXRenderer becomes drf_excel.renderers.XLSXRenderer.
  • xlsx_date_format_mappings has been removed in favor of column_data_styles which provides more flexibility

Configuring Styles

Styles can be added to your worksheet header, column header row, body and column data from view attributes header, column_header, body, column_data_styles. Any arguments from the OpenPyXL package can be used for font, alignment, fill and border_side (border will always be all side of cell).

If provided, column data styles will override body style

Note that column data styles can take an extra 'format' argument that follows openpyxl formats.

class MyExampleViewSet(XLSXFileMixin, ReadOnlyModelViewSet):
    queryset = MyExampleModel.objects.all()
    serializer_class = MyExampleSerializer
    renderer_classes = (XLSXRenderer,)

    column_header = {
        'titles': [
            "Column_1_name",
            "Column_2_name",
            "Column_3_name",
        ],
        'column_width': [17, 30, 17],
        'height': 25,
        'style': {
            'fill': {
                'fill_type': 'solid',
                'start_color': 'FFCCFFCC',
            },
            'alignment': {
                'horizontal': 'center',
                'vertical': 'center',
                'wrapText': True,
                'shrink_to_fit': True,
            },
            'border_side': {
                'border_style': 'thin',
                'color': 'FF000000',
            },
            'font': {
                'name': 'Arial',
                'size': 14,
                'bold': True,
                'color': 'FF000000',
            },
        },
    }
    body = {
        'style': {
            'fill': {
                'fill_type': 'solid',
                'start_color': 'FFCCFFCC',
            },
            'alignment': {
                'horizontal': 'center',
                'vertical': 'center',
                'wrapText': True,
                'shrink_to_fit': True,
            },
            'border_side': {
                'border_style': 'thin',
                'color': 'FF000000',
            },
            'font': {
                'name': 'Arial',
                'size': 14,
                'bold': False,
                'color': 'FF000000',
            }
        },
        'height': 40,
    }
    column_data_styles = {
        'distance': {
            'alignment': {
                'horizontal': 'right',
                'vertical': 'top',
            },
            'format': '0.00E+00'
        },
        'created_at': {
            'format': 'd.m.y h:mm',
        }
    }

You can dynamically generate style attributes in methods get_body, get_header, get_column_header, get_column_data_styles.

def get_header(self):
    start_time, end_time = parse_times(request=self.request)
    datetime_format = "%H:%M:%S %d.%m.%Y"
    return {
        'tab_title': 'MyReport', # title of tab/workbook
        'use_header': True,  # show the header_title
        'header_title': 'Report from {} to {}'.format(
            start_time.strftime(datetime_format),
            end_time.strftime(datetime_format),
        ),
        'height': 45,
        'img': 'app/images/MyLogo.png',
        'style': {
            'fill': {
                'fill_type': 'solid',
                'start_color': 'FFFFFFFF',
            },
            'alignment': {
                'horizontal': 'center',
                'vertical': 'center',
                'wrapText': True,
                'shrink_to_fit': True,
            },
            'border_side': {
                'border_style': 'thin',
                'color': 'FF000000',
            },
            'font': {
                'name': 'Arial',
                'size': 16,
                'bold': True,
                'color': 'FF000000',
            }
        }
    }

Also, you can add the row_color field to your serializer and fill body rows.

class ExampleSerializer(serializers.Serializer):
    row_color = serializers.SerializerMethodField()

    def get_row_color(self, instance):
        color_map = {'w': 'FFFFFFCC', 'a': 'FFFFCCCC'}
        return color_map.get(instance.alarm_level, 'FFFFFFFF')

Configuring Sheet View Options

View options follow openpyxl sheet view options

They can be set in the view as a property sheet_view_options:

class MyExampleViewSet(serializers.Serializer):
    sheet_view_options = {
        'rightToLeft': True,
        'showGridLines': False
    }

or using method get_sheet_view_options:

class MyExampleViewSet(serializers.Serializer):

    def get_sheet_view_options(self):
        return {
            'rightToLeft': True,
            'showGridLines': False
        }

Controlling XLSX headers and values

Use Serializer Field labels as header names

By default, headers will use the same 'names' as they are returned by the API. This can be changed by setting xlsx_use_labels = True inside your API View.

Instead of using the field names, the export will use the labels as they are defined inside your Serializer. A serializer field defined as title = serializers.CharField(label=_("Some title")) would return Some title instead of title, also supporting translations. If no label is set, it will fall back to using title.

Auto filter header fields

Filters can automatically be added to the header row by setting xlsx_auto_filter = True. The filter will include all header columns in the worksheet.

Specify or ignore fields

By default, all fields are exported. However, this behavior can be changed.

To include only a specified list of fields, provide them with: xlsx_specify_headers = [<fields to include>]. Conversely, to exclude certain fields from your export, provide them with: xlsx_ignore_headers = [<excluded fields>].

These both work with nested fields, separated with a dot (i.e. icon.url). Naming a nested serializer itself (i.e. icon) includes all of the fields below it.

A few things worth knowing:

  • When both options are set, xlsx_ignore_headers wins: a field named in both is excluded.
  • Columns follow the order in which the fields are declared on the serializer, not the order of xlsx_specify_headers. Keep this in mind when combining it with the titles of column_header, which are applied positionally.
  • Names that match no serializer field are ignored silently, so a typo results in a missing column rather than an error.

Date/time and number formatting

Formatting for cells follows openpyxl formats.

To set global formats, set the following variables in settings.py:

# Date formats
DRF_EXCEL_DATETIME_FORMAT = 'mm-dd-yy h:mm AM/PM'
DRF_EXCEL_DATE_FORMAT = 'mm-dd-yy'
DRF_EXCEL_TIME_FORMAT = 'h:mm AM/PM'

# Number formats
DRF_EXCEL_INTEGER_FORMAT = '0%'
DRF_EXCEL_DECIMAL_FORMAT = '0.00E+00'

Name boolean values

True and False as values for boolean fields are not always the best representation and don't support translation.

This can be controlled with in you API view with xlsx_boolean_labels.

xlsx_boolean_labels = {True: _('Yes'), False: _('No')}

will replace True with Yes and False with No.

This can also be set globally in settings.py:

DRF_EXCEL_BOOLEAN_DISPLAY = {True: _('Yes'), False: _('No')}

Custom columns

You might find yourself explicitly returning a dict in your API response and would like to use its data to display additional columns. This can be done by passing xlsx_custom_cols.

xlsx_custom_cols = {
    'my_custom_col.val1.title': {
        'label': 'Custom column!',
        'formatter': custom_value_formatter
    }
}

### Example function:
def custom_value_formatter(val):
    return val + '!!!'

### Example response:
{
    results: [
        {
            title: 'XLSX renderer',
            url: 'https://github.com/django-commons/drf-excel'
            returned_dict: {
                val1: {
                    title: 'Sometimes'
                },
                val2: {
                    title: 'There is no way around'
                }
            }
        }
    ]
}

When no label is passed, drf-excel will display the key name in the header.

formatter is also optional and accepts a function, which will then receive the value it is mapped to (it would receive "Sometimes" and return "Sometimes!!!" in our example).

Custom mappings

Assuming you have a field that returns a dict instead of a simple str, you might not want to return the whole object but only a value of it. Let's say status returns { value: 1, display: 'Active' }. To return the display value in the status column, we can do this:

xlsx_custom_mappings = {
    'status': 'display'
}

A more common case is that you want to change how a value is formatted. xlsx_custom_mappings also takes functions as values. Assuming we have a field description, and for some strange reason want to reverse the text, we can do this:

def reverse_text(val):
    return val[::-1]

xlsx_custom_mappings = {
    'description': reverse_text
}

Release Notes and Contributors

Maintainers

This package is a member of Django Commons and adheres to the community's Code of Conduct. This package was created by the staff of Wharton Research Data Services. We are thrilled that The Wharton School allows us a certain amount of time to contribute to open-source projects. We add features as they are necessary for our projects, and try to keep up with Issues and Pull Requests as best we can. Due to constraints of time (our full time jobs!), Feature Requests without a Pull Request may not be implemented, but we are always open to new ideas and grateful for contributions and our users.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

drf_excel-2.6.0.tar.gz (55.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

drf_excel-2.6.0-py3-none-any.whl (15.6 kB view details)

Uploaded Python 3

File details

Details for the file drf_excel-2.6.0.tar.gz.

File metadata

  • Download URL: drf_excel-2.6.0.tar.gz
  • Upload date:
  • Size: 55.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for drf_excel-2.6.0.tar.gz
Algorithm Hash digest
SHA256 ea9b36f9d01a50981b4c821366847c243073dd004ebd20f62a7ddbc659791b39
MD5 5c7e9e40e44b9d181eea2997e9ca39fd
BLAKE2b-256 6d655867e155780b0a6928a961ca5931dbac9ce9a4c105aefe1d0a5e975ef98f

See more details on using hashes here.

Provenance

The following attestation bundles were made for drf_excel-2.6.0.tar.gz:

Publisher: release.yml on django-commons/drf-excel

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file drf_excel-2.6.0-py3-none-any.whl.

File metadata

  • Download URL: drf_excel-2.6.0-py3-none-any.whl
  • Upload date:
  • Size: 15.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for drf_excel-2.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 609a2c882a2ec393357b5a0bf3f6bc0d6b6622f0f0baab09b77152c37e0f5f71
MD5 89a985bc9458682528b5875718f80c20
BLAKE2b-256 a0a4edc327efb441d8690198391acd190d8ac22458eae35d1fd68afe4297e987

See more details on using hashes here.

Provenance

The following attestation bundles were made for drf_excel-2.6.0-py3-none-any.whl:

Publisher: release.yml on django-commons/drf-excel

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page