26 Commits

Author SHA1 Message Date
heinzel 6dfaaa4965 Merge pull request 'Merge stage (Djang5.2/Python3.14 stuff) into production' (#97) from stage into production
Run tests / Execute tox to run the test suite (push) Successful in 3m23s
Reviewed-on: #97
2026-05-08 09:30:29 +02:00
heinzel fe43a44c9c Merge pull request 'Update to django 5.2 to support python 3.14' (#96) from master into stage
Deploy into stage environment / Deploy into stage environment (push) Successful in 2s
Run tests / Execute tox to run the test suite (push) Successful in 4m2s
Reviewed-on: #96
2026-04-29 13:59:10 +02:00
heinzel 04c8130da8 Merge pull request 'Deploy Django5 stuff on production' (#93) from stage into production
Run tests / Execute tox to run the test suite (push) Successful in 4m42s
Reviewed-on: #93
2025-04-11 11:23:23 +02:00
heinzel d43584ef03 Merge pull request 'Test django 5 and Python 3.13' (#92) from master into stage
Deploy into stage environment / Deploy into stage environment (push) Successful in 5s
Run tests / Execute tox to run the test suite (push) Successful in 4m51s
Reviewed-on: #92
2025-04-11 10:36:26 +02:00
heinzel f3db0424be Merge pull request 'New Stuff' (#90) from stage into production
Run tests / Execute tox to run the test suite (push) Successful in 3m38s
Reviewed-on: #90
2024-09-19 15:51:14 +02:00
heinzel 3fd8dfa9eb Merge pull request 'Test new stuff' (#89) from master into stage
Deploy into stage environment / Deploy into stage environment (push) Successful in 4s
Run tests / Execute tox to run the test suite (push) Successful in 3m38s
Reviewed-on: #89
2024-09-19 15:49:23 +02:00
heinzel 26bd7ccdb6 Merge pull request 'Release new stuff from stage' (#87) from stage into production
Run tests / Execute tox to run the test suite (push) Successful in 3m24s
Reviewed-on: #87
2024-09-16 15:05:17 +02:00
heinzel bdbb4622c1 Merge pull request 'Test new stuff from master' (#86) from master into stage
Deploy into stage environment / Deploy into stage environment (push) Successful in 3s
Run tests / Execute tox to run the test suite (push) Successful in 3m24s
Reviewed-on: #86
2024-09-16 15:03:35 +02:00
heinzel ad87130f64 Merge pull request 'stage' (#85) from stage into production
Run tests / Execute tox to run the test suite (push) Successful in 3m42s
Reviewed-on: #85
2024-09-16 09:50:11 +02:00
heinzel 3c2730f571 Merge pull request 'dav_registration: made the new JSON-View export a dict instead of a list' (#84) from master into stage
Deploy into stage environment / Deploy into stage environment (push) Successful in 4s
Run tests / Execute tox to run the test suite (push) Successful in 3m39s
Reviewed-on: #84
2024-09-16 09:48:56 +02:00
heinzel 5474b9d9db Merge pull request 'stage' (#83) from stage into production
Run tests / Execute tox to run the test suite (push) Successful in 3m36s
Reviewed-on: #83
2024-09-16 08:58:34 +02:00
heinzel a95269cdb0 Merge pull request 'Update the production branch' (#82) from master into production
Run tests / Execute tox to run the test suite (push) Successful in 3m29s
Reviewed-on: #82
2024-09-16 08:50:38 +02:00
heinzel 47f06c52db Merge pull request 'master' (#81) from master into stage
Deploy into stage environment / Deploy into stage environment (push) Successful in 4s
Run tests / Execute tox to run the test suite (push) Successful in 3m50s
Reviewed-on: #81
2024-09-12 15:29:19 +02:00
heinzel 59a79720e3 Merge pull request 'Test master stuff on stage' (#79) from master into stage
Deploy into stage environment / Deploy into stage environment (push) Successful in 4s
Run tests / Execute tox to run the test suite (push) Successful in 3m21s
Reviewed-on: #79
2024-09-12 08:55:38 +02:00
heinzel 5016ec5d0e Merge pull request 'Test new stuff from master on stage' (#77) from master into stage
Deploy into stage environment / Deploy into stage environment (push) Successful in 3s
Run tests / Execute tox to run the test suite (push) Successful in 3m21s
Reviewed-on: #77
2024-09-10 16:19:29 +02:00
heinzel 0e0d302f0c Merge branch 'master' into stage
Deploy into stage environment / Deploy into stage environment (push) Successful in 3s
Run tests / Execute tox to run the test suite (push) Successful in 3m23s
2024-09-10 15:03:54 +02:00
heinzel fab87ffa21 Merge pull request 'test newest stuff from master on stage' (#75) from master into stage
Deploy into stage environment / Deploy into stage environment (push) Successful in 3s
Run tests / Execute tox to run the test suite (push) Successful in 3m9s
Reviewed-on: #75
2024-09-10 09:42:52 +02:00
heinzel e4da2738e0 Merge pull request 'deploy latest stuff on stage' (#73) from master into stage
Deploy into stage environment / Deploy into stage environment (push) Successful in 3s
Run tests / Execute tox to run the test suite (push) Successful in 2m21s
Reviewed-on: #73
2024-08-02 15:59:09 +02:00
heinzel 65ace70981 Merge pull request 'Fix #61' (#68) from master into production
buildbot/django-dav-events--test Build done.
Reviewed-on: #68
2023-05-08 15:37:47 +02:00
heinzel ca6ca88b34 Merge pull request 'Fixed typo in "Schwierigkeitsniveau"' (#67) from master into production
buildbot/django-dav-events--test Build done.
Reviewed-on: #67
2023-05-08 13:53:50 +02:00
heinzel c10d447802 Merge pull request 'Update production branch with latest stuff' (#66) from master into production
buildbot/django-dav-events--test Build done.
Reviewed-on: #66
2023-05-08 12:48:23 +02:00
heinzel cb3c9a0e98 Merge pull request 'Merge Makefile into production for CI/CD stuff' (#58) from master into production
Reviewed-on: #58
2023-02-17 14:09:15 +01:00
heinzel c9572d631f Merge pull request 'update production branch' (#57) from master into production
buildbot/tox Build done.
Reviewed-on: #57
2023-02-15 19:48:19 +01:00
heinzel 6e67e2767b Merge pull request 'merge new master stuff into production' (#47) from master into production
buildbot/tox Build done.
Reviewed-on: #47
2021-01-27 12:13:21 +01:00
heinzel b603f852a3 Merge pull request 'Update production branch to master' (#45) from master into production
buildbot/tox Build done.
Reviewed-on: #45
2021-01-27 11:55:47 +01:00
heinzel cb364e5745 Merge pull request 'Production environment is updated.' (#43) from master into production
buildbot/tox Build done.
Reviewed-on: #43
2021-01-07 14:42:48 +01:00
54 changed files with 242 additions and 1892 deletions
-1
View File
@@ -6,7 +6,6 @@ source =
dav_registration dav_registration
omit = omit =
dav_base/tests/generic.py dav_base/tests/generic.py
dav_base/tests/utils.py
dav_auth/tests/generic.py dav_auth/tests/generic.py
dav_events/tests/generic.py dav_events/tests/generic.py
dav_registration/tests/generic.py dav_registration/tests/generic.py
-5
View File
@@ -3,11 +3,6 @@ from dav_base.config.apps import AppConfig as _AppConfig, DefaultSetting
DEFAULT_SETTINGS = ( DEFAULT_SETTINGS = (
DefaultSetting('login_redirect_url', 'root'), DefaultSetting('login_redirect_url', 'root'),
DefaultSetting('logout_redirect_url', 'root'), DefaultSetting('logout_redirect_url', 'root'),
DefaultSetting('auto_password_length', 32),
DefaultSetting('auto_password_characters', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'abcdefghijklmnopqrstuvwxyz'
'0123456789'
'#$%&@^~.,:;/_-*+!?'),
) )
@@ -1,4 +1,2 @@
# LOGIN_REDIRECT_URL = 'root' # LOGIN_REDIRECT_URL = 'root'
# LOGOUT_REDIRECT_URL = 'root' # LOGOUT_REDIRECT_URL = 'root'
# AUTO_PASSWORD_LENGTH = 32
# AUTO_PASSWORD_CHARACTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$%&@^~.,:;/_-*+!?'
@@ -1,11 +0,0 @@
{% load bootstrap3 %}
{% load i18n %}
<br />
<p>
Dein Passwort entspricht nicht mehr den aktuellen Passwortrichtlinien.<br />
Bitte hilf uns die Daten deiner Teilnehmer zu schützen und ändere dein Passwort.<br />
</p>
<p>
<a href="{% url 'dav_auth:set_password' %}">{% trans 'Passwort ändern' %}</a>
</p>
<br />
+3 -7
View File
@@ -1,4 +1,5 @@
from django.apps import apps from django.apps import apps
from six import string_types
from dav_base.tests.generic import AppSetting, AppsTestCase from dav_base.tests.generic import AppSetting, AppsTestCase
@@ -7,11 +8,6 @@ class TestCase(AppsTestCase):
app_config = apps.get_app_config('dav_auth') app_config = apps.get_app_config('dav_auth')
settings = ( settings = (
AppSetting('login_redirect_url', 'root', str), AppSetting('login_redirect_url', string_types),
AppSetting('logout_redirect_url', 'root', str), AppSetting('logout_redirect_url', string_types),
AppSetting('auto_password_length', 32, int),
AppSetting('auto_password_characters', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'abcdefghijklmnopqrstuvwxyz'
'0123456789'
'#$%&@^~.,:;/_-*+!?', str),
) )
+2 -2
View File
@@ -147,7 +147,7 @@ class SetPasswordFormTestCase(FormsTestCase):
def test_save(self): def test_save(self):
new_passwords = [ new_passwords = [
'"ä§ Mellon12', '"ä§ Mellon12'
'mellon12' * 128, 'mellon12' * 128,
] ]
@@ -162,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 = [
'"ä§ Mellon12', '"ä§ Mellon12'
'mellon12' * 128, 'mellon12' * 128,
] ]
+1 -3
View File
@@ -5,8 +5,7 @@ from django.test import TestCase, Client
class ModelsTestCase(TestCase): class ModelsTestCase(TestCase):
@skip('user.save() should raise an ValidationError when the user name is to long.' @skip('I do not know, why the user.save() does not raise an exception')
' But it does not. I do not know why.')
def test_username_length(self): def test_username_length(self):
max_length = 150 # Hard coded in django.contrib.auth.models.AbstractUser max_length = 150 # Hard coded in django.contrib.auth.models.AbstractUser
username_ok = 'u' * max_length username_ok = 'u' * max_length
@@ -17,7 +16,6 @@ class ModelsTestCase(TestCase):
first_name='A', first_name='A',
last_name='User', last_name='User',
email='a.user@example.com') email='a.user@example.com')
with self.assertRaises(Exception):
user.save() user.save()
def test_last_login(self): def test_last_login(self):
+9 -37
View File
@@ -9,8 +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_STRONG_PASSWORD = 'me||ön 21ABll' TEST_PASSWORD = 'me||ön 21ABll'
TEST_WEAK_PASSWORD = 'mellon'
TEST_EMAIL = TEST_USERNAME TEST_EMAIL = TEST_USERNAME
@@ -21,11 +20,10 @@ class TestCase(ScreenshotTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
# Need a test user # Need a test user
# (we start with a weak password, because we want to see the weak password warning)
self.test_username = TEST_USERNAME self.test_username = TEST_USERNAME
self.test_password = TEST_WEAK_PASSWORD self.test_password = TEST_PASSWORD
model = get_user_model() model = get_user_model()
self.user = model.objects.create_user(username=TEST_USERNAME, password=TEST_WEAK_PASSWORD, email=TEST_EMAIL) self.user = model.objects.create_user(username=TEST_USERNAME, password=TEST_PASSWORD, email=TEST_EMAIL)
def test_screenshots(self): def test_screenshots(self):
sequence_name = 'walkthrough' sequence_name = 'walkthrough'
@@ -88,7 +86,7 @@ class TestCase(ScreenshotTestCase):
self.user.is_active = True self.user.is_active = True
self.user.save() self.user.save()
# Login -> save success message and weak password warning # Login -> save success message
username_field = c.find_element(By.ID, 'id_username') username_field = c.find_element(By.ID, 'id_username')
username_field.clear() username_field.clear()
username_field.send_keys(self.test_username) username_field.send_keys(self.test_username)
@@ -97,7 +95,7 @@ class TestCase(ScreenshotTestCase):
password_field.send_keys(self.test_password) password_field.send_keys(self.test_password)
password_field.send_keys(Keys.RETURN) password_field.send_keys(Keys.RETURN)
alert_button = self.wait_on_presence(c, (By.CSS_SELECTOR, '#messages .alert-success button.close')) alert_button = self.wait_on_presence(c, (By.CSS_SELECTOR, '#messages .alert-success button.close'))
self.save_screenshot('login_weak_password_succeed', sequence=sequence_name) self.save_screenshot('login_succeed', sequence=sequence_name)
alert_button.click() alert_button.click()
# Open user dropdown menu -> save menu # Open user dropdown menu -> save menu
@@ -124,9 +122,9 @@ class TestCase(ScreenshotTestCase):
# New passwords mismatch -> save error message # New passwords mismatch -> save error message
password_field.clear() password_field.clear()
password_field.send_keys(TEST_STRONG_PASSWORD) password_field.send_keys(self.test_password)
password2_field.clear() password2_field.clear()
password2_field.send_keys(TEST_WEAK_PASSWORD) password2_field.send_keys(self.test_password[::-1])
password2_field.send_keys(Keys.RETURN) password2_field.send_keys(Keys.RETURN)
self.wait_until_stale(c, password2_field) self.wait_until_stale(c, password2_field)
self.save_screenshot('error_mismatch', sequence=sequence_name) self.save_screenshot('error_mismatch', sequence=sequence_name)
@@ -168,7 +166,7 @@ class TestCase(ScreenshotTestCase):
self.save_screenshot('error_too_similar', sequence=sequence_name) self.save_screenshot('error_too_similar', sequence=sequence_name)
# Change password -> save success message # Change password -> save success message
password = TEST_STRONG_PASSWORD password = self.test_password[::-1]
password_field = c.find_element(By.ID, 'id_new_password') password_field = c.find_element(By.ID, 'id_new_password')
password_field.clear() password_field.clear()
password_field.send_keys(password) password_field.send_keys(password)
@@ -178,7 +176,6 @@ class TestCase(ScreenshotTestCase):
password2_field.send_keys(Keys.RETURN) password2_field.send_keys(Keys.RETURN)
self.wait_until_stale(c, password2_field) self.wait_until_stale(c, password2_field)
self.save_screenshot('set_password_succeed', sequence=sequence_name) self.save_screenshot('set_password_succeed', sequence=sequence_name)
self.test_password = password
# Get password recreate page -> since we are logged in, it should # Get password recreate page -> since we are logged in, it should
# redirect to set password page again -> save # redirect to set password page again -> save
@@ -199,31 +196,6 @@ class TestCase(ScreenshotTestCase):
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)
# Login again, this time with a new strong password
link = c.find_element(By.CSS_SELECTOR, '#login-widget a')
link.click()
self.wait_on_presence(c, (By.ID, 'id_username'))
username_field = c.find_element(By.ID, 'id_username')
username_field.clear()
username_field.send_keys(self.test_username)
password_field = c.find_element(By.ID, 'id_password')
password_field.clear()
password_field.send_keys(self.test_password)
password_field.send_keys(Keys.RETURN)
alert_button = self.wait_on_presence(c, (By.CSS_SELECTOR, '#messages .alert-success button.close'))
self.save_screenshot('login_strong_password_succeed', sequence=sequence_name)
alert_button.click()
# Logout again
dropdown_button = self.wait_on_presence(c, (By.ID, 'user_dropdown_button'))
dropdown_button.click()
user_menu = c.find_element(By.CSS_SELECTOR, '#login-widget ul')
#link = user_menu.find_element(By.PARTIAL_LINK_TEXT, gettext('Logout'))
#link.click()
button = c.find_element(By.ID, 'id_logout_button')
button.click()
self.wait_until_stale(c, user_menu)
# Click on 'login' to access password recreate link # Click on 'login' to access password recreate link
link = c.find_element(By.CSS_SELECTOR, '#login-widget a') link = c.find_element(By.CSS_SELECTOR, '#login-widget a')
link.click() link.click()
@@ -235,7 +207,7 @@ class TestCase(ScreenshotTestCase):
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)
# Enter invalid username -> save result (login form, success message - do not reveal user does not exist) # Enter invalid username -> save result (login form, no message)
username_field.send_keys(self.test_username[::-1]) username_field.send_keys(self.test_username[::-1])
username_field.send_keys(Keys.RETURN) username_field.send_keys(Keys.RETURN)
self.wait_until_stale(c, username_field) self.wait_until_stale(c, username_field)
+16 -32
View File
@@ -26,9 +26,12 @@ class PasswordScoreValidatorTestCase(SimpleTestCase):
validator = PasswordScoreValidator(min_classes=0) validator = PasswordScoreValidator(min_classes=0)
for password in passwords: for password in passwords:
with self.assertRaises(ValidationError, msg=password) as cm: try:
validator.validate(password) validator.validate(password)
self.assertEqual('too_little_score', cm.exception.code) except ValidationError as e:
self.assertEqual('too_little_score', e.code)
else:
self.fail('%s: no validation error was raised' % password)
def test_too_few_classes(self): def test_too_few_classes(self):
passwords = [ passwords = [
@@ -46,9 +49,12 @@ class PasswordScoreValidatorTestCase(SimpleTestCase):
validator = PasswordScoreValidator(min_score=0, min_classes=4) validator = PasswordScoreValidator(min_score=0, min_classes=4)
for password in passwords: for password in passwords:
with self.assertRaises(ValidationError, msg=password) as cm: try:
validator.validate(password) validator.validate(password)
self.assertEqual('too_few_classes', cm.exception.code) except ValidationError as e:
self.assertEqual('too_few_classes', e.code)
else:
self.fail('%s: no validation error was raised' % password)
def test_valid(self): def test_valid(self):
passwords = [ passwords = [
@@ -69,15 +75,8 @@ class PasswordScoreValidatorTestCase(SimpleTestCase):
for password in passwords: for password in passwords:
try: try:
validator.validate(password) validator.validate(password)
except ValidationError as e: # pragma: no cover except ValidationError as e:
self.fail('%s: %s' % (password, e)) self.fail(e)
def test_help_text(self):
validator = PasswordScoreValidator()
help_text = validator.get_help_text()
self.assertIn('The password must get a minimum score of 18 points.', help_text)
self.assertIn('Also the password must contain characters from 2 different character classes'
' (i.e. lower, upper, digits, others).', help_text)
class CustomWordlistPasswordValidatorTestCase(SimpleTestCase): class CustomWordlistPasswordValidatorTestCase(SimpleTestCase):
@@ -115,7 +114,7 @@ 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('%s: no validation error was raised' % password) # pragma: no cover self.fail('%s: no validation error was raised' % password)
def test_valid(self): def test_valid(self):
passwords = [ passwords = [
@@ -129,15 +128,9 @@ class CustomWordlistPasswordValidatorTestCase(SimpleTestCase):
for password in passwords: for password in passwords:
try: try:
validator.validate(password) validator.validate(password)
except ValidationError as e: # pragma: no cover except ValidationError as e:
self.fail(e) self.fail(e)
def test_help_text(self):
validator = CustomWordlistPasswordValidator()
help_text = validator.get_help_text()
self.assertIn('The password must not contain some specific words,', help_text)
self.assertIn('All words are matched case insensitive.', help_text)
class CharacterClassPasswordValidatorTestCase(SimpleTestCase): class CharacterClassPasswordValidatorTestCase(SimpleTestCase):
def setUp(self): def setUp(self):
@@ -222,7 +215,7 @@ 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('%s: no validation error was raised' % password) # pragma: no cover self.fail('%s: no validation error was raised' % password)
def test_valid(self): def test_valid(self):
valid_passwords = ['abCD12+-'] valid_passwords = ['abCD12+-']
@@ -230,14 +223,5 @@ class CharacterClassPasswordValidatorTestCase(SimpleTestCase):
for password in valid_passwords: for password in valid_passwords:
try: try:
validator.validate(password) validator.validate(password)
except ValidationError as e: # pragma: no cover except ValidationError as e:
self.fail(e) self.fail(e)
def test_help_text(self):
validator = self.validator
help_text = validator.get_help_text()
self.assertIn('The password must contain at least 2 characters from a-z.', help_text)
self.assertIn('The password must contain at least 2 characters from A-Z.', help_text)
self.assertIn('The password must contain at least 2 digits from 0-9.', help_text)
self.assertIn('The password must contain at least 2 non alpha numeric characters.', help_text)
+5 -104
View File
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
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.password_validation import validate_password
from django.contrib.messages import get_messages
from django.core import mail as django_mail from django.core import mail as django_mail
from django.shortcuts import resolve_url from django.shortcuts import resolve_url
from django.test import TestCase from django.test import TestCase
@@ -12,8 +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_STRONG_PASSWORD = 'me||ön 21ABll' TEST_PASSWORD = 'me||ön 21ABll'
TEST_WEAK_PASSWORD = 'mellon'
TEST_EMAIL = TEST_USERNAME TEST_EMAIL = TEST_USERNAME
@@ -34,22 +31,16 @@ class ViewsTestCase(TestCase):
# Some messages # Some messages
cls.wrong_credentials_message = gettext('Benutzername oder Passwort falsch.') cls.wrong_credentials_message = gettext('Benutzername oder Passwort falsch.')
cls.login_message = gettext('Benutzer angemeldet: %(username)s')
cls.logout_message = gettext('Benutzer abgemeldet.') cls.logout_message = gettext('Benutzer abgemeldet.')
cls.set_password_message = gettext('Passwort gespeichert.') cls.set_password_message = gettext('Passwort gespeichert.')
cls.weak_password_warning_message = gettext('Dein Passwort entspricht nicht mehr den aktuellen Passwortrichtlinien.')
cls.new_password_sent_message = gettext('Neues Passwort versendet.')
def setUp(self): def setUp(self):
super().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_STRONG_PASSWORD self.test_password = TEST_PASSWORD
self.test_email = TEST_EMAIL
model = get_user_model() model = get_user_model()
self.user = model.objects.create_user(username=self.test_username, self.user = model.objects.create_user(username=TEST_USERNAME, password=TEST_PASSWORD, email=TEST_EMAIL)
password=self.test_password,
email=self.test_email)
def test_integrated_login_get(self): def test_integrated_login_get(self):
response = self.client.get(self.login_url) response = self.client.get(self.login_url)
@@ -63,12 +54,6 @@ class ViewsTestCase(TestCase):
field = response.context['form'].fields['password'] field = response.context['form'].fields['password']
self.assertTrue(field.required) self.assertTrue(field.required)
def test_integrated_login_invalid_user(self):
response = self.client.post(self.login_url, {'username': 'toor', 'password': self.test_password})
self.assertEqual(response.status_code, 200)
self.assertFormError(response.context['form'], None, self.wrong_credentials_message)
self.assertFalse(response.context['user'].is_authenticated, 'User is logged in')
def test_integrated_login_inactive_user(self): def test_integrated_login_inactive_user(self):
user = self.user user = self.user
user.is_active = False user.is_active = False
@@ -89,44 +74,14 @@ class ViewsTestCase(TestCase):
def test_integrated_login_succeed(self): def test_integrated_login_succeed(self):
username = self.user.username username = self.user.username
expected_message = self.login_message % {'username': username} message = gettext('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)
self.assertEqual(response.url, self.login_redirect_url) self.assertEqual(response.url, self.login_redirect_url)
response = self.client.get(response.url) response = self.client.get(response.url)
messages = list(get_messages(response.wsgi_request)) self.assertContains(response, message)
self.assertEqual(len(messages), 1)
self.assertEqual(messages[0].message, expected_message)
self.assertContains(response, expected_message)
self.assertTrue(response.context['user'].is_authenticated, 'Login failed')
def test_integrated_login_weak_password(self):
username = self.user.username
password = TEST_WEAK_PASSWORD
expected_message = self.login_message % {'username': username}
user_model = get_user_model()
user = user_model.objects.get(username=username)
user.set_password(password)
user.save()
with self.assertLogs('dav_auth.views', level='WARNING') as cm:
response = self.client.post(self.login_url, {'username': username, 'password': password})
self.assertStartsWith(cm.output[0], 'WARNING:dav_auth.views:Detected weak password for user id')
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, self.login_redirect_url)
response = self.client.get(response.url)
messages = list(get_messages(response.wsgi_request))
self.assertEqual(len(messages), 2)
self.assertEqual(messages[0].message, expected_message)
self.assertContains(response, expected_message)
self.assertIn(self.weak_password_warning_message, messages[1].message)
self.assertContains(response, self.weak_password_warning_message)
self.assertTrue(response.context['user'].is_authenticated, 'Login failed') self.assertTrue(response.context['user'].is_authenticated, 'Login failed')
@@ -138,8 +93,6 @@ class ViewsTestCase(TestCase):
self.assertEqual(response.url, self.logout_redirect_url) self.assertEqual(response.url, self.logout_redirect_url)
response = self.client.get(response.url) response = self.client.get(response.url)
messages = list(get_messages(response.wsgi_request))
self.assertEqual(messages[0].message, self.logout_message)
self.assertContains(response, self.logout_message) self.assertContains(response, self.logout_message)
self.assertFalse(response.context['user'].is_authenticated, 'Logout failed') self.assertFalse(response.context['user'].is_authenticated, 'Logout failed')
@@ -189,8 +142,6 @@ class ViewsTestCase(TestCase):
self.assertEqual(len(django_mail.outbox), 0) self.assertEqual(len(django_mail.outbox), 0)
response = self.client.get(response.url) response = self.client.get(response.url)
messages = list(get_messages(response.wsgi_request))
self.assertEqual(messages[0].message, self.set_password_message)
self.assertContains(response, self.set_password_message) self.assertContains(response, self.set_password_message)
self.client.logout() self.client.logout()
@@ -222,8 +173,6 @@ class ViewsTestCase(TestCase):
self.assertIn(new_password, mail.body) self.assertIn(new_password, mail.body)
response = self.client.get(response.url) response = self.client.get(response.url)
messages = list(get_messages(response.wsgi_request))
self.assertEqual(messages[0].message, self.set_password_message)
self.assertContains(response, self.set_password_message) self.assertContains(response, self.set_password_message)
self.client.logout() self.client.logout()
@@ -249,10 +198,6 @@ class ViewsTestCase(TestCase):
location = self.recreate_password_url location = self.recreate_password_url
response = self.client.post(location, {'username': self.user.username}) response = self.client.post(location, {'username': self.user.username})
new_password = response.context['password']
messages = list(get_messages(response.wsgi_request))
self.assertEqual(len(messages), 1)
self.assertEqual(messages[0].message, self.new_password_sent_message)
self.assertRedirects(response, self.login_url) self.assertRedirects(response, self.login_url)
self.assertEqual(len(django_mail.outbox), 1) self.assertEqual(len(django_mail.outbox), 1)
@@ -261,53 +206,9 @@ class ViewsTestCase(TestCase):
recipients = mail.recipients() recipients = mail.recipients()
self.assertIn(recipient, recipients) self.assertIn(recipient, recipients)
self.assertEqual(len(recipients), 1) self.assertEqual(len(recipients), 1)
self.assertIn(new_password, mail.body)
response = self.client.get(location) response = self.client.get(location)
self.assertFalse(response.context['user'].is_authenticated, 'User is logged in') self.assertFalse(response.context['user'].is_authenticated, 'User is logged in')
self.assertFalse(self.client.login(username=self.test_username, password=self.test_password), self.assertFalse(self.client.login(username=self.test_username, password=self.test_password),
'Old password still valid') 'Old password still valid')
def test_recreate_password_invalid_user(self):
location = self.recreate_password_url
response = self.client.post(location, {'username': 'toor'})
messages = list(get_messages(response.wsgi_request))
self.assertEqual(len(messages), 1)
self.assertEqual(messages[0].message, self.new_password_sent_message)
self.assertRedirects(response, self.login_url)
self.assertEqual(len(django_mail.outbox), 0)
def test_new_password_length(self):
location = self.recreate_password_url
expected_password_length = 32
response = self.client.post(location, {'username': self.user.username})
new_password = response.context['password']
self.assertEqual(len(new_password), expected_password_length)
expected_password_length = 12
self.app_settings.auto_password_length = expected_password_length
response = self.client.post(location, {'username': self.user.username})
new_password = response.context['password']
self.assertEqual(len(new_password), expected_password_length)
def test_new_password_chars(self):
location = self.recreate_password_url
password_chars = 'xYz0'
self.app_settings.auto_password_characters = password_chars
response = self.client.post(location, {'username': self.user.username})
new_password = response.context['password']
self.assertTrue(all(c in password_chars for c in new_password))
def test_new_password_is_valid(self):
location = self.recreate_password_url
response = self.client.post(location, {'username': self.user.username})
new_password = response.context['password']
validate_password(new_password)
+15 -22
View File
@@ -8,7 +8,6 @@ from django.contrib.auth import views as auth_views, get_user_model
from django.contrib.auth.password_validation import validate_password from django.contrib.auth.password_validation import validate_password
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import resolve_url from django.shortcuts import resolve_url
from django.template.loader import render_to_string
from django.urls import reverse_lazy, reverse from django.urls import reverse_lazy, reverse
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@@ -24,7 +23,6 @@ logger = logging.getLogger(__name__)
class LoginView(auth_views.LoginView): class LoginView(auth_views.LoginView):
form_class = forms.LoginForm form_class = forms.LoginForm
template_name = 'dav_auth/forms/login.html' template_name = 'dav_auth/forms/login.html'
weak_password_warning_template_name = 'dav_auth/includes/weak_password_warning.html'
def get_redirect_url(self): def get_redirect_url(self):
url = super().get_redirect_url() url = super().get_redirect_url()
@@ -38,8 +36,14 @@ class LoginView(auth_views.LoginView):
try: try:
validate_password(form.cleaned_data['password']) validate_password(form.cleaned_data['password'])
except ValidationError as e: except ValidationError as e:
logger.warning('Detected weak password for user id %d: %s', self.request.user.pk, e) logger.warning('Weak password (%d): %s', self.request.user.pk, e)
message = render_to_string(self.weak_password_warning_template_name) message = '<br />\n<p>\n'
message += 'Dein Passwort entspricht nicht mehr den aktuellen Passwortrichtlinien.<br />\n'
message += 'Bitte hilf uns die Daten deiner Teilnehmer zu schützen und ändere dein Passwort.<br />\n'
message += '</p>\n'
message += '<p>\n'
message += '<a href="%(href)s">Passwort ändern</a>\n' % {'href': reverse('dav_auth:set_password')}
message += '</p>\n<br />\n'
messages.warning(self.request, mark_safe(message)) messages.warning(self.request, mark_safe(message))
return r return r
@@ -68,7 +72,7 @@ class SetPasswordView(auth_views.PasswordChangeView):
def form_valid(self, form): def form_valid(self, form):
r = super().form_valid(form) r = super().form_valid(form)
messages.success(self.request, _('Passwort gespeichert.')) messages.success(self.request, _('Passwort gespeichert.'))
logger.info('Changed password for user \'%s\'', self.request.user) logger.info('Changed Password for user \'%s\'', self.request.user)
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()
@@ -79,34 +83,23 @@ class CreateAndSendPasswordView(generic.FormView):
form_class = forms.CreateAndSendPasswordForm form_class = forms.CreateAndSendPasswordForm
template_name = 'dav_auth/forms/recreate_password.html' template_name = 'dav_auth/forms/recreate_password.html'
success_url = reverse_lazy('dav_auth:login') success_url = reverse_lazy('dav_auth:login')
password_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$%&@^~.,:;/_-*+!?'
@staticmethod password_length = 32
def _create_new_password(length=None, characters=None):
if length is None:
length = app_config.settings.auto_password_length
if characters is None:
characters = app_config.settings.auto_password_characters
return ''.join(secrets.choice(characters) for i in range(length))
def form_valid(self, form): def form_valid(self, form):
username = form.cleaned_data.get('username') username = form.cleaned_data.get('username')
user_model = get_user_model() user_model = get_user_model()
# Generate a new password (even if the user does not exist, to avoid revealing that fact).
random_password = self._create_new_password()
try: try:
user = user_model.objects.get(username=username) user = user_model.objects.get(username=username)
random_password = ''.join(secrets.choice(self.password_chars) for i in range(self.password_length))
user.set_password(random_password) user.set_password(random_password)
user.save() user.save()
email = emails.PasswordSetEmail(user, random_password) email = emails.PasswordSetEmail(user, random_password)
email.send() email.send()
logger.info('Recreated password for user \'%s\'', username)
except user_model.DoesNotExist:
logger.warning('Recreated password for unknown user \'%s\'', username)
# Show message, that we sent an email, even we did not, so we do not reveal that the user doesn't exist.
messages.success(self.request, _('Neues Passwort versendet.')) messages.success(self.request, _('Neues Passwort versendet.'))
logger.info('Password recreated for user \'%s\'', username)
except user_model.DoesNotExist:
logger.warning('Password recreated for unknown user \'%s\'', username)
return super().form_valid(form) return super().form_valid(form)
-1
View File
@@ -1,3 +1,2 @@
from .constants import DJANGO_MAIN_MODULE, MODULE_APP_SETTINGS_PREFIX, MODULES_CONFIG_FILE_NAME
from . import apps from . import apps
from . import modules from . import modules
+2 -7
View File
@@ -4,8 +4,6 @@ import re
from django.apps import AppConfig as _AppConfig from django.apps import AppConfig as _AppConfig
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from ..config import DJANGO_MAIN_MODULE, MODULE_APP_SETTINGS_PREFIX
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -20,8 +18,7 @@ class DefaultSetting: # pylint: disable=too-few-public-methods
self.validator = validator self.validator = validator
def validate(self, value): def validate(self, value):
if self.validator is None: if hasattr(self, 'validator') and self.validator is not None:
return
if callable(self.validator): if callable(self.validator):
if not self.validator(value): if not self.validator(value):
raise ImproperlyConfigured('Validator callback {clb} returned False'.format(clb=self.validator)) raise ImproperlyConfigured('Validator callback {clb} returned False'.format(clb=self.validator))
@@ -32,9 +29,7 @@ class DefaultSetting: # pylint: disable=too-few-public-methods
class AppSettings: # pylint: disable=too-few-public-methods class AppSettings: # pylint: disable=too-few-public-methods
def __init__(self, app_name, defaults): def __init__(self, app_name, defaults):
settings_name = '{main_module}.{prefix}{app_name}'.format(main_module=DJANGO_MAIN_MODULE, settings_name = 'main.settings-' + app_name
prefix=MODULE_APP_SETTINGS_PREFIX,
app_name=app_name)
try: try:
settings_module = importlib.import_module(settings_name) settings_module = importlib.import_module(settings_name)
-3
View File
@@ -1,3 +0,0 @@
DJANGO_MAIN_MODULE = 'main'
MODULES_CONFIG_FILE_NAME = 'module_config.json'
MODULE_APP_SETTINGS_PREFIX = 'settings-'
+6 -10
View File
@@ -5,7 +5,8 @@ from importlib.resources import files as resource_files
from django.conf import settings from django.conf import settings
from django.urls import re_path, include from django.urls import re_path, include
from ..config import DJANGO_MAIN_MODULE, MODULES_CONFIG_FILE_NAME DJANGO_MAIN_MODULE = 'main'
MODULE_CONFIG_FILE_NAME = 'module_config.json'
class ModuleConfigError(Exception): class ModuleConfigError(Exception):
@@ -16,12 +17,11 @@ class ModuleMeta:
_json_file = 'module.json' _json_file = 'module.json'
_root_url_name = 'root' _root_url_name = 'root'
def __init__(self, package_name, load=True): def __init__(self, package_name):
self._package_name = package_name self._package_name = package_name
self._app_config = None self._app_config = None
self._additional_apps = [] self._additional_apps = []
self._url_prefix = None self._url_prefix = None
if load:
self._load_from_package() self._load_from_package()
def __str__(self): def __str__(self):
@@ -63,10 +63,6 @@ class ModuleMeta:
url_conf = self._package_name + '.urls' url_conf = self._package_name + '.urls'
return re_path(url_pattern, include(url_conf, self.url_namespace)) return re_path(url_pattern, include(url_conf, self.url_namespace))
@property
def root_url_name(self):
return '{}:{}'.format(self.url_namespace, self._root_url_name)
def _load_from_package(self): def _load_from_package(self):
package_name = self._package_name package_name = self._package_name
json_text = resource_files(package_name).joinpath(self._json_file).read_bytes() json_text = resource_files(package_name).joinpath(self._json_file).read_bytes()
@@ -100,13 +96,13 @@ class ModuleConfig:
if config_file_path is None: if config_file_path is None:
if django_base_dir is None: if django_base_dir is None:
django_base_dir = settings.BASE_DIR django_base_dir = settings.BASE_DIR
config_file_path = os.path.join(django_base_dir, DJANGO_MAIN_MODULE, MODULES_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 = {} self._modules = {}
self._loaded = False self._loaded = False
if not self._lazy_load: # pragma: no cover if not self._lazy_load:
self._load() self._load()
def _lazy_init(self): def _lazy_init(self):
@@ -127,7 +123,7 @@ class ModuleConfig:
if 'modules' in data: if 'modules' in data:
for meta_dict in data['modules']: for meta_dict in data['modules']:
module_name = meta_dict['package'] module_name = meta_dict['package']
self._modules[module_name] = ModuleMeta(module_name, load=False) self._modules[module_name] = ModuleMeta(module_name)
self._modules[module_name].load_from_dict(meta_dict) self._modules[module_name].load_from_dict(meta_dict)
self._loaded = True self._loaded = True
+11 -14
View File
@@ -5,8 +5,7 @@ import sys
from importlib.resources import files as resource_files from importlib.resources import files as resource_files
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
from dav_base.config import DJANGO_MAIN_MODULE, MODULE_APP_SETTINGS_PREFIX from dav_base.config.modules import DJANGO_MAIN_MODULE, ModuleConfig
from dav_base.config.modules import ModuleConfig
VERSION = '0.1' VERSION = '0.1'
@@ -95,48 +94,46 @@ class AdminCommand: # pylint: disable=too-few-public-methods
config = ModuleConfig(django_base_dir=django_base_dir) config = ModuleConfig(django_base_dir=django_base_dir)
config.save() config.save()
input_file = resource_files(__package__).joinpath('django_project_config', input_file = resource_files(__package__).joinpath('django_project_config', 'additional_settings.py')
'additional_settings.py')
output_file = os.path.join(django_base_dir, django_main_module, 'settings.py') output_file = os.path.join(django_base_dir, django_main_module, 'settings.py')
with open(output_file, 'ab') as f: with open(output_file, 'ab') as f:
f.write(input_file.read_bytes()) f.write(input_file.read_bytes())
input_file = resource_files(__package__).joinpath('django_project_config', input_file = resource_files(__package__).joinpath('django_project_config', 'urls.py')
'urls.py')
output_file = os.path.join(django_base_dir, django_main_module, 'urls.py') output_file = os.path.join(django_base_dir, django_main_module, 'urls.py')
with open(output_file, 'wb') as f: with open(output_file, 'wb') as f:
f.write(input_file.read_bytes()) f.write(input_file.read_bytes())
input_file = resource_files(__package__).joinpath('django_project_config', input_file = resource_files(__package__).joinpath('django_project_config', 'settings-dav_base.py')
'{prefix}dav_base.py'.format(prefix=MODULE_APP_SETTINGS_PREFIX)) output_file = os.path.join(django_base_dir, django_main_module, 'settings-dav_base.py')
output_file = os.path.join(django_base_dir,
django_main_module,
'{prefix}dav_base.py'.format(prefix=MODULE_APP_SETTINGS_PREFIX))
with open(output_file, 'wb') as f: with open(output_file, 'wb') as f:
f.write(input_file.read_bytes()) f.write(input_file.read_bytes())
return posix.EX_OK return posix.EX_OK
def _subcmd_enable_module(self, cmd_args): def _subcmd_enable_module(self, cmd_args):
django_main_module = DJANGO_MAIN_MODULE
django_base_dir = cmd_args.path django_base_dir = cmd_args.path
module_name = cmd_args.module module_name = cmd_args.module
sys.path.append(django_base_dir) sys.path.append(django_base_dir)
os.environ['DJANGO_SETTINGS_MODULE'] = '{}.settings'.format(DJANGO_MAIN_MODULE) os.environ['DJANGO_SETTINGS_MODULE'] = '{}.settings'.format(django_main_module)
execute_from_command_line(['manage.py', 'enable_module', module_name]) execute_from_command_line(['manage.py', 'enable_module', module_name])
return posix.EX_OK return posix.EX_OK
def _subcmd_disable_module(self, cmd_args): def _subcmd_disable_module(self, cmd_args):
django_main_module = DJANGO_MAIN_MODULE
django_base_dir = cmd_args.path django_base_dir = cmd_args.path
module_name = cmd_args.module module_name = cmd_args.module
sys.path.append(django_base_dir) sys.path.append(django_base_dir)
os.environ['DJANGO_SETTINGS_MODULE'] = '{}.settings'.format(DJANGO_MAIN_MODULE) os.environ['DJANGO_SETTINGS_MODULE'] = '{}.settings'.format(django_main_module)
execute_from_command_line(['manage.py', 'disable_module', module_name]) execute_from_command_line(['manage.py', 'disable_module', module_name])
return posix.EX_OK return posix.EX_OK
def _subcmd_list_modules(self, cmd_args): def _subcmd_list_modules(self, cmd_args):
django_main_module = DJANGO_MAIN_MODULE
django_base_dir = cmd_args.path django_base_dir = cmd_args.path
sys.path.append(django_base_dir) sys.path.append(django_base_dir)
os.environ['DJANGO_SETTINGS_MODULE'] = '{}.settings'.format(DJANGO_MAIN_MODULE) os.environ['DJANGO_SETTINGS_MODULE'] = '{}.settings'.format(django_main_module)
execute_from_command_line(['manage.py', 'list_modules']) execute_from_command_line(['manage.py', 'list_modules'])
return posix.EX_OK return posix.EX_OK
+5 -2
View File
@@ -48,7 +48,7 @@ class AbstractMail: # pylint: disable=too-few-public-methods
def _get_reply_to(self): def _get_reply_to(self):
return None return None
def send(self): def send(self, fail_silently=False):
subject = self._get_subject() subject = self._get_subject()
body = self._get_body() body = self._get_body()
sender = self._get_sender() sender = self._get_sender()
@@ -56,5 +56,8 @@ class AbstractMail: # pylint: disable=too-few-public-methods
reply_to = self._get_reply_to() reply_to = self._get_reply_to()
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:
logger.info('Fake sending %s to %s', self.__class__.__name__, recipients)
else:
logger.info('Send %s to %s', self.__class__.__name__, recipients) logger.info('Send %s to %s', self.__class__.__name__, recipients)
email.send() email.send(fail_silently=fail_silently)
@@ -3,8 +3,7 @@ from importlib.resources import files as resource_files
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from dav_base.config import DJANGO_MAIN_MODULE, MODULE_APP_SETTINGS_PREFIX from dav_base.config.modules import DJANGO_MAIN_MODULE, ModuleMeta
from dav_base.config.modules import ModuleMeta
class Command(BaseCommand): class Command(BaseCommand):
@@ -23,9 +22,7 @@ class Command(BaseCommand):
if module_name in config.modules.keys(): if module_name in config.modules.keys():
raise CommandError('Module \'{}\' is already enabled'.format(module_name)) raise CommandError('Module \'{}\' is already enabled'.format(module_name))
settings_file_name = '{prefix}{module_name}.py'.format(prefix=MODULE_APP_SETTINGS_PREFIX, settings_file_name = 'settings-{}.py'.format(module_name)
module_name=module_name)
input_file = resource_files(module_name).joinpath('django_project_config', settings_file_name) input_file = resource_files(module_name).joinpath('django_project_config', settings_file_name)
if input_file.is_file(): if input_file.is_file():
output_file = os.path.join(django_base_dir, django_main_module, settings_file_name) output_file = os.path.join(django_base_dir, django_main_module, settings_file_name)
+1 -2
View File
@@ -13,8 +13,7 @@
* purple #866dac #566088 #e1d8f0 #c2b0e1 #a694c2 #866dac #8067a8 #2f263c #5a4876 -------- MTB - ----- ------ * purple #866dac #566088 #e1d8f0 #c2b0e1 #a694c2 #866dac #8067a8 #2f263c #5a4876 -------- MTB - ----- ------
* plum #aa6c95 #784c69 #f0d4e7 #e6b0d4 #be91af #aa6c95 #a66691 #3c2635 #764867 -------- ----- famil ------ * plum #aa6c95 #784c69 #f0d4e7 #e6b0d4 #be91af #aa6c95 #a66691 #3c2635 #764867 -------- ----- famil ------
* brown #??? #??? #??? #??? #925f36 #??? #??? #??? #??? -------- ----- ----- ------ * brown #??? #??? #??? #??? #925f36 #??? #??? #??? #??? -------- ----- ----- ------
* black #333 #??? #333 #??? #??? #??? #??? #??? #??? -------- ----- ----- clear * black #??? #??? #??? #??? #??? #??? #??? #??? #??? -------- ----- ----- clear
* gray #??? #??? #878787 #??? #??? #??? #??? #??? #??? -------- ----- ----- ------
* white #??? #??? #??? #??? #??? #??? #??? #??? #??? -------- ----- ----- ------ * white #??? #??? #??? #??? #??? #??? #??? #??? #??? -------- ----- ----- ------
*/ */
+1 -1
View File
@@ -1 +1 @@
{# This template is used by software tests #}{{ var1 }}{{ var2 }}MAILBODY {# This template is used by software tests #}MAILBODY
+2 -2
View File
@@ -10,7 +10,7 @@ def do_include_if_exist(parser, token):
include_if_exist support an optional keyword 'default', which must be followed by the name of a default template. include_if_exist support an optional keyword 'default', which must be followed by the name of a default template.
The default template will be included, if the first template does not exist. The default template will be included, if the first template does not exist.
If also the default template does not exist, the behavior is the same as for the original (builtin) include tag. If also the default template does not exist, the behaviour is the same as for the original (builtin) include tag.
If no default template is given and the first template does not exist an empty string will be returned. If no default template is given and the first template does not exist an empty string will be returned.
""" """
@@ -27,7 +27,7 @@ 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 believe, this ist not the correct way to do things. # 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: # But without setting default_node.origin here the following AttributeError will be risen within the tests:
# AttributeError: 'IncludeNode' object has no attribute 'origin' # AttributeError: 'IncludeNode' object has no attribute 'origin'
default_node.origin = template.Origin(name='<unknown_source>', template_name=None) default_node.origin = template.Origin(name='<unknown_source>', template_name=None)
-4
View File
@@ -1,4 +0,0 @@
{
"url_prefix": "test",
"app_config": ".apps.AppConfig"
}
-8
View File
@@ -1,8 +0,0 @@
from django.urls import re_path
from django.views import generic
app_name = 'fake_app'
urlpatterns = [
re_path(r'^$', generic.TemplateView.as_view(), name='root'),
]
+7 -23
View File
@@ -12,13 +12,10 @@ 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 ExpectedConditions from selenium.webdriver.support import expected_conditions as ExpectedConditions
from ..config.apps import DefaultSetting
class AppSetting: # pylint: disable=too-few-public-methods class AppSetting: # pylint: disable=too-few-public-methods
def __init__(self, name, default, of=None): def __init__(self, name, of=None):
self.name = name self.name = name
self.default = default
self.of = of self.of = of
@@ -29,29 +26,16 @@ class AppsTestCase(SimpleTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
if self.app_config: if self.app_config:
self.default_settings = self.app_config.default_settings
self.configured_settings = self.app_config.settings self.configured_settings = self.app_config.settings
else: else:
self.default_settings = ()
self.configured_settings = None self.configured_settings = None
def test_defaults(self): def test_settings(self):
defaults = {} config = self.configured_settings
for d in self.default_settings:
self.assertIsInstance(d, DefaultSetting)
defaults[d.name] = d.value
for setting in self.settings: for setting in self.settings:
name = setting.name name = setting.name
self.assertIn(name, defaults.keys()) self.assertTrue(hasattr(config, name), 'Settings do not contain {}'.format(name))
self.assertEqual(defaults[name], setting.default, 'Default value of {} is not correct'.format(name)) value = getattr(config, name)
def test_configured_settings(self):
for setting in self.settings:
name = setting.name
self.assertTrue(hasattr(self.configured_settings, name), 'Settings do not contain {}'.format(name))
value = getattr(self.configured_settings, name)
of = setting.of of = setting.of
if of is not None: if of is not None:
self.assertIsInstance(value, of) self.assertIsInstance(value, of)
@@ -86,8 +70,8 @@ class EmailTestMixin:
for expected_recipient in recipients: for expected_recipient in recipients:
if isinstance(expected_recipient, AbstractUser): if isinstance(expected_recipient, AbstractUser):
expected_recipient = '"%s" <%s>' % (expected_recipient.get_full_name(), expected_recipient.email) expected_recipient = '"%s" <%s>' % (expected_recipient.get_full_name(), expected_recipient.email)
real_recipients = mail.recipients() recipients = mail.recipients()
self.assertIn(expected_recipient, real_recipients) self.assertIn(expected_recipient, recipients)
def assertSubject(self, mail, subject): # pylint: disable=invalid-name def assertSubject(self, mail, subject): # pylint: disable=invalid-name
expected_subject = '{} {}'.format(self.email_subject_prefix, subject) expected_subject = '{} {}'.format(self.email_subject_prefix, subject)
+4 -3
View File
@@ -1,4 +1,5 @@
from django.apps import apps from django.apps import apps
from six import string_types
from .generic import AppSetting, AppsTestCase from .generic import AppSetting, AppsTestCase
@@ -7,7 +8,7 @@ class TestCase(AppsTestCase):
app_config = apps.get_app_config('dav_base') app_config = apps.get_app_config('dav_base')
settings = ( settings = (
AppSetting('email_sender', None, str), AppSetting('email_sender', string_types),
AppSetting('email_base_url', None, str), AppSetting('email_base_url', string_types),
AppSetting('email_subject_prefix', '', str), AppSetting('email_subject_prefix', string_types),
) )
@@ -169,3 +169,15 @@ class AppConfigTestCase(SimpleTestCase):
self.assertEqual(app_config.__class__, RealAppConfig) self.assertEqual(app_config.__class__, RealAppConfig)
self.assertIsInstance(app_config, AppConfig) self.assertIsInstance(app_config, AppConfig)
self.assertEqual(app_config.settings.__class__, AppSettings) self.assertEqual(app_config.settings.__class__, AppSettings)
class ModuleMetaTestCase(SimpleTestCase):
def test_some(self):
# TODO
pass
class ModuleConfigTestCase(SimpleTestCase):
def test_some(self):
# TODO
pass
-240
View File
@@ -1,240 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
import json
import os
import shutil
from unittest.mock import patch
from django.core.exceptions import ImproperlyConfigured
from django.test import SimpleTestCase
from django.urls import URLResolver
from ..config.modules import ModuleMeta, ModuleConfig
from .utils import mkdtemp
TMP_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'tmp')
class ModuleMetaTestCase(SimpleTestCase):
def test_init(self):
mm = ModuleMeta('dav_base.tests.fake_app1')
self.assertEqual(mm.package, 'dav_base.tests.fake_app1')
self.assertEqual(mm.app, 'dav_base.tests.fake_app1.apps.AppConfig')
self.assertEqual(mm.additional_apps, [])
self.assertEqual(mm.url_prefix, 'test')
self.assertEqual(mm.url_namespace, 'dav_base_tests_fake_app1')
self.assertEqual(mm.root_url_name, 'dav_base_tests_fake_app1:root')
pattern = mm.url_conf_pattern
self.assertIsInstance(pattern, URLResolver)
self.assertEqual('^test/', str(pattern.pattern))
def test_module_not_exists(self):
with self.assertRaises(ModuleNotFoundError):
_ = ModuleMeta('dav_base.tests.non_existent_app')
def test_init_without_load(self):
app_name = 'dav_base.tests.non_existent_app'
app_namespace = app_name.replace('.', '_')
mm = ModuleMeta(app_name, load=False)
self.assertEqual(mm.package, app_name)
self.assertEqual(mm.app, app_name)
self.assertEqual(mm.url_prefix, app_name)
self.assertEqual(mm.url_namespace, app_namespace)
self.assertEqual(mm.root_url_name, app_namespace + ':root')
def test_url_config_not_exists(self):
mm = ModuleMeta('dav_base.tests.fake_app1')
dd = {'package': 'dav_base'}
mm.load_from_dict(dd)
with self.assertRaises(ImproperlyConfigured):
_ = mm.url_conf_pattern
def test_str(self):
app_name = 'dav_base.tests.fake_app1'
mm = ModuleMeta(app_name)
self.assertEqual(str(mm), '- {}'.format(app_name))
def test_load_from_dict(self):
mm = ModuleMeta('dav_base.tests.fake_app1')
dd = {'package': 'dav_base2.foo',
'app_config': 'MyApp.MyAppConfig',
'additional_apps': ['test1', 'test2.subtest'],
'url_prefix': 'test_url_prefix',
}
expected_namespace = dd['package'].replace('.', '_')
mm.load_from_dict(dd)
self.assertEqual(mm.package, dd['package'])
self.assertEqual(mm.app, dd['app_config'])
self.assertEqual(mm.additional_apps, dd['additional_apps'])
self.assertEqual(mm.url_prefix, dd['url_prefix'])
self.assertEqual(mm.url_namespace, expected_namespace)
self.assertEqual(mm.root_url_name, expected_namespace + ':root')
dd = {'package': 'dav_base2.bar'}
expected_namespace = dd['package'].replace('.', '_')
mm.load_from_dict(dd)
self.assertEqual(mm.url_prefix, dd['package'])
self.assertEqual(mm.app, dd['package'])
self.assertEqual(mm.additional_apps, [])
self.assertEqual(mm.url_prefix, dd['package'])
self.assertEqual(mm.url_namespace, expected_namespace)
self.assertEqual(mm.root_url_name, expected_namespace + ':root')
def test_dump_as_dict(self):
mm = ModuleMeta('dav_base.tests.fake_app1')
dd_in = {'package': 'dav_base2.foo',
'app_config': '.mymod.MyAppConfig',
'additional_apps': ['test1'],
'url_prefix': 'test_url_prefix',
}
mm.load_from_dict(dd_in)
dd_out = mm.dump_as_dict()
self.assertEqual(dd_in, dd_out)
class ModuleConfigTestCase(SimpleTestCase):
def setUp(self):
prefix = 'dav_base.tests.test_config_modules-{datetime}-'.format(
datetime=datetime.datetime.now().strftime('%Y%m%d-%H%M')
)
self.temp_dir = mkdtemp(prefix=prefix, base_dir=TMP_BASE_DIR)
self.config_dir = os.path.join(self.temp_dir, 'main')
os.makedirs(self.config_dir)
self.config_file = os.path.join(self.config_dir, 'module_config.json')
def tearDown(self):
shutil.rmtree(self.temp_dir)
def test_default_config_file_not_exists(self):
with patch('dav_base.config.modules.settings') as mock_settings:
mock_settings.BASE_DIR = self.temp_dir
mc = ModuleConfig()
self.assertEqual(mc.modules, {})
def test_default_config_file_exists(self):
config_data = {
'modules': [
{'package': 'pkg1'},
{'package': 'pkg2'},
]
}
with open(self.config_file, 'w', encoding='ascii') as f:
json.dump(config_data, f)
with patch('dav_base.config.modules.settings') as mock_settings:
mock_settings.BASE_DIR = self.temp_dir
mc = ModuleConfig()
self.assertEqual(list(mc.modules.keys()), ['pkg1', 'pkg2'])
def test_django_base_dir_parameter(self):
config_data = {
'modules': [
{'package': 'pkgA'},
{'package': 'pkgB'},
]
}
with open(self.config_file, 'w', encoding='ascii') as f:
json.dump(config_data, f)
with patch('dav_base.config.modules.settings') as mock_settings:
mock_settings.BASE_DIR = self.config_dir # No config file there
mc = ModuleConfig()
self.assertEqual(mc.modules, {})
mc = ModuleConfig(django_base_dir=self.temp_dir)
self.assertEqual(list(mc.modules.keys()), ['pkgA', 'pkgB'])
def test_custom_config_file_path(self):
config_data = {
'modules': [
{'package': 'pkgX'},
{'package': 'pkgY'},
]
}
config_file = os.path.join(self.config_dir, 'test.json')
with open(config_file, 'w', encoding='ascii') as f:
json.dump(config_data, f)
with patch('dav_base.config.modules.settings') as mock_settings:
mock_settings.BASE_DIR = self.temp_dir
mc = ModuleConfig(config_file_path=config_file)
self.assertEqual(list(mc.modules.keys()), ['pkgX', 'pkgY'])
def test_custom_config_file_path_and_django_base_dir(self):
config_data = {
'modules': [
{'package': 'pkg11'},
{'package': 'pkg22'},
]
}
config_file = os.path.join(self.config_dir, 'test.json')
with open(config_file, 'w', encoding='ascii') as f:
json.dump(config_data, f)
with patch('dav_base.config.modules.settings') as mock_settings:
mock_settings.BASE_DIR = self.temp_dir
mc = ModuleConfig(config_file_path=config_file, django_base_dir=self.config_dir)
self.assertEqual(list(mc.modules.keys()), ['pkg11', 'pkg22'])
def test_save(self):
config_data = {
'modules': [
{'package': 'pkg_foo'},
{'package': 'pkg_bar'},
{'package': 'pkg_baz'},
{'package': 'pkg_quux', 'url_prefix': 'quux'},
]
}
copy_modules = ['pkg_bar', 'pkg_quux']
expected_config_data = {'modules': [d for d in config_data['modules'] if d['package'] in copy_modules]}
config_file = os.path.join(self.config_dir, 'test.json')
with open(config_file, 'w', encoding='ascii') as f:
json.dump(config_data, f)
with patch('dav_base.config.modules.settings') as mock_settings:
mock_settings.BASE_DIR = self.temp_dir
mc1 = ModuleConfig()
mc2 = ModuleConfig(config_file_path=config_file)
for module_name in copy_modules:
mc1.modules[module_name] = mc2.modules[module_name]
mc1.save()
with open(self.config_file, 'r', encoding='ascii') as f:
config_data = json.load(f)
self.assertEqual(config_data, expected_config_data)
def test_save_empty(self):
self.assertFalse(os.path.exists(self.config_file))
with patch('dav_base.config.modules.settings') as mock_settings:
mock_settings.BASE_DIR = self.temp_dir
mc = ModuleConfig()
mc.save()
self.assertTrue(os.path.isfile(self.config_file))
with open(self.config_file, 'r', encoding='ascii') as f:
config_data = json.load(f)
self.assertEqual(config_data, {'modules': []})
def test_save_overwrite(self):
with patch('dav_base.config.modules.settings') as mock_settings:
mock_settings.BASE_DIR = self.temp_dir
mc = ModuleConfig()
mc.save()
self.assertTrue(os.path.isfile(self.config_file))
mc.modules['mod1'] = ModuleMeta('mod1', load=False)
mc.save()
with open(self.config_file, 'r', encoding='ascii') as f:
config_data = json.load(f)
self.assertEqual(config_data, {'modules': [{'package': 'mod1'}]})
+1 -21
View File
@@ -43,24 +43,4 @@ class TestCase(EmailTestMixin, SimpleTestCase):
self.assertSender(mail) self.assertSender(mail)
self.assertRecipients(mail, [recipient]) self.assertRecipients(mail, [recipient])
self.assertSubject(mail, '') self.assertSubject(mail, '')
self.assertBody(mail, 'MAILBODY\n') self.assertBody(mail, 'MAILBODY')
def test_send_extra_context(self):
recipient = 'root@localhost'
class ConcreteMail(AbstractMail): # pylint: disable=too-few-public-methods
_template_name = MAIL_TEMPLATE
def _get_recipients(self):
return [recipient]
def _get_body(self, context=None):
context = {'var1': 'Here is',
'var2': ' extra context. '}
return super()._get_body(context)
email = ConcreteMail()
email.send()
mail = django_mail.outbox[0]
self.assertBody(mail, 'Here is extra context. MAILBODY\n')
+3 -8
View File
@@ -6,23 +6,18 @@ from django.test import SimpleTestCase
class TemplateTagsTestCase(SimpleTestCase): class TemplateTagsTestCase(SimpleTestCase):
def test_include_if_exist_without_argument(self): def test_include_if_exist_without_argument(self):
template_name = 'dav_base/tests/include_if_exist_without_argument.html' template_name = 'dav_base/tests/include_if_exist_without_argument.html'
with self.assertRaises(TemplateSyntaxError) as cm: with self.assertRaises(TemplateSyntaxError):
get_template(template_name) get_template(template_name)
self.assertEqual(str(cm.exception), '\'include_if_exist\' tag takes at least one argument:'
' the name of the template to be included')
def test_include_if_exist_with_unexpected_argument(self): def test_include_if_exist_with_unexpected_argument(self):
template_name = 'dav_base/tests/include_if_exist_with_unexpected_argument.html' template_name = 'dav_base/tests/include_if_exist_with_unexpected_argument.html'
with self.assertRaises(TemplateSyntaxError) as cm: with self.assertRaises(TemplateSyntaxError):
get_template(template_name) get_template(template_name)
self.assertEqual(str(cm.exception), 'Unknown argument for \'include_if_exist\' tag: "\'bogus\'".')
def test_include_if_exist_default_without_argument(self): def test_include_if_exist_default_without_argument(self):
template_name = 'dav_base/tests/include_if_exist_default_without_argument.html' template_name = 'dav_base/tests/include_if_exist_default_without_argument.html'
with self.assertRaises(TemplateSyntaxError) as cm: with self.assertRaises(TemplateSyntaxError):
get_template(template_name) get_template(template_name)
self.assertEqual(str(cm.exception), '\'default\' keyword in \'include_if_exist\' tag requires another arguments:'
' the name of the default template')
def test_include_if_exist_default_missing(self): def test_include_if_exist_default_missing(self):
template_name = 'dav_base/tests/include_if_exist_default_missing.html' template_name = 'dav_base/tests/include_if_exist_default_missing.html'
+16 -37
View File
@@ -10,49 +10,28 @@ class DAVNumberValidatorTestCase(ValidatorTestMixin, SimpleTestCase):
def test_valid_data(self): def test_valid_data(self):
data = ( data = (
'001/00/1*0001*1869*1869*01011800',
'999/99/999999*9999*2025*2026',
'131/00/654321*1000*1999',
'131/00/54321*1000',
'131/00/1', '131/00/1',
'076/22/012345',
'1', '1',
'23', '22',
'4567', '333',
'678912', '1/22/22',
'999999*9999', '54321/54321/54321*54321',
'54321x0001x2004', '54321/54321/54321*54321*4321*4321',
'54321 0001 2004 2014', '54321/54321/54321*54321*4321*4321*87654321',
'4321*0202x1999 2022*30121999', '54321*54321',
'54321*54321*4321*4321',
'54321*54321*4321*4321*87654321',
) )
self.assertValid(self.validator, data) self.assertValid(self.validator, data)
def test_invalid_data(self): def test_invalid_data(self):
data = ( data = (
'1/00/1', # Sektionsnummer nicht dreistellig '131/00/',
'21/00/1', # Sektionsnummer nicht dreistellig '1/1/1',
'4321/00/1', # Sektionsnummer nicht dreistellig '1/1',
'131/1/1', # Ortsgruppennummer nicht zweistellig 'abc',
'131/321/1', # Ortsgruppennummer nicht zweistellig '131/00/abc',
'131/00/', # Fehlende Mitgliedsnummer 'abc/00/131',
'131/00', # Fehlende Mitgliedsnummer '131/ab/131',
'7654321', # Mitgliedsnummer mehr als sechs Stellen
'999999*321', # Kategorienummer nicht vierstellig
'999999*54321', # Kategorienummer nicht vierstellig
'999999*9999*321', # DAV-Eintrittsjahr nicht vierstellig
'999999*9999*54321', # DAV-Eintrittsjahr nicht vierstellig
'999999*9999*9999*321', # Sektions-Eintrittsjahr nicht vierstellig
'999999*9999*9999*54321', # Sektions-Eintrittsjahr nicht vierstellig
'999999*9999*9999*9999*7654321', # Geburtsdatum nicht achtstellig
'999999*9999*9999*9999*987654321', # Geburtsdatum nicht achtstellig
'', # Leerstring
' 1', # Leerzeichen am Anfang
'54321 0001 2004 2014 ', # Leerzeichen am Ende
'abc', # Nicht numerisch
'131/00/abc', # Nicht numerisch
'abc/00/131', # Nicht numerisch
'131/ab/131', # Nicht numerisch
'131-00-131', # - statt /
'131/00/131-0001', # - statt * oder x oder Leerzeichen
) )
self.assertInvalid(self.validator, data) self.assertInvalid(self.validator, data)
+5 -45
View File
@@ -1,47 +1,10 @@
from unittest.mock import patch from django.test import SimpleTestCase
from django.test import SimpleTestCase, override_settings
from django.urls import NoReverseMatch
from ..views import RootView from ..views import RootView
class DummyModuleMeta:
def __init__(self, package):
self.package = package
@property
def root_url_name(self):
return self.package.replace('.', '_') + ':root'
class DummyModuleConfig:
def __init__(self, modules):
# modules: dict-like mapping name -> DummyMeta
self._modules = modules
@property
def modules(self):
return self._modules
class ViewsTestCase(SimpleTestCase): class ViewsTestCase(SimpleTestCase):
def test_root(self): def test_root(self):
modules = {
'module1': DummyModuleMeta('pkg1'),
'module2': DummyModuleMeta('pkg2'),
'moduleC': DummyModuleMeta('pkgC'),
'moduleD': DummyModuleMeta('pkgD'),
}
expected_root_urls = [
('pkg1', 'pkg1:root'), ('pkg2', 'pkg2:root'), ('pkgD', 'pkgD:root')
]
def fake_reverse(name):
if name == 'pkgC:root':
raise NoReverseMatch()
return '/'
with override_settings(MODULE_CONFIG=DummyModuleConfig(modules)):
with patch('dav_base.views.reverse', side_effect=fake_reverse) as mocked_reverse:
view = RootView() view = RootView()
template_names = view.get_template_names() template_names = view.get_template_names()
self.assertEqual(len(template_names), 1) self.assertEqual(len(template_names), 1)
@@ -49,16 +12,13 @@ class ViewsTestCase(SimpleTestCase):
context = view.get_context_data() context = view.get_context_data()
self.assertIn('root_urls', context) self.assertIn('root_urls', context)
self.assertIsInstance(context['root_urls'], list) self.assertIsInstance(context['root_urls'], list)
self.assertEqual(context['root_urls'], expected_root_urls)
called_names = [call.args[0] for call in mocked_reverse.call_args_list]
self.assertEqual(len(called_names), len(modules))
for m in modules.values():
self.assertIn(m.root_url_name, called_names)
def test_integrated_root(self): def test_integrated_root(self):
with override_settings(MODULE_CONFIG=DummyModuleConfig({})):
response = self.client.get('/') response = self.client.get('/')
self.assertTemplateUsed(response, 'dav_base/root.html') self.assertTemplateUsed(response, 'dav_base/root.html')
self.assertIn('root_urls', response.context, '\'root_urls\' not in context of root view') self.assertIn('root_urls', response.context, '\'root_urls\' not in context of root view')
self.assertIsInstance(response.context['root_urls'], list) self.assertIsInstance(response.context['root_urls'], list)
self.assertEqual(response.context['root_urls'], [])
# TODO
# Maybe we should set a defined module config, so we could
# test the content of the root_urls context variable.
+1
View File
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os import os
from tempfile import mkdtemp as _mkdtemp from tempfile import mkdtemp as _mkdtemp
+5 -5
View File
@@ -4,10 +4,10 @@ from django.utils.translation import gettext_lazy as _
DAVNumberValidator = RegexValidator(r'^' DAVNumberValidator = RegexValidator(r'^'
r'([0-9]{3}/[0-9]{2}/)?' # Optional: <Sektionsnummer 3-stellig>/<Ortsgruppennummer 2-stellig>/ r'([0-9]{1,10}/[0-9]{2,10}/)?'
r'[0-9]{1,6}' # <Mitgliedsnummer 1- bis 6-stellig> r'[0-9]{1,10}'
r'([*x ][0-9]{4})?' # Optional: *<Kategorienummer 4-stellig> r'(\*[0-9]{1,10})?'
r'([*x ][0-9]{4}[*x ][0-9]{4})?' # Optional: *<Jahreszahl DAV-Eintritt>*<Jahreszahl Sektionseintritt> r'(\*[0-9]{4}\*[0-9]{4})?'
r'([*x ][0-9]{8})?' # Optional: *<Geburtsdatum YYYYMMDD> r'([* ][0-9]{8})?'
r'$', r'$',
_('Ungültiges Format.')) _('Ungültiges Format.'))
+3 -1
View File
@@ -10,7 +10,9 @@ class RootView(generic.TemplateView):
c = super().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 = module_meta_obj.root_url_name root_url_name = 'root'
if module_meta_obj.url_namespace:
root_url_name = '%s:%s' % (module_meta_obj.url_namespace, root_url_name)
try: try:
reverse(root_url_name) reverse(root_url_name)
root_urls.append((module_meta_obj.package, root_url_name)) root_urls.append((module_meta_obj.package, root_url_name))
-2
View File
@@ -10,8 +10,6 @@ class ChoiceSet(object):
self._codes = list() self._codes = list()
self._labels = dict() self._labels = dict()
for code, label in choices: for code, label in choices:
if code in self._codes:
raise ValueError(u'Code not unique: {}'.format(code))
self._codes.append(code) self._codes.append(code)
self._labels[code] = label self._labels[code] = label
+3 -5
View File
@@ -20,7 +20,7 @@ ADDITIONAL_COSTS_MAX_LENGTH = COMMON_CHAR_FIELD_LENGTH
class FieldInitial(object): class FieldInitial(object):
_constraint_re = re.compile(r'^(?P<field>[a-z_]+)(?P<op>==)(?P<value>[^= ].*)?$') _constraint_re = re.compile(r'^(?P<field>[a-z_]+)(?P<op>[=]+)(?P<value>.*)$')
def __init__(self, *args): def __init__(self, *args):
self._tuples = [] self._tuples = []
@@ -61,10 +61,8 @@ class FieldInitial(object):
if c_field not in parameters: if c_field not in parameters:
logger.error('FieldInitial: Invalid field: \'%s\'', sub_constraint) logger.error('FieldInitial: Invalid field: \'%s\'', sub_constraint)
continue continue
c_value = c.group('value')
if c_value is None:
c_value = ''
c_op = c.group('op') c_op = c.group('op')
c_value = c.group('value')
if c_op == '==': if c_op == '==':
if parameters[c_field] == c_value: if parameters[c_field] == c_value:
match = True match = True
@@ -72,7 +70,7 @@ class FieldInitial(object):
else: else:
match = False match = False
break break
else: # pragma: no cover else:
logger.error('FieldInitial: Invalid operator: \'%s\'', sub_constraint) logger.error('FieldInitial: Invalid operator: \'%s\'', sub_constraint)
continue continue
else: else:
+19 -31
View File
@@ -2,6 +2,7 @@ import datetime
import logging import logging
import re import re
import pytz import pytz
from six import string_types
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -23,17 +24,15 @@ class Iso8601Serializer:
r')?' r')?'
r'(?P<offset>(?P<offdir>[\-+])(?P<offhours>([01][0-9])|(2[0123]))(:(?P<offmins>[0-5][0-9]))?)?$') r'(?P<offset>(?P<offdir>[\-+])(?P<offhours>([01][0-9])|(2[0123]))(:(?P<offmins>[0-5][0-9]))?)?$')
def __init__(self, value): def __init__(self, instance=None, text=None):
if isinstance(value, datetime.datetime) \ if instance is not None:
or isinstance(value, datetime.date) \ self.instance = instance
or isinstance(value, datetime.time): elif text is not None:
dt_obj = value self.instance = Iso8601Serializer.deserialize(text)
else:
dt_obj = Iso8601Serializer.deserialize(value)
self._serialized = Iso8601Serializer.serialize(dt_obj)
def __str__(self): @property
return self._serialized def str(self):
return Iso8601Serializer.serialize(self.instance)
@classmethod @classmethod
def serialize(cls, value, ignore_unsupported_input=False): def serialize(cls, value, ignore_unsupported_input=False):
@@ -56,26 +55,11 @@ class Iso8601Serializer:
@classmethod @classmethod
def deserialize(cls, value, ignore_unsupported_input=False): def deserialize(cls, value, ignore_unsupported_input=False):
prefix = '{marker}{sep}'.format(marker=cls.marker, sep=cls.separator) prefix = '{marker}{sep}'.format(marker=cls.marker, sep=cls.separator)
if isinstance(value, string_types) and value.startswith(prefix):
if not isinstance(value, str):
if ignore_unsupported_input:
return value
raise TypeError('Expected string type, not {}'.format(value.__class__.__name__))
if not value.startswith(prefix):
if ignore_unsupported_input:
return value
raise ValueError('String must begin with \'{prefix}\''.format(prefix=prefix))
haystack = value[len(prefix):] haystack = value[len(prefix):]
match = cls._re.match(haystack) m = cls._re.match(haystack)
if m is not None:
if match is None: gd = m.groupdict()
if ignore_unsupported_input:
return value
raise ValueError('Format not recognized \'{str}\''.format(str=haystack))
gd = match.groupdict()
if gd['hour'] is None: if gd['hour'] is None:
value = datetime.date(int(gd['year']), int(gd['mon']), int(gd['day'])) value = datetime.date(int(gd['year']), int(gd['mon']), int(gd['day']))
elif gd['year'] is None: elif gd['year'] is None:
@@ -91,8 +75,12 @@ class Iso8601Serializer:
value -= offset value -= offset
elif gd['offdir'] == '-': elif gd['offdir'] == '-':
value += offset value += offset
else: # pragma no cover else:
raise ValueError('Offset format not recognized \'{str}\''.format(str=gd['offset'])) raise ValueError('Offset format not recognized \'{str}\''.format(str=gd['offset']))
value = value.replace(tzinfo=pytz.UTC) value = value.replace(tzinfo=pytz.UTC)
elif not ignore_unsupported_input:
raise ValueError('Format not recognized \'{str}\''.format(str=haystack))
elif not ignore_unsupported_input:
raise ValueError('Expected string type,'
' not {}'.format(value.__class__.__name__))
return value return value
@@ -1,29 +0,0 @@
# Generated by Django 5.2.13 on 2026-06-16 13:21
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dav_events', '0044_alter_event_level'),
]
operations = [
migrations.AlterField(
model_name='eventstatus',
name='bootstrap_context',
field=models.CharField(blank=True, choices=[('default', 'default'), ('primary', 'primary'), ('success', 'success'), ('info', 'info'), ('warning', 'warning'), ('danger', 'danger'), ('dav-purple', 'dav-purple'), ('dav-lime', 'dav-lime'), ('dav-cyan', 'dav-cyan'), ('dav-caramel', 'dav-caramel'), ('dav-mandarin', 'dav-mandarin'), ('dav-brown', 'dav-brown'), ('orange', 'orange'), ('green', 'green'), ('blue', 'blue'), ('yellow', 'yellow'), ('red', 'red'), ('mandarin', 'mandarin'), ('lime', 'lime'), ('cyan', 'cyan'), ('caramel', 'caramel'), ('purple', 'purple'), ('plum', 'plum'), ('black', 'black'), ('white', 'white')], max_length=20),
),
migrations.AlterField(
model_name='participant',
name='dav_number',
field=models.CharField(blank=True, max_length=62, validators=[django.core.validators.RegexValidator('^([0-9]{3}/[0-9]{2}/)?[0-9]{1,6}([*x ][0-9]{4})?([*x ][0-9]{4}[*x ][0-9]{4})?([*x ][0-9]{8})?$', 'Ungültiges Format.')], verbose_name='DAV Mitgliedsnummer'),
),
migrations.AlterField(
model_name='trashedparticipant',
name='dav_number',
field=models.CharField(blank=True, max_length=62, validators=[django.core.validators.RegexValidator('^([0-9]{3}/[0-9]{2}/)?[0-9]{1,6}([*x ][0-9]{4})?([*x ][0-9]{4}[*x ][0-9]{4})?([*x ][0-9]{8})?$', 'Ungültiges Format.')], verbose_name='DAV Mitgliedsnummer'),
),
]
+1 -13
View File
@@ -11,25 +11,13 @@ BOOTSTRAP_CONTEXT_CHOICES = (
('info', 'info'), ('info', 'info'),
('warning', 'warning'), ('warning', 'warning'),
('danger', 'danger'), ('danger', 'danger'),
('black', 'black'),
('dav-purple', 'dav-purple'), ('dav-purple', 'dav-purple'),
('dav-lime', 'dav-lime'), ('dav-lime', 'dav-lime'),
('dav-cyan', 'dav-cyan'), ('dav-cyan', 'dav-cyan'),
('dav-caramel', 'dav-caramel'), ('dav-caramel', 'dav-caramel'),
('dav-mandarin', 'dav-mandarin'), ('dav-mandarin', 'dav-mandarin'),
('dav-brown', 'dav-brown'), ('dav-brown', 'dav-brown'),
('orange', 'orange'),
('green', 'green'),
('blue', 'blue'),
('yellow', 'yellow'),
('red', 'red'),
('mandarin', 'mandarin'),
('lime', 'lime'),
('cyan', 'cyan'),
('caramel', 'caramel'),
('purple', 'purple'),
('plum', 'plum'),
('black', 'black'),
('white', 'white'),
) )
@@ -183,9 +183,9 @@
{% if not event.registration_required %} {% if not event.registration_required %}
<span class="label label-success">{% trans 'Anmeldung nicht erforderlich' %}</span> <span class="label label-success">{% trans 'Anmeldung nicht erforderlich' %}</span>
{% elif is_canceled %} {% elif is_canceled %}
<span class="label label-{{ event.workflow.get_status_list.0.bootstrap_context|default:'default' }}">{% trans 'Veranstaltung abgesagt' %}</span> <span class="label label-dav-mandarin">{% trans 'Veranstaltung abgesagt' %}</span>
{% elif is_realized or is_expired %} {% elif is_realized or is_expired %}
<span class="label label-{{ event.workflow.get_status_list.0.bootstrap_context|default:'default' }}">{% trans 'Veranstaltung beendet' %}</span> <span class="label label-dav-lime">{% trans 'Veranstaltung beendet' %}</span>
{% elif event.registration_closed %} {% elif event.registration_closed %}
<span class="label label-danger">{% trans 'Anmeldung geschlossen' %}</span> <span class="label label-danger">{% trans 'Anmeldung geschlossen' %}</span>
{% elif event.is_deadline_expired %} {% elif event.is_deadline_expired %}
+18 -19
View File
@@ -1,5 +1,4 @@
from django.apps import apps from django.apps import apps
from django.core.exceptions import ImproperlyConfigured
from dav_base.tests.generic import AppSetting, AppsTestCase from dav_base.tests.generic import AppSetting, AppsTestCase
@@ -8,22 +7,22 @@ class TestCase(AppsTestCase):
app_config = apps.get_app_config('dav_events') app_config = apps.get_app_config('dav_events')
settings = ( settings = (
AppSetting('enable_email_on_status_update', False, bool), AppSetting('enable_email_on_status_update', bool),
AppSetting('enable_email_on_update', False, bool), AppSetting('enable_email_on_update', bool),
AppSetting('enable_email_on_registration_closed', False, bool), AppSetting('enable_email_on_registration_closed', bool),
AppSetting('groups_manager_super', [], list), AppSetting('groups_manager_super', list),
AppSetting('groups_manager_w', [], list), AppSetting('groups_manager_w', list),
AppSetting('groups_manager_s', [], list), AppSetting('groups_manager_s', list),
AppSetting('groups_manager_m', [], list), AppSetting('groups_manager_m', list),
AppSetting('groups_manager_k', [], list), AppSetting('groups_manager_k', list),
AppSetting('groups_manager_b', [], list), AppSetting('groups_manager_b', list),
AppSetting('groups_publisher_print', [], list), AppSetting('groups_publisher_print', list),
AppSetting('groups_publisher_web', [], list), AppSetting('groups_publisher_web', list),
AppSetting('groups_publisher_facebook', [], list), AppSetting('groups_publisher_facebook', list),
AppSetting('forms_development_init', False, bool), AppSetting('forms_development_init', bool),
AppSetting('form_initials', {}, dict), AppSetting('form_initials', dict),
AppSetting('matrix_config', ImproperlyConfigured, dict), AppSetting('matrix_config', dict),
AppSetting('publish_before_begin_days', 10, int), AppSetting('publish_before_begin_days', int),
AppSetting('publish_before_deadline_days', 7, int), AppSetting('publish_before_deadline_days', int),
AppSetting('publish_issues', [], list), AppSetting('publish_issues', list),
) )
-191
View File
@@ -1,191 +0,0 @@
# -*- coding: utf-8 -*-
from django.test import SimpleTestCase
from django.utils.translation import gettext_lazy as _
from ..choices import ChoiceSet
class ChoiceSetTestCase(SimpleTestCase):
def setUp(self):
# We need at least three tuples with code and label.
# Codes (i.e. first field of the tuples) must not be in sorted order!
self.tuples = [
('4', 'A single digit number'),
('b', 'A lowercase bee'),
('B', 'A capital bee'),
('W', _('Wanderung')),
('bee', 'A B'),
('CO2', 'Carbon dioxide'),
('H2o', 'Water with mixed case'),
('h2O', 'Water with mixed case'),
('H-O-H', 'Water molecule'),
('O=C=O', 'Carbon dioxide'),
('.dot', 'Label with a dot'),
('_underscore', 'Label with underscore'),
('miXed.ALL_to-gether-98_76.543210', 'Label with mixed characters'),
('üÄö', 'Mixed case Umlaute'),
(' ', 'single blank space as code'),
('', 'Empty code'),
('empty', ''),
('A long code without umlaute for a short label', 'ÜäÖ'),
('True', 'Yes'),
('False', 'No'),
('None', 'Unknown'),
('NONE', 'Often used'),
('OTHER', 'Often used'),
]
self.choiceset = ChoiceSet(self.tuples)
self.code_not_in_choiceset = 'A'
def test_testdata(self):
codes = [c for c, l in self.tuples]
self.assertNotIn(self.code_not_in_choiceset, codes, "Improper test data")
sorted_codes = sorted(codes)
self.assertNotEqual(codes, sorted_codes, "Improper test data:"
" codes of test tuples must not be in sorted order")
def test_code_not_unique(self):
with self.assertRaises(ValueError) as cm:
_ = ChoiceSet([('A', 'First Label'), ('A', 'Second Label')])
self.assertEqual(str(cm.exception), 'Code not unique: A')
def test_len(self):
cs = ChoiceSet([])
self.assertEqual(len(cs), 0)
cs = ChoiceSet([('X', 'Single')])
self.assertEqual(len(cs), 1)
cs = self.choiceset
self.assertEqual(len(cs), len(self.tuples))
def test_getitem(self):
cs = ChoiceSet([])
with self.assertRaises(IndexError):
_ = cs[0]
cs = self.choiceset
code, label = cs[0]
self.assertEqual(code, self.tuples[0][0])
self.assertEqual(label, self.tuples[0][1])
code, label = cs[1]
self.assertEqual(code, self.tuples[1][0])
self.assertEqual(label, self.tuples[1][1])
code, label = cs[-1]
self.assertEqual(code, self.tuples[-1][0])
self.assertEqual(label, self.tuples[-1][1])
code, label = cs[-2]
self.assertEqual(code, self.tuples[-2][0])
self.assertEqual(label, self.tuples[-2][1])
with self.assertRaises(IndexError):
_ = cs[len(cs) + 1]
with self.assertRaises(IndexError):
_ = cs[-len(cs) - 1]
def test_iter_returns_tuples(self):
items = list(self.choiceset)
self.assertEqual(items, self.tuples)
def test_iter_empty_choiceset(self):
items = list(ChoiceSet([]))
self.assertEqual(items, [])
def test_iter_with_for_loop(self):
expected_codes = [c for c, l in self.tuples]
expected_labels = [l for c, l in self.tuples]
codes = []
labels = []
for code, label in self.choiceset:
codes.append(code)
labels.append(label)
self.assertEqual(codes, expected_codes)
self.assertEqual(labels, expected_labels)
def test_iter_multiple_times(self):
first_iteration = list(self.choiceset)
second_iteration = list(self.choiceset)
self.assertEqual(first_iteration, second_iteration)
def test_contains(self):
for c, l in self.tuples:
self.assertIn((c, l), self.choiceset)
self.assertIn((c, l[::-1]), self.choiceset)
self.assertNotIn((self.code_not_in_choiceset, self.tuples[0][1]), self.choiceset)
def test_contains_empty_choiceset(self):
cs = ChoiceSet([])
self.assertNotIn(self.tuples[0], cs)
def test_codes(self):
expected_codes = []
codes = ChoiceSet([]).codes
self.assertEqual(codes, expected_codes)
self.assertIsInstance(codes, list)
expected_codes = [c for c, l in self.tuples]
codes = self.choiceset.codes
self.assertEqual(codes, expected_codes)
self.assertIsInstance(codes, list)
def test_get_label_valid_code(self):
for c, l in self.tuples:
self.assertEqual(self.choiceset.get_label(c), l)
def test_get_label_invalid_code_raises_keyerror(self):
with self.assertRaises(KeyError):
self.choiceset.get_label(self.code_not_in_choiceset)
def test_get_label_case_sensitive(self):
cs = ChoiceSet([('UPPER', 'Label')])
with self.assertRaises(KeyError):
cs.get_label('upper')
def test_sort_numeric_codes(self):
cs = ChoiceSet([('3', 'Three'), ('1', 'One'), ('2', 'Two'), ('10', 'Ten')])
cs.sort()
self.assertEqual(cs.codes, ['1', '10', '2', '3'])
def test_sort_alphanumeric_codes(self):
cs = ChoiceSet([
('B1', 'Three'),
('A2', 'Two'),
('B2', 'Four'),
('A1', 'One'),
('a20', 'Five'),
('a3', 'Six'),
('b1', 'Seven'),
])
cs.sort()
self.assertEqual(cs.codes, ['A1', 'A2', 'B1', 'B2', 'a20', 'a3', 'b1'])
def test_sort_unsortable(self):
cs = ChoiceSet([])
cs.sort()
self.assertEqual(cs.codes, [])
cs = ChoiceSet([('X', 'Single')])
cs.sort()
self.assertEqual(cs.codes, ['X'])
def test_sort_already_sorted(self):
cs = ChoiceSet([('1', 'One'), ('2', 'Two'), ('3', 'Three')])
cs.sort()
self.assertEqual(cs.codes, ['1', '2', '3'])
def test_sort_preserves_labels(self):
cs = ChoiceSet([('B', 'Two'), ('A', 'One'), ('C', 'Three')])
cs.sort()
self.assertEqual(cs.get_label('A'), 'One')
self.assertEqual(cs.get_label('B'), 'Two')
self.assertEqual(cs.get_label('C'), 'Three')
def test_sort_test_data(self):
expected_codes = [c for c, l in self.tuples]
expected_codes.sort()
cs = ChoiceSet(self.tuples)
cs.sort()
self.assertEqual(cs.codes, expected_codes)
-345
View File
@@ -1,345 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from unittest import TestCase
from ..config import FieldInitial
class FieldInitialTestCase(TestCase):
def test_single_argument(self):
value = 'a value'
fi = FieldInitial(value)
session = {'mode': 'a mode'}
output = fi.get_value(session)
self.assertEqual(output, value)
def test_single_argument_empty_session(self):
value = 'a value'
fi = FieldInitial(value)
session = {}
output = fi.get_value(session)
self.assertEqual(output, value)
def test_non_string_value(self):
values = (42, False, None)
for value in values:
fi = FieldInitial(value)
session = {}
output = fi.get_value(session)
self.assertEqual(output, value)
def test_single_simple_constraint_session_match(self):
args = (
'sport==heli skiing', 'not allowed',
)
fi = FieldInitial(*args)
session = {'sport': 'heli skiing'}
output = fi.get_value(session)
self.assertEqual(output, 'not allowed')
def test_single_simple_constraint_session_not_match(self):
args = (
'sport==heli skiing', 'not allowed',
)
fi = FieldInitial(*args)
session = {'sport': 'Heli Skiing'}
output = fi.get_value(session)
self.assertEqual(output, None)
session = {'sport': 'heli'}
output = fi.get_value(session)
self.assertEqual(output, None)
session = {'mode': 'heli skiing', 'sport': 'biking'}
output = fi.get_value(session)
self.assertEqual(output, None)
def test_single_simple_constraint_empty_session(self):
args = (
'sport==heli skiing', 'not allowed',
)
fi = FieldInitial(*args)
session = {}
output = fi.get_value(session)
self.assertEqual(output, None)
def test_empty_value_in_constraint(self):
args = (
'sport==', 'lazy',
)
fi = FieldInitial(*args)
session = {'sport': ''}
output = fi.get_value(session)
self.assertEqual(output, 'lazy')
session = {'sport': 'any'}
output = fi.get_value(session)
self.assertEqual(output, None)
def test_invalid_constraints(self):
invalid_constraints = (
'sport', # Not in <key><operator><value> form
'sport ==hiking', # Blank before operator
'sport== hiking', # Blank after operator
'sport=hiking', # Invalid operator
'sport===hiking', # Invalid operator
'sport!=hiking', # Invalid operator
'==hiking', # Missing key
)
for constraint in invalid_constraints:
fi = FieldInitial(constraint, 'ignored', 'mode==joint', 'recognized')
with self.assertLogs('dav_events.config', 'ERROR') as cm:
output = fi.get_value({'mode': 'joint'})
self.assertEqual(output, 'recognized')
self.assertStartsWith(cm.output[0], 'ERROR:dav_events.config:FieldInitial: Invalid constraint: ')
def test_valid_constraint_keys(self):
valid_keys = ('mode', 'sport', 'level', 'overnight', 'country', 'terrain', 'transport')
for key in valid_keys:
fi = FieldInitial('{}==True'.format(key), 'matched')
if key == 'overnight':
session = {'last_day': 'True'}
else:
session = {key: 'True'}
with self.assertNoLogs('dav_events.config'):
output = fi.get_value(session)
self.assertEqual(output, 'matched', key)
def test_ignore_invalid_constraint_keys(self):
invalid_keys = ('ski_lift', 'last_day', 'location')
for key in invalid_keys:
fi = FieldInitial('{}==True'.format(key), 'ignored', 'mode==joint', 'matched')
session = {key: 'True', 'mode': 'joint'}
with self.assertLogs('dav_events.config', 'ERROR') as cm:
output = fi.get_value(session)
self.assertEqual(output, 'matched')
self.assertStartsWith(cm.output[0], 'ERROR:dav_events.config:FieldInitial: Invalid field: ')
def test_multiple_simple_constraints(self):
args = (
'mode==guided', 'guide',
'sport==biking', 'bicycle',
)
fi = FieldInitial(*args)
session = {}
output = fi.get_value(session)
self.assertEqual(output, None)
session = {'mode': 'joint'}
output = fi.get_value(session)
self.assertEqual(output, None)
session = {'sport': 'biking'}
output = fi.get_value(session)
self.assertEqual(output, 'bicycle')
session = {'mode': 'joint', 'sport': 'hiking'}
output = fi.get_value(session)
self.assertEqual(output, None)
session = {'mode': 'joint', 'sport': 'biking'}
output = fi.get_value(session)
self.assertEqual(output, 'bicycle')
session = {'mode': 'guided', 'sport': 'hiking'}
output = fi.get_value(session)
self.assertEqual(output, 'guide')
session = {'mode': 'guided', 'sport': 'biking'}
output = fi.get_value(session)
self.assertEqual(output, 'guide')
def test_or(self):
args = (
'sport==hiking', 'shoes',
'sport==biking', 'bicycle',
)
fi = FieldInitial(*args)
session = {'sport': 'hiking'}
output = fi.get_value(session)
self.assertEqual(output, 'shoes')
session = {'sport': 'biking'}
output = fi.get_value(session)
self.assertEqual(output, 'bicycle')
session = {'sport': 'swimming'}
output = fi.get_value(session)
self.assertEqual(output, None)
def test_and(self):
args = []
for mode in ('0', '1'):
for sport in ('0', '1'):
for level in ('0', '1'):
constraint_arg = 'mode=={},sport=={},level=={}'.format(mode, sport, level)
value = 'mode{}sport{}level{}'.format(mode, sport, level)
args.append(constraint_arg)
args.append(value)
fi = FieldInitial(*args)
for mode in ('0', '1', ''):
for sport in ('0', '1', '*'):
for level in ('0', '1', None):
session = {'mode': mode, 'sport': sport, 'level': level}
if mode in ('0', '1') and sport in ('0', '1') and level in ('0', '1'):
expected_value = 'mode{}sport{}level{}'.format(mode, sport, level)
else:
expected_value = None
output = fi.get_value(session)
self.assertEqual(output, expected_value)
session = {'mode': '0', 'sport': '0'}
output = fi.get_value(session)
self.assertEqual(output, None)
def test_empty_constraint_only(self):
fi = FieldInitial(
'', 'default_value'
)
session = {}
output = fi.get_value(session)
self.assertEqual(output, 'default_value')
session = {'mode': 'guided'}
output = fi.get_value(session)
self.assertEqual(output, 'default_value')
def test_empty_constraint_match_always(self):
# XXX: Unwanted behavior
fi = FieldInitial(
'', 'default_value',
'mode==guided', 'guide',
)
session = {}
output = fi.get_value(session)
self.assertEqual(output, 'default_value')
session = {'mode': 'joint'}
output = fi.get_value(session)
self.assertEqual(output, 'default_value')
session = {'mode': 'guided'}
output = fi.get_value(session)
self.assertEqual(output, 'default_value')
def test_empty_constraint_as_default(self):
fi = FieldInitial(
'mode==guided', 'guide',
'', 'default_value',
)
session = {}
output = fi.get_value(session)
self.assertEqual(output, 'default_value')
session = {'mode': 'joint'}
output = fi.get_value(session)
self.assertEqual(output, 'default_value')
session = {'mode': 'guided'}
output = fi.get_value(session)
self.assertEqual(output, 'guide')
def test_missing_return_value(self):
# XXX: Unwanted behavior
args = (
'sport==hiking', 'shoes',
'sport==biking',
)
fi = FieldInitial(*args)
session = {'sport': 'hiking'}
output = fi.get_value(session)
self.assertEqual(output, 'shoes')
session = {'sport': 'biking'}
output = fi.get_value(session)
self.assertEqual(output, None)
def test_overnight_affected_by_last_day(self):
fi = FieldInitial(
'overnight==True', 'overnight_value',
'overnight==False', 'not_overnight_value',
)
session = {'last_day': True}
output = fi.get_value(session)
self.assertEqual(output, 'overnight_value')
session = {'last_day': 'Yesterday'}
output = fi.get_value(session)
self.assertEqual(output, 'overnight_value')
session = {'last_day': datetime.date(1900, 1, 1)}
output = fi.get_value(session)
self.assertEqual(output, 'overnight_value')
# XXX: Unwanted behavior
session = {'last_day': 'False'}
output = fi.get_value(session)
self.assertEqual(output, 'overnight_value')
session = {'last_day': False}
output = fi.get_value(session)
self.assertEqual(output, 'not_overnight_value')
session = {}
output = fi.get_value(session)
self.assertEqual(output, 'not_overnight_value')
def test_real_world_example(self):
fi = FieldInitial(
'sport==hiking,level==beginner', 'hike_for_beginners',
'sport==hiking,level==advanced', 'hike_for_pros',
'sport==hiking', 'hike_for_any',
'sport==climbing,level==beginner', '3-5',
'sport==climbing,level==advanced', '5-8',
'sport==biking', 'pedal'
)
# Advanced hiking
self.assertEqual(
fi.get_value({'sport': 'hiking', 'level': 'advanced'}),
'hike_for_pros'
)
# Beginner hiking
self.assertEqual(
fi.get_value({'sport': 'hiking', 'level': 'beginner'}),
'hike_for_beginners'
)
# Hiking on unknown level
self.assertEqual(
fi.get_value({'sport': 'hiking', 'level': 'elite'}),
'hike_for_any'
)
# Hiking but level missing
self.assertEqual(
fi.get_value({'sport': 'hiking'}),
'hike_for_any'
)
# Climbing on unknown level
self.assertEqual(
fi.get_value({'sport': 'climbing', 'level': 'elite'}),
None
)
# Biking
self.assertEqual(
fi.get_value({'sport': 'biking'}),
'pedal'
)
+5 -84
View File
@@ -27,44 +27,7 @@ class Iso8601SerializerTestCase(TestCase):
obj = datetime.datetime.combine(date, time) obj = datetime.datetime.combine(date, time)
yield (obj, text) yield (obj, text)
def test_init_without_arg(self): def test_serialize(self):
with self.assertRaises(TypeError) as cm:
_ = Iso8601Serializer()
self.assertEqual(str(cm.exception), 'Iso8601Serializer.__init__()'
' missing 1 required positional argument: \'value\'')
def test_init_with_date(self):
date_obj = datetime.date(1976, 2, 1)
serializer = Iso8601Serializer(date_obj)
self.assertEqual(str(serializer), 'ISO8601:1976-02-01')
def test_init_with_datetime(self):
datetime_obj = datetime.datetime(1976, 2, 1, 12, 34, 56)
serializer = Iso8601Serializer(datetime_obj)
self.assertEqual(str(serializer), 'ISO8601:1976-02-01T12:34:56')
def test_init_with_time(self):
time_obj = datetime.time(12, 34, 56)
serializer = Iso8601Serializer(time_obj)
self.assertEqual(str(serializer), 'ISO8601:12:34:56')
def test_init_with_valid_text(self):
text = 'ISO8601:1976-02-01'
serializer = Iso8601Serializer(text)
self.assertEqual(str(serializer), text)
def test_init_with_invalid_format(self):
text = 'ISO8601:02.01.1976'
with self.assertRaises(ValueError) as cm:
_ = Iso8601Serializer(text)
self.assertEqual(str(cm.exception), 'Format not recognized \'02.01.1976\'')
def test_init_with_invalid_string(self):
text = '1976-02-01'
with self.assertRaisesRegex(ValueError, 'String must begin with \'ISO8601:\''):
_ = Iso8601Serializer(text)
def test_serialize_valid(self):
test_data = [] test_data = []
# Check date objects # Check date objects
@@ -80,12 +43,10 @@ class Iso8601SerializerTestCase(TestCase):
serialized = Iso8601Serializer.serialize(obj) serialized = Iso8601Serializer.serialize(obj)
self.assertEqual(serialized, expected) self.assertEqual(serialized, expected)
def test_serialize_invalid(self): # Check invalid input
invalid_values = ( invalid_values = (
None, None, True, False,
True, '2019-03-01',
False,
'ISO8601:1976-02-01',
) )
for value in invalid_values: for value in invalid_values:
emsg = ('Expected datetime.datetime, datetime.date or datetime.time,' emsg = ('Expected datetime.datetime, datetime.date or datetime.time,'
@@ -95,7 +56,7 @@ class Iso8601SerializerTestCase(TestCase):
serialized = Iso8601Serializer.serialize(value, ignore_unsupported_input=True) serialized = Iso8601Serializer.serialize(value, ignore_unsupported_input=True)
self.assertEqual(serialized, value) self.assertEqual(serialized, value)
def test_deserialize_valid(self): def test_deserialize(self):
test_data = [] test_data = []
# Check '<YYYY>-<MM>-<DD>' format # Check '<YYYY>-<MM>-<DD>' format
@@ -110,43 +71,3 @@ class Iso8601SerializerTestCase(TestCase):
deserialized = Iso8601Serializer.deserialize(text) deserialized = Iso8601Serializer.deserialize(text)
self.assertIsInstance(deserialized, obj.__class__) self.assertIsInstance(deserialized, obj.__class__)
self.assertEqual(deserialized, obj) self.assertEqual(deserialized, obj)
def test_deserialize_with_valid_offset(self):
test_data = (
('1976-02-01T12:34:56+02', '1976-02-01 10:34:56+00:00'),
('1976-02-01T12:34:56+10:04', '1976-02-01 02:30:56+00:00'),
('1976-02-01T12:34:56-02', '1976-02-01 14:34:56+00:00'),
('1976-02-01T12:34:56-12:14', '1976-02-02 00:48:56+00:00'),
)
for input_text, dt_str in test_data:
text = 'ISO8601:{}'.format(input_text)
deserialized = Iso8601Serializer.deserialize(text)
self.assertEqual(str(deserialized), dt_str)
def test_deserialize_with_invalid_offset(self):
invalid_data = (
'1976-02-01T12:34:56+2',
'1976-02-01T12:34:56+02:02:02',
'1976-02-01T12:34:56+-02',
'1976-02-01T12:34:56=02',
'1976-02-01T12:34:56+CEST',
)
for input_text in invalid_data:
text = 'ISO8601:{}'.format(input_text)
with self.assertRaisesRegex(ValueError, 'Format not recognized \'.*\''):
Iso8601Serializer.deserialize(text)
def test_deserialize_invalid(self):
invalid_data = (
(None, TypeError, 'Expected string type, not NoneType'),
(True, TypeError, 'Expected string type, not bool'),
(False, TypeError, 'Expected string type, not bool'),
('1976-02-01', ValueError, 'String must begin with \'ISO8601:\''),
('ISO8601:02.01.1976', ValueError, 'Format not recognized \'02.01.1976\''),
)
for value, expected_exception, expected_msg in invalid_data:
with self.assertRaisesRegex(expected_exception, expected_msg):
Iso8601Serializer.deserialize(value)
serialized = Iso8601Serializer.deserialize(value, ignore_unsupported_input=True)
self.assertEqual(serialized, value)
+1 -39
View File
@@ -1,16 +1,13 @@
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model
from django.urls import reverse
from dav_base.tests.generic import Url, UrlsTestCase from dav_base.tests.generic import Url, UrlsTestCase
from .generic import EventMixin
from .. import views from .. import views
url_prefix = settings.MODULE_CONFIG.modules['dav_events'].url_prefix url_prefix = settings.MODULE_CONFIG.modules['dav_events'].url_prefix
class TestCase(EventMixin, UrlsTestCase): class TestCase(UrlsTestCase):
urls = ( urls = (
Url('/{}/home'.format(url_prefix), 'dav_events:root', views.base.HomeView.as_view()), Url('/{}/home'.format(url_prefix), 'dav_events:root', views.base.HomeView.as_view()),
Url('/{}/'.format(url_prefix), 'dav_events:list', views.events.EventListView.as_view(), Url('/{}/'.format(url_prefix), 'dav_events:list', views.events.EventListView.as_view(),
@@ -19,38 +16,3 @@ class TestCase(EventMixin, UrlsTestCase):
redirect='/auth/login?next=/{}/export'.format(url_prefix)), redirect='/auth/login?next=/{}/export'.format(url_prefix)),
Url('/{}/create'.format(url_prefix), 'dav_events:create', views.events.EventCreateView.as_view()), Url('/{}/create'.format(url_prefix), 'dav_events:create', views.events.EventCreateView.as_view()),
) )
def setUp(self):
self.event = self.create_event_by_model()
model = get_user_model()
self.username = 'root'
self.password = 'mellon'
self.user = model.objects.create_superuser(username=self.username, password=self.password,
email='root@localhost')
def test_registrations(self):
pk = self.event.pk
expected_location = '/{}/{}/registrations'.format(url_prefix, pk)
location = reverse('dav_events:registrations', kwargs={'pk': pk})
self.assertEqual(location, expected_location)
self.client.login(username=self.username, password=self.password)
response = self.client.get(location)
self.assertEqual(response.status_code, 200)
def test_update(self):
pk = self.event.pk
expected_location = '/{}/{}/edit'.format(url_prefix, pk)
location = reverse('dav_events:update', kwargs={'pk': pk})
self.assertEqual(location, expected_location)
self.client.login(username=self.username, password=self.password)
response = self.client.get(location)
self.assertEqual(response.status_code, 200)
def test_detail(self):
pk = self.event.pk
expected_location = '/{}/{}/'.format(url_prefix, pk)
location = reverse('dav_events:detail', kwargs={'pk': pk})
self.assertEqual(location, expected_location)
self.client.login(username=self.username, password=self.password)
response = self.client.get(location)
self.assertEqual(response.status_code, 200)
-109
View File
@@ -1,109 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import datetime
from django.test import TestCase
from django.utils import timezone
from ..models import Participant, TrashedParticipant
from .. import utils
from .generic import EventMixin
class PurgeParticipantsTestCase(EventMixin, TestCase):
def test_purging_participants_only_with_elapsed_purge_date(self):
event = self.create_event_by_model()
test_dates = [
timezone.now(),
timezone.now() - datetime.timedelta(days=1),
timezone.now() + datetime.timedelta(days=1),
timezone.now() - datetime.timedelta(days=720),
timezone.now() + datetime.timedelta(days=720),
]
position = 0
for date in test_dates:
position += 1
participant = Participant(
event=event,
personal_names='Walter',
family_names='Bonatti',
address='Street 1',
postal_code='23032',
city='Bormio',
email_address='walter@farbemachtstark.de',
phone_number='555 1',
year_of_birth=1930,
dav_number='1',
position=position,
purge_at=date,
)
participant.save()
self.assertEqual(Participant.objects.count(), len(test_dates))
with self.assertLogs('dav_events.utils', level='INFO') as cm:
utils.purge_participants()
position = 0
expected_messages = 0
for date in test_dates:
position += 1
expected_purge = date <= timezone.now()
if expected_purge:
expected_messages += 1
self.assertEqual(expected_purge, not Participant.objects.filter(position=position).exists())
self.assertEqual(len(cm.output), expected_messages)
for i in range(expected_messages):
self.assertStartsWith(cm.output[i], 'INFO:dav_events.utils:Purge participant \'')
def test_purging_trashed_participants_only_with_elapsed_purge_date(self):
event = self.create_event_by_model()
test_dates = [
timezone.now(),
timezone.now() - datetime.timedelta(days=1),
timezone.now() + datetime.timedelta(days=1),
timezone.now() - datetime.timedelta(days=720),
timezone.now() + datetime.timedelta(days=720),
]
position = 0
for date in test_dates:
position += 1
participant = TrashedParticipant(
event=event,
personal_names='Walter',
family_names='Bonatti',
address='Street 1',
postal_code='23032',
city='Bormio',
email_address='walter@farbemachtstark.de',
phone_number='555 1',
year_of_birth=1930,
dav_number='1',
position=position,
created_at=timezone.now(),
purge_at=date,
)
participant.save()
self.assertEqual(TrashedParticipant.objects.count(), len(test_dates))
with self.assertLogs('dav_events.utils', level='INFO') as cm:
utils.purge_participants()
position = 0
expected_messages = 0
for date in test_dates:
position += 1
expected_purge = date <= timezone.now()
if expected_purge:
expected_messages += 1
self.assertEqual(expected_purge, not TrashedParticipant.objects.filter(position=position).exists())
self.assertEqual(len(cm.output), expected_messages)
for i in range(expected_messages):
self.assertStartsWith(cm.output[i], 'INFO:dav_events.utils:Purge participant from trash \'')
-274
View File
@@ -1,274 +0,0 @@
# -*- coding: utf-8 -*-
from django.test import SimpleTestCase
from dav_base.tests.generic import ValidatorTestMixin
from ..validators import AlphanumericValidator, LowerAlphanumericValidator, IdStringValidator
class AlphanumericValidatorTestCase(ValidatorTestMixin, SimpleTestCase):
validator = AlphanumericValidator
def test_valid_data(self):
data = (
'', # Empty string is valid
'A', # Single uppercase letter
'a', # Single lowercase letter
'0', # Single digit
'abc', # Multiple lowercase letters
'ABCD', # Multiple uppercase letters
'AbCde', # Mixed case
'123456', # Multiple digits
'bcd2345', # Letters and digits
'CDEF3456', # Uppercase and digits
'AbcD123', # Mixed case and digits
'a1B2c3', # Alternating case and digits
'012345678909876543210', # Long digit sequence
'abcdefghijklmnopqrstuvwxyz', # All lowercase letters
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', # All uppercase letters
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789', # All valid chars
'EventCode2024', # Real world example
'USER123EVENT', # Real world example
'A1', # Single letter and digit
'1A', # Single digit and letter
)
self.assertValid(self.validator, data)
def test_invalid_data(self):
"""Test invalid strings with special characters, spaces, umlauts"""
data = (
' ', # Space
'abc ', # Trailing space
' abc', # Leading space
'a b c', # Space in the middle
'-', # Hyphen
'abc-def', # Letters with hyphen
'_', # Underscore
'abc_def', # Letters with underscore
'.', # Dot
'abc.def', # Letters with dot
',', # Comma
'abc,def', # Letters with comma
'!', # Exclamation mark
'abc!def', # Letters with exclamation
'@', # At sign
'abc@def', # Letters with at sign
'#', # Hash
'abc#def', # Letters with hash
'$', # Dollar sign
'abc$def', # Letters with dollar
'%', # Percent
'abc%def', # Letters with percent
'&', # Ampersand
'abc&def', # Letters with ampersand
'*', # Asterisk
'abc*def', # Letters with asterisk
'+', # Plus
'abc+def', # Letters with plus
'=', # Equals
'abc=def', # Letters with equals
'/', # Slash
'abc/def', # Letters with slash
'\\', # Backslash
'abc\\def', # Letters with backslash
'?', # Question mark
'abc?def', # Letters with question mark
':', # Colon
'abc:def', # Letters with colon
';', # Semicolon
'abc;def', # Letters with semicolon
'"', # Double quote
'abc"def', # Letters with double quote
"'", # Single quote
"abc'def", # Letters with single quote
'<', # Less than
'abc<def', # Letters with less than
'>', # Greater than
'abc>def', # Letters with greater than
'(', # Opening parenthesis
'abc(def)', # Letters with parentheses
'[', # Opening bracket
'abc[def]', # Letters with brackets
'{', # Opening brace
'abc{def}', # Letters with braces
'ä', # Umlaut
'äbc', # Letters with umlaut
'ö', # Umlaut o
'ö', # Umlaut O
'ü', # Umlaut u
'ü', # Umlaut U
'ß', # German sharp s
'Straße', # German word with umlaut
'café', # Accented character
'naïve', # Accented character
'中文', # Chinese characters
'русский', # Cyrillic characters
'العربية', # Arabic characters
)
self.assertInvalid(self.validator, data)
class LowerAlphanumericValidatorTestCase(ValidatorTestMixin, SimpleTestCase):
validator = LowerAlphanumericValidator
def test_valid_data(self):
data = (
'', # Empty string is valid
'a', # Single lowercase letter
'0', # Single digit
'abc', # Multiple lowercase letters
'123456', # Multiple digits
'bcd2345', # Letters and digits
'a1b2c3', # Alternating lowercase and digits
'012345678909876543210', # Long digit sequence
'abcdefghijklmnopqrstuvwxyz', # All lowercase letters
'eventcode2024', # Real world example (all lowercase)
'user123event', # Real world example (all lowercase)
'a1', # Single letter and digit
'1a', # Single digit and letter
)
self.assertValid(self.validator, data)
def test_invalid_data(self):
"""Test invalid strings with uppercase letters and other characters"""
data = (
'A', # Single uppercase letter
'ABC', # Multiple uppercase letters
'AbC', # Mixed case
'ABC123', # Uppercase and digits
'AbC123', # Mixed case (with uppercase)
' ', # Space
'abc ', # Trailing space
' abc', # Leading space
'a b c', # Space in the middle
'-', # Hyphen
'abc-def', # Letters with hyphen
'_', # Underscore
'abc_def', # Letters with underscore
'.', # Dot
'abc.def', # Letters with dot
',', # Comma
'abc,def', # Letters with comma
'!', # Exclamation mark
'abc!def', # Letters with exclamation
'@', # At sign
'abc@def', # Letters with at sign
'#', # Hash
'abc#def', # Letters with hash
'ä', # Umlaut
'äbc', # Letters with umlaut
'café', # Accented character
'naïve', # Accented character
)
self.assertInvalid(self.validator, data)
class IdStringValidatorTestCase(ValidatorTestMixin, SimpleTestCase):
validator = IdStringValidator
def test_valid_data(self):
data = (
'', # Empty string is valid
'a', # Single lowercase letter
'0', # Single digit
'abc', # Multiple lowercase letters
'123', # Multiple digits
'_', # Single underscore
'-', # Single hyphen
'.', # Single dot
'abc123', # Lowercase letters and digits
'a_b', # Underscore separator
'a-b', # Hyphen separator
'a.b', # Dot separator
'user_name', # Underscore in middle
'user-name', # Hyphen in middle
'user.name', # Dot in middle
'user_name_123', # Multiple underscores
'user-name-123', # Multiple hyphens
'user.name.123', # Multiple dots
'mixed_name-with.numbers123', # Mixed separators
'example.com', # Domain-like format
'v1.0.0', # Version string
'2024-01-15', # Date-like format
'snake_case', # Snake case convention
'kebab-case', # Kebab case convention
'dot.case', # Dot case convention
'a1b2c3d4e5f6g7h8i9j0', # Alternating
'test_123-abc.xyz', # Complex mixed format
'_leading_underscore', # Leading underscore
'-leading-hyphen', # Leading hyphen
'.leading-dot', # Leading dot
'trailing_underscore_', # Trailing underscore
'trailing-hyphen-', # Trailing hyphen
'trailing.dot.', # Trailing dot
'___', # Multiple underscores only
'---', # Multiple hyphens only
'...', # Multiple dots only
'a_b-c.d_e-f.g', # All separators mixed
)
self.assertValid(self.validator, data)
def test_invalid_data(self):
data = (
'A', # Uppercase letter
'ABC', # Multiple uppercase letters
'AbC', # Mixed case
'ABC123', # Uppercase and digits
'aBC123', # Mixed case (any uppercase)
' ', # Space
'abc ', # Trailing space
' abc', # Leading space
'a b c', # Space in the middle
'a b-c', # Space mixed with valid chars
'@', # At sign
'abc@def', # At sign in the middle
'#', # Hash
'abc#def', # Hash in the middle
'$', # Dollar sign
'abc$def', # Dollar in the middle
'%', # Percent
'abc%def', # Percent in the middle
'&', # Ampersand
'abc&def', # Ampersand in the middle
'*', # Asterisk
'abc*def', # Asterisk in the middle
'+', # Plus
'abc+def', # Plus in the middle
'=', # Equals
'abc=def', # Equals in the middle
'/', # Slash
'abc/def', # Slash in the middle
'\\', # Backslash
'abc\\def', # Backslash in the middle
'?', # Question mark
'abc?def', # Question mark in the middle
':', # Colon
'abc:def', # Colon in the middle
';', # Semicolon
'abc;def', # Semicolon in the middle
'"', # Double quote
'abc"def', # Double quote in the middle
"'", # Single quote
"abc'def", # Single quote in the middle
'<', # Less than
'abc<def', # Less than in the middle
'>', # Greater than
'abc>def', # Greater than in the middle
'(', # Opening parenthesis
'abc(def)', # Parentheses in the middle
'[', # Opening bracket
'abc[def]', # Brackets in the middle
'{', # Opening brace
'abc{def}', # Braces in the middle
'!', # Exclamation mark
'abc!def', # Exclamation mark in the middle
',', # Comma
'abc,def', # Comma in the middle
'ä', # Umlaut
'äbc', # Letters with umlaut
'café', # Accented character
'naïve', # Accented character
'中文', # Chinese characters
'русский', # Cyrillic characters
)
self.assertInvalid(self.validator, data)
+11 -11
View File
@@ -21,18 +21,18 @@ oneday = datetime.timedelta(1)
DEFAULT_EVENT_STATI = { DEFAULT_EVENT_STATI = {
'void': (0, _(u'Ungültig'), None), 'void': (0, _(u'Ungültig'), None),
'draft': (10, _(u'Entwurf'), 'blue'), 'draft': (10, _(u'Entwurf'), 'info'),
'submitted': (30, _(u'Eingereicht'), 'red'), 'submitted': (30, _(u'Eingereicht'), 'danger'),
'accepted': (50, _(u'Freigegeben'), 'yellow'), 'accepted': (50, _(u'Freigegeben'), 'warning'),
'publishing_facebook': (68, _(u'Veröffentlichung (Facebook)'), 'yellow'), 'publishing_facebook': (68, _(u'Veröffentlichung (Facebook)'), 'warning'),
'publishing_web': (69, _(u'Veröffentlichung (Web)'), 'yellow'), 'publishing_web': (69, _(u'Veröffentlichung (Web)'), 'warning'),
'publishing': (70, _(u'Veröffentlichung'), 'yellow'), 'publishing': (70, _(u'Veröffentlichung'), 'warning'),
'published_facebook': (78, _(u'Veröffentlicht (Facebook)'), 'green'), 'published_facebook': (78, _(u'Veröffentlicht (Facebook)'), 'success'),
'published_web': (79, _(u'Veröffentlicht (Web)'), 'green'), 'published_web': (79, _(u'Veröffentlicht (Web)'), 'success'),
'published': (80, _(u'Veröffentlicht'), 'green'), 'published': (80, _(u'Veröffentlicht'), 'success'),
'expired': (100, _(u'Ausgelaufen'), None), 'expired': (100, _(u'Ausgelaufen'), None),
'canceled': (101, _(u'Abgesagt'), 'mandarin'), 'canceled': (101, _(u'Abgesagt'), 'dav-mandarin'),
'realized': (102, _(u'Durchgeführt'), 'lime'), 'realized': (102, _(u'Durchgeführt'), 'dav-lime'),
'cleared': (110, _(u'Abgerechnet'), 'black'), 'cleared': (110, _(u'Abgerechnet'), 'black'),
} }
@@ -1,19 +0,0 @@
# Generated by Django 5.2.13 on 2026-06-16 13:21
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dav_registration', '0012_alter_registrationstatus_accepted'),
]
operations = [
migrations.AlterField(
model_name='registration',
name='dav_number',
field=models.CharField(blank=True, help_text='Deine Mitgliedsnummer findest du unter dem Strichcode auf deinem DAV Ausweis.<br /> Beispiel: <tt>131/00/012345</tt> (der Teil bis zum ersten * genügt)', max_length=62, validators=[django.core.validators.RegexValidator('^([0-9]{3}/[0-9]{2}/)?[0-9]{1,6}([*x ][0-9]{4})?([*x ][0-9]{4}[*x ][0-9]{4})?([*x ][0-9]{8})?$', 'Ungültiges Format.')], verbose_name='DAV Mitgliedsnummer'),
),
]
+2 -2
View File
@@ -1,5 +1,5 @@
from django.apps import apps from django.apps import apps
from django.core.exceptions import ImproperlyConfigured from six import string_types
from dav_base.tests.generic import AppSetting, AppsTestCase from dav_base.tests.generic import AppSetting, AppsTestCase
@@ -8,5 +8,5 @@ class TestCase(AppsTestCase):
app_config = apps.get_app_config('dav_registration') app_config = apps.get_app_config('dav_registration')
settings = ( settings = (
AppSetting('privacy_policy', ImproperlyConfigured, str), AppSetting('privacy_policy', string_types),
) )
+2 -2
View File
@@ -166,7 +166,7 @@ class EmailsTestCase(EmailTestMixin, EventMixin, RegistrationMixin, TestCase):
'year_of_birth': 1976, 'year_of_birth': 1976,
'apply_reduced_fee': True, 'apply_reduced_fee': True,
'dav_member': False, 'dav_member': False,
'dav_number': '131/00/007*1234', 'dav_number': '131/00/007*12345',
'emergency_contact': 'Call 911!', 'emergency_contact': 'Call 911!',
'experience': 'Yes, we can!', 'experience': 'Yes, we can!',
'note': 'Automatischer Software Test\nGruß\n heinzel =u}', 'note': 'Automatischer Software Test\nGruß\n heinzel =u}',
@@ -277,7 +277,7 @@ class EmailsTestCase(EmailTestMixin, EventMixin, RegistrationMixin, TestCase):
'year_of_birth': THIS_YEAR, 'year_of_birth': THIS_YEAR,
'apply_reduced_fee': True, 'apply_reduced_fee': True,
'dav_member': False, 'dav_member': False,
'dav_number': '131/00/007*1234', 'dav_number': '131/00/007*12345',
'emergency_contact': 'Call 911!', 'emergency_contact': 'Call 911!',
'experience': 'Yes, we can!', 'experience': 'Yes, we can!',
'note': 'Automatischer Software Test\nGruß\n heinzel =u}', 'note': 'Automatischer Software Test\nGruß\n heinzel =u}',
+2 -1
View File
@@ -8,8 +8,9 @@ import django
from django.test.utils import get_runner from django.test.utils import get_runner
from dav_base.tests.utils import mkdtemp from dav_base.tests.utils import mkdtemp
from dav_base.config import DJANGO_MAIN_MODULE
# from dav_base.console_scripts.admin import DJANGO_MAIN_MODULE
DJANGO_MAIN_MODULE = 'main'
TMP_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'tmp') TMP_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'tmp')
-8
View File
@@ -27,11 +27,3 @@ description = Report test coverage
deps = {[testenv:fresh]deps} deps = {[testenv:fresh]deps}
commands = python -m coverage report --skip-covered commands = python -m coverage report --skip-covered
[testenv:lint]
description = Run pylint
deps = {[testenv]deps}
pylint-django
commands_pre = {[testenv]commands_pre}
python -m pylint --version
commands = python -m pylint --fail-under=8 --django-settings-module='tests.settings' dav_*