diff --git a/.gitignore b/.gitignore index 35ee2b2..ef37281 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /env/ /tmp/ +/.eggs/ *.pyc *.mo /.coverage diff --git a/.pylintrc b/.pylintrc index 752daa2..4bd19ba 100644 --- a/.pylintrc +++ b/.pylintrc @@ -10,7 +10,8 @@ disable=missing-docstring, missing-module-docstring, missing-class-docstring, missing-function-docstring, - useless-object-inheritance, + consider-using-f-string, + duplicate-code, [BASIC] @@ -18,10 +19,20 @@ good-names=_, c, d, e, + f, i, j, k, + n, + r, + s, + t, + pk, [FORMAT] max-line-length=120 + +[SIMILARITIES] + +ignore-comments=no diff --git a/INSTALL.rst b/INSTALL.rst index da4c2cb..f20a374 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -1,6 +1,6 @@ REQUIREMENTS ============ -- Python 2.7 +- Python 3 - Python package virtualenv (in most cases this is distributed or installed together with python) - Django (will be installed automatically) - Several additional django related python packages (will be installed automatically) diff --git a/bin/coverage-html.py b/bin/coverage-html.py index 41baad4..d3836f2 100755 --- a/bin/coverage-html.py +++ b/bin/coverage-html.py @@ -1,18 +1,17 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import unicode_literals import argparse -import coverage import datetime import os import shutil import sys import time +import coverage from selenium import webdriver from selenium.common.exceptions import WebDriverException -class Command(object): +class Command: # pylint: disable=too-few-public-methods default_browser = 'firefox' @staticmethod diff --git a/dav_auth/__init__.py b/dav_auth/__init__.py index 77b977e..7c90b21 100644 --- a/dav_auth/__init__.py +++ b/dav_auth/__init__.py @@ -1 +1 @@ -default_app_config = 'dav_auth.apps.AppConfig' +default_app_config = 'dav_auth.apps.AppConfig' # pylint: disable=invalid-name diff --git a/dav_auth/apps.py b/dav_auth/apps.py index 5311b2b..b22f976 100644 --- a/dav_auth/apps.py +++ b/dav_auth/apps.py @@ -8,5 +8,5 @@ DEFAULT_SETTINGS = ( class AppConfig(_AppConfig): name = 'dav_auth' - verbose_name = u'DAV Benutzerverwaltung' + verbose_name = 'DAV Benutzerverwaltung' default_settings = DEFAULT_SETTINGS diff --git a/dav_auth/emails.py b/dav_auth/emails.py index 3ea4ad5..7372e55 100644 --- a/dav_auth/emails.py +++ b/dav_auth/emails.py @@ -2,8 +2,8 @@ from dav_base.emails import AbstractMail -class PasswordSetEmail(AbstractMail): - _subject = u'Zugangsdaten' +class PasswordSetEmail(AbstractMail): # pylint: disable=too-few-public-methods + _subject = 'Zugangsdaten' _template_name = 'dav_auth/emails/password_set.txt' def __init__(self, user, password): @@ -11,12 +11,12 @@ class PasswordSetEmail(AbstractMail): self._password = password def _get_recipients(self): - r = u'"{fullname}" <{email}>'.format(fullname=self._user.get_full_name(), + r = '"{fullname}" <{email}>'.format(fullname=self._user.get_full_name(), email=self._user.email) return [r] def _get_context_data(self, extra_context=None): - context = super(PasswordSetEmail, self)._get_context_data(extra_context=extra_context) + context = super()._get_context_data(extra_context=extra_context) context.update({ 'fullname': self._user.get_full_name(), 'username': self._user.username, diff --git a/dav_auth/forms.py b/dav_auth/forms.py index 8c1faa4..6e51dfd 100644 --- a/dav_auth/forms.py +++ b/dav_auth/forms.py @@ -9,14 +9,13 @@ logger = logging.getLogger(__name__) class LoginForm(auth_forms.AuthenticationForm): username = auth_forms.UsernameField( - max_length=254, - label=_(u'E-Mail-Adresse'), + label=_('E-Mail-Adresse'), widget=forms.TextInput(attrs={'autofocus': True}), ) error_messages = { - 'invalid_login': _(u'Benutzername oder Passwort falsch.'), - 'inactive': _("This account is inactive."), + 'invalid_login': _('Benutzername oder Passwort falsch.'), + 'inactive': _('This account is inactive.'), } def clean_username(self): @@ -25,18 +24,18 @@ class LoginForm(auth_forms.AuthenticationForm): class SetPasswordForm(forms.Form): - new_password = forms.CharField(label=_(u'Neues Passwort'), + new_password = forms.CharField(label=_('Neues Passwort'), widget=forms.PasswordInput) - new_password_repeat = forms.CharField(label=_(u'Neues Passwort wiederholen'), + new_password_repeat = forms.CharField(label=_('Neues Passwort wiederholen'), widget=forms.PasswordInput) send_password_mail = forms.BooleanField(required=False, initial=False, - label=_(u'Neues Passwort per E-Mail zusenden'), + label=_('Neues Passwort per E-Mail zusenden'), ) def __init__(self, user, *args, **kwargs): self.user = user - super(SetPasswordForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean_new_password(self): password = self.cleaned_data.get('new_password') @@ -49,7 +48,7 @@ class SetPasswordForm(forms.Form): if password1 and password2: if password1 != password2: raise forms.ValidationError( - ugettext(u'Passwörter stimmen nicht überein'), + ugettext('Passwörter stimmen nicht überein'), code='password_mismatch', ) return password2 @@ -64,8 +63,7 @@ class SetPasswordForm(forms.Form): class CreateAndSendPasswordForm(forms.Form): username = auth_forms.UsernameField( - max_length=254, - label=_(u'E-Mail-Adresse'), + label=_('E-Mail-Adresse'), widget=forms.TextInput(attrs={'autofocus': True}), ) diff --git a/dav_auth/tests/generic.py b/dav_auth/tests/generic.py index 202b9b6..ace764a 100644 --- a/dav_auth/tests/generic.py +++ b/dav_auth/tests/generic.py @@ -3,7 +3,7 @@ from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys -class SeleniumAuthMixin(object): +class SeleniumAuthMixin: def login(self, driver, username, password): driver.get(self.complete_url(reverse('dav_auth:login'))) username_field = self.wait_on_presence(driver, (By.ID, 'id_username')) diff --git a/dav_auth/tests/test_emails.py b/dav_auth/tests/test_emails.py index 5696276..15591d6 100644 --- a/dav_auth/tests/test_emails.py +++ b/dav_auth/tests/test_emails.py @@ -9,10 +9,10 @@ from ..emails import PasswordSetEmail TEST_USERNAME = 'user' -TEST_PASSWORD = u'me||ön 21ABll' +TEST_PASSWORD = 'me||ön 21ABll' TEST_EMAIL = 'root@localhost' -PASSWORD_EMAIL_TEMPLATE = u"""Hallo {fullname}, +PASSWORD_EMAIL_TEMPLATE = """Hallo {fullname}, Benutzername: {username} Passwort: {password} @@ -23,7 +23,7 @@ URL: {base_url}/ class EmailTestCase(EmailTestMixin, TestCase): def setUp(self): - super(EmailTestCase, self).setUp() + super().setUp() model = get_user_model() self.user = model.objects.create_user(username=TEST_USERNAME, password=TEST_PASSWORD, email=TEST_EMAIL) @@ -38,7 +38,7 @@ class EmailTestCase(EmailTestMixin, TestCase): self.assertSender(mail) self.assertRecipients(mail, [self.user]) - self.assertSubject(mail, u'Zugangsdaten') + self.assertSubject(mail, 'Zugangsdaten') expected_body = PASSWORD_EMAIL_TEMPLATE.format( fullname=self.user.get_full_name(), diff --git a/dav_auth/tests/test_forms.py b/dav_auth/tests/test_forms.py index d02c9ba..e317150 100644 --- a/dav_auth/tests/test_forms.py +++ b/dav_auth/tests/test_forms.py @@ -9,9 +9,8 @@ from dav_base.tests.generic import FormDataSet, FormsTestCase from ..forms import LoginForm, SetPasswordForm, CreateAndSendPasswordForm TEST_USERNAME = 'root@localhost' -TEST_PASSWORD = u'me||ön 21ABll' +TEST_PASSWORD = 'me||ön 21ABll' TEST_EMAIL = TEST_USERNAME -USERNAME_MAX_LENGTH = 254 class LoginFormTestCase(FormsTestCase): @@ -24,15 +23,10 @@ class LoginFormTestCase(FormsTestCase): model = get_user_model() self.user = model.objects.create_user(username=TEST_USERNAME, password=TEST_PASSWORD, email=TEST_EMAIL) - def test_length(self): - form = self.form_class() - self.assertEqual(form.fields['username'].max_length, USERNAME_MAX_LENGTH) - self.assertEqual(form.fields['password'].max_length, None) - def test_labels(self): form = self.form_class() - self.assertEqual(form.fields['username'].label, ugettext(u'E-Mail-Adresse')) - self.assertEqual(form.fields['password'].label, ugettext(u'Password')) + self.assertEqual(form.fields['username'].label, ugettext('E-Mail-Adresse')) + self.assertEqual(form.fields['password'].label, ugettext('Password')) def test_required(self): form = self.form_class() @@ -44,35 +38,28 @@ class LoginFormTestCase(FormsTestCase): FormDataSet({'username': '', 'password': self.test_password}, expected_errors=[('username', 'required')]), ] - super(LoginFormTestCase, self).test_invalid_data(data_sets=data_sets) + super().test_invalid_data(data_sets=data_sets) def test_password_empty(self): data_sets = [ FormDataSet({'username': self.test_username, 'password': ''}, expected_errors=[('password', 'required')]), ] - super(LoginFormTestCase, self).test_invalid_data(data_sets=data_sets) - - def test_username_too_long(self): - data_sets = [ - FormDataSet({'username': 'u' * USERNAME_MAX_LENGTH + 'u', 'password': self.test_password}, - expected_errors=[('username', 'max_length')]), - ] - super(LoginFormTestCase, self).test_invalid_data(data_sets=data_sets) + super().test_invalid_data(data_sets=data_sets) def test_invalid_username(self): data_sets = [ FormDataSet({'username': self.test_username[::-1], 'password': self.test_password}, expected_errors=[('__all__', 'invalid_login')]), ] - super(LoginFormTestCase, self).test_invalid_data(data_sets=data_sets) + super().test_invalid_data(data_sets=data_sets) def test_invalid_password(self): data_sets = [ FormDataSet({'username': self.test_username, 'password': self.test_password[::-1]}, expected_errors=[('__all__', 'invalid_login')]), ] - super(LoginFormTestCase, self).test_invalid_data(data_sets=data_sets) + super().test_invalid_data(data_sets=data_sets) def test_inactive_user(self): self.user.is_active = False @@ -81,13 +68,13 @@ class LoginFormTestCase(FormsTestCase): FormDataSet({'username': self.test_username, 'password': self.test_password}, expected_errors=[('__all__', 'invalid_login')]), ] - super(LoginFormTestCase, self).test_invalid_data(data_sets=data_sets) + super().test_invalid_data(data_sets=data_sets) def test_valid_credentials(self): data_sets = [ FormDataSet({'username': self.test_username, 'password': self.test_password}), ] - super(LoginFormTestCase, self).test_valid_data(data_sets=data_sets) + super().test_valid_data(data_sets=data_sets) class SetPasswordFormTestCase(FormsTestCase): @@ -111,56 +98,56 @@ class SetPasswordFormTestCase(FormsTestCase): FormDataSet({'new_password': 'mellonAB12+-', 'new_password_repeat': 'mellonAB13+-'}, [('new_password_repeat', 'password_mismatch')]), ] - super(SetPasswordFormTestCase, self).test_invalid_data(data_sets=data_sets, form_kwargs={'user': self.user}) + super().test_invalid_data(data_sets=data_sets, form_kwargs={'user': self.user}) def test_empty(self): data_sets = [ FormDataSet({'new_password': '', 'new_password_repeat': ''}, [('new_password', 'required')]), ] - super(SetPasswordFormTestCase, self).test_invalid_data(data_sets=data_sets, form_kwargs={'user': self.user}) + super().test_invalid_data(data_sets=data_sets, form_kwargs={'user': self.user}) def test_too_short(self): data_sets = [ FormDataSet({'new_password': 'mellon', 'new_password_repeat': 'mellon'}, [('new_password', 'password_too_short')]), ] - super(SetPasswordFormTestCase, self).test_invalid_data(data_sets=data_sets, form_kwargs={'user': self.user}) + super().test_invalid_data(data_sets=data_sets, form_kwargs={'user': self.user}) def test_entirely_numeric(self): data_sets = [ FormDataSet({'new_password': '1357924680', 'new_password_repeat': '1357924680'}, [('new_password', 'password_entirely_numeric')]), ] - super(SetPasswordFormTestCase, self).test_invalid_data(data_sets=data_sets, form_kwargs={'user': self.user}) + super().test_invalid_data(data_sets=data_sets, form_kwargs={'user': self.user}) def test_too_similar(self): data_sets = [ FormDataSet({'new_password': self.test_username, 'new_password_repeat': self.test_username}, [('new_password', 'password_too_similar')]), ] - super(SetPasswordFormTestCase, self).test_invalid_data(data_sets=data_sets, form_kwargs={'user': self.user}) + super().test_invalid_data(data_sets=data_sets, form_kwargs={'user': self.user}) def test_too_common(self): data_sets = [ FormDataSet({'new_password': 'password', 'new_password_repeat': 'password'}, [('new_password', 'password_too_common')]), ] - super(SetPasswordFormTestCase, self).test_invalid_data(data_sets=data_sets, form_kwargs={'user': self.user}) + super().test_invalid_data(data_sets=data_sets, form_kwargs={'user': self.user}) def test_valid(self): data_sets = [ FormDataSet({'new_password': 'mellonAB12+-', 'new_password_repeat': 'mellonAB12+-'}), FormDataSet({'new_password': 'mellonAB12+-', 'new_password_repeat': 'mellonAB12+-', 'send_password_mail': True}), - FormDataSet({'new_password': u'"ä§ MellonAB12+-', 'new_password_repeat': u'"ä§ MellonAB12+-'}), + FormDataSet({'new_password': '"ä§ MellonAB12+-', 'new_password_repeat': '"ä§ MellonAB12+-'}), FormDataSet({'new_password': 'mellon12' * 128, 'new_password_repeat': 'mellon12' * 128}), ] - super(SetPasswordFormTestCase, self).test_valid_data(data_sets=data_sets, form_kwargs={'user': self.user}) + super().test_valid_data(data_sets=data_sets, form_kwargs={'user': self.user}) def test_save(self): new_passwords = [ - u'"ä§ Mellon12' + '"ä§ Mellon12' 'mellon12' * 128, ] @@ -175,7 +162,7 @@ class SetPasswordFormTestCase(FormsTestCase): @skip('Function is implemented in SetPasswordView instead of SetPasswordForm') def test_save_with_mail(self): # pragma: no cover new_passwords = [ - u'"ä§ Mellon12' + '"ä§ Mellon12' 'mellon12' * 128, ] @@ -202,16 +189,11 @@ class CreateAndSendPasswordFormTestCase(FormsTestCase): ) invalid_data_sets = ( FormDataSet({'username': ''}, expected_errors=[('username', 'required')]), - FormDataSet({'username': 'u' * USERNAME_MAX_LENGTH + 'u'}, expected_errors=[('username', 'max_length')]), ) - def test_length(self): - form = self.form_class() - self.assertEqual(form.fields['username'].max_length, USERNAME_MAX_LENGTH) - def test_labels(self): form = self.form_class() - self.assertEqual(form.fields['username'].label, ugettext(u'E-Mail-Adresse')) + self.assertEqual(form.fields['username'].label, ugettext('E-Mail-Adresse')) def test_required(self): form = self.form_class() diff --git a/dav_auth/tests/test_models.py b/dav_auth/tests/test_models.py new file mode 100644 index 0000000..81af0a2 --- /dev/null +++ b/dav_auth/tests/test_models.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +from unittest import skip +from django.contrib.auth import get_user_model +from django.test import TestCase, Client + + +class ModelsTestCase(TestCase): + @skip('I do not know, why the user.save() does not raise an exception') + def test_username_length(self): + max_length = 150 # Hard coded in django.contrib.auth.models.AbstractUser + username_ok = 'u' * max_length + username_too_long = username_ok + 'u' + + user_model = get_user_model() + user = user_model(username=username_too_long, + first_name='A', + last_name='User', + email='a.user@example.com') + user.save() + + def test_last_login(self): + user_model = get_user_model() + user = user_model(username='auser', + first_name='A', + last_name='User', + email='a.user@example.com') + user.save() + self.assertEqual(user.last_login, None) + + c = Client() + c.force_login(user) + self.assertNotEqual(user.last_login, None) diff --git a/dav_auth/tests/test_screenshots.py b/dav_auth/tests/test_screenshots.py index 7ab5aee..168476a 100644 --- a/dav_auth/tests/test_screenshots.py +++ b/dav_auth/tests/test_screenshots.py @@ -9,7 +9,7 @@ from selenium.webdriver.common.keys import Keys from dav_base.tests.generic import ScreenshotTestCase TEST_USERNAME = 'root@localhost' -TEST_PASSWORD = u'me||ön 21ABll' +TEST_PASSWORD = 'me||ön 21ABll' TEST_EMAIL = TEST_USERNAME @@ -18,7 +18,7 @@ class TestCase(ScreenshotTestCase): screenshot_prefix = 'dav_auth-' def setUp(self): - super(TestCase, self).setUp() + super().setUp() # Need a test user self.test_username = TEST_USERNAME self.test_password = TEST_PASSWORD @@ -105,7 +105,7 @@ class TestCase(ScreenshotTestCase): # Click on 'set password' -> save set password page user_menu = c.find_element_by_css_selector('#login-widget ul') - link = user_menu.find_element_by_partial_link_text(ugettext(u'Passwort ändern')) + link = user_menu.find_element_by_partial_link_text(ugettext('Passwort ändern')) link.click() password_field = self.wait_on_presence(c, (By.ID, 'id_new_password')) self.save_screenshot('empty_set_password_form', sequence=sequence_name) @@ -189,7 +189,7 @@ class TestCase(ScreenshotTestCase): dropdown_button = self.wait_on_presence(c, (By.ID, 'user_dropdown_button')) dropdown_button.click() user_menu = c.find_element_by_css_selector('#login-widget ul') - link = user_menu.find_element_by_partial_link_text(ugettext(u'Logout')) + link = user_menu.find_element_by_partial_link_text(ugettext('Logout')) link.click() self.wait_until_stale(c, user_menu) self.save_screenshot('logout_succeed', sequence=sequence_name) @@ -200,7 +200,7 @@ class TestCase(ScreenshotTestCase): self.wait_on_presence(c, (By.ID, 'id_username')) # Locate password recreate link, click it -> save password recreate form - link = c.find_element_by_partial_link_text(ugettext(u'Passwort vergessen')) + link = c.find_element_by_partial_link_text(ugettext('Passwort vergessen')) link.click() username_field = self.wait_on_presence(c, (By.ID, 'id_username')) self.save_screenshot('empty_recreate_password_form', sequence=sequence_name) @@ -212,7 +212,7 @@ class TestCase(ScreenshotTestCase): self.save_screenshot('recreate_password_invalid_user', sequence=sequence_name) # Locate password recreate link, click it - link = c.find_element_by_partial_link_text(ugettext(u'Passwort vergessen')) + link = c.find_element_by_partial_link_text(ugettext('Passwort vergessen')) link.click() username_field = self.wait_on_presence(c, (By.ID, 'id_username')) diff --git a/dav_auth/tests/test_templates.py b/dav_auth/tests/test_templates.py index ebd1c66..211d7c0 100644 --- a/dav_auth/tests/test_templates.py +++ b/dav_auth/tests/test_templates.py @@ -29,7 +29,7 @@ class TemplatesTestCase(SimpleTestCase): @tag('browser') class TestCase(SeleniumAuthMixin, SeleniumTestCase): def setUp(self): - super(TestCase, self).setUp() + super().setUp() # Need a test user self.test_username = TEST_USERNAME self.test_password = TEST_PASSWORD @@ -40,7 +40,7 @@ class TestCase(SeleniumAuthMixin, SeleniumTestCase): c = self.selenium c.get(self.complete_url('/')) try: - link = c.find_element_by_css_selector('#login-widget a') + c.find_element_by_css_selector('#login-widget a') except NoSuchElementException as e: # pragma: no cover self.fail(str(e)) @@ -65,4 +65,3 @@ class TestCase(SeleniumAuthMixin, SeleniumTestCase): self.assertEqual(field.get_attribute('required'), 'true') field = c.find_element_by_id('id_send_password_mail') self.assertEqual(field.get_attribute('required'), None) - diff --git a/dav_auth/tests/test_validators.py b/dav_auth/tests/test_validators.py index 62918fc..6de1f77 100644 --- a/dav_auth/tests/test_validators.py +++ b/dav_auth/tests/test_validators.py @@ -8,19 +8,19 @@ from ..validators import PasswordScoreValidator, CustomWordlistPasswordValidator class PasswordScoreValidatorTestCase(SimpleTestCase): def test_too_little_score(self): passwords = [ - u'', # score = 0 - u'abcdefghijklmnopq', # score = 17 - u'Abcdefghijklmnop', # score = 16 + 1 - u'ABcdefghijklmno', # score = 15 + 2 - u'AB1defghijklmn', # score = 14 + 2 + 1 - u'AB12efghijklm', # score = 13 + 2 + 2 - u'AB12+fghijkl', # score = 12 + 2 + 2 + 1 - u'AB12+-ghijk', # score = 11 + 2 + 2 + 2 - u'AB12+-*hij', # score = 10 + 2 + 2 + 3 - u'AB12+-*hi/', # score = 10 + 2 + 2 + 3 - u'AB12+-*hi3', # score = 10 + 2 + 2 + 3 - u'AB12+-*hiC', # score = 10 + 2 + 2 + 3 - u'abcbbbgbbjklmnopqr', # score = 17 + '', # score = 0 + 'abcdefghijklmnopq', # score = 17 + 'Abcdefghijklmnop', # score = 16 + 1 + 'ABcdefghijklmno', # score = 15 + 2 + 'AB1defghijklmn', # score = 14 + 2 + 1 + 'AB12efghijklm', # score = 13 + 2 + 2 + 'AB12+fghijkl', # score = 12 + 2 + 2 + 1 + 'AB12+-ghijk', # score = 11 + 2 + 2 + 2 + 'AB12+-*hij', # score = 10 + 2 + 2 + 3 + 'AB12+-*hi/', # score = 10 + 2 + 2 + 3 + 'AB12+-*hi3', # score = 10 + 2 + 2 + 3 + 'AB12+-*hiC', # score = 10 + 2 + 2 + 3 + 'abcbbbgbbjklmnopqr', # score = 17 ] validator = PasswordScoreValidator(min_classes=0) @@ -31,19 +31,19 @@ class PasswordScoreValidatorTestCase(SimpleTestCase): except ValidationError as e: self.assertEqual('too_little_score', e.code) else: - self.fail(u'%s: no validation error was raised' % password) + self.fail('%s: no validation error was raised' % password) def test_too_few_classes(self): passwords = [ - u'', # classes = 0 - u'abcdefgh', # classes = 1 - u'abcdef+-', # classes = 2 - u'abcd12gh', # classes = 2 - u'abcd12-+', # classes = 3 - u'abCDefgh', # classes = 2 - u'abCDef+-', # classes = 3 - u'abCD12gh', # classes = 3 - u'ABCD12-+', # classes = 3 + '', # classes = 0 + 'abcdefgh', # classes = 1 + 'abcdef+-', # classes = 2 + 'abcd12gh', # classes = 2 + 'abcd12-+', # classes = 3 + 'abCDefgh', # classes = 2 + 'abCDef+-', # classes = 3 + 'abCD12gh', # classes = 3 + 'ABCD12-+', # classes = 3 ] validator = PasswordScoreValidator(min_score=0, min_classes=4) @@ -54,20 +54,20 @@ class PasswordScoreValidatorTestCase(SimpleTestCase): except ValidationError as e: self.assertEqual('too_few_classes', e.code) else: - self.fail(u'%s: no validation error was raised' % password) + self.fail('%s: no validation error was raised' % password) def test_valid(self): passwords = [ - u'Abcdefghijklmnopq', - u'ABcdefghijklmnop', - u'AB1defghijklmno', - u'AB12efghijklmn', - u'AB12+fghijklm', - u'AB12+-ghijkl', - u'AB12+-*hijk', - u'ab1defghijklmnopq', - u'abcd+fghijklmnopq', - u'AB1CDEFGHIJKLMNOPQ', + 'Abcdefghijklmnopq', + 'ABcdefghijklmnop', + 'AB1defghijklmno', + 'AB12efghijklmn', + 'AB12+fghijklm', + 'AB12+-ghijkl', + 'AB12+-*hijk', + 'ab1defghijklmnopq', + 'abcd+fghijklmnopq', + 'AB1CDEFGHIJKLMNOPQ', ] validator = PasswordScoreValidator() @@ -82,23 +82,23 @@ class PasswordScoreValidatorTestCase(SimpleTestCase): class CustomWordlistPasswordValidatorTestCase(SimpleTestCase): def test_invalid(self): invalid_passwords = [ - (u'passwort', [ - u'Das Passwort darf nicht die Zeichenfolge \'passwort\' enthalten.', + ('passwort', [ + 'Das Passwort darf nicht die Zeichenfolge \'passwort\' enthalten.', ]), - (u'abcdDaVefgh', [ - u'Das Passwort darf nicht die Zeichenfolge \'dav\' enthalten.', + ('abcdDaVefgh', [ + 'Das Passwort darf nicht die Zeichenfolge \'dav\' enthalten.', ]), - (u'abcdsektIonefgh', [ - u'Das Passwort darf nicht die Zeichenfolge \'sektion\' enthalten.', + ('abcdsektIonefgh', [ + 'Das Passwort darf nicht die Zeichenfolge \'sektion\' enthalten.', ]), - (u'alpen12verein34KArlsruhE berge', [ - u'Das Passwort darf nicht die Zeichenfolge \'karlsruhe\' enthalten.', - u'Das Passwort darf nicht die Zeichenfolge \'berge\' enthalten.', + ('alpen12verein34KArlsruhE berge', [ + 'Das Passwort darf nicht die Zeichenfolge \'karlsruhe\' enthalten.', + 'Das Passwort darf nicht die Zeichenfolge \'berge\' enthalten.', ]), - (u'heinzel@alpenverein-karlsruhe.de', [ - u'Das Passwort darf nicht die Zeichenfolge \'heinzel\' enthalten.', - u'Das Passwort darf nicht die Zeichenfolge \'alpenverein\' enthalten.', - u'Das Passwort darf nicht die Zeichenfolge \'karlsruhe\' enthalten.', + ('heinzel@alpenverein-karlsruhe.de', [ + 'Das Passwort darf nicht die Zeichenfolge \'heinzel\' enthalten.', + 'Das Passwort darf nicht die Zeichenfolge \'alpenverein\' enthalten.', + 'Das Passwort darf nicht die Zeichenfolge \'karlsruhe\' enthalten.', ]), ] @@ -114,13 +114,13 @@ class CustomWordlistPasswordValidatorTestCase(SimpleTestCase): for error in errors: self.assertIn(error, expected_errors) else: - self.fail(u'%s: no validation error was raised' % password) + self.fail('%s: no validation error was raised' % password) def test_valid(self): passwords = [ - u'', - u'password', - u'münchen', + '', + 'password', + 'münchen', ] validator = CustomWordlistPasswordValidator() @@ -134,72 +134,72 @@ class CustomWordlistPasswordValidatorTestCase(SimpleTestCase): class CharacterClassPasswordValidatorTestCase(SimpleTestCase): def setUp(self): - super(CharacterClassPasswordValidatorTestCase, self).setUp() + super().setUp() self.validator = CharacterClassPasswordValidator() def test_invalid(self): invalid_passwords = [ - (u'', [ - u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', - u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', - u'Das Passwort muss mindestens 2 Ziffern enthalten.', - u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', + ('', [ + 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', + 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', + 'Das Passwort muss mindestens 2 Ziffern enthalten.', + 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', ]), - (u'A+-', [ - u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', - u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', - u'Das Passwort muss mindestens 2 Ziffern enthalten.', + ('A+-', [ + 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', + 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', + 'Das Passwort muss mindestens 2 Ziffern enthalten.', ]), - (u'1234567890*', [ - u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', - u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', - u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', + ('1234567890*', [ + 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', + 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', + 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', ]), - (u'34*/()', [ - u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', - u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', + ('34*/()', [ + 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', + 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', ]), - (u'AA', [ - u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', - u'Das Passwort muss mindestens 2 Ziffern enthalten.', - u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', + ('AA', [ + 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', + 'Das Passwort muss mindestens 2 Ziffern enthalten.', + 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', ]), - (u'CD0.,', [ - u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', - u'Das Passwort muss mindestens 2 Ziffern enthalten.', + ('CD0.,', [ + 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', + 'Das Passwort muss mindestens 2 Ziffern enthalten.', ]), - (u'EF56', [ - u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', - u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', + ('EF56', [ + 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', + 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', ]), - (u'8GH?!8', [ - u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', + ('8GH?!8', [ + 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', ]), - (u'bbX', [ - u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', - u'Das Passwort muss mindestens 2 Ziffern enthalten.', - u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', + ('bbX', [ + 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', + 'Das Passwort muss mindestens 2 Ziffern enthalten.', + 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', ]), - (u'$cd%', [ - u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', - u'Das Passwort muss mindestens 2 Ziffern enthalten.', + ('$cd%', [ + 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', + 'Das Passwort muss mindestens 2 Ziffern enthalten.', ]), - (u'ef90', [ - u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', - u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', + ('ef90', [ + 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', + 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', ]), - (u'1g=h3~', [ - u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', + ('1g=h3~', [ + 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', ]), - (u'Gi&jH', [ - u'Das Passwort muss mindestens 2 Ziffern enthalten.', - u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', + ('Gi&jH', [ + 'Das Passwort muss mindestens 2 Ziffern enthalten.', + 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', ]), - (u'IkK:i;', [ - u'Das Passwort muss mindestens 2 Ziffern enthalten.', + ('IkK:i;', [ + 'Das Passwort muss mindestens 2 Ziffern enthalten.', ]), - (u'mKn4L8', [ - u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', + ('mKn4L8', [ + 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', ]), ] @@ -215,10 +215,10 @@ class CharacterClassPasswordValidatorTestCase(SimpleTestCase): for error in errors: self.assertIn(error, expected_errors) else: - self.fail(u'%s: no validation error was raised' % password) + self.fail('%s: no validation error was raised' % password) def test_valid(self): - valid_passwords = [u'abCD12+-'] + valid_passwords = ['abCD12+-'] validator = self.validator for password in valid_passwords: try: diff --git a/dav_auth/tests/test_views.py b/dav_auth/tests/test_views.py index 28a1ee0..2e3a601 100644 --- a/dav_auth/tests/test_views.py +++ b/dav_auth/tests/test_views.py @@ -10,7 +10,7 @@ from django.urls import reverse from ..forms import LoginForm, SetPasswordForm, CreateAndSendPasswordForm TEST_USERNAME = 'root@localhost' -TEST_PASSWORD = u'me||ön 21ABll' +TEST_PASSWORD = 'me||ön 21ABll' TEST_EMAIL = TEST_USERNAME @@ -30,12 +30,12 @@ class ViewsTestCase(TestCase): cls.recreate_password_url = reverse('dav_auth:recreate_password') # Some messages - cls.wrong_credentials_message = ugettext(u'Benutzername oder Passwort falsch.') - cls.logout_message = ugettext(u'Benutzer abgemeldet.') - cls.set_password_message = ugettext(u'Passwort gespeichert.') + cls.wrong_credentials_message = ugettext('Benutzername oder Passwort falsch.') + cls.logout_message = ugettext('Benutzer abgemeldet.') + cls.set_password_message = ugettext('Passwort gespeichert.') def setUp(self): - super(TestCase, self).setUp() + super().setUp() # Need a test user self.test_username = TEST_USERNAME self.test_password = TEST_PASSWORD @@ -74,7 +74,7 @@ class ViewsTestCase(TestCase): def test_integrated_login_succeed(self): username = self.user.username - message = ugettext(u'Benutzer angemeldet: %(username)s') % {'username': username} + message = ugettext('Benutzer angemeldet: %(username)s') % {'username': username} response = self.client.post(self.login_url, {'username': username, 'password': self.test_password}) self.assertEqual(response.status_code, 302) @@ -166,7 +166,7 @@ class ViewsTestCase(TestCase): 'send_password_mail': True}) self.assertEqual(len(django_mail.outbox), 1) mail = django_mail.outbox[0] - recipient = u'"%s" <%s>' % (self.user.get_full_name(), self.user.email) + recipient = '"%s" <%s>' % (self.user.get_full_name(), self.user.email) recipients = mail.recipients() self.assertIn(recipient, recipients) self.assertEqual(len(recipients), 1) @@ -202,7 +202,7 @@ class ViewsTestCase(TestCase): self.assertEqual(len(django_mail.outbox), 1) mail = django_mail.outbox[0] - recipient = u'"%s" <%s>' % (self.user.get_full_name(), self.user.email) + recipient = '"%s" <%s>' % (self.user.get_full_name(), self.user.email) recipients = mail.recipients() self.assertIn(recipient, recipients) self.assertEqual(len(recipients), 1) diff --git a/dav_auth/urls.py b/dav_auth/urls.py index dd3c1b5..8d4bdf7 100644 --- a/dav_auth/urls.py +++ b/dav_auth/urls.py @@ -2,6 +2,8 @@ from django.conf.urls import url from . import views +app_name = 'dav_auth' + urlpatterns = [ url(r'^login$', views.LoginView.as_view(), name='login'), url(r'^logout$', views.LogoutView.as_view(), name='logout'), diff --git a/dav_auth/validators.py b/dav_auth/validators.py index 481eea4..d514445 100644 --- a/dav_auth/validators.py +++ b/dav_auth/validators.py @@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ -class PasswordScoreValidator(object): +class PasswordScoreValidator: def _get_score(self, password, user=None): score = 0 char_counters = {} @@ -56,67 +56,67 @@ class PasswordScoreValidator(object): score, used_classes = self._get_score(password, user=user) if used_classes < self.min_classes: - raise ValidationError(_(u'Das Passwort muss Zeichen aus mindestens %(min_classes)d' - u' verschiedenen Arten von Zeichen bestehen' - u' (d.h. Kleinbuchstaben, Großbuchstaben, Ziffern und Sonderzeichen).'), + raise ValidationError(_('Das Passwort muss Zeichen aus mindestens %(min_classes)d' + ' verschiedenen Arten von Zeichen bestehen' + ' (d.h. Kleinbuchstaben, Großbuchstaben, Ziffern und Sonderzeichen).'), code='too_few_classes', params={'min_classes': self.min_classes}) if score < self.min_score: - raise ValidationError(_(u'Dieses Passwort ist zu einfach. Benutze mehr Zeichen' - u' und gegebenenfalls auch Großbuchstaben, Ziffern und Sonderzeichen.'), + raise ValidationError(_('Dieses Passwort ist zu einfach. Benutze mehr Zeichen' + ' und gegebenenfalls auch Großbuchstaben, Ziffern und Sonderzeichen.'), code='too_little_score') return score def get_help_text(self): - text = u'%s\n%s' % ( - _(u'The password must get a minimum score of %d points.') % self.min_score, - _(u'For each character the score is increased by 1 point.'), + text = '%s\n%s' % ( + _('The password must get a minimum score of %d points.') % self.min_score, + _('For each character the score is increased by 1 point.'), ) if self.lcredit > 0: text += '\n' - text += _(u'The first %d lower characters increase the score by 1 point each.') % self.lcredit + text += _('The first %d lower characters increase the score by 1 point each.') % self.lcredit if self.ucredit > 0: text += '\n' - text += _(u'The first %d upper characters increase the score by 1 point each.') % self.ucredit + text += _('The first %d upper characters increase the score by 1 point each.') % self.ucredit if self.dcredit > 0: text += '\n' - text += _(u'The first %d digits increase the score by 1 point each.') % self.dcredit + text += _('The first %d digits increase the score by 1 point each.') % self.dcredit if self.ocredit > 0: text += '\n' - text += _(u'The first %d non alpha numeric characters' - u' increase the score by 1 point each.') % self.ocredit + text += _('The first %d non alpha numeric characters' + ' increase the score by 1 point each.') % self.ocredit if self.max_repeat > 0: text += '\n' - text += _(u'If a particular character is used more than %d times,' - u' it will not increase the score anymore.') % self.max_repeat + text += _('If a particular character is used more than %d times,' + ' it will not increase the score anymore.') % self.max_repeat if self.min_classes > 0: text += '\n' - text += _(u'Also the password must contain characters from %d different character classes' - u' (i.e. lower, upper, digits, others).') % self.min_classes + text += _('Also the password must contain characters from %d different character classes' + ' (i.e. lower, upper, digits, others).') % self.min_classes return text -class CustomWordlistPasswordValidator(object): +class CustomWordlistPasswordValidator: context = 'the Sektion Karlsruhe' words = ( - u'dav', - u'berge', - u'sektion', - u'karlsruhe', - u'alpenverein', - u'heinzel', - u'passwort', + 'dav', + 'berge', + 'sektion', + 'karlsruhe', + 'alpenverein', + 'heinzel', + 'passwort', ) - def validate(self, password, user=None): + def validate(self, password, user=None): # pylint: disable=unused-argument errors = [] lower_pw = password.lower() for word in self.words: if word in lower_pw: - error = ValidationError(_(u'Das Passwort darf nicht die Zeichenfolge \'%(word)s\' enthalten.'), + error = ValidationError(_('Das Passwort darf nicht die Zeichenfolge \'%(word)s\' enthalten.'), code='forbidden_word', params={'word': word}) errors.append(error) @@ -125,14 +125,14 @@ class CustomWordlistPasswordValidator(object): raise ValidationError(errors) def get_help_text(self): - text = _(u'The password must not contain some specific words,' - u' that are common in context with %(context)s.') % {'context': self.context} - text += u'\n' - text += _(u'All words are matched case insensitive.') + text = _('The password must not contain some specific words,' + ' that are common in context with %(context)s.') % {'context': self.context} + text += '\n' + text += _('All words are matched case insensitive.') return text -class CharacterClassPasswordValidator(object): +class CharacterClassPasswordValidator: def _is_enough_lower(self, password): lower = re.sub(r'[^a-z]', '', password) if len(lower) < self.minimum_lower: @@ -163,26 +163,26 @@ class CharacterClassPasswordValidator(object): self.minimum_digits = minimum_digits self.minimum_others = minimum_others - def validate(self, password, user=None): + def validate(self, password, user=None): # pylint: disable=unused-argument errors = [] if not self._is_enough_lower(password): - error = ValidationError(_(u'Das Passwort muss mindestens %(min_lower)d Kleinbuchstaben enthalten.'), + error = ValidationError(_('Das Passwort muss mindestens %(min_lower)d Kleinbuchstaben enthalten.'), code='too_few_lower_characters', params={'min_lower': self.minimum_lower}) errors.append(error) if not self._is_enough_upper(password): - error = ValidationError(_(u'Das Passwort muss mindestens %(min_upper)d Großbuchstaben enthalten.'), + error = ValidationError(_('Das Passwort muss mindestens %(min_upper)d Großbuchstaben enthalten.'), code='too_few_upper_characters', params={'min_upper': self.minimum_upper}) errors.append(error) if not self._is_enough_digits(password): - error = ValidationError(_(u'Das Passwort muss mindestens %(min_digits)d Ziffern enthalten.'), + error = ValidationError(_('Das Passwort muss mindestens %(min_digits)d Ziffern enthalten.'), code='too_few_digits', params={'min_digits': self.minimum_digits}) errors.append(error) if not self._is_enough_others(password): - error = ValidationError(_(u'Das Passwort muss mindestens %(min_others)d' - u' Sonderzeichen enthalten.'), + error = ValidationError(_('Das Passwort muss mindestens %(min_others)d' + ' Sonderzeichen enthalten.'), code='too_few_other_characters', params={'min_others': self.minimum_others}) errors.append(error) @@ -191,7 +191,7 @@ class CharacterClassPasswordValidator(object): raise ValidationError(errors) def get_help_text(self): - text = u'%s %s %s %s' % ( + text = '%s %s %s %s' % ( _('The password must contain at least %d characters from a-z.') % self.minimum_lower, _('The password must contain at least %d characters from A-Z.') % self.minimum_upper, _('The password must contain at least %d digits from 0-9.') % self.minimum_digits, diff --git a/dav_auth/views.py b/dav_auth/views.py index fcf30ec..5349edf 100644 --- a/dav_auth/views.py +++ b/dav_auth/views.py @@ -24,39 +24,39 @@ class LoginView(auth_views.LoginView): template_name = 'dav_auth/forms/login.html' def get_redirect_url(self): - url = super(LoginView, self).get_redirect_url() + url = super().get_redirect_url() if not url and app_config.settings.login_redirect_url: url = resolve_url(app_config.settings.login_redirect_url) return url def form_valid(self, form): - r = super(LoginView, self).form_valid(form) - messages.success(self.request, _(u'Benutzer angemeldet: %(username)s') % {'username': form.get_user()}) + r = super().form_valid(form) + messages.success(self.request, _('Benutzer angemeldet: %(username)s') % {'username': form.get_user()}) try: validate_password(form.cleaned_data['password']) except ValidationError as e: - logger.warning(u'Weak password (%d): %s', self.request.user.pk, e) - message = u'
\n

\n' - message += u'Dein Passwort entspricht nicht mehr den aktuellen Passwortrichtlinien.
\n' - message += u'Bitte hilf uns die Daten deiner Teilnehmer zu schützen und ändere dein Passwort.
\n' - message += u'

\n' - message += u'

\n' - message += u'Passwort ändern\n' % {'href': reverse('dav_auth:set_password')} - message += u'

\n
\n' + logger.warning('Weak password (%d): %s', self.request.user.pk, e) + message = '
\n

\n' + message += 'Dein Passwort entspricht nicht mehr den aktuellen Passwortrichtlinien.
\n' + message += 'Bitte hilf uns die Daten deiner Teilnehmer zu schützen und ändere dein Passwort.
\n' + message += '

\n' + message += '

\n' + message += 'Passwort ändern\n' % {'href': reverse('dav_auth:set_password')} + message += '

\n
\n' messages.warning(self.request, mark_safe(message)) return r class LogoutView(auth_views.LogoutView): def get_next_page(self): - url = super(LogoutView, self).get_next_page() + url = super().get_next_page() if not url and app_config.settings.logout_redirect_url: url = resolve_url(app_config.settings.logout_redirect_url) return url def dispatch(self, request, *args, **kwargs): - r = super(LogoutView, self).dispatch(request, *args, **kwargs) - messages.success(self.request, _(u'Benutzer abgemeldet.')) + r = super().dispatch(request, *args, **kwargs) + messages.success(self.request, _('Benutzer abgemeldet.')) return r @@ -68,8 +68,8 @@ class SetPasswordView(auth_views.PasswordChangeView): return resolve_url(app_config.settings.login_redirect_url) def form_valid(self, form): - r = super(SetPasswordView, self).form_valid(form) - messages.success(self.request, _(u'Passwort gespeichert.')) + r = super().form_valid(form) + messages.success(self.request, _('Passwort gespeichert.')) if form.cleaned_data.get('send_password_mail', False): email = emails.PasswordSetEmail(self.request.user, form.cleaned_data['new_password']) email.send() @@ -91,14 +91,14 @@ class CreateAndSendPasswordView(generic.FormView): user.save() email = emails.PasswordSetEmail(user, random_password) email.send() - messages.success(self.request, _(u'Neues Passwort versendet.')) + messages.success(self.request, _('Neues Passwort versendet.')) logger.info('Password recreated for user \'%s\'', username) except user_model.DoesNotExist: logger.warning('Password recreated for unknown user \'%s\'', username) - return super(CreateAndSendPasswordView, self).form_valid(form) + return super().form_valid(form) def get(self, request, *args, **kwargs): if request.user.is_authenticated: return HttpResponseRedirect(reverse('dav_auth:set_password')) - return super(CreateAndSendPasswordView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) diff --git a/dav_base/__init__.py b/dav_base/__init__.py index 5467fdc..c57740f 100644 --- a/dav_base/__init__.py +++ b/dav_base/__init__.py @@ -1 +1 @@ -default_app_config = 'dav_base.apps.AppConfig' +default_app_config = 'dav_base.apps.AppConfig' # pylint: disable=invalid-name diff --git a/dav_base/apps.py b/dav_base/apps.py index e0ad555..e4ab62d 100644 --- a/dav_base/apps.py +++ b/dav_base/apps.py @@ -9,5 +9,5 @@ DEFAULT_SETTINGS = ( class AppConfig(_AppConfig): name = 'dav_base' - verbose_name = u'DAV Base App' + verbose_name = 'DAV Base App' default_settings = DEFAULT_SETTINGS diff --git a/dav_base/config/apps.py b/dav_base/config/apps.py index f074bdf..b57f220 100644 --- a/dav_base/config/apps.py +++ b/dav_base/config/apps.py @@ -7,7 +7,7 @@ from django.core.exceptions import ImproperlyConfigured logger = logging.getLogger(__name__) -class DefaultSetting(object): +class DefaultSetting: # pylint: disable=too-few-public-methods def __init__(self, name, value, key_name=None, validator=None): self.name = name self.value = value @@ -27,7 +27,7 @@ class DefaultSetting(object): raise ImproperlyConfigured('Does not match /{re}/'.format(re=self.validator)) -class AppSettings(object): +class AppSettings: # pylint: disable=too-few-public-methods def __init__(self, app_name, defaults): settings_name = 'main.settings-' + app_name @@ -45,7 +45,7 @@ class AppSettings(object): msg = 'Invalid value of {key} in {module}: {cause}'.format(key=default.key_name, module=settings_name, cause=e) - raise ImproperlyConfigured(msg) + raise ImproperlyConfigured(msg) from e setattr(self, default.name, value) elif isinstance(default.value, ImproperlyConfigured): raise default.value @@ -59,13 +59,13 @@ class AppSettings(object): msg = '{key} must be defined in {module}'.format(key=default.key_name, module=settings_name) raise default.value(msg) - else: - setattr(self, default.name, default.value) + + setattr(self, default.name, default.value) class AppConfig(_AppConfig): default_settings = () def __init__(self, app_name, app_module): - super(AppConfig, self).__init__(app_name, app_module) + super().__init__(app_name, app_module) self.settings = AppSettings(app_name, self.default_settings) diff --git a/dav_base/config/modules.py b/dav_base/config/modules.py index 024883f..ebba340 100644 --- a/dav_base/config/modules.py +++ b/dav_base/config/modules.py @@ -13,7 +13,7 @@ class ModuleConfigError(Exception): pass -class ModuleMeta(object): +class ModuleMeta: _json_file = 'module.json' _root_url_name = 'root' @@ -73,7 +73,7 @@ class ModuleMeta(object): return d -class ModuleConfig(object): +class ModuleConfig: _lazy_load = True def __init__(self, config_file_path=None, django_base_dir=None): @@ -83,7 +83,7 @@ class ModuleConfig(object): config_file_path = os.path.join(django_base_dir, DJANGO_MAIN_MODULE, MODULE_CONFIG_FILE_NAME) self._config_file_path = config_file_path - self._modules = dict() + self._modules = {} self._loaded = False if not self._lazy_load: @@ -96,13 +96,13 @@ class ModuleConfig(object): def _load(self): path = self._config_file_path - self._modules = dict() + self._modules = {} if os.path.exists(path): - with open(path, 'r') as f: + with open(path, 'r', encoding='ascii') as f: data = json.load(f) else: - data = dict() + data = {} if 'modules' in data: for meta_dict in data['modules']: @@ -131,5 +131,5 @@ class ModuleConfig(object): for meta_obj in self._modules.values(): data['modules'].append(meta_obj.dump_as_dict()) - with open(path, 'w') as f: + with open(path, 'w', encoding='ascii') as f: json.dump(data, f, indent=4) diff --git a/dav_base/console_scripts/admin.py b/dav_base/console_scripts/admin.py index 94d7fd2..70defda 100644 --- a/dav_base/console_scripts/admin.py +++ b/dav_base/console_scripts/admin.py @@ -1,8 +1,8 @@ import argparse import os -import pkg_resources import posix import sys +import pkg_resources from django.core.management import execute_from_command_line from dav_base.config.modules import DJANGO_MAIN_MODULE, ModuleConfig @@ -10,7 +10,7 @@ from dav_base.config.modules import DJANGO_MAIN_MODULE, ModuleConfig VERSION = '0.1' -class AdminCommand(object): +class AdminCommand: # pylint: disable=too-few-public-methods def _setup_argparser(self): kwargs = { 'description': 'Tool to manage the DAV django project installation.', @@ -60,7 +60,7 @@ class AdminCommand(object): def _subcmd_setup(self, cmd_args): django_main_module = DJANGO_MAIN_MODULE django_base_dir = cmd_args.path - + sys.stdout.write('Setup installation in \'{path}\'...\n'.format(path=django_base_dir)) if os.path.exists(django_base_dir): diff --git a/dav_base/console_scripts/django_project_config/additional_settings.py b/dav_base/console_scripts/django_project_config/additional_settings.py index 470bb04..1bd7468 100644 --- a/dav_base/console_scripts/django_project_config/additional_settings.py +++ b/dav_base/console_scripts/django_project_config/additional_settings.py @@ -3,8 +3,8 @@ # Additional settings for django-dav # -BASE_VAR_DIR = os.path.join(BASE_DIR, 'var') -BASE_SHARE_DIR = os.path.join(BASE_DIR, 'common') +BASE_VAR_DIR = BASE_DIR / 'var' +BASE_SHARE_DIR = BASE_DIR / 'common' # Get modules config from dav_base.config.modules import ModuleConfig @@ -14,7 +14,7 @@ INSTALLED_APPS += [ 'bootstrap3', 'datetimewidget', 'django_countries', - 'django_extensions', + # 'django_extensions', # Our main app 'dav_base', ] @@ -45,11 +45,11 @@ TEMPLATES += [ # Add our local templates directory to the template engine configurations. for config in TEMPLATES: - config['DIRS'].append(os.path.join(BASE_SHARE_DIR, 'templates')) + config['DIRS'].append(BASE_SHARE_DIR / 'templates') DATABASES['default'] = { 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_VAR_DIR, 'db', 'devel.sqlite3'), + 'NAME': BASE_VAR_DIR / 'db' / 'devel.sqlite3', } AUTH_PASSWORD_VALIDATORS = [ @@ -76,7 +76,7 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] -STATIC_ROOT = os.path.join(BASE_VAR_DIR, 'www', 'static') +STATIC_ROOT = BASE_VAR_DIR / 'www' / 'static' LANGUAGE_CODE = 'de' TIME_ZONE = 'Europe/Berlin' @@ -130,20 +130,20 @@ LOGGING = { 'default_log': { 'level': 'INFO', 'class': 'logging.FileHandler', - 'filename': os.path.join(BASE_VAR_DIR, 'log', 'default.log'), + 'filename': BASE_VAR_DIR / 'log' / 'default.log', 'formatter': 'default', }, 'error_log': { 'level': 'ERROR', 'class': 'logging.FileHandler', - 'filename': os.path.join(BASE_VAR_DIR, 'log', 'error.log'), + 'filename': BASE_VAR_DIR / 'log' / 'error.log', 'formatter': 'default', }, 'debug_log': { 'level': 'DEBUG', 'filters': ['require_debug_true'], 'class': 'logging.FileHandler', - 'filename': os.path.join(BASE_VAR_DIR, 'log', 'debug.log'), + 'filename': BASE_VAR_DIR / 'log' / 'debug.log', 'formatter': 'verbose', }, }, diff --git a/dav_base/emails.py b/dav_base/emails.py index b5b8d78..22029c2 100644 --- a/dav_base/emails.py +++ b/dav_base/emails.py @@ -9,8 +9,8 @@ app_config = apps.get_containing_app_config(__package__) logger = logging.getLogger(__name__) -class AbstractMail(object): - _subject = u'' +class AbstractMail: # pylint: disable=too-few-public-methods + _subject = '' _template_name = None def _get_sender(self): @@ -20,7 +20,7 @@ class AbstractMail(object): if subject_fmt is None: subject_fmt = self._subject if app_config.settings.email_subject_prefix: - subject_fmt = u'%s %s' % (app_config.settings.email_subject_prefix, subject_fmt) + subject_fmt = '%s %s' % (app_config.settings.email_subject_prefix, subject_fmt) subject = subject_fmt.format(**kwargs) return subject @@ -57,7 +57,7 @@ class AbstractMail(object): email = EmailMessage(subject=subject, body=body, from_email=sender, to=recipients, reply_to=reply_to) if fail_silently: - logger.info(u'Fake sending %s to %s', self.__class__.__name__, recipients) + logger.info('Fake sending %s to %s', self.__class__.__name__, recipients) else: - logger.info(u'Send %s to %s', self.__class__.__name__, recipients) + logger.info('Send %s to %s', self.__class__.__name__, recipients) email.send(fail_silently=fail_silently) diff --git a/dav_base/templates/dav_base/tests/include_if_exist.html b/dav_base/templates/dav_base/tests/include_if_exist.html index 22eefd4..5971d6c 100644 --- a/dav_base/templates/dav_base/tests/include_if_exist.html +++ b/dav_base/templates/dav_base/tests/include_if_exist.html @@ -1,6 +1,6 @@ {# This template is used by software tests #}{% load dav_base %} ---{% include_if_exist './includes/include_missing.html' %}-- ---{% include_if_exist './includes/include_missing.html' default 'dav_base/tests/includes/include_default.html' %}-- +--{% include_if_exist './includes/include_missing3.html' %}-- +--{% include_if_exist './includes/include_missing4.html' default 'dav_base/tests/includes/include_default.html' %}-- --{% include_if_exist './includes/include_optional.html' %}-- --{% include_if_exist './includes/include_optional.html' default 'dav_base/tests/includes/include_default.html' %}-- --{% include_if_exist './includes/include_with_missing_include.html' %}-- diff --git a/dav_base/templates/dav_base/tests/include_if_exist_default_missing.html b/dav_base/templates/dav_base/tests/include_if_exist_default_missing.html index 62da605..98ee02c 100644 --- a/dav_base/templates/dav_base/tests/include_if_exist_default_missing.html +++ b/dav_base/templates/dav_base/tests/include_if_exist_default_missing.html @@ -1,2 +1,2 @@ {# This template is used by software tests #}{% load dav_base %} ---{% include_if_exist './includes/include_missing.html' default './includes/include_missing.html' %}-- +--{% include_if_exist './includes/include_missing1.html' default './includes/include_missing2.html' %}-- diff --git a/dav_base/templates/dav_base/tests/includes/include_with_missing_include.html b/dav_base/templates/dav_base/tests/includes/include_with_missing_include.html index 8694ad9..a6a44a1 100644 --- a/dav_base/templates/dav_base/tests/includes/include_with_missing_include.html +++ b/dav_base/templates/dav_base/tests/includes/include_with_missing_include.html @@ -1,2 +1,2 @@ {# This template is used by software tests #}{% load dav_base %} ---{% include './include_missing.html' %}-- +--{% include './include_missing5.html' %}-- diff --git a/dav_base/templatetags/dav_base.py b/dav_base/templatetags/dav_base.py index b8f9b3c..b8efef8 100644 --- a/dav_base/templatetags/dav_base.py +++ b/dav_base/templatetags/dav_base.py @@ -7,7 +7,7 @@ register = template.Library() def do_include_if_exist(parser, token): """ Used to include a template, that maybe does not exist. - + include_if_exist support an optional keyword 'default', which must be followed by the name of a default template. The default template will be included, if the first template does not exist. If also the default template does not exist, the behaviour is the same as for the original (builtin) include tag. @@ -16,7 +16,7 @@ def do_include_if_exist(parser, token): """ bits = token.split_contents() if len(bits) < 2: - raise template.TemplateSyntaxError("%r tag takes at least two arguments:" + raise template.TemplateSyntaxError("%r tag takes at least one argument:" " the name of the template to be included" % bits[0]) try: @@ -27,12 +27,16 @@ def do_include_if_exist(parser, token): token = template.base.Token(token.token_type, ' '.join(bits)) token2 = template.base.Token(token.token_type, bits[0] + ' ' + default_template + ' '.join(bits[2:])) default_node = template.loader_tags.do_include(parser, token2) + # TODO: I belive, this ist not the correct way to do things. + # But without setting default_node.origin here the following AttributeError will be risen within the tests: + # AttributeError: 'IncludeNode' object has no attribute 'origin' + default_node.origin = template.Origin(name='', template_name=None) except ValueError: default_node = template.defaulttags.CommentNode() except IndexError: raise template.TemplateSyntaxError("'default' keyword in %r tag requires another arguments:" " the name of the default template" % bits[0]) - + try: include_node = template.loader_tags.do_include(parser, token) except template.TemplateDoesNotExist: diff --git a/dav_base/tests/generic.py b/dav_base/tests/generic.py index 9fc2e41..f631bdf 100644 --- a/dav_base/tests/generic.py +++ b/dav_base/tests/generic.py @@ -1,5 +1,6 @@ import datetime import os +from urllib.parse import quote from django.apps import apps from django.contrib.auth.models import AbstractUser from django.contrib.staticfiles.testing import StaticLiveServerTestCase @@ -9,11 +10,10 @@ from django.test import SimpleTestCase, TestCase, tag from django.urls import reverse from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC -from six.moves.urllib.parse import quote +from selenium.webdriver.support import expected_conditions as ExpectedConditions -class AppSetting(object): +class AppSetting: # pylint: disable=too-few-public-methods def __init__(self, name, of=None): self.name = name self.of = of @@ -24,7 +24,7 @@ class AppsTestCase(SimpleTestCase): settings = () def setUp(self): - super(AppsTestCase, self).setUp() + super().setUp() if self.app_config: self.configured_settings = self.app_config.settings else: @@ -41,7 +41,7 @@ class AppsTestCase(SimpleTestCase): self.assertIsInstance(value, of) -class EmailTestMixin(object): +class EmailTestMixin: email_sender = 'Automatic Software Test ' email_base_url = 'http://localhost' email_subject_prefix = '[Test]' @@ -55,29 +55,29 @@ class EmailTestMixin(object): return mails - def assertSender(self, mail): + def assertSender(self, mail): # pylint: disable=invalid-name self.assertEqual(mail.from_email, self.email_sender) - def assertReplyTo(self, mail, addresses): + def assertReplyTo(self, mail, addresses): # pylint: disable=invalid-name self.assertEqual(len(mail.reply_to), len(addresses)) for expected_address in addresses: if isinstance(expected_address, AbstractUser): - expected_address = u'"%s" <%s>' % (expected_address.get_full_name(), expected_address.email) + expected_address = '"%s" <%s>' % (expected_address.get_full_name(), expected_address.email) self.assertIn(expected_address, mail.reply_to) - def assertRecipients(self, mail, recipients): + def assertRecipients(self, mail, recipients): # pylint: disable=invalid-name self.assertEqual(len(mail.recipients()), len(recipients)) for expected_recipient in recipients: if isinstance(expected_recipient, AbstractUser): - expected_recipient = u'"%s" <%s>' % (expected_recipient.get_full_name(), expected_recipient.email) + expected_recipient = '"%s" <%s>' % (expected_recipient.get_full_name(), expected_recipient.email) recipients = mail.recipients() self.assertIn(expected_recipient, recipients) - def assertSubject(self, mail, subject): - expected_subject = u'{} {}'.format(self.email_subject_prefix, subject) + def assertSubject(self, mail, subject): # pylint: disable=invalid-name + expected_subject = '{} {}'.format(self.email_subject_prefix, subject) self.assertEqual(mail.subject, expected_subject) - def assertBody(self, mail, body): + def assertBody(self, mail, body): # pylint: disable=invalid-name expected_lines = body.splitlines() lines = mail.body.splitlines() i = 0 @@ -93,14 +93,14 @@ class EmailTestMixin(object): self.fail('line %d: %s' % (i, e)) self.assertEqual(mail.body, body) - def setUp(self): + def setUp(self): # pylint: disable=invalid-name app_config = apps.get_app_config('dav_base') app_config.settings.email_sender = self.email_sender app_config.settings.email_base_url = self.email_base_url app_config.settings.email_subject_prefix = self.email_subject_prefix -class FormDataSet(object): +class FormDataSet: # pylint: disable=too-few-public-methods def __init__(self, data, expected_errors=None, form_kwargs=None): self.data = data self.expected_errors = expected_errors @@ -116,44 +116,46 @@ class FormsTestCase(TestCase): if form_class is None: form_class = self.form_class if form_class is None: - return True + return if data_sets is None: data_sets = self.valid_data_sets + given_form_kwargs = form_kwargs for data_set in data_sets: - fk = {} - if form_kwargs is not None: - fk.update(form_kwargs) + form_kwargs = {} + if given_form_kwargs is not None: + form_kwargs.update(given_form_kwargs) if data_set.form_kwargs is not None: - fk.update(data_set.form_kwargs) - fk['data'] = data_set.data - form = form_class(**fk) + form_kwargs.update(data_set.form_kwargs) + form_kwargs['data'] = data_set.data + form = form_class(**form_kwargs) if not form.is_valid(): errors = [] for key in form.errors.as_data(): - for ve in form.errors[key].as_data(): - errors.append(u'%s (%s)' % (ve.code, ve.message)) - self.fail(u'Invalid form data \'%s\': %s' % (data_set.data, errors)) + for e in form.errors[key].as_data(): + errors.append('%s (%s)' % (e.code, e.message)) + self.fail('Invalid form data \'%s\': %s' % (data_set.data, errors)) def test_invalid_data(self, form_class=None, data_sets=None, form_kwargs=None): if form_class is None: form_class = self.form_class if form_class is None: - return True + return if data_sets is None: data_sets = self.invalid_data_sets + given_form_kwargs = form_kwargs for data_set in data_sets: - fk = {} - if form_kwargs is not None: - fk.update(form_kwargs) + form_kwargs = {} + if given_form_kwargs is not None: + form_kwargs.update(given_form_kwargs) if data_set.form_kwargs is not None: - fk.update(data_set.form_kwargs) - fk['data'] = data_set.data + form_kwargs.update(data_set.form_kwargs) + form_kwargs['data'] = data_set.data - form = form_class(**fk) + form = form_class(**form_kwargs) if form.is_valid(): self.fail('Valid form data: \'%s\'' % data_set.data) @@ -164,7 +166,7 @@ class FormsTestCase(TestCase): self.assertIn(code, error_codes) -class Url(object): +class Url: # pylint: disable=too-few-public-methods def __init__(self, location, name=None, func=None, **kwargs): self.location = location self.name = name @@ -210,15 +212,15 @@ class UrlsTestCase(TestCase): 'Getting url named \'{}\' resolve to wrong function'.format(url.name)) -class ValidatorTestMixin(object): - def assertValid(self, validator, data): +class ValidatorTestMixin: + def assertValid(self, validator, data): # pylint: disable=invalid-name for val in data: try: validator(val) except ValidationError as e: # pragma: no cover self.fail('%s: %s' % (val, e)) - def assertInvalid(self, validator, data): + def assertInvalid(self, validator, data): # pylint: disable=invalid-name for val in data: try: validator(val) @@ -234,7 +236,7 @@ class SeleniumTestCase(StaticLiveServerTestCase): window_height = 768 def __init__(self, *args, **kwargs): - super(SeleniumTestCase, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._driver = None self._driver_options = webdriver.FirefoxOptions() self.quit_selenium = None @@ -254,7 +256,7 @@ class SeleniumTestCase(StaticLiveServerTestCase): def tearDown(self): if self.quit_selenium: self.selenium.quit() - super(SeleniumTestCase, self).tearDown() + super().tearDown() def complete_url(self, location): base_url = self.live_server_url @@ -264,8 +266,8 @@ class SeleniumTestCase(StaticLiveServerTestCase): return self.selenium.get(self.complete_url(location)) def wait_on(self, driver, ec_name, ec_argument, timeout=30): - ec = getattr(EC, ec_name) - return WebDriverWait(driver, timeout).until(ec(ec_argument)) + expected_condition = getattr(ExpectedConditions, ec_name) + return WebDriverWait(driver, timeout).until(expected_condition(ec_argument)) def wait_on_presence(self, driver, locator, timeout=30): ec_name = 'presence_of_element_located' @@ -281,7 +283,7 @@ class ScreenshotTestCase(SeleniumTestCase): locations = () def __init__(self, *args, **kwargs): - super(ScreenshotTestCase, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) screenshot_base_dir = os.path.join('tmp', 'test-screenshots') self.screenshot_path = screenshot_base_dir self.screenshot_sequences = {} @@ -298,7 +300,7 @@ class ScreenshotTestCase(SeleniumTestCase): else: self.screenshot_sequences[sequence] = 1 n = self.screenshot_sequences[sequence] - sequence = u'%s-%04d-' % (sequence, n) + sequence = '%s-%04d-' % (sequence, n) if title is None: location = self.selenium.current_url if location.startswith(self.live_server_url): @@ -308,7 +310,7 @@ class ScreenshotTestCase(SeleniumTestCase): location = 'root' title = location - base_name = u'{timestamp}-{prefix}{sequence}{title}.png'.format( + base_name = '{timestamp}-{prefix}{sequence}{title}.png'.format( timestamp=datetime.datetime.now().strftime('%Y%m%d-%H%M%S.%f'), prefix=self.screenshot_prefix, sequence=sequence, diff --git a/dav_base/tests/test_config.py b/dav_base/tests/test_config.py index 0769ffd..1ea107d 100644 --- a/dav_base/tests/test_config.py +++ b/dav_base/tests/test_config.py @@ -48,8 +48,8 @@ class DefaultSettingTestCase(SimpleTestCase): re_validator = r'[a-z]' - valid_values = u'aocd' - invalid_values = u'Aö1.' + valid_values = 'aocd' + invalid_values = 'Aö1.' setting = DefaultSetting(name, None, validator=re_validator) for val in valid_values: @@ -89,7 +89,7 @@ class AppSettingsTestCase(SimpleTestCase): DefaultSetting('no_test_setting', ImproperlyConfigured), ) with self.assertRaisesRegex(ImproperlyConfigured, 'NO_TEST_SETTING must be defined in main.settings-dav_base'): - app_settings = AppSettings(self.app_name, default_settings) + _ = AppSettings(self.app_name, default_settings) def test_improperlyconfigured_by_instance(self): """Test if mandatory but unset setting raise correct exception""" @@ -97,17 +97,17 @@ class AppSettingsTestCase(SimpleTestCase): DefaultSetting('no_test_setting', ImproperlyConfigured('Some Error Message')), ) with self.assertRaisesRegex(ImproperlyConfigured, 'Some Error Message'): - app_settings = AppSettings(self.app_name, default_settings) + _ = AppSettings(self.app_name, default_settings) def test_improperlyconfigured_by_func(self): """Test if invalid setting raise correct exception""" - def validator(value): + def validator(value): # pylint: disable=unused-argument return False default_settings = ( DefaultSetting('test_setting', 1, validator=validator), ) with self.assertRaises(ImproperlyConfigured): - app_settings = AppSettings(self.app_name, default_settings) + _ = AppSettings(self.app_name, default_settings) def test_improperlyconfigured_by_regex(self): """Test if invalid setting raise correct exception""" @@ -115,7 +115,7 @@ class AppSettingsTestCase(SimpleTestCase): DefaultSetting('test_setting', 1, validator=r'^[01]$'), ) with self.assertRaises(ImproperlyConfigured): - app_settings = AppSettings(self.app_name, default_settings) + _ = AppSettings(self.app_name, default_settings) def test_settings_from_file(self): """Test if value from settings file eliminate exception""" @@ -145,7 +145,7 @@ class AppSettingsTestCase(SimpleTestCase): class AppConfigTestCase(SimpleTestCase): def test_init(self): app_name = 'dav_base' - import dav_base as app_module + import dav_base as app_module # pylint: disable=import-outside-toplevel test_default_settings = ( DefaultSetting('test_setting', ImproperlyConfigured), @@ -154,7 +154,7 @@ class AppConfigTestCase(SimpleTestCase): class TestAppConfig(AppConfig): name = 'not_dav_base' - verbose_name = u'DAV Base App' + verbose_name = 'DAV Base App' default_settings = test_default_settings app_config = TestAppConfig(app_name, app_module) diff --git a/dav_base/tests/test_emails.py b/dav_base/tests/test_emails.py index de5648e..306aa85 100644 --- a/dav_base/tests/test_emails.py +++ b/dav_base/tests/test_emails.py @@ -10,7 +10,7 @@ MAIL_TEMPLATE = 'dav_base/tests/mail.txt' class TestCase(EmailTestMixin, SimpleTestCase): def test_no_template_configured(self): - class ConcreteMail(AbstractMail): + class ConcreteMail(AbstractMail): # pylint: disable=abstract-method disable=too-few-public-methods pass email = ConcreteMail() @@ -18,7 +18,7 @@ class TestCase(EmailTestMixin, SimpleTestCase): email.send() def test_no_get_recipients_implemented(self): - class ConcreteMail(AbstractMail): + class ConcreteMail(AbstractMail): # pylint: disable=abstract-method disable=too-few-public-methods _template_name = MAIL_TEMPLATE email = ConcreteMail() @@ -28,7 +28,7 @@ class TestCase(EmailTestMixin, SimpleTestCase): def test_send(self): recipient = 'root@localhost' - class ConcreteMail(AbstractMail): + class ConcreteMail(AbstractMail): # pylint: disable=too-few-public-methods _template_name = MAIL_TEMPLATE def _get_recipients(self): @@ -42,5 +42,5 @@ class TestCase(EmailTestMixin, SimpleTestCase): self.assertSender(mail) self.assertRecipients(mail, [recipient]) - self.assertSubject(mail, u'') + self.assertSubject(mail, '') self.assertBody(mail, 'MAILBODY') diff --git a/dav_base/tests/test_templates.py b/dav_base/tests/test_templates.py index cacf9ba..f5fb812 100644 --- a/dav_base/tests/test_templates.py +++ b/dav_base/tests/test_templates.py @@ -5,7 +5,7 @@ from django.test import SimpleTestCase class TemplatesTestCase(SimpleTestCase): def setUp(self): - super(TemplatesTestCase, self).setUp() + super().setUp() self.response_from_root_view = self.client.get('/') def test_template_usage(self): diff --git a/dav_base/tests/test_templatetags.py b/dav_base/tests/test_templatetags.py index 1cba8a1..8ee4cdc 100644 --- a/dav_base/tests/test_templatetags.py +++ b/dav_base/tests/test_templatetags.py @@ -28,20 +28,20 @@ class TemplateTagsTestCase(SimpleTestCase): def test_include_if_exist(self): template_name = 'dav_base/tests/include_if_exist.html' - template = get_template(template_name) - content = template.render() - expected_content = """ + with self.settings(DEBUG=False): + template = get_template(template_name) + + content = template.render() + expected_content = """ ---- --DEFAULT INCLUDED HTML-- --OPTIONAL INCLUDED HTML-- --OPTIONAL INCLUDED HTML-- --- ---- --- """ - self.assertEqual(content, expected_content) + self.assertEqual(content, expected_content) def test_include_if_exist_while_debug(self): template_name = 'dav_base/tests/include_if_exist.html' diff --git a/dav_base/validators.py b/dav_base/validators.py index f8d0dad..c7966ed 100644 --- a/dav_base/validators.py +++ b/dav_base/validators.py @@ -10,4 +10,4 @@ DAVNumberValidator = RegexValidator(r'^' r'(\*[0-9]{4}\*[0-9]{4})?' r'([* ][0-9]{8})?' r'$', - _(u'Ungültiges Format.')) + _('Ungültiges Format.')) diff --git a/dav_base/views.py b/dav_base/views.py index 731ea31..76422ba 100644 --- a/dav_base/views.py +++ b/dav_base/views.py @@ -7,7 +7,7 @@ class RootView(generic.TemplateView): template_name = 'dav_base/root.html' def get_context_data(self, **kwargs): - c = super(RootView, self).get_context_data(**kwargs) + c = super().get_context_data(**kwargs) root_urls = [] for module_meta_obj in settings.MODULE_CONFIG.modules.values(): root_url_name = 'root' diff --git a/dav_event_office/__init__.py b/dav_event_office/__init__.py index 6d3b94b..2136438 100644 --- a/dav_event_office/__init__.py +++ b/dav_event_office/__init__.py @@ -1 +1 @@ -default_app_config = 'dav_event_office.apps.AppConfig' +default_app_config = 'dav_event_office.apps.AppConfig' # pylint: disable=invalid-name diff --git a/dav_event_office/apps.py b/dav_event_office/apps.py index 5ea50d1..8edcfce 100644 --- a/dav_event_office/apps.py +++ b/dav_event_office/apps.py @@ -5,5 +5,5 @@ DEFAULT_SETTINGS = () class AppConfig(_AppConfig): name = 'dav_event_office' - verbose_name = u'DAV Touren- & Kursreferat' + verbose_name = 'DAV Touren- & Kursreferat' default_settings = DEFAULT_SETTINGS diff --git a/dav_event_office/templates/dav_event_office/includes/home_tiles.html b/dav_event_office/templates/dav_event_office/includes/home_tiles.html index fb68229..b30cf09 100644 --- a/dav_event_office/templates/dav_event_office/includes/home_tiles.html +++ b/dav_event_office/templates/dav_event_office/includes/home_tiles.html @@ -8,7 +8,7 @@

Hier sind alle Veranstaltungen gelistet.

@@ -18,14 +18,14 @@ Zu jeder Veranstaltung ist eine Detail-Seite aufrufbar, über die Details und an

-

{% trans 'Teilnehmer' %} ({% trans 'Zugang nur für Geschäftstelle' %})

+

{% trans 'Teilnehmer*innen' %} ({% trans 'Zugang nur für Geschäftstelle' %})

- Hier sind alle angemeldeten Teilnehmer gelistet. + Hier sind alle angemeldeten Teilnehmer*innen gelistet.

- {% trans 'Teilnehmer' %} + {% trans 'Teilnehmer*innen' %}

diff --git a/dav_event_office/urls.py b/dav_event_office/urls.py index e2686c2..3def02d 100644 --- a/dav_event_office/urls.py +++ b/dav_event_office/urls.py @@ -2,6 +2,8 @@ from django.conf.urls import url from . import views +app_name = 'dav_event_office' + urlpatterns = [ url(r'^home$', views.HomeView.as_view(), name='root'), url(r'^participants$', views.ParticipantListView.as_view(), name='participant-list'), diff --git a/dav_event_office/views.py b/dav_event_office/views.py index 148d3b1..fc3b808 100644 --- a/dav_event_office/views.py +++ b/dav_event_office/views.py @@ -24,7 +24,7 @@ class EventListView(_EventListView): def dispatch(self, request, *args, **kwargs): if not DefaultWorkflow.has_global_permission(request.user, 'payment'): raise PermissionDenied('payment') - return super(EventListView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) class EventDetailView(_EventRegistrationsView): @@ -34,7 +34,7 @@ class EventDetailView(_EventRegistrationsView): def dispatch(self, request, *args, **kwargs): if not DefaultWorkflow.has_global_permission(request.user, 'payment'): raise PermissionDenied('payment') - return super(EventDetailView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) class ParticipantListView(generic.ListView): @@ -66,4 +66,4 @@ class ParticipantListView(generic.ListView): def dispatch(self, request, *args, **kwargs): if not DefaultWorkflow.has_global_permission(request.user, 'payment'): raise PermissionDenied('payment') - return super(ParticipantListView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) diff --git a/dav_events/__init__.py b/dav_events/__init__.py index 5b92057..54def6e 100644 --- a/dav_events/__init__.py +++ b/dav_events/__init__.py @@ -1 +1 @@ -default_app_config = 'dav_events.apps.AppConfig' +default_app_config = 'dav_events.apps.AppConfig' # pylint: disable=invalid-name diff --git a/dav_events/apps.py b/dav_events/apps.py index 1b56f05..663cfb7 100644 --- a/dav_events/apps.py +++ b/dav_events/apps.py @@ -16,17 +16,17 @@ DEFAULT_SETTINGS = ( DefaultSetting('groups_publisher_facebook', []), DefaultSetting('groups_office', []), DefaultSetting('forms_development_init', False), - DefaultSetting('form_initials', dict()), + DefaultSetting('form_initials', {}), DefaultSetting('matrix_config', ImproperlyConfigured), DefaultSetting('publish_before_begin_days', 10), DefaultSetting('publish_before_deadline_days', 7), - DefaultSetting('publish_issues', list()), + DefaultSetting('publish_issues', []), ) class AppConfig(_AppConfig): name = 'dav_events' - verbose_name = u'DAV Touren & Kurse' + verbose_name = 'DAV Touren & Kurse' default_settings = DEFAULT_SETTINGS def ready(self): diff --git a/dav_events/converters.py b/dav_events/converters.py index 3a09a34..86f3b9f 100644 --- a/dav_events/converters.py +++ b/dav_events/converters.py @@ -1,13 +1,13 @@ import datetime import logging -import pytz import re +import pytz from six import string_types logger = logging.getLogger(__name__) -class Iso8601Serializer(object): +class Iso8601Serializer: marker = 'ISO8601' separator = ':' diff --git a/dav_events/forms/registration.py b/dav_events/forms/registration.py index 2d28b2b..f4561b4 100644 --- a/dav_events/forms/registration.py +++ b/dav_events/forms/registration.py @@ -5,4 +5,4 @@ from django.utils.translation import ugettext_lazy as _ class RegistrationResponseForm(forms.Form): apply_reduced_fee = forms.BooleanField(required=False, - label=_(u'Reduzierte Teilnahmegebühr')) + label=_('Reduzierte Teilnahmegebühr')) diff --git a/dav_events/migrations/0042_auto_20220607_1345.py b/dav_events/migrations/0042_auto_20220607_1345.py new file mode 100644 index 0000000..6599ac5 --- /dev/null +++ b/dav_events/migrations/0042_auto_20220607_1345.py @@ -0,0 +1,44 @@ +# Generated by Django 3.2.13 on 2022-06-07 11:45 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('dav_events', '0041_auto_20210107_1220'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='eventchange', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='eventflag', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='participant', + name='event', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='participants', to='dav_events.event'), + ), + migrations.AlterField( + model_name='participant', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='trashedparticipant', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/dav_events/models/event.py b/dav_events/models/event.py index 18c33d6..01fcc02 100644 --- a/dav_events/models/event.py +++ b/dav_events/models/event.py @@ -10,10 +10,9 @@ import unicodedata from babel.dates import format_date from django.conf import settings from django.contrib.auth import get_user_model -from django.core.urlresolvers import reverse +from django.urls import reverse from django.db import models from django.template.loader import get_template -from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import get_language, ugettext_lazy as _ from django_countries.fields import Country, CountryField @@ -27,7 +26,6 @@ from .eventchange import EventChange logger = logging.getLogger(__name__) -@python_2_unicode_compatible class Event(models.Model): # Metadata owner = models.ForeignKey(settings.AUTH_USER_MODEL, diff --git a/dav_events/models/eventchange.py b/dav_events/models/eventchange.py index a1f3084..8991fbb 100644 --- a/dav_events/models/eventchange.py +++ b/dav_events/models/eventchange.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals from django.conf import settings from django.db import models from django.utils import timezone -from django.utils.encoding import python_2_unicode_compatible from . import get_ghost_user, get_system_user @@ -12,7 +11,6 @@ def get_system_user_id(): return get_system_user().id -@python_2_unicode_compatible class EventChange(models.Model): UPDATE = 'update' RAISE_FLAG = 'set_flag' @@ -23,7 +21,7 @@ class EventChange(models.Model): (LOWER_FLAG, 'Lower Flag'), ) - event = models.ForeignKey('dav_events.Event', related_name='changes') + event = models.ForeignKey('dav_events.Event', related_name='changes', on_delete=models.CASCADE) timestamp = models.DateTimeField(default=timezone.now) user = models.ForeignKey(settings.AUTH_USER_MODEL, default=get_system_user_id, diff --git a/dav_events/models/eventflag.py b/dav_events/models/eventflag.py index 2fe4976..c825c91 100644 --- a/dav_events/models/eventflag.py +++ b/dav_events/models/eventflag.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals from django.conf import settings from django.db import models from django.utils import timezone -from django.utils.encoding import python_2_unicode_compatible from . import get_ghost_user, get_system_user @@ -11,9 +10,8 @@ def get_system_user_id(): return get_system_user().id -@python_2_unicode_compatible class EventFlag(models.Model): - event = models.ForeignKey('dav_events.Event', related_name='flags') + event = models.ForeignKey('dav_events.Event', related_name='flags', on_delete=models.CASCADE) status = models.ForeignKey('dav_events.EventStatus', on_delete=models.PROTECT, related_name='+') diff --git a/dav_events/models/eventstatus.py b/dav_events/models/eventstatus.py index af7f5ce..fd62e61 100644 --- a/dav_events/models/eventstatus.py +++ b/dav_events/models/eventstatus.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals from django.db import models -from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from ..validators import IdStringValidator @@ -22,7 +21,6 @@ BOOTSTRAP_CONTEXT_CHOICES = ( ) -@python_2_unicode_compatible class EventStatus(models.Model): code = models.CharField(primary_key=True, max_length=254, validators=[IdStringValidator]) severity = models.IntegerField(unique=True) diff --git a/dav_events/models/oneclickaction.py b/dav_events/models/oneclickaction.py index 336babc..63d48bd 100644 --- a/dav_events/models/oneclickaction.py +++ b/dav_events/models/oneclickaction.py @@ -3,10 +3,9 @@ from __future__ import unicode_literals import logging import uuid from django.contrib.auth import get_user_model -from django.core.urlresolvers import reverse +from django.urls import reverse from django.db import models from django.utils import timezone -from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext, ugettext_lazy as _ from .event import Event @@ -14,7 +13,6 @@ from .event import Event logger = logging.getLogger(__name__) -@python_2_unicode_compatible class OneClickAction(models.Model): COMMANDS = ( ('EVENT_LIST', 'login and go to event list (user id)'), diff --git a/dav_events/models/participant.py b/dav_events/models/participant.py index da0b692..d664c35 100644 --- a/dav_events/models/participant.py +++ b/dav_events/models/participant.py @@ -4,7 +4,6 @@ import datetime from django.core.exceptions import ValidationError from django.db import models from django.utils import timezone -from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from dav_base.validators import DAVNumberValidator @@ -12,7 +11,6 @@ from dav_base.validators import DAVNumberValidator midnight = datetime.time(00, 00, 00) -@python_2_unicode_compatible class AbstractParticipant(models.Model): personal_names = models.CharField(max_length=1024, verbose_name=_('Vorname(n)')) @@ -158,9 +156,8 @@ class AbstractParticipant(models.Model): return timezone.make_aware(datetime.datetime.combine(purge_date, midnight)) -@python_2_unicode_compatible class Participant(AbstractParticipant): - event = models.ForeignKey('Event', related_name='participants') + event = models.ForeignKey('Event', related_name='participants', on_delete=models.PROTECT) created_at = models.DateTimeField(auto_now_add=True) position = models.IntegerField(verbose_name='Listennummer') diff --git a/dav_events/models/trash/__init__.py b/dav_events/models/trash/__init__.py index 6723f08..4c8a1bc 100644 --- a/dav_events/models/trash/__init__.py +++ b/dav_events/models/trash/__init__.py @@ -1 +1 @@ -from .trashed_participant import TrashedParticipant \ No newline at end of file +from .trashed_participant import TrashedParticipant diff --git a/dav_events/models/trash/trashed_participant.py b/dav_events/models/trash/trashed_participant.py index 90609ac..916ca08 100644 --- a/dav_events/models/trash/trashed_participant.py +++ b/dav_events/models/trash/trashed_participant.py @@ -1,15 +1,13 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models -from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from ..participant import AbstractParticipant -@python_2_unicode_compatible class TrashedParticipant(AbstractParticipant): - event = models.ForeignKey('Event', related_name='trashed_participants') + event = models.ForeignKey('Event', related_name='trashed_participants', on_delete=models.CASCADE) created_at = models.DateTimeField() trashed_at = models.DateTimeField(auto_now_add=True) position = models.IntegerField(verbose_name='Listennummer') diff --git a/dav_events/templates/dav_events/emails/event_to_publish_web.txt b/dav_events/templates/dav_events/emails/event_to_publish_web.txt index bcaa884..ad3735f 100644 --- a/dav_events/templates/dav_events/emails/event_to_publish_web.txt +++ b/dav_events/templates/dav_events/emails/event_to_publish_web.txt @@ -11,10 +11,10 @@ Der folgende Link führt zur Veranstaltung: Veröffentlichung: ({% if planned_publication_date %}{{ planned_publication_date|date:'l, d. F Y' }}{% else %}sofort{% endif %}) ================= +Titel: {{ number }} - {{ title }} Veröffentlichung starten: {% if planned_publication_date %}{{ planned_publication_date|date:'d.m.Y' }} 00:00:00{% else %}--{% endif %} Veröffentlichung beenden: {{ day_after|date:'d.m.Y' }} 00:00:00 Erstellungsdatum: {{ first_day|date:'d.m.Y' }} 00:00:00 -Titel: {{ number }} - {{ title }} {% if internal_note %} Bearbeitungshinweis: ==================== diff --git a/dav_events/templates/dav_events/includes/home_tiles.html b/dav_events/templates/dav_events/includes/home_tiles.html index 9c7b3f1..7d32e34 100644 --- a/dav_events/templates/dav_events/includes/home_tiles.html +++ b/dav_events/templates/dav_events/includes/home_tiles.html @@ -26,7 +26,7 @@ Du wirst dann per E-Mail auf dem laufenden gehalten.

Hier kannst du deine eingetragenen Touren und Kurse verwalten.

diff --git a/dav_events/templatetags/dav_events.py b/dav_events/templatetags/dav_events.py index cbeac1d..367679c 100644 --- a/dav_events/templatetags/dav_events.py +++ b/dav_events/templatetags/dav_events.py @@ -14,9 +14,9 @@ register = template.Library() @register.simple_tag def render_event_status(event, show_void=True): - label_html = u'{label} ' + label_html = '{label} ' - html = u'' + html = '' status_list = event.workflow.get_status_list() if show_void and len(status_list) < 1: @@ -39,29 +39,29 @@ def render_event_status(event, show_void=True): @register.simple_tag def render_event_changelog(event): - change_templ = u'
  • \n' \ - u'\t

    ' \ - u'' \ - u' {timestamp}' \ - u' - ' \ - u' {user}

    \n' \ - u'\t{content}\n' \ - u'
  • \n' - update_sub_templ = u'
  • \n' \ - u'\t{field}:{separator1}\n' \ - u'\t{refer}\n' \ - u'\t{separator2}\n' \ - u'\t{current}\n' \ - u'
  • \n' - raise_flag_templ = u'' \ - u' {label}' \ - u' ' - lower_flag_templ = u'' \ - u' {label}' \ - u' ' + change_templ = '
  • \n' \ + '\t

    ' \ + '' \ + ' {timestamp}' \ + ' - ' \ + ' {user}

    \n' \ + '\t{content}\n' \ + '
  • \n' + update_sub_templ = '
  • \n' \ + '\t{field}:{separator1}\n' \ + '\t{refer}\n' \ + '\t{separator2}\n' \ + '\t{current}\n' \ + '
  • \n' + raise_flag_templ = '' \ + ' {label}' \ + ' ' + lower_flag_templ = '' \ + ' {label}' \ + ' ' if event.changes.exists(): - html = u'\n' else: - html = _(u'Keine Einträge') + html = _('Keine Einträge') return mark_safe(html) diff --git a/dav_events/tests/generic.py b/dav_events/tests/generic.py index be47c69..f12a516 100644 --- a/dav_events/tests/generic.py +++ b/dav_events/tests/generic.py @@ -6,13 +6,12 @@ import os from django.apps import apps from django.contrib.auth import get_user_model from django.contrib.auth.models import Group -from django.db import models from ..models.event import Event from ..models.eventstatus import EventStatus -class RoleMixin(object): +class RoleMixin: def create_user_for_role(self, role_name, password, first_name, last_name): group = Group(name=role_name) group.save() @@ -31,7 +30,7 @@ class RoleMixin(object): return user -class EventMixin(object): +class EventMixin: def get_status_label(self, status_code): return EventStatus.objects.get(code=status_code).label diff --git a/dav_events/tests/test_converters.py b/dav_events/tests/test_converters.py index a43c796..bd94c86 100644 --- a/dav_events/tests/test_converters.py +++ b/dav_events/tests/test_converters.py @@ -1,4 +1,3 @@ -import sys import datetime from unittest import TestCase @@ -10,7 +9,7 @@ class Iso8601SerializerTestCase(TestCase): date = start_date while date <= end_date: text = '%04d-%02d-%02d' % (date.year, date.month, date.day) - yield (date, text) + yield date, text date += datetime.timedelta(step) def _gen_times(self, step_hours=1, step_minutes=1, step_seconds=1): @@ -50,7 +49,7 @@ class Iso8601SerializerTestCase(TestCase): '2019-03-01', ) for value in invalid_values: - emsg = ('Expected datetime.datetime, datetime.date or datetime.time,' + emsg = ('Expected datetime.datetime, datetime.date or datetime.time,' ' not {}'.format(value.__class__.__name__)) with self.assertRaisesRegexp(ValueError, emsg): Iso8601Serializer.serialize(value) diff --git a/dav_events/tests/test_emails.py b/dav_events/tests/test_emails.py index 2e3c3e8..f17fb42 100644 --- a/dav_events/tests/test_emails.py +++ b/dav_events/tests/test_emails.py @@ -83,7 +83,7 @@ Ausschreibung: ============== {event_text}""" -EVENT_ACCEPTED_EMAIL_TEMPLATE=u"""Hallo {recipient_first_name}, +EVENT_ACCEPTED_EMAIL_TEMPLATE = """Hallo {recipient_first_name}, deine Veranstaltung wurde von {editor_full_name} freigegeben. Die Redaktion wurde informiert, um deine Veranstaltung zu veröffentlichen. @@ -113,10 +113,10 @@ Der folgende Link führt zur Veranstaltung: Veröffentlichung: ({planned_publication_date}) ================= +Titel: {number} - {title} Veröffentlichung starten: {planned_publication_date_short} 00:00:00 Veröffentlichung beenden: {day_after_short} 00:00:00 Erstellungsdatum: {first_day_short} 00:00:00 -Titel: {number} - {title} Bearbeitungshinweis: ==================== diff --git a/dav_events/tests/test_oneclickactions.py b/dav_events/tests/test_oneclickactions.py index beee8c2..10ddbdb 100644 --- a/dav_events/tests/test_oneclickactions.py +++ b/dav_events/tests/test_oneclickactions.py @@ -117,15 +117,12 @@ class ActionTestCase(EmailTestMixin, RoleMixin, EventMixin, TestCase): ' von %(user)s' ' auf \'%(status)s\' gesetzt.') % { 'status': status_label, - 'date': datetime.datetime.now().strftime('%d.%m.%Y %H:%M:%S'), + # 'date': datetime.datetime.now().strftime('%d.%m.%Y %H:%M:%S'), + 'date': '\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}:\d{2}', 'user': user.get_full_name(), }) - html = message.replace('\'', ''') - # Sometimes this test fail, and we cannot see the tested content, so we create our own Exception - try: - self.assertInHTML(html, content) - except AssertionError: - raise AssertionError('Not in HTML:\n{}\n-----\n{}\n'.format(html, content)) + html = message.replace('\'', ''') + self.assertRegex(content, html) self.assertRegex(content, r'alert-success') def setUp(self): diff --git a/dav_events/tests/test_views.py b/dav_events/tests/test_views.py index b17fc06..0f9a355 100644 --- a/dav_events/tests/test_views.py +++ b/dav_events/tests/test_views.py @@ -158,4 +158,3 @@ class EventsTestCase(EventMixin, TestCase): def test_event_create_view(self): for data in self.gen_create_event_input(): self.create_event_by_view(data) - diff --git a/dav_events/urls.py b/dav_events/urls.py index 40cb2d7..bef399c 100644 --- a/dav_events/urls.py +++ b/dav_events/urls.py @@ -2,6 +2,8 @@ from django.conf.urls import url from . import views +app_name = 'dav_events' + urlpatterns = [ url(r'^home$', views.base.HomeView.as_view(), name='root'), url(r'^$', views.events.EventListView.as_view(), name='list'), diff --git a/dav_events/views/actions.py b/dav_events/views/actions.py index f7406ea..413d2c0 100644 --- a/dav_events/views/actions.py +++ b/dav_events/views/actions.py @@ -23,7 +23,7 @@ class OneClickActionRunView(generic.DetailView): login(self.request, user) logger.info('Logged in via OneClickAction: %s', user.username) messages.success(self.request, - _(u'Benutzer angemeldet: %(username)s') % {'username': user.username}) + _('Benutzer angemeldet: %(username)s') % {'username': user.username}) if 'location' in result: return HttpResponseRedirect(result['location']) diff --git a/dav_events/views/events.py b/dav_events/views/events.py index c6711c9..40f3f37 100644 --- a/dav_events/views/events.py +++ b/dav_events/views/events.py @@ -754,14 +754,14 @@ class EventCreateView(EventPermissionMixin, generic.FormView): _(u'Du hast jemand anderen als Tourenleiter eingetragen.'), _(u'Warum machst du sowas?') )) - elif owner.has_usable_password(): - next_url = reverse('dav_events:list') - else: + elif owner.last_login is None: login(self.request, owner) next_url = reverse('dav_auth:set_password') messages.success(self.request, _(u'Neuen Benutzer angemeldet: %(username)s') % {'username': owner.username}) messages.warning(self.request, _(u'Bitte neues Passwort setzen!')) + else: + next_url = reverse('dav_events:list') return HttpResponseRedirect(next_url) def clean_session_data(self, session=None): diff --git a/dav_registration/__init__.py b/dav_registration/__init__.py index 2d6cb8c..f6bcdc8 100644 --- a/dav_registration/__init__.py +++ b/dav_registration/__init__.py @@ -1 +1 @@ -default_app_config = 'dav_registration.apps.AppConfig' +default_app_config = 'dav_registration.apps.AppConfig' # pylint: disable=invalid-name diff --git a/dav_registration/apps.py b/dav_registration/apps.py index e121790..af6440f 100644 --- a/dav_registration/apps.py +++ b/dav_registration/apps.py @@ -9,7 +9,7 @@ DEFAULT_SETTINGS = ( class AppConfig(_AppConfig): name = 'dav_registration' - verbose_name = u'DAV Kurs-Anmeldungen' + verbose_name = 'DAV Kurs-Anmeldungen' default_settings = DEFAULT_SETTINGS def ready(self): diff --git a/dav_registration/emails.py b/dav_registration/emails.py index 5cc2659..bddba0f 100644 --- a/dav_registration/emails.py +++ b/dav_registration/emails.py @@ -2,7 +2,7 @@ from dav_base.emails import AbstractMail -class AbstractRegistrationMail(AbstractMail): +class AbstractRegistrationMail(AbstractMail): # pylint: disable=too-few-public-methods def __init__(self, recipient, registration): self._recipient = recipient self._registration = registration @@ -13,45 +13,45 @@ class AbstractRegistrationMail(AbstractMail): subject_fmt = self._subject if self._event.number: - subject_fmt = u'%s: %s' % (self._event.number, subject_fmt) + subject_fmt = '%s: %s' % (self._event.number, subject_fmt) - return super(AbstractRegistrationMail, self)._get_subject(subject_fmt=subject_fmt, **kwargs) + return super()._get_subject(subject_fmt=subject_fmt, **kwargs) def _get_recipients(self): if hasattr(self._recipient, 'get_full_name') and hasattr(self._recipient, 'email'): - r = u'"{fullname}" <{email}>'.format(fullname=self._recipient.get_full_name(), - email=self._recipient.email) + r = '"{fullname}" <{email}>'.format(fullname=self._recipient.get_full_name(), + email=self._recipient.email) else: r = self._recipient return [r] def _get_context_data(self, extra_context=None): - context = super(AbstractRegistrationMail, self)._get_context_data(extra_context=extra_context) + context = super()._get_context_data(extra_context=extra_context) context['recipient'] = self._recipient context['registration'] = self._registration context['event'] = self._event return context -class InformTrainerRegistrationMail(AbstractRegistrationMail): - _subject = u'Anmeldung {full_name}' +class InformTrainerRegistrationMail(AbstractRegistrationMail): # pylint: disable=too-few-public-methods + _subject = 'Anmeldung {full_name}' _template_name = 'dav_registration/emails/inform_trainer.txt' def _get_subject(self, subject_fmt=None, **kwargs): kwargs['full_name'] = self._registration.get_full_name() - return super(InformTrainerRegistrationMail, self)._get_subject(subject_fmt=subject_fmt, **kwargs) + return super()._get_subject(subject_fmt=subject_fmt, **kwargs) def _get_reply_to(self): - s = u'"{fullname}" <{email}>'.format(fullname=self._registration.get_full_name(), - email=self._registration.email_address) - return [s] + reply_to = '"{fullname}" <{email}>'.format(fullname=self._registration.get_full_name(), + email=self._registration.email_address) + return [reply_to] -class InformSelfRegistrationMail(AbstractRegistrationMail): - _subject = u'Deine Anmeldung' +class InformSelfRegistrationMail(AbstractRegistrationMail): # pylint: disable=too-few-public-methods + _subject = 'Deine Anmeldung' _template_name = 'dav_registration/emails/inform_self.txt' def _get_reply_to(self): - s = u'"{fullname}" <{email}>'.format(fullname=self._event.owner.get_full_name(), - email=self._event.owner.email) - return [s] + reply_to = '"{fullname}" <{email}>'.format(fullname=self._event.owner.get_full_name(), + email=self._event.owner.email) + return [reply_to] diff --git a/dav_registration/forms.py b/dav_registration/forms.py index 2b10316..5b907b9 100644 --- a/dav_registration/forms.py +++ b/dav_registration/forms.py @@ -11,13 +11,13 @@ logger = logging.getLogger(__name__) class RegistrationForm(forms.ModelForm): not_dav_member = forms.BooleanField(required=False, - label=_(u'Ich bin noch kein DAV Mitglied.'), - help_text=u'%s
    \n%s' % ( - _(u'Wenn du noch kein DAV Mitglied bist,' - u' oder deine Aufnahme noch in Arbeit ist,' - u' kreuze dieses Feld hier an.'), - _(u'Spätestens zu Veranstaltungsbeginn muss' - u' jedoch eine Mitgliedschaft bestehen.') + label=_('Ich bin noch kein DAV Mitglied.'), + help_text='%s
    \n%s' % ( + _('Wenn du noch kein DAV Mitglied bist,' + ' oder deine Aufnahme noch in Arbeit ist,' + ' kreuze dieses Feld hier an.'), + _('Spätestens zu Veranstaltungsbeginn muss' + ' jedoch eine Mitgliedschaft bestehen.') )) class Meta: @@ -30,7 +30,7 @@ class RegistrationForm(forms.ModelForm): 'note': forms.Textarea(attrs={'rows': 5}), } labels = { - 'apply_reduced_fee': _(u'Ich bin noch keine 25 Jahre alt oder besitze einen "Karlsruher Pass".'), + 'apply_reduced_fee': _('Ich bin noch keine 25 Jahre alt oder besitze einen "Karlsruher Pass".'), } def clean_year_of_birth(self): @@ -40,16 +40,16 @@ class RegistrationForm(forms.ModelForm): val = self.cleaned_data.get('year_of_birth') if val > year_now: raise forms.ValidationError( - ugettext(u'Dein Geburtsjahr liegt in der Zukunft?' - u' Das finden wir gut,' - u' aber bitte melde dich besser mal per E-Mail bei uns.'), + ugettext('Dein Geburtsjahr liegt in der Zukunft?' + ' Das finden wir gut,' + ' aber bitte melde dich besser mal per E-Mail bei uns.'), code='to_young', ) elif val < (year_now - max_age): raise forms.ValidationError( - ugettext(u'Du bist schon über %(max_age)d Jahre alt?' - u' Das finden wir gut,' - u' aber bitte melde dich besser mal per E-Mail bei uns.'), + ugettext('Du bist schon über %(max_age)d Jahre alt?' + ' Das finden wir gut,' + ' aber bitte melde dich besser mal per E-Mail bei uns.'), params={'max_age': max_age}, code='to_old', ) @@ -63,8 +63,8 @@ class RegistrationForm(forms.ModelForm): need_experience = False if need_experience: raise forms.ValidationError( - ugettext(u'Die Tourenleiter*innen brauchen ein paar Angaben,' - u' was du bereits kannst oder wie fit du bist.'), + ugettext('Die Tourenleiter*innen brauchen ein paar Angaben,' + ' was du bereits kannst oder wie fit du bist.'), code='need_experience', ) return val @@ -73,18 +73,18 @@ class RegistrationForm(forms.ModelForm): val = self.cleaned_data.get('privacy_policy_accepted') if not val and self.instance.privacy_policy: raise forms.ValidationError( - ugettext(u'Wir müssen deine Daten leider speichern können,' - u' damit wir wissen, dass du teilnehmen möchtest.'), + ugettext('Wir müssen deine Daten leider speichern können,' + ' damit wir wissen, dass du teilnehmen möchtest.'), code='privacy_policy_not_accepted', ) return val def clean(self): - super(RegistrationForm, self).clean() + super().clean() dav_member = self.cleaned_data.get('dav_member') dav_number = self.cleaned_data.get('dav_number') if dav_member and not dav_number: - error_msg = ugettext(u'Wenn du DAV Mitglied bist, brauchen wir deine Mitgliedsnummer.') + error_msg = ugettext('Wenn du DAV Mitglied bist, brauchen wir deine Mitgliedsnummer.') self.add_error('not_dav_member', error_msg) raise forms.ValidationError(error_msg, code='dav_number_missing') return self.cleaned_data diff --git a/dav_registration/migrations/0011_auto_20220607_1345.py b/dav_registration/migrations/0011_auto_20220607_1345.py new file mode 100644 index 0000000..c07273e --- /dev/null +++ b/dav_registration/migrations/0011_auto_20220607_1345.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.13 on 2022-06-07 11:45 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('dav_events', '0042_auto_20220607_1345'), + ('dav_registration', '0010_registration_apply_reduced_fee'), + ] + + operations = [ + migrations.AlterField( + model_name='registration', + name='event', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='registrations', to='dav_events.event'), + ), + migrations.AlterField( + model_name='registration', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='registrationstatus', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/dav_registration/migrations/0012_alter_registrationstatus_accepted.py b/dav_registration/migrations/0012_alter_registrationstatus_accepted.py new file mode 100644 index 0000000..789970d --- /dev/null +++ b/dav_registration/migrations/0012_alter_registrationstatus_accepted.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.13 on 2023-02-15 16:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dav_registration', '0011_auto_20220607_1345'), + ] + + operations = [ + migrations.AlterField( + model_name='registrationstatus', + name='accepted', + field=models.BooleanField(blank=True, null=True, verbose_name='Zusage erteilt'), + ), + ] diff --git a/dav_registration/models.py b/dav_registration/models.py index d5cc281..dd6d111 100644 --- a/dav_registration/models.py +++ b/dav_registration/models.py @@ -6,7 +6,6 @@ from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse from django.utils import timezone -from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from dav_base.validators import DAVNumberValidator @@ -19,9 +18,8 @@ logger = logging.getLogger(__name__) midnight = datetime.time(00, 00, 00) -@python_2_unicode_compatible class Registration(models.Model): - event = models.ForeignKey(Event, related_name='registrations') + event = models.ForeignKey(Event, related_name='registrations', on_delete=models.PROTECT) created_at = models.DateTimeField(auto_now_add=True) personal_names = models.CharField(max_length=1024, @@ -184,7 +182,7 @@ Anmerkung: self.purge_at = self.__class__.calc_purge_at(self.event) self.full_clean() - super(Registration, self).save(**kwargs) + super().save(**kwargs) if creating: status = RegistrationStatus(registration=self) @@ -219,12 +217,11 @@ Anmerkung: return timezone.make_aware(datetime.datetime.combine(purge_date, midnight)) -@python_2_unicode_compatible class RegistrationStatus(models.Model): registration = models.OneToOneField(Registration, on_delete=models.CASCADE, related_name='status') updated_at = models.DateTimeField(auto_now=True) answered = models.BooleanField(_('Durch Tourleitung beantwortet'), default=False) - accepted = models.NullBooleanField(_('Zusage erteilt')) + accepted = models.BooleanField(_('Zusage erteilt'), null=True, blank=True) class Meta: verbose_name = _('Anmeldungsstatus') @@ -240,7 +237,7 @@ class RegistrationStatus(models.Model): def save(self, **kwargs): self.full_clean() - super(RegistrationStatus, self).save(**kwargs) + super().save(**kwargs) def set_accepted(self): self.accepted = True diff --git a/dav_registration/signals.py b/dav_registration/signals.py index 032910a..d33bf24 100644 --- a/dav_registration/signals.py +++ b/dav_registration/signals.py @@ -5,7 +5,7 @@ from . import emails registration_created = Signal(providing_args=['registration']) -def send_emails_on_registration(sender, **kwargs): +def send_emails_on_registration(sender, **kwargs): # pylint: disable=unused-argument registration = kwargs.get('registration') # Inform the event owner (trainer) @@ -14,7 +14,7 @@ def send_emails_on_registration(sender, **kwargs): email.send() # Inform the potential participant - recipient = u'"{fullname}" <{email}>'.format(fullname=registration.get_full_name(), - email=registration.email_address) + recipient = '"{fullname}" <{email}>'.format(fullname=registration.get_full_name(), + email=registration.email_address) email = emails.InformSelfRegistrationMail(recipient=recipient, registration=registration) email.send() diff --git a/dav_registration/templatetags/dav_registration.py b/dav_registration/templatetags/dav_registration.py index 0485408..3cac4cd 100644 --- a/dav_registration/templatetags/dav_registration.py +++ b/dav_registration/templatetags/dav_registration.py @@ -12,5 +12,3 @@ def render_event_paragraphs(event): @register.simple_tag def render_event_facts(event): return render_to_string('dav_registration/event/facts.html', {'event': event}) - - diff --git a/dav_registration/tests/generic.py b/dav_registration/tests/generic.py index 499023b..86daee2 100644 --- a/dav_registration/tests/generic.py +++ b/dav_registration/tests/generic.py @@ -4,7 +4,7 @@ from ..models import Registration THIS_YEAR = timezone.now().year -class RegistrationMixin(object): +class RegistrationMixin: # pylint: disable=too-few-public-methods def create_registration(self, data): r = Registration(**data) r.save() diff --git a/dav_registration/tests/test_emails.py b/dav_registration/tests/test_emails.py index 1c6547b..d288c5d 100644 --- a/dav_registration/tests/test_emails.py +++ b/dav_registration/tests/test_emails.py @@ -95,7 +95,7 @@ Zeitpunkt der Datenlöschung: {purge_at} class EmailsTestCase(EmailTestMixin, EventMixin, RegistrationMixin, TestCase): def setUp(self): - super(EmailsTestCase, self).setUp() + super().setUp() app_config = apps.get_app_config('dav_events') app_config.settings.enable_email_on_status_update = False diff --git a/dav_registration/tests/test_models.py b/dav_registration/tests/test_models.py index 0644611..c4fbe90 100644 --- a/dav_registration/tests/test_models.py +++ b/dav_registration/tests/test_models.py @@ -11,7 +11,7 @@ from .generic import THIS_YEAR, RegistrationMixin class RegistrationTestCase(EventMixin, RegistrationMixin, TestCase): def setUp(self): - super(RegistrationTestCase, self).setUp() + super().setUp() app_config = apps.get_app_config('dav_events') app_config.settings.enable_email_on_status_update = False @@ -100,4 +100,3 @@ class RegistrationTestCase(EventMixin, RegistrationMixin, TestCase): 'dav_member': False, } self.create_registration(registration_data) - diff --git a/dav_registration/tests/test_utils.py b/dav_registration/tests/test_utils.py index 0dd3994..b7221de 100644 --- a/dav_registration/tests/test_utils.py +++ b/dav_registration/tests/test_utils.py @@ -51,7 +51,7 @@ class UtilsTestCase(RegistrationMixin, EventMixin, TestCase): self.submit_event(event) self.accept_event(event) - for i in range(0, registrations_per_event): + for _ in range(0, registrations_per_event): d = registration_data d['event'] = event self.create_registration(d) diff --git a/dav_registration/urls.py b/dav_registration/urls.py index 62988b8..c0585d8 100644 --- a/dav_registration/urls.py +++ b/dav_registration/urls.py @@ -2,6 +2,8 @@ from django.conf.urls import url from . import views +app_name = 'dav_registration' + urlpatterns = [ url(r'^$', views.RootView.as_view(), name='root'), url(r'^finished', views.RegistrationSuccessView.as_view(), name='registered'), diff --git a/dav_registration/views.py b/dav_registration/views.py index 33531dd..d8efc02 100644 --- a/dav_registration/views.py +++ b/dav_registration/views.py @@ -23,7 +23,7 @@ class RootView(generic.RedirectView): def get(self, request, *args, **kwargs): purge_registrations() - return super(RootView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) class EventListView(generic.ListView): @@ -33,14 +33,14 @@ class EventListView(generic.ListView): def get_queryset(self): today = datetime.date.today() - filter = Q(flags__status__code__in=('publishing', 'publishing_web', 'publishing_facebook', - 'published', 'published_web', 'published_facebook')) - filter &= Q(planned_publication_date__isnull=True) | Q(planned_publication_date__lte=today) - filter &= Q(first_day__gte=today) - # filter &= Q(registration_closed=False) - # filter &= Q(deadline__isnull=True) | Q(deadline__gte=today) + filter_exp = Q(flags__status__code__in=('publishing', 'publishing_web', 'publishing_facebook', + 'published', 'published_web', 'published_facebook')) + filter_exp &= Q(planned_publication_date__isnull=True) | Q(planned_publication_date__lte=today) + filter_exp &= Q(first_day__gte=today) + # filter_exp &= Q(registration_closed=False) + # filter_exp &= Q(deadline__isnull=True) | Q(deadline__gte=today) - qs = self.model.objects.filter(filter).order_by('first_day', 'number').distinct() + qs = self.model.objects.filter(filter_exp).order_by('first_day', 'number').distinct() return qs @@ -52,14 +52,14 @@ class EventDetailView(generic.DetailView): def get_queryset(self): today = datetime.date.today() - filter = Q(flags__status__code__in=('publishing', 'publishing_web', 'publishing_facebook', - 'published', 'published_web', 'published_facebook')) - filter &= Q(planned_publication_date__isnull=True) | Q(planned_publication_date__lte=today) - filter &= Q(first_day__gte=today) - # filter &= Q(registration_closed=False) - # filter &= Q(deadline__isnull=True) | Q(deadline__gte=today) + filter_exp = Q(flags__status__code__in=('publishing', 'publishing_web', 'publishing_facebook', + 'published', 'published_web', 'published_facebook')) + filter_exp &= Q(planned_publication_date__isnull=True) | Q(planned_publication_date__lte=today) + filter_exp &= Q(first_day__gte=today) + # filter_exp &= Q(registration_closed=False) + # filter_exp &= Q(deadline__isnull=True) | Q(deadline__gte=today) - qs = self.model.objects.filter(filter).distinct() + qs = self.model.objects.filter(filter_exp).distinct() return qs @@ -74,72 +74,72 @@ class RegistrationView(generic.CreateView): def get_queryset(self): today = datetime.date.today() - filter = Q(flags__status__code__in=('publishing', 'publishing_web', 'publishing_facebook', - 'published', 'published_web', 'published_facebook')) - filter &= Q(planned_publication_date__isnull=True) | Q(planned_publication_date__lte=today) - filter &= Q(first_day__gte=today) - filter &= Q(registration_closed=False) - filter &= Q(deadline__isnull=True) | Q(deadline__gte=today) + filter_exp = Q(flags__status__code__in=('publishing', 'publishing_web', 'publishing_facebook', + 'published', 'published_web', 'published_facebook')) + filter_exp &= Q(planned_publication_date__isnull=True) | Q(planned_publication_date__lte=today) + filter_exp &= Q(first_day__gte=today) + filter_exp &= Q(registration_closed=False) + filter_exp &= Q(deadline__isnull=True) | Q(deadline__gte=today) - qs = Event.objects.filter(filter).distinct() + qs = Event.objects.filter(filter_exp).distinct() return qs def get_initial(self): - initials = super(RegistrationView, self).get_initial() + initials = super().get_initial() return initials def get_form(self, form_class=None): - form = super(RegistrationView, self).get_form(form_class) + form = super().get_form(form_class) event = self.get_object() experience_label = form.fields['experience'].label experience_help_text = form.fields['experience'].help_text if event.sport == 'B': - experience_label = _(u'Eigene Bergerfahrung') + experience_label = _('Eigene Bergerfahrung') elif event.sport == 'K' and event.terrain == 'alpine': - experience_label = _(u'Eigene Fels- und Bergerfahrung') + experience_label = _('Eigene Fels- und Bergerfahrung') if event.level == 'beginner': - experience_help_text = u'%s
    %s
    %s' % ( - _(u'Warst du schon mal im Gebirge klettern?'), - _(u'In welchem Schwierigkeitsgrad hast du Spaß?'), - _(u'Bist du schon mal vorgestiegen?') + experience_help_text = '%s
    %s
    %s' % ( + _('Warst du schon mal im Gebirge klettern?'), + _('In welchem Schwierigkeitsgrad hast du Spaß?'), + _('Bist du schon mal vorgestiegen?') ) else: - experience_help_text = u'%s
    %s
    %s' % ( - _(u'In welchen Klettergebieten/Touren warst du bisher unterwegs?'), - _(u'In welchem Schwierigkeitsgrad hast du im Vorstieg Spaß?'), - _(u'Wie waren die Touren abgesichert, in denen du dich noch wohlgefühlt hast?') + experience_help_text = '%s
    %s
    %s' % ( + _('In welchen Klettergebieten/Touren warst du bisher unterwegs?'), + _('In welchem Schwierigkeitsgrad hast du im Vorstieg Spaß?'), + _('Wie waren die Touren abgesichert, in denen du dich noch wohlgefühlt hast?') ) elif event.sport == 'K': - experience_label = _(u'Eigene Klettererfahrung') + experience_label = _('Eigene Klettererfahrung') if event.level == 'beginner': - experience_help_text = u'%s
    %s
    %s' % ( - _(u'Warst du schon mal am Fels klettern?'), - _(u'In welchem Schwierigkeitsgrad hast du Spaß?'), - _(u'Bist du schon mal vorgestiegen?') + experience_help_text = '%s
    %s
    %s' % ( + _('Warst du schon mal am Fels klettern?'), + _('In welchem Schwierigkeitsgrad hast du Spaß?'), + _('Bist du schon mal vorgestiegen?') ) else: - experience_help_text = u'%s
    %s
    %s' % ( - _(u'In welchen Klettergebieten warst du bisher unterwegs?'), - _(u'In welchem Schwierigkeitsgrad hast du im Vorstieg Spaß?'), - _(u'Wie waren die Touren abgesichert, in denen du dich noch wohlgefühlt hast?') + experience_help_text = '%s
    %s
    %s' % ( + _('In welchen Klettergebieten warst du bisher unterwegs?'), + _('In welchem Schwierigkeitsgrad hast du im Vorstieg Spaß?'), + _('Wie waren die Touren abgesichert, in denen du dich noch wohlgefühlt hast?') ) elif event.sport == 'M': - experience_label = _(u'Eigene MTB-Erfahrung') - experience_help_text = u'%s' % ( - _(u'Was für Touren bist du schon gefahren?') + experience_label = _('Eigene MTB-Erfahrung') + experience_help_text = '%s' % ( + _('Was für Touren bist du schon gefahren?') ) if event.level != 'beginner': - experience_help_text += u'
    %s' % ( - _(u'Single-Trail-Schwierigkeit (S0-S5) bei der du dich wohl fühlst?') + experience_help_text += '
    %s' % ( + _('Single-Trail-Schwierigkeit (S0-S5) bei der du dich wohl fühlst?') ) elif event.sport == 'S': - experience_label = _(u'Eigene Skitouren- und Bergerfahrung') + experience_label = _('Eigene Skitouren- und Bergerfahrung') elif event.sport == 'W': - experience_help_text += u'
    %s' % ( - _(u'Kann frei gelassen werden.') + experience_help_text += '
    %s' % ( + _('Kann frei gelassen werden.') ) form.fields['experience'].label = experience_label @@ -150,7 +150,7 @@ class RegistrationView(generic.CreateView): return form def get_context_data(self, **kwargs): - context = super(RegistrationView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) event = self.get_object() context['event'] = event context['privacy_policy'] = app_config.settings.privacy_policy @@ -158,16 +158,16 @@ class RegistrationView(generic.CreateView): return context def form_valid(self, form): - r = super(RegistrationView, self).form_valid(form) + r = super().form_valid(form) self.request.session['registration_id'] = form.instance.pk - message = _(u'Deine Anmeldung wurde erfolgreich gespeichert.') + message = _('Deine Anmeldung wurde erfolgreich gespeichert.') messages.success(self.request, message) return r def post(self, request, *args, **kwargs): if 'registration_id' in request.session: del request.session['registration_id'] - return super(RegistrationView, self).post(request, *args, **kwargs) + return super().post(request, *args, **kwargs) class RegistrationSuccessView(generic.DetailView): diff --git a/dav_submission/__init__.py b/dav_submission/__init__.py index e2bee2f..c7a7261 100644 --- a/dav_submission/__init__.py +++ b/dav_submission/__init__.py @@ -1 +1 @@ -default_app_config = 'dav_submission.apps.AppConfig' +default_app_config = 'dav_submission.apps.AppConfig' # pylint: disable=invalid-name diff --git a/dav_submission/apps.py b/dav_submission/apps.py index f69b138..89310ce 100644 --- a/dav_submission/apps.py +++ b/dav_submission/apps.py @@ -19,5 +19,5 @@ DEFAULT_SETTINGS = ( class AppConfig(_AppConfig): name = 'dav_submission' - verbose_name = u'DAV Beitragsupload (150 Jahre DAV)' + verbose_name = 'DAV Beitragsupload (150 Jahre DAV)' default_settings = DEFAULT_SETTINGS diff --git a/dav_submission/emails.py b/dav_submission/emails.py index 230e927..4ed5199 100644 --- a/dav_submission/emails.py +++ b/dav_submission/emails.py @@ -6,8 +6,8 @@ from dav_base.emails import AbstractMail app_config = apps.get_containing_app_config(__package__) -class NewSubmissionMail(AbstractMail): - _subject = u'Neuer Beitrag: {title}' +class NewSubmissionMail(AbstractMail): # pylint: disable=too-few-public-methods + _subject = 'Neuer Beitrag: {title}' _template_name = 'dav_submission/emails/new_submission.txt' def __init__(self, metadata): @@ -15,18 +15,18 @@ class NewSubmissionMail(AbstractMail): def _get_subject(self, subject_fmt=None, **kwargs): kwargs['title'] = self._metadata['title'] - return super(NewSubmissionMail, self)._get_subject(subject_fmt=subject_fmt, **kwargs) + return super()._get_subject(subject_fmt=subject_fmt, **kwargs) def _get_reply_to(self): - s = u'"{fullname}" <{email}>'.format(fullname=self._metadata['name'], - email=self._metadata['email_address']) - return [s] + reply_to = '"{fullname}" <{email}>'.format(fullname=self._metadata['name'], + email=self._metadata['email_address']) + return [reply_to] def _get_recipients(self): r = app_config.settings.notify_address return [r] def _get_context_data(self, extra_context=None): - context = super(NewSubmissionMail, self)._get_context_data(extra_context=extra_context) + context = super()._get_context_data(extra_context=extra_context) context['metadata'] = self._metadata return context diff --git a/dav_submission/forms.py b/dav_submission/forms.py index db7f95c..bab1679 100644 --- a/dav_submission/forms.py +++ b/dav_submission/forms.py @@ -8,59 +8,59 @@ app_config = apps.get_containing_app_config(__package__) class UploadForm(forms.Form): name = forms.CharField(max_length=1024, - label=_(u'Dein Name'), - help_text=_(u'Wenn wir wissen, wie du heißt, wird uns das echt weiter helfen')) - email_address = forms.EmailField(label=_(u'Deine E-Mail-Adresse'), - help_text=_(u'Damit wir dich für Rückfragen kontaktieren können')) + label=_('Dein Name'), + help_text=_('Wenn wir wissen, wie du heißt, wird uns das echt weiter helfen')) + email_address = forms.EmailField(label=_('Deine E-Mail-Adresse'), + help_text=_('Damit wir dich für Rückfragen kontaktieren können')) group = forms.CharField(max_length=1024, required=False, - label=_(u'DAV Gruppe'), - help_text=_(u'Optional, falls du aktiv in einer der Sektionsgruppen bist')) + label=_('DAV Gruppe'), + help_text=_('Optional, falls du aktiv in einer der Sektionsgruppen bist')) title = forms.CharField(max_length=60, - label=_(u'Titel deines Beitrags / Stichwort'), - help_text=u'%s
    \n%s' % ( - _(u'Kommt zum Bild, falls es veröffentlicht wird'), - _(u'Maximal 60 Zeichen') + label=_('Titel deines Beitrags / Stichwort'), + help_text='%s
    \n%s' % ( + _('Kommt zum Bild, falls es veröffentlicht wird'), + _('Maximal 60 Zeichen') )) description = forms.CharField(max_length=300, - label=_(u'Beschreibung'), - help_text=u'%s
    \n%s' % ( - _(u'Wo warst du? Was hast du gemacht? Worum ging es bei der Aktion?'), - _(u'Maximal 300 Zeichen') + label=_('Beschreibung'), + help_text='%s
    \n%s' % ( + _('Wo warst du? Was hast du gemacht? Worum ging es bei der Aktion?'), + _('Maximal 300 Zeichen') ), widget=forms.Textarea(attrs={'rows': 2})) - files = forms.FileField(label=_(u'Dateien'), - help_text=_(u'Wenn du auf den Button klickst, kannst du mehrere Dateien auswählen' - u' (nötigenfalls Strg- oder Command-Taste benutzen)'), + files = forms.FileField(label=_('Dateien'), + help_text=_('Wenn du auf den Button klickst, kannst du mehrere Dateien auswählen' + ' (nötigenfalls Strg- oder Command-Taste benutzen)'), widget=forms.ClearableFileInput(attrs={'multiple': True})) accepted = forms.BooleanField(required=False, - label=_(u'Ja, ich stimme den Teilnahmebedingungen zu!')) + label=_('Ja, ich stimme den Teilnahmebedingungen zu!')) def __init__(self, *args, **kwargs): - super(UploadForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['title'].widget.attrs['placeholder'] = \ - u'z.B. Nacktbesteigung der Nose' \ - u' oder Juma jümart Jung-Mann-Weg'[:self.fields['title'].max_length] + 'z.B. Nacktbesteigung der Nose' \ + ' oder Juma jümart Jung-Mann-Weg'[:self.fields['title'].max_length] self.fields['group'].widget.attrs['placeholder'] = \ - ugettext(u'Kann frei gelassen werden')[:self.fields['title'].max_length] + ugettext('Kann frei gelassen werden')[:self.fields['title'].max_length] help_text = self.fields['files'].help_text if app_config.settings.max_files: - help_text += u'
    \n%s' % (ugettext(u'Wähle bis zu %d Dateien aus') - % app_config.settings.max_files) + help_text += '
    \n%s' % (ugettext('Wähle bis zu %d Dateien aus') + % app_config.settings.max_files) if app_config.settings.max_file_size_mib: - help_text += u'
    \n%s' % (ugettext(u'Einzelne Dateien dürfen maximal %d MiB groß sein') - % app_config.settings.max_file_size_mib) + help_text += '
    \n%s' % (ugettext('Einzelne Dateien dürfen maximal %d MiB groß sein') + % app_config.settings.max_file_size_mib) if app_config.settings.max_total_size_mib: - help_text += u'
    \n%s' % (ugettext(u'Alle Dateien zusammen dürfen maximal %d MiB groß sein') - % app_config.settings.max_total_size_mib) + help_text += '
    \n%s' % (ugettext('Alle Dateien zusammen dürfen maximal %d MiB groß sein') + % app_config.settings.max_total_size_mib) self.fields['files'].help_text = help_text def clean_files(self): @@ -77,35 +77,35 @@ class UploadForm(forms.Form): n_files = 0 for file in files: if file.name in not_allowed_file_names: - ve = forms.ValidationError( - ugettext(u'Dateiname nicht erlaubt: %s') % file.name, + e = forms.ValidationError( + ugettext('Dateiname nicht erlaubt: %s') % file.name, code='filename_not_allowed', ) - validation_errors.append(ve) + validation_errors.append(e) if max_file_size and file.size > max_file_size: - ve = forms.ValidationError( - ugettext(u'Die Datei ist insgesamt zu groß:' - u' %(name)s (> %(max_mib)s MiB)') % {'name': file.name, 'max_mib': max_file_size_mib}, + e = forms.ValidationError( + ugettext('Die Datei ist insgesamt zu groß:' + ' %(name)s (> %(max_mib)s MiB)') % {'name': file.name, 'max_mib': max_file_size_mib}, code='file_to_big', ) - validation_errors.append(ve) + validation_errors.append(e) size_total += file.size n_files += 1 max_total_size = max_total_size_mib * 1024 * 1024 if max_total_size and size_total > max_total_size: - ve = forms.ValidationError( - ugettext(u'Dein Beitrag ist zu groß (%s MiB)') % max_total_size_mib, + e = forms.ValidationError( + ugettext('Dein Beitrag ist zu groß (%s MiB)') % max_total_size_mib, code='files_to_big', ) - validation_errors.append(ve) + validation_errors.append(e) if max_files and n_files > max_files: - ve = forms.ValidationError( - ugettext(u'Dein Beitrag enthält mehr als %d Dateien') % max_files, + e = forms.ValidationError( + ugettext('Dein Beitrag enthält mehr als %d Dateien') % max_files, code='files_to_big', ) - validation_errors.append(ve) + validation_errors.append(e) if validation_errors: raise forms.ValidationError(validation_errors) @@ -116,9 +116,9 @@ class UploadForm(forms.Form): val = self.cleaned_data.get('accepted') if not val: raise forms.ValidationError( - ugettext(u'Um deinen Beitrag hochladen zu können,' - u' musst du den Teilnahmebedingungen zustimmen.' - u' Dazu musst du das Kästchen ankreuzen!'), + ugettext('Um deinen Beitrag hochladen zu können,' + ' musst du den Teilnahmebedingungen zustimmen.' + ' Dazu musst du das Kästchen ankreuzen!'), code='not_accepted', ) return val diff --git a/dav_submission/urls.py b/dav_submission/urls.py index c6ebfd9..b556c42 100644 --- a/dav_submission/urls.py +++ b/dav_submission/urls.py @@ -2,6 +2,8 @@ from django.conf.urls import url from . import views +app_name = 'dav_submission' + urlpatterns = [ url(r'^$', views.UploadView.as_view(), name='root'), url(r'^danke', views.SuccessView.as_view(), name='success'), diff --git a/dav_submission/views.py b/dav_submission/views.py index 58eb657..0d7773e 100644 --- a/dav_submission/views.py +++ b/dav_submission/views.py @@ -3,10 +3,10 @@ import codecs import datetime import logging import os -import pytz import re import urllib import zipfile +import pytz from django.apps import apps from django.contrib import messages from django.contrib.auth.decorators import login_required @@ -90,7 +90,7 @@ class ListView(generic.ListView): permission_group = app_config.settings.download_group if not request.user.groups.filter(name=permission_group).exists(): raise PermissionDenied() - return super(ListView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) class DownloadView(generic.DetailView): @@ -139,7 +139,7 @@ class DownloadView(generic.DetailView): permission_group = app_config.settings.download_group if not request.user.groups.filter(name=permission_group).exists(): raise PermissionDenied() - return super(DownloadView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) class UploadView(generic.edit.FormView): @@ -155,30 +155,30 @@ class UploadView(generic.edit.FormView): form_class = UploadForm success_url = reverse_lazy('dav_submission:success') - def _sanitize_filename(self, input): + def _sanitize_filename(self, filename): max_length = None - discard_chars = u'' + discard_chars = '' replace_chars = { - u'ä': u'ae', - u'ö': u'oe', - u'ü': u'ue', - u'ß': u'ss', - u'Ä': u'Ae', - u'Ö': u'Oe', - u'Ü': u'Ue', + 'ä': 'ae', + 'ö': 'oe', + 'ü': 'ue', + 'ß': 'ss', + 'Ä': 'Ae', + 'Ö': 'Oe', + 'Ü': 'Ue', } - allowed_chars = (u'abcdefghijklmnopqrstuvwxyz' - u'ABCDEFGHIJKLMNOPQRSTUVWXYZ' - u'0123456789' - u'._-') - non_allowed_substitute = u'_' - space_substitute = u'_' + allowed_chars = ('abcdefghijklmnopqrstuvwxyz' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + '0123456789' + '._-') + non_allowed_substitute = '_' + space_substitute = '_' if space_substitute is None: space_substitute = non_allowed_substitute r = '' - for c in input: + for c in filename: if c in discard_chars: continue elif c in replace_chars: @@ -195,14 +195,14 @@ class UploadView(generic.edit.FormView): return r[:max_length] def get_context_data(self, **kwargs): - c = super(UploadView, self).get_context_data(**kwargs) + c = super().get_context_data(**kwargs) c['show_upload_form'] = app_config.settings.enable_upload return c def form_valid(self, form): base_path = app_config.settings.upload_path - subdir_format_str = u'{datetime}--{title}' + subdir_format_str = '{datetime}--{title}' now = timezone.now() subdir_format_kwargs = {'datetime': now.strftime('%Y-%m-%d--%H%M%S'), 'date': now.strftime('%Y-%m-%d'), @@ -214,7 +214,7 @@ class UploadView(generic.edit.FormView): subdir_path = os.path.join(base_path, subdir_name) if os.path.isdir(subdir_path): - message = _(u'Es gibt bereits einen Beitrag mit dem Titel "%(title)s".') % subdir_format_kwargs + message = _('Es gibt bereits einen Beitrag mit dem Titel "%(title)s".') % subdir_format_kwargs messages.error(self.request, message) form.add_error('title', message) return self.render_to_response(self.get_context_data(form=form)) @@ -222,7 +222,7 @@ class UploadView(generic.edit.FormView): os.makedirs(subdir_path) try: - metadata_format_str = u"""Absender: {name} <{email_address}> + metadata_format_str = """Absender: {name} <{email_address}> Gruppe: {group} Datum: {date} {time} Titel: {title} @@ -246,10 +246,10 @@ Beschreibung: with codecs.open(metadata_file_path, 'w', encoding='utf-8') as metadata_file: metadata_file.write(metadata) except Exception as e: - message = _(u'Jetzt ist irgendwas schief gelaufen.') + message = _('Jetzt ist irgendwas schief gelaufen.') messages.error(self.request, message) logger.error('dav_submission.views.UploadView.form_valid(): Catched Exception #2: %s', str(e)) - return super(UploadView, self).form_valid(form) + return super().form_valid(form) try: for input_file in form.files.getlist('files'): @@ -257,7 +257,7 @@ Beschreibung: file_path = os.path.join(subdir_path, file_name) if os.path.exists(file_path): - message = _(u'Die Datei %(name)s haben wir bereits.') % {'name': input_file.name} + message = _('Die Datei %(name)s haben wir bereits.') % {'name': input_file.name} messages.error(self.request, message) continue @@ -267,28 +267,28 @@ Beschreibung: size = os.path.getsize(file_path) if size > (1024 * 1024): - size_str = u'%s MiB' % ('%.3f' % (size / 1024.0 / 1024.0)).rstrip('0').rstrip('.') + size_str = '%s MiB' % ('%.3f' % (size / 1024.0 / 1024.0)).rstrip('0').rstrip('.') elif size > 1024: - size_str = u'%s KiB' % ('%.3f' % (size / 1024.0)).rstrip('0').rstrip('.') + size_str = '%s KiB' % ('%.3f' % (size / 1024.0)).rstrip('0').rstrip('.') else: - size_str = u'%d Byte' % size - message = _(u'Datei erfolgreich hochgeladen: %(name)s (%(size)s)') % {'name': input_file.name, - 'size': size_str} + size_str = '%d Byte' % size + message = _('Datei erfolgreich hochgeladen: %(name)s (%(size)s)') % {'name': input_file.name, + 'size': size_str} messages.success(self.request, message) except Exception as e: - message = _(u'Jetzt ist irgendwas schief gelaufen.') + message = _('Jetzt ist irgendwas schief gelaufen.') messages.error(self.request, message) logger.error('dav_submission.views.UploadView.form_valid(): Catched Exception #3: %s', str(e)) - return super(UploadView, self).form_valid(form) + return super().form_valid(form) mail = NewSubmissionMail(metadata_format_kwargs) mail.send() - return super(UploadView, self).form_valid(form) + return super().form_valid(form) def post(self, request, *args, **kwargs): if not app_config.settings.enable_upload: - raise PermissionDenied(_(u'Der Upload ist noch nicht freigeschaltet.')) - return super(UploadView, self).post(request, *args, **kwargs) + raise PermissionDenied(_('Der Upload ist noch nicht freigeschaltet.')) + return super().post(request, *args, **kwargs) class SuccessView(generic.TemplateView): diff --git a/setup.py b/setup.py index 37da236..1a64cef 100644 --- a/setup.py +++ b/setup.py @@ -22,12 +22,7 @@ class SetupPythonEnvironment(MyCommand): def run(self): python_bin = sys.executable if sys.executable else 'python' python_ver = sys.version_info.major - if python_ver == 2: - path = os.path.join('env', 'python2') - symlink_path = os.path.join('env', 'python') - venv_module = 'virtualenv' - prompt = '(py2-dav) ' - elif python_ver == 3: + if python_ver == 3: path = os.path.join('env', 'python3') symlink_path = os.path.join('env', 'python') venv_module = 'venv' @@ -97,7 +92,7 @@ class QuickSetup(MyCommand): setup( name='django-dav-events', - version='2.0', + version='2.1', description='A django based web application project to organize DAV Events.', url='https://touren.alpenverein-karlsruhe.de', author='Jens Kleineheismann', @@ -116,11 +111,12 @@ setup( }, install_requires=[ 'babel', - 'django >= 1.11, < 2.0', - 'django-extensions', - 'django-bootstrap3 < 12', - 'django-countries < 6', - 'django-datetime-widget', + #'django >= 1.11, < 2.0', + 'django >= 1.11, < 3.3', + # 'django-extensions', + 'django-bootstrap3', + 'django-countries', + 'django-datetime-widget2', 'pytz', 'selenium', 'coverage', diff --git a/tests/__main__.py b/tests/__main__.py new file mode 100644 index 0000000..a145d81 --- /dev/null +++ b/tests/__main__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +from test_suite import main + +if __name__ == '__main__': + main() diff --git a/tests/settings.py b/tests/settings.py new file mode 100644 index 0000000..89cf3cb --- /dev/null +++ b/tests/settings.py @@ -0,0 +1,4 @@ +from pathlib import Path +BASE_DIR = Path(__file__).resolve().parent + +SECRET_KEY = 'fake-key' diff --git a/tests/test_suite.py b/tests/test_suite.py index 78919ed..4ca6bcb 100644 --- a/tests/test_suite.py +++ b/tests/test_suite.py @@ -1,18 +1,19 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import unicode_literals import datetime -import django import os import shutil import sys +import django from django.test.utils import get_runner -#from dav_base.console_scripts.admin import DJANGO_MAIN_MODULE -DJANGO_MAIN_MODULE = 'main' from dav_base.tests.utils import mkdtemp +# from dav_base.console_scripts.admin import DJANGO_MAIN_MODULE +DJANGO_MAIN_MODULE = 'main' -class DjangoEnvironment(object): + +class DjangoEnvironment: @staticmethod def _install_djangoproject(path, modules=None): cmd = 'django-dav-admin setup "{}"'.format(path) @@ -34,6 +35,8 @@ class DjangoEnvironment(object): else: self._enable_modules = [] + self.settings = None + def __enter__(self): if self.path is None: prefix = 'testrun-{datetime}-'.format( @@ -50,7 +53,7 @@ class DjangoEnvironment(object): os.environ['DJANGO_SETTINGS_MODULE'] = '{}.settings'.format(DJANGO_MAIN_MODULE) django.setup() - from django.conf import settings + from django.conf import settings # pylint: disable=import-outside-toplevel self.settings = settings return self @@ -63,7 +66,7 @@ class DjangoEnvironment(object): shutil.rmtree(self.path) -class TestSuite(object): +class TestSuite: @staticmethod def run(): modules = ['dav_auth', 'dav_events', 'dav_registration', 'dav_event_office'] @@ -80,3 +83,12 @@ class TestSuite(object): def __call__(self): sys.exit(self.run()) + + +def main(): + test_suite = TestSuite() + test_suite() + + +if __name__ == '__main__': + main() diff --git a/tox.ini b/tox.ini index f23d041..4c6403c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] -envlist = py3,py2 +envlist = py3 [testenv] commands = python --version - python -m coverage run setup.py test + python -m coverage run tests/test_suite.py coverage report --skip-covered