Skip to content

Commit 98cc2a4

Browse files
Add xlsx_auto_filter option (#115)
* Add xlsx_auto_filter option Filters can automatically be added to the header row by setting xlsx_auto_filter. * Reformat with Ruff * Fix type hint for fixture * Tweak test for readability --------- Co-authored-by: Bruno Alla <[email protected]>
1 parent 5401ad2 commit 98cc2a4

File tree

6 files changed

+37
-3
lines changed

6 files changed

+37
-3
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ By default, headers will use the same 'names' as they are returned by the API. T
235235

236236
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`.
237237

238+
### Auto filter header fields
239+
240+
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.
241+
238242
### Ignore fields
239243

240244
By default, all fields are exported, but you might want to exclude some fields from your export. To do so, you can set an array with fields you want to exclude: `xlsx_ignore_headers = [<excluded fields>]`.

drf_excel/renderers.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
9494
# Make column headers
9595
column_titles = column_header.get("titles", [])
9696

97+
# Check for auto_filter
98+
auto_filter = get_attribute(drf_view, "xlsx_auto_filter", False)
99+
97100
# If we have results, then flatten field names
98101
if len(results):
99102
# Set `xlsx_use_labels = True` inside the API View to enable labels.
@@ -216,6 +219,10 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
216219
self._make_body(body, row, row_count)
217220
row_count += 1
218221

222+
# Enable auto filters if requested
223+
if auto_filter and column_count:
224+
self.ws.auto_filter.ref = f"A1:{get_column_letter(column_count)}{row_count}"
225+
219226
# Set sheet view options
220227
# Example:
221228
# sheet_view_options = {

tests/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ def worksheet(workbook: Workbook) -> Worksheet:
1818

1919

2020
@pytest.fixture
21-
def workbook_reader() -> Callable[[Union[bytes, str]], Workbook]:
22-
def reader_func(buffer: Union[bytes, str]) -> Workbook:
21+
def workbook_reader() -> Callable[[Union[bytes, str], bool], Workbook]:
22+
def reader_func(buffer: Union[bytes, str], read_only: bool = True) -> Workbook:
2323
io_buffer = io.BytesIO(buffer)
24-
return load_workbook(io_buffer, read_only=True)
24+
return load_workbook(io_buffer, read_only=read_only)
2525

2626
return reader_func

tests/test_viewset_mixin.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,16 @@ def test_dynamic_field_viewset(api_client, workbook_reader):
126126

127127
row_1_values = [cell.value for cell in data]
128128
assert row_1_values == ["YUL", "CDG", "YYZ", "MAR"]
129+
130+
131+
def test_auto_filter_viewset(api_client, workbook_reader):
132+
ExampleModel.objects.create(title="test 1", description="This is a test")
133+
134+
response = api_client.get("/auto-filter/")
135+
assert response.status_code == 200
136+
137+
# Note: auto_filter.ref is not available for read-only workbooks
138+
wb = workbook_reader(response.content, read_only=False)
139+
sheet = wb.worksheets[0]
140+
141+
assert sheet.auto_filter.ref == "A1:B2"

tests/testapp/views.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,11 @@ def list(self, request, *args, **kwargs):
5151
)
5252
serializer.is_valid(raise_exception=True)
5353
return Response(serializer.data)
54+
55+
56+
class AutoFilterViewSet(XLSXFileMixin, ReadOnlyModelViewSet):
57+
queryset = ExampleModel.objects.all()
58+
serializer_class = ExampleSerializer
59+
renderer_classes = (XLSXRenderer,)
60+
61+
xlsx_auto_filter = True

tests/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from .testapp.views import (
44
AllFieldsViewSet,
5+
AutoFilterViewSet,
56
DynamicFieldViewSet,
67
ExampleViewSet,
78
SecretFieldViewSet,
@@ -12,5 +13,6 @@
1213
router.register(r"all-fields", AllFieldsViewSet)
1314
router.register(r"secret-field", SecretFieldViewSet)
1415
router.register(r"dynamic-field", DynamicFieldViewSet, basename="dynamic-field")
16+
router.register(r"auto-filter", AutoFilterViewSet, basename="auto-filter")
1517

1618
urlpatterns = router.urls

0 commit comments

Comments
 (0)