diff --git a/README.md b/README.md index c923236..743890c 100644 --- a/README.md +++ b/README.md @@ -235,11 +235,13 @@ By default, headers will use the same 'names' as they are returned by the API. T 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`. -### Ignore fields +### Specify or ignore fields -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 = []`. +By default, all fields are exported. However, this behavior can be changed. -This also works with nested fields, separated with a dot (i.e. `icon.url`). +To include only a specified list of fields, provide them with: `xlsx_specify_headers = []`. Conversely, to exclude certain fields from your export, provide them with: `xlsx_ignore_headers = []`. + +These both work with nested fields, separated with a dot (i.e. `icon.url`). ### Date/time and number formatting Formatting for cells follows [openpyxl formats](https://openpyxl.readthedocs.io/en/stable/_modules/openpyxl/styles/numbers.html). diff --git a/drf_excel/renderers.py b/drf_excel/renderers.py index 002d7ba..e3c7814 100644 --- a/drf_excel/renderers.py +++ b/drf_excel/renderers.py @@ -42,6 +42,7 @@ class XLSXRenderer(BaseRenderer): format = "xlsx" # Reserved word, but required by BaseRenderer combined_header_dict = {} fields_dict = {} + specify_headers = None ignore_headers = [] boolean_display = None column_data_styles = None @@ -99,7 +100,8 @@ def render(self, data, accepted_media_type=None, renderer_context=None): # Set `xlsx_use_labels = True` inside the API View to enable labels. use_labels = getattr(drf_view, "xlsx_use_labels", False) - # A list of header keys to ignore in our export + # A list of header keys to use or ignore in our export + self.specify_headers = getattr(drf_view, "xlsx_specify_headers", None) self.ignore_headers = getattr(drf_view, "xlsx_ignore_headers", []) # Create a mapping dict named `xlsx_boolean_labels` inside the API View. @@ -277,8 +279,10 @@ def _get_label(parent_label, label_sep, obj): _fields = serializer.fields for k, v in _fields.items(): new_key = f"{parent_key}{key_sep}{k}" if parent_key else k - # Skip headers we want to ignore - if new_key in self.ignore_headers or getattr(v, "write_only", False): + # Skip headers that weren't in the list (if present) or were specifically ignored + if self.specify_headers is not None and new_key not in self.specify_headers or \ + new_key in self.ignore_headers or \ + getattr(v, "write_only", False): continue # Iterate through fields if field is a serializer. Check for labels and # append if `use_labels` is True. Fallback to using keys. diff --git a/tests/test_viewset_mixin.py b/tests/test_viewset_mixin.py index 31b2911..5e34edd 100644 --- a/tests/test_viewset_mixin.py +++ b/tests/test_viewset_mixin.py @@ -126,3 +126,19 @@ def test_dynamic_field_viewset(api_client, workbook_reader): row_1_values = [cell.value for cell in data] assert row_1_values == ["YUL", "CDG", "YYZ", "MAR"] + + +def test_specify_headers(api_client, workbook_reader): + AllFieldsModel.objects.create(title="Hello", age=36) + + response = api_client.get("/specify-headers/") + assert response.status_code == 200 + + wb = workbook_reader(response.content) + sheet = wb.worksheets[0] + + header, data = list(sheet.rows) + + assert len(header) == 1 + assert len(data) == 1 + assert header[0].value == "title" diff --git a/tests/testapp/views.py b/tests/testapp/views.py index 77c43a8..de2d005 100644 --- a/tests/testapp/views.py +++ b/tests/testapp/views.py @@ -51,3 +51,11 @@ def list(self, request, *args, **kwargs): ) serializer.is_valid(raise_exception=True) return Response(serializer.data) + + +class SpecifyHeadersViewSet(XLSXFileMixin, ReadOnlyModelViewSet): + queryset = AllFieldsModel.objects.all() + serializer_class = AllFieldsSerializer + renderer_classes = (XLSXRenderer,) + + xlsx_specify_headers = ['title'] diff --git a/tests/urls.py b/tests/urls.py index 8eadca0..bf850af 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -5,6 +5,7 @@ DynamicFieldViewSet, ExampleViewSet, SecretFieldViewSet, + SpecifyHeadersViewSet, ) router = routers.SimpleRouter() @@ -12,5 +13,6 @@ router.register(r"all-fields", AllFieldsViewSet) router.register(r"secret-field", SecretFieldViewSet) router.register(r"dynamic-field", DynamicFieldViewSet, basename="dynamic-field") +router.register(r"specify-headers", SpecifyHeadersViewSet, basename="specify-headers") urlpatterns = router.urls