Merge pull request 'update production branch' (#57) from master into production
All checks were successful
buildbot/tox Build done.

Reviewed-on: #57
This commit was merged in pull request #57.
This commit is contained in:
2023-02-15 19:48:19 +01:00
92 changed files with 785 additions and 665 deletions

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@
/env/ /env/
/tmp/ /tmp/
/.eggs/
*.pyc *.pyc
*.mo *.mo
/.coverage /.coverage

View File

@@ -10,7 +10,8 @@ disable=missing-docstring,
missing-module-docstring, missing-module-docstring,
missing-class-docstring, missing-class-docstring,
missing-function-docstring, missing-function-docstring,
useless-object-inheritance, consider-using-f-string,
duplicate-code,
[BASIC] [BASIC]
@@ -18,10 +19,20 @@ good-names=_,
c, c,
d, d,
e, e,
f,
i, i,
j, j,
k, k,
n,
r,
s,
t,
pk,
[FORMAT] [FORMAT]
max-line-length=120 max-line-length=120
[SIMILARITIES]
ignore-comments=no

View File

@@ -1,6 +1,6 @@
REQUIREMENTS REQUIREMENTS
============ ============
- Python 2.7 - Python 3
- Python package virtualenv (in most cases this is distributed or installed together with python) - Python package virtualenv (in most cases this is distributed or installed together with python)
- Django (will be installed automatically) - Django (will be installed automatically)
- Several additional django related python packages (will be installed automatically) - Several additional django related python packages (will be installed automatically)

View File

@@ -1,18 +1,17 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import argparse import argparse
import coverage
import datetime import datetime
import os import os
import shutil import shutil
import sys import sys
import time import time
import coverage
from selenium import webdriver from selenium import webdriver
from selenium.common.exceptions import WebDriverException from selenium.common.exceptions import WebDriverException
class Command(object): class Command: # pylint: disable=too-few-public-methods
default_browser = 'firefox' default_browser = 'firefox'
@staticmethod @staticmethod

View File

@@ -1 +1 @@
default_app_config = 'dav_auth.apps.AppConfig' default_app_config = 'dav_auth.apps.AppConfig' # pylint: disable=invalid-name

View File

@@ -8,5 +8,5 @@ DEFAULT_SETTINGS = (
class AppConfig(_AppConfig): class AppConfig(_AppConfig):
name = 'dav_auth' name = 'dav_auth'
verbose_name = u'DAV Benutzerverwaltung' verbose_name = 'DAV Benutzerverwaltung'
default_settings = DEFAULT_SETTINGS default_settings = DEFAULT_SETTINGS

View File

@@ -2,8 +2,8 @@
from dav_base.emails import AbstractMail from dav_base.emails import AbstractMail
class PasswordSetEmail(AbstractMail): class PasswordSetEmail(AbstractMail): # pylint: disable=too-few-public-methods
_subject = u'Zugangsdaten' _subject = 'Zugangsdaten'
_template_name = 'dav_auth/emails/password_set.txt' _template_name = 'dav_auth/emails/password_set.txt'
def __init__(self, user, password): def __init__(self, user, password):
@@ -11,12 +11,12 @@ class PasswordSetEmail(AbstractMail):
self._password = password self._password = password
def _get_recipients(self): 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) email=self._user.email)
return [r] return [r]
def _get_context_data(self, extra_context=None): 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({ context.update({
'fullname': self._user.get_full_name(), 'fullname': self._user.get_full_name(),
'username': self._user.username, 'username': self._user.username,

View File

@@ -9,14 +9,13 @@ logger = logging.getLogger(__name__)
class LoginForm(auth_forms.AuthenticationForm): class LoginForm(auth_forms.AuthenticationForm):
username = auth_forms.UsernameField( username = auth_forms.UsernameField(
max_length=254, label=_('E-Mail-Adresse'),
label=_(u'E-Mail-Adresse'),
widget=forms.TextInput(attrs={'autofocus': True}), widget=forms.TextInput(attrs={'autofocus': True}),
) )
error_messages = { error_messages = {
'invalid_login': _(u'Benutzername oder Passwort falsch.'), 'invalid_login': _('Benutzername oder Passwort falsch.'),
'inactive': _("This account is inactive."), 'inactive': _('This account is inactive.'),
} }
def clean_username(self): def clean_username(self):
@@ -25,18 +24,18 @@ class LoginForm(auth_forms.AuthenticationForm):
class SetPasswordForm(forms.Form): class SetPasswordForm(forms.Form):
new_password = forms.CharField(label=_(u'Neues Passwort'), new_password = forms.CharField(label=_('Neues Passwort'),
widget=forms.PasswordInput) 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) widget=forms.PasswordInput)
send_password_mail = forms.BooleanField(required=False, send_password_mail = forms.BooleanField(required=False,
initial=False, initial=False,
label=_(u'Neues Passwort per E-Mail zusenden'), label=_('Neues Passwort per E-Mail zusenden'),
) )
def __init__(self, user, *args, **kwargs): def __init__(self, user, *args, **kwargs):
self.user = user self.user = user
super(SetPasswordForm, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def clean_new_password(self): def clean_new_password(self):
password = self.cleaned_data.get('new_password') password = self.cleaned_data.get('new_password')
@@ -49,7 +48,7 @@ class SetPasswordForm(forms.Form):
if password1 and password2: if password1 and password2:
if password1 != password2: if password1 != password2:
raise forms.ValidationError( raise forms.ValidationError(
ugettext(u'Passwörter stimmen nicht überein'), ugettext('Passwörter stimmen nicht überein'),
code='password_mismatch', code='password_mismatch',
) )
return password2 return password2
@@ -64,8 +63,7 @@ class SetPasswordForm(forms.Form):
class CreateAndSendPasswordForm(forms.Form): class CreateAndSendPasswordForm(forms.Form):
username = auth_forms.UsernameField( username = auth_forms.UsernameField(
max_length=254, label=_('E-Mail-Adresse'),
label=_(u'E-Mail-Adresse'),
widget=forms.TextInput(attrs={'autofocus': True}), widget=forms.TextInput(attrs={'autofocus': True}),
) )

View File

@@ -3,7 +3,7 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
class SeleniumAuthMixin(object): class SeleniumAuthMixin:
def login(self, driver, username, password): def login(self, driver, username, password):
driver.get(self.complete_url(reverse('dav_auth:login'))) driver.get(self.complete_url(reverse('dav_auth:login')))
username_field = self.wait_on_presence(driver, (By.ID, 'id_username')) username_field = self.wait_on_presence(driver, (By.ID, 'id_username'))

View File

@@ -9,10 +9,10 @@ from ..emails import PasswordSetEmail
TEST_USERNAME = 'user' TEST_USERNAME = 'user'
TEST_PASSWORD = u'me||ön 21ABll' TEST_PASSWORD = 'me||ön 21ABll'
TEST_EMAIL = 'root@localhost' TEST_EMAIL = 'root@localhost'
PASSWORD_EMAIL_TEMPLATE = u"""Hallo {fullname}, PASSWORD_EMAIL_TEMPLATE = """Hallo {fullname},
Benutzername: {username} Benutzername: {username}
Passwort: {password} Passwort: {password}
@@ -23,7 +23,7 @@ URL: {base_url}/
class EmailTestCase(EmailTestMixin, TestCase): class EmailTestCase(EmailTestMixin, TestCase):
def setUp(self): def setUp(self):
super(EmailTestCase, self).setUp() super().setUp()
model = get_user_model() model = get_user_model()
self.user = model.objects.create_user(username=TEST_USERNAME, password=TEST_PASSWORD, email=TEST_EMAIL) 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.assertSender(mail)
self.assertRecipients(mail, [self.user]) self.assertRecipients(mail, [self.user])
self.assertSubject(mail, u'Zugangsdaten') self.assertSubject(mail, 'Zugangsdaten')
expected_body = PASSWORD_EMAIL_TEMPLATE.format( expected_body = PASSWORD_EMAIL_TEMPLATE.format(
fullname=self.user.get_full_name(), fullname=self.user.get_full_name(),

View File

@@ -9,9 +9,8 @@ from dav_base.tests.generic import FormDataSet, FormsTestCase
from ..forms import LoginForm, SetPasswordForm, CreateAndSendPasswordForm from ..forms import LoginForm, SetPasswordForm, CreateAndSendPasswordForm
TEST_USERNAME = 'root@localhost' TEST_USERNAME = 'root@localhost'
TEST_PASSWORD = u'me||ön 21ABll' TEST_PASSWORD = 'me||ön 21ABll'
TEST_EMAIL = TEST_USERNAME TEST_EMAIL = TEST_USERNAME
USERNAME_MAX_LENGTH = 254
class LoginFormTestCase(FormsTestCase): class LoginFormTestCase(FormsTestCase):
@@ -24,15 +23,10 @@ class LoginFormTestCase(FormsTestCase):
model = get_user_model() model = get_user_model()
self.user = model.objects.create_user(username=TEST_USERNAME, password=TEST_PASSWORD, email=TEST_EMAIL) 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): def test_labels(self):
form = self.form_class() 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'))
self.assertEqual(form.fields['password'].label, ugettext(u'Password')) self.assertEqual(form.fields['password'].label, ugettext('Password'))
def test_required(self): def test_required(self):
form = self.form_class() form = self.form_class()
@@ -44,35 +38,28 @@ class LoginFormTestCase(FormsTestCase):
FormDataSet({'username': '', 'password': self.test_password}, FormDataSet({'username': '', 'password': self.test_password},
expected_errors=[('username', 'required')]), 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): def test_password_empty(self):
data_sets = [ data_sets = [
FormDataSet({'username': self.test_username, 'password': ''}, FormDataSet({'username': self.test_username, 'password': ''},
expected_errors=[('password', 'required')]), expected_errors=[('password', 'required')]),
] ]
super(LoginFormTestCase, self).test_invalid_data(data_sets=data_sets) super().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)
def test_invalid_username(self): def test_invalid_username(self):
data_sets = [ data_sets = [
FormDataSet({'username': self.test_username[::-1], 'password': self.test_password}, FormDataSet({'username': self.test_username[::-1], 'password': self.test_password},
expected_errors=[('__all__', 'invalid_login')]), 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): def test_invalid_password(self):
data_sets = [ data_sets = [
FormDataSet({'username': self.test_username, 'password': self.test_password[::-1]}, FormDataSet({'username': self.test_username, 'password': self.test_password[::-1]},
expected_errors=[('__all__', 'invalid_login')]), 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): def test_inactive_user(self):
self.user.is_active = False self.user.is_active = False
@@ -81,13 +68,13 @@ class LoginFormTestCase(FormsTestCase):
FormDataSet({'username': self.test_username, 'password': self.test_password}, FormDataSet({'username': self.test_username, 'password': self.test_password},
expected_errors=[('__all__', 'invalid_login')]), 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): def test_valid_credentials(self):
data_sets = [ data_sets = [
FormDataSet({'username': self.test_username, 'password': self.test_password}), 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): class SetPasswordFormTestCase(FormsTestCase):
@@ -111,56 +98,56 @@ class SetPasswordFormTestCase(FormsTestCase):
FormDataSet({'new_password': 'mellonAB12+-', 'new_password_repeat': 'mellonAB13+-'}, FormDataSet({'new_password': 'mellonAB12+-', 'new_password_repeat': 'mellonAB13+-'},
[('new_password_repeat', 'password_mismatch')]), [('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): def test_empty(self):
data_sets = [ data_sets = [
FormDataSet({'new_password': '', 'new_password_repeat': ''}, FormDataSet({'new_password': '', 'new_password_repeat': ''},
[('new_password', 'required')]), [('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): def test_too_short(self):
data_sets = [ data_sets = [
FormDataSet({'new_password': 'mellon', 'new_password_repeat': 'mellon'}, FormDataSet({'new_password': 'mellon', 'new_password_repeat': 'mellon'},
[('new_password', 'password_too_short')]), [('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): def test_entirely_numeric(self):
data_sets = [ data_sets = [
FormDataSet({'new_password': '1357924680', 'new_password_repeat': '1357924680'}, FormDataSet({'new_password': '1357924680', 'new_password_repeat': '1357924680'},
[('new_password', 'password_entirely_numeric')]), [('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): def test_too_similar(self):
data_sets = [ data_sets = [
FormDataSet({'new_password': self.test_username, 'new_password_repeat': self.test_username}, FormDataSet({'new_password': self.test_username, 'new_password_repeat': self.test_username},
[('new_password', 'password_too_similar')]), [('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): def test_too_common(self):
data_sets = [ data_sets = [
FormDataSet({'new_password': 'password', 'new_password_repeat': 'password'}, FormDataSet({'new_password': 'password', 'new_password_repeat': 'password'},
[('new_password', 'password_too_common')]), [('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): def test_valid(self):
data_sets = [ data_sets = [
FormDataSet({'new_password': 'mellonAB12+-', 'new_password_repeat': 'mellonAB12+-'}), FormDataSet({'new_password': 'mellonAB12+-', 'new_password_repeat': 'mellonAB12+-'}),
FormDataSet({'new_password': 'mellonAB12+-', 'new_password_repeat': 'mellonAB12+-', FormDataSet({'new_password': 'mellonAB12+-', 'new_password_repeat': 'mellonAB12+-',
'send_password_mail': True}), '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}), 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): def test_save(self):
new_passwords = [ new_passwords = [
u'"ä§ Mellon12' '"ä§ Mellon12'
'mellon12' * 128, 'mellon12' * 128,
] ]
@@ -175,7 +162,7 @@ class SetPasswordFormTestCase(FormsTestCase):
@skip('Function is implemented in SetPasswordView instead of SetPasswordForm') @skip('Function is implemented in SetPasswordView instead of SetPasswordForm')
def test_save_with_mail(self): # pragma: no cover def test_save_with_mail(self): # pragma: no cover
new_passwords = [ new_passwords = [
u'"ä§ Mellon12' '"ä§ Mellon12'
'mellon12' * 128, 'mellon12' * 128,
] ]
@@ -202,16 +189,11 @@ class CreateAndSendPasswordFormTestCase(FormsTestCase):
) )
invalid_data_sets = ( invalid_data_sets = (
FormDataSet({'username': ''}, expected_errors=[('username', 'required')]), 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): def test_labels(self):
form = self.form_class() 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): def test_required(self):
form = self.form_class() form = self.form_class()

View File

@@ -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)

View File

@@ -9,7 +9,7 @@ from selenium.webdriver.common.keys import Keys
from dav_base.tests.generic import ScreenshotTestCase from dav_base.tests.generic import ScreenshotTestCase
TEST_USERNAME = 'root@localhost' TEST_USERNAME = 'root@localhost'
TEST_PASSWORD = u'me||ön 21ABll' TEST_PASSWORD = 'me||ön 21ABll'
TEST_EMAIL = TEST_USERNAME TEST_EMAIL = TEST_USERNAME
@@ -18,7 +18,7 @@ class TestCase(ScreenshotTestCase):
screenshot_prefix = 'dav_auth-' screenshot_prefix = 'dav_auth-'
def setUp(self): def setUp(self):
super(TestCase, self).setUp() super().setUp()
# Need a test user # Need a test user
self.test_username = TEST_USERNAME self.test_username = TEST_USERNAME
self.test_password = TEST_PASSWORD self.test_password = TEST_PASSWORD
@@ -105,7 +105,7 @@ class TestCase(ScreenshotTestCase):
# Click on 'set password' -> save set password page # Click on 'set password' -> save set password page
user_menu = c.find_element_by_css_selector('#login-widget ul') 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() link.click()
password_field = self.wait_on_presence(c, (By.ID, 'id_new_password')) password_field = self.wait_on_presence(c, (By.ID, 'id_new_password'))
self.save_screenshot('empty_set_password_form', sequence=sequence_name) 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 = self.wait_on_presence(c, (By.ID, 'user_dropdown_button'))
dropdown_button.click() dropdown_button.click()
user_menu = c.find_element_by_css_selector('#login-widget ul') 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() link.click()
self.wait_until_stale(c, user_menu) self.wait_until_stale(c, user_menu)
self.save_screenshot('logout_succeed', sequence=sequence_name) self.save_screenshot('logout_succeed', sequence=sequence_name)
@@ -200,7 +200,7 @@ class TestCase(ScreenshotTestCase):
self.wait_on_presence(c, (By.ID, 'id_username')) self.wait_on_presence(c, (By.ID, 'id_username'))
# Locate password recreate link, click it -> save password recreate form # 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() link.click()
username_field = self.wait_on_presence(c, (By.ID, 'id_username')) username_field = self.wait_on_presence(c, (By.ID, 'id_username'))
self.save_screenshot('empty_recreate_password_form', sequence=sequence_name) 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) self.save_screenshot('recreate_password_invalid_user', sequence=sequence_name)
# Locate password recreate link, click it # 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() link.click()
username_field = self.wait_on_presence(c, (By.ID, 'id_username')) username_field = self.wait_on_presence(c, (By.ID, 'id_username'))

View File

@@ -29,7 +29,7 @@ class TemplatesTestCase(SimpleTestCase):
@tag('browser') @tag('browser')
class TestCase(SeleniumAuthMixin, SeleniumTestCase): class TestCase(SeleniumAuthMixin, SeleniumTestCase):
def setUp(self): def setUp(self):
super(TestCase, self).setUp() super().setUp()
# Need a test user # Need a test user
self.test_username = TEST_USERNAME self.test_username = TEST_USERNAME
self.test_password = TEST_PASSWORD self.test_password = TEST_PASSWORD
@@ -40,7 +40,7 @@ class TestCase(SeleniumAuthMixin, SeleniumTestCase):
c = self.selenium c = self.selenium
c.get(self.complete_url('/')) c.get(self.complete_url('/'))
try: 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 except NoSuchElementException as e: # pragma: no cover
self.fail(str(e)) self.fail(str(e))
@@ -65,4 +65,3 @@ class TestCase(SeleniumAuthMixin, SeleniumTestCase):
self.assertEqual(field.get_attribute('required'), 'true') self.assertEqual(field.get_attribute('required'), 'true')
field = c.find_element_by_id('id_send_password_mail') field = c.find_element_by_id('id_send_password_mail')
self.assertEqual(field.get_attribute('required'), None) self.assertEqual(field.get_attribute('required'), None)

View File

@@ -8,19 +8,19 @@ from ..validators import PasswordScoreValidator, CustomWordlistPasswordValidator
class PasswordScoreValidatorTestCase(SimpleTestCase): class PasswordScoreValidatorTestCase(SimpleTestCase):
def test_too_little_score(self): def test_too_little_score(self):
passwords = [ passwords = [
u'', # score = 0 '', # score = 0
u'abcdefghijklmnopq', # score = 17 'abcdefghijklmnopq', # score = 17
u'Abcdefghijklmnop', # score = 16 + 1 'Abcdefghijklmnop', # score = 16 + 1
u'ABcdefghijklmno', # score = 15 + 2 'ABcdefghijklmno', # score = 15 + 2
u'AB1defghijklmn', # score = 14 + 2 + 1 'AB1defghijklmn', # score = 14 + 2 + 1
u'AB12efghijklm', # score = 13 + 2 + 2 'AB12efghijklm', # score = 13 + 2 + 2
u'AB12+fghijkl', # score = 12 + 2 + 2 + 1 'AB12+fghijkl', # score = 12 + 2 + 2 + 1
u'AB12+-ghijk', # score = 11 + 2 + 2 + 2 'AB12+-ghijk', # score = 11 + 2 + 2 + 2
u'AB12+-*hij', # score = 10 + 2 + 2 + 3 'AB12+-*hij', # score = 10 + 2 + 2 + 3
u'AB12+-*hi/', # score = 10 + 2 + 2 + 3 'AB12+-*hi/', # score = 10 + 2 + 2 + 3
u'AB12+-*hi3', # score = 10 + 2 + 2 + 3 'AB12+-*hi3', # score = 10 + 2 + 2 + 3
u'AB12+-*hiC', # score = 10 + 2 + 2 + 3 'AB12+-*hiC', # score = 10 + 2 + 2 + 3
u'abcbbbgbbjklmnopqr', # score = 17 'abcbbbgbbjklmnopqr', # score = 17
] ]
validator = PasswordScoreValidator(min_classes=0) validator = PasswordScoreValidator(min_classes=0)
@@ -31,19 +31,19 @@ class PasswordScoreValidatorTestCase(SimpleTestCase):
except ValidationError as e: except ValidationError as e:
self.assertEqual('too_little_score', e.code) self.assertEqual('too_little_score', e.code)
else: 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): def test_too_few_classes(self):
passwords = [ passwords = [
u'', # classes = 0 '', # classes = 0
u'abcdefgh', # classes = 1 'abcdefgh', # classes = 1
u'abcdef+-', # classes = 2 'abcdef+-', # classes = 2
u'abcd12gh', # classes = 2 'abcd12gh', # classes = 2
u'abcd12-+', # classes = 3 'abcd12-+', # classes = 3
u'abCDefgh', # classes = 2 'abCDefgh', # classes = 2
u'abCDef+-', # classes = 3 'abCDef+-', # classes = 3
u'abCD12gh', # classes = 3 'abCD12gh', # classes = 3
u'ABCD12-+', # classes = 3 'ABCD12-+', # classes = 3
] ]
validator = PasswordScoreValidator(min_score=0, min_classes=4) validator = PasswordScoreValidator(min_score=0, min_classes=4)
@@ -54,20 +54,20 @@ class PasswordScoreValidatorTestCase(SimpleTestCase):
except ValidationError as e: except ValidationError as e:
self.assertEqual('too_few_classes', e.code) self.assertEqual('too_few_classes', e.code)
else: else:
self.fail(u'%s: no validation error was raised' % password) self.fail('%s: no validation error was raised' % password)
def test_valid(self): def test_valid(self):
passwords = [ passwords = [
u'Abcdefghijklmnopq', 'Abcdefghijklmnopq',
u'ABcdefghijklmnop', 'ABcdefghijklmnop',
u'AB1defghijklmno', 'AB1defghijklmno',
u'AB12efghijklmn', 'AB12efghijklmn',
u'AB12+fghijklm', 'AB12+fghijklm',
u'AB12+-ghijkl', 'AB12+-ghijkl',
u'AB12+-*hijk', 'AB12+-*hijk',
u'ab1defghijklmnopq', 'ab1defghijklmnopq',
u'abcd+fghijklmnopq', 'abcd+fghijklmnopq',
u'AB1CDEFGHIJKLMNOPQ', 'AB1CDEFGHIJKLMNOPQ',
] ]
validator = PasswordScoreValidator() validator = PasswordScoreValidator()
@@ -82,23 +82,23 @@ class PasswordScoreValidatorTestCase(SimpleTestCase):
class CustomWordlistPasswordValidatorTestCase(SimpleTestCase): class CustomWordlistPasswordValidatorTestCase(SimpleTestCase):
def test_invalid(self): def test_invalid(self):
invalid_passwords = [ invalid_passwords = [
(u'passwort', [ ('passwort', [
u'Das Passwort darf nicht die Zeichenfolge \'passwort\' enthalten.', 'Das Passwort darf nicht die Zeichenfolge \'passwort\' enthalten.',
]), ]),
(u'abcdDaVefgh', [ ('abcdDaVefgh', [
u'Das Passwort darf nicht die Zeichenfolge \'dav\' enthalten.', 'Das Passwort darf nicht die Zeichenfolge \'dav\' enthalten.',
]), ]),
(u'abcdsektIonefgh', [ ('abcdsektIonefgh', [
u'Das Passwort darf nicht die Zeichenfolge \'sektion\' enthalten.', 'Das Passwort darf nicht die Zeichenfolge \'sektion\' enthalten.',
]), ]),
(u'alpen12verein34KArlsruhE berge', [ ('alpen12verein34KArlsruhE berge', [
u'Das Passwort darf nicht die Zeichenfolge \'karlsruhe\' enthalten.', 'Das Passwort darf nicht die Zeichenfolge \'karlsruhe\' enthalten.',
u'Das Passwort darf nicht die Zeichenfolge \'berge\' enthalten.', 'Das Passwort darf nicht die Zeichenfolge \'berge\' enthalten.',
]), ]),
(u'heinzel@alpenverein-karlsruhe.de', [ ('heinzel@alpenverein-karlsruhe.de', [
u'Das Passwort darf nicht die Zeichenfolge \'heinzel\' enthalten.', 'Das Passwort darf nicht die Zeichenfolge \'heinzel\' enthalten.',
u'Das Passwort darf nicht die Zeichenfolge \'alpenverein\' enthalten.', 'Das Passwort darf nicht die Zeichenfolge \'alpenverein\' enthalten.',
u'Das Passwort darf nicht die Zeichenfolge \'karlsruhe\' enthalten.', 'Das Passwort darf nicht die Zeichenfolge \'karlsruhe\' enthalten.',
]), ]),
] ]
@@ -114,13 +114,13 @@ class CustomWordlistPasswordValidatorTestCase(SimpleTestCase):
for error in errors: for error in errors:
self.assertIn(error, expected_errors) self.assertIn(error, expected_errors)
else: else:
self.fail(u'%s: no validation error was raised' % password) self.fail('%s: no validation error was raised' % password)
def test_valid(self): def test_valid(self):
passwords = [ passwords = [
u'', '',
u'password', 'password',
u'münchen', 'münchen',
] ]
validator = CustomWordlistPasswordValidator() validator = CustomWordlistPasswordValidator()
@@ -134,72 +134,72 @@ class CustomWordlistPasswordValidatorTestCase(SimpleTestCase):
class CharacterClassPasswordValidatorTestCase(SimpleTestCase): class CharacterClassPasswordValidatorTestCase(SimpleTestCase):
def setUp(self): def setUp(self):
super(CharacterClassPasswordValidatorTestCase, self).setUp() super().setUp()
self.validator = CharacterClassPasswordValidator() self.validator = CharacterClassPasswordValidator()
def test_invalid(self): def test_invalid(self):
invalid_passwords = [ invalid_passwords = [
(u'', [ ('', [
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
u'Das Passwort muss mindestens 2 Ziffern enthalten.', 'Das Passwort muss mindestens 2 Ziffern enthalten.',
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
]), ]),
(u'A+-', [ ('A+-', [
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
u'Das Passwort muss mindestens 2 Ziffern enthalten.', 'Das Passwort muss mindestens 2 Ziffern enthalten.',
]), ]),
(u'1234567890*', [ ('1234567890*', [
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
]), ]),
(u'34*/()', [ ('34*/()', [
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
]), ]),
(u'AA', [ ('AA', [
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
u'Das Passwort muss mindestens 2 Ziffern enthalten.', 'Das Passwort muss mindestens 2 Ziffern enthalten.',
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
]), ]),
(u'CD0.,', [ ('CD0.,', [
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
u'Das Passwort muss mindestens 2 Ziffern enthalten.', 'Das Passwort muss mindestens 2 Ziffern enthalten.',
]), ]),
(u'EF56', [ ('EF56', [
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
]), ]),
(u'8GH?!8', [ ('8GH?!8', [
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
]), ]),
(u'bbX', [ ('bbX', [
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
u'Das Passwort muss mindestens 2 Ziffern enthalten.', 'Das Passwort muss mindestens 2 Ziffern enthalten.',
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
]), ]),
(u'$cd%', [ ('$cd%', [
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
u'Das Passwort muss mindestens 2 Ziffern enthalten.', 'Das Passwort muss mindestens 2 Ziffern enthalten.',
]), ]),
(u'ef90', [ ('ef90', [
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
]), ]),
(u'1g=h3~', [ ('1g=h3~', [
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.', 'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
]), ]),
(u'Gi&jH', [ ('Gi&jH', [
u'Das Passwort muss mindestens 2 Ziffern enthalten.', 'Das Passwort muss mindestens 2 Ziffern enthalten.',
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
]), ]),
(u'IkK:i;', [ ('IkK:i;', [
u'Das Passwort muss mindestens 2 Ziffern enthalten.', 'Das Passwort muss mindestens 2 Ziffern enthalten.',
]), ]),
(u'mKn4L8', [ ('mKn4L8', [
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.', 'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
]), ]),
] ]
@@ -215,10 +215,10 @@ class CharacterClassPasswordValidatorTestCase(SimpleTestCase):
for error in errors: for error in errors:
self.assertIn(error, expected_errors) self.assertIn(error, expected_errors)
else: else:
self.fail(u'%s: no validation error was raised' % password) self.fail('%s: no validation error was raised' % password)
def test_valid(self): def test_valid(self):
valid_passwords = [u'abCD12+-'] valid_passwords = ['abCD12+-']
validator = self.validator validator = self.validator
for password in valid_passwords: for password in valid_passwords:
try: try:

View File

@@ -10,7 +10,7 @@ from django.urls import reverse
from ..forms import LoginForm, SetPasswordForm, CreateAndSendPasswordForm from ..forms import LoginForm, SetPasswordForm, CreateAndSendPasswordForm
TEST_USERNAME = 'root@localhost' TEST_USERNAME = 'root@localhost'
TEST_PASSWORD = u'me||ön 21ABll' TEST_PASSWORD = 'me||ön 21ABll'
TEST_EMAIL = TEST_USERNAME TEST_EMAIL = TEST_USERNAME
@@ -30,12 +30,12 @@ class ViewsTestCase(TestCase):
cls.recreate_password_url = reverse('dav_auth:recreate_password') cls.recreate_password_url = reverse('dav_auth:recreate_password')
# Some messages # Some messages
cls.wrong_credentials_message = ugettext(u'Benutzername oder Passwort falsch.') cls.wrong_credentials_message = ugettext('Benutzername oder Passwort falsch.')
cls.logout_message = ugettext(u'Benutzer abgemeldet.') cls.logout_message = ugettext('Benutzer abgemeldet.')
cls.set_password_message = ugettext(u'Passwort gespeichert.') cls.set_password_message = ugettext('Passwort gespeichert.')
def setUp(self): def setUp(self):
super(TestCase, self).setUp() super().setUp()
# Need a test user # Need a test user
self.test_username = TEST_USERNAME self.test_username = TEST_USERNAME
self.test_password = TEST_PASSWORD self.test_password = TEST_PASSWORD
@@ -74,7 +74,7 @@ class ViewsTestCase(TestCase):
def test_integrated_login_succeed(self): def test_integrated_login_succeed(self):
username = self.user.username 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}) response = self.client.post(self.login_url, {'username': username, 'password': self.test_password})
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
@@ -166,7 +166,7 @@ class ViewsTestCase(TestCase):
'send_password_mail': True}) 'send_password_mail': True})
self.assertEqual(len(django_mail.outbox), 1) self.assertEqual(len(django_mail.outbox), 1)
mail = django_mail.outbox[0] 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() recipients = mail.recipients()
self.assertIn(recipient, recipients) self.assertIn(recipient, recipients)
self.assertEqual(len(recipients), 1) self.assertEqual(len(recipients), 1)
@@ -202,7 +202,7 @@ class ViewsTestCase(TestCase):
self.assertEqual(len(django_mail.outbox), 1) self.assertEqual(len(django_mail.outbox), 1)
mail = django_mail.outbox[0] 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() recipients = mail.recipients()
self.assertIn(recipient, recipients) self.assertIn(recipient, recipients)
self.assertEqual(len(recipients), 1) self.assertEqual(len(recipients), 1)

View File

@@ -2,6 +2,8 @@ from django.conf.urls import url
from . import views from . import views
app_name = 'dav_auth'
urlpatterns = [ urlpatterns = [
url(r'^login$', views.LoginView.as_view(), name='login'), url(r'^login$', views.LoginView.as_view(), name='login'),
url(r'^logout$', views.LogoutView.as_view(), name='logout'), url(r'^logout$', views.LogoutView.as_view(), name='logout'),

View File

@@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
class PasswordScoreValidator(object): class PasswordScoreValidator:
def _get_score(self, password, user=None): def _get_score(self, password, user=None):
score = 0 score = 0
char_counters = {} char_counters = {}
@@ -56,67 +56,67 @@ class PasswordScoreValidator(object):
score, used_classes = self._get_score(password, user=user) score, used_classes = self._get_score(password, user=user)
if used_classes < self.min_classes: if used_classes < self.min_classes:
raise ValidationError(_(u'Das Passwort muss Zeichen aus mindestens %(min_classes)d' raise ValidationError(_('Das Passwort muss Zeichen aus mindestens %(min_classes)d'
u' verschiedenen Arten von Zeichen bestehen' ' verschiedenen Arten von Zeichen bestehen'
u' (d.h. Kleinbuchstaben, Großbuchstaben, Ziffern und Sonderzeichen).'), ' (d.h. Kleinbuchstaben, Großbuchstaben, Ziffern und Sonderzeichen).'),
code='too_few_classes', code='too_few_classes',
params={'min_classes': self.min_classes}) params={'min_classes': self.min_classes})
if score < self.min_score: if score < self.min_score:
raise ValidationError(_(u'Dieses Passwort ist zu einfach. Benutze mehr Zeichen' raise ValidationError(_('Dieses Passwort ist zu einfach. Benutze mehr Zeichen'
u' und gegebenenfalls auch Großbuchstaben, Ziffern und Sonderzeichen.'), ' und gegebenenfalls auch Großbuchstaben, Ziffern und Sonderzeichen.'),
code='too_little_score') code='too_little_score')
return score return score
def get_help_text(self): def get_help_text(self):
text = u'%s\n%s' % ( text = '%s\n%s' % (
_(u'The password must get a minimum score of %d points.') % self.min_score, _('The password must get a minimum score of %d points.') % self.min_score,
_(u'For each character the score is increased by 1 point.'), _('For each character the score is increased by 1 point.'),
) )
if self.lcredit > 0: if self.lcredit > 0:
text += '\n' 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: if self.ucredit > 0:
text += '\n' 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: if self.dcredit > 0:
text += '\n' 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: if self.ocredit > 0:
text += '\n' text += '\n'
text += _(u'The first %d non alpha numeric characters' text += _('The first %d non alpha numeric characters'
u' increase the score by 1 point each.') % self.ocredit ' increase the score by 1 point each.') % self.ocredit
if self.max_repeat > 0: if self.max_repeat > 0:
text += '\n' text += '\n'
text += _(u'If a particular character is used more than %d times,' text += _('If a particular character is used more than %d times,'
u' it will not increase the score anymore.') % self.max_repeat ' it will not increase the score anymore.') % self.max_repeat
if self.min_classes > 0: if self.min_classes > 0:
text += '\n' text += '\n'
text += _(u'Also the password must contain characters from %d different character classes' text += _('Also the password must contain characters from %d different character classes'
u' (i.e. lower, upper, digits, others).') % self.min_classes ' (i.e. lower, upper, digits, others).') % self.min_classes
return text return text
class CustomWordlistPasswordValidator(object): class CustomWordlistPasswordValidator:
context = 'the Sektion Karlsruhe' context = 'the Sektion Karlsruhe'
words = ( words = (
u'dav', 'dav',
u'berge', 'berge',
u'sektion', 'sektion',
u'karlsruhe', 'karlsruhe',
u'alpenverein', 'alpenverein',
u'heinzel', 'heinzel',
u'passwort', 'passwort',
) )
def validate(self, password, user=None): def validate(self, password, user=None): # pylint: disable=unused-argument
errors = [] errors = []
lower_pw = password.lower() lower_pw = password.lower()
for word in self.words: for word in self.words:
if word in lower_pw: 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', code='forbidden_word',
params={'word': word}) params={'word': word})
errors.append(error) errors.append(error)
@@ -125,14 +125,14 @@ class CustomWordlistPasswordValidator(object):
raise ValidationError(errors) raise ValidationError(errors)
def get_help_text(self): def get_help_text(self):
text = _(u'The password must not contain some specific words,' text = _('The password must not contain some specific words,'
u' that are common in context with %(context)s.') % {'context': self.context} ' that are common in context with %(context)s.') % {'context': self.context}
text += u'\n' text += '\n'
text += _(u'All words are matched case insensitive.') text += _('All words are matched case insensitive.')
return text return text
class CharacterClassPasswordValidator(object): class CharacterClassPasswordValidator:
def _is_enough_lower(self, password): def _is_enough_lower(self, password):
lower = re.sub(r'[^a-z]', '', password) lower = re.sub(r'[^a-z]', '', password)
if len(lower) < self.minimum_lower: if len(lower) < self.minimum_lower:
@@ -163,26 +163,26 @@ class CharacterClassPasswordValidator(object):
self.minimum_digits = minimum_digits self.minimum_digits = minimum_digits
self.minimum_others = minimum_others self.minimum_others = minimum_others
def validate(self, password, user=None): def validate(self, password, user=None): # pylint: disable=unused-argument
errors = [] errors = []
if not self._is_enough_lower(password): 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', code='too_few_lower_characters',
params={'min_lower': self.minimum_lower}) params={'min_lower': self.minimum_lower})
errors.append(error) errors.append(error)
if not self._is_enough_upper(password): 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', code='too_few_upper_characters',
params={'min_upper': self.minimum_upper}) params={'min_upper': self.minimum_upper})
errors.append(error) errors.append(error)
if not self._is_enough_digits(password): 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', code='too_few_digits',
params={'min_digits': self.minimum_digits}) params={'min_digits': self.minimum_digits})
errors.append(error) errors.append(error)
if not self._is_enough_others(password): if not self._is_enough_others(password):
error = ValidationError(_(u'Das Passwort muss mindestens %(min_others)d' error = ValidationError(_('Das Passwort muss mindestens %(min_others)d'
u' Sonderzeichen enthalten.'), ' Sonderzeichen enthalten.'),
code='too_few_other_characters', code='too_few_other_characters',
params={'min_others': self.minimum_others}) params={'min_others': self.minimum_others})
errors.append(error) errors.append(error)
@@ -191,7 +191,7 @@ class CharacterClassPasswordValidator(object):
raise ValidationError(errors) raise ValidationError(errors)
def get_help_text(self): 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_lower,
_('The password must contain at least %d characters from A-Z.') % self.minimum_upper, _('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, _('The password must contain at least %d digits from 0-9.') % self.minimum_digits,

View File

@@ -24,39 +24,39 @@ class LoginView(auth_views.LoginView):
template_name = 'dav_auth/forms/login.html' template_name = 'dav_auth/forms/login.html'
def get_redirect_url(self): 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: if not url and app_config.settings.login_redirect_url:
url = resolve_url(app_config.settings.login_redirect_url) url = resolve_url(app_config.settings.login_redirect_url)
return url return url
def form_valid(self, form): def form_valid(self, form):
r = super(LoginView, self).form_valid(form) r = super().form_valid(form)
messages.success(self.request, _(u'Benutzer angemeldet: %(username)s') % {'username': form.get_user()}) messages.success(self.request, _('Benutzer angemeldet: %(username)s') % {'username': form.get_user()})
try: try:
validate_password(form.cleaned_data['password']) validate_password(form.cleaned_data['password'])
except ValidationError as e: except ValidationError as e:
logger.warning(u'Weak password (%d): %s', self.request.user.pk, e) logger.warning('Weak password (%d): %s', self.request.user.pk, e)
message = u'<br />\n<p>\n' message = '<br />\n<p>\n'
message += u'Dein Passwort entspricht nicht mehr den aktuellen Passwortrichtlinien.<br />\n' message += 'Dein Passwort entspricht nicht mehr den aktuellen Passwortrichtlinien.<br />\n'
message += u'Bitte hilf uns die Daten deiner Teilnehmer zu schützen und ändere dein Passwort.<br />\n' message += 'Bitte hilf uns die Daten deiner Teilnehmer zu schützen und ändere dein Passwort.<br />\n'
message += u'</p>\n' message += '</p>\n'
message += u'<p>\n' message += '<p>\n'
message += u'<a href="%(href)s">Passwort ändern</a>\n' % {'href': reverse('dav_auth:set_password')} message += '<a href="%(href)s">Passwort ändern</a>\n' % {'href': reverse('dav_auth:set_password')}
message += u'</p>\n<br />\n' message += '</p>\n<br />\n'
messages.warning(self.request, mark_safe(message)) messages.warning(self.request, mark_safe(message))
return r return r
class LogoutView(auth_views.LogoutView): class LogoutView(auth_views.LogoutView):
def get_next_page(self): 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: if not url and app_config.settings.logout_redirect_url:
url = resolve_url(app_config.settings.logout_redirect_url) url = resolve_url(app_config.settings.logout_redirect_url)
return url return url
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
r = super(LogoutView, self).dispatch(request, *args, **kwargs) r = super().dispatch(request, *args, **kwargs)
messages.success(self.request, _(u'Benutzer abgemeldet.')) messages.success(self.request, _('Benutzer abgemeldet.'))
return r return r
@@ -68,8 +68,8 @@ class SetPasswordView(auth_views.PasswordChangeView):
return resolve_url(app_config.settings.login_redirect_url) return resolve_url(app_config.settings.login_redirect_url)
def form_valid(self, form): def form_valid(self, form):
r = super(SetPasswordView, self).form_valid(form) r = super().form_valid(form)
messages.success(self.request, _(u'Passwort gespeichert.')) messages.success(self.request, _('Passwort gespeichert.'))
if form.cleaned_data.get('send_password_mail', False): if form.cleaned_data.get('send_password_mail', False):
email = emails.PasswordSetEmail(self.request.user, form.cleaned_data['new_password']) email = emails.PasswordSetEmail(self.request.user, form.cleaned_data['new_password'])
email.send() email.send()
@@ -91,14 +91,14 @@ class CreateAndSendPasswordView(generic.FormView):
user.save() user.save()
email = emails.PasswordSetEmail(user, random_password) email = emails.PasswordSetEmail(user, random_password)
email.send() 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) logger.info('Password recreated for user \'%s\'', username)
except user_model.DoesNotExist: except user_model.DoesNotExist:
logger.warning('Password recreated for unknown user \'%s\'', username) 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): def get(self, request, *args, **kwargs):
if request.user.is_authenticated: if request.user.is_authenticated:
return HttpResponseRedirect(reverse('dav_auth:set_password')) return HttpResponseRedirect(reverse('dav_auth:set_password'))
return super(CreateAndSendPasswordView, self).get(request, *args, **kwargs) return super().get(request, *args, **kwargs)

View File

@@ -1 +1 @@
default_app_config = 'dav_base.apps.AppConfig' default_app_config = 'dav_base.apps.AppConfig' # pylint: disable=invalid-name

View File

@@ -9,5 +9,5 @@ DEFAULT_SETTINGS = (
class AppConfig(_AppConfig): class AppConfig(_AppConfig):
name = 'dav_base' name = 'dav_base'
verbose_name = u'DAV Base App' verbose_name = 'DAV Base App'
default_settings = DEFAULT_SETTINGS default_settings = DEFAULT_SETTINGS

View File

@@ -7,7 +7,7 @@ from django.core.exceptions import ImproperlyConfigured
logger = logging.getLogger(__name__) 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): def __init__(self, name, value, key_name=None, validator=None):
self.name = name self.name = name
self.value = value self.value = value
@@ -27,7 +27,7 @@ class DefaultSetting(object):
raise ImproperlyConfigured('Does not match /{re}/'.format(re=self.validator)) 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): def __init__(self, app_name, defaults):
settings_name = 'main.settings-' + app_name 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, msg = 'Invalid value of {key} in {module}: {cause}'.format(key=default.key_name,
module=settings_name, module=settings_name,
cause=e) cause=e)
raise ImproperlyConfigured(msg) raise ImproperlyConfigured(msg) from e
setattr(self, default.name, value) setattr(self, default.name, value)
elif isinstance(default.value, ImproperlyConfigured): elif isinstance(default.value, ImproperlyConfigured):
raise default.value raise default.value
@@ -59,13 +59,13 @@ class AppSettings(object):
msg = '{key} must be defined in {module}'.format(key=default.key_name, msg = '{key} must be defined in {module}'.format(key=default.key_name,
module=settings_name) module=settings_name)
raise default.value(msg) raise default.value(msg)
else:
setattr(self, default.name, default.value) setattr(self, default.name, default.value)
class AppConfig(_AppConfig): class AppConfig(_AppConfig):
default_settings = () default_settings = ()
def __init__(self, app_name, app_module): 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) self.settings = AppSettings(app_name, self.default_settings)

View File

@@ -13,7 +13,7 @@ class ModuleConfigError(Exception):
pass pass
class ModuleMeta(object): class ModuleMeta:
_json_file = 'module.json' _json_file = 'module.json'
_root_url_name = 'root' _root_url_name = 'root'
@@ -73,7 +73,7 @@ class ModuleMeta(object):
return d return d
class ModuleConfig(object): class ModuleConfig:
_lazy_load = True _lazy_load = True
def __init__(self, config_file_path=None, django_base_dir=None): 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) config_file_path = os.path.join(django_base_dir, DJANGO_MAIN_MODULE, MODULE_CONFIG_FILE_NAME)
self._config_file_path = config_file_path self._config_file_path = config_file_path
self._modules = dict() self._modules = {}
self._loaded = False self._loaded = False
if not self._lazy_load: if not self._lazy_load:
@@ -96,13 +96,13 @@ class ModuleConfig(object):
def _load(self): def _load(self):
path = self._config_file_path path = self._config_file_path
self._modules = dict() self._modules = {}
if os.path.exists(path): if os.path.exists(path):
with open(path, 'r') as f: with open(path, 'r', encoding='ascii') as f:
data = json.load(f) data = json.load(f)
else: else:
data = dict() data = {}
if 'modules' in data: if 'modules' in data:
for meta_dict in data['modules']: for meta_dict in data['modules']:
@@ -131,5 +131,5 @@ class ModuleConfig(object):
for meta_obj in self._modules.values(): for meta_obj in self._modules.values():
data['modules'].append(meta_obj.dump_as_dict()) 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) json.dump(data, f, indent=4)

View File

@@ -1,8 +1,8 @@
import argparse import argparse
import os import os
import pkg_resources
import posix import posix
import sys import sys
import pkg_resources
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
from dav_base.config.modules import DJANGO_MAIN_MODULE, ModuleConfig 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' VERSION = '0.1'
class AdminCommand(object): class AdminCommand: # pylint: disable=too-few-public-methods
def _setup_argparser(self): def _setup_argparser(self):
kwargs = { kwargs = {
'description': 'Tool to manage the DAV django project installation.', 'description': 'Tool to manage the DAV django project installation.',

View File

@@ -3,8 +3,8 @@
# Additional settings for django-dav # Additional settings for django-dav
# #
BASE_VAR_DIR = os.path.join(BASE_DIR, 'var') BASE_VAR_DIR = BASE_DIR / 'var'
BASE_SHARE_DIR = os.path.join(BASE_DIR, 'common') BASE_SHARE_DIR = BASE_DIR / 'common'
# Get modules config # Get modules config
from dav_base.config.modules import ModuleConfig from dav_base.config.modules import ModuleConfig
@@ -14,7 +14,7 @@ INSTALLED_APPS += [
'bootstrap3', 'bootstrap3',
'datetimewidget', 'datetimewidget',
'django_countries', 'django_countries',
'django_extensions', # 'django_extensions',
# Our main app # Our main app
'dav_base', 'dav_base',
] ]
@@ -45,11 +45,11 @@ TEMPLATES += [
# Add our local templates directory to the template engine configurations. # Add our local templates directory to the template engine configurations.
for config in TEMPLATES: for config in TEMPLATES:
config['DIRS'].append(os.path.join(BASE_SHARE_DIR, 'templates')) config['DIRS'].append(BASE_SHARE_DIR / 'templates')
DATABASES['default'] = { DATABASES['default'] = {
'ENGINE': 'django.db.backends.sqlite3', '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 = [ 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' LANGUAGE_CODE = 'de'
TIME_ZONE = 'Europe/Berlin' TIME_ZONE = 'Europe/Berlin'
@@ -130,20 +130,20 @@ LOGGING = {
'default_log': { 'default_log': {
'level': 'INFO', 'level': 'INFO',
'class': 'logging.FileHandler', 'class': 'logging.FileHandler',
'filename': os.path.join(BASE_VAR_DIR, 'log', 'default.log'), 'filename': BASE_VAR_DIR / 'log' / 'default.log',
'formatter': 'default', 'formatter': 'default',
}, },
'error_log': { 'error_log': {
'level': 'ERROR', 'level': 'ERROR',
'class': 'logging.FileHandler', 'class': 'logging.FileHandler',
'filename': os.path.join(BASE_VAR_DIR, 'log', 'error.log'), 'filename': BASE_VAR_DIR / 'log' / 'error.log',
'formatter': 'default', 'formatter': 'default',
}, },
'debug_log': { 'debug_log': {
'level': 'DEBUG', 'level': 'DEBUG',
'filters': ['require_debug_true'], 'filters': ['require_debug_true'],
'class': 'logging.FileHandler', 'class': 'logging.FileHandler',
'filename': os.path.join(BASE_VAR_DIR, 'log', 'debug.log'), 'filename': BASE_VAR_DIR / 'log' / 'debug.log',
'formatter': 'verbose', 'formatter': 'verbose',
}, },
}, },

View File

@@ -9,8 +9,8 @@ app_config = apps.get_containing_app_config(__package__)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class AbstractMail(object): class AbstractMail: # pylint: disable=too-few-public-methods
_subject = u'' _subject = ''
_template_name = None _template_name = None
def _get_sender(self): def _get_sender(self):
@@ -20,7 +20,7 @@ class AbstractMail(object):
if subject_fmt is None: if subject_fmt is None:
subject_fmt = self._subject subject_fmt = self._subject
if app_config.settings.email_subject_prefix: 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) subject = subject_fmt.format(**kwargs)
return subject return subject
@@ -57,7 +57,7 @@ class AbstractMail(object):
email = EmailMessage(subject=subject, body=body, from_email=sender, to=recipients, reply_to=reply_to) email = EmailMessage(subject=subject, body=body, from_email=sender, to=recipients, reply_to=reply_to)
if fail_silently: 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: 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) email.send(fail_silently=fail_silently)

View File

@@ -1,6 +1,6 @@
{# This template is used by software tests #}{% load dav_base %} {# This template is used by software tests #}{% load dav_base %}
--{% include_if_exist './includes/include_missing.html' %}-- --{% include_if_exist './includes/include_missing3.html' %}--
--{% include_if_exist './includes/include_missing.html' default 'dav_base/tests/includes/include_default.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' %}--
--{% include_if_exist './includes/include_optional.html' default 'dav_base/tests/includes/include_default.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' %}-- --{% include_if_exist './includes/include_with_missing_include.html' %}--

View File

@@ -1,2 +1,2 @@
{# This template is used by software tests #}{% load dav_base %} {# 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' %}--

View File

@@ -1,2 +1,2 @@
{# This template is used by software tests #}{% load dav_base %} {# This template is used by software tests #}{% load dav_base %}
--{% include './include_missing.html' %}-- --{% include './include_missing5.html' %}--

View File

@@ -16,7 +16,7 @@ def do_include_if_exist(parser, token):
""" """
bits = token.split_contents() bits = token.split_contents()
if len(bits) < 2: 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]) " the name of the template to be included" % bits[0])
try: try:
@@ -27,6 +27,10 @@ def do_include_if_exist(parser, token):
token = template.base.Token(token.token_type, ' '.join(bits)) token = template.base.Token(token.token_type, ' '.join(bits))
token2 = template.base.Token(token.token_type, bits[0] + ' ' + default_template + ' '.join(bits[2:])) token2 = template.base.Token(token.token_type, bits[0] + ' ' + default_template + ' '.join(bits[2:]))
default_node = template.loader_tags.do_include(parser, token2) 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='<unknown_source>', template_name=None)
except ValueError: except ValueError:
default_node = template.defaulttags.CommentNode() default_node = template.defaulttags.CommentNode()
except IndexError: except IndexError:

View File

@@ -1,5 +1,6 @@
import datetime import datetime
import os import os
from urllib.parse import quote
from django.apps import apps from django.apps import apps
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.contrib.staticfiles.testing import StaticLiveServerTestCase
@@ -9,11 +10,10 @@ from django.test import SimpleTestCase, TestCase, tag
from django.urls import reverse from django.urls import reverse
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as ExpectedConditions
from six.moves.urllib.parse import quote
class AppSetting(object): class AppSetting: # pylint: disable=too-few-public-methods
def __init__(self, name, of=None): def __init__(self, name, of=None):
self.name = name self.name = name
self.of = of self.of = of
@@ -24,7 +24,7 @@ class AppsTestCase(SimpleTestCase):
settings = () settings = ()
def setUp(self): def setUp(self):
super(AppsTestCase, self).setUp() super().setUp()
if self.app_config: if self.app_config:
self.configured_settings = self.app_config.settings self.configured_settings = self.app_config.settings
else: else:
@@ -41,7 +41,7 @@ class AppsTestCase(SimpleTestCase):
self.assertIsInstance(value, of) self.assertIsInstance(value, of)
class EmailTestMixin(object): class EmailTestMixin:
email_sender = 'Automatic Software Test <root@localhost>' email_sender = 'Automatic Software Test <root@localhost>'
email_base_url = 'http://localhost' email_base_url = 'http://localhost'
email_subject_prefix = '[Test]' email_subject_prefix = '[Test]'
@@ -55,29 +55,29 @@ class EmailTestMixin(object):
return mails return mails
def assertSender(self, mail): def assertSender(self, mail): # pylint: disable=invalid-name
self.assertEqual(mail.from_email, self.email_sender) 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)) self.assertEqual(len(mail.reply_to), len(addresses))
for expected_address in addresses: for expected_address in addresses:
if isinstance(expected_address, AbstractUser): 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) 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)) self.assertEqual(len(mail.recipients()), len(recipients))
for expected_recipient in recipients: for expected_recipient in recipients:
if isinstance(expected_recipient, AbstractUser): 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() recipients = mail.recipients()
self.assertIn(expected_recipient, recipients) self.assertIn(expected_recipient, recipients)
def assertSubject(self, mail, subject): def assertSubject(self, mail, subject): # pylint: disable=invalid-name
expected_subject = u'{} {}'.format(self.email_subject_prefix, subject) expected_subject = '{} {}'.format(self.email_subject_prefix, subject)
self.assertEqual(mail.subject, expected_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() expected_lines = body.splitlines()
lines = mail.body.splitlines() lines = mail.body.splitlines()
i = 0 i = 0
@@ -93,14 +93,14 @@ class EmailTestMixin(object):
self.fail('line %d: %s' % (i, e)) self.fail('line %d: %s' % (i, e))
self.assertEqual(mail.body, body) 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 = apps.get_app_config('dav_base')
app_config.settings.email_sender = self.email_sender app_config.settings.email_sender = self.email_sender
app_config.settings.email_base_url = self.email_base_url app_config.settings.email_base_url = self.email_base_url
app_config.settings.email_subject_prefix = self.email_subject_prefix 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): def __init__(self, data, expected_errors=None, form_kwargs=None):
self.data = data self.data = data
self.expected_errors = expected_errors self.expected_errors = expected_errors
@@ -116,44 +116,46 @@ class FormsTestCase(TestCase):
if form_class is None: if form_class is None:
form_class = self.form_class form_class = self.form_class
if form_class is None: if form_class is None:
return True return
if data_sets is None: if data_sets is None:
data_sets = self.valid_data_sets data_sets = self.valid_data_sets
given_form_kwargs = form_kwargs
for data_set in data_sets: for data_set in data_sets:
fk = {} form_kwargs = {}
if form_kwargs is not None: if given_form_kwargs is not None:
fk.update(form_kwargs) form_kwargs.update(given_form_kwargs)
if data_set.form_kwargs is not None: if data_set.form_kwargs is not None:
fk.update(data_set.form_kwargs) form_kwargs.update(data_set.form_kwargs)
fk['data'] = data_set.data form_kwargs['data'] = data_set.data
form = form_class(**fk) form = form_class(**form_kwargs)
if not form.is_valid(): if not form.is_valid():
errors = [] errors = []
for key in form.errors.as_data(): for key in form.errors.as_data():
for ve in form.errors[key].as_data(): for e in form.errors[key].as_data():
errors.append(u'%s (%s)' % (ve.code, ve.message)) errors.append('%s (%s)' % (e.code, e.message))
self.fail(u'Invalid form data \'%s\': %s' % (data_set.data, errors)) 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): def test_invalid_data(self, form_class=None, data_sets=None, form_kwargs=None):
if form_class is None: if form_class is None:
form_class = self.form_class form_class = self.form_class
if form_class is None: if form_class is None:
return True return
if data_sets is None: if data_sets is None:
data_sets = self.invalid_data_sets data_sets = self.invalid_data_sets
given_form_kwargs = form_kwargs
for data_set in data_sets: for data_set in data_sets:
fk = {} form_kwargs = {}
if form_kwargs is not None: if given_form_kwargs is not None:
fk.update(form_kwargs) form_kwargs.update(given_form_kwargs)
if data_set.form_kwargs is not None: if data_set.form_kwargs is not None:
fk.update(data_set.form_kwargs) form_kwargs.update(data_set.form_kwargs)
fk['data'] = data_set.data form_kwargs['data'] = data_set.data
form = form_class(**fk) form = form_class(**form_kwargs)
if form.is_valid(): if form.is_valid():
self.fail('Valid form data: \'%s\'' % data_set.data) self.fail('Valid form data: \'%s\'' % data_set.data)
@@ -164,7 +166,7 @@ class FormsTestCase(TestCase):
self.assertIn(code, error_codes) 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): def __init__(self, location, name=None, func=None, **kwargs):
self.location = location self.location = location
self.name = name self.name = name
@@ -210,15 +212,15 @@ class UrlsTestCase(TestCase):
'Getting url named \'{}\' resolve to wrong function'.format(url.name)) 'Getting url named \'{}\' resolve to wrong function'.format(url.name))
class ValidatorTestMixin(object): class ValidatorTestMixin:
def assertValid(self, validator, data): def assertValid(self, validator, data): # pylint: disable=invalid-name
for val in data: for val in data:
try: try:
validator(val) validator(val)
except ValidationError as e: # pragma: no cover except ValidationError as e: # pragma: no cover
self.fail('%s: %s' % (val, e)) self.fail('%s: %s' % (val, e))
def assertInvalid(self, validator, data): def assertInvalid(self, validator, data): # pylint: disable=invalid-name
for val in data: for val in data:
try: try:
validator(val) validator(val)
@@ -234,7 +236,7 @@ class SeleniumTestCase(StaticLiveServerTestCase):
window_height = 768 window_height = 768
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(SeleniumTestCase, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._driver = None self._driver = None
self._driver_options = webdriver.FirefoxOptions() self._driver_options = webdriver.FirefoxOptions()
self.quit_selenium = None self.quit_selenium = None
@@ -254,7 +256,7 @@ class SeleniumTestCase(StaticLiveServerTestCase):
def tearDown(self): def tearDown(self):
if self.quit_selenium: if self.quit_selenium:
self.selenium.quit() self.selenium.quit()
super(SeleniumTestCase, self).tearDown() super().tearDown()
def complete_url(self, location): def complete_url(self, location):
base_url = self.live_server_url base_url = self.live_server_url
@@ -264,8 +266,8 @@ class SeleniumTestCase(StaticLiveServerTestCase):
return self.selenium.get(self.complete_url(location)) return self.selenium.get(self.complete_url(location))
def wait_on(self, driver, ec_name, ec_argument, timeout=30): def wait_on(self, driver, ec_name, ec_argument, timeout=30):
ec = getattr(EC, ec_name) expected_condition = getattr(ExpectedConditions, ec_name)
return WebDriverWait(driver, timeout).until(ec(ec_argument)) return WebDriverWait(driver, timeout).until(expected_condition(ec_argument))
def wait_on_presence(self, driver, locator, timeout=30): def wait_on_presence(self, driver, locator, timeout=30):
ec_name = 'presence_of_element_located' ec_name = 'presence_of_element_located'
@@ -281,7 +283,7 @@ class ScreenshotTestCase(SeleniumTestCase):
locations = () locations = ()
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ScreenshotTestCase, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
screenshot_base_dir = os.path.join('tmp', 'test-screenshots') screenshot_base_dir = os.path.join('tmp', 'test-screenshots')
self.screenshot_path = screenshot_base_dir self.screenshot_path = screenshot_base_dir
self.screenshot_sequences = {} self.screenshot_sequences = {}
@@ -298,7 +300,7 @@ class ScreenshotTestCase(SeleniumTestCase):
else: else:
self.screenshot_sequences[sequence] = 1 self.screenshot_sequences[sequence] = 1
n = self.screenshot_sequences[sequence] n = self.screenshot_sequences[sequence]
sequence = u'%s-%04d-' % (sequence, n) sequence = '%s-%04d-' % (sequence, n)
if title is None: if title is None:
location = self.selenium.current_url location = self.selenium.current_url
if location.startswith(self.live_server_url): if location.startswith(self.live_server_url):
@@ -308,7 +310,7 @@ class ScreenshotTestCase(SeleniumTestCase):
location = 'root' location = 'root'
title = location 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'), timestamp=datetime.datetime.now().strftime('%Y%m%d-%H%M%S.%f'),
prefix=self.screenshot_prefix, prefix=self.screenshot_prefix,
sequence=sequence, sequence=sequence,

View File

@@ -48,8 +48,8 @@ class DefaultSettingTestCase(SimpleTestCase):
re_validator = r'[a-z]' re_validator = r'[a-z]'
valid_values = u'aocd' valid_values = 'aocd'
invalid_values = u'Aö1.' invalid_values = 'Aö1.'
setting = DefaultSetting(name, None, validator=re_validator) setting = DefaultSetting(name, None, validator=re_validator)
for val in valid_values: for val in valid_values:
@@ -89,7 +89,7 @@ class AppSettingsTestCase(SimpleTestCase):
DefaultSetting('no_test_setting', ImproperlyConfigured), DefaultSetting('no_test_setting', ImproperlyConfigured),
) )
with self.assertRaisesRegex(ImproperlyConfigured, 'NO_TEST_SETTING must be defined in main.settings-dav_base'): 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): def test_improperlyconfigured_by_instance(self):
"""Test if mandatory but unset setting raise correct exception""" """Test if mandatory but unset setting raise correct exception"""
@@ -97,17 +97,17 @@ class AppSettingsTestCase(SimpleTestCase):
DefaultSetting('no_test_setting', ImproperlyConfigured('Some Error Message')), DefaultSetting('no_test_setting', ImproperlyConfigured('Some Error Message')),
) )
with self.assertRaisesRegex(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): def test_improperlyconfigured_by_func(self):
"""Test if invalid setting raise correct exception""" """Test if invalid setting raise correct exception"""
def validator(value): def validator(value): # pylint: disable=unused-argument
return False return False
default_settings = ( default_settings = (
DefaultSetting('test_setting', 1, validator=validator), DefaultSetting('test_setting', 1, validator=validator),
) )
with self.assertRaises(ImproperlyConfigured): with self.assertRaises(ImproperlyConfigured):
app_settings = AppSettings(self.app_name, default_settings) _ = AppSettings(self.app_name, default_settings)
def test_improperlyconfigured_by_regex(self): def test_improperlyconfigured_by_regex(self):
"""Test if invalid setting raise correct exception""" """Test if invalid setting raise correct exception"""
@@ -115,7 +115,7 @@ class AppSettingsTestCase(SimpleTestCase):
DefaultSetting('test_setting', 1, validator=r'^[01]$'), DefaultSetting('test_setting', 1, validator=r'^[01]$'),
) )
with self.assertRaises(ImproperlyConfigured): with self.assertRaises(ImproperlyConfigured):
app_settings = AppSettings(self.app_name, default_settings) _ = AppSettings(self.app_name, default_settings)
def test_settings_from_file(self): def test_settings_from_file(self):
"""Test if value from settings file eliminate exception""" """Test if value from settings file eliminate exception"""
@@ -145,7 +145,7 @@ class AppSettingsTestCase(SimpleTestCase):
class AppConfigTestCase(SimpleTestCase): class AppConfigTestCase(SimpleTestCase):
def test_init(self): def test_init(self):
app_name = 'dav_base' app_name = 'dav_base'
import dav_base as app_module import dav_base as app_module # pylint: disable=import-outside-toplevel
test_default_settings = ( test_default_settings = (
DefaultSetting('test_setting', ImproperlyConfigured), DefaultSetting('test_setting', ImproperlyConfigured),
@@ -154,7 +154,7 @@ class AppConfigTestCase(SimpleTestCase):
class TestAppConfig(AppConfig): class TestAppConfig(AppConfig):
name = 'not_dav_base' name = 'not_dav_base'
verbose_name = u'DAV Base App' verbose_name = 'DAV Base App'
default_settings = test_default_settings default_settings = test_default_settings
app_config = TestAppConfig(app_name, app_module) app_config = TestAppConfig(app_name, app_module)

View File

@@ -10,7 +10,7 @@ MAIL_TEMPLATE = 'dav_base/tests/mail.txt'
class TestCase(EmailTestMixin, SimpleTestCase): class TestCase(EmailTestMixin, SimpleTestCase):
def test_no_template_configured(self): def test_no_template_configured(self):
class ConcreteMail(AbstractMail): class ConcreteMail(AbstractMail): # pylint: disable=abstract-method disable=too-few-public-methods
pass pass
email = ConcreteMail() email = ConcreteMail()
@@ -18,7 +18,7 @@ class TestCase(EmailTestMixin, SimpleTestCase):
email.send() email.send()
def test_no_get_recipients_implemented(self): 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 _template_name = MAIL_TEMPLATE
email = ConcreteMail() email = ConcreteMail()
@@ -28,7 +28,7 @@ class TestCase(EmailTestMixin, SimpleTestCase):
def test_send(self): def test_send(self):
recipient = 'root@localhost' recipient = 'root@localhost'
class ConcreteMail(AbstractMail): class ConcreteMail(AbstractMail): # pylint: disable=too-few-public-methods
_template_name = MAIL_TEMPLATE _template_name = MAIL_TEMPLATE
def _get_recipients(self): def _get_recipients(self):
@@ -42,5 +42,5 @@ class TestCase(EmailTestMixin, SimpleTestCase):
self.assertSender(mail) self.assertSender(mail)
self.assertRecipients(mail, [recipient]) self.assertRecipients(mail, [recipient])
self.assertSubject(mail, u'') self.assertSubject(mail, '')
self.assertBody(mail, 'MAILBODY') self.assertBody(mail, 'MAILBODY')

View File

@@ -5,7 +5,7 @@ from django.test import SimpleTestCase
class TemplatesTestCase(SimpleTestCase): class TemplatesTestCase(SimpleTestCase):
def setUp(self): def setUp(self):
super(TemplatesTestCase, self).setUp() super().setUp()
self.response_from_root_view = self.client.get('/') self.response_from_root_view = self.client.get('/')
def test_template_usage(self): def test_template_usage(self):

View File

@@ -28,20 +28,20 @@ class TemplateTagsTestCase(SimpleTestCase):
def test_include_if_exist(self): def test_include_if_exist(self):
template_name = 'dav_base/tests/include_if_exist.html' template_name = 'dav_base/tests/include_if_exist.html'
template = get_template(template_name)
content = template.render() with self.settings(DEBUG=False):
expected_content = """ template = get_template(template_name)
content = template.render()
expected_content = """
---- ----
--DEFAULT INCLUDED HTML-- --DEFAULT INCLUDED HTML--
--OPTIONAL INCLUDED HTML-- --OPTIONAL 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): def test_include_if_exist_while_debug(self):
template_name = 'dav_base/tests/include_if_exist.html' template_name = 'dav_base/tests/include_if_exist.html'

View File

@@ -10,4 +10,4 @@ DAVNumberValidator = RegexValidator(r'^'
r'(\*[0-9]{4}\*[0-9]{4})?' r'(\*[0-9]{4}\*[0-9]{4})?'
r'([* ][0-9]{8})?' r'([* ][0-9]{8})?'
r'$', r'$',
_(u'Ungültiges Format.')) _('Ungültiges Format.'))

View File

@@ -7,7 +7,7 @@ class RootView(generic.TemplateView):
template_name = 'dav_base/root.html' template_name = 'dav_base/root.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
c = super(RootView, self).get_context_data(**kwargs) c = super().get_context_data(**kwargs)
root_urls = [] root_urls = []
for module_meta_obj in settings.MODULE_CONFIG.modules.values(): for module_meta_obj in settings.MODULE_CONFIG.modules.values():
root_url_name = 'root' root_url_name = 'root'

View File

@@ -1 +1 @@
default_app_config = 'dav_event_office.apps.AppConfig' default_app_config = 'dav_event_office.apps.AppConfig' # pylint: disable=invalid-name

View File

@@ -5,5 +5,5 @@ DEFAULT_SETTINGS = ()
class AppConfig(_AppConfig): class AppConfig(_AppConfig):
name = 'dav_event_office' name = 'dav_event_office'
verbose_name = u'DAV Touren- & Kursreferat' verbose_name = 'DAV Touren- & Kursreferat'
default_settings = DEFAULT_SETTINGS default_settings = DEFAULT_SETTINGS

View File

@@ -8,7 +8,7 @@
<p> <p>
Hier sind alle Veranstaltungen gelistet. Hier sind alle Veranstaltungen gelistet.
<span class="glyphicon glyphicon-question-sign" title=" <span class="glyphicon glyphicon-question-sign" title="
Zu jeder Veranstaltung ist eine Detail-Seite aufrufbar, über die Details und angemeldete Teilnehmer einsehbar sind. Zu jeder Veranstaltung ist eine Detail-Seite aufrufbar, über die Details und angemeldete Teilnehmer*innen einsehbar sind.
"></span> "></span>
</p> </p>
<p> <p>
@@ -18,14 +18,14 @@ Zu jeder Veranstaltung ist eine Detail-Seite aufrufbar, über die Details und an
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<div class="well"> <div class="well">
<p class="lead">{% trans 'Teilnehmer' %} <small><small>({% trans 'Zugang nur für Geschäftstelle' %})</small></small></p> <p class="lead">{% trans 'Teilnehmer*innen' %} <small><small>({% trans 'Zugang nur für Geschäftstelle' %})</small></small></p>
<p> <p>
Hier sind alle angemeldeten Teilnehmer gelistet. Hier sind alle angemeldeten Teilnehmer*innen gelistet.
<span class="glyphicon glyphicon-question-sign" title=" <span class="glyphicon glyphicon-question-sign" title="
"></span> "></span>
</p> </p>
<p> <p>
<a class="btn btn-success" href="{% url 'dav_event_office:participant-list' %}">{% trans 'Teilnehmer' %}</a> <a class="btn btn-success" href="{% url 'dav_event_office:participant-list' %}">{% trans 'Teilnehmer*innen' %}</a>
</p> </p>
</div> </div>
</div> </div>

View File

@@ -2,6 +2,8 @@ from django.conf.urls import url
from . import views from . import views
app_name = 'dav_event_office'
urlpatterns = [ urlpatterns = [
url(r'^home$', views.HomeView.as_view(), name='root'), url(r'^home$', views.HomeView.as_view(), name='root'),
url(r'^participants$', views.ParticipantListView.as_view(), name='participant-list'), url(r'^participants$', views.ParticipantListView.as_view(), name='participant-list'),

View File

@@ -24,7 +24,7 @@ class EventListView(_EventListView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not DefaultWorkflow.has_global_permission(request.user, 'payment'): if not DefaultWorkflow.has_global_permission(request.user, 'payment'):
raise PermissionDenied('payment') raise PermissionDenied('payment')
return super(EventListView, self).dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
class EventDetailView(_EventRegistrationsView): class EventDetailView(_EventRegistrationsView):
@@ -34,7 +34,7 @@ class EventDetailView(_EventRegistrationsView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not DefaultWorkflow.has_global_permission(request.user, 'payment'): if not DefaultWorkflow.has_global_permission(request.user, 'payment'):
raise PermissionDenied('payment') raise PermissionDenied('payment')
return super(EventDetailView, self).dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
class ParticipantListView(generic.ListView): class ParticipantListView(generic.ListView):
@@ -66,4 +66,4 @@ class ParticipantListView(generic.ListView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not DefaultWorkflow.has_global_permission(request.user, 'payment'): if not DefaultWorkflow.has_global_permission(request.user, 'payment'):
raise PermissionDenied('payment') raise PermissionDenied('payment')
return super(ParticipantListView, self).dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)

View File

@@ -1 +1 @@
default_app_config = 'dav_events.apps.AppConfig' default_app_config = 'dav_events.apps.AppConfig' # pylint: disable=invalid-name

View File

@@ -16,17 +16,17 @@ DEFAULT_SETTINGS = (
DefaultSetting('groups_publisher_facebook', []), DefaultSetting('groups_publisher_facebook', []),
DefaultSetting('groups_office', []), DefaultSetting('groups_office', []),
DefaultSetting('forms_development_init', False), DefaultSetting('forms_development_init', False),
DefaultSetting('form_initials', dict()), DefaultSetting('form_initials', {}),
DefaultSetting('matrix_config', ImproperlyConfigured), DefaultSetting('matrix_config', ImproperlyConfigured),
DefaultSetting('publish_before_begin_days', 10), DefaultSetting('publish_before_begin_days', 10),
DefaultSetting('publish_before_deadline_days', 7), DefaultSetting('publish_before_deadline_days', 7),
DefaultSetting('publish_issues', list()), DefaultSetting('publish_issues', []),
) )
class AppConfig(_AppConfig): class AppConfig(_AppConfig):
name = 'dav_events' name = 'dav_events'
verbose_name = u'DAV Touren & Kurse' verbose_name = 'DAV Touren & Kurse'
default_settings = DEFAULT_SETTINGS default_settings = DEFAULT_SETTINGS
def ready(self): def ready(self):

View File

@@ -1,13 +1,13 @@
import datetime import datetime
import logging import logging
import pytz
import re import re
import pytz
from six import string_types from six import string_types
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Iso8601Serializer(object): class Iso8601Serializer:
marker = 'ISO8601' marker = 'ISO8601'
separator = ':' separator = ':'

View File

@@ -5,4 +5,4 @@ from django.utils.translation import ugettext_lazy as _
class RegistrationResponseForm(forms.Form): class RegistrationResponseForm(forms.Form):
apply_reduced_fee = forms.BooleanField(required=False, apply_reduced_fee = forms.BooleanField(required=False,
label=_(u'Reduzierte Teilnahmegebühr')) label=_('Reduzierte Teilnahmegebühr'))

View File

@@ -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'),
),
]

View File

@@ -10,10 +10,9 @@ import unicodedata
from babel.dates import format_date from babel.dates import format_date
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model 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.db import models
from django.template.loader import get_template 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.utils.translation import get_language, ugettext_lazy as _
from django_countries.fields import Country, CountryField from django_countries.fields import Country, CountryField
@@ -27,7 +26,6 @@ from .eventchange import EventChange
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@python_2_unicode_compatible
class Event(models.Model): class Event(models.Model):
# Metadata # Metadata
owner = models.ForeignKey(settings.AUTH_USER_MODEL, owner = models.ForeignKey(settings.AUTH_USER_MODEL,

View File

@@ -3,7 +3,6 @@ from __future__ import unicode_literals
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from . import get_ghost_user, get_system_user from . import get_ghost_user, get_system_user
@@ -12,7 +11,6 @@ def get_system_user_id():
return get_system_user().id return get_system_user().id
@python_2_unicode_compatible
class EventChange(models.Model): class EventChange(models.Model):
UPDATE = 'update' UPDATE = 'update'
RAISE_FLAG = 'set_flag' RAISE_FLAG = 'set_flag'
@@ -23,7 +21,7 @@ class EventChange(models.Model):
(LOWER_FLAG, 'Lower Flag'), (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) timestamp = models.DateTimeField(default=timezone.now)
user = models.ForeignKey(settings.AUTH_USER_MODEL, user = models.ForeignKey(settings.AUTH_USER_MODEL,
default=get_system_user_id, default=get_system_user_id,

View File

@@ -2,7 +2,6 @@ from __future__ import unicode_literals
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from . import get_ghost_user, get_system_user from . import get_ghost_user, get_system_user
@@ -11,9 +10,8 @@ def get_system_user_id():
return get_system_user().id return get_system_user().id
@python_2_unicode_compatible
class EventFlag(models.Model): 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', status = models.ForeignKey('dav_events.EventStatus',
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='+') related_name='+')

View File

@@ -1,6 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from ..validators import IdStringValidator from ..validators import IdStringValidator
@@ -22,7 +21,6 @@ BOOTSTRAP_CONTEXT_CHOICES = (
) )
@python_2_unicode_compatible
class EventStatus(models.Model): class EventStatus(models.Model):
code = models.CharField(primary_key=True, max_length=254, validators=[IdStringValidator]) code = models.CharField(primary_key=True, max_length=254, validators=[IdStringValidator])
severity = models.IntegerField(unique=True) severity = models.IntegerField(unique=True)

View File

@@ -3,10 +3,9 @@ from __future__ import unicode_literals
import logging import logging
import uuid import uuid
from django.contrib.auth import get_user_model 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.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
from .event import Event from .event import Event
@@ -14,7 +13,6 @@ from .event import Event
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@python_2_unicode_compatible
class OneClickAction(models.Model): class OneClickAction(models.Model):
COMMANDS = ( COMMANDS = (
('EVENT_LIST', 'login and go to event list (user id)'), ('EVENT_LIST', 'login and go to event list (user id)'),

View File

@@ -4,7 +4,6 @@ import datetime
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from dav_base.validators import DAVNumberValidator from dav_base.validators import DAVNumberValidator
@@ -12,7 +11,6 @@ from dav_base.validators import DAVNumberValidator
midnight = datetime.time(00, 00, 00) midnight = datetime.time(00, 00, 00)
@python_2_unicode_compatible
class AbstractParticipant(models.Model): class AbstractParticipant(models.Model):
personal_names = models.CharField(max_length=1024, personal_names = models.CharField(max_length=1024,
verbose_name=_('Vorname(n)')) verbose_name=_('Vorname(n)'))
@@ -158,9 +156,8 @@ class AbstractParticipant(models.Model):
return timezone.make_aware(datetime.datetime.combine(purge_date, midnight)) return timezone.make_aware(datetime.datetime.combine(purge_date, midnight))
@python_2_unicode_compatible
class Participant(AbstractParticipant): 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) created_at = models.DateTimeField(auto_now_add=True)
position = models.IntegerField(verbose_name='Listennummer') position = models.IntegerField(verbose_name='Listennummer')

View File

@@ -1,15 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from ..participant import AbstractParticipant from ..participant import AbstractParticipant
@python_2_unicode_compatible
class TrashedParticipant(AbstractParticipant): 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() created_at = models.DateTimeField()
trashed_at = models.DateTimeField(auto_now_add=True) trashed_at = models.DateTimeField(auto_now_add=True)
position = models.IntegerField(verbose_name='Listennummer') position = models.IntegerField(verbose_name='Listennummer')

View File

@@ -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 %}) 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 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 Veröffentlichung beenden: {{ day_after|date:'d.m.Y' }} 00:00:00
Erstellungsdatum: {{ first_day|date:'d.m.Y' }} 00:00:00 Erstellungsdatum: {{ first_day|date:'d.m.Y' }} 00:00:00
Titel: {{ number }} - {{ title }}
{% if internal_note %} {% if internal_note %}
Bearbeitungshinweis: Bearbeitungshinweis:
==================== ====================

View File

@@ -26,7 +26,7 @@ Du wirst dann per E-Mail auf dem laufenden gehalten.
<p> <p>
Hier kannst du deine eingetragenen Touren und Kurse verwalten. Hier kannst du deine eingetragenen Touren und Kurse verwalten.
<span class="glyphicon glyphicon-question-sign" title=" <span class="glyphicon glyphicon-question-sign" title="
Als Tourenleiter kannst du hier die Anmeldungen und Teilnehmerlisten zu deinen Touren verwalten. Als Tourenleiter*in kannst du hier die Anmeldungen und Teilnehmerlisten zu deinen Touren verwalten.
Tourenreferenten und Redakteure können hier Veranstaltungen freigeben und Programmlisten herunterladen. Tourenreferenten und Redakteure können hier Veranstaltungen freigeben und Programmlisten herunterladen.
"></span> "></span>
</p> </p>

View File

@@ -14,9 +14,9 @@ register = template.Library()
@register.simple_tag @register.simple_tag
def render_event_status(event, show_void=True): def render_event_status(event, show_void=True):
label_html = u'<span class="label label-{context}">{label}</span> ' label_html = '<span class="label label-{context}">{label}</span> '
html = u'' html = ''
status_list = event.workflow.get_status_list() status_list = event.workflow.get_status_list()
if show_void and len(status_list) < 1: if show_void and len(status_list) < 1:
@@ -39,29 +39,29 @@ def render_event_status(event, show_void=True):
@register.simple_tag @register.simple_tag
def render_event_changelog(event): def render_event_changelog(event):
change_templ = u'<li class="list-group-item">\n' \ change_templ = '<li class="list-group-item">\n' \
u'\t<p class="list-group-item-heading">' \ '\t<p class="list-group-item-heading">' \
u'<span class="glyphicon glyphicon-{icon}"></span>' \ '<span class="glyphicon glyphicon-{icon}"></span>' \
u' {timestamp}' \ ' {timestamp}' \
u' - ' \ ' - ' \
u' {user}</p>\n' \ ' {user}</p>\n' \
u'\t{content}\n' \ '\t{content}\n' \
u'</li>\n' '</li>\n'
update_sub_templ = u'<li class="list-group-item">\n' \ update_sub_templ = '<li class="list-group-item">\n' \
u'\t{field}:{separator1}\n' \ '\t{field}:{separator1}\n' \
u'\t<span style="background-color: #ffe0e0;">{refer}</span>\n' \ '\t<span style="background-color: #ffe0e0;">{refer}</span>\n' \
u'\t{separator2}\n' \ '\t{separator2}\n' \
u'\t<span style="background-color: #e0ffe0;">{current}</span>\n' \ '\t<span style="background-color: #e0ffe0;">{current}</span>\n' \
u'</li>\n' '</li>\n'
raise_flag_templ = u'<span class="text-success glyphicon glyphicon-plus"></span>' \ raise_flag_templ = '<span class="text-success glyphicon glyphicon-plus"></span>' \
u' <span class="label label-{bcontext}">{label}</span>' \ ' <span class="label label-{bcontext}">{label}</span>' \
u' <span class="text-success glyphicon glyphicon-plus"></span>' ' <span class="text-success glyphicon glyphicon-plus"></span>'
lower_flag_templ = u'<span class="text-danger glyphicon glyphicon-minus"></span>' \ lower_flag_templ = '<span class="text-danger glyphicon glyphicon-minus"></span>' \
u' <del><span class="label label-{bcontext}">{label}</span></del>' \ ' <del><span class="label label-{bcontext}">{label}</span></del>' \
u' <span class="text-danger glyphicon glyphicon-minus"></span>' ' <span class="text-danger glyphicon glyphicon-minus"></span>'
if event.changes.exists(): if event.changes.exists():
html = u'<ul class="list-group">\n' html = '<ul class="list-group">\n'
for change in event.changes.all(): for change in event.changes.all():
@@ -70,8 +70,8 @@ def render_event_changelog(event):
username = change.user username = change.user
if change.operation == EventChange.UPDATE: if change.operation == EventChange.UPDATE:
icon = u'pencil' icon = 'pencil'
content_html = u'<ul class="list-group">' content_html = '<ul class="list-group">'
subchanges = json.loads(change.content) subchanges = json.loads(change.content)
for subchange in subchanges: for subchange in subchanges:
field_label = event._meta.get_field(subchange['field']).verbose_name field_label = event._meta.get_field(subchange['field']).verbose_name
@@ -80,33 +80,33 @@ def render_event_changelog(event):
except TypeError: except TypeError:
is_long_strings = False is_long_strings = False
if is_long_strings: if is_long_strings:
separator1 = u'<br />' separator1 = '<br />'
separator2 = u'<br />' separator2 = '<br />'
else: else:
separator1 = u' ' separator1 = ' '
separator2 = u' -&gt; ' separator2 = ' -&gt; '
content_html += format_html(update_sub_templ, content_html += format_html(update_sub_templ,
field=field_label, field=field_label,
separator1=mark_safe(separator1), separator1=mark_safe(separator1),
refer=subchange['refer'], refer=subchange['refer'],
separator2=mark_safe(separator2), separator2=mark_safe(separator2),
current=subchange['current']) current=subchange['current'])
content_html += u'</ul>' content_html += '</ul>'
elif change.operation == EventChange.RAISE_FLAG: elif change.operation == EventChange.RAISE_FLAG:
icon = u'flag' icon = 'flag'
status = get_or_create_event_status(change.content) status = get_or_create_event_status(change.content)
content_html = format_html(raise_flag_templ, content_html = format_html(raise_flag_templ,
bcontext=status.bootstrap_context or u'default', bcontext=status.bootstrap_context or 'default',
label=status.label) label=status.label)
elif change.operation == EventChange.LOWER_FLAG: elif change.operation == EventChange.LOWER_FLAG:
icon = u'flag' icon = 'flag'
status = get_or_create_event_status(change.content) status = get_or_create_event_status(change.content)
content_html = format_html(lower_flag_templ, content_html = format_html(lower_flag_templ,
bcontext=status.bootstrap_context or u'default', bcontext=status.bootstrap_context or 'default',
label=status.label) label=status.label)
else: else:
icon = u'question-sign' icon = 'question-sign'
content_html = format_html(u'{content}', content=change.content) content_html = format_html('{content}', content=change.content)
html += format_html(change_templ, html += format_html(change_templ,
icon=icon, icon=icon,
@@ -114,7 +114,7 @@ def render_event_changelog(event):
user=username, user=username,
content=mark_safe(content_html)) content=mark_safe(content_html))
html += u'</ul>\n' html += '</ul>\n'
else: else:
html = _(u'Keine Einträge') html = _('Keine Einträge')
return mark_safe(html) return mark_safe(html)

View File

@@ -6,13 +6,12 @@ import os
from django.apps import apps from django.apps import apps
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.db import models
from ..models.event import Event from ..models.event import Event
from ..models.eventstatus import EventStatus from ..models.eventstatus import EventStatus
class RoleMixin(object): class RoleMixin:
def create_user_for_role(self, role_name, password, first_name, last_name): def create_user_for_role(self, role_name, password, first_name, last_name):
group = Group(name=role_name) group = Group(name=role_name)
group.save() group.save()
@@ -31,7 +30,7 @@ class RoleMixin(object):
return user return user
class EventMixin(object): class EventMixin:
def get_status_label(self, status_code): def get_status_label(self, status_code):
return EventStatus.objects.get(code=status_code).label return EventStatus.objects.get(code=status_code).label

View File

@@ -1,4 +1,3 @@
import sys
import datetime import datetime
from unittest import TestCase from unittest import TestCase
@@ -10,7 +9,7 @@ class Iso8601SerializerTestCase(TestCase):
date = start_date date = start_date
while date <= end_date: while date <= end_date:
text = '%04d-%02d-%02d' % (date.year, date.month, date.day) text = '%04d-%02d-%02d' % (date.year, date.month, date.day)
yield (date, text) yield date, text
date += datetime.timedelta(step) date += datetime.timedelta(step)
def _gen_times(self, step_hours=1, step_minutes=1, step_seconds=1): def _gen_times(self, step_hours=1, step_minutes=1, step_seconds=1):

View File

@@ -83,7 +83,7 @@ Ausschreibung:
============== ==============
{event_text}""" {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. deine Veranstaltung wurde von {editor_full_name} freigegeben.
Die Redaktion wurde informiert, um deine Veranstaltung zu veröffentlichen. 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}) Veröffentlichung: ({planned_publication_date})
================= =================
Titel: {number} - {title}
Veröffentlichung starten: {planned_publication_date_short} 00:00:00 Veröffentlichung starten: {planned_publication_date_short} 00:00:00
Veröffentlichung beenden: {day_after_short} 00:00:00 Veröffentlichung beenden: {day_after_short} 00:00:00
Erstellungsdatum: {first_day_short} 00:00:00 Erstellungsdatum: {first_day_short} 00:00:00
Titel: {number} - {title}
Bearbeitungshinweis: Bearbeitungshinweis:
==================== ====================

View File

@@ -117,15 +117,12 @@ class ActionTestCase(EmailTestMixin, RoleMixin, EventMixin, TestCase):
' von %(user)s' ' von %(user)s'
' auf \'%(status)s\' gesetzt.') % { ' auf \'%(status)s\' gesetzt.') % {
'status': status_label, '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(), 'user': user.get_full_name(),
}) })
html = message.replace('\'', '&#39;') html = message.replace('\'', '&#x27;')
# Sometimes this test fail, and we cannot see the tested content, so we create our own Exception self.assertRegex(content, html)
try:
self.assertInHTML(html, content)
except AssertionError:
raise AssertionError('Not in HTML:\n{}\n-----\n{}\n'.format(html, content))
self.assertRegex(content, r'alert-success') self.assertRegex(content, r'alert-success')
def setUp(self): def setUp(self):

View File

@@ -158,4 +158,3 @@ class EventsTestCase(EventMixin, TestCase):
def test_event_create_view(self): def test_event_create_view(self):
for data in self.gen_create_event_input(): for data in self.gen_create_event_input():
self.create_event_by_view(data) self.create_event_by_view(data)

View File

@@ -2,6 +2,8 @@ from django.conf.urls import url
from . import views from . import views
app_name = 'dav_events'
urlpatterns = [ urlpatterns = [
url(r'^home$', views.base.HomeView.as_view(), name='root'), url(r'^home$', views.base.HomeView.as_view(), name='root'),
url(r'^$', views.events.EventListView.as_view(), name='list'), url(r'^$', views.events.EventListView.as_view(), name='list'),

View File

@@ -23,7 +23,7 @@ class OneClickActionRunView(generic.DetailView):
login(self.request, user) login(self.request, user)
logger.info('Logged in via OneClickAction: %s', user.username) logger.info('Logged in via OneClickAction: %s', user.username)
messages.success(self.request, messages.success(self.request,
_(u'Benutzer angemeldet: %(username)s') % {'username': user.username}) _('Benutzer angemeldet: %(username)s') % {'username': user.username})
if 'location' in result: if 'location' in result:
return HttpResponseRedirect(result['location']) return HttpResponseRedirect(result['location'])

View File

@@ -754,14 +754,14 @@ class EventCreateView(EventPermissionMixin, generic.FormView):
_(u'Du hast jemand anderen als Tourenleiter eingetragen.'), _(u'Du hast jemand anderen als Tourenleiter eingetragen.'),
_(u'Warum machst du sowas?') _(u'Warum machst du sowas?')
)) ))
elif owner.has_usable_password(): elif owner.last_login is None:
next_url = reverse('dav_events:list')
else:
login(self.request, owner) login(self.request, owner)
next_url = reverse('dav_auth:set_password') next_url = reverse('dav_auth:set_password')
messages.success(self.request, messages.success(self.request,
_(u'Neuen Benutzer angemeldet: %(username)s') % {'username': owner.username}) _(u'Neuen Benutzer angemeldet: %(username)s') % {'username': owner.username})
messages.warning(self.request, _(u'Bitte neues Passwort setzen!')) messages.warning(self.request, _(u'Bitte neues Passwort setzen!'))
else:
next_url = reverse('dav_events:list')
return HttpResponseRedirect(next_url) return HttpResponseRedirect(next_url)
def clean_session_data(self, session=None): def clean_session_data(self, session=None):

View File

@@ -1 +1 @@
default_app_config = 'dav_registration.apps.AppConfig' default_app_config = 'dav_registration.apps.AppConfig' # pylint: disable=invalid-name

View File

@@ -9,7 +9,7 @@ DEFAULT_SETTINGS = (
class AppConfig(_AppConfig): class AppConfig(_AppConfig):
name = 'dav_registration' name = 'dav_registration'
verbose_name = u'DAV Kurs-Anmeldungen' verbose_name = 'DAV Kurs-Anmeldungen'
default_settings = DEFAULT_SETTINGS default_settings = DEFAULT_SETTINGS
def ready(self): def ready(self):

View File

@@ -2,7 +2,7 @@
from dav_base.emails import AbstractMail from dav_base.emails import AbstractMail
class AbstractRegistrationMail(AbstractMail): class AbstractRegistrationMail(AbstractMail): # pylint: disable=too-few-public-methods
def __init__(self, recipient, registration): def __init__(self, recipient, registration):
self._recipient = recipient self._recipient = recipient
self._registration = registration self._registration = registration
@@ -13,45 +13,45 @@ class AbstractRegistrationMail(AbstractMail):
subject_fmt = self._subject subject_fmt = self._subject
if self._event.number: 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): def _get_recipients(self):
if hasattr(self._recipient, 'get_full_name') and hasattr(self._recipient, 'email'): if hasattr(self._recipient, 'get_full_name') and hasattr(self._recipient, 'email'):
r = u'"{fullname}" <{email}>'.format(fullname=self._recipient.get_full_name(), r = '"{fullname}" <{email}>'.format(fullname=self._recipient.get_full_name(),
email=self._recipient.email) email=self._recipient.email)
else: else:
r = self._recipient r = self._recipient
return [r] return [r]
def _get_context_data(self, extra_context=None): 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['recipient'] = self._recipient
context['registration'] = self._registration context['registration'] = self._registration
context['event'] = self._event context['event'] = self._event
return context return context
class InformTrainerRegistrationMail(AbstractRegistrationMail): class InformTrainerRegistrationMail(AbstractRegistrationMail): # pylint: disable=too-few-public-methods
_subject = u'Anmeldung {full_name}' _subject = 'Anmeldung {full_name}'
_template_name = 'dav_registration/emails/inform_trainer.txt' _template_name = 'dav_registration/emails/inform_trainer.txt'
def _get_subject(self, subject_fmt=None, **kwargs): def _get_subject(self, subject_fmt=None, **kwargs):
kwargs['full_name'] = self._registration.get_full_name() 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): def _get_reply_to(self):
s = u'"{fullname}" <{email}>'.format(fullname=self._registration.get_full_name(), reply_to = '"{fullname}" <{email}>'.format(fullname=self._registration.get_full_name(),
email=self._registration.email_address) email=self._registration.email_address)
return [s] return [reply_to]
class InformSelfRegistrationMail(AbstractRegistrationMail): class InformSelfRegistrationMail(AbstractRegistrationMail): # pylint: disable=too-few-public-methods
_subject = u'Deine Anmeldung' _subject = 'Deine Anmeldung'
_template_name = 'dav_registration/emails/inform_self.txt' _template_name = 'dav_registration/emails/inform_self.txt'
def _get_reply_to(self): def _get_reply_to(self):
s = u'"{fullname}" <{email}>'.format(fullname=self._event.owner.get_full_name(), reply_to = '"{fullname}" <{email}>'.format(fullname=self._event.owner.get_full_name(),
email=self._event.owner.email) email=self._event.owner.email)
return [s] return [reply_to]

View File

@@ -11,13 +11,13 @@ logger = logging.getLogger(__name__)
class RegistrationForm(forms.ModelForm): class RegistrationForm(forms.ModelForm):
not_dav_member = forms.BooleanField(required=False, not_dav_member = forms.BooleanField(required=False,
label=_(u'Ich bin noch kein DAV Mitglied.'), label=_('Ich bin noch kein DAV Mitglied.'),
help_text=u'%s<br />\n%s' % ( help_text='%s<br />\n%s' % (
_(u'Wenn du noch kein DAV Mitglied bist,' _('Wenn du noch kein DAV Mitglied bist,'
u' oder deine Aufnahme noch in Arbeit ist,' ' oder deine Aufnahme noch in Arbeit ist,'
u' kreuze dieses Feld hier an.'), ' kreuze dieses Feld hier an.'),
_(u'Spätestens zu Veranstaltungsbeginn muss' _('Spätestens zu Veranstaltungsbeginn muss'
u' jedoch eine Mitgliedschaft bestehen.') ' jedoch eine Mitgliedschaft bestehen.')
)) ))
class Meta: class Meta:
@@ -30,7 +30,7 @@ class RegistrationForm(forms.ModelForm):
'note': forms.Textarea(attrs={'rows': 5}), 'note': forms.Textarea(attrs={'rows': 5}),
} }
labels = { 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): def clean_year_of_birth(self):
@@ -40,16 +40,16 @@ class RegistrationForm(forms.ModelForm):
val = self.cleaned_data.get('year_of_birth') val = self.cleaned_data.get('year_of_birth')
if val > year_now: if val > year_now:
raise forms.ValidationError( raise forms.ValidationError(
ugettext(u'Dein Geburtsjahr liegt in der Zukunft?' ugettext('Dein Geburtsjahr liegt in der Zukunft?'
u' Das finden wir gut,' ' Das finden wir gut,'
u' aber bitte melde dich besser mal per E-Mail bei uns.'), ' aber bitte melde dich besser mal per E-Mail bei uns.'),
code='to_young', code='to_young',
) )
elif val < (year_now - max_age): elif val < (year_now - max_age):
raise forms.ValidationError( raise forms.ValidationError(
ugettext(u'Du bist schon über %(max_age)d Jahre alt?' ugettext('Du bist schon über %(max_age)d Jahre alt?'
u' Das finden wir gut,' ' Das finden wir gut,'
u' aber bitte melde dich besser mal per E-Mail bei uns.'), ' aber bitte melde dich besser mal per E-Mail bei uns.'),
params={'max_age': max_age}, params={'max_age': max_age},
code='to_old', code='to_old',
) )
@@ -63,8 +63,8 @@ class RegistrationForm(forms.ModelForm):
need_experience = False need_experience = False
if need_experience: if need_experience:
raise forms.ValidationError( raise forms.ValidationError(
ugettext(u'Die Tourenleiter*innen brauchen ein paar Angaben,' ugettext('Die Tourenleiter*innen brauchen ein paar Angaben,'
u' was du bereits kannst oder wie fit du bist.'), ' was du bereits kannst oder wie fit du bist.'),
code='need_experience', code='need_experience',
) )
return val return val
@@ -73,18 +73,18 @@ class RegistrationForm(forms.ModelForm):
val = self.cleaned_data.get('privacy_policy_accepted') val = self.cleaned_data.get('privacy_policy_accepted')
if not val and self.instance.privacy_policy: if not val and self.instance.privacy_policy:
raise forms.ValidationError( raise forms.ValidationError(
ugettext(u'Wir müssen deine Daten leider speichern können,' ugettext('Wir müssen deine Daten leider speichern können,'
u' damit wir wissen, dass du teilnehmen möchtest.'), ' damit wir wissen, dass du teilnehmen möchtest.'),
code='privacy_policy_not_accepted', code='privacy_policy_not_accepted',
) )
return val return val
def clean(self): def clean(self):
super(RegistrationForm, self).clean() super().clean()
dav_member = self.cleaned_data.get('dav_member') dav_member = self.cleaned_data.get('dav_member')
dav_number = self.cleaned_data.get('dav_number') dav_number = self.cleaned_data.get('dav_number')
if dav_member and not 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) self.add_error('not_dav_member', error_msg)
raise forms.ValidationError(error_msg, code='dav_number_missing') raise forms.ValidationError(error_msg, code='dav_number_missing')
return self.cleaned_data return self.cleaned_data

View File

@@ -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'),
),
]

View File

@@ -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'),
),
]

View File

@@ -6,7 +6,6 @@ from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from dav_base.validators import DAVNumberValidator from dav_base.validators import DAVNumberValidator
@@ -19,9 +18,8 @@ logger = logging.getLogger(__name__)
midnight = datetime.time(00, 00, 00) midnight = datetime.time(00, 00, 00)
@python_2_unicode_compatible
class Registration(models.Model): 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) created_at = models.DateTimeField(auto_now_add=True)
personal_names = models.CharField(max_length=1024, personal_names = models.CharField(max_length=1024,
@@ -184,7 +182,7 @@ Anmerkung:
self.purge_at = self.__class__.calc_purge_at(self.event) self.purge_at = self.__class__.calc_purge_at(self.event)
self.full_clean() self.full_clean()
super(Registration, self).save(**kwargs) super().save(**kwargs)
if creating: if creating:
status = RegistrationStatus(registration=self) status = RegistrationStatus(registration=self)
@@ -219,12 +217,11 @@ Anmerkung:
return timezone.make_aware(datetime.datetime.combine(purge_date, midnight)) return timezone.make_aware(datetime.datetime.combine(purge_date, midnight))
@python_2_unicode_compatible
class RegistrationStatus(models.Model): class RegistrationStatus(models.Model):
registration = models.OneToOneField(Registration, on_delete=models.CASCADE, related_name='status') registration = models.OneToOneField(Registration, on_delete=models.CASCADE, related_name='status')
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
answered = models.BooleanField(_('Durch Tourleitung beantwortet'), default=False) answered = models.BooleanField(_('Durch Tourleitung beantwortet'), default=False)
accepted = models.NullBooleanField(_('Zusage erteilt')) accepted = models.BooleanField(_('Zusage erteilt'), null=True, blank=True)
class Meta: class Meta:
verbose_name = _('Anmeldungsstatus') verbose_name = _('Anmeldungsstatus')
@@ -240,7 +237,7 @@ class RegistrationStatus(models.Model):
def save(self, **kwargs): def save(self, **kwargs):
self.full_clean() self.full_clean()
super(RegistrationStatus, self).save(**kwargs) super().save(**kwargs)
def set_accepted(self): def set_accepted(self):
self.accepted = True self.accepted = True

View File

@@ -5,7 +5,7 @@ from . import emails
registration_created = Signal(providing_args=['registration']) 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') registration = kwargs.get('registration')
# Inform the event owner (trainer) # Inform the event owner (trainer)
@@ -14,7 +14,7 @@ def send_emails_on_registration(sender, **kwargs):
email.send() email.send()
# Inform the potential participant # Inform the potential participant
recipient = u'"{fullname}" <{email}>'.format(fullname=registration.get_full_name(), recipient = '"{fullname}" <{email}>'.format(fullname=registration.get_full_name(),
email=registration.email_address) email=registration.email_address)
email = emails.InformSelfRegistrationMail(recipient=recipient, registration=registration) email = emails.InformSelfRegistrationMail(recipient=recipient, registration=registration)
email.send() email.send()

View File

@@ -12,5 +12,3 @@ def render_event_paragraphs(event):
@register.simple_tag @register.simple_tag
def render_event_facts(event): def render_event_facts(event):
return render_to_string('dav_registration/event/facts.html', {'event': event}) return render_to_string('dav_registration/event/facts.html', {'event': event})

View File

@@ -4,7 +4,7 @@ from ..models import Registration
THIS_YEAR = timezone.now().year THIS_YEAR = timezone.now().year
class RegistrationMixin(object): class RegistrationMixin: # pylint: disable=too-few-public-methods
def create_registration(self, data): def create_registration(self, data):
r = Registration(**data) r = Registration(**data)
r.save() r.save()

View File

@@ -95,7 +95,7 @@ Zeitpunkt der Datenlöschung: {purge_at}
class EmailsTestCase(EmailTestMixin, EventMixin, RegistrationMixin, TestCase): class EmailsTestCase(EmailTestMixin, EventMixin, RegistrationMixin, TestCase):
def setUp(self): def setUp(self):
super(EmailsTestCase, self).setUp() super().setUp()
app_config = apps.get_app_config('dav_events') app_config = apps.get_app_config('dav_events')
app_config.settings.enable_email_on_status_update = False app_config.settings.enable_email_on_status_update = False

View File

@@ -11,7 +11,7 @@ from .generic import THIS_YEAR, RegistrationMixin
class RegistrationTestCase(EventMixin, RegistrationMixin, TestCase): class RegistrationTestCase(EventMixin, RegistrationMixin, TestCase):
def setUp(self): def setUp(self):
super(RegistrationTestCase, self).setUp() super().setUp()
app_config = apps.get_app_config('dav_events') app_config = apps.get_app_config('dav_events')
app_config.settings.enable_email_on_status_update = False app_config.settings.enable_email_on_status_update = False
@@ -100,4 +100,3 @@ class RegistrationTestCase(EventMixin, RegistrationMixin, TestCase):
'dav_member': False, 'dav_member': False,
} }
self.create_registration(registration_data) self.create_registration(registration_data)

View File

@@ -51,7 +51,7 @@ class UtilsTestCase(RegistrationMixin, EventMixin, TestCase):
self.submit_event(event) self.submit_event(event)
self.accept_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 = registration_data
d['event'] = event d['event'] = event
self.create_registration(d) self.create_registration(d)

View File

@@ -2,6 +2,8 @@ from django.conf.urls import url
from . import views from . import views
app_name = 'dav_registration'
urlpatterns = [ urlpatterns = [
url(r'^$', views.RootView.as_view(), name='root'), url(r'^$', views.RootView.as_view(), name='root'),
url(r'^finished', views.RegistrationSuccessView.as_view(), name='registered'), url(r'^finished', views.RegistrationSuccessView.as_view(), name='registered'),

View File

@@ -23,7 +23,7 @@ class RootView(generic.RedirectView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
purge_registrations() purge_registrations()
return super(RootView, self).get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
class EventListView(generic.ListView): class EventListView(generic.ListView):
@@ -33,14 +33,14 @@ class EventListView(generic.ListView):
def get_queryset(self): def get_queryset(self):
today = datetime.date.today() today = datetime.date.today()
filter = Q(flags__status__code__in=('publishing', 'publishing_web', 'publishing_facebook', filter_exp = Q(flags__status__code__in=('publishing', 'publishing_web', 'publishing_facebook',
'published', 'published_web', 'published_facebook')) 'published', 'published_web', 'published_facebook'))
filter &= Q(planned_publication_date__isnull=True) | Q(planned_publication_date__lte=today) filter_exp &= Q(planned_publication_date__isnull=True) | Q(planned_publication_date__lte=today)
filter &= Q(first_day__gte=today) filter_exp &= Q(first_day__gte=today)
# filter &= Q(registration_closed=False) # filter_exp &= Q(registration_closed=False)
# filter &= Q(deadline__isnull=True) | Q(deadline__gte=today) # 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 return qs
@@ -52,14 +52,14 @@ class EventDetailView(generic.DetailView):
def get_queryset(self): def get_queryset(self):
today = datetime.date.today() today = datetime.date.today()
filter = Q(flags__status__code__in=('publishing', 'publishing_web', 'publishing_facebook', filter_exp = Q(flags__status__code__in=('publishing', 'publishing_web', 'publishing_facebook',
'published', 'published_web', 'published_facebook')) 'published', 'published_web', 'published_facebook'))
filter &= Q(planned_publication_date__isnull=True) | Q(planned_publication_date__lte=today) filter_exp &= Q(planned_publication_date__isnull=True) | Q(planned_publication_date__lte=today)
filter &= Q(first_day__gte=today) filter_exp &= Q(first_day__gte=today)
# filter &= Q(registration_closed=False) # filter_exp &= Q(registration_closed=False)
# filter &= Q(deadline__isnull=True) | Q(deadline__gte=today) # 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 return qs
@@ -74,72 +74,72 @@ class RegistrationView(generic.CreateView):
def get_queryset(self): def get_queryset(self):
today = datetime.date.today() today = datetime.date.today()
filter = Q(flags__status__code__in=('publishing', 'publishing_web', 'publishing_facebook', filter_exp = Q(flags__status__code__in=('publishing', 'publishing_web', 'publishing_facebook',
'published', 'published_web', 'published_facebook')) 'published', 'published_web', 'published_facebook'))
filter &= Q(planned_publication_date__isnull=True) | Q(planned_publication_date__lte=today) filter_exp &= Q(planned_publication_date__isnull=True) | Q(planned_publication_date__lte=today)
filter &= Q(first_day__gte=today) filter_exp &= Q(first_day__gte=today)
filter &= Q(registration_closed=False) filter_exp &= Q(registration_closed=False)
filter &= Q(deadline__isnull=True) | Q(deadline__gte=today) 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 return qs
def get_initial(self): def get_initial(self):
initials = super(RegistrationView, self).get_initial() initials = super().get_initial()
return initials return initials
def get_form(self, form_class=None): 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() event = self.get_object()
experience_label = form.fields['experience'].label experience_label = form.fields['experience'].label
experience_help_text = form.fields['experience'].help_text experience_help_text = form.fields['experience'].help_text
if event.sport == 'B': if event.sport == 'B':
experience_label = _(u'Eigene Bergerfahrung') experience_label = _('Eigene Bergerfahrung')
elif event.sport == 'K' and event.terrain == 'alpine': 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': if event.level == 'beginner':
experience_help_text = u'%s<br /> %s<br /> %s' % ( experience_help_text = '%s<br /> %s<br /> %s' % (
_(u'Warst du schon mal im Gebirge klettern?'), _('Warst du schon mal im Gebirge klettern?'),
_(u'In welchem Schwierigkeitsgrad hast du Spaß?'), _('In welchem Schwierigkeitsgrad hast du Spaß?'),
_(u'Bist du schon mal vorgestiegen?') _('Bist du schon mal vorgestiegen?')
) )
else: else:
experience_help_text = u'%s<br /> %s<br /> %s' % ( experience_help_text = '%s<br /> %s<br /> %s' % (
_(u'In welchen Klettergebieten/Touren warst du bisher unterwegs?'), _('In welchen Klettergebieten/Touren warst du bisher unterwegs?'),
_(u'In welchem Schwierigkeitsgrad hast du im Vorstieg Spaß?'), _('In welchem Schwierigkeitsgrad hast du im Vorstieg Spaß?'),
_(u'Wie waren die Touren abgesichert, in denen du dich noch wohlgefühlt hast?') _('Wie waren die Touren abgesichert, in denen du dich noch wohlgefühlt hast?')
) )
elif event.sport == 'K': elif event.sport == 'K':
experience_label = _(u'Eigene Klettererfahrung') experience_label = _('Eigene Klettererfahrung')
if event.level == 'beginner': if event.level == 'beginner':
experience_help_text = u'%s<br /> %s<br /> %s' % ( experience_help_text = '%s<br /> %s<br /> %s' % (
_(u'Warst du schon mal am Fels klettern?'), _('Warst du schon mal am Fels klettern?'),
_(u'In welchem Schwierigkeitsgrad hast du Spaß?'), _('In welchem Schwierigkeitsgrad hast du Spaß?'),
_(u'Bist du schon mal vorgestiegen?') _('Bist du schon mal vorgestiegen?')
) )
else: else:
experience_help_text = u'%s<br /> %s<br /> %s' % ( experience_help_text = '%s<br /> %s<br /> %s' % (
_(u'In welchen Klettergebieten warst du bisher unterwegs?'), _('In welchen Klettergebieten warst du bisher unterwegs?'),
_(u'In welchem Schwierigkeitsgrad hast du im Vorstieg Spaß?'), _('In welchem Schwierigkeitsgrad hast du im Vorstieg Spaß?'),
_(u'Wie waren die Touren abgesichert, in denen du dich noch wohlgefühlt hast?') _('Wie waren die Touren abgesichert, in denen du dich noch wohlgefühlt hast?')
) )
elif event.sport == 'M': elif event.sport == 'M':
experience_label = _(u'Eigene MTB-Erfahrung') experience_label = _('Eigene MTB-Erfahrung')
experience_help_text = u'%s' % ( experience_help_text = '%s' % (
_(u'Was für Touren bist du schon gefahren?') _('Was für Touren bist du schon gefahren?')
) )
if event.level != 'beginner': if event.level != 'beginner':
experience_help_text += u'<br /> %s' % ( experience_help_text += '<br /> %s' % (
_(u'Single-Trail-Schwierigkeit (S0-S5) bei der du dich wohl fühlst?') _('Single-Trail-Schwierigkeit (S0-S5) bei der du dich wohl fühlst?')
) )
elif event.sport == 'S': elif event.sport == 'S':
experience_label = _(u'Eigene Skitouren- und Bergerfahrung') experience_label = _('Eigene Skitouren- und Bergerfahrung')
elif event.sport == 'W': elif event.sport == 'W':
experience_help_text += u'<br /> %s' % ( experience_help_text += '<br /> %s' % (
_(u'Kann frei gelassen werden.') _('Kann frei gelassen werden.')
) )
form.fields['experience'].label = experience_label form.fields['experience'].label = experience_label
@@ -150,7 +150,7 @@ class RegistrationView(generic.CreateView):
return form return form
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(RegistrationView, self).get_context_data(**kwargs) context = super().get_context_data(**kwargs)
event = self.get_object() event = self.get_object()
context['event'] = event context['event'] = event
context['privacy_policy'] = app_config.settings.privacy_policy context['privacy_policy'] = app_config.settings.privacy_policy
@@ -158,16 +158,16 @@ class RegistrationView(generic.CreateView):
return context return context
def form_valid(self, form): 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 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) messages.success(self.request, message)
return r return r
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if 'registration_id' in request.session: if 'registration_id' in request.session:
del request.session['registration_id'] del request.session['registration_id']
return super(RegistrationView, self).post(request, *args, **kwargs) return super().post(request, *args, **kwargs)
class RegistrationSuccessView(generic.DetailView): class RegistrationSuccessView(generic.DetailView):

View File

@@ -1 +1 @@
default_app_config = 'dav_submission.apps.AppConfig' default_app_config = 'dav_submission.apps.AppConfig' # pylint: disable=invalid-name

View File

@@ -19,5 +19,5 @@ DEFAULT_SETTINGS = (
class AppConfig(_AppConfig): class AppConfig(_AppConfig):
name = 'dav_submission' name = 'dav_submission'
verbose_name = u'DAV Beitragsupload (150 Jahre DAV)' verbose_name = 'DAV Beitragsupload (150 Jahre DAV)'
default_settings = DEFAULT_SETTINGS default_settings = DEFAULT_SETTINGS

View File

@@ -6,8 +6,8 @@ from dav_base.emails import AbstractMail
app_config = apps.get_containing_app_config(__package__) app_config = apps.get_containing_app_config(__package__)
class NewSubmissionMail(AbstractMail): class NewSubmissionMail(AbstractMail): # pylint: disable=too-few-public-methods
_subject = u'Neuer Beitrag: {title}' _subject = 'Neuer Beitrag: {title}'
_template_name = 'dav_submission/emails/new_submission.txt' _template_name = 'dav_submission/emails/new_submission.txt'
def __init__(self, metadata): def __init__(self, metadata):
@@ -15,18 +15,18 @@ class NewSubmissionMail(AbstractMail):
def _get_subject(self, subject_fmt=None, **kwargs): def _get_subject(self, subject_fmt=None, **kwargs):
kwargs['title'] = self._metadata['title'] 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): def _get_reply_to(self):
s = u'"{fullname}" <{email}>'.format(fullname=self._metadata['name'], reply_to = '"{fullname}" <{email}>'.format(fullname=self._metadata['name'],
email=self._metadata['email_address']) email=self._metadata['email_address'])
return [s] return [reply_to]
def _get_recipients(self): def _get_recipients(self):
r = app_config.settings.notify_address r = app_config.settings.notify_address
return [r] return [r]
def _get_context_data(self, extra_context=None): 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 context['metadata'] = self._metadata
return context return context

View File

@@ -8,59 +8,59 @@ app_config = apps.get_containing_app_config(__package__)
class UploadForm(forms.Form): class UploadForm(forms.Form):
name = forms.CharField(max_length=1024, name = forms.CharField(max_length=1024,
label=_(u'Dein Name'), label=_('Dein Name'),
help_text=_(u'Wenn wir wissen, wie du heißt, wird uns das echt weiter helfen')) help_text=_('Wenn wir wissen, wie du heißt, wird uns das echt weiter helfen'))
email_address = forms.EmailField(label=_(u'Deine E-Mail-Adresse'), email_address = forms.EmailField(label=_('Deine E-Mail-Adresse'),
help_text=_(u'Damit wir dich für Rückfragen kontaktieren können')) help_text=_('Damit wir dich für Rückfragen kontaktieren können'))
group = forms.CharField(max_length=1024, group = forms.CharField(max_length=1024,
required=False, required=False,
label=_(u'DAV Gruppe'), label=_('DAV Gruppe'),
help_text=_(u'Optional, falls du aktiv in einer der Sektionsgruppen bist')) help_text=_('Optional, falls du aktiv in einer der Sektionsgruppen bist'))
title = forms.CharField(max_length=60, title = forms.CharField(max_length=60,
label=_(u'Titel deines Beitrags / Stichwort'), label=_('Titel deines Beitrags / Stichwort'),
help_text=u'%s<br />\n%s' % ( help_text='%s<br />\n%s' % (
_(u'Kommt zum Bild, falls es veröffentlicht wird'), _('Kommt zum Bild, falls es veröffentlicht wird'),
_(u'Maximal 60 Zeichen') _('Maximal 60 Zeichen')
)) ))
description = forms.CharField(max_length=300, description = forms.CharField(max_length=300,
label=_(u'Beschreibung'), label=_('Beschreibung'),
help_text=u'%s<br />\n%s' % ( help_text='%s<br />\n%s' % (
_(u'Wo warst du? Was hast du gemacht? Worum ging es bei der Aktion?'), _('Wo warst du? Was hast du gemacht? Worum ging es bei der Aktion?'),
_(u'Maximal 300 Zeichen') _('Maximal 300 Zeichen')
), ),
widget=forms.Textarea(attrs={'rows': 2})) widget=forms.Textarea(attrs={'rows': 2}))
files = forms.FileField(label=_(u'Dateien'), files = forms.FileField(label=_('Dateien'),
help_text=_(u'Wenn du auf den Button klickst, kannst du mehrere Dateien auswählen' help_text=_('Wenn du auf den Button klickst, kannst du mehrere Dateien auswählen'
u' (nötigenfalls Strg- oder Command-Taste benutzen)'), ' (nötigenfalls Strg- oder Command-Taste benutzen)'),
widget=forms.ClearableFileInput(attrs={'multiple': True})) widget=forms.ClearableFileInput(attrs={'multiple': True}))
accepted = forms.BooleanField(required=False, 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): def __init__(self, *args, **kwargs):
super(UploadForm, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['title'].widget.attrs['placeholder'] = \ self.fields['title'].widget.attrs['placeholder'] = \
u'z.B. Nacktbesteigung der Nose' \ 'z.B. Nacktbesteigung der Nose' \
u' oder Juma jümart Jung-Mann-Weg'[:self.fields['title'].max_length] ' oder Juma jümart Jung-Mann-Weg'[:self.fields['title'].max_length]
self.fields['group'].widget.attrs['placeholder'] = \ 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 help_text = self.fields['files'].help_text
if app_config.settings.max_files: if app_config.settings.max_files:
help_text += u'<br />\n%s' % (ugettext(u'Wähle bis zu %d Dateien aus') help_text += '<br />\n%s' % (ugettext('Wähle bis zu %d Dateien aus')
% app_config.settings.max_files) % app_config.settings.max_files)
if app_config.settings.max_file_size_mib: if app_config.settings.max_file_size_mib:
help_text += u'<br />\n%s' % (ugettext(u'Einzelne Dateien dürfen maximal %d MiB groß sein') help_text += '<br />\n%s' % (ugettext('Einzelne Dateien dürfen maximal %d MiB groß sein')
% app_config.settings.max_file_size_mib) % app_config.settings.max_file_size_mib)
if app_config.settings.max_total_size_mib: if app_config.settings.max_total_size_mib:
help_text += u'<br />\n%s' % (ugettext(u'Alle Dateien zusammen dürfen maximal %d MiB groß sein') help_text += '<br />\n%s' % (ugettext('Alle Dateien zusammen dürfen maximal %d MiB groß sein')
% app_config.settings.max_total_size_mib) % app_config.settings.max_total_size_mib)
self.fields['files'].help_text = help_text self.fields['files'].help_text = help_text
def clean_files(self): def clean_files(self):
@@ -77,35 +77,35 @@ class UploadForm(forms.Form):
n_files = 0 n_files = 0
for file in files: for file in files:
if file.name in not_allowed_file_names: if file.name in not_allowed_file_names:
ve = forms.ValidationError( e = forms.ValidationError(
ugettext(u'Dateiname nicht erlaubt: %s') % file.name, ugettext('Dateiname nicht erlaubt: %s') % file.name,
code='filename_not_allowed', code='filename_not_allowed',
) )
validation_errors.append(ve) validation_errors.append(e)
if max_file_size and file.size > max_file_size: if max_file_size and file.size > max_file_size:
ve = forms.ValidationError( e = forms.ValidationError(
ugettext(u'Die Datei ist insgesamt zu groß:' ugettext('Die Datei ist insgesamt zu groß:'
u' %(name)s (> %(max_mib)s MiB)') % {'name': file.name, 'max_mib': max_file_size_mib}, ' %(name)s (> %(max_mib)s MiB)') % {'name': file.name, 'max_mib': max_file_size_mib},
code='file_to_big', code='file_to_big',
) )
validation_errors.append(ve) validation_errors.append(e)
size_total += file.size size_total += file.size
n_files += 1 n_files += 1
max_total_size = max_total_size_mib * 1024 * 1024 max_total_size = max_total_size_mib * 1024 * 1024
if max_total_size and size_total > max_total_size: if max_total_size and size_total > max_total_size:
ve = forms.ValidationError( e = forms.ValidationError(
ugettext(u'Dein Beitrag ist zu groß (%s MiB)') % max_total_size_mib, ugettext('Dein Beitrag ist zu groß (%s MiB)') % max_total_size_mib,
code='files_to_big', code='files_to_big',
) )
validation_errors.append(ve) validation_errors.append(e)
if max_files and n_files > max_files: if max_files and n_files > max_files:
ve = forms.ValidationError( e = forms.ValidationError(
ugettext(u'Dein Beitrag enthält mehr als %d Dateien') % max_files, ugettext('Dein Beitrag enthält mehr als %d Dateien') % max_files,
code='files_to_big', code='files_to_big',
) )
validation_errors.append(ve) validation_errors.append(e)
if validation_errors: if validation_errors:
raise forms.ValidationError(validation_errors) raise forms.ValidationError(validation_errors)
@@ -116,9 +116,9 @@ class UploadForm(forms.Form):
val = self.cleaned_data.get('accepted') val = self.cleaned_data.get('accepted')
if not val: if not val:
raise forms.ValidationError( raise forms.ValidationError(
ugettext(u'Um deinen Beitrag hochladen zu können,' ugettext('Um deinen Beitrag hochladen zu können,'
u' musst du den Teilnahmebedingungen zustimmen.' ' musst du den Teilnahmebedingungen zustimmen.'
u' Dazu musst du das Kästchen ankreuzen!'), ' Dazu musst du das Kästchen ankreuzen!'),
code='not_accepted', code='not_accepted',
) )
return val return val

View File

@@ -2,6 +2,8 @@ from django.conf.urls import url
from . import views from . import views
app_name = 'dav_submission'
urlpatterns = [ urlpatterns = [
url(r'^$', views.UploadView.as_view(), name='root'), url(r'^$', views.UploadView.as_view(), name='root'),
url(r'^danke', views.SuccessView.as_view(), name='success'), url(r'^danke', views.SuccessView.as_view(), name='success'),

View File

@@ -3,10 +3,10 @@ import codecs
import datetime import datetime
import logging import logging
import os import os
import pytz
import re import re
import urllib import urllib
import zipfile import zipfile
import pytz
from django.apps import apps from django.apps import apps
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
@@ -90,7 +90,7 @@ class ListView(generic.ListView):
permission_group = app_config.settings.download_group permission_group = app_config.settings.download_group
if not request.user.groups.filter(name=permission_group).exists(): if not request.user.groups.filter(name=permission_group).exists():
raise PermissionDenied() raise PermissionDenied()
return super(ListView, self).dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
class DownloadView(generic.DetailView): class DownloadView(generic.DetailView):
@@ -139,7 +139,7 @@ class DownloadView(generic.DetailView):
permission_group = app_config.settings.download_group permission_group = app_config.settings.download_group
if not request.user.groups.filter(name=permission_group).exists(): if not request.user.groups.filter(name=permission_group).exists():
raise PermissionDenied() raise PermissionDenied()
return super(DownloadView, self).dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
class UploadView(generic.edit.FormView): class UploadView(generic.edit.FormView):
@@ -155,30 +155,30 @@ class UploadView(generic.edit.FormView):
form_class = UploadForm form_class = UploadForm
success_url = reverse_lazy('dav_submission:success') success_url = reverse_lazy('dav_submission:success')
def _sanitize_filename(self, input): def _sanitize_filename(self, filename):
max_length = None max_length = None
discard_chars = u'' discard_chars = ''
replace_chars = { replace_chars = {
u'ä': u'ae', 'ä': 'ae',
u'ö': u'oe', 'ö': 'oe',
u'ü': u'ue', 'ü': 'ue',
u'ß': u'ss', 'ß': 'ss',
u'Ä': u'Ae', 'Ä': 'Ae',
u'Ö': u'Oe', 'Ö': 'Oe',
u'Ü': u'Ue', 'Ü': 'Ue',
} }
allowed_chars = (u'abcdefghijklmnopqrstuvwxyz' allowed_chars = ('abcdefghijklmnopqrstuvwxyz'
u'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
u'0123456789' '0123456789'
u'._-') '._-')
non_allowed_substitute = u'_' non_allowed_substitute = '_'
space_substitute = u'_' space_substitute = '_'
if space_substitute is None: if space_substitute is None:
space_substitute = non_allowed_substitute space_substitute = non_allowed_substitute
r = '' r = ''
for c in input: for c in filename:
if c in discard_chars: if c in discard_chars:
continue continue
elif c in replace_chars: elif c in replace_chars:
@@ -195,14 +195,14 @@ class UploadView(generic.edit.FormView):
return r[:max_length] return r[:max_length]
def get_context_data(self, **kwargs): 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 c['show_upload_form'] = app_config.settings.enable_upload
return c return c
def form_valid(self, form): def form_valid(self, form):
base_path = app_config.settings.upload_path base_path = app_config.settings.upload_path
subdir_format_str = u'{datetime}--{title}' subdir_format_str = '{datetime}--{title}'
now = timezone.now() now = timezone.now()
subdir_format_kwargs = {'datetime': now.strftime('%Y-%m-%d--%H%M%S'), subdir_format_kwargs = {'datetime': now.strftime('%Y-%m-%d--%H%M%S'),
'date': now.strftime('%Y-%m-%d'), 'date': now.strftime('%Y-%m-%d'),
@@ -214,7 +214,7 @@ class UploadView(generic.edit.FormView):
subdir_path = os.path.join(base_path, subdir_name) subdir_path = os.path.join(base_path, subdir_name)
if os.path.isdir(subdir_path): 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) messages.error(self.request, message)
form.add_error('title', message) form.add_error('title', message)
return self.render_to_response(self.get_context_data(form=form)) return self.render_to_response(self.get_context_data(form=form))
@@ -222,7 +222,7 @@ class UploadView(generic.edit.FormView):
os.makedirs(subdir_path) os.makedirs(subdir_path)
try: try:
metadata_format_str = u"""Absender: {name} <{email_address}> metadata_format_str = """Absender: {name} <{email_address}>
Gruppe: {group} Gruppe: {group}
Datum: {date} {time} Datum: {date} {time}
Titel: {title} Titel: {title}
@@ -246,10 +246,10 @@ Beschreibung:
with codecs.open(metadata_file_path, 'w', encoding='utf-8') as metadata_file: with codecs.open(metadata_file_path, 'w', encoding='utf-8') as metadata_file:
metadata_file.write(metadata) metadata_file.write(metadata)
except Exception as e: except Exception as e:
message = _(u'Jetzt ist irgendwas schief gelaufen.') message = _('Jetzt ist irgendwas schief gelaufen.')
messages.error(self.request, message) messages.error(self.request, message)
logger.error('dav_submission.views.UploadView.form_valid(): Catched Exception #2: %s', str(e)) 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: try:
for input_file in form.files.getlist('files'): for input_file in form.files.getlist('files'):
@@ -257,7 +257,7 @@ Beschreibung:
file_path = os.path.join(subdir_path, file_name) file_path = os.path.join(subdir_path, file_name)
if os.path.exists(file_path): 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) messages.error(self.request, message)
continue continue
@@ -267,28 +267,28 @@ Beschreibung:
size = os.path.getsize(file_path) size = os.path.getsize(file_path)
if size > (1024 * 1024): 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: 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: else:
size_str = u'%d Byte' % size size_str = '%d Byte' % size
message = _(u'Datei erfolgreich hochgeladen: %(name)s (%(size)s)') % {'name': input_file.name, message = _('Datei erfolgreich hochgeladen: %(name)s (%(size)s)') % {'name': input_file.name,
'size': size_str} 'size': size_str}
messages.success(self.request, message) messages.success(self.request, message)
except Exception as e: except Exception as e:
message = _(u'Jetzt ist irgendwas schief gelaufen.') message = _('Jetzt ist irgendwas schief gelaufen.')
messages.error(self.request, message) messages.error(self.request, message)
logger.error('dav_submission.views.UploadView.form_valid(): Catched Exception #3: %s', str(e)) 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 = NewSubmissionMail(metadata_format_kwargs)
mail.send() mail.send()
return super(UploadView, self).form_valid(form) return super().form_valid(form)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if not app_config.settings.enable_upload: if not app_config.settings.enable_upload:
raise PermissionDenied(_(u'Der Upload ist noch nicht freigeschaltet.')) raise PermissionDenied(_('Der Upload ist noch nicht freigeschaltet.'))
return super(UploadView, self).post(request, *args, **kwargs) return super().post(request, *args, **kwargs)
class SuccessView(generic.TemplateView): class SuccessView(generic.TemplateView):

View File

@@ -22,12 +22,7 @@ class SetupPythonEnvironment(MyCommand):
def run(self): def run(self):
python_bin = sys.executable if sys.executable else 'python' python_bin = sys.executable if sys.executable else 'python'
python_ver = sys.version_info.major python_ver = sys.version_info.major
if python_ver == 2: if python_ver == 3:
path = os.path.join('env', 'python2')
symlink_path = os.path.join('env', 'python')
venv_module = 'virtualenv'
prompt = '(py2-dav) '
elif python_ver == 3:
path = os.path.join('env', 'python3') path = os.path.join('env', 'python3')
symlink_path = os.path.join('env', 'python') symlink_path = os.path.join('env', 'python')
venv_module = 'venv' venv_module = 'venv'
@@ -97,7 +92,7 @@ class QuickSetup(MyCommand):
setup( setup(
name='django-dav-events', name='django-dav-events',
version='2.0', version='2.1',
description='A django based web application project to organize DAV Events.', description='A django based web application project to organize DAV Events.',
url='https://touren.alpenverein-karlsruhe.de', url='https://touren.alpenverein-karlsruhe.de',
author='Jens Kleineheismann', author='Jens Kleineheismann',
@@ -116,11 +111,12 @@ setup(
}, },
install_requires=[ install_requires=[
'babel', 'babel',
'django >= 1.11, < 2.0', #'django >= 1.11, < 2.0',
'django-extensions', 'django >= 1.11, < 3.3',
'django-bootstrap3 < 12', # 'django-extensions',
'django-countries < 6', 'django-bootstrap3',
'django-datetime-widget', 'django-countries',
'django-datetime-widget2',
'pytz', 'pytz',
'selenium', 'selenium',
'coverage', 'coverage',

5
tests/__main__.py Normal file
View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from test_suite import main
if __name__ == '__main__':
main()

4
tests/settings.py Normal file
View File

@@ -0,0 +1,4 @@
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
SECRET_KEY = 'fake-key'

View File

@@ -1,18 +1,19 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import datetime import datetime
import django
import os import os
import shutil import shutil
import sys import sys
import django
from django.test.utils import get_runner 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.tests.utils import mkdtemp
# from dav_base.console_scripts.admin import DJANGO_MAIN_MODULE
DJANGO_MAIN_MODULE = 'main'
class DjangoEnvironment(object):
class DjangoEnvironment:
@staticmethod @staticmethod
def _install_djangoproject(path, modules=None): def _install_djangoproject(path, modules=None):
cmd = 'django-dav-admin setup "{}"'.format(path) cmd = 'django-dav-admin setup "{}"'.format(path)
@@ -34,6 +35,8 @@ class DjangoEnvironment(object):
else: else:
self._enable_modules = [] self._enable_modules = []
self.settings = None
def __enter__(self): def __enter__(self):
if self.path is None: if self.path is None:
prefix = 'testrun-{datetime}-'.format( prefix = 'testrun-{datetime}-'.format(
@@ -50,7 +53,7 @@ class DjangoEnvironment(object):
os.environ['DJANGO_SETTINGS_MODULE'] = '{}.settings'.format(DJANGO_MAIN_MODULE) os.environ['DJANGO_SETTINGS_MODULE'] = '{}.settings'.format(DJANGO_MAIN_MODULE)
django.setup() django.setup()
from django.conf import settings from django.conf import settings # pylint: disable=import-outside-toplevel
self.settings = settings self.settings = settings
return self return self
@@ -63,7 +66,7 @@ class DjangoEnvironment(object):
shutil.rmtree(self.path) shutil.rmtree(self.path)
class TestSuite(object): class TestSuite:
@staticmethod @staticmethod
def run(): def run():
modules = ['dav_auth', 'dav_events', 'dav_registration', 'dav_event_office'] modules = ['dav_auth', 'dav_events', 'dav_registration', 'dav_event_office']
@@ -80,3 +83,12 @@ class TestSuite(object):
def __call__(self): def __call__(self):
sys.exit(self.run()) sys.exit(self.run())
def main():
test_suite = TestSuite()
test_suite()
if __name__ == '__main__':
main()

View File

@@ -1,7 +1,7 @@
[tox] [tox]
envlist = py3,py2 envlist = py3
[testenv] [testenv]
commands = python --version commands = python --version
python -m coverage run setup.py test python -m coverage run tests/test_suite.py
coverage report --skip-covered coverage report --skip-covered