Skip to content

Commit 9a0f47f

Browse files
committed
Merge branch 'main' into patch-1
# Conflicts: # drf_excel/fields.py
2 parents e662c39 + 339e088 commit 9a0f47f

23 files changed

+1114
-34
lines changed

.github/workflows/test.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
9+
jobs:
10+
lint:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: actions/setup-python@v5
15+
with:
16+
python-version: "3.12"
17+
- uses: pre-commit/[email protected]
18+
- uses: pre-commit-ci/[email protected]
19+
if: always()
20+
21+
test:
22+
runs-on: ubuntu-latest
23+
strategy:
24+
fail-fast: false
25+
matrix:
26+
python-version:
27+
- "3.8"
28+
- "3.9"
29+
- "3.10"
30+
- "3.11"
31+
- "3.12"
32+
steps:
33+
- uses: actions/checkout@v4
34+
- uses: actions/setup-python@v5
35+
with:
36+
python-version: ${{ matrix.python-version }}
37+
- run: python -m pip install tox
38+
- run: tox -f py$(echo ${{ matrix.python-version }} | tr -d .)
39+
- uses: codecov/codecov-action@v4
40+
with:
41+
token: ${{ secrets.CODECOV_TOKEN }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ Desktop.ini
3333

3434
# Coverage
3535
htmlcov/
36+
.coverage
37+
coverage.xml

.pre-commit-config.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v4.6.0
4+
hooks:
5+
- id: check-case-conflict
6+
- id: check-merge-conflict
7+
- id: check-symlinks
8+
- id: check-json
9+
- id: check-toml
10+
- id: check-yaml
11+
- id: debug-statements
12+
- id: end-of-file-fixer
13+
- id: trailing-whitespace
14+
15+
- repo: https://github.com/astral-sh/ruff-pre-commit
16+
rev: v0.6.8
17+
hooks:
18+
- id: ruff-format
19+
- id: ruff

README.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# DRF Excel: Django REST Framework Excel Spreadsheet (xlsx) Renderer
22

3+
[![codecov](https://codecov.io/gh/wharton/drf-excel/graph/badge.svg?token=EETTI9XRNO)](https://codecov.io/gh/wharton/drf-excel)
4+
35
`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.
46

57
## Requirements
@@ -56,7 +58,7 @@ To upgrade to `drf_excel` 2.0.0 from `drf_renderer_xlsx`, update your import pat
5658
* `drf_renderer_xlsx.renderers.XLSXRenderer` becomes `drf_excel.renderers.XLSXRenderer`.
5759
* `xlsx_date_format_mappings` has been removed in favor of `column_data_styles` which provides more flexibility
5860

59-
## Configuring Styles
61+
## Configuring Styles
6062

6163
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](https://openpyxl.readthedocs.io/en/stable/styles.html) can be used for font, alignment, fill and border_side (border will always be all side of cell).
6264

@@ -148,7 +150,7 @@ def get_header(self):
148150
datetime_format = "%H:%M:%S %d.%m.%Y"
149151
return {
150152
'tab_title': 'MyReport', # title of tab/workbook
151-
'use_header': True, # show the header_title
153+
'use_header': True, # show the header_title
152154
'header_title': 'Report from {} to {}'.format(
153155
start_time.strftime(datetime_format),
154156
end_time.strftime(datetime_format),
@@ -200,7 +202,7 @@ They can be set in the view as a property `sheet_view_options`:
200202
```python
201203
class MyExampleViewSet(serializers.Serializer):
202204
sheet_view_options = {
203-
'rightToLeft': True,
205+
'rightToLeft': True,
204206
'showGridLines': False
205207
}
206208
```
@@ -209,18 +211,18 @@ or using method `get_sheet_view_options`:
209211

210212
```python
211213
class MyExampleViewSet(serializers.Serializer):
212-
214+
213215
def get_sheet_view_options(self):
214216
return {
215-
'rightToLeft': True,
217+
'rightToLeft': True,
216218
'showGridLines': False
217219
}
218220
```
219221
## Controlling XLSX headers and values
220222

221223
### Use Serializer Field labels as header names
222224

223-
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.
225+
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.
224226

225227
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`.
226228

@@ -248,9 +250,9 @@ DRF_EXCEL_DECIMAL_FORMAT = '0.00E+00'
248250

249251
### Name boolean values
250252

251-
`True` and `False` as values for boolean fields are not always the best representation and don't support translation.
253+
`True` and `False` as values for boolean fields are not always the best representation and don't support translation.
252254

253-
This can be controlled with in you API view with `xlsx_boolean_labels`.
255+
This can be controlled with in you API view with `xlsx_boolean_labels`.
254256

255257
```
256258
xlsx_boolean_labels = {True: _('Yes'), False: _('No')}
@@ -282,7 +284,7 @@ def custom_value_formatter(val):
282284
return val + '!!!'
283285

284286
### Example response:
285-
{
287+
{
286288
results: [
287289
{
288290
title: 'XLSX renderer',
@@ -336,4 +338,4 @@ xlsx_custom_mappings = {
336338
* [Thomas Willems](https://github.com/willtho89)
337339
* [Mathieu Rampant](https://github.com/rptmat57)
338340

339-
This package was created by the staff of [Wharton Research Data Services](https://wrds.wharton.upenn.edu/). We are thrilled that [The Wharton School](https://www.wharton.upenn.edu/) 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.
341+
This package is a member of [Django Commons](https://github.com/django-commons/) and adheres to the community's [Code of Conduct](https://github.com/django-commons/membership/blob/main/CODE_OF_CONDUCT.md). This package was created by the staff of [Wharton Research Data Services](https://wrds.wharton.upenn.edu/). We are thrilled that [The Wharton School](https://www.wharton.upenn.edu/) 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.

drf_excel/fields.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,14 @@ def __init__(self, **kwargs):
8585
super().__init__(**kwargs)
8686

8787
def init_value(self, value):
88-
8988
with contextlib.suppress(Exception):
90-
if isinstance(self.drf_field, IntegerField) and type(value) != int:
89+
if isinstance(self.drf_field, IntegerField) and type(value) is not int:
9190
return int(value)
92-
elif isinstance(self.drf_field, FloatField) and type(value) != float:
91+
elif isinstance(self.drf_field, FloatField) and type(value) is not float:
9392
return float(value)
94-
elif isinstance(self.drf_field, DecimalField) and type(value) != Decimal:
93+
elif (
94+
isinstance(self.drf_field, DecimalField) and type(value) is not Decimal
95+
):
9596
return Decimal(value)
9697

9798
return value
@@ -129,18 +130,24 @@ def init_value(self, value):
129130
try:
130131
if (
131132
isinstance(self.drf_field, DateTimeField)
132-
and type(value) != datetime.datetime
133+
and type(value) is not datetime.datetime
133134
):
134135
return self._parse_date(
135136
value, "DATETIME_FORMAT", parse_datetime
136137
).replace(tzinfo=None)
137-
elif isinstance(self.drf_field, DateField) and type(value) != datetime.date:
138+
elif (
139+
isinstance(self.drf_field, DateField)
140+
and type(value) is not datetime.date
141+
):
138142
return self._parse_date(value, "DATE_FORMAT", parse_date)
139-
elif isinstance(self.drf_field, TimeField) and type(value) != datetime.time:
143+
elif (
144+
isinstance(self.drf_field, TimeField)
145+
and type(value) is not datetime.time
146+
):
140147
return self._parse_date(value, "TIME_FORMAT", parse_time).replace(
141148
tzinfo=None
142149
)
143-
except:
150+
except Exception:
144151
pass
145152
return value
146153

@@ -162,7 +169,11 @@ def __init__(self, list_sep, **kwargs):
162169
def prep_value(self) -> Any:
163170
if self.value is None:
164171
return super().prep_value()
165-
elif len(self.value) > 0 and isinstance(self.value[0], Iterable) and not isinstance(self.value[0], str):
172+
if (
173+
len(self.value) > 0
174+
and isinstance(self.value[0], Iterable)
175+
and not isinstance(self.value[0], str)
176+
):
166177
# array of array; write as json
167178
return json.dumps(self.value, ensure_ascii=False)
168179
else:

drf_excel/mixins.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ def finalize_response(self, request, response, *args, **kwargs):
2121
Return the response with the proper content disposition and the customized
2222
filename instead of the browser default (or lack thereof).
2323
"""
24-
response = super().finalize_response(
25-
request, response, *args, **kwargs
26-
)
24+
response = super().finalize_response(request, response, *args, **kwargs)
2725
if (
2826
isinstance(response, Response)
2927
and response.accepted_renderer.format == "xlsx"

drf_excel/renderers.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
5656
"""
5757
Render `data` into XLSX workbook, returning a workbook.
5858
"""
59-
if not self._check_validation_data(data):
60-
return json.dumps(data)
61-
6259
if data is None:
6360
return bytes()
6461

62+
if not self._check_validation_data(data):
63+
return json.dumps(data)
64+
6565
wb = Workbook()
6666
self.ws = wb.active
6767

@@ -232,7 +232,7 @@ def _save_virtual_workbook(self, wb):
232232
with TemporaryFile() as tmp:
233233
save_workbook(wb, tmp)
234234
tmp.seek(0)
235-
virtual_workbook = tmp.read()
235+
virtual_workbook = tmp.read()
236236

237237
return virtual_workbook
238238

@@ -266,7 +266,11 @@ def _flatten_serializer_keys(
266266

267267
def _get_label(parent_label, label_sep, obj):
268268
if getattr(v, "label", None):
269-
return f"{parent_label}{label_sep}{v.label}" if parent_label else str(v.label)
269+
return (
270+
f"{parent_label}{label_sep}{v.label}"
271+
if parent_label
272+
else str(v.label)
273+
)
270274
else:
271275
return False
272276

@@ -346,9 +350,7 @@ def _make_body(self, body, row, row_count):
346350

347351
if "row_color" in row:
348352
last_letter = get_column_letter(column_count)
349-
cell_range = self.ws[
350-
f"A{row_count}" : f"{last_letter}{row_count}"
351-
]
353+
cell_range = self.ws[f"A{row_count}" : f"{last_letter}{row_count}"]
352354
fill = PatternFill(fill_type="solid", start_color=row["row_color"])
353355

354356
for r in cell_range:
@@ -380,7 +382,7 @@ def _drf_to_xlsx_field(self, key, value) -> XLSXField:
380382
elif isinstance(field, (IntegerField, FloatField, DecimalField)):
381383
return XLSXNumberField(**kwargs)
382384
elif isinstance(field, (DateTimeField, DateField, TimeField)):
383-
return XLSXDateField(**kwargs)
385+
return XLSXDateField(**kwargs)
384386
elif (
385387
isinstance(field, ListField)
386388
or isinstance(value, Iterable)

drf_excel/utilities.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from django.conf import settings as django_settings
2-
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
2+
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE, Cell
33
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
44

55
ESCAPE_CHARS = ("=", "-", "+", "@", "\t", "\r", "\n")
@@ -86,7 +86,7 @@ def sanitize_value(value):
8686
return value
8787

8888

89-
def set_cell_style(cell, style: XLSXStyle):
89+
def set_cell_style(cell: Cell, style: XLSXStyle):
9090
# We are not applying the whole style directly, otherwise we cannot override any part of it
9191
if style:
9292
# Only set properties that are provided

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ build-backend = "setuptools.build_meta"
5454
write_to = "drf_excel/_version.py"
5555

5656
[tool.pytest.ini_options]
57-
addopts = "--cov --cov-report=html"
57+
addopts = "--cov --cov-report=xml --cov-report=term"
5858
python_files = "tests.py test_*.py"
5959
DJANGO_SETTINGS_MODULE = "tests.settings"
6060

tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)