Skip to content

Add new option xlsx_specify_headers #114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [<excluded fields>]`.
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 = [<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`).

### Date/time and number formatting
Formatting for cells follows [openpyxl formats](https://openpyxl.readthedocs.io/en/stable/_modules/openpyxl/styles/numbers.html).
Expand Down
10 changes: 7 additions & 3 deletions drf_excel/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
16 changes: 16 additions & 0 deletions tests/test_viewset_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
8 changes: 8 additions & 0 deletions tests/testapp/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
2 changes: 2 additions & 0 deletions tests/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
DynamicFieldViewSet,
ExampleViewSet,
SecretFieldViewSet,
SpecifyHeadersViewSet,
)

router = routers.SimpleRouter()
router.register(r"examples", ExampleViewSet)
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
Loading