Skip to content
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

Validate XLS rows count before exporting. #612

Open
wants to merge 4 commits 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
1 change: 1 addition & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# A visitor mode has been added; from any list-view, you can visit all the related detail-views the one after the other.
(button "Enter the exploration mode" in list-views).
# The link in the list-views selectors are now opened in other tabs automatically.
# The .XLS file export now displays an error when its maximum number of rows is exceeded.
# In forms :
- The custom fields with ENUM/MULTI_ENUM type are now using the Select2 combobox with lazy loading & search.
- In the form for entity-filters :
Expand Down
39 changes: 38 additions & 1 deletion creme/creme_config/apps.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
################################################################################
# Creme is a free/open-source Customer Relationship Management software
# Copyright (C) 2015-2022 Hybird
# Copyright (C) 2015-2023 Hybird
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
Expand All @@ -17,6 +17,7 @@
################################################################################

from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext

from creme.creme_core.apps import CremeAppConfig

Expand All @@ -31,9 +32,45 @@ class CremeConfigConfig(CremeAppConfig):
def all_apps_ready(self):
super().all_apps_ready()

self.hook_password_validators()

from .registry import config_registry
self.populate_config_registry(config_registry)

# TODO: define our own classes?
def hook_password_validators(self):
from django.contrib.auth import password_validation

# ---
def minlen_get_help_text(this):
return ngettext(
'The password must contain at least %(min_length)d character.',
'The password must contain at least %(min_length)d characters.',
this.min_length,
) % {'min_length': this.min_length}

password_validation.MinimumLengthValidator.get_help_text = minlen_get_help_text

# ---
def personal_get_help_text(self):
return _(
"The password can’t be too similar to the other personal information."
)

password_validation.UserAttributeSimilarityValidator.get_help_text = personal_get_help_text

# ---
def common_get_help_text(self):
return _("The password can’t be a commonly used password.")

password_validation.CommonPasswordValidator.get_help_text = common_get_help_text

# ---
def numeric_get_help_text(self):
return _("The password can’t be entirely numeric.")

password_validation.NumericPasswordValidator.get_help_text = numeric_get_help_text

def populate_config_registry(self, config_registry):
from creme.creme_core.apps import creme_app_configs

Expand Down
12 changes: 10 additions & 2 deletions creme/creme_config/forms/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ class UserPasswordChangeForm(CremeForm):
}

old_password = CharField(
label=_('Old password'),
# label=_('Old password'),
label=_('Your old password'),
strip=False,
widget=PasswordInput(attrs={'autocomplete': 'current-password', 'autofocus': True}),
)
Expand All @@ -177,9 +178,16 @@ class UserPasswordChangeForm(CremeForm):
)

def __init__(self, *args, **kwargs):
self.user2edit = kwargs.pop('instance')
self.user2edit = user2edit = kwargs.pop('instance')
super().__init__(*args, **kwargs)

if self.user != user2edit:
fields = self.fields
fields['old_password'].label = _('Your password')
fields['password_1'].label = gettext(
'New password for «{user}»'
).format(user=user2edit)

def clean_old_password(self):
old_password = self.cleaned_data["old_password"]

Expand Down
Binary file modified creme/creme_config/locale/fr/LC_MESSAGES/django.mo
Binary file not shown.
37 changes: 33 additions & 4 deletions creme/creme_config/locale/fr/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Creme Creme-Config 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-20 10:12+0200\n"
"POT-Creation-Date: 2023-09-06 13:44+0200\n"
"Last-Translator: Hybird <[email protected]>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
Expand All @@ -19,6 +19,26 @@ msgstr ""
msgid "General configuration"
msgstr "Configuration générale"

#, python-format
msgid "The password must contain at least %(min_length)d character."
msgid_plural "The password must contain at least %(min_length)d characters."
msgstr[0] ""
"Le mot de passe doit contenir au minimum %(min_length)d caractère."
msgstr[1] ""
"Le mot de passe doit contenir au minimum %(min_length)d caractères."

msgid "The password can’t be too similar to the other personal information."
msgstr ""
"Le mot de passe ne peut pas trop ressembler aux autres informations "
"personnelles."

msgid "The password can’t be a commonly used password."
msgstr ""
"Le mot de passe ne peut pas être un mot de passe couramment utilisé."

msgid "The password can’t be entirely numeric."
msgstr "Le mot de passe ne peut pas être entièrement numérique."

msgid "Export & import configuration"
msgstr "Exporter & importer la configuration"

Expand Down Expand Up @@ -702,9 +722,8 @@ msgstr "Confirmation du mot de passe"
msgid "Enter the same password as before, for verification."
msgstr ""

# Already translated in django.auth
msgid "Old password"
msgstr ""
msgid "Your old password"
msgstr "Votre ancien mot de passe"

# Already translated in django.auth
msgid "New password"
Expand All @@ -714,6 +733,13 @@ msgstr ""
msgid "New password confirmation"
msgstr ""

msgid "Your password"
msgstr "Votre mot de passe"

#, python-brace-format
msgid "New password for «{user}»"
msgstr "Nouveau mot de passe pour «{user}»"

msgid "Teammates"
msgstr "Coéquipiers"

Expand Down Expand Up @@ -2403,6 +2429,9 @@ msgstr "Importer"
msgid "Change password for «{object}»"
msgstr "Changer le mot de passe pour «{object}»"

msgid "Change your password"
msgstr "Changer votre mot de passe"

msgid "Save the team"
msgstr "Enregistrer l'équipe"

Expand Down
83 changes: 81 additions & 2 deletions creme/creme_config/tests/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -1044,10 +1044,25 @@ def test_change_password01(self):
# GET ---
response1 = self.assertGET200(url)
self.assertTemplateUsed(response1, 'creme_core/generics/blockform/edit-popup.html')

context1 = response1.context
self.assertEqual(
_('Change password for «{object}»').format(object=other_user),
response1.context.get('title'),
context1.get('title'),
)

with self.assertNoException():
fields = context1['form'].fields
old_password_f = fields['old_password']
password1_f = fields['password_1']
password2_f = fields['password_2']

self.assertEqual(_('Your password'), old_password_f.label)
self.assertEqual(
_('New password for «{user}»').format(user=other_user),
password1_f.label,
)
self.assertEqual(_('New password confirmation'), password2_f.label)

# POST (error) ---
new_password = 'password'
Expand Down Expand Up @@ -1082,6 +1097,7 @@ def test_change_password01(self):

@skipIfNotCremeUser
def test_change_password02(self):
"Not administrator."
self.login_as_config_admin()

other_user = User.objects.create(username='deunan')
Expand Down Expand Up @@ -1125,8 +1141,19 @@ def test_change_password04(self):

other_user = User.objects.create(username='deunan')
url = self._build_edit_url(other_user.id, password=True)
self.assertGET200(url)
response1 = self.assertGET200(url)

with self.assertNoException():
password1_f = response1.context['form'].fields['password_1']

msg = ngettext(
"The password must contain at least %(min_length)d character.",
"The password must contain at least %(min_length)d characters.",
8,
) % {"min_length": 8}
self.assertHTMLEqual(f'<ul><li>{msg}</li></ul>', password1_f.help_text)

# ---
password = 'pass'
response = self.assertPOST200(
url, data={'password_1': password, 'password_2': password}
Expand All @@ -1141,6 +1168,58 @@ def test_change_password04(self):
) % {'min_length': 8},
)

@skipIfNotCremeUser
def test_change_own_password(self):
user = self.login_as_root_and_get()

url = self._build_edit_url(user.id, password=True)

# GET ---
response1 = self.assertGET200(url)

context1 = response1.context
self.assertEqual(_('Change your password'), context1.get('title'))

with self.assertNoException():
fields = context1['form'].fields
old_password_f = fields['old_password']
password1_f = fields['password_1']
password2_f = fields['password_2']

self.assertEqual(_('Your old password'), old_password_f.label)
self.assertEqual(_('New password'), password1_f.label)
self.assertEqual(_('New password confirmation'), password2_f.label)

# POST (error) ---
new_password = 'password'
response2 = self.assertPOST200(
url,
follow=True,
data={
'old_password': 'mismatch',
'password_1': new_password,
'password_2': new_password,
},
)
self.assertFormError(
response2.context['form'],
field='old_password',
errors=_('Your old password was entered incorrectly. Please enter it again.'),
)

# POST ---
response3 = self.client.post(
url,
follow=True,
data={
'old_password': ROOT_PASSWORD,
'password_1': new_password,
'password_2': new_password,
},
)
self.assertNoFormError(response3)
self.assertTrue(self.refresh(user).check_password(new_password))

@skipIfNotCremeUser
def test_user_activation(self):
# self.login()
Expand Down
6 changes: 6 additions & 0 deletions creme/creme_config/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,17 @@ class PasswordChange(generic.CremeModelEditionPopup):
pk_url_kwarg = 'user_id'
permissions = SUPERUSER_PERM
title = _('Change password for «{object}»')
title_for_own = _('Change your password')

@method_decorator(sensitive_post_parameters())
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)

def get_title(self) -> str:
title = self.title_for_own if self.get_object() == self.request.user else self.title

return title.format(**self.get_title_format_data())


class BaseUserCreation(generic.CremeModelCreationPopup):
model = get_user_model()
Expand Down
3 changes: 3 additions & 0 deletions creme/creme_core/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,6 @@ def save(self, filename: str, user):
instance of <django.contrib.auth.get_user_model()>.
"""
raise NotImplementedError

def validate(self, *, total_count=None):
pass
12 changes: 12 additions & 0 deletions creme/creme_core/backends/xls_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
from django.conf import settings
from django.http import HttpResponseRedirect
from django.template.defaultfilters import slugify
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _

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

def __init__(self, encoding='utf-8'):
super().__init__()
Expand All @@ -56,3 +59,12 @@ def save(self, filename, user):

def writerow(self, row):
self.writer.writerow(row)

def validate(self, *, total_count=None):
if total_count is not None and total_count > self.max_row_index:
raise ConflictError(
gettext(
"The Microsoft Excel 97–2003 Workbook (.xls) format has a limit of"
" {count} rows. Use a CSV format instead.".format(count=self.max_row_index)
)
)
7 changes: 7 additions & 0 deletions creme/creme_core/locale/fr/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ msgstr ""
msgid "XLS File"
msgstr "Fichier XLS"

#, python-brace-format
msgid ""
"The Microsoft Excel 97–2003 Workbook (.xls) format has a limit of {count} "
"rows. Use a CSV format instead."
msgstr "Le format Microsoft Excel 97–2003 Workbook (.xls) est limité à {count} lignes. "
"Utilisez un format CSV à la place."

msgid ""
"XLS is a file extension for a spreadsheet file format created by Microsoft "
"for use with Microsoft Excel (Excel 97-2003 Workbook)."
Expand Down
6 changes: 5 additions & 1 deletion creme/creme_core/static/creme_core/js/lib/color.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,12 @@ RGBColor.prototype = {
return clamp(scaleround(c, 3), 1, 21);
},

isDark: function(gamma) {
return this.contrast(0, gamma) < 10;
},

foreground: function(gamma) {
return this.contrast(0, gamma) < 10 ? new RGBColor(0xFFFFFF) : new RGBColor(0x000000);
return this.isDark(gamma) ? new RGBColor(0xFFFFFF) : new RGBColor(0x000000);
}
};

Expand Down
Loading