Merge pull request 'Update the production branch' (#82) from master into production
All checks were successful
Run tests / Execute tox to run the test suite (push) Successful in 3m29s

Reviewed-on: #82
This commit was merged in pull request #82.
This commit is contained in:
2024-09-16 08:50:38 +02:00
30 changed files with 1175 additions and 426 deletions

View File

@@ -0,0 +1,23 @@
name: Deploy into stage environment
on:
push:
branches:
- stage
workflow_dispatch:
env:
DEPLOY_DIR: "/var/www/touren.alpenverein-karlsruhe.de/wsgi/django-dav-events.stage"
jobs:
deploy-on-stage:
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"
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

View File

@@ -0,0 +1,15 @@
name: Run tests every night at 05:05
on:
schedule:
- cron: "05 05 * * *"
jobs:
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 test via tox"
working-directory: ./repository
run: tox

View File

@@ -0,0 +1,13 @@
name: Run tests
on: [push]
jobs:
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 tests via tox"
working-directory: ./repository
run: tox

View File

@@ -1,19 +1,27 @@
REQUIREMENTS REQUIREMENTS
============ ============
- Python 3 - Python >= 3.12
- Python package virtualenv (in most cases this is distributed or installed together with python) - Django and some other python packages, that will be installed throughout
- Django (will be installed automatically) the installation process
- Several additional django related python packages (will be installed automatically)
For production use you surly want a real web server that supports WSGI 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 QUICK INSTALLATION FOR THE IMPATIENT
==================================== ====================================
- python setup.py mkpyenv - python -m venv ./etc/python
- source env/python/bin/activate - 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 INSTALLATION
@@ -24,14 +32,9 @@ INSTALLATION
It is strongly recommended to create a separated python environment It is strongly recommended to create a separated python environment
for this django project. But it is not exactly necessary. 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: - 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), - If you use a posix compatible shell (like bash, the linux default shell),
you have to activate the environment for the current shell session you have to activate the environment for the current shell session
@@ -50,16 +53,29 @@ 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 reactivate the environment (e.g. to execute a python command) use the
previous ``source ...`` command. 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 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.
- ``python setup.py develop``
- ``django-dav-events-admin setup ./env/django`` - ``django-dav-events-admin setup ./env/django``
The django project directory ('./env/django' within the previous example) 5. Enable modules
will be called *project root* for now on.
3. Enable modules
----------------- -----------------
Our web application consist of several modules, that care about single Our web application consist of several modules, that care about single
aspects of the whole picture. aspects of the whole picture.
@@ -71,6 +87,8 @@ and run
- ``python manage.py enable_module dav_auth`` - ``python manage.py enable_module dav_auth``
- ``python manage.py enable_module dav_events`` - ``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 4. Create the database schema / Populate the database
----------------------------------------------------- -----------------------------------------------------
@@ -94,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 Now you should be able to connect to the test server via
http://localhost:8000 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.

View File

@@ -1,6 +1,3 @@
# common dist files
include README.rst INSTALL.rst
include setup.py requirements.txt
# dav_base # dav_base
recursive-include dav_base/console_scripts/django_project_config *.py recursive-include dav_base/console_scripts/django_project_config *.py
recursive-include dav_base/static * recursive-include dav_base/static *

View File

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

View File

@@ -1,6 +1,8 @@
ABOUT 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 REQUIREMENTS

View File

@@ -9,7 +9,7 @@ class SeleniumAuthMixin:
username_field = self.wait_on_presence(driver, (By.ID, 'id_username')) username_field = self.wait_on_presence(driver, (By.ID, 'id_username'))
username_field.clear() username_field.clear()
username_field.send_keys(username) 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.clear()
password_field.send_keys(password) password_field.send_keys(password)
password_field.send_keys(Keys.RETURN) password_field.send_keys(Keys.RETURN)

View File

@@ -31,25 +31,25 @@ class TestCase(ScreenshotTestCase):
# Go to login page via login button on root page -> save plain login form # Go to login page via login button on root page -> save plain login form
c = self.selenium c = self.selenium
c.get(self.complete_url('/')) 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() link.click()
self.wait_on_presence(c, (By.ID, 'id_username')) self.wait_on_presence(c, (By.ID, 'id_username'))
self.save_screenshot('empty_login_form', sequence=sequence_name) self.save_screenshot('empty_login_form', sequence=sequence_name)
# Fill in a password -> Save dots in password field # 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.clear()
username_field.send_keys('username') 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.clear()
password_field.send_keys(self.test_password) password_field.send_keys(self.test_password)
self.save_screenshot('filled_login_form', sequence=sequence_name) self.save_screenshot('filled_login_form', sequence=sequence_name)
# Wrong username -> save error message # 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.clear()
username_field.send_keys(self.test_username[::-1]) 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.clear()
password_field.send_keys(self.test_password) password_field.send_keys(self.test_password)
password_field.send_keys(Keys.RETURN) password_field.send_keys(Keys.RETURN)
@@ -58,10 +58,10 @@ class TestCase(ScreenshotTestCase):
alert_button.click() alert_button.click()
# Wrong password -> save error message # 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.clear()
username_field.send_keys(self.test_username) 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.clear()
password_field.send_keys(self.test_password[::-1]) password_field.send_keys(self.test_password[::-1])
password_field.send_keys(Keys.RETURN) password_field.send_keys(Keys.RETURN)
@@ -72,10 +72,10 @@ class TestCase(ScreenshotTestCase):
# Login of inactive user -> save error message # Login of inactive user -> save error message
self.user.is_active = False self.user.is_active = False
self.user.save() 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.clear()
username_field.send_keys(self.test_username) 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.clear()
password_field.send_keys(self.test_password) password_field.send_keys(self.test_password)
password_field.send_keys(Keys.RETURN) password_field.send_keys(Keys.RETURN)
@@ -87,10 +87,10 @@ class TestCase(ScreenshotTestCase):
self.user.save() self.user.save()
# Login -> save success message # Login -> save success message
username_field = c.find_element_by_id('id_username') username_field = c.find_element(By.ID, 'id_username')
username_field.clear() username_field.clear()
username_field.send_keys(self.test_username) username_field.send_keys(self.test_username)
password_field = c.find_element_by_id('id_password') password_field = c.find_element(By.ID, 'id_password')
password_field.clear() password_field.clear()
password_field.send_keys(self.test_password) password_field.send_keys(self.test_password)
password_field.send_keys(Keys.RETURN) password_field.send_keys(Keys.RETURN)
@@ -104,18 +104,18 @@ class TestCase(ScreenshotTestCase):
self.save_screenshot('user_menu', sequence=sequence_name) self.save_screenshot('user_menu', sequence=sequence_name)
# Click on 'set password' -> save set password page # Click on 'set password' -> save set password page
user_menu = c.find_element_by_css_selector('#login-widget ul') user_menu = c.find_element(By.CSS_SELECTOR, '#login-widget ul')
link = user_menu.find_element_by_partial_link_text(ugettext('Passwort ändern')) link = user_menu.find_element(By.PARTIAL_LINK_TEXT, ugettext('Passwort ändern'))
link.click() link.click()
password_field = self.wait_on_presence(c, (By.ID, 'id_new_password')) password_field = self.wait_on_presence(c, (By.ID, 'id_new_password'))
self.save_screenshot('empty_set_password_form', sequence=sequence_name) self.save_screenshot('empty_set_password_form', sequence=sequence_name)
# Fill in a password -> Save dots in password field # 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() send_mail_field.click()
password_field.clear() password_field.clear()
password_field.send_keys(self.test_password) 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.clear()
password2_field.send_keys(self.test_password) password2_field.send_keys(self.test_password)
self.save_screenshot('filled_set_password_form', sequence=sequence_name) 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 # New passwords too common and too short -> save error message
password = 'abcdef' 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.clear()
password_field.send_keys(password) 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.clear()
password2_field.send_keys(password) password2_field.send_keys(password)
password2_field.send_keys(Keys.RETURN) password2_field.send_keys(Keys.RETURN)
@@ -143,10 +143,10 @@ class TestCase(ScreenshotTestCase):
# New passwords entirely_numeric -> save error message # New passwords entirely_numeric -> save error message
password = '9126735804' 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.clear()
password_field.send_keys(password) 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.clear()
password2_field.send_keys(password) password2_field.send_keys(password)
password2_field.send_keys(Keys.RETURN) password2_field.send_keys(Keys.RETURN)
@@ -155,10 +155,10 @@ class TestCase(ScreenshotTestCase):
# New passwords too similar -> save error message # New passwords too similar -> save error message
password = self.test_username 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.clear()
password_field.send_keys(password) 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.clear()
password2_field.send_keys(password) password2_field.send_keys(password)
password2_field.send_keys(Keys.RETURN) password2_field.send_keys(Keys.RETURN)
@@ -167,10 +167,10 @@ class TestCase(ScreenshotTestCase):
# Change password -> save success message # Change password -> save success message
password = self.test_password[::-1] password = self.test_password[::-1]
password_field = c.find_element_by_id('id_new_password') password_field = c.find_element(By.ID, 'id_new_password')
password_field.clear() password_field.clear()
password_field.send_keys(password) password_field.send_keys(password)
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.clear()
password2_field.send_keys(password) password2_field.send_keys(password)
password2_field.send_keys(Keys.RETURN) password2_field.send_keys(Keys.RETURN)
@@ -179,7 +179,7 @@ class TestCase(ScreenshotTestCase):
# Get password recreate page -> since we are logged in, it should # Get password recreate page -> since we are logged in, it should
# redirect to set password page again -> save # redirect to set password page again -> save
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'))) c.get(self.complete_url(reverse('dav_auth:recreate_password')))
self.wait_until_stale(c, html) self.wait_until_stale(c, html)
self.wait_on_presence(c, (By.ID, 'id_new_password')) self.wait_on_presence(c, (By.ID, 'id_new_password'))
@@ -188,19 +188,19 @@ class TestCase(ScreenshotTestCase):
# Click on 'logout' -> save page # Click on 'logout' -> save page
dropdown_button = self.wait_on_presence(c, (By.ID, 'user_dropdown_button')) dropdown_button = self.wait_on_presence(c, (By.ID, 'user_dropdown_button'))
dropdown_button.click() dropdown_button.click()
user_menu = c.find_element_by_css_selector('#login-widget ul') user_menu = c.find_element(By.CSS_SELECTOR, '#login-widget ul')
link = user_menu.find_element_by_partial_link_text(ugettext('Logout')) link = user_menu.find_element(By.PARTIAL_LINK_TEXT, ugettext('Logout'))
link.click() link.click()
self.wait_until_stale(c, user_menu) self.wait_until_stale(c, user_menu)
self.save_screenshot('logout_succeed', sequence=sequence_name) self.save_screenshot('logout_succeed', sequence=sequence_name)
# Click on 'login' to access password recreate link # Click on 'login' to access password recreate link
link = c.find_element_by_css_selector('#login-widget a') link = c.find_element(By.CSS_SELECTOR, '#login-widget a')
link.click() link.click()
self.wait_on_presence(c, (By.ID, 'id_username')) self.wait_on_presence(c, (By.ID, 'id_username'))
# Locate password recreate link, click it -> save password recreate form # Locate password recreate link, click it -> save password recreate form
link = c.find_element_by_partial_link_text(ugettext('Passwort vergessen')) link = c.find_element(By.PARTIAL_LINK_TEXT, ugettext('Passwort vergessen'))
link.click() link.click()
username_field = self.wait_on_presence(c, (By.ID, 'id_username')) username_field = self.wait_on_presence(c, (By.ID, 'id_username'))
self.save_screenshot('empty_recreate_password_form', sequence=sequence_name) self.save_screenshot('empty_recreate_password_form', sequence=sequence_name)
@@ -212,7 +212,7 @@ class TestCase(ScreenshotTestCase):
self.save_screenshot('recreate_password_invalid_user', sequence=sequence_name) self.save_screenshot('recreate_password_invalid_user', sequence=sequence_name)
# Locate password recreate link, click it # Locate password recreate link, click it
link = c.find_element_by_partial_link_text(ugettext('Passwort vergessen')) link = c.find_element(By.PARTIAL_LINK_TEXT, ugettext('Passwort vergessen'))
link.click() link.click()
username_field = self.wait_on_presence(c, (By.ID, 'id_username')) username_field = self.wait_on_presence(c, (By.ID, 'id_username'))

View File

@@ -40,7 +40,7 @@ class TestCase(SeleniumAuthMixin, SeleniumTestCase):
c = self.selenium c = self.selenium
c.get(self.complete_url('/')) c.get(self.complete_url('/'))
try: try:
c.find_element_by_css_selector('#login-widget a') c.find_element(By.CSS_SELECTOR, '#login-widget a')
except NoSuchElementException as e: # pragma: no cover except NoSuchElementException as e: # pragma: no cover
self.fail(str(e)) self.fail(str(e))
@@ -50,9 +50,9 @@ class TestCase(SeleniumAuthMixin, SeleniumTestCase):
location = reverse('dav_auth:login') location = reverse('dav_auth:login')
c.get(self.complete_url(location)) 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') 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') self.assertEqual(field.get_attribute('required'), 'true')
def test_required_fields_in_set_password_form(self): 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'))) c.get(self.complete_url(reverse('dav_auth:set_password')))
field = self.wait_on_presence(c, (By.ID, 'id_new_password')) field = self.wait_on_presence(c, (By.ID, 'id_new_password'))
self.assertEqual(field.get_attribute('required'), 'true') 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') self.assertEqual(field.get_attribute('required'), 'true')
field = c.find_element_by_id('id_send_password_mail') field = c.find_element(By.ID, 'id_send_password_mail')
self.assertEqual(field.get_attribute('required'), None) self.assertEqual(field.get_attribute('required'), None)

View File

@@ -4,10 +4,7 @@ import os
from tempfile import mkdtemp as _mkdtemp from tempfile import mkdtemp as _mkdtemp
def mkdtemp(prefix): def mkdtemp(prefix, base_dir):
dirname = os.path.dirname if not os.path.exists(base_dir):
pkg_base_dir = dirname(dirname(dirname(__file__))) os.makedirs(base_dir)
tmp_dir = os.path.join(pkg_base_dir, 'tmp') return _mkdtemp(prefix=prefix, dir=base_dir)
if not os.path.exists(tmp_dir):
os.makedirs(tmp_dir)
return _mkdtemp(prefix=prefix, dir=tmp_dir)

View File

@@ -31,6 +31,10 @@ class ChoiceSet(object):
else: else:
return False return False
@property
def codes(self):
return self._codes
def get_label(self, code): def get_label(self, code):
return self._labels[code] return self._labels[code]

View File

@@ -134,121 +134,145 @@ FORM_INITIALS = {
# FORMS_DEVELOPMENT_INIT = False # FORMS_DEVELOPMENT_INIT = False
MATRIX_CONFIG = { MATRIX_CONFIG = {
'0': {'description': _(u'Keiner / direkte Abrechnung (Tageswanderung)'), '0': {'description': _(u'Keiner / direkte Abrechnung (Tageswanderung)'),
'trainer_fee': 0, 'orga_compensation': 0,
'trainer_day_fee': 0, 'pubtrans_compensation': 0,
'trainer_compensation': 0,
'trainer_daily_compensation': 0,
'pre_meeting_compensation': 0,
'participant_fee': 0, 'participant_fee': 0,
'participant_day_fee': 0, 'participant_daily_fee': 0,
'pre_meeting_fee': 0,
'pubtrans_bonus': 0, 'pubtrans_bonus': 0,
'min_participants': 0, 'min_participants': 0,
'max_participants': 0, 'max_participants': 0,
}, },
'A': {'description': _(u'A (Mehrtageswanderung Mittelgebirge)'), 'A': {'description': _(u'A (Mehrtageswanderung Mittelgebirge)'),
'trainer_fee': 40, 'orga_compensation': 30,
'trainer_day_fee': 50, 'pubtrans_compensation': 30,
'participant_fee': 10, 'trainer_compensation': 38,
'participant_day_fee': 10, 'trainer_daily_compensation': 48,
'pre_meeting_fee': 20, 'pre_meeting_compensation': 20,
'participant_fee': 11,
'participant_daily_fee': 11,
'pubtrans_bonus': 20, 'pubtrans_bonus': 20,
'min_participants': 5, 'min_participants': 5,
'max_participants': 8, 'max_participants': 8,
}, },
'B': {'description': _(u'B (Alpine Mehrtageswanderung)'), 'B': {'description': _(u'B (Alpine Mehrtageswanderung)'),
'trainer_fee': 50, 'orga_compensation': 30,
'trainer_day_fee': 75, 'pubtrans_compensation': 50,
'participant_fee': 10, 'trainer_compensation': 48,
'participant_day_fee': 20, 'trainer_daily_compensation': 73,
'pre_meeting_fee': 20, 'pre_meeting_compensation': 20,
'participant_fee': 11,
'participant_daily_fee': 21,
'pubtrans_bonus': 30, 'pubtrans_bonus': 30,
'min_participants': 3, 'min_participants': 3,
'max_participants': 6, 'max_participants': 6,
}, },
'C': {'description': _(u'C (Tour/Kurs ohne Übernachtung)'), 'C': {'description': _(u'C (Tour/Kurs ohne Übernachtung)'),
'trainer_fee': 30, 'orga_compensation': 30,
'trainer_day_fee': 60, 'pubtrans_compensation': 30,
'participant_fee': 10, 'trainer_compensation': 28,
'participant_day_fee': 30, 'trainer_daily_compensation': 57,
'pre_meeting_fee': 20, 'pre_meeting_compensation': 20,
'pubtrans_bonus': 0, 'participant_fee': 11,
'participant_daily_fee': 32,
'pubtrans_bonus': 20,
'min_participants': 3, 'min_participants': 3,
'max_participants': 5, 'max_participants': 5,
}, },
'D': {'description': _(u'D (Tour/Kurs Mittelgebirge)'), 'D': {'description': _(u'D (Tour/Kurs Mittelgebirge)'),
'trainer_fee': 50, 'orga_compensation': 30,
'trainer_day_fee': 75, 'pubtrans_compensation': 30,
'participant_fee': 20, 'trainer_compensation': 48,
'participant_day_fee': 25, 'trainer_daily_compensation': 73,
'pre_meeting_fee': 20, 'pre_meeting_compensation': 20,
'pubtrans_bonus': 30, 'participant_fee': 21,
'participant_daily_fee': 26,
'pubtrans_bonus': 20,
'min_participants': 3, 'min_participants': 3,
'max_participants': 5, 'max_participants': 5,
}, },
'E': {'description': _(u'E (Alpine Klettertour DE/AT)'), 'E': {'description': _(u'E (Alpine Klettertour DE/AT)'),
'trainer_fee': 80, 'orga_compensation': 40,
'trainer_day_fee': 75, 'pubtrans_compensation': 50,
'participant_fee': 40, 'trainer_compensation': 76,
'participant_day_fee': 40, 'trainer_daily_compensation': 73,
'pre_meeting_fee': 20, 'pre_meeting_compensation': 20,
'participant_fee': 42,
'participant_daily_fee': 42,
'pubtrans_bonus': 30, 'pubtrans_bonus': 30,
'min_participants': 2, 'min_participants': 2,
'max_participants': 3, 'max_participants': 3,
}, },
'F': {'description': _(u'F (Alpine Klettertour CH/FR/IT/..)'), 'F': {'description': _(u'F (Alpine Klettertour CH/FR/IT/..)'),
'trainer_fee': 80, 'orga_compensation': 40,
'trainer_day_fee': 85, 'pubtrans_compensation': 50,
'participant_fee': 40, 'trainer_compensation': 76,
'participant_day_fee': 45, 'trainer_daily_compensation': 82,
'pre_meeting_fee': 20, 'pre_meeting_compensation': 20,
'participant_fee': 42,
'participant_daily_fee': 47,
'pubtrans_bonus': 30, 'pubtrans_bonus': 30,
'min_participants': 2, 'min_participants': 2,
'max_participants': 3, 'max_participants': 3,
}, },
'G': {'description': _(u'G (Alpiner Kurs DE/AT)'), 'G': {'description': _(u'G (Alpiner Kurs DE/AT)'),
'trainer_fee': 100, 'orga_compensation': 50,
'trainer_day_fee': 75, 'pubtrans_compensation': 50,
'participant_fee': 35, 'trainer_compensation': 96,
'participant_day_fee': 30, 'trainer_daily_compensation': 73,
'pre_meeting_fee': 20, 'pre_meeting_compensation': 20,
'participant_fee': 37,
'participant_daily_fee': 32,
'pubtrans_bonus': 30, 'pubtrans_bonus': 30,
'min_participants': 3, 'min_participants': 3,
'max_participants': 4, 'max_participants': 4,
}, },
'H': {'description': _(u'H (Alpiner Kurs CH/FR/IT/..)'), 'H': {'description': _(u'H (Alpiner Kurs CH/FR/IT/..)'),
'trainer_fee': 100, 'orga_compensation': 50,
'trainer_day_fee': 85, 'pubtrans_compensation': 50,
'participant_fee': 35, 'trainer_compensation': 96,
'participant_day_fee': 30, 'trainer_daily_compensation': 82,
'pre_meeting_fee': 20, 'pre_meeting_compensation': 20,
'participant_fee': 37,
'participant_daily_fee': 32,
'pubtrans_bonus': 30, 'pubtrans_bonus': 30,
'min_participants': 3, 'min_participants': 3,
'max_participants': 4, 'max_participants': 4,
}, },
'I': {'description': _(u'I (Alpine MTB/Ski-Tour DE/AT)'), 'I': {'description': _(u'I (Alpine MTB/Ski-Tour DE/AT)'),
'trainer_fee': 80, 'orga_compensation': 40,
'trainer_day_fee': 75, 'pubtrans_compensation': 50,
'participant_fee': 25, 'trainer_compensation': 76,
'participant_day_fee': 25, 'trainer_daily_compensation': 73,
'pre_meeting_fee': 20, 'pre_meeting_compensation': 20,
'participant_fee': 26,
'participant_daily_fee': 26,
'pubtrans_bonus': 30, 'pubtrans_bonus': 30,
'min_participants': 3, 'min_participants': 3,
'max_participants': 6, 'max_participants': 6,
}, },
'J': {'description': _(u'J (Alpine MTB/Ski-Tour CH/FR/IT/..)'), 'J': {'description': _(u'J (Alpine MTB/Ski-Tour CH/FR/IT/..)'),
'trainer_fee': 80, 'orga_compensation': 40,
'trainer_day_fee': 85, 'pubtrans_compensation': 50,
'participant_fee': 25, 'trainer_compensation': 76,
'participant_day_fee': 25, 'trainer_daily_compensation': 82,
'pre_meeting_fee': 20, 'pre_meeting_compensation': 20,
'participant_fee': 26,
'participant_daily_fee': 26,
'pubtrans_bonus': 30, 'pubtrans_bonus': 30,
'min_participants': 3, 'min_participants': 3,
'max_participants': 6, 'max_participants': 6,
}, },
'K': {'description': _(u'K (Ski-Tour/-Kurs mit Liftbenutzung)'), 'K': {'description': _(u'K (Ski-Tour/-Kurs mit Liftbenutzung)'),
'trainer_fee': 80, 'orga_compensation': 40,
'trainer_day_fee': 130, 'pubtrans_compensation': 50,
'participant_fee': 40, 'trainer_compensation': 76,
'participant_day_fee': 40, 'trainer_daily_compensation': 124,
'pre_meeting_fee': 20, 'pre_meeting_compensation': 20,
'participant_fee': 42,
'participant_daily_fee': 42,
'pubtrans_bonus': 30, 'pubtrans_bonus': 30,
'min_participants': 3, 'min_participants': 3,
'max_participants': 4, 'max_participants': 4,

View File

@@ -773,26 +773,35 @@ class ChargesForm(EventCreateForm):
_form_title = _(u'Kosten') _form_title = _(u'Kosten')
_next_form_name = 'DescriptionForm' _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, charge_key = forms.CharField(disabled=True,
label=_(u'Kostenschlüssel'), label=_(u'Kostenschlüssel'),
) )
trainer_fee = forms.FloatField(disabled=True, orga_compensation = forms.FloatField(disabled=True,
label=_(u'Pauschale Trainer*in'), label=_(u'Aufwand für Tourenleitung'),
) )
trainer_day_fee = forms.FloatField(disabled=True, pubtrans_compensation = forms.FloatField(disabled=True,
label=_(u'Tagespauschale Trainer*in'), 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, participant_fee = forms.FloatField(disabled=True,
label=_(u'Pauschale Teilnehmer*in'), label=_(u'Beitrag für Teilnehmer*in'),
) )
participant_day_fee = forms.FloatField(disabled=True, participant_daily_fee = forms.FloatField(disabled=True,
label=_(u'Tagespauschale Teilnehmer*in'), label=_(u'Täglicher Beitrag für Teilnehmer*in'),
)
pre_meeting_fee = forms.FloatField(disabled=True,
label=_(u'Pauschale pro Vortreffen'),
) )
pubtrans_bonus = forms.FloatField(disabled=True, 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, trainer1_reward = forms.FloatField(disabled=True,
label=_(u'Aufwandsentschädigung Tourenleiter*in'), label=_(u'Aufwandsentschädigung Tourenleiter*in'),
@@ -832,6 +841,10 @@ class ChargesForm(EventCreateForm):
additional_costs_text = u'' additional_costs_text = u''
if transport != 'coach': if transport != 'coach':
additional_costs_text += ugettext(u'Fahrtkosten') additional_costs_text += ugettext(u'Fahrtkosten')
if transport == 'public':
pubtrans_planned = True
else:
pubtrans_planned = False
if last_day: if last_day:
timedelta = last_day - first_day timedelta = last_day - first_day
@@ -850,57 +863,83 @@ class ChargesForm(EventCreateForm):
else: else:
n_pre_meetings = 0 n_pre_meetings = 0
trainer_reward = ( trainer1_reward = (
matrix_config['trainer_fee'] matrix_config['orga_compensation']
+ ndays * matrix_config['trainer_day_fee'] + matrix_config['trainer_compensation']
+ n_pre_meetings * matrix_config['pre_meeting_fee'] + 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 = ( charge = (
matrix_config['participant_fee'] matrix_config['participant_fee']
+ ndays * matrix_config['participant_day_fee'] + ndays * matrix_config['participant_daily_fee']
) )
if arrival_previous_day: if pubtrans_planned:
trainer_reward += matrix_config['trainer_day_fee'] / 2.0 trainer1_reward += matrix_config['pubtrans_compensation']
charge += matrix_config['participant_day_fee'] / 2.0
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['charge_key'].initial = matrix_config['description'] or matrix_key
self.fields['trainer_fee'].initial = matrix_config['trainer_fee'] self.fields['orga_compensation'].initial = matrix_config['orga_compensation']
self.fields['trainer_day_fee'].initial = matrix_config['trainer_day_fee'] 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_fee'].initial = matrix_config['participant_fee']
self.fields['participant_day_fee'].initial = matrix_config['participant_day_fee'] self.fields['participant_daily_fee'].initial = matrix_config['participant_daily_fee']
self.fields['pre_meeting_fee'].initial = matrix_config['pre_meeting_fee']
self.fields['pubtrans_bonus'].initial = matrix_config['pubtrans_bonus'] self.fields['pubtrans_bonus'].initial = matrix_config['pubtrans_bonus']
self.fields['charge'].initial = charge self.fields['charge'].initial = charge
self.fields['trainer1_reward'].initial = trainer_reward self.fields['trainer1_reward'].initial = trainer1_reward
self.fields['trainer23_reward'].initial = trainer_reward * 0.95 self.fields['trainer23_reward'].initial = trainer23_reward
self.fields['pubtrans_bonus'].widget.attrs['title'] = ugettext(u'Der Bonus wird nachträglich' self.fields['pubtrans_bonus'].widget.attrs['title'] = ugettext(u'Der Bonus wird nachträglich'
u' auf Meldung der Tourenleitung' u' auf Meldung der Tourenleitung'
u' verrechnet und ist noch nicht' u' verrechnet und ist noch nicht'
u' in den hier dargestellten Zahlen enthalten.') u' in den hier dargestellten Zahlen enthalten.')
self.fields['charge'].widget.attrs['title'] = (u'%dPauschale \n' self.fields['charge'].widget.attrs['title'] = (u'%dBeitrag \n'
u'+ %d Tage * %dTagespauschale \n' u'+ %d Tage * %dtäglicher Beitrag \n'
u'+ %d halben Anreisetag * %dTagespauschale / 2' u'+ %d halben Anreisetag * %dhalbtäglicher Beitrag'
% ( % (
matrix_config['participant_fee'], matrix_config['participant_fee'],
ndays, matrix_config['participant_day_fee'], ndays, matrix_config['participant_daily_fee'],
int(arrival_previous_day), matrix_config['participant_day_fee'], int(arrival_previous_day), matrix_config['participant_daily_fee']/2,
) )
) )
self.fields['trainer1_reward'].widget.attrs['title'] = (u'%dPauschale \n' self.fields['trainer1_reward'].widget.attrs['title'] = (u'%dAufwand für Tourenleitung\n'
u'+ %d Tage * %d € Tagespauschale \n' u'+ %d € Aufwand für Organisation Bahn/Bus\n'
u'+ %d halben Anreisetag * %d € Tagespauschale / 2 \n' u'+ %d € Aufwand für Trainer*in\n'
u'+ %d Vortreffen * %dVortreffenpauschale' u'+ %d Tage * %dtäglicher Aufwand \n'
u'+ %d halben Anreisetag * %d € halbtäglicher Aufwand\n'
u'+ %d Vortreffen * %d € Aufwand pro Vortreffen'
% ( % (
matrix_config['trainer_fee'], matrix_config['orga_compensation'],
ndays, matrix_config['trainer_day_fee'], int(pubtrans_planned)*matrix_config['pubtrans_compensation'],
int(arrival_previous_day), matrix_config['trainer_day_fee'], matrix_config['trainer_compensation'],
n_pre_meetings, matrix_config['pre_meeting_fee'] 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'].widget.attrs['placeholder'] = ugettext(u'Kann freigelassen werden')
self.fields['additional_costs'].initial = additional_costs_text self.fields['additional_costs'].initial = additional_costs_text

View File

View File

@@ -0,0 +1,196 @@
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)
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:
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')

View File

@@ -527,3 +527,98 @@ class Event(models.Model):
template_name = os.path.join('dav_events', 'event', 'default.html') template_name = os.path.join('dav_events', 'event', 'default.html')
template = get_template(template_name) template = get_template(template_name)
return template.render(self.get_template_context()) return template.render(self.get_template_context())
def as_dict(self, json=True, add_registration_url=False):
d = {
'id': self.id,
'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
if add_registration_url:
d['registration_url'] = reverse('dav_registration:event', kwargs={'pk': self.pk})
return d

View File

@@ -11,21 +11,29 @@
{% bootstrap_field form.charge_key %} {% bootstrap_field form.charge_key %}
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
{% bootstrap_field form.trainer_fee %} {% bootstrap_field form.orga_compensation %}
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
{% bootstrap_field form.participant_fee %} {% bootstrap_field form.pubtrans_compensation %}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-4"> <div class="col-sm-4">
{% bootstrap_field form.pre_meeting_fee %} {% bootstrap_field form.trainer_compensation %}
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
{% bootstrap_field form.trainer_day_fee %} {% bootstrap_field form.trainer_daily_compensation %}
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
{% bootstrap_field form.participant_day_fee %} {% bootstrap_field form.pre_meeting_compensation %}
</div>
</div>
<div class="row">
<div class="col-sm-4">
{% bootstrap_field form.participant_fee %}
</div>
<div class="col-sm-4">
{% bootstrap_field form.participant_daily_fee %}
</div> </div>
</div> </div>
</div> </div>
@@ -34,6 +42,9 @@
<div class="col-sm-8"> <div class="col-sm-8">
{% bootstrap_field form.pubtrans_bonus %} {% bootstrap_field form.pubtrans_bonus %}
</div> </div>
<div class="col-sm-4">
{% bootstrap_field form.pubtrans_planned %}
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">

View File

@@ -78,6 +78,7 @@
$(document).ready( function () { $(document).ready( function () {
var table = $("#objects_table").DataTable( { var table = $("#objects_table").DataTable( {
orderCellsTop: true, orderCellsTop: true,
order: [[3, 'desc']],
paging: false, paging: false,
language: { language: {
search: "{% trans 'Filter' %}:", search: "{% trans 'Filter' %}:",

View File

@@ -51,7 +51,7 @@ class Iso8601SerializerTestCase(TestCase):
for value in invalid_values: for value in invalid_values:
emsg = ('Expected datetime.datetime, datetime.date or datetime.time,' emsg = ('Expected datetime.datetime, datetime.date or datetime.time,'
' not {}'.format(value.__class__.__name__)) ' not {}'.format(value.__class__.__name__))
with self.assertRaisesRegexp(ValueError, emsg): with self.assertRaisesRegex(ValueError, emsg):
Iso8601Serializer.serialize(value) Iso8601Serializer.serialize(value)
serialized = Iso8601Serializer.serialize(value, ignore_unsupported_input=True) serialized = Iso8601Serializer.serialize(value, ignore_unsupported_input=True)
self.assertEqual(serialized, value) self.assertEqual(serialized, value)

View File

@@ -135,30 +135,30 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
self.save_screenshot('mode_form', sequence=sequence_name) self.save_screenshot('mode_form', sequence=sequence_name)
if 'mode' in data: if 'mode' in data:
field = c.find_element_by_id('id_mode') field = c.find_element(By.ID, 'id_mode')
radio = field.find_element_by_css_selector('input[value=\'{}\']'.format(data['mode'])) radio = field.find_element(By.CSS_SELECTOR, 'input[value=\'{}\']'.format(data['mode']))
radio.click() radio.click()
if 'sport' in data: if 'sport' in data:
field = c.find_element_by_id('id_sport') field = c.find_element(By.ID, 'id_sport')
radio = field.find_element_by_css_selector('input[value=\'{}\']'.format(data['sport'])) radio = field.find_element(By.CSS_SELECTOR, 'input[value=\'{}\']'.format(data['sport']))
radio.click() radio.click()
if 'level' in data: if 'level' in data:
field = c.find_element_by_id('id_level') field = c.find_element(By.ID, 'id_level')
radio = field.find_element_by_css_selector('input[value=\'{}\']'.format(data['level'])) radio = field.find_element(By.CSS_SELECTOR, 'input[value=\'{}\']'.format(data['level']))
radio.click() radio.click()
if 'ski_lift' in data and data['ski_lift']: 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.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']) 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() 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() field.click()
if screenshots: if screenshots:
self.save_screenshot('last_date_widget', sequence=sequence_name) self.save_screenshot('last_date_widget', sequence=sequence_name)
@@ -166,74 +166,74 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
field.send_keys(data['last_day']) field.send_keys(data['last_day'])
if 'alt_first_day' in data: 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']) field.send_keys(data['alt_first_day'])
if 'alt_last_day' in data: 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']) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
if screenshots: if screenshots:
self.save_screenshot('location_form', sequence=sequence_name) self.save_screenshot('location_form', sequence=sequence_name)
if 'country' in data: 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']) Select(field).select_by_value(data['country'])
if 'terrain' in data: 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']) Select(field).select_by_value(data['terrain'])
if 'location' in data: 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']) field.send_keys(data['location'])
if 'transport_other' in data: 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']) field.send_keys(data['transport_other'])
if 'transport' in data: 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']) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
if screenshots: if screenshots:
self.save_screenshot('journey_form', sequence=sequence_name) self.save_screenshot('journey_form', sequence=sequence_name)
if 'meeting_point_other' in data: 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']) field.send_keys(data['meeting_point_other'])
if 'meeting_point' in data: 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']) Select(field).select_by_value(data['meeting_point'])
if 'meeting_time' in data: 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']) field.send_keys(data['meeting_time'])
if 'departure_time' in data: 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']) field.send_keys(data['departure_time'])
if 'departure_ride' in data: 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']) field.send_keys(data['departure_ride'])
if 'return_departure_time' in data: 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']) field.send_keys(data['return_departure_time'])
if 'return_arrival_time' in data: 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']) field.send_keys(data['return_arrival_time'])
if 'arrival_previous_day' in data and data['arrival_previous_day']: 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() field.click()
button = c.find_element_by_id('btn-form-next') button = c.find_element(By.ID, 'btn-form-next')
button.click() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
@@ -242,18 +242,18 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
self.save_screenshot('accommodation_form', sequence=sequence_name) self.save_screenshot('accommodation_form', sequence=sequence_name)
if 'basecamp' in data: 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']) field.send_keys(data['basecamp'])
if 'accommodation' in data: 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']) Select(field).select_by_value(data['accommodation'])
if 'meals_other' in data: 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']) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
@@ -261,7 +261,7 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
self.save_screenshot('requirements_form', sequence=sequence_name) self.save_screenshot('requirements_form', sequence=sequence_name)
if 'requirements_add' in data: 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) field.send_keys(Keys.RETURN)
if isinstance(data['requirements_add'], list): if isinstance(data['requirements_add'], list):
field.send_keys(Keys.RETURN.join(data['requirements_add'])) field.send_keys(Keys.RETURN.join(data['requirements_add']))
@@ -269,14 +269,14 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
field.send_keys(data['requirements_add']) field.send_keys(data['requirements_add'])
if 'pre_meeting_1' in data: 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']) field.send_keys(data['pre_meeting_1'])
if 'pre_meeting_2' in data: 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']) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
if screenshots: if screenshots:
@@ -289,60 +289,60 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
field.send_keys(data['trainer_firstname']) field.send_keys(data['trainer_firstname'])
if 'trainer_familyname' in data: 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: if auth:
field.clear() field.clear()
field.send_keys(data['trainer_familyname']) field.send_keys(data['trainer_familyname'])
if 'trainer_email' in data: 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: if auth:
field.clear() field.clear()
field.send_keys(data['trainer_email']) field.send_keys(data['trainer_email'])
if 'trainer_phone' in data: 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: if auth:
field.clear() field.clear()
field.send_keys(data['trainer_phone']) field.send_keys(data['trainer_phone'])
if 'trainer_2_fullname' in data: 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']) field.send_keys(data['trainer_2_fullname'])
if 'trainer_3_fullname' in data: 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']) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
if screenshots: if screenshots:
self.save_screenshot('registration_form', sequence=sequence_name) self.save_screenshot('registration_form', sequence=sequence_name)
if 'deadline' in data: if 'deadline' in data:
field = c.find_element_by_id('id_deadline') field = c.find_element(By.ID, 'id_deadline')
radio = field.find_element_by_css_selector('input[value=\'{}\']'.format(data['deadline'])) radio = field.find_element(By.CSS_SELECTOR, 'input[value=\'{}\']'.format(data['deadline']))
radio.click() radio.click()
button = c.find_element_by_id('btn-form-next') button = c.find_element(By.ID, 'btn-form-next')
button.click() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
if screenshots: if screenshots:
self.save_screenshot('charges_form', sequence=sequence_name) self.save_screenshot('charges_form', sequence=sequence_name)
if 'charge' in data: if 'charge' in data:
field = c.find_element_by_id('id_charge') field = c.find_element(By.ID, 'id_charge')
field.clear() field.clear()
field.send_keys(data['charge']) field.send_keys(data['charge'])
if 'additional_costs' in data: 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() field.clear()
if data['additional_costs'] is not None: if data['additional_costs'] is not None:
field.send_keys(data['additional_costs']) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
@@ -350,49 +350,49 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
if screenshots: if screenshots:
self.save_screenshot('training_form', sequence=sequence_name) 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): if isinstance(data['course_topic_1'], list):
field.send_keys(Keys.RETURN.join(data['course_topic_1'])) field.send_keys(Keys.RETURN.join(data['course_topic_1']))
else: else:
field.send_keys(data['course_topic_1']) field.send_keys(data['course_topic_1'])
if 'course_topic_2' in data: 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']) field.send_keys(data['course_topic_2'])
if 'course_topic_3' in data: 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() 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']) field.send_keys(data['course_topic_3'])
if 'course_topic_4' in data: if 'course_topic_4' in data:
button.click() 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.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): if isinstance(data['course_goal_1'], list):
field.send_keys(Keys.RETURN.join(data['course_goal_1'])) field.send_keys(Keys.RETURN.join(data['course_goal_1']))
else: else:
field.send_keys(data['course_goal_1']) field.send_keys(data['course_goal_1'])
if 'course_goal_2' in data: 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']) field.send_keys(data['course_goal_2'])
if 'course_goal_3' in data: 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() 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']) field.send_keys(data['course_goal_3'])
if 'course_goal_4' in data: if 'course_goal_4' in data:
button.click() 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']) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
@@ -400,29 +400,29 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
self.save_screenshot('description_form', sequence=sequence_name) self.save_screenshot('description_form', sequence=sequence_name)
if 'title' in data: if 'title' in data:
field = c.find_element_by_id('id_title') field = c.find_element(By.ID, 'id_title')
field.clear() field.clear()
field.send_keys(data['title']) 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): if isinstance(data['description'], list):
field.send_keys(Keys.RETURN.join(data['description'])) field.send_keys(Keys.RETURN.join(data['description']))
else: else:
field.send_keys(data['description']) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
if screenshots: if screenshots:
self.save_screenshot('summary_form', sequence=sequence_name) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
for i in range(0, 11): for i in range(0, 11):
try: try:
button = c.find_element_by_id('btn-form-next') button = c.find_element(By.ID, 'btn-form-next')
button.click() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
except NoSuchElementException: except NoSuchElementException:
@@ -431,13 +431,13 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
self.fail('Too many sub forms') self.fail('Too many sub forms')
if 'internal_note' in data: 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): if isinstance(data['internal_note'], list):
field.send_keys(Keys.RETURN.join(data['internal_note'])) field.send_keys(Keys.RETURN.join(data['internal_note']))
else: else:
field.send_keys(data['internal_note']) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
@@ -445,9 +445,9 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
if screenshots: if screenshots:
self.save_screenshot('user_and_event_created_set_password', sequence=sequence_name) 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.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(TEST_PASSWORD)
field.send_keys(Keys.RETURN) field.send_keys(Keys.RETURN)
@@ -471,20 +471,20 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
if screenshots: if screenshots:
self.save_screenshot('event_list_before', sequence=sequence_name) 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() link.click()
self.wait_until_stale(c, link) self.wait_until_stale(c, link)
if screenshots: if screenshots:
self.save_screenshot('event_details', sequence=sequence_name) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
for i in range(0, 11): for i in range(0, 11):
try: try:
button = c.find_element_by_id('btn-form-next') button = c.find_element(By.ID, 'btn-form-next')
except NoSuchElementException: except NoSuchElementException:
break break
@@ -492,8 +492,8 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
self.save_screenshot('form', sequence=sequence_name) self.save_screenshot('form', sequence=sequence_name)
try: try:
field = c.find_element_by_id('id_deadline') field = c.find_element(By.ID, 'id_deadline')
radio = field.find_element_by_css_selector('input[value=\'OTHER\']') radio = field.find_element(By.CSS_SELECTOR, 'input[value=\'OTHER\']')
radio.click() radio.click()
except NoSuchElementException: except NoSuchElementException:
pass pass
@@ -506,7 +506,7 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
if screenshots: if screenshots:
self.save_screenshot('summary', sequence=sequence_name) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
@@ -523,28 +523,28 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
button.click() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
link = c.find_element_by_link_text(title) link = c.find_element(By.LINK_TEXT, title)
link.click() link.click()
self.wait_until_stale(c, link) self.wait_until_stale(c, link)
action_tabs = c.find_element_by_css_selector('.action-tabs > ul.nav-tabs') 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 = action_tabs.find_element(By.LINK_TEXT, ugettext(u'Ändern'))
tab.click() tab.click()
self.wait_until_stale(c, tab) 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]: for panel in panels[:-1]:
if screenshots: if screenshots:
self.save_screenshot('edit-form', sequence=sequence_name) 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() button.click()
time.sleep(.5) time.sleep(.5)
if screenshots: if screenshots:
self.save_screenshot('edit-form', sequence=sequence_name) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
@@ -561,26 +561,26 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
if screenshots: if screenshots:
self.save_screenshot('event_list_before', sequence=sequence_name) 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() link.click()
self.wait_until_stale(c, link) self.wait_until_stale(c, link)
if screenshots: if screenshots:
self.save_screenshot('event_details', sequence=sequence_name) 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() button.click()
time.sleep(.5) time.sleep(.5)
if screenshots: if screenshots:
self.save_screenshot('accept_modal', sequence=sequence_name) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
if screenshots: if screenshots:
self.save_screenshot('accepted', sequence=sequence_name) 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() link.click()
self.wait_until_stale(c, link) self.wait_until_stale(c, link)
if screenshots: if screenshots:
@@ -599,26 +599,26 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
if screenshots: if screenshots:
self.save_screenshot('event_list_before', sequence=sequence_name) 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() link.click()
self.wait_until_stale(c, link) self.wait_until_stale(c, link)
if screenshots: if screenshots:
self.save_screenshot('event_details', sequence=sequence_name) 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() button.click()
time.sleep(.5) time.sleep(.5)
if screenshots: if screenshots:
self.save_screenshot('confirmpublication_modal', sequence=sequence_name) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
if screenshots: if screenshots:
self.save_screenshot('confirmed_{}'.format(channel), sequence=sequence_name) 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() link.click()
self.wait_until_stale(c, link) self.wait_until_stale(c, link)
if screenshots: if screenshots:
@@ -637,20 +637,20 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
if screenshots: if screenshots:
self.save_screenshot('event_list_before', sequence=sequence_name) 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() link.click()
self.wait_until_stale(c, link) self.wait_until_stale(c, link)
if screenshots: if screenshots:
self.save_screenshot('event_details', sequence=sequence_name) 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() button.click()
self.wait_until_stale(c, button) self.wait_until_stale(c, button)
if screenshots: if screenshots:
self.save_screenshot('confirmed_clearance', sequence=sequence_name) 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() link.click()
self.wait_until_stale(c, link) self.wait_until_stale(c, link)
if screenshots: 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_W, auth=True)
self.create_event(TEST_EVENT_DATA_M, 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() link.click()
self.wait_until_stale(c, link) self.wait_until_stale(c, link)
self.save_screenshot('owner_event_details') self.save_screenshot('owner_event_details')
@@ -730,7 +730,7 @@ class TestCase(SeleniumAuthMixin, RoleMixin, ScreenshotTestCase):
button.click() button.click()
self.wait_until_stale(c, button) 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() link.click()
self.wait_until_stale(c, link) self.wait_until_stale(c, link)
self.save_screenshot('event_export_form') self.save_screenshot('event_export_form')

View File

@@ -18,16 +18,13 @@
<table id="objects_table" class="table table-bordered table-hover"> <table id="objects_table" class="table table-bordered table-hover">
<thead> <thead>
<tr> <tr>
<th class="hidden">Sport</th>
<th class="hidden">Level</th>
<th><input type="text" id="searchfield" placeholder="{% trans 'Volltextsuche' %}"></th> <th><input type="text" id="searchfield" placeholder="{% trans 'Volltextsuche' %}"></th>
<th class="hidden">Attributes</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for event in event_list %} {% for event in event_list %}
<tr> <tr>
<td class="hidden">{{ event.sport }}</td>
<td class="hidden">{{ event.level }}</td>
<td> <td>
<div class="pull-right" style="margin-left: 2em;"> <div class="pull-right" style="margin-left: 2em;">
<a role="button" id="controlChevronCollapseDetails{{ event.id }}" data-toggle="collapse" <a role="button" id="controlChevronCollapseDetails{{ event.id }}" data-toggle="collapse"
@@ -122,42 +119,51 @@
}); });
</script> </script>
</td> </td>
<td class="hidden">{{ event.sport }},{{ event.level }},</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<script type="text/javascript"> <script type="text/javascript">
function filter_table(table, sport, level) { function filter_table(table, filter) {
const sport_choices = ["B", "K", "M", "S", "W"]; const sport_choices = ["B", "K", "M", "S", "W"];
const level_choices = ["beginner", "advanced", "family"]; const level_choices = ["beginner", "advanced", "family"];
var choices = sport_choices.concat(level_choices);
var filter_cleaned = [];
var filter_expr;
if(sport != "*") for(let i in choices) {
$("#btn-filter-" + sport).removeClass("btn-white"); if(filter.indexOf(choices[i]) >= 0) {
if(level != "*") filter_cleaned.push(choices[i]);
$("#btn-filter-" + level).removeClass("btn-white"); $("#btn-filter-" + choices[i]).removeClass("btn-white");
} else {
$("#btn-filter-" + choices[i]).addClass("btn-white");
}
}
if(sport == "*" && level == "*") if(filter_cleaned.length > 0) {
$("#btn-filter-All").removeClass("btn-white");
else
$("#btn-filter-All").addClass("btn-white"); $("#btn-filter-All").addClass("btn-white");
filter_expr = "(" + filter_cleaned.join("|") + "),";
for(let i in sport_choices) { } else {
if(sport != sport_choices[i]) $("#btn-filter-All").removeClass("btn-white");
$("#btn-filter-" + sport_choices[i]).addClass("btn-white"); filter_expr = "";
}
for(let i in level_choices) {
if(level != level_choices[i])
$("#btn-filter-" + level_choices[i]).addClass("btn-white");
} }
if(sport == "*") table.column(1).search(filter_expr, true, false);
sport = "" table.draw();
table.column(0).search(sport).draw(); }
function toggle_filter(table, filter, attribute) {
if(level == "*") let i = filter.indexOf(attribute);
level = "" if(i >= 0)
table.column(1).search(level).draw(); filter.splice(i, 1);
}; else
filter.push(attribute);
filter_table(table, filter);
}
function reset_filter(table, filter) {
filter.splice(0, filter.length);
filter_table(table, filter);
}
$(document).ready( function () { $(document).ready( function () {
var table = $("#objects_table").DataTable( { var table = $("#objects_table").DataTable( {
ordering: false, ordering: false,
@@ -172,32 +178,33 @@
zeroRecords: "{% trans 'Keine passenden Einträge.' %}", zeroRecords: "{% trans 'Keine passenden Einträge.' %}",
} }
} ); } );
var filter = ['{{ init_filter|join:"','" }}'];
$("#btn-filter-All").on("click", function() { $("#btn-filter-All").on("click", function() {
filter_table(table, "*", "*"); reset_filter(table, filter);
} ); } );
$("#btn-filter-B").on("click", function() { $("#btn-filter-B").on("click", function() {
filter_table(table, "B", "*") toggle_filter(table, filter, "B");
} ); } );
$("#btn-filter-K").on("click", function() { $("#btn-filter-K").on("click", function() {
filter_table(table, "K", "*") toggle_filter(table, filter, "K");
} ); } );
$("#btn-filter-M").on("click", function() { $("#btn-filter-M").on("click", function() {
filter_table(table, "M", "*") toggle_filter(table, filter, "M");
} ); } );
$("#btn-filter-S").on("click", function() { $("#btn-filter-S").on("click", function() {
filter_table(table, "S", "*") toggle_filter(table, filter, "S");
} ); } );
$("#btn-filter-W").on("click", function() { $("#btn-filter-W").on("click", function() {
filter_table(table, "W", "*") toggle_filter(table, filter, "W");
} ); } );
$("#btn-filter-family").on("click", function() { $("#btn-filter-family").on("click", function() {
filter_table(table, "*", "family") toggle_filter(table, filter, "family");
} ); } );
$("#searchfield").on( "keyup change", function() { $("#searchfield").on( "keyup change", function() {
table.column(2).search( this.value ).draw(); table.column(0).search( this.value ).draw();
} ); } );
$("#objects_table_filter").hide(); $("#objects_table_filter").hide();
filter_table(table, "{{ init_sport_filter|default:'*' }}", "{{ init_level_filter|default:'*' }}"); filter_table(table, filter);
} ); } );
</script> </script>
</div> </div>

View File

@@ -11,4 +11,5 @@ urlpatterns = [
url(r'^event/(?P<pk>\d+)/registration', views.RegistrationView.as_view(), name='register'), url(r'^event/(?P<pk>\d+)/registration', views.RegistrationView.as_view(), name='register'),
url(r'^event/(?P<pk>\d+)/', views.EventDetailView.as_view(), name='event'), url(r'^event/(?P<pk>\d+)/', views.EventDetailView.as_view(), name='event'),
url(r'^event', views.EventListView.as_view(), name='events'), url(r'^event', views.EventListView.as_view(), name='events'),
url(r'^api/v1/event', views.EventListAsJSONView, name='api_events'),
] ]

View File

@@ -4,6 +4,7 @@ import logging
from django.apps import apps from django.apps import apps
from django.contrib import messages from django.contrib import messages
from django.db.models import Q from django.db.models import Q
from django.http import JsonResponse
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views import generic from django.views import generic
@@ -51,24 +52,43 @@ class EventListView(generic.ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
if hasattr(self, 'init_sport_filter'): if hasattr(self, 'init_filter'):
context['init_sport_filter'] = self.init_sport_filter context['init_filter'] = self.init_filter
if hasattr(self, 'init_level_filter'):
context['init_level_filter'] = self.init_level_filter
return context return context
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if 'sport' in request.GET: if 'filter' in request.GET:
sport = request.GET['sport'] choices = SPORT_CHOICES.codes + LEVEL_CHOICES.codes
if (sport, 'Bogus') in SPORT_CHOICES: filter_input = request.GET['filter'].split(',')
self.init_sport_filter = sport filter_cleaned = list(set(filter_input) & set(choices))
if 'level' in request.GET: self.init_filter = filter_cleaned
level = request.GET['level']
if (level, 'Bogus') in LEVEL_CHOICES:
self.init_level_filter = level
return super().get(request, *args, **kwargs) 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, add_registration_url=True) for event in qs]
response = JsonResponse(data, safe=False)
return response
class EventDetailView(generic.DetailView): class EventDetailView(generic.DetailView):
model = Event model = Event
template_name = 'dav_registration/event_detail.html' template_name = 'dav_registration/event_detail.html'

283
etc/test_data.json Normal file
View File

@@ -0,0 +1,283 @@
{
"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",
"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}",
"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",
"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,
"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,
"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"]
}
]
}

6
requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
babel
django<3.3
django-bootstrap3
django-countries
django-datetime-widget2
setuptools

View File

@@ -6,29 +6,21 @@ from setuptools import setup, find_packages
from setuptools import Command from setuptools import Command
class MyCommand(Command): class SetupPythonEnvironment(Command):
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
class SetupPythonEnvironment(MyCommand):
description = 'create a (virtual) python environment' description = 'create a (virtual) python environment'
def run(self): def run(self):
python_bin = sys.executable if sys.executable else 'python' python_bin = sys.executable if sys.executable else 'python'
python_ver = sys.version_info.major python_major_ver = sys.version_info.major
if python_ver == 3: python_minor_ver = sys.version_info.minor
path = os.path.join('env', 'python3') 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') symlink_path = os.path.join('env', 'python')
venv_module = 'venv' venv_module = 'venv'
prompt = 'py3-dav' prompt = 'py%i.%i-dav' % (python_major_ver, python_minor_ver)
else: 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) sys.exit(posix.EX_USAGE)
print('Creating new python environment in {path}'.format(path=path)) print('Creating new python environment in {path}'.format(path=path))
@@ -52,7 +44,7 @@ class SetupPythonEnvironment(MyCommand):
print('- All others: source %s/bin/activate' % path) print('- All others: source %s/bin/activate' % path)
class QuickSetup(MyCommand): class QuickSetup(Command):
description = 'create a typical installation for developing' description = 'create a typical installation for developing'
def run(self): def run(self):
@@ -90,11 +82,20 @@ class QuickSetup(MyCommand):
os.system(cmd) 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( setup(
name='django-dav-events', name='django-dav-events',
version='2.1', 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.',
url='https://touren.alpenverein-karlsruhe.de', 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='Jens Kleineheismann',
author_email='heinzel@alpenverein-karlsruhe.de', author_email='heinzel@alpenverein-karlsruhe.de',
cmdclass={ cmdclass={
@@ -103,25 +104,9 @@ setup(
}, },
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
test_suite='tests.test_suite',
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'django-dav-admin = dav_base.console_scripts.admin:main', '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',
'coverage',
],
extras_require={
'production': ['psycopg2'],
},
) )

View File

@@ -11,6 +11,7 @@ from dav_base.tests.utils import mkdtemp
# from dav_base.console_scripts.admin import DJANGO_MAIN_MODULE # from dav_base.console_scripts.admin import DJANGO_MAIN_MODULE
DJANGO_MAIN_MODULE = 'main' DJANGO_MAIN_MODULE = 'main'
TMP_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'tmp')
class DjangoEnvironment: class DjangoEnvironment:
@@ -42,7 +43,7 @@ class DjangoEnvironment:
prefix = 'testrun-{datetime}-'.format( prefix = 'testrun-{datetime}-'.format(
datetime=datetime.datetime.now().strftime('%Y%m%d-%H%M') 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) self._install_djangoproject(self.path, modules=self._enable_modules)

30
tox.ini
View File

@@ -1,7 +1,29 @@
[tox] [tox]
envlist = py3 envlist = fresh, coverage
[testenv] [testenv]
commands = python --version setenv =
python -m coverage run tests/test_suite.py PYTHONPATH = .
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