Skip to content

Commit 8bab66d

Browse files
committed
Add new option xlsx_specify_headers
The xlsx_ignore_headers option allows specific fields to be excluded when enumerating the serializer. This adds the ability to explicitly specify the fields that should be included. The absence of the option will result in the default behavior.
1 parent 5401ad2 commit 8bab66d

File tree

5 files changed

+38
-6
lines changed

5 files changed

+38
-6
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,13 @@ 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-
### Ignore fields
238+
### Specify or ignore fields
239239

240-
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>]`.
240+
By default, all fields are exported. However, this behavior can be changed.
241241

242-
This also works with nested fields, separated with a dot (i.e. `icon.url`).
242+
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>]`.
243+
244+
These both work with nested fields, separated with a dot (i.e. `icon.url`).
243245

244246
### Date/time and number formatting
245247
Formatting for cells follows [openpyxl formats](https://openpyxl.readthedocs.io/en/stable/_modules/openpyxl/styles/numbers.html).

drf_excel/renderers.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class XLSXRenderer(BaseRenderer):
4242
format = "xlsx" # Reserved word, but required by BaseRenderer
4343
combined_header_dict = {}
4444
fields_dict = {}
45+
specify_headers = None
4546
ignore_headers = []
4647
boolean_display = None
4748
column_data_styles = None
@@ -99,7 +100,8 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
99100
# Set `xlsx_use_labels = True` inside the API View to enable labels.
100101
use_labels = getattr(drf_view, "xlsx_use_labels", False)
101102

102-
# A list of header keys to ignore in our export
103+
# A list of header keys to use or ignore in our export
104+
self.specify_headers = getattr(drf_view, "xlsx_specify_headers", None)
103105
self.ignore_headers = getattr(drf_view, "xlsx_ignore_headers", [])
104106

105107
# Create a mapping dict named `xlsx_boolean_labels` inside the API View.
@@ -277,8 +279,10 @@ def _get_label(parent_label, label_sep, obj):
277279
_fields = serializer.fields
278280
for k, v in _fields.items():
279281
new_key = f"{parent_key}{key_sep}{k}" if parent_key else k
280-
# Skip headers we want to ignore
281-
if new_key in self.ignore_headers or getattr(v, "write_only", False):
282+
# Skip headers that weren't in the list (if present) or were specifically ignored
283+
if self.specify_headers is not None and new_key not in self.specify_headers or \
284+
new_key in self.ignore_headers or \
285+
getattr(v, "write_only", False):
282286
continue
283287
# Iterate through fields if field is a serializer. Check for labels and
284288
# append if `use_labels` is True. Fallback to using keys.

tests/test_viewset_mixin.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,19 @@ 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_specify_headers(api_client, workbook_reader):
132+
AllFieldsModel.objects.create(title="Hello", age=36)
133+
134+
response = api_client.get("/specify-headers/")
135+
assert response.status_code == 200
136+
137+
wb = workbook_reader(response.content)
138+
sheet = wb.worksheets[0]
139+
140+
header, data = list(sheet.rows)
141+
142+
assert len(header) == 1
143+
assert len(data) == 1
144+
assert header[0].value == "title"

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 SpecifyHeadersViewSet(XLSXFileMixin, ReadOnlyModelViewSet):
57+
queryset = AllFieldsModel.objects.all()
58+
serializer_class = AllFieldsSerializer
59+
renderer_classes = (XLSXRenderer,)
60+
61+
xlsx_specify_headers = ['title']

tests/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
DynamicFieldViewSet,
66
ExampleViewSet,
77
SecretFieldViewSet,
8+
SpecifyHeadersViewSet,
89
)
910

1011
router = routers.SimpleRouter()
1112
router.register(r"examples", ExampleViewSet)
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"specify-headers", SpecifyHeadersViewSet, basename="specify-headers")
1517

1618
urlpatterns = router.urls

0 commit comments

Comments
 (0)