Skip to content

Commit f318d0b

Browse files
committed
Validate XLS rows count before exporting.
1 parent 3086712 commit f318d0b

File tree

7 files changed

+63
-0
lines changed

7 files changed

+63
-0
lines changed

CHANGELOG.txt

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
# A visitor mode has been added; from any list-view, you can visit all the related detail-views the one after the other.
1010
(button "Enter the exploration mode" in list-views).
1111
# The link in the list-views selectors are now opened in other tabs automatically.
12+
# Prevent an error when reaching 65535 rows during an .XLS file export, which is a hard limit defined by the .XLS
13+
format specs. Display a comprehensive error message prior to the export.
1214
# In forms :
1315
- The custom fields with ENUM/MULTI_ENUM type are now using the Select2 combobox with lazy loading & search.
1416
- In the form for entity-filters :

creme/creme_core/backends/base.py

+3
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,6 @@ def save(self, filename: str, user):
7171
instance of <django.contrib.auth.get_user_model()>.
7272
"""
7373
raise NotImplementedError
74+
75+
def validate(self, **kwargs):
76+
pass

creme/creme_core/backends/xls_export.py

+13
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
from django.conf import settings
2222
from django.http import HttpResponseRedirect
2323
from django.template.defaultfilters import slugify
24+
from django.utils.translation import gettext
2425
from django.utils.translation import gettext_lazy as _
2526

27+
from ..core.exceptions import ConflictError
2628
from ..models import FileRef
2729
from ..utils.file_handling import FileCreator
2830
from ..utils.xlwt_utils import XlwtWriter
@@ -34,6 +36,7 @@ class XLSExportBackend(ExportBackend):
3436
verbose_name = _('XLS File')
3537
help_text = ''
3638
dir_parts = ('xls',) # Sub-directory under settings.MEDIA_ROOT
39+
max_row_index = 65535
3740

3841
def __init__(self, encoding='utf-8'):
3942
super().__init__()
@@ -56,3 +59,13 @@ def save(self, filename, user):
5659

5760
def writerow(self, row):
5861
self.writer.writerow(row)
62+
63+
def validate(self, **kwargs):
64+
total_count = kwargs.get("total_count")
65+
if total_count and total_count > self.max_row_index:
66+
raise ConflictError(
67+
gettext(
68+
"The Microsoft Excel 97–2003 Workbook (.xls) format has a limit of"
69+
" {count} rows. Use a CSV format instead.".format(count=self.max_row_index)
70+
)
71+
)

creme/creme_core/locale/fr/LC_MESSAGES/django.po

+7
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ msgstr ""
7575
msgid "XLS File"
7676
msgstr "Fichier XLS"
7777

78+
#, python-brace-format
79+
msgid ""
80+
"The Microsoft Excel 97–2003 Workbook (.xls) format has a limit of {count} "
81+
"rows. Use a CSV format instead."
82+
msgstr "Le format Microsoft Excel 97–2003 Workbook (.xls) est limité à {count} lignes. "
83+
"Utilisez un format CSV à la place."
84+
7885
msgid ""
7986
"XLS is a file extension for a spreadsheet file format created by Microsoft "
8087
"for use with Microsoft Excel (Excel 97-2003 Workbook)."

creme/creme_core/tests/test_backends.py

+13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from creme.creme_core.backends.csv_import import CSVImportBackend
33
from creme.creme_core.backends.xls_import import XLSImportBackend
44

5+
from ..backends.xls_export import XLSExportBackend
6+
from ..core.exceptions import ConflictError
57
from .base import CremeTestCase
68

79

@@ -56,3 +58,14 @@ def test_registration_errors03(self):
5658

5759
with self.assertRaises(registry.InvalidClass):
5860
registry.get_backend_class(CSVImportBackend.id)
61+
62+
63+
class XLSExportBackendTestCase(CremeTestCase):
64+
def test_validate_total_count__lte_max(self):
65+
backend = XLSExportBackend()
66+
backend.validate(total_count=XLSExportBackend.max_row_index)
67+
68+
def test_validate_total_count__gt_max(self):
69+
backend = XLSExportBackend()
70+
with self.assertRaises(ConflictError):
71+
backend.validate(total_count=XLSExportBackend.max_row_index + 1)

creme/creme_core/tests/views/test_mass_export.py

+23
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,24 @@
4646
from creme.creme_core.utils.queries import QSerializer
4747
from creme.creme_core.utils.xlrd_utils import XlrdReader
4848

49+
from ...backends import export_backend_registry
50+
from ...backends.base import ExportBackend
51+
from ...core.exceptions import ConflictError
4952
from .base import ViewsTestCase
5053

5154

55+
class TestExportBackend(ExportBackend):
56+
id: str = 'test_backend_validator'
57+
verbose_name: str = 'test_backend_validator'
58+
help_text: str = 'test_backend_validator'
59+
60+
def writerow(self, row):
61+
pass
62+
63+
def validate(self, **kwargs):
64+
raise ConflictError("TestExportBackend.validate fail")
65+
66+
5267
class MassExportViewsTestCase(ViewsTestCase):
5368
@classmethod
5469
def setUpClass(cls):
@@ -529,6 +544,14 @@ def test_extra_filter(self):
529544
# Error
530545
self.assertGET(400, self._build_contact_dl_url(extra_q='[123]'))
531546

547+
def test_backend_validator(self):
548+
self.login_as_root()
549+
self.assertIsNone(export_backend_registry.get_backend_class(TestExportBackend.id))
550+
export_backend_registry._backend_classes[TestExportBackend.id] = TestExportBackend
551+
response = self.assertGET409(self._build_contact_dl_url(doc_type=TestExportBackend.id))
552+
self.assertContains(response, 'TestExportBackend.validate fail', status_code=409)
553+
export_backend_registry._backend_classes = None
554+
532555
def test_list_view_export_with_filter01(self):
533556
# user = self.login()
534557
user = self.login_as_root_and_get()

creme/creme_core/views/mass_export.py

+2
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ def get(self, request, *args, **kwargs):
227227
if use_distinct:
228228
entities_qs = entities_qs.distinct()
229229

230+
writer.validate(total_count=entities_qs.count())
231+
230232
paginator = self.get_paginator(queryset=entities_qs, ordering=ordering)
231233

232234
total_count = 0

0 commit comments

Comments
 (0)