From 1bf1c1a9b7d40d6a7bd9bee2cec3ab51a1ae725b Mon Sep 17 00:00:00 2001 From: heinzel Date: Wed, 10 May 2023 17:09:00 +0200 Subject: [PATCH 01/29] Added management command to populate database with test data --- dav_events/management/__init__.py | 0 dav_events/management/commands/__init__.py | 0 .../management/commands/create_test_data.py | 191 ++++++++++++++++ etc/test_data.json | 203 ++++++++++++++++++ 4 files changed, 394 insertions(+) create mode 100644 dav_events/management/__init__.py create mode 100644 dav_events/management/commands/__init__.py create mode 100644 dav_events/management/commands/create_test_data.py create mode 100644 etc/test_data.json diff --git a/dav_events/management/__init__.py b/dav_events/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dav_events/management/commands/__init__.py b/dav_events/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dav_events/management/commands/create_test_data.py b/dav_events/management/commands/create_test_data.py new file mode 100644 index 0000000..8213c2f --- /dev/null +++ b/dav_events/management/commands/create_test_data.py @@ -0,0 +1,191 @@ +import datetime +import json +from django.apps import apps +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group +from django.core.management.base import BaseCommand, CommandError +from django.utils.termcolors import colorize + +from dav_events.models import Event, EventStatus, OneClickAction + + +class Command(BaseCommand): + help = 'Populate database with test data.' + + def _substitute_data_vars(self, data_dict, vars_dict): + for k in data_dict: + try: + data_dict[k] = data_dict[k].format(**vars_dict) + except AttributeError: + pass + return data_dict + + def _get_groups_for_role(self, role_name): + settings = self._app_settings + var_name = 'groups_{}'.format(role_name) + group_names = getattr(settings, var_name) + return group_names + + def _get_group_for_role(self, role_name): + group_names = self._get_groups_for_role(role_name) + group_name = group_names[0] + return group_name + + def _grant_role_to_user(self, user, role_name): + group_name = self._get_group_for_role(role_name) + group = Group.objects.get(name=group_name) + user.groups.add(group) + self.stdout.write(self.style.SUCCESS( + 'Added user \'{}\' to group \'{}\''.format(user, group) + )) + + def _create_group(self, group_name): + group, created = Group.objects.get_or_create(name=group_name) + if created: + self.stdout.write(self.style.SUCCESS('Created group \'{}\''.format(group))) + else: + self.stdout.write(self.style.WARNING('Recycled group \'{}\''.format(group))) + return group + + def _create_groups(self): + roles = ( + 'manager_super', + 'manager_w', + 'manager_s', + 'manager_m', + 'manager_k', + 'manager_b', + 'publisher_print', + 'publisher_web', + 'office', + ) + for role_name in roles: + group_names = self._get_groups_for_role(role_name) + for group_name in group_names: + self._create_group(group_name) + + def _create_user(self, username, email, first_name, last_name, password=None, is_superuser=False): + if password is None: + password = email + + user_model = get_user_model() + try: + user = user_model.objects.get(username=username) + self.stdout.write(self.style.WARNING('Recycled user \'{}\''.format(user))) + user.groups.clear() + except user_model.DoesNotExist: + user = user_model.objects.create_user(username=username, + email=email, + first_name=first_name, + last_name=last_name, + password=password) + self.stdout.write(self.style.SUCCESS('Created user \'{}\''.format(user))) + + if is_superuser: + user.is_staff = True + user.is_superuser = True + user.save() + self.stdout.write(self.style.SUCCESS('Made user \'{}\' a super user'.format(user))) + + return user + + def _create_users(self, user_list, data_vars=None): + if data_vars is None: + data_vars = {} + + for data_set in user_list: + if data_vars: + data_set = self._substitute_data_vars(data_set, data_vars) + + if 'username' not in data_set: + data_set['username'] = data_set['email'] + if 'roles' not in data_set: + data_set['roles'] = () + if 'is_superuser' not in data_set: + data_set['is_superuser'] = False + if 'password' not in data_set: + data_set['password'] = None + + user = self._create_user(username=data_set['username'], + email=data_set['email'], + first_name=data_set['first_name'], + last_name=data_set['last_name'], + password=data_set['password'], + is_superuser=data_set['is_superuser']) + + for role_name in data_set['roles']: + self._grant_role_to_user(user, role_name) + + def _create_event(self, event_data): + event = Event(**event_data) + event.save() + self.stdout.write(self.style.SUCCESS('Created event \'{}\''.format(event))) + return event + + def _create_events(self, event_list, data_vars=None): + if data_vars is None: + data_vars = {} + + for data_set in event_list: + if data_vars: + data_set = self._substitute_data_vars(data_set, data_vars) + + if 'first_day_from_today' in data_set: + data_set['first_day'] = datetime.date.today() + datetime.timedelta(data_set['first_day_from_today']) + del data_set['first_day_from_today'] + if 'last_day_from_today' in data_set: + data_set['last_day'] = datetime.date.today() + datetime.timedelta(data_set['last_day_from_today']) + del data_set['last_day_from_today'] + if 'deadline_from_today' in data_set: + data_set['deadline'] = datetime.date.today() + datetime.timedelta(data_set['deadline_from_today']) + del data_set['deadline_from_today'] + + status_updates = [] + if 'status_updates' in data_set: + status_updates = data_set['status_updates'] + del data_set['status_updates'] + + event = self._create_event(data_set) + + for status in status_updates: + event.workflow.update_status(status, event.owner) + + def add_arguments(self, parser): + parser.add_argument('--purge', action='store_true', help='remove all data from the database beforehand') + parser.add_argument('--var', '-e', action='append', dest='data_vars', metavar='KEY=VALUE', + help='Give a key=value pair for variable substitution in the test data') + parser.add_argument('data_file', metavar='FILE.json', help='File containing the test data as JSON') + + def handle(self, *args, **options): + app_config = apps.get_containing_app_config(__package__) + app_settings = app_config.settings + self._app_settings = app_settings + + with open(options['data_file'], 'r') as f: + test_data = json.load(f) + + data_vars = test_data['vars'] + if options['data_vars'] is not None: + for pair in options['data_vars']: + key = pair.split('=', 1)[0] + value = pair.split('=', 1)[1] + data_vars[key] = value + + if options['purge']: + self.stdout.write(self.style.NOTICE('Purge database...')) + OneClickAction.objects.all().delete() + Event.objects.all().delete() + EventStatus.objects.all().delete() + user_model = get_user_model() + user_model.objects.all().delete() + Group.objects.all().delete() + + self.stdout.write(colorize('Creating groups...', fg='cyan', opts=('bold',))) + self._create_groups() + self.stdout.write(colorize('Creating users...', fg='cyan', opts=('bold',))) + self._create_users(test_data['users'], data_vars) + self.stdout.write(colorize('Creating events...', fg='cyan', opts=('bold',))) + self._create_events(test_data['events'], data_vars) + + # raise CommandError('This is an error') + diff --git a/etc/test_data.json b/etc/test_data.json new file mode 100644 index 0000000..e26a85a --- /dev/null +++ b/etc/test_data.json @@ -0,0 +1,203 @@ +{ + "vars": { + "PASSWORD": "mellon", + "ADMIN_EMAIL": "admin@heinzelwerk.de", + "TRAINER_WEIT": "heinzel@alpenverein-karlsruhe.de", + "TRAINER_HOCH": "heinzel@farbemachtstark.de" + }, + "users": [ + { + "username": "root", + "first_name": "Admin", + "last_name": "of the system", + "email": "{ADMIN_EMAIL}", + "password": "{PASSWORD}", + "is_superuser": true + }, + { + "username": "manager@all", + "first_name": "Supermanager", + "last_name": "für alle Touren", + "roles": ["manager_super"], + "email": "{ADMIN_EMAIL}", + "password": "{PASSWORD}" + }, + { + "username": "manager@hoch", + "first_name": "Höhenmanager", + "last_name": "für Bergsteigen und Klettern", + "roles": ["manager_b", "manager_k"], + "email": "{ADMIN_EMAIL}", + "password": "{PASSWORD}" + }, + { + "username": "manager@weit", + "first_name": "Distanzmanager", + "last_name": "für Wandern, Ski und MTB", + "roles": ["manager_w", "manager_s", "manager_m"], + "email": "{ADMIN_EMAIL}", + "password": "{PASSWORD}" + }, + { + "username": "redakteur@web", + "first_name": "Web-Redakteur", + "last_name": "für die Webseite", + "roles": ["publisher_web"], + "email": "{ADMIN_EMAIL}", + "password": "{PASSWORD}" + }, + { + "username": "redakteur@print", + "first_name": "Print-Redakteur", + "last_name": "für das Heft", + "roles": ["publisher_print"], + "email": "{ADMIN_EMAIL}", + "password": "{PASSWORD}" + }, + { + "username": "office", + "first_name": "Geschäftsstellenmitarbeiter*in", + "last_name": "für die Geschäftsstelle", + "roles": ["office"], + "email": "{ADMIN_EMAIL}", + "password": "{PASSWORD}" + }, + { + "first_name": "Distanztrainer", + "last_name": "für Wandern, Ski und MTB", + "email": "{TRAINER_WEIT}", + "password": "{PASSWORD}" + }, + { + "first_name": "Höhentrainer", + "last_name": "für Bergsteigen und Klettern", + "email": "{TRAINER_HOCH}", + "password": "{PASSWORD}" + } + ], + "events": [ + { + "title": "Einfache Tageswanderung (Morgen)", + "description": "TEST!\n Wir gehen morgen Wandern.", + "mode": "joint", + "sport": "W", + "level": "beginner", + "country": "DE", + "trainer_firstname": "Distanztrainer", + "trainer_familyname": "für Wanderungen", + "trainer_email": "{TRAINER_WEIT}", + "first_day_from_today": 1, + "status_updates": ["submitted", "accepted", "published"] + }, + { + "title": "Einfache Tageswanderung (Übermorgen)", + "description": "TEST!\n Wir gehen übermorgen Wandern.", + "mode": "joint", + "sport": "W", + "level": "beginner", + "country": "DE", + "trainer_firstname": "Distanztrainer", + "trainer_familyname": "für Wanderungen", + "trainer_email": "{TRAINER_WEIT}", + "first_day_from_today": 2, + "status_updates": ["submitted", "accepted"] + }, + { + "title": "Einfache Tageswanderung (Bald)", + "description": "TEST!\n Wir gehen bald Wandern.", + "mode": "joint", + "sport": "W", + "level": "beginner", + "country": "DE", + "trainer_firstname": "Distanztrainer", + "trainer_familyname": "für Wanderungen", + "trainer_email": "{TRAINER_WEIT}", + "first_day_from_today": 3, + "status_updates": ["submitted"] + }, + { + "title": "Einfache Tageswanderung (Demnächst)", + "description": "TEST!\n Wir gehen demnächst Wandern.", + "mode": "joint", + "sport": "W", + "level": "beginner", + "country": "DE", + "trainer_firstname": "Distanztrainer", + "trainer_familyname": "für Wanderungen", + "trainer_email": "{TRAINER_WEIT}", + "first_day_from_today": 4 + }, + { + "title": "MTB-Fahrtechnik für Fortgeschrittene", + "description": "TEST!\nIhr habt bereits einen Fahrtechnikkurs für Einsteiger*innen absolviert oder Euch die elementaren MTB-Fahrtechniken selber angeeignet?\nDann ist es Zeit, ins Gelände zu gehen und Fahrgenuss auf Singletrails zu erleben!\nIn diesem Kurs wollen wir gemeinsam Eure Fahrtechniken verbessern und das Gelernte direkt auf Trails anwenden.", + "mode": "training", + "sport": "M", + "level": "advanced", + "country": "DE", + "location": "Karlsruher Umland", + "meeting_point": "OTHER", + "meeting_point_other": "Wasserwerkbrücke Karlsruhe", + "meeting_time": "10:00:00", + "return_arrival_time": "17:00:00", + "requirements": "Kondition für MTB-Touren bis 40 km und 700 Hm, allgemeine Sportlichkeit. Erfolgte Teilnahme am Einsteiger*innen-Kurs oder entsprechende selbst erworbene Kenntnisse und Fähigkeiten.", + "equipment": "Funktionstüchtiges MTB (bitte vorher überprüfen), Fahrrad-Helm und Radhandschuhe sind zwingend erforderlich, Brille und Protektoren empfehlenswert. Die Sattelstütze muss sich tief versenken lassen.", + "course_topic_1": "Bremsübungen, verschiedene Kurventechniken, Überqueren von kleinen Hindernissen, Koordinations- und Balanceschulung, Anwenden des Gelernten im Gelände.", + "course_goal_1": "Sicheres und flüssiges Befahren von einfachen bis mittelschweren Trails der Schwierigkeit S1-S2 (siehe www.singletrail-skala.de), Verbessern der MTB-Fahretechniken.", + "min_participants": 0, + "max_participants": 4, + "registration_required": true, + "deadline_from_today": 1, + "charge": 40.0, + "trainer_firstname": "Distanztrainer", + "trainer_familyname": "für MTB", + "trainer_email": "{TRAINER_WEIT}", + "first_day_from_today": 2, + "status_updates": ["submitted", "accepted", "published"] + }, + { + "title": "MTB-Fahrtechnik für Einsteiger*innen", + "description": "TEST!\nFahrvergnügen auf herrlichen Singletrails, viele landschaftliche Eindrücke, Fahrtwind um die Nase und einfach Spaß – das alles kann man mit dem Mountainbike (fast) vor der Haustür, in der Pfalz und anderen Mittelgebirgen oder in den Alpen genießen.\nLeider führen die Anforderungen an Fahrtechnik und Balance sowie das Einschätzen und Bewältigungen kritischer Stellen oft schnell zu Stress, Angst und Stürzen.\nWir möchten in diesem Kurs Einsteigern die Grundkenntnisse der Fahrtechnik vermitteln, damit in Zukunft Vergnügen und Genuss bei Euren Touren im Vordergrund stehen!", + "mode": "training", + "sport": "M", + "level": "beginner", + "country": "DE", + "location": "Karlsruher Umland", + "meeting_point": "OTHER", + "meeting_point_other": "Wasserwerkbrücke Karlsruhe", + "meeting_time": "10:00:00", + "return_arrival_time": "17:00:00", + "requirements": "Kondition für Radtouren bis 30 km und 500 Hm in ca. 3 Stunden.", + "equipment": "Funktionstüchtiges MTB (bitte vorher überprüfen), Fahrrad-Helm und Radhandschuhe sind zwingend erforderlich, Brille und Protektoren empfehlenswert. Die Sattelstütze muss sich tief versenken lassen.", + "course_topic_1": "Kontroll-Check, Grundeinstellung des Bikes (Sitzposition), Verzögern mit beiden Bremsen, Beschleunigen (Taktik beim Schalten), Richtungswechsel (Kurven sicher befahren), Koordinations- und Balanceschulung, Techniken beim Bergauf- und Bergabfahren", + "course_goal_1": "Sicheres und angstfreies Befahren von einfachen Trails der Schwierigkeit S1 (siehe www.singletrail-skala.de), Beherrschen der elementaren MTB-Fahrtechniken", + "min_participants": 0, + "max_participants": 4, + "registration_required": true, + "deadline_from_today": -1, + "charge": 40.0, + "trainer_firstname": "Distanztrainer", + "trainer_familyname": "für MTB", + "trainer_email": "{TRAINER_WEIT}", + "first_day_from_today": 3, + "status_updates": ["submitted", "accepted", "published"] + }, + { + "title": "Geführte Familien Bergtour", + "description": "TEST!", + "mode": "guided", + "sport": "B", + "level": "family", + "country": "AT", + "min_participants": 2, + "max_participants": 4, + "registration_required": true, + "deadline_from_today": 2, + "trainer_firstname": "Höhentrainer", + "trainer_familyname": "für Bergsteigen", + "trainer_email": "{TRAINER_HOCH}", + "first_day_from_today": 4, + "last_day_from_today": 6, + "status_updates": ["submitted", "accepted", "published"] + } + ] +} From 5edec18ea9582f7f878d03114a13de39e9b568d1 Mon Sep 17 00:00:00 2001 From: heinzel Date: Wed, 17 May 2023 12:04:32 +0200 Subject: [PATCH 02/29] More test data --- .../management/commands/create_test_data.py | 23 ++++-- etc/test_data.json | 82 ++++++++++++++++++- 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/dav_events/management/commands/create_test_data.py b/dav_events/management/commands/create_test_data.py index 8213c2f..eac774d 100644 --- a/dav_events/management/commands/create_test_data.py +++ b/dav_events/management/commands/create_test_data.py @@ -130,15 +130,20 @@ class Command(BaseCommand): if data_vars: data_set = self._substitute_data_vars(data_set, data_vars) - if 'first_day_from_today' in data_set: - data_set['first_day'] = datetime.date.today() + datetime.timedelta(data_set['first_day_from_today']) - del data_set['first_day_from_today'] - if 'last_day_from_today' in data_set: - data_set['last_day'] = datetime.date.today() + datetime.timedelta(data_set['last_day_from_today']) - del data_set['last_day_from_today'] - if 'deadline_from_today' in data_set: - data_set['deadline'] = datetime.date.today() + datetime.timedelta(data_set['deadline_from_today']) - del data_set['deadline_from_today'] + for date_key in ('first_day', 'last_day', 'alt_first_day', 'alt_last_day', 'deadline'): + k = '{}_from_today'.format(date_key) + if k in data_set: + d = datetime.date.today() + datetime.timedelta(data_set[k]) + data_set[date_key] = d + del data_set[k] + if 'pre_meeting_1_from_today' in data_set: + day = datetime.date.today() + datetime.timedelta(data_set['pre_meeting_1_from_today']) + data_set['pre_meeting_1'] = datetime.datetime.combine(day, datetime.time(19, 30)) + del data_set['pre_meeting_1_from_today'] + if 'pre_meeting_2_from_today' in data_set: + day = datetime.date.today() + datetime.timedelta(data_set['pre_meeting_2_from_today']) + data_set['pre_meeting_2'] = datetime.datetime.combine(day, datetime.time(19, 30)) + del data_set['pre_meeting_2_from_today'] status_updates = [] if 'status_updates' in data_set: diff --git a/etc/test_data.json b/etc/test_data.json index e26a85a..ef6de14 100644 --- a/etc/test_data.json +++ b/etc/test_data.json @@ -83,6 +83,11 @@ "sport": "W", "level": "beginner", "country": "DE", + "terrain": "submountains", + "location": "Pfälzerwald", + "transport": "public", + "meeting_point": "hbf", + "meeting_time": "08:00:00", "trainer_firstname": "Distanztrainer", "trainer_familyname": "für Wanderungen", "trainer_email": "{TRAINER_WEIT}", @@ -182,12 +187,19 @@ "status_updates": ["submitted", "accepted", "published"] }, { - "title": "Geführte Familien Bergtour", + "title": "Geführte Familien-Bergtour", "description": "TEST!", "mode": "guided", "sport": "B", "level": "family", "country": "AT", + "location": "Wilder Kaiser", + "meeting_point": "OTHER", + "meeting_point_other": "Parkplatz Wochenbrunner Alm", + "meeting_time": "07:30:00", + "transport": "self", + "accommodation": "hut", + "meals": "vp", "min_participants": 2, "max_participants": 4, "registration_required": true, @@ -197,7 +209,75 @@ "trainer_email": "{TRAINER_HOCH}", "first_day_from_today": 4, "last_day_from_today": 6, + "pre_meeting_1_from_today": 3, "status_updates": ["submitted", "accepted", "published"] + }, + { + "title": "Begleitete Hochtour", + "description": "TEST!", + "mode": "supervised", + "sport": "B", + "level": "advanced", + "country": "CH", + "location": "Wallis", + "min_participants": 2, + "max_participants": 2, + "registration_required": true, + "deadline_from_today": 0, + "trainer_firstname": "Höhentrainer", + "trainer_familyname": "für Bergsteigen", + "trainer_email": "{TRAINER_HOCH}", + "first_day_from_today": 6, + "alt_first_day_from_today": 8, + "last_day_from_today": 7, + "alt_last_day_from_today": 9, + "pre_meeting_1_from_today": 5, + "status_updates": ["submitted", "accepted", "published"] + }, + { + "title": "Familienklettertour", + "description": "TEST!", + "mode": "joint", + "sport": "K", + "level": "family", + "country": "DE", + "location": "Battert", + "registration_required": true, + "trainer_firstname": "Höhentrainer", + "trainer_familyname": "für Klettern", + "trainer_email": "{TRAINER_HOCH}", + "first_day_from_today": 5, + "status_updates": ["submitted", "accepted", "published"] + }, + { + "title": "Skikurs", + "description": "TEST!", + "mode": "training", + "sport": "S", + "level": "advanced", + "country": "DE", + "location": "Mehliskopf", + "registration_required": true, + "trainer_firstname": "Distanztrainer", + "trainer_familyname": "für Ski", + "trainer_email": "{TRAINER_WEIT}", + "first_day_from_today": -10, + "status_updates": ["submitted", "accepted", "published"] + }, + { + "title": "Skikurs", + "description": "TEST!", + "mode": "training", + "sport": "S", + "level": "advanced", + "country": "DE", + "location": "Mehliskopf", + "registration_required": true, + "trainer_firstname": "Distanztrainer", + "trainer_familyname": "für Ski", + "trainer_email": "{TRAINER_WEIT}", + "first_day_from_today": 10, + "status_updates": ["submitted", "accepted", "published", "canceled"] } ] } From 811983abfea4f0f7bddf033dbfed89cb967e9b0f Mon Sep 17 00:00:00 2001 From: heinzel Date: Wed, 24 May 2023 09:47:38 +0200 Subject: [PATCH 03/29] Make tox work with python 3.11 --- MANIFEST.in | 3 --- dav_base/tests/utils.py | 11 ++++------- setup.py | 4 ++-- tests/test_suite.py | 3 ++- tox.ini | 7 +++++-- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index fa47c47..07a2b08 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,3 @@ -# common dist files -include README.rst INSTALL.rst -include setup.py requirements.txt # dav_base recursive-include dav_base/console_scripts/django_project_config *.py recursive-include dav_base/static * diff --git a/dav_base/tests/utils.py b/dav_base/tests/utils.py index 92bf16e..0355f82 100644 --- a/dav_base/tests/utils.py +++ b/dav_base/tests/utils.py @@ -4,10 +4,7 @@ import os from tempfile import mkdtemp as _mkdtemp -def mkdtemp(prefix): - dirname = os.path.dirname - pkg_base_dir = dirname(dirname(dirname(__file__))) - tmp_dir = os.path.join(pkg_base_dir, 'tmp') - if not os.path.exists(tmp_dir): - os.makedirs(tmp_dir) - return _mkdtemp(prefix=prefix, dir=tmp_dir) +def mkdtemp(prefix, base_dir): + if not os.path.exists(base_dir): + os.makedirs(base_dir) + return _mkdtemp(prefix=prefix, dir=base_dir) diff --git a/setup.py b/setup.py index 1a64cef..1d76df9 100644 --- a/setup.py +++ b/setup.py @@ -92,9 +92,9 @@ class QuickSetup(MyCommand): setup( name='django-dav-events', - version='2.1', + version='2.1.2', description='A django based web application project to organize DAV Events.', - url='https://touren.alpenverein-karlsruhe.de', + url='https://dev.heinzelwerk.de/git/DAV-KA/django-dav-events', author='Jens Kleineheismann', author_email='heinzel@alpenverein-karlsruhe.de', cmdclass={ diff --git a/tests/test_suite.py b/tests/test_suite.py index 4ca6bcb..8e9ea24 100644 --- a/tests/test_suite.py +++ b/tests/test_suite.py @@ -11,6 +11,7 @@ from dav_base.tests.utils import mkdtemp # 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') class DjangoEnvironment: @@ -42,7 +43,7 @@ class DjangoEnvironment: prefix = 'testrun-{datetime}-'.format( datetime=datetime.datetime.now().strftime('%Y%m%d-%H%M') ) - self.path = mkdtemp(prefix=prefix) + self.path = mkdtemp(prefix=prefix, base_dir=TMP_BASE_DIR) self._install_djangoproject(self.path, modules=self._enable_modules) diff --git a/tox.ini b/tox.ini index 4c6403c..bd6dda3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,10 @@ [tox] -envlist = py3 +envlist = py311 [testenv] +recreate = false +setenv = + PYTHONPATH = . commands = python --version - python -m coverage run tests/test_suite.py + python -m coverage run tests coverage report --skip-covered From b86708598f7b1cde5c5b664548e294d7bc3c8f1e Mon Sep 17 00:00:00 2001 From: heinzel Date: Wed, 12 Jul 2023 10:06:01 +0200 Subject: [PATCH 04/29] dav_registration: change filter button logic from select buttons to OR --- dav_events/choices.py | 4 + .../dav_registration/event_list.html | 85 +++++++++++-------- dav_registration/views.py | 19 ++--- 3 files changed, 59 insertions(+), 49 deletions(-) diff --git a/dav_events/choices.py b/dav_events/choices.py index 6388955..32b23de 100644 --- a/dav_events/choices.py +++ b/dav_events/choices.py @@ -31,6 +31,10 @@ class ChoiceSet(object): else: return False + @property + def codes(self): + return self._codes + def get_label(self, code): return self._labels[code] diff --git a/dav_registration/templates/dav_registration/event_list.html b/dav_registration/templates/dav_registration/event_list.html index 216e170..6b11534 100644 --- a/dav_registration/templates/dav_registration/event_list.html +++ b/dav_registration/templates/dav_registration/event_list.html @@ -18,16 +18,13 @@ - - + {% for event in event_list %} - - + {% endfor %}
diff --git a/dav_registration/views.py b/dav_registration/views.py index f2b39d0..0f3d1ac 100644 --- a/dav_registration/views.py +++ b/dav_registration/views.py @@ -51,21 +51,16 @@ class EventListView(generic.ListView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - if hasattr(self, 'init_sport_filter'): - context['init_sport_filter'] = self.init_sport_filter - if hasattr(self, 'init_level_filter'): - context['init_level_filter'] = self.init_level_filter + if hasattr(self, 'init_filter'): + context['init_filter'] = self.init_filter return context def get(self, request, *args, **kwargs): - if 'sport' in request.GET: - sport = request.GET['sport'] - if (sport, 'Bogus') in SPORT_CHOICES: - self.init_sport_filter = sport - if 'level' in request.GET: - level = request.GET['level'] - if (level, 'Bogus') in LEVEL_CHOICES: - self.init_level_filter = level + if 'filter' in request.GET: + choices = SPORT_CHOICES.codes + LEVEL_CHOICES.codes + filter_input = request.GET['filter'].split(',') + filter_cleaned = list(set(filter_input) & set(choices)) + self.init_filter = filter_cleaned return super().get(request, *args, **kwargs) From 0c0d538677e8c13dfca4b9270e8b9431952d7196 Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Thu, 14 Mar 2024 10:53:49 +0100 Subject: [PATCH 05/29] Added a demo gitea workflow --- .gitea/workflows/demo.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .gitea/workflows/demo.yaml diff --git a/.gitea/workflows/demo.yaml b/.gitea/workflows/demo.yaml new file mode 100644 index 0000000..0f582f8 --- /dev/null +++ b/.gitea/workflows/demo.yaml @@ -0,0 +1,19 @@ +name: Gitea Actions Demo +run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀 +on: [push] + +jobs: + Explore-Gitea-Actions: + runs-on: linux_amd64 + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event." + - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!" + - run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}." + - name: Check out repository code + uses: actions/checkout@v3 + - run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner." + - run: echo "🖥️ The workflow is now ready to test your code on the runner." + - name: List files in the repository + run: | + ls ${{ gitea.workspace }} + - run: echo "🍏 This job's status is ${{ job.status }}." From 0502b3c2edb2442e04f097828c59b0e2841e47da Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Thu, 14 Mar 2024 11:16:33 +0100 Subject: [PATCH 06/29] Play with gitea workflows --- .gitea/workflows/demo.yaml | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/.gitea/workflows/demo.yaml b/.gitea/workflows/demo.yaml index 0f582f8..a387e38 100644 --- a/.gitea/workflows/demo.yaml +++ b/.gitea/workflows/demo.yaml @@ -1,19 +1,10 @@ -name: Gitea Actions Demo -run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀 +name: Gitea Test on: [push] jobs: Explore-Gitea-Actions: runs-on: linux_amd64 steps: - - run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event." - - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!" - - run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}." - - name: Check out repository code - uses: actions/checkout@v3 - - run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner." - - run: echo "🖥️ The workflow is now ready to test your code on the runner." - - name: List files in the repository - run: | - ls ${{ gitea.workspace }} - - run: echo "🍏 This job's status is ${{ job.status }}." + - run: echo "Echo ${{ gitea }}" + - run: echo "Echo ${{ runner }}" + - run: echo "Echo ${{ job }}" From 5d8d2e72d675eccbe4e4ca985230134dfc0e6f60 Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Thu, 14 Mar 2024 11:25:43 +0100 Subject: [PATCH 07/29] More workflow experiments --- .gitea/workflows/demo.yaml | 10 ---------- .gitea/workflows/test.yml | 10 ++++++++++ 2 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 .gitea/workflows/demo.yaml create mode 100644 .gitea/workflows/test.yml diff --git a/.gitea/workflows/demo.yaml b/.gitea/workflows/demo.yaml deleted file mode 100644 index a387e38..0000000 --- a/.gitea/workflows/demo.yaml +++ /dev/null @@ -1,10 +0,0 @@ -name: Gitea Test -on: [push] - -jobs: - Explore-Gitea-Actions: - runs-on: linux_amd64 - steps: - - run: echo "Echo ${{ gitea }}" - - run: echo "Echo ${{ runner }}" - - run: echo "Echo ${{ job }}" diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..7049669 --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,10 @@ +name: Run tests +on: [push] + +jobs: + run-tests: + runs-on: linux_amd64 + steps: + - run: echo "Echo ${{ gitea.repository }}" + - run: echo "Echo ${{ gitea.repositoryUrl }}" + - run: echo "Echo ${{ gitea.server_url }}" From 7543f00cad6490b1fdafb65820f492dc3848050e Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Thu, 14 Mar 2024 11:28:10 +0100 Subject: [PATCH 08/29] workflow experiment --- .gitea/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 7049669..f96fb56 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -5,6 +5,6 @@ jobs: run-tests: runs-on: linux_amd64 steps: - - run: echo "Echo ${{ gitea.repository }}" - - run: echo "Echo ${{ gitea.repositoryUrl }}" - - run: echo "Echo ${{ gitea.server_url }}" + - run: pwd + - run: ls + - run: git clone "${{ gitea.server_url }}/${{ gitea.repository }}" From cde59a9012ec48dd480ada9dc923f0499c3bfc9b Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Thu, 14 Mar 2024 11:40:54 +0100 Subject: [PATCH 09/29] Workflow test --- .gitea/workflows/test.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index f96fb56..efe82b4 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -2,9 +2,11 @@ name: Run tests on: [push] jobs: - run-tests: + make-test: + name: Run make test runs-on: linux_amd64 steps: - run: pwd - - run: ls - run: git clone "${{ gitea.server_url }}/${{ gitea.repository }}" + - run: pwd + - run: ls From f8bd2f922d3fa7fa49cc927fc649d3a7510b5527 Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Thu, 14 Mar 2024 11:48:34 +0100 Subject: [PATCH 10/29] Another test --- .gitea/workflows/test.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index efe82b4..7c16c9e 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -6,7 +6,11 @@ jobs: name: Run make test runs-on: linux_amd64 steps: - - run: pwd - - run: git clone "${{ gitea.server_url }}/${{ gitea.repository }}" - - run: pwd - - run: ls + - name: "Checkout the repository" + run: git clone "${{ gitea.server_url }}/${{ gitea.repository }}" + - name: "real Run make test" + working-directory: ./django-dav-events + run: pwd + - name: "ls" + working-directory: ./django-dav-events + run: ls From b01d3fe5aed25efaf4a91c1a9a0db5fb73dbdf5e Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Thu, 14 Mar 2024 11:51:18 +0100 Subject: [PATCH 11/29] Will it work? --- .gitea/workflows/test.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 7c16c9e..c46eca2 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -7,10 +7,7 @@ jobs: runs-on: linux_amd64 steps: - name: "Checkout the repository" - run: git clone "${{ gitea.server_url }}/${{ gitea.repository }}" - - name: "real Run make test" - working-directory: ./django-dav-events - run: pwd - - name: "ls" - working-directory: ./django-dav-events - run: ls + run: git clone "${{ gitea.server_url }}/${{ gitea.repository }}" ./repository + - name: "Run make test" + working-directory: ./repository + run: make test From 4e9da7789641d1806c59b467ac84911e2b56b8cd Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Thu, 14 Mar 2024 16:00:50 +0100 Subject: [PATCH 12/29] Improved gitea test pipeline --- .gitea/workflows/{test.yml => test_on_push.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .gitea/workflows/{test.yml => test_on_push.yml} (90%) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test_on_push.yml similarity index 90% rename from .gitea/workflows/test.yml rename to .gitea/workflows/test_on_push.yml index c46eca2..d337f9f 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test_on_push.yml @@ -4,7 +4,7 @@ on: [push] jobs: make-test: name: Run make test - runs-on: linux_amd64 + runs-on: [django-dav-events] steps: - name: "Checkout the repository" run: git clone "${{ gitea.server_url }}/${{ gitea.repository }}" ./repository From fd14702e4f6628697b9414d3e8e0088baa5dbfe4 Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Thu, 14 Mar 2024 16:45:37 +0100 Subject: [PATCH 13/29] Tests are broken --- setup.py | 13 ++++++++----- tox.ini | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 1d76df9..dd31e5a 100644 --- a/setup.py +++ b/setup.py @@ -21,14 +21,16 @@ class SetupPythonEnvironment(MyCommand): def run(self): python_bin = sys.executable if sys.executable else 'python' - python_ver = sys.version_info.major - if python_ver == 3: - path = os.path.join('env', 'python3') + python_major_ver = sys.version_info.major + python_minor_ver = sys.version_info.minor + if python_major_ver == 3: + dirname = 'python%i.%i' % (python_major_ver, python_minor_ver) + path = os.path.join('env', dirname) symlink_path = os.path.join('env', 'python') venv_module = 'venv' - prompt = 'py3-dav' + prompt = 'py%i.%i-dav' % (python_major_ver, python_minor_ver) else: - sys.stderr.write('Python %d is not supported.\n' % python_ver) + sys.stderr.write('Python %d is not supported.\n' % python_major_ver) sys.exit(posix.EX_USAGE) print('Creating new python environment in {path}'.format(path=path)) @@ -119,6 +121,7 @@ setup( 'django-datetime-widget2', 'pytz', 'selenium', + 'setuptools', 'coverage', ], extras_require={ diff --git a/tox.ini b/tox.ini index bd6dda3..52eb553 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py311 +envlist = py312 [testenv] recreate = false From 6242efd7484078dbb7e43a234bcd75f0c664efd8 Mon Sep 17 00:00:00 2001 From: heinzel Date: Fri, 15 Mar 2024 11:59:27 +0100 Subject: [PATCH 14/29] Run tests under python 3.11 and 3.12 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 52eb553..a04e348 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py312 +envlist = py311, py312 [testenv] recreate = false From 4da2430ed1c9f2e6294df087edf3471f0e2ccf29 Mon Sep 17 00:00:00 2001 From: heinzel Date: Fri, 15 Mar 2024 11:59:59 +0100 Subject: [PATCH 15/29] Make tests work again --- dav_events/tests/test_converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dav_events/tests/test_converters.py b/dav_events/tests/test_converters.py index bd94c86..dd4588d 100644 --- a/dav_events/tests/test_converters.py +++ b/dav_events/tests/test_converters.py @@ -51,7 +51,7 @@ class Iso8601SerializerTestCase(TestCase): for value in invalid_values: emsg = ('Expected datetime.datetime, datetime.date or datetime.time,' ' not {}'.format(value.__class__.__name__)) - with self.assertRaisesRegexp(ValueError, emsg): + with self.assertRaisesRegex(ValueError, emsg): Iso8601Serializer.serialize(value) serialized = Iso8601Serializer.serialize(value, ignore_unsupported_input=True) self.assertEqual(serialized, value) From 2cad3d8f97aa9d78ce4bcd8b0185ede2539ea83b Mon Sep 17 00:00:00 2001 From: heinzel Date: Fri, 15 Mar 2024 16:54:48 +0100 Subject: [PATCH 16/29] Add a nightly test workflow --- .gitea/workflows/nightly_test.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .gitea/workflows/nightly_test.yml diff --git a/.gitea/workflows/nightly_test.yml b/.gitea/workflows/nightly_test.yml new file mode 100644 index 0000000..6cfe39d --- /dev/null +++ b/.gitea/workflows/nightly_test.yml @@ -0,0 +1,15 @@ +name: Run tests every night +on: + schedule: + - cron: "05 17 * * *" + +jobs: + make-test: + name: Run make test + runs-on: [django-dav-events] + steps: + - name: "Checkout the repository" + run: git clone "${{ gitea.server_url }}/${{ gitea.repository }}" ./repository + - name: "Run make test" + working-directory: ./repository + run: make test From b49cdc4e0ea95dacbe51a6bb2a24b7803d9c6222 Mon Sep 17 00:00:00 2001 From: heinzel Date: Fri, 15 Mar 2024 17:21:06 +0100 Subject: [PATCH 17/29] Added more gitea Actions --- .gitea/workflows/deploy_stage.yml | 15 +++++++++++++++ .gitea/workflows/nightly_test.yml | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 .gitea/workflows/deploy_stage.yml diff --git a/.gitea/workflows/deploy_stage.yml b/.gitea/workflows/deploy_stage.yml new file mode 100644 index 0000000..6b91e55 --- /dev/null +++ b/.gitea/workflows/deploy_stage.yml @@ -0,0 +1,15 @@ +name: Deploy into stage environment +on: workflow_dispatch + +jobs: + make-deploy: + name: Deploy into stage environment + runs-on: [django-dav-events, kitty] + steps: + - name: "Checkout the repository" + run: git clone "${{ gitea.server_url }}/${{ gitea.repository }}" ./repository + - name: "Run make deploy" + working-directory: ./repository + env: + DEPLOY_DIR: "/var/www/touren.alpenverein-karlsruhe.de/wsgi/django-dav-events.stage" + run: make deploy diff --git a/.gitea/workflows/nightly_test.yml b/.gitea/workflows/nightly_test.yml index 6cfe39d..a4b5fc0 100644 --- a/.gitea/workflows/nightly_test.yml +++ b/.gitea/workflows/nightly_test.yml @@ -1,7 +1,7 @@ -name: Run tests every night +name: Run tests every night at 05:05 on: schedule: - - cron: "05 17 * * *" + - cron: "05 05 * * *" jobs: make-test: From 9c43cc6d69561cdacac1a727d8591d44da4f5097 Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Fri, 12 Jul 2024 11:02:39 +0200 Subject: [PATCH 18/29] Trigger deploy stage workflow on push to stage branch --- .gitea/workflows/deploy_stage.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/deploy_stage.yml b/.gitea/workflows/deploy_stage.yml index 6b91e55..c780d97 100644 --- a/.gitea/workflows/deploy_stage.yml +++ b/.gitea/workflows/deploy_stage.yml @@ -1,13 +1,17 @@ name: Deploy into stage environment -on: workflow_dispatch +on: + push: + branches: + - stage + workflow_dispatch: jobs: make-deploy: name: Deploy into stage environment runs-on: [django-dav-events, kitty] steps: - - name: "Checkout the repository" - run: git clone "${{ gitea.server_url }}/${{ gitea.repository }}" ./repository + - name: "Checkout stage branch of the repository" + run: git clone -b stage "${{ gitea.server_url }}/${{ gitea.repository }}" ./repository - name: "Run make deploy" working-directory: ./repository env: From 4e525b2d68f38a9df259e2e7276513bc478cbfbc Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Fri, 2 Aug 2024 13:14:58 +0200 Subject: [PATCH 19/29] gitea workflow: start switching from using Makefile to direkt commands --- .gitea/workflows/nightly_test.yml | 8 ++++---- .gitea/workflows/test_on_push.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.gitea/workflows/nightly_test.yml b/.gitea/workflows/nightly_test.yml index a4b5fc0..25a90b9 100644 --- a/.gitea/workflows/nightly_test.yml +++ b/.gitea/workflows/nightly_test.yml @@ -4,12 +4,12 @@ on: - cron: "05 05 * * *" jobs: - make-test: - name: Run make test + run-tests: + name: Execute tox to run the test suite runs-on: [django-dav-events] steps: - name: "Checkout the repository" run: git clone "${{ gitea.server_url }}/${{ gitea.repository }}" ./repository - - name: "Run make test" + - name: "Run test via tox" working-directory: ./repository - run: make test + run: tox diff --git a/.gitea/workflows/test_on_push.yml b/.gitea/workflows/test_on_push.yml index d337f9f..38d3ada 100644 --- a/.gitea/workflows/test_on_push.yml +++ b/.gitea/workflows/test_on_push.yml @@ -2,12 +2,12 @@ name: Run tests on: [push] jobs: - make-test: - name: Run make test + run-tests: + name: Execute tox to run the test suite runs-on: [django-dav-events] steps: - name: "Checkout the repository" run: git clone "${{ gitea.server_url }}/${{ gitea.repository }}" ./repository - - name: "Run make test" + - name: "Run tests via tox" working-directory: ./repository - run: make test + run: tox From af838d8fb6a7baad5caa8d618cfb0b527625c4a0 Mon Sep 17 00:00:00 2001 From: heinzel Date: Fri, 9 Aug 2024 13:25:11 +0200 Subject: [PATCH 20/29] Gitea Workflow deploy_stage does not use Makefile anymore --- .gitea/workflows/deploy_stage.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.gitea/workflows/deploy_stage.yml b/.gitea/workflows/deploy_stage.yml index c780d97..70f79f9 100644 --- a/.gitea/workflows/deploy_stage.yml +++ b/.gitea/workflows/deploy_stage.yml @@ -5,15 +5,17 @@ on: - stage workflow_dispatch: +env: + DEPLOY_DIR: "/var/www/touren.alpenverein-karlsruhe.de/wsgi/django-dav-events.stage" + jobs: - make-deploy: + deploy-on-stage: name: Deploy into stage environment runs-on: [django-dav-events, kitty] steps: - - name: "Checkout stage branch of the repository" - run: git clone -b stage "${{ gitea.server_url }}/${{ gitea.repository }}" ./repository - - name: "Run make deploy" - working-directory: ./repository - env: - DEPLOY_DIR: "/var/www/touren.alpenverein-karlsruhe.de/wsgi/django-dav-events.stage" - run: make deploy + - name: "Migrate database" + run: $DEPLOY_DIR/python/bin/python $DEPLOY_DIR/django/manage.py migrate --noinput + - name: "Collect static files" + run: $DEPLOY_DIR/python/bin/python $DEPLOY_DIR/django/manage.py collectstatic --noinput + - name: "Touch wsgi file" + run: touch $DEPLOY_DIR/django/main/wsgi.py From 8a766c760dc7bd692f14e687557e85f22c9d3323 Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Thu, 29 Aug 2024 14:09:44 +0200 Subject: [PATCH 21/29] Updated tests for new version of selenium --- dav_auth/tests/generic.py | 2 +- dav_auth/tests/test_screenshots.py | 58 ++++----- dav_auth/tests/test_templates.py | 10 +- dav_events/tests/test_screenshots.py | 186 +++++++++++++-------------- 4 files changed, 128 insertions(+), 128 deletions(-) diff --git a/dav_auth/tests/generic.py b/dav_auth/tests/generic.py index ace764a..eede63a 100644 --- a/dav_auth/tests/generic.py +++ b/dav_auth/tests/generic.py @@ -9,7 +9,7 @@ class SeleniumAuthMixin: username_field = self.wait_on_presence(driver, (By.ID, 'id_username')) username_field.clear() username_field.send_keys(username) - password_field = driver.find_element_by_id('id_password') + password_field = driver.find_element(By.ID, 'id_password') password_field.clear() password_field.send_keys(password) password_field.send_keys(Keys.RETURN) diff --git a/dav_auth/tests/test_screenshots.py b/dav_auth/tests/test_screenshots.py index 168476a..9d30265 100644 --- a/dav_auth/tests/test_screenshots.py +++ b/dav_auth/tests/test_screenshots.py @@ -31,25 +31,25 @@ class TestCase(ScreenshotTestCase): # Go to login page via login button on root page -> save plain login form c = self.selenium c.get(self.complete_url('/')) - link = c.find_element_by_css_selector('#login-widget a') + link = c.find_element(By.CSS_SELECTOR, '#login-widget a') link.click() self.wait_on_presence(c, (By.ID, 'id_username')) self.save_screenshot('empty_login_form', sequence=sequence_name) # Fill in a password -> Save dots in password field - username_field = c.find_element_by_id('id_username') + username_field = c.find_element(By.ID, 'id_username') username_field.clear() username_field.send_keys('username') - password_field = c.find_element_by_id('id_password') + password_field = c.find_element(By.ID, 'id_password') password_field.clear() password_field.send_keys(self.test_password) self.save_screenshot('filled_login_form', sequence=sequence_name) # Wrong username -> save error message - username_field = c.find_element_by_id('id_username') + username_field = c.find_element(By.ID, 'id_username') username_field.clear() username_field.send_keys(self.test_username[::-1]) - password_field = c.find_element_by_id('id_password') + 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) @@ -58,10 +58,10 @@ class TestCase(ScreenshotTestCase): alert_button.click() # Wrong password -> save error message - username_field = c.find_element_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 = c.find_element(By.ID, 'id_password') password_field.clear() password_field.send_keys(self.test_password[::-1]) password_field.send_keys(Keys.RETURN) @@ -72,10 +72,10 @@ class TestCase(ScreenshotTestCase): # Login of inactive user -> save error message self.user.is_active = False self.user.save() - username_field = c.find_element_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 = c.find_element(By.ID, 'id_password') password_field.clear() password_field.send_keys(self.test_password) password_field.send_keys(Keys.RETURN) @@ -87,10 +87,10 @@ class TestCase(ScreenshotTestCase): self.user.save() # 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.send_keys(self.test_username) - password_field = c.find_element_by_id('id_password') + 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) @@ -104,18 +104,18 @@ class TestCase(ScreenshotTestCase): self.save_screenshot('user_menu', sequence=sequence_name) # Click on 'set password' -> save set password page - user_menu = c.find_element_by_css_selector('#login-widget ul') - link = user_menu.find_element_by_partial_link_text(ugettext('Passwort ändern')) + user_menu = c.find_element(By.CSS_SELECTOR, '#login-widget ul') + link = user_menu.find_element(By.PARTIAL_LINK_TEXT, ugettext('Passwort ändern')) link.click() password_field = self.wait_on_presence(c, (By.ID, 'id_new_password')) self.save_screenshot('empty_set_password_form', sequence=sequence_name) # Fill in a password -> Save dots in password field - send_mail_field = c.find_element_by_id('id_send_password_mail') + send_mail_field = c.find_element(By.ID, 'id_send_password_mail') send_mail_field.click() password_field.clear() password_field.send_keys(self.test_password) - password2_field = c.find_element_by_id('id_new_password_repeat') + password2_field = c.find_element(By.ID, 'id_new_password_repeat') password2_field.clear() password2_field.send_keys(self.test_password) self.save_screenshot('filled_set_password_form', sequence=sequence_name) @@ -131,10 +131,10 @@ class TestCase(ScreenshotTestCase): # New passwords too common and too short -> save error message password = 'abcdef' - 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.send_keys(password) - password2_field = c.find_element_by_id('id_new_password_repeat') + password2_field = c.find_element(By.ID, 'id_new_password_repeat') password2_field.clear() password2_field.send_keys(password) password2_field.send_keys(Keys.RETURN) @@ -143,10 +143,10 @@ class TestCase(ScreenshotTestCase): # New passwords entirely_numeric -> save error message password = '9126735804' - 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.send_keys(password) - password2_field = c.find_element_by_id('id_new_password_repeat') + password2_field = c.find_element(By.ID, 'id_new_password_repeat') password2_field.clear() password2_field.send_keys(password) password2_field.send_keys(Keys.RETURN) @@ -155,10 +155,10 @@ class TestCase(ScreenshotTestCase): # New passwords too similar -> save error message password = self.test_username - 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.send_keys(password) - password2_field = c.find_element_by_id('id_new_password_repeat') + password2_field = c.find_element(By.ID, 'id_new_password_repeat') password2_field.clear() password2_field.send_keys(password) password2_field.send_keys(Keys.RETURN) @@ -167,10 +167,10 @@ class TestCase(ScreenshotTestCase): # Change password -> save success message 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.send_keys(password) - password2_field = c.find_element_by_id('id_new_password_repeat') + password2_field = c.find_element(By.ID, 'id_new_password_repeat') password2_field.clear() password2_field.send_keys(password) password2_field.send_keys(Keys.RETURN) @@ -179,7 +179,7 @@ class TestCase(ScreenshotTestCase): # Get password recreate page -> since we are logged in, it should # redirect to set password page again -> save - html = c.find_element_by_tag_name('html') + html = c.find_element(By.TAG_NAME, 'html') c.get(self.complete_url(reverse('dav_auth:recreate_password'))) self.wait_until_stale(c, html) self.wait_on_presence(c, (By.ID, 'id_new_password')) @@ -188,19 +188,19 @@ class TestCase(ScreenshotTestCase): # Click on 'logout' -> save page dropdown_button = self.wait_on_presence(c, (By.ID, 'user_dropdown_button')) dropdown_button.click() - user_menu = c.find_element_by_css_selector('#login-widget ul') - link = user_menu.find_element_by_partial_link_text(ugettext('Logout')) + user_menu = c.find_element(By.CSS_SELECTOR, '#login-widget ul') + link = user_menu.find_element(By.PARTIAL_LINK_TEXT, ugettext('Logout')) link.click() self.wait_until_stale(c, user_menu) self.save_screenshot('logout_succeed', sequence=sequence_name) # 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() self.wait_on_presence(c, (By.ID, 'id_username')) # Locate password recreate link, click it -> save password recreate form - link = c.find_element_by_partial_link_text(ugettext('Passwort vergessen')) + link = c.find_element(By.PARTIAL_LINK_TEXT, ugettext('Passwort vergessen')) link.click() username_field = self.wait_on_presence(c, (By.ID, 'id_username')) self.save_screenshot('empty_recreate_password_form', sequence=sequence_name) @@ -212,7 +212,7 @@ class TestCase(ScreenshotTestCase): self.save_screenshot('recreate_password_invalid_user', sequence=sequence_name) # Locate password recreate link, click it - link = c.find_element_by_partial_link_text(ugettext('Passwort vergessen')) + link = c.find_element(By.PARTIAL_LINK_TEXT, ugettext('Passwort vergessen')) link.click() username_field = self.wait_on_presence(c, (By.ID, 'id_username')) diff --git a/dav_auth/tests/test_templates.py b/dav_auth/tests/test_templates.py index 211d7c0..b33ca12 100644 --- a/dav_auth/tests/test_templates.py +++ b/dav_auth/tests/test_templates.py @@ -40,7 +40,7 @@ class TestCase(SeleniumAuthMixin, SeleniumTestCase): c = self.selenium c.get(self.complete_url('/')) try: - c.find_element_by_css_selector('#login-widget a') + c.find_element(By.CSS_SELECTOR, '#login-widget a') except NoSuchElementException as e: # pragma: no cover self.fail(str(e)) @@ -50,9 +50,9 @@ class TestCase(SeleniumAuthMixin, SeleniumTestCase): location = reverse('dav_auth:login') c.get(self.complete_url(location)) - field = c.find_element_by_id('id_username') + field = c.find_element(By.ID, 'id_username') self.assertEqual(field.get_attribute('required'), 'true') - field = c.find_element_by_id('id_password') + field = c.find_element(By.ID, 'id_password') self.assertEqual(field.get_attribute('required'), 'true') def test_required_fields_in_set_password_form(self): @@ -61,7 +61,7 @@ class TestCase(SeleniumAuthMixin, SeleniumTestCase): c.get(self.complete_url(reverse('dav_auth:set_password'))) field = self.wait_on_presence(c, (By.ID, 'id_new_password')) self.assertEqual(field.get_attribute('required'), 'true') - field = c.find_element_by_id('id_new_password_repeat') + field = c.find_element(By.ID, 'id_new_password_repeat') self.assertEqual(field.get_attribute('required'), 'true') - field = c.find_element_by_id('id_send_password_mail') + field = c.find_element(By.ID, 'id_send_password_mail') self.assertEqual(field.get_attribute('required'), None) diff --git a/dav_events/tests/test_screenshots.py b/dav_events/tests/test_screenshots.py index 78de5aa..40d7668 100644 --- a/dav_events/tests/test_screenshots.py +++ b/dav_events/tests/test_screenshots.py @@ -135,30 +135,30 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): self.save_screenshot('mode_form', sequence=sequence_name) if 'mode' in data: - field = c.find_element_by_id('id_mode') - radio = field.find_element_by_css_selector('input[value=\'{}\']'.format(data['mode'])) + field = c.find_element(By.ID, 'id_mode') + radio = field.find_element(By.CSS_SELECTOR, 'input[value=\'{}\']'.format(data['mode'])) radio.click() if 'sport' in data: - field = c.find_element_by_id('id_sport') - radio = field.find_element_by_css_selector('input[value=\'{}\']'.format(data['sport'])) + field = c.find_element(By.ID, 'id_sport') + radio = field.find_element(By.CSS_SELECTOR, 'input[value=\'{}\']'.format(data['sport'])) radio.click() if 'level' in data: - field = c.find_element_by_id('id_level') - radio = field.find_element_by_css_selector('input[value=\'{}\']'.format(data['level'])) + field = c.find_element(By.ID, 'id_level') + radio = field.find_element(By.CSS_SELECTOR, 'input[value=\'{}\']'.format(data['level'])) radio.click() if 'ski_lift' in data and data['ski_lift']: - field = c.find_element_by_id('id_ski_lift') + field = c.find_element(By.ID, 'id_ski_lift') field.click() - field = c.find_element_by_css_selector('input#id_first_day_widget') + field = c.find_element(By.CSS_SELECTOR, 'input#id_first_day_widget') field.send_keys(data['first_day']) - td = c.find_element_by_css_selector('div.datetimepicker-days td.active') + td = c.find_element(By.CSS_SELECTOR, 'div.datetimepicker-days td.active') td.click() - field = c.find_element_by_css_selector('input#id_last_day_widget') + field = c.find_element(By.CSS_SELECTOR, 'input#id_last_day_widget') field.click() if screenshots: self.save_screenshot('last_date_widget', sequence=sequence_name) @@ -166,74 +166,74 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): field.send_keys(data['last_day']) if 'alt_first_day' in data: - field = c.find_element_by_css_selector('input#id_alt_first_day_widget') + field = c.find_element(By.CSS_SELECTOR, 'input#id_alt_first_day_widget') field.send_keys(data['alt_first_day']) if 'alt_last_day' in data: - field = c.find_element_by_css_selector('input#id_alt_last_day_widget') + field = c.find_element(By.CSS_SELECTOR, 'input#id_alt_last_day_widget') field.send_keys(data['alt_last_day']) - button = c.find_element_by_id('btn-form-next') + button = c.find_element(By.ID, 'btn-form-next') button.click() self.wait_until_stale(c, button) if screenshots: self.save_screenshot('location_form', sequence=sequence_name) if 'country' in data: - field = c.find_element_by_id('id_country') + field = c.find_element(By.ID, 'id_country') Select(field).select_by_value(data['country']) if 'terrain' in data: - field = c.find_element_by_id('id_terrain') + field = c.find_element(By.ID, 'id_terrain') Select(field).select_by_value(data['terrain']) if 'location' in data: - field = c.find_element_by_id('id_location') + field = c.find_element(By.ID, 'id_location') field.send_keys(data['location']) if 'transport_other' in data: - field = c.find_element_by_id('id_transport_other') + field = c.find_element(By.ID, 'id_transport_other') field.send_keys(data['transport_other']) if 'transport' in data: - field = c.find_element_by_id('id_transport') + field = c.find_element(By.ID, 'id_transport') Select(field).select_by_value(data['transport']) - button = c.find_element_by_id('btn-form-next') + button = c.find_element(By.ID, 'btn-form-next') button.click() self.wait_until_stale(c, button) if screenshots: self.save_screenshot('journey_form', sequence=sequence_name) if 'meeting_point_other' in data: - field = c.find_element_by_id('id_meeting_point_other') + field = c.find_element(By.ID, 'id_meeting_point_other') field.send_keys(data['meeting_point_other']) if 'meeting_point' in data: - field = c.find_element_by_id('id_meeting_point') + field = c.find_element(By.ID, 'id_meeting_point') Select(field).select_by_value(data['meeting_point']) if 'meeting_time' in data: - field = c.find_element_by_css_selector('input#id_meeting_time_widget') + field = c.find_element(By.CSS_SELECTOR, 'input#id_meeting_time_widget') field.send_keys(data['meeting_time']) if 'departure_time' in data: - field = c.find_element_by_css_selector('input#id_departure_time_widget') + field = c.find_element(By.CSS_SELECTOR, 'input#id_departure_time_widget') field.send_keys(data['departure_time']) if 'departure_ride' in data: - field = c.find_element_by_id('id_departure_ride') + field = c.find_element(By.ID, 'id_departure_ride') field.send_keys(data['departure_ride']) if 'return_departure_time' in data: - field = c.find_element_by_css_selector('input#id_return_departure_time_widget') + field = c.find_element(By.CSS_SELECTOR, 'input#id_return_departure_time_widget') field.send_keys(data['return_departure_time']) if 'return_arrival_time' in data: - field = c.find_element_by_css_selector('input#id_return_arrival_time_widget') + field = c.find_element(By.CSS_SELECTOR, 'input#id_return_arrival_time_widget') field.send_keys(data['return_arrival_time']) if 'arrival_previous_day' in data and data['arrival_previous_day']: - field = c.find_element_by_id('id_arrival_previous_day') + field = c.find_element(By.ID, 'id_arrival_previous_day') field.click() - button = c.find_element_by_id('btn-form-next') + button = c.find_element(By.ID, 'btn-form-next') button.click() self.wait_until_stale(c, button) @@ -242,18 +242,18 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): self.save_screenshot('accommodation_form', sequence=sequence_name) if 'basecamp' in data: - field = c.find_element_by_id('id_basecamp') + field = c.find_element(By.ID, 'id_basecamp') field.send_keys(data['basecamp']) if 'accommodation' in data: - field = c.find_element_by_id('id_accommodation') + field = c.find_element(By.ID, 'id_accommodation') Select(field).select_by_value(data['accommodation']) if 'meals_other' in data: - field = c.find_element_by_id('id_meals_other') + field = c.find_element(By.ID, 'id_meals_other') field.send_keys(data['meals_other']) - button = c.find_element_by_id('btn-form-next') + button = c.find_element(By.ID, 'btn-form-next') button.click() self.wait_until_stale(c, button) @@ -261,7 +261,7 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): self.save_screenshot('requirements_form', sequence=sequence_name) if 'requirements_add' in data: - field = c.find_element_by_id('id_requirements') + field = c.find_element(By.ID, 'id_requirements') field.send_keys(Keys.RETURN) if isinstance(data['requirements_add'], list): field.send_keys(Keys.RETURN.join(data['requirements_add'])) @@ -269,14 +269,14 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): field.send_keys(data['requirements_add']) if 'pre_meeting_1' in data: - field = c.find_element_by_css_selector('input#id_pre_meeting_1_widget') + field = c.find_element(By.CSS_SELECTOR, 'input#id_pre_meeting_1_widget') field.send_keys(data['pre_meeting_1']) if 'pre_meeting_2' in data: - field = c.find_element_by_css_selector('input#id_pre_meeting_2_widget') + field = c.find_element(By.CSS_SELECTOR, 'input#id_pre_meeting_2_widget') field.send_keys(data['pre_meeting_2']) - button = c.find_element_by_id('btn-form-next') + button = c.find_element(By.ID, 'btn-form-next') button.click() self.wait_until_stale(c, button) if screenshots: @@ -289,60 +289,60 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): field.send_keys(data['trainer_firstname']) if 'trainer_familyname' in data: - field = c.find_element_by_id('id_trainer_familyname') + field = c.find_element(By.ID, 'id_trainer_familyname') if auth: field.clear() field.send_keys(data['trainer_familyname']) if 'trainer_email' in data: - field = c.find_element_by_id('id_trainer_email') + field = c.find_element(By.ID, 'id_trainer_email') if auth: field.clear() field.send_keys(data['trainer_email']) if 'trainer_phone' in data: - field = c.find_element_by_id('id_trainer_phone') + field = c.find_element(By.ID, 'id_trainer_phone') if auth: field.clear() field.send_keys(data['trainer_phone']) if 'trainer_2_fullname' in data: - field = c.find_element_by_id('id_trainer_2_fullname') + field = c.find_element(By.ID, 'id_trainer_2_fullname') field.send_keys(data['trainer_2_fullname']) if 'trainer_3_fullname' in data: - field = c.find_element_by_id('id_trainer_3_fullname') + field = c.find_element(By.ID, 'id_trainer_3_fullname') field.send_keys(data['trainer_3_fullname']) - button = c.find_element_by_id('btn-form-next') + button = c.find_element(By.ID, 'btn-form-next') button.click() self.wait_until_stale(c, button) if screenshots: self.save_screenshot('registration_form', sequence=sequence_name) if 'deadline' in data: - field = c.find_element_by_id('id_deadline') - radio = field.find_element_by_css_selector('input[value=\'{}\']'.format(data['deadline'])) + field = c.find_element(By.ID, 'id_deadline') + radio = field.find_element(By.CSS_SELECTOR, 'input[value=\'{}\']'.format(data['deadline'])) radio.click() - button = c.find_element_by_id('btn-form-next') + button = c.find_element(By.ID, 'btn-form-next') button.click() self.wait_until_stale(c, button) if screenshots: self.save_screenshot('charges_form', sequence=sequence_name) if 'charge' in data: - field = c.find_element_by_id('id_charge') + field = c.find_element(By.ID, 'id_charge') field.clear() field.send_keys(data['charge']) if 'additional_costs' in data: - field = c.find_element_by_id('id_additional_costs') + field = c.find_element(By.ID, 'id_additional_costs') field.clear() if data['additional_costs'] is not None: field.send_keys(data['additional_costs']) - button = c.find_element_by_id('btn-form-next') + button = c.find_element(By.ID, 'btn-form-next') button.click() self.wait_until_stale(c, button) @@ -350,49 +350,49 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): if screenshots: self.save_screenshot('training_form', sequence=sequence_name) - field = c.find_element_by_id('id_course_topic_1') + field = c.find_element(By.ID, 'id_course_topic_1') if isinstance(data['course_topic_1'], list): field.send_keys(Keys.RETURN.join(data['course_topic_1'])) else: field.send_keys(data['course_topic_1']) if 'course_topic_2' in data: - field = c.find_element_by_id('id_course_topic_2') + field = c.find_element(By.ID, 'id_course_topic_2') field.send_keys(data['course_topic_2']) if 'course_topic_3' in data: - button = c.find_element_by_id('btn-topic-add') + button = c.find_element(By.ID, 'btn-topic-add') button.click() - field = c.find_element_by_id('id_course_topic_3') + field = c.find_element(By.ID, 'id_course_topic_3') field.send_keys(data['course_topic_3']) if 'course_topic_4' in data: button.click() - field = c.find_element_by_id('id_course_topic_4') + field = c.find_element(By.ID, 'id_course_topic_4') field.send_keys(data['course_topic_4']) - field = c.find_element_by_id('id_course_goal_1') + field = c.find_element(By.ID, 'id_course_goal_1') if isinstance(data['course_goal_1'], list): field.send_keys(Keys.RETURN.join(data['course_goal_1'])) else: field.send_keys(data['course_goal_1']) if 'course_goal_2' in data: - field = c.find_element_by_id('id_course_goal_2') + field = c.find_element(By.ID, 'id_course_goal_2') field.send_keys(data['course_goal_2']) if 'course_goal_3' in data: - button = c.find_element_by_id('btn-goal-add') + button = c.find_element(By.ID, 'btn-goal-add') button.click() - field = c.find_element_by_id('id_course_goal_3') + field = c.find_element(By.ID, 'id_course_goal_3') field.send_keys(data['course_goal_3']) if 'course_goal_4' in data: button.click() - field = c.find_element_by_id('id_course_goal_4') + field = c.find_element(By.ID, 'id_course_goal_4') field.send_keys(data['course_goal_4']) - button = c.find_element_by_id('btn-form-next') + button = c.find_element(By.ID, 'btn-form-next') button.click() self.wait_until_stale(c, button) @@ -400,29 +400,29 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): self.save_screenshot('description_form', sequence=sequence_name) if 'title' in data: - field = c.find_element_by_id('id_title') + field = c.find_element(By.ID, 'id_title') field.clear() field.send_keys(data['title']) - field = c.find_element_by_id('id_description') + field = c.find_element(By.ID, 'id_description') if isinstance(data['description'], list): field.send_keys(Keys.RETURN.join(data['description'])) else: field.send_keys(data['description']) - button = c.find_element_by_id('btn-form-next') + button = c.find_element(By.ID, 'btn-form-next') button.click() self.wait_until_stale(c, button) if screenshots: self.save_screenshot('summary_form', sequence=sequence_name) - button = c.find_element_by_id('btn-form-back') + button = c.find_element(By.ID, 'btn-form-back') button.click() self.wait_until_stale(c, button) for i in range(0, 11): try: - button = c.find_element_by_id('btn-form-next') + button = c.find_element(By.ID, 'btn-form-next') button.click() self.wait_until_stale(c, button) except NoSuchElementException: @@ -431,13 +431,13 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): self.fail('Too many sub forms') if 'internal_note' in data: - field = c.find_element_by_id('id_internal_note') + field = c.find_element(By.ID, 'id_internal_note') if isinstance(data['internal_note'], list): field.send_keys(Keys.RETURN.join(data['internal_note'])) else: field.send_keys(data['internal_note']) - button = c.find_element_by_id('btn-form-submit') + button = c.find_element(By.ID, 'btn-form-submit') button.click() self.wait_until_stale(c, button) @@ -445,9 +445,9 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): if screenshots: self.save_screenshot('user_and_event_created_set_password', sequence=sequence_name) - field = c.find_element_by_id('id_new_password') + field = c.find_element(By.ID, 'id_new_password') field.send_keys(TEST_PASSWORD) - field = c.find_element_by_id('id_new_password_repeat') + field = c.find_element(By.ID, 'id_new_password_repeat') field.send_keys(TEST_PASSWORD) field.send_keys(Keys.RETURN) @@ -471,20 +471,20 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): if screenshots: self.save_screenshot('event_list_before', sequence=sequence_name) - link = c.find_element_by_link_text(title) + link = c.find_element(By.LINK_TEXT, title) link.click() self.wait_until_stale(c, link) if screenshots: self.save_screenshot('event_details', sequence=sequence_name) - button = c.find_element_by_id('btn-clone') + button = c.find_element(By.ID, 'btn-clone') button.click() self.wait_until_stale(c, button) for i in range(0, 11): try: - button = c.find_element_by_id('btn-form-next') + button = c.find_element(By.ID, 'btn-form-next') except NoSuchElementException: break @@ -492,8 +492,8 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): self.save_screenshot('form', sequence=sequence_name) try: - field = c.find_element_by_id('id_deadline') - radio = field.find_element_by_css_selector('input[value=\'OTHER\']') + field = c.find_element(By.ID, 'id_deadline') + radio = field.find_element(By.CSS_SELECTOR, 'input[value=\'OTHER\']') radio.click() except NoSuchElementException: pass @@ -506,7 +506,7 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): if screenshots: self.save_screenshot('summary', sequence=sequence_name) - button = c.find_element_by_id('btn-form-submit') + button = c.find_element(By.ID, 'btn-form-submit') button.click() self.wait_until_stale(c, button) @@ -523,28 +523,28 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): button.click() self.wait_until_stale(c, button) - link = c.find_element_by_link_text(title) + link = c.find_element(By.LINK_TEXT, title) link.click() self.wait_until_stale(c, link) - action_tabs = c.find_element_by_css_selector('.action-tabs > ul.nav-tabs') - tab = action_tabs.find_element_by_link_text(ugettext(u'Ändern')) + action_tabs = c.find_element(By.CSS_SELECTOR, '.action-tabs > ul.nav-tabs') + tab = action_tabs.find_element(By.LINK_TEXT, ugettext(u'Ändern')) tab.click() self.wait_until_stale(c, tab) - panels = c.find_elements_by_css_selector('#form-accordion .panel-collapse') + panels = c.find_elements(By.CSS_SELECTOR, '#form-accordion .panel-collapse') for panel in panels[:-1]: if screenshots: self.save_screenshot('edit-form', sequence=sequence_name) - button = panel.find_element_by_partial_link_text(ugettext(u'Nächster Abschnitt')) + button = panel.find_element(By.PARTIAL_LINK_TEXT, ugettext(u'Nächster Abschnitt')) button.click() time.sleep(.5) if screenshots: self.save_screenshot('edit-form', sequence=sequence_name) - button = c.find_element_by_css_selector('form button[type="submit"]') + button = c.find_element(By.CSS_SELECTOR, 'form button[type="submit"]') button.click() self.wait_until_stale(c, button) @@ -561,26 +561,26 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): if screenshots: self.save_screenshot('event_list_before', sequence=sequence_name) - link = c.find_element_by_link_text(title) + link = c.find_element(By.LINK_TEXT, title) link.click() self.wait_until_stale(c, link) if screenshots: self.save_screenshot('event_details', sequence=sequence_name) - button = c.find_element_by_id('btn-accept') + button = c.find_element(By.ID, 'btn-accept') button.click() time.sleep(.5) if screenshots: self.save_screenshot('accept_modal', sequence=sequence_name) - button = c.find_element_by_css_selector('#modal-accept-dialog a.btn-success') + button = c.find_element(By.CSS_SELECTOR, '#modal-accept-dialog a.btn-success') button.click() self.wait_until_stale(c, button) if screenshots: self.save_screenshot('accepted', sequence=sequence_name) - link = c.find_element_by_link_text(ugettext('Veranstaltungsliste')) + link = c.find_element(By.LINK_TEXT, ugettext('Veranstaltungsliste')) link.click() self.wait_until_stale(c, link) if screenshots: @@ -599,26 +599,26 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): if screenshots: self.save_screenshot('event_list_before', sequence=sequence_name) - link = c.find_element_by_link_text(title) + link = c.find_element(By.LINK_TEXT, title) link.click() self.wait_until_stale(c, link) if screenshots: self.save_screenshot('event_details', sequence=sequence_name) - button = c.find_element_by_id('btn-confirmpublication') + button = c.find_element(By.ID, 'btn-confirmpublication') button.click() time.sleep(.5) if screenshots: self.save_screenshot('confirmpublication_modal', sequence=sequence_name) - button = c.find_element_by_css_selector('#btn-confirmpublication-{}'.format(channel)) + button = c.find_element(By.CSS_SELECTOR, '#btn-confirmpublication-{}'.format(channel)) button.click() self.wait_until_stale(c, button) if screenshots: self.save_screenshot('confirmed_{}'.format(channel), sequence=sequence_name) - link = c.find_element_by_link_text(ugettext('Veranstaltungsliste')) + link = c.find_element(By.LINK_TEXT, ugettext('Veranstaltungsliste')) link.click() self.wait_until_stale(c, link) if screenshots: @@ -637,20 +637,20 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): if screenshots: self.save_screenshot('event_list_before', sequence=sequence_name) - link = c.find_element_by_link_text(title) + link = c.find_element(By.LINK_TEXT, title) link.click() self.wait_until_stale(c, link) if screenshots: self.save_screenshot('event_details', sequence=sequence_name) - button = c.find_element_by_id('btn-confirmclearance') + button = c.find_element(By.ID, 'btn-confirmclearance') button.click() self.wait_until_stale(c, button) if screenshots: self.save_screenshot('confirmed_clearance', sequence=sequence_name) - link = c.find_element_by_link_text(ugettext('Veranstaltungsliste')) + link = c.find_element(By.LINK_TEXT, ugettext('Veranstaltungsliste')) link.click() self.wait_until_stale(c, link) if screenshots: @@ -688,7 +688,7 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): self.create_event(TEST_EVENT_DATA_W, auth=True) self.create_event(TEST_EVENT_DATA_M, auth=True) - link = c.find_element_by_link_text(TEST_EVENT_DATA_W['title']) + link = c.find_element(By.LINK_TEXT, TEST_EVENT_DATA_W['title']) link.click() self.wait_until_stale(c, link) self.save_screenshot('owner_event_details') @@ -730,7 +730,7 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase): button.click() self.wait_until_stale(c, button) - link = c.find_element_by_partial_link_text(ugettext(u'Veranstaltungsliste herunterladen')) + link = c.find_element(By.PARTIAL_LINK_TEXT, ugettext(u'Veranstaltungsliste herunterladen')) link.click() self.wait_until_stale(c, link) self.save_screenshot('event_export_form') From 05da45f2715468ec4394777f97c16c9af4c74888 Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Mon, 9 Sep 2024 11:56:38 +0200 Subject: [PATCH 22/29] Modernize the meta files of the project --- INSTALL.rst | 31 ++++++++++++++++++------------- Makefile | 20 -------------------- README.rst | 4 +++- requirements.txt | 6 ++++++ setup.py | 33 +++------------------------------ tox.ini | 29 ++++++++++++++++++++++++----- 6 files changed, 54 insertions(+), 69 deletions(-) delete mode 100644 Makefile create mode 100644 requirements.txt diff --git a/INSTALL.rst b/INSTALL.rst index f20a374..008ec8a 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -1,12 +1,11 @@ REQUIREMENTS ============ -- Python 3 -- Python package virtualenv (in most cases this is distributed or installed together with python) -- Django (will be installed automatically) -- Several additional django related python packages (will be installed automatically) +- Python >= 3 +- Django and some other python packages, that will be installed throughout + the installation process For production use you surly want a real web server that supports WSGI -(e.g. Apache httpd with mod_wsgi) and a real Database like PostgreSQL. +(e.g. Apache httpd with mod_wsgi) and a real Database like PostgreSQL (psycopg2). QUICK INSTALLATION FOR THE IMPATIENT @@ -24,14 +23,11 @@ INSTALLATION It is strongly recommended to create a separated python environment for this django project. But it is not exactly necessary. -The creation of a separated python environment is very easy with the -virtualenv tool (a python package). - If you decide to not use virtualenv, proceed with step 2. - Create the python environment in a directory called ./env/python: - ``virtualenv --prompt="(dav)" ./env/python`` + ``python -m venv --prompt="(dav)" ./env/python`` - If you use a posix compatible shell (like bash, the linux default shell), you have to activate the environment for the current shell session @@ -50,16 +46,25 @@ If you have left the session or deactivated the environment and want to reactivate the environment (e.g. to execute a python command) use the previous ``source ...`` command. -2. Install files ----------------- +2. Install requirements +----------------------- + +- ``python -m pip install -r requirements.txt`` + +3. Install project code in development mode +------------------------------------------- + +- ``python -m pip install -e .`` + +4. Setup django project directory +--------------------------------- -- ``python setup.py develop`` - ``django-dav-events-admin setup ./env/django`` The django project directory ('./env/django' within the previous example) will be called *project root* for now on. -3. Enable modules +5. Enable modules ----------------- Our web application consist of several modules, that care about single aspects of the whole picture. diff --git a/Makefile b/Makefile deleted file mode 100644 index d2b8c8b..0000000 --- a/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -PYTHON := python - -DEPLOY_DIR ?= /var/www/wsgi/django-dav-events - -.PHONY: default help test deploy - -default: help - -help: - @echo "The make stuff is used by our CI/CD buildbot." - -test: - tox - -deploy: - git -C "$(DEPLOY_DIR)/src/django-dav-events" pull - "$(DEPLOY_DIR)/python/bin/python" "$(DEPLOY_DIR)/django/manage.py" migrate --noinput - "$(DEPLOY_DIR)/python/bin/python" "$(DEPLOY_DIR)/django/manage.py" collectstatic --noinput - touch "$(DEPLOY_DIR)/django/main/wsgi.py" - diff --git a/README.rst b/README.rst index b1d97bc..12cb170 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,8 @@ ABOUT ===== -This is the DAV Events django project. +django-dav-events is a django based web app to drive the +"Touren- & Kurseportal" of +"Sektion Karlsruhe im Deutschen Alpenverein (DAV) e.V." REQUIREMENTS diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d457057 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +babel +django<3.3 +django-bootstrap3 +django-countries +django-datetime-widget2 +setuptools diff --git a/setup.py b/setup.py index dd31e5a..a5a38e8 100644 --- a/setup.py +++ b/setup.py @@ -6,17 +6,7 @@ from setuptools import setup, find_packages from setuptools import Command -class MyCommand(Command): - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - -class SetupPythonEnvironment(MyCommand): +class SetupPythonEnvironment(Command): description = 'create a (virtual) python environment' def run(self): @@ -54,7 +44,7 @@ class SetupPythonEnvironment(MyCommand): print('- All others: source %s/bin/activate' % path) -class QuickSetup(MyCommand): +class QuickSetup(Command): description = 'create a typical installation for developing' def run(self): @@ -94,7 +84,7 @@ class QuickSetup(MyCommand): setup( name='django-dav-events', - version='2.1.2', + version='2.2.1', description='A django based web application project to organize DAV Events.', url='https://dev.heinzelwerk.de/git/DAV-KA/django-dav-events', author='Jens Kleineheismann', @@ -105,26 +95,9 @@ setup( }, packages=find_packages(), include_package_data=True, - test_suite='tests.test_suite', entry_points={ 'console_scripts': [ 'django-dav-admin = dav_base.console_scripts.admin:main', ], }, - install_requires=[ - 'babel', - #'django >= 1.11, < 2.0', - 'django >= 1.11, < 3.3', - # 'django-extensions', - 'django-bootstrap3', - 'django-countries', - 'django-datetime-widget2', - 'pytz', - 'selenium', - 'setuptools', - 'coverage', - ], - extras_require={ - 'production': ['psycopg2'], - }, ) diff --git a/tox.ini b/tox.ini index a04e348..510b9e5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,29 @@ [tox] -envlist = py311, py312 +envlist = fresh, coverage [testenv] -recreate = false setenv = PYTHONPATH = . -commands = python --version - python -m coverage run tests - coverage report --skip-covered +deps = -r{toxinidir}/requirements.txt + selenium +commands_pre = python --version + python -m django --version +commands = python tests + +[testenv:fast] +description = Only run the testsuite + +[testenv:fresh] +description = Run tests in freshly created environment (with coverage) +recreate = true +deps = {[testenv]deps} + coverage +commands_pre = {[testenv]commands_pre} + python -m coverage --version +commands = python -m coverage run tests + +[testenv:coverage] +description = Report test coverage +deps = {[testenv:fresh]deps} +commands = python -m coverage report --skip-covered + From 0c2261074a895503b9f1a2eb51030bea1bd1604e Mon Sep 17 00:00:00 2001 From: heinzel Date: Tue, 10 Sep 2024 09:32:51 +0200 Subject: [PATCH 23/29] Update INSTALL.rst --- INSTALL.rst | 42 ++++++++++++++++++++++++++++++++---------- setup.py | 11 ++++++++++- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/INSTALL.rst b/INSTALL.rst index 008ec8a..bb3207e 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -1,6 +1,6 @@ REQUIREMENTS ============ -- Python >= 3 +- Python >= 3.12 - Django and some other python packages, that will be installed throughout the installation process @@ -10,9 +10,18 @@ For production use you surly want a real web server that supports WSGI QUICK INSTALLATION FOR THE IMPATIENT ==================================== -- python setup.py mkpyenv +- python -m venv ./etc/python - source env/python/bin/activate -- python setup.py quickdev +- python -m pip install -r requirements.txt +- python -m pip install -e . +- django-dav-events-admin setup ./env/django +- python ./env/django/manage.py enable_module dav_auth +- python ./env/django/manage.py enable_module dav_events +- python ./env/django/manage.py enable_module dav_registration +- python ./env/django/manage.py enable_module dav_event_office +- python ./env/django/manage.py makemigrations +- python ./env/django/manage.py migrate +- python ./env/django/manage.py createsuperuser INSTALLATION @@ -23,8 +32,6 @@ INSTALLATION It is strongly recommended to create a separated python environment for this django project. But it is not exactly necessary. -If you decide to not use virtualenv, proceed with step 2. - - Create the python environment in a directory called ./env/python: ``python -m venv --prompt="(dav)" ./env/python`` @@ -56,14 +63,18 @@ previous ``source ...`` command. - ``python -m pip install -e .`` -4. Setup django project directory ---------------------------------- +4. Setup django project root +---------------------------- + +To run a django app, you need a django project root directory, with some +static and variable files in it. +In the last step a tool was installed, that can be used to create such +a project directory with all the neccessary subdirectories and files. +Our example will create the django project in ./etc/django and we will +call this directory *project root* for now on. - ``django-dav-events-admin setup ./env/django`` -The django project directory ('./env/django' within the previous example) -will be called *project root* for now on. - 5. Enable modules ----------------- Our web application consist of several modules, that care about single @@ -76,6 +87,8 @@ and run - ``python manage.py enable_module dav_auth`` - ``python manage.py enable_module dav_events`` +- ``python manage.py enable_module dav_registration`` +- ``python manage.py enable_module dav_event_office`` 4. Create the database schema / Populate the database ----------------------------------------------------- @@ -99,3 +112,12 @@ While you still are in the *project root* directory, run Now you should be able to connect to the test server via http://localhost:8000 + +7. Configure production web server +---------------------------------- +For production use you do not want to run the test server, +but have a real web server like apache or nginx running the +django app via the WSGI interface. +The entry point for your WSGI server is the file +``main/wsgi.py`` within the *project root* directory. + diff --git a/setup.py b/setup.py index a5a38e8..3ba0d06 100644 --- a/setup.py +++ b/setup.py @@ -82,10 +82,19 @@ class QuickSetup(Command): os.system(cmd) +def get_long_description(): + path = os.path.abspath(os.path.dirname(__file__)) + file = os.path.join(path, 'README.rst') + with open(file) as f: + return f.read() + + setup( name='django-dav-events', version='2.2.1', - description='A django based web application project to organize DAV Events.', + description='A django based web application project to drive the Touren- & Kurseportal of DAV Karlsruhe.', + long_description=get_long_description(), + long_description_content_type='text/x-rst', url='https://dev.heinzelwerk.de/git/DAV-KA/django-dav-events', author='Jens Kleineheismann', author_email='heinzel@alpenverein-karlsruhe.de', From faf71976bdd8b8d42794c3cc9f66e7fa7abfd9f8 Mon Sep 17 00:00:00 2001 From: heinzel Date: Tue, 10 Sep 2024 10:21:10 +0200 Subject: [PATCH 24/29] Fix deploy_stage workflow --- .gitea/workflows/deploy_stage.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitea/workflows/deploy_stage.yml b/.gitea/workflows/deploy_stage.yml index 70f79f9..566acaa 100644 --- a/.gitea/workflows/deploy_stage.yml +++ b/.gitea/workflows/deploy_stage.yml @@ -13,6 +13,8 @@ jobs: name: Deploy into stage environment runs-on: [django-dav-events, kitty] steps: + - name: "Pull code" + run: git -C "$DEPLOY_DIR/src/django-dav-events" pull - name: "Migrate database" run: $DEPLOY_DIR/python/bin/python $DEPLOY_DIR/django/manage.py migrate --noinput - name: "Collect static files" From aef55c7479bd1e000e15e9a88a7d49b7965b77b9 Mon Sep 17 00:00:00 2001 From: heinzel Date: Tue, 10 Sep 2024 10:51:02 +0200 Subject: [PATCH 25/29] dav_events: Change default order of events in "Verwaltungsansicht" to date descending --- dav_events/templates/dav_events/event_list.html | 1 + 1 file changed, 1 insertion(+) diff --git a/dav_events/templates/dav_events/event_list.html b/dav_events/templates/dav_events/event_list.html index 2716654..81b92ce 100644 --- a/dav_events/templates/dav_events/event_list.html +++ b/dav_events/templates/dav_events/event_list.html @@ -78,6 +78,7 @@ $(document).ready( function () { var table = $("#objects_table").DataTable( { orderCellsTop: true, + order: [[3, 'desc']], paging: false, language: { search: "{% trans 'Filter' %}:", From a43c15422c3e81f84afec43c6bdb6aca30d341de Mon Sep 17 00:00:00 2001 From: heinzel Date: Tue, 10 Sep 2024 15:02:33 +0200 Subject: [PATCH 26/29] dav_registration: fixed filter buttons ("all" button not active) --- .../templates/dav_registration/event_list.html | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dav_registration/templates/dav_registration/event_list.html b/dav_registration/templates/dav_registration/event_list.html index 6b11534..5073e72 100644 --- a/dav_registration/templates/dav_registration/event_list.html +++ b/dav_registration/templates/dav_registration/event_list.html @@ -132,12 +132,6 @@ var filter_cleaned = []; var filter_expr; - if(filter.length > 0) { - $("#btn-filter-All").addClass("btn-white"); - } else { - $("#btn-filter-All").removeClass("btn-white"); - } - for(let i in choices) { if(filter.indexOf(choices[i]) >= 0) { filter_cleaned.push(choices[i]); @@ -148,8 +142,10 @@ } if(filter_cleaned.length > 0) { + $("#btn-filter-All").addClass("btn-white"); filter_expr = "(" + filter_cleaned.join("|") + "),"; } else { + $("#btn-filter-All").removeClass("btn-white"); filter_expr = ""; } From e4680114e86341ac74d017b430aae7e13d8b11c2 Mon Sep 17 00:00:00 2001 From: heinzel Date: Wed, 11 Sep 2024 16:54:48 +0200 Subject: [PATCH 27/29] dav_events: Incorporated new rewards and fees --- .../settings-dav_events.py | 240 ++++++++++-------- dav_events/forms/events.py | 125 +++++---- .../dav_events/event_create/ChargesForm.html | 21 +- 3 files changed, 230 insertions(+), 156 deletions(-) diff --git a/dav_events/django_project_config/settings-dav_events.py b/dav_events/django_project_config/settings-dav_events.py index c0df80f..fc8ca9c 100644 --- a/dav_events/django_project_config/settings-dav_events.py +++ b/dav_events/django_project_config/settings-dav_events.py @@ -133,125 +133,149 @@ FORM_INITIALS = { # EventCreateForm and sub classes # FORMS_DEVELOPMENT_INIT = False MATRIX_CONFIG = { - '0': {'description': _(u'Keiner / direkte Abrechnung (Tageswanderung)'), - 'trainer_fee': 0, - 'trainer_day_fee': 0, - 'participant_fee': 0, - 'participant_day_fee': 0, - 'pre_meeting_fee': 0, - 'pubtrans_bonus': 0, - 'min_participants': 0, - 'max_participants': 0, + '0': {'description': _(u'Keiner / direkte Abrechnung (Tageswanderung)'), + 'orga_compensation': 0, + 'pubtrans_compensation': 0, + 'trainer_compensation': 0, + 'trainer_daily_compensation': 0, + 'pre_meeting_compensation': 0, + 'participant_fee': 0, + 'participant_daily_fee': 0, + 'pubtrans_bonus': 0, + 'min_participants': 0, + 'max_participants': 0, }, - 'A': {'description': _(u'A (Mehrtageswanderung Mittelgebirge)'), - 'trainer_fee': 40, - 'trainer_day_fee': 50, - 'participant_fee': 10, - 'participant_day_fee': 10, - 'pre_meeting_fee': 20, - 'pubtrans_bonus': 20, - 'min_participants': 5, - 'max_participants': 8, + 'A': {'description': _(u'A (Mehrtageswanderung Mittelgebirge)'), + 'orga_compensation': 30, + 'pubtrans_compensation': 30, + 'trainer_compensation': 38, + 'trainer_daily_compensation': 48, + 'pre_meeting_compensation': 20, + 'participant_fee': 11, + 'participant_daily_fee': 11, + 'pubtrans_bonus': 20, + 'min_participants': 5, + 'max_participants': 8, }, - 'B': {'description': _(u'B (Alpine Mehrtageswanderung)'), - 'trainer_fee': 50, - 'trainer_day_fee': 75, - 'participant_fee': 10, - 'participant_day_fee': 20, - 'pre_meeting_fee': 20, - 'pubtrans_bonus': 30, - 'min_participants': 3, - 'max_participants': 6, + 'B': {'description': _(u'B (Alpine Mehrtageswanderung)'), + 'orga_compensation': 30, + 'pubtrans_compensation': 50, + 'trainer_compensation': 48, + 'trainer_daily_compensation': 73, + 'pre_meeting_compensation': 20, + 'participant_fee': 11, + 'participant_daily_fee': 21, + 'pubtrans_bonus': 30, + 'min_participants': 3, + 'max_participants': 6, }, - 'C': {'description': _(u'C (Tour/Kurs ohne Übernachtung)'), - 'trainer_fee': 30, - 'trainer_day_fee': 60, - 'participant_fee': 10, - 'participant_day_fee': 30, - 'pre_meeting_fee': 20, - 'pubtrans_bonus': 0, - 'min_participants': 3, - 'max_participants': 5, + 'C': {'description': _(u'C (Tour/Kurs ohne Übernachtung)'), + 'orga_compensation': 30, + 'pubtrans_compensation': 30, + 'trainer_compensation': 28, + 'trainer_daily_compensation': 57, + 'pre_meeting_compensation': 20, + 'participant_fee': 11, + 'participant_daily_fee': 32, + 'pubtrans_bonus': 20, + 'min_participants': 3, + 'max_participants': 5, }, - 'D': {'description': _(u'D (Tour/Kurs Mittelgebirge)'), - 'trainer_fee': 50, - 'trainer_day_fee': 75, - 'participant_fee': 20, - 'participant_day_fee': 25, - 'pre_meeting_fee': 20, - 'pubtrans_bonus': 30, - 'min_participants': 3, - 'max_participants': 5, + 'D': {'description': _(u'D (Tour/Kurs Mittelgebirge)'), + 'orga_compensation': 30, + 'pubtrans_compensation': 30, + 'trainer_compensation': 48, + 'trainer_daily_compensation': 73, + 'pre_meeting_compensation': 20, + 'participant_fee': 21, + 'participant_daily_fee': 26, + 'pubtrans_bonus': 20, + 'min_participants': 3, + 'max_participants': 5, }, - 'E': {'description': _(u'E (Alpine Klettertour DE/AT)'), - 'trainer_fee': 80, - 'trainer_day_fee': 75, - 'participant_fee': 40, - 'participant_day_fee': 40, - 'pre_meeting_fee': 20, - 'pubtrans_bonus': 30, - 'min_participants': 2, - 'max_participants': 3, + 'E': {'description': _(u'E (Alpine Klettertour DE/AT)'), + 'orga_compensation': 40, + 'pubtrans_compensation': 50, + 'trainer_compensation': 76, + 'trainer_daily_compensation': 73, + 'pre_meeting_compensation': 20, + 'participant_fee': 42, + 'participant_daily_fee': 42, + 'pubtrans_bonus': 30, + 'min_participants': 2, + 'max_participants': 3, }, - 'F': {'description': _(u'F (Alpine Klettertour CH/FR/IT/..)'), - 'trainer_fee': 80, - 'trainer_day_fee': 85, - 'participant_fee': 40, - 'participant_day_fee': 45, - 'pre_meeting_fee': 20, - 'pubtrans_bonus': 30, - 'min_participants': 2, - 'max_participants': 3, + 'F': {'description': _(u'F (Alpine Klettertour CH/FR/IT/..)'), + 'orga_compensation': 40, + 'pubtrans_compensation': 50, + 'trainer_compensation': 76, + 'trainer_daily_compensation': 82, + 'pre_meeting_compensation': 20, + 'participant_fee': 42, + 'participant_daily_fee': 47, + 'pubtrans_bonus': 30, + 'min_participants': 2, + 'max_participants': 3, }, - 'G': {'description': _(u'G (Alpiner Kurs DE/AT)'), - 'trainer_fee': 100, - 'trainer_day_fee': 75, - 'participant_fee': 35, - 'participant_day_fee': 30, - 'pre_meeting_fee': 20, - 'pubtrans_bonus': 30, - 'min_participants': 3, - 'max_participants': 4, + 'G': {'description': _(u'G (Alpiner Kurs DE/AT)'), + 'orga_compensation': 50, + 'pubtrans_compensation': 50, + 'trainer_compensation': 96, + 'trainer_daily_compensation': 73, + 'pre_meeting_compensation': 20, + 'participant_fee': 37, + 'participant_daily_fee': 32, + 'pubtrans_bonus': 30, + 'min_participants': 3, + 'max_participants': 4, }, - 'H': {'description': _(u'H (Alpiner Kurs CH/FR/IT/..)'), - 'trainer_fee': 100, - 'trainer_day_fee': 85, - 'participant_fee': 35, - 'participant_day_fee': 30, - 'pre_meeting_fee': 20, - 'pubtrans_bonus': 30, - 'min_participants': 3, - 'max_participants': 4, + 'H': {'description': _(u'H (Alpiner Kurs CH/FR/IT/..)'), + 'orga_compensation': 50, + 'pubtrans_compensation': 50, + 'trainer_compensation': 96, + 'trainer_daily_compensation': 82, + 'pre_meeting_compensation': 20, + 'participant_fee': 37, + 'participant_daily_fee': 32, + 'pubtrans_bonus': 30, + 'min_participants': 3, + 'max_participants': 4, }, - 'I': {'description': _(u'I (Alpine MTB/Ski-Tour DE/AT)'), - 'trainer_fee': 80, - 'trainer_day_fee': 75, - 'participant_fee': 25, - 'participant_day_fee': 25, - 'pre_meeting_fee': 20, - 'pubtrans_bonus': 30, - 'min_participants': 3, - 'max_participants': 6, + 'I': {'description': _(u'I (Alpine MTB/Ski-Tour DE/AT)'), + 'orga_compensation': 40, + 'pubtrans_compensation': 50, + 'trainer_compensation': 76, + 'trainer_daily_compensation': 73, + 'pre_meeting_compensation': 20, + 'participant_fee': 26, + 'participant_daily_fee': 26, + 'pubtrans_bonus': 30, + 'min_participants': 3, + 'max_participants': 6, }, - 'J': {'description': _(u'J (Alpine MTB/Ski-Tour CH/FR/IT/..)'), - 'trainer_fee': 80, - 'trainer_day_fee': 85, - 'participant_fee': 25, - 'participant_day_fee': 25, - 'pre_meeting_fee': 20, - 'pubtrans_bonus': 30, - 'min_participants': 3, - 'max_participants': 6, + 'J': {'description': _(u'J (Alpine MTB/Ski-Tour CH/FR/IT/..)'), + 'orga_compensation': 40, + 'pubtrans_compensation': 50, + 'trainer_compensation': 76, + 'trainer_daily_compensation': 82, + 'pre_meeting_compensation': 20, + 'participant_fee': 26, + 'participant_daily_fee': 26, + 'pubtrans_bonus': 30, + 'min_participants': 3, + 'max_participants': 6, }, - 'K': {'description': _(u'K (Ski-Tour/-Kurs mit Liftbenutzung)'), - 'trainer_fee': 80, - 'trainer_day_fee': 130, - 'participant_fee': 40, - 'participant_day_fee': 40, - 'pre_meeting_fee': 20, - 'pubtrans_bonus': 30, - 'min_participants': 3, - 'max_participants': 4, + 'K': {'description': _(u'K (Ski-Tour/-Kurs mit Liftbenutzung)'), + 'orga_compensation': 40, + 'pubtrans_compensation': 50, + 'trainer_compensation': 76, + 'trainer_daily_compensation': 124, + 'pre_meeting_compensation': 20, + 'participant_fee': 42, + 'participant_daily_fee': 42, + 'pubtrans_bonus': 30, + 'min_participants': 3, + 'max_participants': 4, }, } diff --git a/dav_events/forms/events.py b/dav_events/forms/events.py index ff3d2c7..487fb5c 100644 --- a/dav_events/forms/events.py +++ b/dav_events/forms/events.py @@ -773,26 +773,35 @@ class ChargesForm(EventCreateForm): _form_title = _(u'Kosten') _next_form_name = 'DescriptionForm' + pubtrans_planned = forms.BooleanField(disabled=True, required=False, + label=_(u'An-/Abreise mit Bahn/Bus geplant'), + ) charge_key = forms.CharField(disabled=True, label=_(u'Kostenschlüssel'), ) - trainer_fee = forms.FloatField(disabled=True, - label=_(u'Pauschale Trainer*in'), - ) - trainer_day_fee = forms.FloatField(disabled=True, - label=_(u'Tagespauschale Trainer*in'), - ) + orga_compensation = forms.FloatField(disabled=True, + label=_(u'Aufwand für Tourenleitung'), + ) + pubtrans_compensation = forms.FloatField(disabled=True, + label=_(u'Aufwand für Organisation Bahn/Bus'), + ) + trainer_compensation = forms.FloatField(disabled=True, + label=_(u'Aufwand für Trainer*in'), + ) + trainer_daily_compensation = forms.FloatField(disabled=True, + label=_(u'Täglicher Aufwand für Trainer*in'), + ) + pre_meeting_compensation = forms.FloatField(disabled=True, + label=_(u'Aufwand pro Vortreffen'), + ) participant_fee = forms.FloatField(disabled=True, - label=_(u'Pauschale Teilnehmer*in'), - ) - participant_day_fee = forms.FloatField(disabled=True, - label=_(u'Tagespauschale Teilnehmer*in'), - ) - pre_meeting_fee = forms.FloatField(disabled=True, - label=_(u'Pauschale pro Vortreffen'), + label=_(u'Beitrag für Teilnehmer*in'), ) + participant_daily_fee = forms.FloatField(disabled=True, + label=_(u'Täglicher Beitrag für Teilnehmer*in'), + ) pubtrans_bonus = forms.FloatField(disabled=True, - label=_(u'Bonus bei Benutzung öffentlicher Verkehrsmittel'), + label=_(u'Rückzahlung bei Benutzung Bahn/Bus'), ) trainer1_reward = forms.FloatField(disabled=True, label=_(u'Aufwandsentschädigung Tourenleiter*in'), @@ -832,6 +841,10 @@ class ChargesForm(EventCreateForm): additional_costs_text = u'' if transport != 'coach': additional_costs_text += ugettext(u'Fahrtkosten') + if transport == 'public': + pubtrans_planned = True + else: + pubtrans_planned = False if last_day: timedelta = last_day - first_day @@ -850,57 +863,83 @@ class ChargesForm(EventCreateForm): else: n_pre_meetings = 0 - trainer_reward = ( - matrix_config['trainer_fee'] - + ndays * matrix_config['trainer_day_fee'] - + n_pre_meetings * matrix_config['pre_meeting_fee'] + trainer1_reward = ( + matrix_config['orga_compensation'] + + matrix_config['trainer_compensation'] + + ndays * matrix_config['trainer_daily_compensation'] + + n_pre_meetings * matrix_config['pre_meeting_compensation'] + ) + trainer23_reward = ( + matrix_config['trainer_compensation'] + + ndays * matrix_config['trainer_daily_compensation'] + + n_pre_meetings * matrix_config['pre_meeting_compensation'] ) charge = ( matrix_config['participant_fee'] - + ndays * matrix_config['participant_day_fee'] + + ndays * matrix_config['participant_daily_fee'] ) - if arrival_previous_day: - trainer_reward += matrix_config['trainer_day_fee'] / 2.0 - charge += matrix_config['participant_day_fee'] / 2.0 + if pubtrans_planned: + trainer1_reward += matrix_config['pubtrans_compensation'] + if arrival_previous_day: + trainer1_reward += matrix_config['trainer_daily_compensation'] / 2.0 + trainer23_reward += matrix_config['trainer_daily_compensation'] / 2.0 + charge += matrix_config['participant_daily_fee'] / 2.0 + + self.fields['pubtrans_planned'].initial = pubtrans_planned self.fields['charge_key'].initial = matrix_config['description'] or matrix_key - self.fields['trainer_fee'].initial = matrix_config['trainer_fee'] - self.fields['trainer_day_fee'].initial = matrix_config['trainer_day_fee'] + self.fields['orga_compensation'].initial = matrix_config['orga_compensation'] + self.fields['pubtrans_compensation'].initial = matrix_config['pubtrans_compensation'] + self.fields['trainer_compensation'].initial = matrix_config['trainer_compensation'] + self.fields['trainer_daily_compensation'].initial = matrix_config['trainer_daily_compensation'] + self.fields['pre_meeting_compensation'].initial = matrix_config['pre_meeting_compensation'] self.fields['participant_fee'].initial = matrix_config['participant_fee'] - self.fields['participant_day_fee'].initial = matrix_config['participant_day_fee'] - self.fields['pre_meeting_fee'].initial = matrix_config['pre_meeting_fee'] + self.fields['participant_daily_fee'].initial = matrix_config['participant_daily_fee'] self.fields['pubtrans_bonus'].initial = matrix_config['pubtrans_bonus'] self.fields['charge'].initial = charge - self.fields['trainer1_reward'].initial = trainer_reward - self.fields['trainer23_reward'].initial = trainer_reward * 0.95 + self.fields['trainer1_reward'].initial = trainer1_reward + self.fields['trainer23_reward'].initial = trainer23_reward self.fields['pubtrans_bonus'].widget.attrs['title'] = ugettext(u'Der Bonus wird nachträglich' u' auf Meldung der Tourenleitung' u' verrechnet und ist noch nicht' u' in den hier dargestellten Zahlen enthalten.') - self.fields['charge'].widget.attrs['title'] = (u'%d € Pauschale \n' - u'+ %d Tage * %d € Tagespauschale \n' - u'+ %d halben Anreisetag * %d € Tagespauschale / 2' + self.fields['charge'].widget.attrs['title'] = (u'%d € Beitrag \n' + u'+ %d Tage * %d € täglicher Beitrag \n' + u'+ %d halben Anreisetag * %d € halbtäglicher Beitrag' % ( matrix_config['participant_fee'], - ndays, matrix_config['participant_day_fee'], - int(arrival_previous_day), matrix_config['participant_day_fee'], + ndays, matrix_config['participant_daily_fee'], + int(arrival_previous_day), matrix_config['participant_daily_fee']/2, ) ) - self.fields['trainer1_reward'].widget.attrs['title'] = (u'%d € Pauschale \n' - u'+ %d Tage * %d € Tagespauschale \n' - u'+ %d halben Anreisetag * %d € Tagespauschale / 2 \n' - u'+ %d Vortreffen * %d € Vortreffenpauschale' + self.fields['trainer1_reward'].widget.attrs['title'] = (u'%d € Aufwand für Tourenleitung\n' + u'+ %d € Aufwand für Organisation Bahn/Bus\n' + u'+ %d € Aufwand für Trainer*in\n' + u'+ %d Tage * %d € täglicher Aufwand \n' + u'+ %d halben Anreisetag * %d € halbtäglicher Aufwand\n' + u'+ %d Vortreffen * %d € Aufwand pro Vortreffen' % ( - matrix_config['trainer_fee'], - ndays, matrix_config['trainer_day_fee'], - int(arrival_previous_day), matrix_config['trainer_day_fee'], - n_pre_meetings, matrix_config['pre_meeting_fee'] + matrix_config['orga_compensation'], + int(pubtrans_planned)*matrix_config['pubtrans_compensation'], + matrix_config['trainer_compensation'], + ndays, matrix_config['trainer_daily_compensation'], + int(arrival_previous_day), matrix_config['trainer_daily_compensation']/2, + n_pre_meetings, matrix_config['pre_meeting_compensation'] + ) + ) + self.fields['trainer23_reward'].widget.attrs['title'] = (u'%d € Aufwand für Trainer*in\n' + u'+ %d Tage * %d € täglicher Aufwand \n' + u'+ %d halben Anreisetag * %d € halbtäglicher Aufwand\n' + u'+ %d Vortreffen * %d € Aufwand pro Vortreffen' + % ( + matrix_config['trainer_compensation'], + ndays, matrix_config['trainer_daily_compensation'], + int(arrival_previous_day), matrix_config['trainer_daily_compensation']/2, + n_pre_meetings, matrix_config['pre_meeting_compensation'] ) ) - self.fields['trainer23_reward'].widget.attrs['title'] = ugettext(u'95% der Aufwandsentschädigung' - u' Tourenleiter*in') self.fields['additional_costs'].widget.attrs['placeholder'] = ugettext(u'Kann freigelassen werden') self.fields['additional_costs'].initial = additional_costs_text diff --git a/dav_events/templates/dav_events/event_create/ChargesForm.html b/dav_events/templates/dav_events/event_create/ChargesForm.html index 1616fa2..00be9e2 100644 --- a/dav_events/templates/dav_events/event_create/ChargesForm.html +++ b/dav_events/templates/dav_events/event_create/ChargesForm.html @@ -11,21 +11,29 @@ {% bootstrap_field form.charge_key %}
- {% bootstrap_field form.trainer_fee %} + {% bootstrap_field form.orga_compensation %}
- {% bootstrap_field form.participant_fee %} + {% bootstrap_field form.pubtrans_compensation %}
- {% bootstrap_field form.pre_meeting_fee %} + {% bootstrap_field form.trainer_compensation %}
- {% bootstrap_field form.trainer_day_fee %} + {% bootstrap_field form.trainer_daily_compensation %}
- {% bootstrap_field form.participant_day_fee %} + {% bootstrap_field form.pre_meeting_compensation %} +
+
+
+
+ {% bootstrap_field form.participant_fee %} +
+
+ {% bootstrap_field form.participant_daily_fee %}
@@ -34,6 +42,9 @@
{% bootstrap_field form.pubtrans_bonus %}
+
+ {% bootstrap_field form.pubtrans_planned %} +
From e2f12577cd1ff365c2d85eea7515533ab45581f8 Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Thu, 12 Sep 2024 13:21:06 +0200 Subject: [PATCH 28/29] Added a view, to get the list of open events for registration as json --- dav_events/models/event.py | 91 ++++++++++++++++++++++++++++++++++++++ dav_registration/urls.py | 1 + dav_registration/views.py | 25 +++++++++++ 3 files changed, 117 insertions(+) diff --git a/dav_events/models/event.py b/dav_events/models/event.py index 97fa65e..e53008a 100644 --- a/dav_events/models/event.py +++ b/dav_events/models/event.py @@ -527,3 +527,94 @@ class Event(models.Model): template_name = os.path.join('dav_events', 'event', 'default.html') template = get_template(template_name) return template.render(self.get_template_context()) + + def as_dict(self, json=True): + d = { + 'number': self.get_number(), + 'title': self.title, + 'description': self.description, + + 'mode': self.mode, + 'mode_display': self.get_mode_display(), + 'sport': self.sport, + 'sport_display': self.get_sport_display(), + 'level': self.level, + 'level_display': self.get_level_display(), + + 'first_day': self.first_day, + 'last_day': self.last_day, + 'alt_first_day': self.alt_first_day, + 'alt_last_day': self.alt_last_day, + + 'location': self.location, + 'basecamp': self.basecamp, + + 'meeting_time': self.meeting_time, + 'departure_time': self.departure_time, + 'departure_ride': self.departure_ride, + 'return_departure_time': self.return_departure_time, + 'return_arrival_time': self.return_arrival_time, + + 'requirements': self.requirements, + 'equipment': self.equipment, + + 'pre_meeting_1': self.pre_meeting_1, + 'pre_meeting_2': self.pre_meeting_2, + + 'trainer_fullname': self.get_trainer_full_name(), + 'trainer_firstname': self.trainer_firstname, + 'trainer_familyname': self.trainer_familyname, + 'trainer_email': self.trainer_email, + 'trainer_phone': self.trainer_phone, + 'trainer_2_fullname': self.trainer_2_fullname, + 'trainer_2_email': self.trainer_2_email, + 'trainer_2_phone': self.trainer_2_phone, + 'trainer_3_fullname': self.trainer_3_fullname, + 'trainer_3_email': self.trainer_3_email, + 'trainer_3_phone': self.trainer_3_phone, + + 'min_participants': self.min_participants, + 'max_participants': self.max_participants, + 'registration_required': self.registration_required, + 'registration_howto': self.registration_howto, + 'deadline': self.deadline, + 'registration_closed': self.registration_closed, + + 'charge': self.charge, + 'additional_costs': self.additional_costs, + + 'course_topic_1': self.course_topic_1, + 'course_topic_2': self.course_topic_2, + 'course_topic_3': self.course_topic_3, + 'course_topic_4': self.course_topic_4, + 'course_topic_5': self.course_topic_5, + 'course_topic_6': self.course_topic_6, + 'course_goal_1': self.course_goal_1, + 'course_goal_2': self.course_goal_2, + 'course_goal_3': self.course_goal_3, + 'course_goal_4': self.course_goal_4, + 'course_goal_5': self.course_goal_5, + 'course_goal_6': self.course_goal_6, + } + if json: + d['country'] = str(self.country) + d['country_display'] = self.get_country_display() + for field in ('transport', 'meeting_point', 'accommodation', 'meals'): + value = getattr(self, field) + d[field] = value + display_field_name = '%s_display' % field + if value == 'NONE': + d[display_field_name] = None + elif value == 'OTHER': + other = getattr(self, '%s_other' % field) + d[display_field_name] = other + else: + func = getattr(self, 'get_%s_display' % field) + d[display_field_name] = func() + else: + d['country'] = self.country + for field in ('transport', 'meeting_point', 'accommodation', 'meals'): + value = getattr(self, field) + d[field] = value + + return d \ No newline at end of file diff --git a/dav_registration/urls.py b/dav_registration/urls.py index 3051da5..89a31e4 100644 --- a/dav_registration/urls.py +++ b/dav_registration/urls.py @@ -11,4 +11,5 @@ urlpatterns = [ url(r'^event/(?P\d+)/registration', views.RegistrationView.as_view(), name='register'), url(r'^event/(?P\d+)/', views.EventDetailView.as_view(), name='event'), url(r'^event', views.EventListView.as_view(), name='events'), + url(r'^api/v1/event', views.EventListAsJSONView, name='api_events'), ] diff --git a/dav_registration/views.py b/dav_registration/views.py index 0f3d1ac..09755e9 100644 --- a/dav_registration/views.py +++ b/dav_registration/views.py @@ -4,6 +4,7 @@ import logging from django.apps import apps from django.contrib import messages from django.db.models import Q +from django.http import JsonResponse from django.urls import reverse_lazy from django.utils.translation import ugettext as _ from django.views import generic @@ -64,6 +65,30 @@ class EventListView(generic.ListView): return super().get(request, *args, **kwargs) +def EventListAsJSONView(request): + filter_cleaned = [] + if 'filter' in request.GET: + choices = SPORT_CHOICES.codes + LEVEL_CHOICES.codes + filter_input = request.GET['filter'].split(',') + filter_cleaned = list(set(filter_input) & set(choices)) + + today = datetime.date.today() + + filter_exp = Q(flags__status__code__in=('publishing', 'publishing_web', 'publishing_facebook', + 'published', 'published_web', 'published_facebook')) + filter_exp &= Q(planned_publication_date__isnull=True) | Q(planned_publication_date__lte=today) + filter_exp &= Q(first_day__gte=today) + # filter_exp &= Q(registration_closed=False) + # filter_exp &= Q(deadline__isnull=True) | Q(deadline__gte=today) + if filter_cleaned: + filter_exp &= Q(sport__in=filter_cleaned) | Q(level__in=filter_cleaned) + + qs = Event.objects.filter(filter_exp).order_by('first_day', 'number').distinct() + data = [event.as_dict(json=True) for event in qs] + response = JsonResponse(data, safe=False) + return response + + class EventDetailView(generic.DetailView): model = Event template_name = 'dav_registration/event_detail.html' From af3afc9f9a63247d9713b43850b55145e1ba1d07 Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Thu, 12 Sep 2024 13:41:27 +0200 Subject: [PATCH 29/29] Added field registration_url to the json --- dav_events/models/event.py | 6 +++++- dav_registration/views.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dav_events/models/event.py b/dav_events/models/event.py index e53008a..eb4b550 100644 --- a/dav_events/models/event.py +++ b/dav_events/models/event.py @@ -528,8 +528,9 @@ class Event(models.Model): template = get_template(template_name) return template.render(self.get_template_context()) - def as_dict(self, json=True): + def as_dict(self, json=True, add_registration_url=False): d = { + 'id': self.id, 'number': self.get_number(), 'title': self.title, 'description': self.description, @@ -617,4 +618,7 @@ class Event(models.Model): value = getattr(self, field) d[field] = value + if add_registration_url: + d['registration_url'] = reverse('dav_registration:event', kwargs={'pk': self.pk}) + return d \ No newline at end of file diff --git a/dav_registration/views.py b/dav_registration/views.py index 09755e9..6e3f5cc 100644 --- a/dav_registration/views.py +++ b/dav_registration/views.py @@ -84,7 +84,7 @@ def EventListAsJSONView(request): filter_exp &= Q(sport__in=filter_cleaned) | Q(level__in=filter_cleaned) qs = Event.objects.filter(filter_exp).order_by('first_day', 'number').distinct() - data = [event.as_dict(json=True) for event in qs] + data = [event.as_dict(json=True, add_registration_url=True) for event in qs] response = JsonResponse(data, safe=False) return response