forked from beba/foerderbarometer
Compare commits
No commits in common. "feature-cc" and "master" have entirely different histories.
feature-cc
...
master
23
.coveragerc
23
.coveragerc
|
|
@ -1,23 +0,0 @@
|
||||||
[run]
|
|
||||||
branch = on
|
|
||||||
source = input
|
|
||||||
omit =
|
|
||||||
# ignore the tests itself
|
|
||||||
*/test*.py
|
|
||||||
*/tests/*.py
|
|
||||||
|
|
||||||
# ignore wsgi & asgi
|
|
||||||
*/?sgi.py
|
|
||||||
|
|
||||||
[report]
|
|
||||||
include =
|
|
||||||
input/*
|
|
||||||
exclude_lines =
|
|
||||||
pragma: no cover
|
|
||||||
raise NotImplementedError
|
|
||||||
omit =
|
|
||||||
# they are excluded in run, so
|
|
||||||
# doesn't need to be reported
|
|
||||||
*/test*.py
|
|
||||||
*/tests/*.py
|
|
||||||
*/?sgi.py
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
ENVIRONMENT = develop
|
|
||||||
DEBUG = yes
|
|
||||||
SECRET_KEY = not-a-secret-key
|
|
||||||
DATABASE_ENGINE = sqlite3
|
|
||||||
EMAIL_BACKEND = console
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
ENVIRONMENT = production
|
|
||||||
DEBUG = no
|
|
||||||
SECRET_KEY = <enter a secret key>
|
|
||||||
HOST = https://foerderung.wikimedia.de
|
|
||||||
DATABASE_ENGINE = mysql
|
|
||||||
DATABASE_PASSWORD = <enter a database password>
|
|
||||||
EMAIL_BACKEND = smtp
|
|
||||||
EMAIL_HOST_USER = <enter an email host user>
|
|
||||||
EMAIL_HOST_PASSWORD = <enter an email host password>
|
|
||||||
OAUTH_ENABLED = yes
|
|
||||||
OAUTH_CLIENT_NAME = <enter a client name>
|
|
||||||
OAUTH_CLIENT_ID = <enter a client id>
|
|
||||||
OAUTH_CLIENT_SECRET = <enter a client secret>
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
# secret passwords and so
|
# secret passwords and so
|
||||||
/secrets.json
|
/secrets.json
|
||||||
/staticfiles
|
/staticfiles
|
||||||
|
# /foerderbarometer/settings.py
|
||||||
|
# /foerderbarometer/*settings*
|
||||||
|
/input/settings.py
|
||||||
/nohup.out
|
/nohup.out
|
||||||
/logfile
|
/logfile
|
||||||
*~
|
*~
|
||||||
|
|
@ -90,10 +93,6 @@ target/
|
||||||
profile_default/
|
profile_default/
|
||||||
ipython_config.py
|
ipython_config.py
|
||||||
|
|
||||||
# IDEA
|
|
||||||
/*.iml
|
|
||||||
/.idea
|
|
||||||
|
|
||||||
# pyenv
|
# pyenv
|
||||||
.python-version
|
.python-version
|
||||||
|
|
||||||
|
|
@ -140,6 +139,3 @@ dmypy.json
|
||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
# MacOS
|
|
||||||
.DS_Store
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
stages:
|
|
||||||
- test
|
|
||||||
|
|
||||||
test:
|
|
||||||
stage: test
|
|
||||||
image: python:3.11-bookworm
|
|
||||||
variables:
|
|
||||||
DJANGO_SETTINGS_MODULE: foerderbarometer.settings
|
|
||||||
ENVIRONMENT: test
|
|
||||||
SECRET_KEY: this-is-not-a-secret-key
|
|
||||||
DATABASE_ENGINE: mysql
|
|
||||||
DATABASE_HOST: mariadb
|
|
||||||
DATABASE_USER: root
|
|
||||||
DATABASE_PASSWORD: fdb
|
|
||||||
OAUTH_ENABLED: no
|
|
||||||
services:
|
|
||||||
- name: mariadb:10.6
|
|
||||||
variables:
|
|
||||||
MARIADB_ROOT_PASSWORD: fdb
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
coverage: /(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/
|
|
||||||
before_script:
|
|
||||||
- pip install -r requirements.txt coverage
|
|
||||||
script:
|
|
||||||
- coverage run manage.py test --noinput
|
|
||||||
after_script:
|
|
||||||
- coverage report
|
|
||||||
90
README.md
90
README.md
|
|
@ -2,13 +2,9 @@
|
||||||
|
|
||||||
purpose: gather data from intern(WMDE) and extern(volunteers) forms to create a database ('förderdatenbank') and send emails with links for a questionary.
|
purpose: gather data from intern(WMDE) and extern(volunteers) forms to create a database ('förderdatenbank') and send emails with links for a questionary.
|
||||||
|
|
||||||
## manual development setup
|
## installation and development setup
|
||||||
|
|
||||||
cp .env.develop.example .env
|
ln -sr foerderbarometer/settings_development.py foerderbarometer/settings.py
|
||||||
|
|
||||||
By default a SQLite database is used for development.
|
|
||||||
To use a MariaDB change `DATABASE_ENGINE` in .env to `mysql` and amend `DATABASE_*` variables according to your setup.
|
|
||||||
For further information see the production setup below.
|
|
||||||
|
|
||||||
build the database with
|
build the database with
|
||||||
|
|
||||||
|
|
@ -25,26 +21,9 @@ run the development server with
|
||||||
access via
|
access via
|
||||||
|
|
||||||
http://localhost:8000/
|
http://localhost:8000/
|
||||||
|
http://localhost:8000/intern/ (login required)
|
||||||
http://localhost:8000/admin/ (login reqiured)
|
http://localhost:8000/admin/ (login reqiured)
|
||||||
|
|
||||||
## docker compose development setup
|
|
||||||
|
|
||||||
The project comes with a `docker-compose.yml` file to run the project in a containerized environment.
|
|
||||||
|
|
||||||
cp .env.develop.example .env
|
|
||||||
docker compose up
|
|
||||||
|
|
||||||
The setup will use a containerized MariaDB database.
|
|
||||||
|
|
||||||
Create your superuser account with
|
|
||||||
|
|
||||||
docker compose exec django python3 manage.py createsuperuser
|
|
||||||
|
|
||||||
You can access the application via
|
|
||||||
|
|
||||||
http://localhost:8000/
|
|
||||||
http://localhost:8000/admin/ (login required)
|
|
||||||
|
|
||||||
## additional admin functionality
|
## additional admin functionality
|
||||||
|
|
||||||
The admin page is the standard admin page delivered by django but with two additional functionalities:
|
The admin page is the standard admin page delivered by django but with two additional functionalities:
|
||||||
|
|
@ -54,24 +33,29 @@ entries to a csv file
|
||||||
|
|
||||||
- There is a new button in the bottom of every Project to "save as new"
|
- There is a new button in the bottom of every Project to "save as new"
|
||||||
|
|
||||||
## mail attachments
|
## versions used in development
|
||||||
|
|
||||||
For all mails, attachments can be defined as URLs. These URLs are fetched and chached when sending the mail and attached to the mail.
|
asgiref==3.2.10
|
||||||
|
Django==3.1.2
|
||||||
Configuration is done via the `ATTACHMENT_URLS` setting. Attachments can be set for user (`RECIPIENT_APPLICANT`) and staff (`RECIPIENT_STAFF`) mails. The following mail types exist:
|
django-formtools==2.4
|
||||||
|
gunicorn==20.0.4
|
||||||
* `TYPE_BIB` Bibliotheksstipendium
|
mysqlclient==2.1.1
|
||||||
* `TYPE_ELIT` eLiteraturstipendium
|
sqlparse==0.4.3
|
||||||
* `TYPE_SOFT` Softwarestipendium
|
whitenoise==6.2.0
|
||||||
* `TYPE_MAIL` E-Mail-Adresse
|
asgiref==3.2.10
|
||||||
* `TYPE_IFG` Kostenübernahme IFG-Anfrage
|
Authlib==1.2.1
|
||||||
* `TYPE_LIT` Literaturstipendium
|
certifi==2023.7.22
|
||||||
* `TYPE_LIST` Mailingliste
|
cffi==1.16.0
|
||||||
* `TYPE_TRAV` Reisekosten
|
chardet==5.2.0
|
||||||
* `TYPE_VIS` Visitenkarten
|
charset-normalizer==3.3.0
|
||||||
* `TYPE_PROJ` Projektförderung
|
cryptography==41.0.4
|
||||||
|
idna==3.4
|
||||||
For further details see `foerderbarometer/settings.py`
|
pycparser==2.21
|
||||||
|
pytz==2023.3.post1
|
||||||
|
requests==2.31.0
|
||||||
|
six==1.16.0
|
||||||
|
typing_extensions==4.8.0
|
||||||
|
urllib3==2.0.6
|
||||||
|
|
||||||
## testing
|
## testing
|
||||||
|
|
||||||
|
|
@ -81,26 +65,16 @@ run some tests with
|
||||||
|
|
||||||
## production setup
|
## production setup
|
||||||
|
|
||||||
cp .env.production.example .env
|
ln -sr foerderbarometer/settings_production.py foerderbarometer/settings.py
|
||||||
|
|
||||||
edit .env and fill in the missing secrets
|
edit /secrets.json to contain something similar to
|
||||||
|
|
||||||
SECRET_KEY
|
{
|
||||||
DATABASE_PASSWORD
|
"DATABASE_PASSWORD": "THIS IS TOP SECRET!",
|
||||||
EMAIL_HOST_USER
|
"SECRET_KEY": "THIS IS ANOTHER SECRET!"
|
||||||
EMAIL_HOST_PASSWORD
|
}
|
||||||
OAUTH_CLIENT_NAME
|
|
||||||
OAUTH_CLIENT_ID
|
|
||||||
OAUTH_CLIENT_SECRET
|
|
||||||
|
|
||||||
amend database variables to .env according to your database setup (tested with MariaDB 10.0.36), e.g.
|
edit foerderbarometer/settings_production.py according to your database setup (tested with MariaDB 10.0.36)
|
||||||
|
|
||||||
DATABASE_NAME
|
|
||||||
DATABASE_USER
|
|
||||||
DATABASE_HOST
|
|
||||||
DATABASE_PORT
|
|
||||||
|
|
||||||
for a full set of all possible env vars have a look at foerderbarometer/settings.py
|
|
||||||
|
|
||||||
run the following commands:
|
run the following commands:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
services:
|
|
||||||
django:
|
|
||||||
image: python:3-alpine
|
|
||||||
working_dir: /app
|
|
||||||
volumes:
|
|
||||||
- .:/app
|
|
||||||
environment:
|
|
||||||
PYTHONUNBUFFERED: 1
|
|
||||||
DATABASE_ENGINE: mysql
|
|
||||||
DATABASE_HOST: mariadb
|
|
||||||
DATABASE_USER: fdb
|
|
||||||
DATABASE_PASSWORD: fdb
|
|
||||||
ports:
|
|
||||||
- 8000:8000
|
|
||||||
command:
|
|
||||||
- sh
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
apk update
|
|
||||||
apk add gcc
|
|
||||||
apk add mariadb-dev
|
|
||||||
apk add musl-dev
|
|
||||||
pip install -Ur requirements.txt
|
|
||||||
python manage.py migrate
|
|
||||||
python manage.py runserver 0.0.0.0:8000
|
|
||||||
|
|
||||||
mariadb:
|
|
||||||
image: mariadb
|
|
||||||
environment:
|
|
||||||
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: yes
|
|
||||||
MARIADB_DATABASE: fdb
|
|
||||||
MARIADB_USER: fdb
|
|
||||||
MARIADB_PASSWORD: fdb
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
TYPE_ALL = 'ALL'
|
|
||||||
|
|
||||||
TYPE_BIB = 'BIB' # Bibliotheksstipendium
|
|
||||||
TYPE_ELIT = 'ELIT' # eLiteraturstipendium
|
|
||||||
TYPE_SOFT = 'SOFT' # Softwarestipendium
|
|
||||||
TYPE_MAIL = 'MAIL' # E-Mail-Adresse
|
|
||||||
TYPE_IFG = 'IFG' # Kostenübernahme IFG-Anfrage
|
|
||||||
TYPE_LIT = 'LIT' # Literaturstipendium
|
|
||||||
TYPE_LIST = 'LIST' # Mailingliste
|
|
||||||
TYPE_TRAV = 'TRAV' # Reisekosten
|
|
||||||
TYPE_VIS = 'VIS' # Visitenkarten
|
|
||||||
TYPE_PROJ = 'PROJ' # Projektförderung
|
|
||||||
|
|
||||||
TYPES = [
|
|
||||||
TYPE_BIB,
|
|
||||||
TYPE_ELIT,
|
|
||||||
TYPE_SOFT,
|
|
||||||
TYPE_MAIL,
|
|
||||||
TYPE_IFG,
|
|
||||||
TYPE_LIT,
|
|
||||||
TYPE_LIST,
|
|
||||||
TYPE_TRAV,
|
|
||||||
TYPE_VIS,
|
|
||||||
TYPE_PROJ,
|
|
||||||
]
|
|
||||||
|
|
||||||
RECIPIENT_APPLICANT = 'applicant'
|
|
||||||
RECIPIENT_STAFF = 'staff'
|
|
||||||
|
|
||||||
RECIPIENTS = [
|
|
||||||
RECIPIENT_APPLICANT,
|
|
||||||
RECIPIENT_STAFF,
|
|
||||||
]
|
|
||||||
|
|
@ -1,195 +0,0 @@
|
||||||
import os
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
|
|
||||||
from input.utils.settings import env, password_validators
|
|
||||||
|
|
||||||
from .constants import *
|
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).parents[1]
|
|
||||||
|
|
||||||
load_dotenv(BASE_DIR / '.env')
|
|
||||||
|
|
||||||
DEBUG = env('DEBUG', False)
|
|
||||||
|
|
||||||
SECRET_KEY = env('SECRET_KEY')
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['*']
|
|
||||||
|
|
||||||
HOST = env('HOST', 'http://localhost:8000')
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'input.apps.InputConfig',
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
|
||||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.locale.LocaleMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
]
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'foerderbarometer.urls'
|
|
||||||
|
|
||||||
DJANGO_TEMPLATES = {
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [],
|
|
||||||
'APP_DIRS': True,
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.template.context_processors.debug',
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
TEMPLATES = [DJANGO_TEMPLATES]
|
|
||||||
|
|
||||||
WSGI_APPLICATION = 'foerderbarometer.wsgi.application'
|
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
|
||||||
|
|
||||||
DATABASE_ENGINE = env('DATABASE_ENGINE', 'mysql')
|
|
||||||
|
|
||||||
DATABASE_SQLITE = {
|
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
|
||||||
'NAME': BASE_DIR / 'db.sqlite3',
|
|
||||||
}
|
|
||||||
|
|
||||||
DATABASE_MYSQL = {
|
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
|
||||||
'NAME': env('DATABASE_NAME', 'fdb'),
|
|
||||||
'USER': env('DATABASE_USER', 'fdb'),
|
|
||||||
'PASSWORD': env('DATABASE_PASSWORD'),
|
|
||||||
'HOST': env('DATABASE_HOST', 'localhost'),
|
|
||||||
'PORT': env('DATABASE_PORT', 3306),
|
|
||||||
'OPTIONS': {
|
|
||||||
'charset' : 'utf8',
|
|
||||||
'use_unicode' : True,
|
|
||||||
'init_command': 'SET '
|
|
||||||
'storage_engine=INNODB,'
|
|
||||||
'character_set_connection=utf8,'
|
|
||||||
'collation_connection=utf8_bin'
|
|
||||||
},
|
|
||||||
'TEST_CHARSET': 'utf8',
|
|
||||||
'TEST_COLLATION': 'utf8_general_ci',
|
|
||||||
}
|
|
||||||
|
|
||||||
if DATABASE_ENGINE == 'mysql':
|
|
||||||
DATABASE_DEFAULT = DATABASE_MYSQL
|
|
||||||
else:
|
|
||||||
DATABASE_DEFAULT = DATABASE_SQLITE
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': DATABASE_DEFAULT,
|
|
||||||
}
|
|
||||||
|
|
||||||
EMAIL_BACKEND = env('EMAIL_BACKEND', 'console')
|
|
||||||
|
|
||||||
if EMAIL_BACKEND == 'smtp':
|
|
||||||
EMAIL_HOST = env('EMAIL_HOST', 'email.wikimedia.de')
|
|
||||||
EMAIL_PORT = env('EMAIL_PORT', 587)
|
|
||||||
EMAIL_USE_TLS = env('EMAIL_USE_TLS', True)
|
|
||||||
EMAIL_HOST_USER = env('EMAIL_HOST_USER')
|
|
||||||
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')
|
|
||||||
|
|
||||||
EMAIL_BACKEND = f'django.core.mail.backends.{EMAIL_BACKEND}.EmailBackend'
|
|
||||||
EMAIL_URL_PREFIX = env('EMAIL_URL_PREFIX', HOST)
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = password_validators(
|
|
||||||
'UserAttributeSimilarityValidator',
|
|
||||||
'MinimumLengthValidator',
|
|
||||||
'CommonPasswordValidator',
|
|
||||||
'NumericPasswordValidator',
|
|
||||||
)
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
USE_L10N = True
|
|
||||||
LANGUAGE_CODE = env('LANGUAGE_CODE', 'de')
|
|
||||||
LANGUAGES = [
|
|
||||||
('de', 'Deutsch'),
|
|
||||||
]
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
TIME_ZONE = env('TIME_ZONE', 'Europe/Berlin')
|
|
||||||
|
|
||||||
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
|
||||||
STATIC_URL = '/static/'
|
|
||||||
|
|
||||||
FORM_RENDERER = 'input.forms.TableFormRenderer'
|
|
||||||
|
|
||||||
if OAUTH_ENABLED := env('OAUTH_ENABLED', not DEBUG):
|
|
||||||
MIDDLEWARE += ['input.middleware.oauth.OAuthMiddleware']
|
|
||||||
|
|
||||||
OAUTH_CLIENT_NAME = env('OAUTH_CLIENT_NAME')
|
|
||||||
|
|
||||||
OAUTH_CLIENT = {
|
|
||||||
'client_id': env('OAUTH_CLIENT_ID'),
|
|
||||||
'client_secret': env('OAUTH_CLIENT_SECRET'),
|
|
||||||
'access_token_url': 'https://meta.wikimedia.org/w/rest.php/oauth2/access_token',
|
|
||||||
'authorize_url': 'https://meta.wikimedia.org/w/rest.php/oauth2/authorize',
|
|
||||||
'api_base_url': 'https://meta.wikimedia.org/w/rest.php/oauth2/resource',
|
|
||||||
'redirect_uri': env('OAUTH_REDIRECT_URI', f'{HOST}/oauth/callback'),
|
|
||||||
'client_kwargs': {
|
|
||||||
'scope': 'basic',
|
|
||||||
'token_placement': 'header'
|
|
||||||
},
|
|
||||||
'userinfo_endpoint': 'resource/profile',
|
|
||||||
}
|
|
||||||
|
|
||||||
OAUTH_URL_WHITELISTS = ['/admin']
|
|
||||||
|
|
||||||
OAUTH_COOKIE_SESSION_ID = 'sso_session_id'
|
|
||||||
|
|
||||||
IF_EMAIL = env('IF_EMAIL', 'community@wikimedia.de')
|
|
||||||
|
|
||||||
SURVEY_EMAIL = env('SURVEY_EMAIL', 'sandro.halank@wikimedia.de')
|
|
||||||
SURVEY_PREFIX = env('SURVEY_PREFIX', 'https://wikimedia.sslsurvey.de/Foerderbarometer/?')
|
|
||||||
|
|
||||||
DATAPROTECTION = 'https://www.wikimedia.de/datenschutz/#datenerfassung'
|
|
||||||
FOERDERRICHTLINIEN = 'https://de.wikipedia.org/wiki/Wikipedia:Wikimedia_Deutschland/Richtlinie_zur_Förderung_der_Communitys'
|
|
||||||
|
|
||||||
NUTZUNGSBEDINGUNGEN = 'static/input/nutzungsbedingungen.html'
|
|
||||||
NUTZUNGSBEDINGUNGEN_EMAIL_SERVICE = 'static/input/nutzungsbedingungen-mail.pdf'
|
|
||||||
NUTZUNGSBEDINGUNGEN_MAILINGLISTEN = 'static/input/nutzungsbedingungen-mailinglisten.pdf'
|
|
||||||
NUTZUNGSBEDINGUNGEN_LITERATURSTIPENDIUM = 'static/input/nutzungsbedingungen-literaturstipendium.pdf'
|
|
||||||
NUTZUNGSBEDINGUNGEN_OTRS = 'static/input/2025_Nutzungsvereinbarung_OTRS.docx.pdf'
|
|
||||||
NUTZUNGSBEDINGUNGEN_VISITENKARTEN = 'static/input/nutzungsbedingungen-visitenkarten.pdf'
|
|
||||||
|
|
||||||
MAIL_ATTACHMENT_CACHE_DIR = env('MAIL_ATTACHMENT_CACHE_DIR', BASE_DIR / 'var' / 'mail-attachments')
|
|
||||||
MAIL_ATTACHMENT_TTL_SECONDS = env('MAIL_ATTACHMENT_TTL_SECONDS', 24 * 60 * 60)
|
|
||||||
MAIL_ATTACHMENT_URLS = {
|
|
||||||
RECIPIENT_APPLICANT: {
|
|
||||||
TYPE_ALL: [],
|
|
||||||
TYPE_VIS: [
|
|
||||||
'https://foerderung.wikimedia.de/static/input/nutzungsbedingungen-visitenkarten.pdf',
|
|
||||||
],
|
|
||||||
TYPE_MAIL: [
|
|
||||||
'https://foerderung.wikimedia.de/static/input/nutzungsbedingungen-mail.pdf',
|
|
||||||
],
|
|
||||||
TYPE_LIST: [
|
|
||||||
'https://foerderung.wikimedia.de/static/input/nutzungsbedingungen-mailinglisten.pdf',
|
|
||||||
],
|
|
||||||
TYPE_LIT: [
|
|
||||||
'https://foerderung.wikimedia.de/static/input/nutzungsbedingungen-literaturstipendium.pdf',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
RECIPIENT_STAFF: {
|
|
||||||
TYPE_ALL: [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,203 @@
|
||||||
|
"""
|
||||||
|
Django settings for foerderbarometer project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.1.1.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
|
# prefix for urls in mails
|
||||||
|
URLPREFIX = 'https://fdb-devel.wikimedia.de'
|
||||||
|
|
||||||
|
|
||||||
|
# mails in development go to stdout
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
|
|
||||||
|
CSRF_TRUSTED_ORIGINS = ['https://fdb-devel.wikimedia.de']
|
||||||
|
|
||||||
|
# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
|
||||||
|
EMAIL_HOST = 'xemail.wikimedia.de'
|
||||||
|
EMAIL_PORT = '587'
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
#EMAIL_HOST_USER = get_secret('EMAIL_HOST_USER')
|
||||||
|
#EMAIL_HOST_PASSWORD = get_secret('EMAIL_HOST_PASSWORD')
|
||||||
|
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# get secrets
|
||||||
|
with open(os.path.join(BASE_DIR, 'secrets.json')) as secrets_file:
|
||||||
|
secrets = json.load(secrets_file)
|
||||||
|
|
||||||
|
def get_secret(setting, secrets=secrets):
|
||||||
|
"""Get secret setting or fail with ImproperlyConfigured"""
|
||||||
|
try:
|
||||||
|
return secrets[setting]
|
||||||
|
except KeyError:
|
||||||
|
raise ImproperlyConfigured("Set the {} setting".format(setting))
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = get_secret('SECRET_KEY')
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'input.apps.InputConfig',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'formtools',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'input.middleware.oauth.OAuthMiddleware'
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'foerderbarometer.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'foerderbarometer.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
|
'NAME': 'fdbdevel',
|
||||||
|
'USER': 'fdbdevel',
|
||||||
|
'PASSWORD': get_secret('DATABASE_PASSWORD'),
|
||||||
|
'HOST': '10.0.6.224', # Or an IP Address that your database is hosted on
|
||||||
|
# 'PORT': '3306',
|
||||||
|
#optional:
|
||||||
|
'OPTIONS': {
|
||||||
|
'charset' : 'utf8',
|
||||||
|
'use_unicode' : True,
|
||||||
|
'init_command': 'SET '
|
||||||
|
'storage_engine=INNODB,'
|
||||||
|
'character_set_connection=utf8,'
|
||||||
|
'collation_connection=utf8_bin'
|
||||||
|
#'sql_mode=STRICT_TRANS_TABLES,' # see note below
|
||||||
|
#'SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED',
|
||||||
|
},
|
||||||
|
'TEST_CHARSET': 'utf8',
|
||||||
|
'TEST_COLLATION': 'utf8_general_ci',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/3.1/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/3.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
# needed since django 3.2
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||||
|
|
||||||
|
|
||||||
|
# OAuth Settings
|
||||||
|
OAUTH_URL_WHITELISTS = ['/admin']
|
||||||
|
|
||||||
|
OAUTH_CLIENT_NAME = '<name-of-the-configured-wikimedia-app>'
|
||||||
|
OAUTH_CLIENT_NAME = get_secret('OAUTH_CLIENT_NAME')
|
||||||
|
|
||||||
|
|
||||||
|
OAUTH_CLIENT = {
|
||||||
|
'client_id': get_secret('OAUTH_CLIENT_ID'),
|
||||||
|
'client_secret': get_secret('OAUTH_CLIENT_SECRET'),
|
||||||
|
'access_token_url': 'https://meta.wikimedia.org/w/rest.php/oauth2/access_token',
|
||||||
|
'authorize_url': 'https://meta.wikimedia.org/w/rest.php/oauth2/authorize',
|
||||||
|
'api_base_url': 'https://meta.wikimedia.org/w/rest.php/oauth2/resource',
|
||||||
|
'redirect_uri': 'https://fdb-devel.wikimedia.de/oauth/callback',
|
||||||
|
'client_kwargs': {
|
||||||
|
'scope': 'basic',
|
||||||
|
'token_placement': 'header'
|
||||||
|
},
|
||||||
|
'userinfo_endpoint': 'resource/profile',
|
||||||
|
}
|
||||||
|
|
||||||
|
OAUTH_COOKIE_SESSION_ID = 'sso_session_id'
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
"""
|
||||||
|
Django settings for foerderbarometer project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.1.1.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
|
# prefix for urls in mails
|
||||||
|
URLPREFIX = 'http://localhost:8000'
|
||||||
|
|
||||||
|
# mails in development go to stdout
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# get secrets
|
||||||
|
with open(os.path.join(BASE_DIR, 'secrets.json')) as secrets_file:
|
||||||
|
secrets = json.load(secrets_file)
|
||||||
|
|
||||||
|
def get_secret(setting, secrets=secrets):
|
||||||
|
"""Get secret setting or fail with ImproperlyConfigured"""
|
||||||
|
try:
|
||||||
|
return secrets[setting]
|
||||||
|
except KeyError:
|
||||||
|
raise ImproperlyConfigured("Set the {} setting".format(setting))
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = '*&7p9#_n$@^%0z49s+7jpy@+j1rw_hqh05knyd6y2*!0)r&b6h'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'input.apps.InputConfig',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'formtools',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'foerderbarometer.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'foerderbarometer.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
'PASSWORD': get_secret('DATABASE_PASSWORD')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/3.1/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/3.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
# needed since django 3.2
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
"""
|
||||||
|
Django settings for foerderbarometer project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.1.1.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
|
# prefix for urls in mails
|
||||||
|
URLPREFIX = 'http://localhost:8000'
|
||||||
|
|
||||||
|
# mails in development go to stdout
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
|
||||||
|
EMAIL_HOST = 'email.wikimedia.de'
|
||||||
|
EMAIL_PORT = '587'
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
EMAIL_HOST_USER = '636ea784dd6ec43'
|
||||||
|
EMAIL_HOST_PASSWORD = 'wsgqp4ZaVRZZEpRJ'
|
||||||
|
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# get secrets
|
||||||
|
with open(os.path.join(BASE_DIR, 'secrets.json')) as secrets_file:
|
||||||
|
secrets = json.load(secrets_file)
|
||||||
|
|
||||||
|
def get_secret(setting, secrets=secrets):
|
||||||
|
"""Get secret setting or fail with ImproperlyConfigured"""
|
||||||
|
try:
|
||||||
|
return secrets[setting]
|
||||||
|
except KeyError:
|
||||||
|
raise ImproperlyConfigured("Set the {} setting".format(setting))
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = '*&7p9#_n$@^%0z49s+7jpy@+j1rw_hqh05knyd6y2*!0)r&b6h'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'input.apps.InputConfig',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'formtools',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'foerderbarometer.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'foerderbarometer.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
'PASSWORD': get_secret('DATABASE_PASSWORD')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/3.1/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/3.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
# needed since django 3.2
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
"""
|
||||||
|
Django settings for foerderbarometer project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.1.1.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
|
# prefix for urls in mails
|
||||||
|
URLPREFIX = 'http://localhost:8000'
|
||||||
|
|
||||||
|
# mails in development go to stdout
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# get secrets
|
||||||
|
with open(os.path.join(BASE_DIR, 'secrets.json')) as secrets_file:
|
||||||
|
secrets = json.load(secrets_file)
|
||||||
|
|
||||||
|
def get_secret(setting, secrets=secrets):
|
||||||
|
"""Get secret setting or fail with ImproperlyConfigured"""
|
||||||
|
try:
|
||||||
|
return secrets[setting]
|
||||||
|
except KeyError:
|
||||||
|
raise ImproperlyConfigured("Set the {} setting".format(setting))
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = '*&7p9#_n$@^%0z49s+7jpy@+j1rw_hqh05knyd6y2*!0)r&b6h'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'input.apps.InputConfig',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'formtools',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'foerderbarometer.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'foerderbarometer.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
|
||||||
|
#
|
||||||
|
# DATABASES = {
|
||||||
|
# 'default': {
|
||||||
|
# 'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
# 'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
# 'PASSWORD': get_secret('DATABASE_PASSWORD')
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
|
'NAME': 'fdb',
|
||||||
|
'USER': 'fdb',
|
||||||
|
'PASSWORD': get_secret('DATABASE_PASSWORD'),
|
||||||
|
'HOST': 'localhost', # Or an IP Address that your database is hosted on
|
||||||
|
# 'PORT': '3306',
|
||||||
|
#optional:
|
||||||
|
'OPTIONS': {
|
||||||
|
'charset' : 'utf8',
|
||||||
|
'use_unicode' : True,
|
||||||
|
'init_command': 'SET '
|
||||||
|
'storage_engine=INNODB,'
|
||||||
|
'character_set_connection=utf8,'
|
||||||
|
'collation_connection=utf8_bin'
|
||||||
|
#'sql_mode=STRICT_TRANS_TABLES,' # see note below
|
||||||
|
#'SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED',
|
||||||
|
},
|
||||||
|
'TEST_CHARSET': 'utf8',
|
||||||
|
'TEST_COLLATION': 'utf8_general_ci',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/3.1/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/3.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
# needed since django 3.2
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
"""
|
||||||
|
Django settings for foerderbarometer project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.1.1.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
|
# mails in development go to stdout
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
|
||||||
|
EMAIL_HOST = 'email.wikimedia.de'
|
||||||
|
EMAIL_PORT = '587'
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
EMAIL_HOST_USER = '636ea784dd6ec43'
|
||||||
|
EMAIL_HOST_PASSWORD = 'wsgqp4ZaVRZZEpRJ'
|
||||||
|
|
||||||
|
# prefix for urls in mails
|
||||||
|
URLPREFIX = 'http://foerderung.wikimedia.de'
|
||||||
|
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# get secrets
|
||||||
|
with open(os.path.join(BASE_DIR, 'secrets.json')) as secrets_file:
|
||||||
|
secrets = json.load(secrets_file)
|
||||||
|
|
||||||
|
def get_secret(setting, secrets=secrets):
|
||||||
|
"""Get secret setting or fail with ImproperlyConfigured"""
|
||||||
|
try:
|
||||||
|
return secrets[setting]
|
||||||
|
except KeyError:
|
||||||
|
raise ImproperlyConfigured("Set the {} setting".format(setting))
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = get_secret('SECRET_KEY')
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'input.apps.InputConfig',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'formtools',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'foerderbarometer.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'foerderbarometer.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
|
'NAME': 'fdbdevel',
|
||||||
|
'USER': 'fdbdevel',
|
||||||
|
'PASSWORD': get_secret('DATABASE_PASSWORD'),
|
||||||
|
'HOST': '10.0.6.7', # Or an IP Address that your database is hosted on
|
||||||
|
# 'PORT': '3306',
|
||||||
|
#optional:
|
||||||
|
'OPTIONS': {
|
||||||
|
'charset' : 'utf8',
|
||||||
|
'use_unicode' : True,
|
||||||
|
'init_command': 'SET '
|
||||||
|
'storage_engine=INNODB,'
|
||||||
|
'character_set_connection=utf8,'
|
||||||
|
'collation_connection=utf8_bin'
|
||||||
|
#'sql_mode=STRICT_TRANS_TABLES,' # see note below
|
||||||
|
#'SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED',
|
||||||
|
},
|
||||||
|
'TEST_CHARSET': 'utf8',
|
||||||
|
'TEST_COLLATION': 'utf8_general_ci',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/3.1/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/3.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
236
input/admin.py
236
input/admin.py
|
|
@ -1,49 +1,14 @@
|
||||||
import csv
|
import csv
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db import models
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
|
||||||
from input.utils.list import reorder_value
|
from .models import Account, Project, HonoraryCertificate, Library, IFG, Travel,\
|
||||||
|
Email, BusinessCard, List, Literature
|
||||||
from .forms import BaseProjectForm
|
|
||||||
from .models import (
|
|
||||||
Account,
|
|
||||||
Project,
|
|
||||||
ProjectCategory,
|
|
||||||
ProjectRequest,
|
|
||||||
ProjectDeclined,
|
|
||||||
WikimediaProject,
|
|
||||||
HonoraryCertificate,
|
|
||||||
Library,
|
|
||||||
ELiterature,
|
|
||||||
Software,
|
|
||||||
IFG,
|
|
||||||
Travel,
|
|
||||||
Email,
|
|
||||||
BusinessCard,
|
|
||||||
List,
|
|
||||||
Literature,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WMDEAdmin(admin.ModelAdmin):
|
|
||||||
|
|
||||||
def get_fields(self, request, obj=None):
|
|
||||||
fields = super().get_fields(request, obj=obj)
|
|
||||||
|
|
||||||
if 'username' in fields:
|
|
||||||
fields = reorder_value(fields, 'username', after='email')
|
|
||||||
|
|
||||||
fields = reorder_value(fields, 'request_url', before='intern_notes')
|
|
||||||
|
|
||||||
if 'terms_accepted' in fields:
|
|
||||||
fields = reorder_value(fields, 'terms_accepted', before='request_url')
|
|
||||||
|
|
||||||
return fields
|
|
||||||
|
|
||||||
|
|
||||||
def export_as_csv(self, request, queryset):
|
def export_as_csv(self, request, queryset):
|
||||||
|
|
||||||
meta = self.model._meta
|
meta = self.model._meta
|
||||||
field_names = [field.name for field in meta.fields]
|
field_names = [field.name for field in meta.fields]
|
||||||
|
|
||||||
|
|
@ -57,160 +22,46 @@ def export_as_csv(self, request, queryset):
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
export_as_csv.short_description = "Ausgewähltes zu CSV exportieren"
|
export_as_csv.short_description = "Ausgewähltes zu CSV exportieren"
|
||||||
|
|
||||||
admin.site.add_action(export_as_csv)
|
admin.site.add_action(export_as_csv)
|
||||||
|
|
||||||
|
@admin.register(Project)
|
||||||
@admin.register(ProjectCategory, WikimediaProject)
|
class ProjectAdmin(admin.ModelAdmin):
|
||||||
class ProjectCategoryAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['name', 'order', 'project_count']
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
return super().get_queryset(request).annotate(
|
|
||||||
project_count=models.Count('projects'),
|
|
||||||
)
|
|
||||||
|
|
||||||
@admin.display(description='# Projekte', ordering='project_count')
|
|
||||||
def project_count(self, obj):
|
|
||||||
return obj.project_count
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectAdminForm(BaseProjectForm):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
for field, model in self.categories.items():
|
|
||||||
if self.initial.get(f'{field}_other'):
|
|
||||||
self.initial[field] = [*self.initial[field], model.other]
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
cleaned_data = BaseProjectForm.clean(self)
|
|
||||||
|
|
||||||
if self.errors:
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
if cleaned_data['granted']:
|
|
||||||
for field in 'granted_date', 'granted_from', 'account':
|
|
||||||
if not cleaned_data[field]:
|
|
||||||
self.add_error(field, 'Dieses Feld ist erforderlich, um dieses Projekt zu bewilligen.')
|
|
||||||
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
|
|
||||||
class BaseProjectAdmin(admin.ModelAdmin):
|
|
||||||
save_as = True
|
save_as = True
|
||||||
form = ProjectAdminForm
|
|
||||||
search_fields = ('name', 'pid','finance_id', 'realname', 'start', 'end', 'participants_estimated', 'participants_real', 'cost', 'status', 'end_quartal')
|
search_fields = ('name', 'pid','finance_id', 'realname', 'start', 'end', 'participants_estimated', 'participants_real', 'cost', 'status', 'end_quartal')
|
||||||
list_display = ('name', 'pid','finance_id', 'realname', 'start', 'end', 'participants_estimated', 'participants_real', 'cost', 'status', 'end_quartal')
|
list_display = ('name', 'pid','finance_id', 'realname', 'start', 'end', 'participants_estimated', 'participants_real', 'cost', 'status', 'end_quartal')
|
||||||
|
fields = ('realname', 'email', 'granted', 'granted_date', 'mail_state', 'end_mail_send', 'survey_mail_send', 'survey_mail_date', 'name', 'description', 'pid', 'finance_id', 'start', 'end', 'otrs', 'plan', 'page', 'urls', 'group', 'location', 'participants_estimated', 'participants_real', 'insurance', 'insurance_technic', 'support', 'cost', 'account', 'granted_from', 'notes', 'intern_notes', 'status', 'project_of_year', 'end_quartal')
|
||||||
|
# action = ['export_as_csv']
|
||||||
date_hierarchy = 'end'
|
date_hierarchy = 'end'
|
||||||
readonly_fields = ('end_quartal', 'project_of_year', 'pid', 'finance_id')
|
readonly_fields = ('end_quartal', 'project_of_year', 'pid', 'finance_id')
|
||||||
fieldsets = [
|
|
||||||
('Kontakt', {'fields': (
|
|
||||||
'realname',
|
|
||||||
'email',
|
|
||||||
'username',
|
|
||||||
)}),
|
|
||||||
('Projekt', {'fields': (
|
|
||||||
'name',
|
|
||||||
'description',
|
|
||||||
'start',
|
|
||||||
'end',
|
|
||||||
'otrs',
|
|
||||||
'plan',
|
|
||||||
'page',
|
|
||||||
'urls',
|
|
||||||
'group',
|
|
||||||
'location',
|
|
||||||
'participants_estimated',
|
|
||||||
'participants_real',
|
|
||||||
'insurance',
|
|
||||||
'insurance_technic',
|
|
||||||
'support',
|
|
||||||
'cost',
|
|
||||||
'categories',
|
|
||||||
'categories_other',
|
|
||||||
'wikimedia_projects',
|
|
||||||
'wikimedia_projects_other',
|
|
||||||
'notes',
|
|
||||||
)}),
|
|
||||||
('Mailing', {'fields': (
|
|
||||||
'mail_state',
|
|
||||||
'end_mail_send',
|
|
||||||
'survey_mail_send',
|
|
||||||
'survey_mail_date',
|
|
||||||
)}),
|
|
||||||
('Bewilligung', {'fields': (
|
|
||||||
'granted',
|
|
||||||
'granted_date',
|
|
||||||
'granted_from',
|
|
||||||
'intern_notes',
|
|
||||||
)}),
|
|
||||||
('Accounting', {'fields': (
|
|
||||||
'account',
|
|
||||||
'status',
|
|
||||||
*readonly_fields,
|
|
||||||
)}),
|
|
||||||
]
|
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/otrs_link.js',)
|
js = ('dropdown/js/otrs_link.js',)
|
||||||
css = {
|
|
||||||
'all': ['css/full-width-related-labels.css'],
|
|
||||||
}
|
|
||||||
|
|
||||||
granted: bool
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
return super().get_queryset(request).filter(granted=self.granted)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Project)
|
|
||||||
class ProjectAdmin(BaseProjectAdmin):
|
|
||||||
granted = True
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ProjectRequest)
|
|
||||||
class ProjectRequestAdmin(BaseProjectAdmin):
|
|
||||||
granted = None
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ProjectDeclined)
|
|
||||||
class ProjectDeclinedAdmin(BaseProjectAdmin):
|
|
||||||
granted = False
|
|
||||||
|
|
||||||
def has_add_permission(self, request):
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(BusinessCard)
|
@admin.register(BusinessCard)
|
||||||
class BusinessCardAdmin(WMDEAdmin):
|
class BusinessCardAdmin(admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date', 'project')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date', 'project')
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'project', 'terms_accepted')
|
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'project')
|
||||||
list_display_links = ('realname', 'service_id')
|
list_display_links = ('realname', 'service_id')
|
||||||
# action = ['export_as_csv']
|
# action = ['export_as_csv']
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
readonly_fields = ['service_id']
|
readonly_fields = ['service_id']
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/base.js', 'dropdown/js/otrs_link.js')
|
js = ('dropdown/js/base.js',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Literature)
|
@admin.register(Literature)
|
||||||
class LiteratureAdmin(WMDEAdmin):
|
class LiteratureAdmin(admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
|
list_display = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display_links = ('realname', 'service_id')
|
list_display_links = ('realname', 'service_id')
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
readonly_fields = ['service_id']
|
readonly_fields = ['service_id']
|
||||||
|
|
||||||
class Media:
|
|
||||||
js = ('dropdown/js/otrs_link.js',)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Account)
|
@admin.register(Account)
|
||||||
class AccountAdmin(admin.ModelAdmin):
|
class AccountAdmin(admin.ModelAdmin):
|
||||||
|
|
@ -221,43 +72,23 @@ class AccountAdmin(admin.ModelAdmin):
|
||||||
class HonoraryCertificateAdmin(admin.ModelAdmin):
|
class HonoraryCertificateAdmin(admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ['realname', 'granted', 'project__name', 'project__pid']
|
search_fields = ['realname', 'granted', 'project__name', 'project__pid']
|
||||||
list_display = ('realname', 'granted', 'project')
|
list_display = ('realname', 'granted','project')
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
autocomplete_fields = ['project']
|
autocomplete_fields = ['project']
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/otrs_link.js',)
|
js = ('dropdown/js/otrs_link.js',)
|
||||||
|
|
||||||
|
@admin.register(Library)
|
||||||
@admin.register(Library, ELiterature, Software)
|
class LibraryAdmin(admin.ModelAdmin):
|
||||||
class LibraryAdmin(WMDEAdmin):
|
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date')
|
list_display = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display_links = ('realname', 'service_id')
|
list_display_links = ('realname', 'service_id')
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
readonly_fields = ['service_id']
|
readonly_fields = ['service_id']
|
||||||
exclude = ['type']
|
|
||||||
|
|
||||||
class Media:
|
|
||||||
js = ('dropdown/js/otrs_link.js',)
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
return super().get_queryset(request).filter(type=self.model.TYPE)
|
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, request, **kwargs):
|
|
||||||
if db_field.name == 'library':
|
|
||||||
kwargs['label'] = self.model.LIBRARY_LABEL
|
|
||||||
kwargs['help_text'] = self.model.LIBRARY_HELP_TEXT
|
|
||||||
|
|
||||||
elif db_field.name == 'duration':
|
|
||||||
kwargs['help_text'] = self.model.DURATION_HELP_TEXT
|
|
||||||
|
|
||||||
return super().formfield_for_dbfield(db_field, request, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(IFG)
|
@admin.register(IFG)
|
||||||
class IFGAdmin(WMDEAdmin):
|
class IFGAdmin(admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date')
|
list_display = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
|
|
@ -265,44 +96,51 @@ class IFGAdmin(WMDEAdmin):
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
readonly_fields = ['service_id']
|
readonly_fields = ['service_id']
|
||||||
|
|
||||||
class Media:
|
|
||||||
js = ('dropdown/js/otrs_link.js',)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Travel)
|
@admin.register(Travel)
|
||||||
class TravelAdmin(WMDEAdmin):
|
class TravelAdmin(admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ['realname', 'service_id', 'granted_date', 'project__name', 'project__pid']
|
search_fields = ['realname', 'service_id', 'granted_date', 'project__name', 'project__pid']
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'project_end', 'project',
|
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'project_end', 'project', 'project_end_quartal')
|
||||||
'project_end_quartal')
|
|
||||||
list_display_links = ('realname', 'project')
|
list_display_links = ('realname', 'project')
|
||||||
date_hierarchy = 'project_end'
|
date_hierarchy = 'project_end'
|
||||||
|
readonly_fields = ('project_end_quartal', 'project_end')
|
||||||
autocomplete_fields = ['project']
|
autocomplete_fields = ['project']
|
||||||
readonly_fields = ['service_id', 'project_end', 'project_end_quartal']
|
readonly_fields = ['service_id']
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/otrs_link.js',)
|
js = ('dropdown/js/otrs_link.js',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Email)
|
@admin.register(Email)
|
||||||
class EmailAdmin(WMDEAdmin):
|
class EmailAdmin(admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
|
list_display = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display_links = ('realname', 'service_id')
|
list_display_links = ('realname', 'service_id')
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
radio_fields = {'adult': admin.VERTICAL}
|
radio_fields = {'adult': admin.VERTICAL}
|
||||||
readonly_fields = ['service_id']
|
readonly_fields = ['service_id']
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/base.js', 'dropdown/js/otrs_link.js')
|
js = ('dropdown/js/base.js',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(List)
|
@admin.register(List)
|
||||||
class ListAdmin(WMDEAdmin):
|
class ListAdmin(admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
|
list_display = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display_links = ('realname', 'service_id')
|
list_display_links = ('realname', 'service_id')
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
readonly_fields = ['service_id']
|
readonly_fields = ['service_id']
|
||||||
|
|
||||||
|
# commented out because of the individual registering to control displays in admin panel
|
||||||
|
|
||||||
|
#admin.site.register([
|
||||||
|
# Account,
|
||||||
|
# HonoraryCertificate,
|
||||||
|
# Library,
|
||||||
|
# IFG,
|
||||||
|
# Travel,
|
||||||
|
# Email,
|
||||||
|
# List,
|
||||||
|
# ])
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
ASGI config for oauth_demo project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oauth_demo.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
396
input/forms.py
396
input/forms.py
|
|
@ -1,198 +1,89 @@
|
||||||
from django import forms
|
from django.db import models
|
||||||
from django.conf import settings
|
from django.forms import ModelForm, DateField, ChoiceField, RadioSelect, BooleanField
|
||||||
from django.contrib.admin.widgets import AdminDateWidget
|
from django.contrib.admin.widgets import AdminDateWidget
|
||||||
from django.forms import ModelForm
|
|
||||||
from django.forms.renderers import DjangoTemplates
|
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
from django.utils.translation import gettext_lazy as trans
|
|
||||||
|
|
||||||
from .models import (
|
from .models import Project, Volunteer, ConcreteVolunteer, Extern, ConcreteExtern, IFG, Library, TYPE_CHOICES,\
|
||||||
Project,
|
HonoraryCertificate, Travel, Email, Literature, List,\
|
||||||
ProjectCategory,
|
BusinessCard
|
||||||
WikimediaProject,
|
from .settings import DATAPROTECTION, FOERDERRICHTLINIEN, NUTZUNGSBEDINGUNGEN
|
||||||
IFG,
|
|
||||||
Library,
|
from . import settings
|
||||||
ELiterature,
|
|
||||||
Software,
|
|
||||||
Travel,
|
|
||||||
Email,
|
|
||||||
Literature,
|
|
||||||
List,
|
|
||||||
BusinessCard,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TableFormRenderer(DjangoTemplates):
|
|
||||||
"""
|
|
||||||
Set in settings as the default form renderer.
|
|
||||||
"""
|
|
||||||
|
|
||||||
form_template_name = 'django/forms/table.html'
|
|
||||||
|
|
||||||
|
|
||||||
class RadioField(forms.ChoiceField):
|
|
||||||
widget = forms.RadioSelect
|
|
||||||
|
|
||||||
|
|
||||||
class BaseApplicationForm(ModelForm):
|
|
||||||
"""
|
|
||||||
Base form for all external applications.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
class FdbForm(ModelForm):
|
||||||
|
'''this base class provides the required css class for all forms'''
|
||||||
required_css_class = 'required'
|
required_css_class = 'required'
|
||||||
|
|
||||||
check = forms.BooleanField(
|
|
||||||
required=True,
|
|
||||||
label=format_html(
|
|
||||||
"""Ich stimme den <a href="{}" target="_blank" rel="noopener">Datenschutzbestimmungen</a> und der<br>
|
|
||||||
<a href="{}" target="_blank" rel="noopener">Richtlinie zur Förderung der Communitys</a> zu.""",
|
|
||||||
settings.DATAPROTECTION,
|
|
||||||
settings.FOERDERRICHTLINIEN
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
class ProjectForm(FdbForm):
|
||||||
|
|
||||||
PROJECT_COST_GT_1000_MESSAGE = format_html(
|
# start = DateField(widget=AdminDateWidget())
|
||||||
"""Bitte beachte, dass für Projektkosten über 1.000 € ein öffentlicher Projektplan erforderlich
|
|
||||||
ist (siehe <a href="{0}" target="blank_">Wikipedia:Förderung/Projektplanung)</a>.""",
|
|
||||||
'https://de.wikipedia.org/wiki/Wikipedia:F%C3%B6rderung/Projektplanung'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseProjectForm(ModelForm):
|
|
||||||
categories = {
|
|
||||||
'categories': ProjectCategory,
|
|
||||||
'wikimedia_projects': WikimediaProject,
|
|
||||||
}
|
|
||||||
|
|
||||||
class Media:
|
|
||||||
js = ('dropdown/js/otrs_link.js', 'js/project-categories.js')
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
cleaned_data = ModelForm.clean(self)
|
|
||||||
|
|
||||||
if self.errors:
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
for field, model in self.categories.items():
|
|
||||||
field_other = f'{field}_other'
|
|
||||||
values = cleaned_data[field]
|
|
||||||
|
|
||||||
if model.other in values:
|
|
||||||
if not cleaned_data[field_other]:
|
|
||||||
self.add_error(field_other, f'Bitte gib einen Wert an oder deselektiere "{model.OTHER}".')
|
|
||||||
else:
|
|
||||||
cleaned_data[field_other] = ''
|
|
||||||
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectForm(BaseProjectForm, BaseApplicationForm):
|
|
||||||
OPTIONAL_FIELDS = {
|
|
||||||
'categories_other',
|
|
||||||
'wikimedia_projects_other',
|
|
||||||
'page',
|
|
||||||
'group',
|
|
||||||
'location',
|
|
||||||
'insurance',
|
|
||||||
'notes',
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
for field in set(self.fields) - self.OPTIONAL_FIELDS:
|
|
||||||
self.fields[field].required = True
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Project
|
model = Project
|
||||||
fields = [
|
exclude = ('pid', 'project_of_year', 'finance_id','granted', 'granted_date', 'realname', 'email',\
|
||||||
'realname',
|
'end_mail_send', 'status', 'persons', 'survey_mail_date', 'mail_state')
|
||||||
'email',
|
widgets = {'start': AdminDateWidget(),
|
||||||
'name',
|
'end': AdminDateWidget(),}
|
||||||
'description',
|
|
||||||
'categories',
|
|
||||||
'categories_other',
|
|
||||||
'wikimedia_projects',
|
|
||||||
'wikimedia_projects_other',
|
|
||||||
'start',
|
|
||||||
'end',
|
|
||||||
'participants_estimated',
|
|
||||||
'page',
|
|
||||||
'group',
|
|
||||||
'location',
|
|
||||||
'cost',
|
|
||||||
'insurance',
|
|
||||||
'notes',
|
|
||||||
]
|
|
||||||
labels = {
|
|
||||||
'cost': 'Kosten in Euro',
|
|
||||||
'insurance': 'Haftpflicht- und Unfallversicherung gewünscht',
|
|
||||||
'participants_estimated': 'Voraussichtliche Zahl der Teilnehmenden',
|
|
||||||
}
|
|
||||||
widgets = {
|
|
||||||
'start': AdminDateWidget,
|
|
||||||
'end': AdminDateWidget,
|
|
||||||
}
|
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
css = {
|
js = ('dropdown/js/otrs_link.js',)
|
||||||
'all': ('css/dateFieldNoNowShortcutInTravels.css',)
|
|
||||||
|
|
||||||
|
class ExternForm(FdbForm):
|
||||||
|
|
||||||
|
choice = ChoiceField(choices=TYPE_CHOICES.items(), widget=RadioSelect,
|
||||||
|
label='Was möchtest Du beantragen?')
|
||||||
|
|
||||||
|
check = BooleanField(required=True,
|
||||||
|
label=format_html("Ich stimme den <a href='{}' target='_blank' rel='noopener'>Datenschutzbestimmungen</a> und der<br> <a href='{}' target='_blank' rel='noopener'>Richtlinie zur Förderung der Communitys</a> zu",
|
||||||
|
DATAPROTECTION, FOERDERRICHTLINIEN))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ConcreteExtern
|
||||||
|
exclude = ('username', 'granted', 'granted_date', 'survey_mail_send', 'service_id', 'survey_mail_date', 'mail_state')
|
||||||
|
|
||||||
|
|
||||||
|
INTERN_CHOICES = {'PRO': 'Projektsteckbrief',
|
||||||
|
'HON': 'Ehrenamtsbescheinigung, Akkreditierung oder Redaktionsbestätigung',
|
||||||
|
'TRAV': 'Reisekostenerstattung'}
|
||||||
|
|
||||||
|
class InternForm(FdbForm):
|
||||||
|
choice = ChoiceField(choices = INTERN_CHOICES.items(), widget=RadioSelect,
|
||||||
|
label = 'Was möchtest Du eingeben?')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ConcreteVolunteer
|
||||||
|
exclude = ('granted', 'granted_date', 'survey_mail_send', 'survey_mail_date', 'mail_state')
|
||||||
|
|
||||||
|
|
||||||
|
HOTEL_CHOICES = {'TRUE': format_html('Hotelzimmer benötigt'),
|
||||||
|
'FALSE': format_html('Kein Hotelzimmer benötigt')
|
||||||
}
|
}
|
||||||
|
|
||||||
def clean_cost(self):
|
|
||||||
cost = self.cleaned_data['cost']
|
|
||||||
|
|
||||||
if cost > 1000:
|
class TravelForm(FdbForm):
|
||||||
raise forms.ValidationError(PROJECT_COST_GT_1000_MESSAGE, code='cost-gt-1000')
|
|
||||||
|
|
||||||
return cost
|
|
||||||
|
|
||||||
|
|
||||||
HOTEL_CHOICES = {
|
|
||||||
'TRUE': mark_safe('Hotelzimmer benötigt'),
|
|
||||||
'FALSE': mark_safe('Kein Hotelzimmer benötigt'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TravelForm(BaseApplicationForm):
|
|
||||||
# TODO: add some javascript to show/hide other-field
|
# TODO: add some javascript to show/hide other-field
|
||||||
|
|
||||||
hotel = RadioField(label='Hotelzimmer benötigt', choices=HOTEL_CHOICES)
|
|
||||||
|
|
||||||
# this is the code, to change required to false if needed
|
# this is the code, to change required to false if needed
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['project_name'].required = True
|
self.fields['project_name'].required = True
|
||||||
self.fields['transport'].required = True
|
self.fields['transport'].required = True
|
||||||
self.fields['travelcost'].required = True
|
self.fields['travelcost'].required = True
|
||||||
self.fields['travelcost'].initial = None
|
|
||||||
self.fields['checkin'].required = True
|
self.fields['checkin'].required = True
|
||||||
self.fields['checkout'].required = True
|
self.fields['checkout'].required = True
|
||||||
self.fields['hotel'].required = True
|
self.fields['hotel'].required = True
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Travel
|
model = Travel
|
||||||
fields = [
|
exclude = ('granted', 'granted_date', 'survey_mail_send', 'realname', 'email', 'survey_mail_date', 'project', 'request_url', 'payed_for_hotel_by', 'payed_for_travel_by', 'intern_notes', 'mail_state' )
|
||||||
'realname',
|
widgets = {'checkin': AdminDateWidget(),
|
||||||
'email',
|
'checkout': AdminDateWidget(),}
|
||||||
'project_name',
|
fields = ['project_name', 'transport', 'travelcost', 'checkin', 'checkout', 'hotel', 'notes']
|
||||||
'transport',
|
hotel = ChoiceField(label='Hotelzimmer benötigt:', choices=HOTEL_CHOICES.items(), widget=RadioSelect())
|
||||||
'travelcost',
|
|
||||||
'checkin',
|
|
||||||
'checkout',
|
|
||||||
'hotel',
|
|
||||||
'notes',
|
|
||||||
]
|
|
||||||
labels = {
|
|
||||||
'checkin': 'Datum der Anreise',
|
|
||||||
'checkout': 'Datum der Abreise',
|
|
||||||
}
|
|
||||||
widgets = {
|
|
||||||
'checkin': AdminDateWidget,
|
|
||||||
'checkout': AdminDateWidget,
|
|
||||||
}
|
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/otrs_link.js',)
|
js = ('dropdown/js/otrs_link.js',)
|
||||||
|
|
@ -200,115 +91,74 @@ class TravelForm(BaseApplicationForm):
|
||||||
'all': ('css/dateFieldNoNowShortcutInTravels.css',)
|
'all': ('css/dateFieldNoNowShortcutInTravels.css',)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LibraryForm(FdbForm):
|
||||||
class LibraryForm(BaseApplicationForm):
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Library
|
model = Library
|
||||||
fields = [
|
fields = ['cost', 'library', 'duration', 'notes', 'survey_mail_send']
|
||||||
'realname',
|
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
||||||
'email',
|
|
||||||
'cost',
|
|
||||||
'library',
|
|
||||||
'duration',
|
|
||||||
'notes',
|
|
||||||
]
|
|
||||||
labels = {
|
|
||||||
'cost': 'Kosten in Euro',
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
class HonoraryCertificateForm(FdbForm):
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
self.fields['library'].label = self._meta.model.LIBRARY_LABEL
|
class Meta:
|
||||||
self.fields['library'].help_text = self._meta.model.LIBRARY_HELP_TEXT
|
model = HonoraryCertificate
|
||||||
self.fields['duration'].help_text = self._meta.model.DURATION_HELP_TEXT
|
fields = ['request_url', 'project']
|
||||||
|
exclude = ['intern_notes']
|
||||||
|
class Media:
|
||||||
|
js = ('dropdown/js/otrs_link.js',)
|
||||||
|
|
||||||
|
|
||||||
class ELiteratureForm(LibraryForm):
|
class IFGForm(FdbForm):
|
||||||
|
|
||||||
class Meta(LibraryForm.Meta):
|
|
||||||
model = ELiterature
|
|
||||||
|
|
||||||
|
|
||||||
class SoftwareForm(LibraryForm):
|
|
||||||
|
|
||||||
class Meta(LibraryForm.Meta):
|
|
||||||
model = Software
|
|
||||||
|
|
||||||
|
|
||||||
class IFGForm(BaseApplicationForm):
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IFG
|
model = IFG
|
||||||
fields = [
|
fields = ['cost', 'url', 'notes']
|
||||||
'realname',
|
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
||||||
'email',
|
|
||||||
'cost',
|
|
||||||
'url',
|
|
||||||
'notes',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class TermsForm(BaseApplicationForm):
|
class CheckForm(FdbForm):
|
||||||
terms_accepted_label = 'Ich stimme den <a href="{}">Nutzungsbedingungen</a> zu.'
|
termstoaccept = NUTZUNGSBEDINGUNGEN
|
||||||
terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['check'] = BooleanField(
|
||||||
self.fields['terms_accepted'].required = True
|
required=True,
|
||||||
self.fields['terms_accepted'].label = format_html(self.terms_accepted_label, self.terms_accepted_url)
|
label=format_html(
|
||||||
|
"Ich stimme den <a href='{}'>Nutzungsbedingungen</a> zu",
|
||||||
|
self.termstoaccept
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LiteratureForm(TermsForm):
|
|
||||||
terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_LITERATURSTIPENDIUM
|
|
||||||
|
|
||||||
|
"""Baseclass for all classes which need a check for Nutzungsbedingungen"""
|
||||||
|
# def __init__(self, *args, **kwargs):
|
||||||
|
# check = BooleanField(required=True,
|
||||||
|
# label=format_html("Ich stimme den <a href='{}'>Nutzungsbedingungen</a> zu",
|
||||||
|
# termstoaccept))
|
||||||
|
# NUTZUNGSBEDINGUNGEN))
|
||||||
|
|
||||||
|
|
||||||
|
class LiteratureForm(CheckForm):
|
||||||
|
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_LITERATURSTIPENDIUM
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['selfbuy_give_data'].required = True
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Literature
|
model = Literature
|
||||||
fields = [
|
fields = ['cost', 'info', 'source', 'notes', 'selfbuy', 'selfbuy_data', 'selfbuy_give_data']
|
||||||
'realname',
|
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
||||||
'email',
|
|
||||||
'cost',
|
|
||||||
'info',
|
|
||||||
'source',
|
|
||||||
'notes',
|
|
||||||
'selfbuy',
|
|
||||||
'selfbuy_data',
|
|
||||||
'selfbuy_give_data',
|
|
||||||
'terms_accepted',
|
|
||||||
]
|
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/literature.js',)
|
js = ('dropdown/js/literature.js',)
|
||||||
|
|
||||||
def clean(self):
|
ADULT_CHOICES = {'TRUE': format_html('Ich bin volljährig.'),
|
||||||
cleaned_data = TermsForm.clean(self)
|
'FALSE': format_html('Ich bin noch nicht volljährig.')
|
||||||
|
}
|
||||||
if self.errors:
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
if cleaned_data['selfbuy'] == 'TRUE':
|
|
||||||
cleaned_data['selfbuy_data'] = ''
|
|
||||||
cleaned_data['selfbuy_give_data'] = False
|
|
||||||
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
for field in 'selfbuy_data', 'selfbuy_give_data':
|
|
||||||
if not cleaned_data.get(field):
|
|
||||||
self.add_error(field, trans('This field is required.'))
|
|
||||||
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
|
|
||||||
ADULT_CHOICES = {
|
class EmailForm(CheckForm):
|
||||||
'TRUE': mark_safe('Ich bin volljährig.'),
|
|
||||||
'FALSE': mark_safe('Ich bin noch nicht volljährig.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_EMAIL_SERVICE
|
||||||
class EmailForm(TermsForm):
|
|
||||||
terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_EMAIL_SERVICE
|
|
||||||
|
|
||||||
# this is the code, to change required to false if needed
|
# this is the code, to change required to false if needed
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
@ -316,28 +166,22 @@ class EmailForm(TermsForm):
|
||||||
self.fields['adult'].required = True
|
self.fields['adult'].required = True
|
||||||
self.fields['other'].required = True
|
self.fields['other'].required = True
|
||||||
|
|
||||||
adult = RadioField(label='Volljährigkeit', choices=ADULT_CHOICES)
|
adult = ChoiceField(label='Volljährigkeit', choices=ADULT_CHOICES.items(), widget=RadioSelect())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: add some javascript to show/hide other-field
|
# TODO: add some javascript to show/hide other-field
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Email
|
model = Email
|
||||||
fields = [
|
fields = ['domain', 'address', 'other', 'adult']
|
||||||
'realname',
|
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
||||||
'email',
|
|
||||||
'domain',
|
|
||||||
'address',
|
|
||||||
'other',
|
|
||||||
'adult',
|
|
||||||
'terms_accepted',
|
|
||||||
]
|
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/mail.js',)
|
js = ('dropdown/js/mail.js',)
|
||||||
|
|
||||||
|
|
||||||
class BusinessCardForm(TermsForm):
|
|
||||||
terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_VISITENKARTEN
|
|
||||||
|
|
||||||
|
class BusinessCardForm(CheckForm):
|
||||||
|
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_VISITENKARTEN
|
||||||
# this is the code, to change required to false if needed
|
# this is the code, to change required to false if needed
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
@ -346,36 +190,16 @@ class BusinessCardForm(TermsForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = BusinessCard
|
model = BusinessCard
|
||||||
fields = [
|
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
||||||
'realname',
|
fields = ['project', 'data', 'variant', 'url_of_pic', 'send_data_to_print', 'sent_to']
|
||||||
'email',
|
|
||||||
'project',
|
|
||||||
'data',
|
|
||||||
'variant',
|
|
||||||
'url_of_pic',
|
|
||||||
'send_data_to_print',
|
|
||||||
'sent_to',
|
|
||||||
'terms_accepted',
|
|
||||||
]
|
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/businessCard.js',)
|
js = ('dropdown/js/businessCard.js',)
|
||||||
|
|
||||||
|
|
||||||
class ListForm(TermsForm):
|
class ListForm(CheckForm):
|
||||||
terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_MAILINGLISTEN
|
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_MAILINGLISTEN
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = List
|
model = List
|
||||||
fields = [
|
fields = ['domain', 'address']
|
||||||
'realname',
|
exclude = ['intern_notes', 'survey_mail_send','mail_state']
|
||||||
'email',
|
|
||||||
'domain',
|
|
||||||
'address',
|
|
||||||
'terms_accepted',
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
self.fields['address'].initial = ''
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
|
import sys
|
||||||
|
|
||||||
from django.core.management import CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.core.management.base import BaseCommand
|
from django.template.loader import get_template
|
||||||
from django.core.mail import BadHeaderError
|
from django.core.mail import send_mail, BadHeaderError, EmailMessage
|
||||||
|
from django.core.mail import EmailMultiAlternatives
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from input.models import Project, Library, HonoraryCertificate, Travel, Email,\
|
from input.models import Project, Library, HonoraryCertificate, Travel, Email,\
|
||||||
BusinessCard, List, IFG, Literature
|
BusinessCard, List, IFG, Literature
|
||||||
from input.utils.mail import send_email
|
from input.settings import IF_EMAIL, SURVEYPREFIX, SURVEY_EMAIL
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
''' mails will be sent here:
|
''' mails will be send here:
|
||||||
|
|
||||||
- two weeks after confirmation of support for volunteer (/extern) send link
|
- two weeks after confirmation of support for volunteer (/extern) send link
|
||||||
with surveylink
|
with surveylink
|
||||||
|
|
@ -33,12 +34,16 @@ class Command(BaseCommand):
|
||||||
'type': type,
|
'type': type,
|
||||||
'name': name,
|
'name': name,
|
||||||
'pid': pid,
|
'pid': pid,
|
||||||
'SURVEY_PREFIX': settings.SURVEY_PREFIX, }
|
'SURVEYPREFIX': SURVEYPREFIX, }
|
||||||
|
txt_mail_template = get_template('input/survey_mail.txt')
|
||||||
subject = 'Dein Feedback zur Förderung durch Wikimedia Deutschland'
|
html_mail_template = get_template('input/survey_mail.html')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
send_email('survey_mail', context, subject, email, bcc=[settings.SURVEY_EMAIL])
|
subject, from_email, to = 'Dein Feedback zur Förderung durch Wikimedia Deutschland', IF_EMAIL, email
|
||||||
|
text_content = txt_mail_template.render(context)
|
||||||
|
html_content = html_mail_template.render(context)
|
||||||
|
msg = EmailMultiAlternatives(subject, text_content, from_email, [to], bcc=[SURVEY_EMAIL])
|
||||||
|
msg.attach_alternative(html_content, "text/html")
|
||||||
|
msg.send()
|
||||||
#print('survey mail would have been send')
|
#print('survey mail would have been send')
|
||||||
|
|
||||||
#survey_mail = EmailMessage('Dein Feedback zur Förderung durch Wikimedia Deutschland',
|
#survey_mail = EmailMessage('Dein Feedback zur Förderung durch Wikimedia Deutschland',
|
||||||
|
|
@ -47,8 +52,8 @@ class Command(BaseCommand):
|
||||||
# [email],
|
# [email],
|
||||||
# bcc=[SURVEY_EMAIL])
|
# bcc=[SURVEY_EMAIL])
|
||||||
#survey_mail.send(fail_silently=False)
|
#survey_mail.send(fail_silently=False)
|
||||||
except BadHeaderError as error:
|
except BadHeaderError:
|
||||||
raise CommandError(f'Invalid header found: {error}')
|
return HttpResponse('Invalid header found.')
|
||||||
|
|
||||||
print(f'send surveylinkemail to {email}...')
|
print(f'send surveylinkemail to {email}...')
|
||||||
|
|
||||||
|
|
@ -66,14 +71,21 @@ class Command(BaseCommand):
|
||||||
.exclude(end_mail_send = True)\
|
.exclude(end_mail_send = True)\
|
||||||
.filter(mail_state = 'NONE')
|
.filter(mail_state = 'NONE')
|
||||||
|
|
||||||
subject = 'Projektende erreicht'
|
txt_mail_template = get_template('input/if_end_of_project.txt')
|
||||||
recipient = settings.IF_EMAIL
|
html_mail_template = get_template('input/if_end_of_project.html')
|
||||||
|
|
||||||
|
|
||||||
for project in old:
|
for project in old:
|
||||||
context = {'project': project, 'URL_PREFIX': settings.EMAIL_URL_PREFIX}
|
context = {'project': project}
|
||||||
|
context['URLPREFIX'] = settings.URLPREFIX
|
||||||
|
|
||||||
try:
|
try:
|
||||||
send_email('if_end_of_project', context, subject, recipient)
|
subject, from_email, to = 'Projektende erreicht', IF_EMAIL, IF_EMAIL
|
||||||
|
text_content = txt_mail_template.render(context)
|
||||||
|
html_content = html_mail_template.render(context)
|
||||||
|
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
|
||||||
|
msg.attach_alternative(html_content, "text/html")
|
||||||
|
msg.send()
|
||||||
#print('end of project mail would have been sent')
|
#print('end of project mail would have been sent')
|
||||||
|
|
||||||
#send_mail('Projektende erreicht',
|
#send_mail('Projektende erreicht',
|
||||||
|
|
@ -99,19 +111,33 @@ class Command(BaseCommand):
|
||||||
approved_end = Project.objects.filter(status = 'END')\
|
approved_end = Project.objects.filter(status = 'END')\
|
||||||
.exclude(end_mail_send = True)\
|
.exclude(end_mail_send = True)\
|
||||||
.filter(mail_state = 'INF')
|
.filter(mail_state = 'INF')
|
||||||
|
txt_mail_template = get_template('input/if_end_of_project_approved.txt')
|
||||||
|
html_mail_template = get_template('input/if_end_of_project_approved.html')
|
||||||
|
|
||||||
|
txt_informMail_template = get_template('input/if_end_of_project_orginformed.txt')
|
||||||
|
html_informMail_template = get_template('input/if_end_of_project_orginformed.html')
|
||||||
# send the mail to project.email, which would be the mail of the volunteer filling out the form
|
# send the mail to project.email, which would be the mail of the volunteer filling out the form
|
||||||
|
|
||||||
for project in approved_end:
|
for project in approved_end:
|
||||||
context = {'project': project, 'URL_PREFIX': settings.EMAIL_URL_PREFIX}
|
context = {'project': project}
|
||||||
|
context['URLPREFIX'] = settings.URLPREFIX
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
send_email('if_end_of_project_approved', context, 'Projektende erreicht', project.email)
|
subject, from_email, to = 'Projektende erreicht', IF_EMAIL, project.email
|
||||||
|
text_content = txt_mail_template.render(context)
|
||||||
|
html_content = html_mail_template.render(context)
|
||||||
|
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
|
||||||
|
msg.attach_alternative(html_content, "text/html")
|
||||||
|
msg.send()
|
||||||
#print('if and of project approved mail would have been sent')
|
#print('if and of project approved mail would have been sent')
|
||||||
|
|
||||||
send_email('if_end_of_project_orginformed', context, 'Projektorganisator*in wurde informiert', settings.IF_EMAIL)
|
inform_subject, inform_from_email, inform_to = 'Projektorganisator*in wurde informiert', IF_EMAIL, IF_EMAIL
|
||||||
|
inform_text_content = txt_informMail_template.render(context)
|
||||||
|
inform_html_content = html_informMail_template.render(context)
|
||||||
|
inform_msg = EmailMultiAlternatives(inform_subject, inform_text_content, inform_from_email, [inform_to])
|
||||||
|
inform_msg.attach_alternative(html_content, "text/html")
|
||||||
|
inform_msg.send()
|
||||||
#print('if end of project orginformed mail would have been sent')
|
#print('if end of project orginformed mail would have been sent')
|
||||||
|
|
||||||
#send_mail('Projektende erreicht',
|
#send_mail('Projektende erreicht',
|
||||||
|
|
@ -143,24 +169,38 @@ class Command(BaseCommand):
|
||||||
.exclude(end_mail_send = True)\
|
.exclude(end_mail_send = True)\
|
||||||
.filter(mail_state = 'INF')
|
.filter(mail_state = 'INF')
|
||||||
|
|
||||||
|
html_mail_template = get_template('input/if_not_of_project_approved.html')
|
||||||
|
txt_mail_template = get_template('input/if_not_of_project_approved.txt')
|
||||||
|
|
||||||
|
txt_informMail_template = get_template('input/if_end_of_project_orginformed.txt')
|
||||||
|
html_informMail_template = get_template('input/if_end_of_project_orginformed.html')
|
||||||
# send the mail to project.email, which would be the mail of the volunteer that filled out the form
|
# send the mail to project.email, which would be the mail of the volunteer that filled out the form
|
||||||
|
|
||||||
for project in approved_notHappened:
|
for project in approved_notHappened:
|
||||||
context = {'project': project, 'URL_PREFIX': settings.EMAIL_URL_PREFIX}
|
context = {'project': project}
|
||||||
|
context['URLPREFIX'] = settings.URLPREFIX
|
||||||
try:
|
try:
|
||||||
send_email('if_not_of_project_approved', context, 'Projektende erreicht', project.email)
|
subject, from_email, to = 'Projektende erreicht', IF_EMAIL, project.email
|
||||||
|
text_content = txt_mail_template.render(context)
|
||||||
|
html_content = html_mail_template.render(context)
|
||||||
|
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
|
||||||
|
msg.attach_alternative(html_content, "text/html")
|
||||||
|
msg.send()
|
||||||
#print('if not of project approved end mail would have been sent')
|
#print('if not of project approved end mail would have been sent')
|
||||||
|
|
||||||
|
|
||||||
#send_mail('Projektende erreicht',
|
#send_mail('Projektende erreicht',
|
||||||
# mail_template.render(context),
|
# mail_template.render(context),
|
||||||
# IF_EMAIL,
|
# IF_EMAIL,
|
||||||
# [project.email],
|
# [project.email],
|
||||||
# fail_silently=False)
|
# fail_silently=False)
|
||||||
|
|
||||||
send_email('if_end_of_project_orginformed', context, 'Projektorganisator*in wurde informiert', settings.IF_EMAIL)
|
inform_subject, inform_from_email, inform_to = 'Projektorganisator*in wurde informiert', IF_EMAIL, IF_EMAIL
|
||||||
|
inform_text_content = txt_informMail_template.render(context)
|
||||||
|
inform_html_content = html_informMail_template.render(context)
|
||||||
|
inform_msg = EmailMultiAlternatives(inform_subject, inform_text_content, inform_from_email, [inform_to])
|
||||||
|
inform_msg.attach_alternative(html_content, "text/html")
|
||||||
|
inform_msg.send()
|
||||||
#print('if not of project approved end mail orginformed would have been sent')
|
#print('if not of project approved end mail orginformed would have been sent')
|
||||||
|
|
||||||
#send_mail('Projektorganisator*in wurde informiert',
|
#send_mail('Projektorganisator*in wurde informiert',
|
||||||
|
|
@ -257,15 +297,16 @@ class Command(BaseCommand):
|
||||||
'''send survey link 2 weeks after mailadresss, mailinglist or businesscards are granted'''
|
'''send survey link 2 weeks after mailadresss, mailinglist or businesscards are granted'''
|
||||||
lastdate = date.today() - timedelta(days=14)
|
lastdate = date.today() - timedelta(days=14)
|
||||||
|
|
||||||
models = Email, BusinessCard, List
|
typefield = ('MAIL','VIS','LIST')
|
||||||
types = 'MAIL', 'VIS', 'LIST'
|
count = 0
|
||||||
|
for c in ('Email', 'BusinessCard', 'List'):
|
||||||
for model, typ in zip(models, types):
|
# get class via string
|
||||||
supported = model.objects.filter(granted=True)\
|
supported = getattr(sys.modules[__name__], c).objects.filter(granted=True)\
|
||||||
.filter(granted_date__lt = lastdate)\
|
.filter(granted_date__lt = lastdate)\
|
||||||
.exclude(survey_mail_send=True)\
|
.exclude(survey_mail_send=True)\
|
||||||
.exclude(mail_state = 'END')
|
.exclude(mail_state = 'END')
|
||||||
self.surveymails_to_object(supported, type=typ)
|
self.surveymails_to_object(supported, type=typefield[count])
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import datetime
|
|
||||||
|
|
||||||
from django.core.management import call_command
|
|
||||||
from django.test import TestCase
|
|
||||||
from django.utils.timezone import localdate
|
|
||||||
|
|
||||||
from input.models import Project, Account, Email, Library, HonoraryCertificate
|
|
||||||
|
|
||||||
|
|
||||||
class ManagementCommandTestCase(TestCase):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
cls.account = Account.objects.create(code='test')
|
|
||||||
|
|
||||||
def test_sendmails(self):
|
|
||||||
today = localdate(None)
|
|
||||||
start = today - datetime.timedelta(days=2)
|
|
||||||
end = today - datetime.timedelta(days=1)
|
|
||||||
granted = today - datetime.timedelta(days=15)
|
|
||||||
|
|
||||||
def create_project(name, **kwargs):
|
|
||||||
kwargs.setdefault('account', self.account)
|
|
||||||
kwargs.setdefault('start', start)
|
|
||||||
kwargs.setdefault('end', end)
|
|
||||||
|
|
||||||
return Project.objects.create(name=name, **kwargs)
|
|
||||||
|
|
||||||
create_project('Test end_of_projects_reached')
|
|
||||||
create_project('Test end_of_projects_approved', status='END', mail_state='INF')
|
|
||||||
create_project('Test notHappened_of_projects_approved', status='NOT', mail_state='INF')
|
|
||||||
|
|
||||||
Email.objects.create(
|
|
||||||
domain='SOURCE',
|
|
||||||
address='cosmocode',
|
|
||||||
adult='TRUE',
|
|
||||||
granted=True,
|
|
||||||
granted_date=granted,
|
|
||||||
)
|
|
||||||
|
|
||||||
Library.objects.create(
|
|
||||||
type='BIB',
|
|
||||||
library='Test',
|
|
||||||
duration='1 Jahr',
|
|
||||||
cost=100,
|
|
||||||
granted=True,
|
|
||||||
granted_date=granted,
|
|
||||||
)
|
|
||||||
|
|
||||||
HonoraryCertificate.objects.create(
|
|
||||||
request_url='https://example.com',
|
|
||||||
granted=True,
|
|
||||||
granted_date=granted,
|
|
||||||
project=create_project('Test surveymails_to_hon'),
|
|
||||||
)
|
|
||||||
|
|
||||||
call_command('sendmails')
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
from authlib.integrations.base_client import OAuthError
|
from authlib.integrations.base_client import OAuthError
|
||||||
from authlib.integrations.django_client import OAuth
|
from authlib.integrations.django_client import OAuth
|
||||||
from authlib.oauth2.rfc6749 import OAuth2Token
|
from authlib.oauth2.rfc6749 import OAuth2Token
|
||||||
|
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils.deprecation import MiddlewareMixin
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
from django.conf import settings
|
from foerderbarometer import settings
|
||||||
|
from input import views
|
||||||
from input.models import Extern
|
from input import models
|
||||||
|
|
||||||
|
|
||||||
class OAuthMiddleware(MiddlewareMixin):
|
class OAuthMiddleware(MiddlewareMixin):
|
||||||
|
|
||||||
|
|
@ -16,7 +14,7 @@ class OAuthMiddleware(MiddlewareMixin):
|
||||||
self.oauth = OAuth()
|
self.oauth = OAuth()
|
||||||
|
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
# added this if-clause to get the landing page before oauth
|
# added this if clause to get the landing page before oauth
|
||||||
if request.path == '/':
|
if request.path == '/':
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
if settings.OAUTH_URL_WHITELISTS is not None:
|
if settings.OAUTH_URL_WHITELISTS is not None:
|
||||||
|
|
@ -39,12 +37,12 @@ class OAuthMiddleware(MiddlewareMixin):
|
||||||
self.clear_session(request)
|
self.clear_session(request)
|
||||||
request.session['token'] = sso_client.authorize_access_token(request)
|
request.session['token'] = sso_client.authorize_access_token(request)
|
||||||
# print('blub', request.session['token'])
|
# print('blub', request.session['token'])
|
||||||
Extern.username = self.get_current_user(sso_client, request)['username']
|
models.Extern.username = self.get_current_user(sso_client, request)['username']
|
||||||
if self.get_current_user(sso_client, request) is not None:
|
if self.get_current_user(sso_client, request) is not None:
|
||||||
redirect_uri = request.session.pop('redirect_uri', None)
|
redirect_uri = request.session.pop('redirect_uri', None)
|
||||||
if redirect_uri is not None:
|
if redirect_uri is not None:
|
||||||
return redirect(redirect_uri)
|
return redirect(redirect_uri)
|
||||||
return redirect('extern')
|
return redirect(views.ExternView)
|
||||||
|
|
||||||
if request.session.get('token', None) is not None:
|
if request.session.get('token', None) is not None:
|
||||||
current_user = self.get_current_user(sso_client, request)
|
current_user = self.get_current_user(sso_client, request)
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class Migration(migrations.Migration):
|
||||||
('survey_mail_send', models.BooleanField(null=True)),
|
('survey_mail_send', models.BooleanField(null=True)),
|
||||||
('username', models.CharField(max_length=200, null=True)),
|
('username', models.CharField(max_length=200, null=True)),
|
||||||
('domain', models.CharField(choices=[('PEDIA', '@wikipedia.de'), ('BOOKS', '@wikibooks.de'), ('QUOTE', '@wikiquote.de'), ('SOURCE', '@wikisource.de'), ('VERSITY', '@wikiversity.de')], default='PEDIA', max_length=10)),
|
('domain', models.CharField(choices=[('PEDIA', '@wikipedia.de'), ('BOOKS', '@wikibooks.de'), ('QUOTE', '@wikiquote.de'), ('SOURCE', '@wikisource.de'), ('VERSITY', '@wikiversity.de')], default='PEDIA', max_length=10)),
|
||||||
('adress', models.CharField(choices=[('REALNAME', 'Vorname.Nachname'), ('USERNAME', 'Username'), ('OTHER', 'Sonstiges')], default='USERNAME', max_length=50)),
|
('adress', models.CharField(choices=[('REALNAME', 'Vorname.Nachname'), ('USERNAME', 'Username'), ('OTHER', 'Sonstiges:')], default='USERNAME', max_length=50)),
|
||||||
('other', models.CharField(blank=True, max_length=50, null=True)),
|
('other', models.CharField(blank=True, max_length=50, null=True)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='email',
|
model_name='email',
|
||||||
name='address',
|
name='address',
|
||||||
field=models.CharField(choices=[('REALNAME', 'Vorname.Nachname'), ('USERNAME', 'Username'), ('OTHER', 'Sonstiges')], default='USERNAME', max_length=50, verbose_name='Adressbestandteil'),
|
field=models.CharField(choices=[('REALNAME', 'Vorname.Nachname'), ('USERNAME', 'Username'), ('OTHER', 'Sonstiges:')], default='USERNAME', max_length=50, verbose_name='Adressbestandteil'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='list',
|
model_name='list',
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='email',
|
model_name='email',
|
||||||
name='address',
|
name='address',
|
||||||
field=models.CharField(choices=[('REALNAME', 'Vorname.Nachname'), ('USERNAME', 'Username'), ('OTHER', 'Sonstiges')], default='USERNAME', help_text='Bitte gib hier den gewünschten Adressbestandteil an,<br>der sich vor der Domain befinden soll.', max_length=50, verbose_name='Adressbestandteil'),
|
field=models.CharField(choices=[('REALNAME', 'Vorname.Nachname'), ('USERNAME', 'Username'), ('OTHER', 'Sonstiges:')], default='USERNAME', help_text='Bitte gib hier den gewünschten Adressbestandteil an,<br>der sich vor der Domain befinden soll.', max_length=50, verbose_name='Adressbestandteil'),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='email',
|
model_name='email',
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,12 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='ifg',
|
model_name='ifg',
|
||||||
name='notes',
|
name='notes',
|
||||||
field=models.TextField(blank=True, help_text='Bitte gib an, wofür Du das Stipendium verwenden willst.', max_length=1000, verbose_name='Anmerkungen'),
|
field=models.TextField(blank=True, help_text='Bitte gib an wofür Du das Stipendium verwenden willst.', max_length=1000, verbose_name='Anmerkungen'),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='library',
|
model_name='library',
|
||||||
name='notes',
|
name='notes',
|
||||||
field=models.TextField(blank=True, help_text='Bitte gib an, wofür Du das Stipendium verwenden willst.', max_length=1000, verbose_name='Anmerkungen'),
|
field=models.TextField(blank=True, help_text='Bitte gib an wofür Du das Stipendium verwenden willst.', max_length=1000, verbose_name='Anmerkungen'),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='library',
|
model_name='library',
|
||||||
|
|
@ -33,7 +33,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='literature',
|
model_name='literature',
|
||||||
name='notes',
|
name='notes',
|
||||||
field=models.TextField(blank=True, help_text='Bitte gib an, wofür Du das Stipendium verwenden willst.', max_length=1000, verbose_name='Anmerkungen'),
|
field=models.TextField(blank=True, help_text='Bitte gib an wofür Du das Stipendium verwenden willst.', max_length=1000, verbose_name='Anmerkungen'),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='project',
|
model_name='project',
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='travel',
|
model_name='travel',
|
||||||
name='project_name',
|
name='project_name',
|
||||||
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Projektname'),
|
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Projektname:'),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='library',
|
model_name='library',
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,6 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='travel',
|
model_name='travel',
|
||||||
name='hotel',
|
name='hotel',
|
||||||
field=models.BooleanField(default=False, verbose_name='Hotelzimmer benötigt'),
|
field=models.BooleanField(default=False, verbose_name='Hotelzimmer benötigt:'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,6 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='travel',
|
model_name='travel',
|
||||||
name='transport',
|
name='transport',
|
||||||
field=models.CharField(choices=[('BAHN', 'Bahn'), ('NONE', 'Keine Fahrtkosten'), ('OTHER', 'Sonstiges (mit Begründung)')], default='BAHN', max_length=5, verbose_name='Transportmittel'),
|
field=models.CharField(choices=[('BAHN', 'Bahn'), ('NONE', 'Keine Fahrtkosten'), ('OTHER', 'Sonstiges (mit Begründung)')], default='BAHN', max_length=5, verbose_name='Transportmittel:'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,6 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='travel',
|
model_name='travel',
|
||||||
name='hotel',
|
name='hotel',
|
||||||
field=models.CharField(choices=[('TRUE', 'Hotelzimmer benötigt'), ('FALSE', 'Kein Hotelzimmer benötigt')], max_length=10, verbose_name='Hotelzimmer benötigt'),
|
field=models.CharField(choices=[('TRUE', 'Hotelzimmer benötigt'), ('FALSE', 'Kein Hotelzimmer benötigt')], max_length=10, verbose_name='Hotelzimmer benötigt:'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
# Generated by Django 5.2.5 on 2025-08-20 09:13
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('input', '0096_auto_20230106_1338'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='businesscard',
|
|
||||||
name='realname',
|
|
||||||
field=models.CharField(default='', help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', max_length=200, null=True, verbose_name='Realname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='businesscard',
|
|
||||||
name='username',
|
|
||||||
field=models.CharField(help_text='Wikimedia Benutzer_innenname', max_length=200, null=True, verbose_name='Benutzer_innenname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='concreteextern',
|
|
||||||
name='realname',
|
|
||||||
field=models.CharField(default='', help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', max_length=200, null=True, verbose_name='Realname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='concreteextern',
|
|
||||||
name='username',
|
|
||||||
field=models.CharField(help_text='Wikimedia Benutzer_innenname', max_length=200, null=True, verbose_name='Benutzer_innenname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='concretevolunteer',
|
|
||||||
name='realname',
|
|
||||||
field=models.CharField(default='', help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', max_length=200, null=True, verbose_name='Realname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='email',
|
|
||||||
name='realname',
|
|
||||||
field=models.CharField(default='', help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', max_length=200, null=True, verbose_name='Realname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='email',
|
|
||||||
name='username',
|
|
||||||
field=models.CharField(help_text='Wikimedia Benutzer_innenname', max_length=200, null=True, verbose_name='Benutzer_innenname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='honorarycertificate',
|
|
||||||
name='realname',
|
|
||||||
field=models.CharField(default='', help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', max_length=200, null=True, verbose_name='Realname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='ifg',
|
|
||||||
name='realname',
|
|
||||||
field=models.CharField(default='', help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', max_length=200, null=True, verbose_name='Realname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='ifg',
|
|
||||||
name='username',
|
|
||||||
field=models.CharField(help_text='Wikimedia Benutzer_innenname', max_length=200, null=True, verbose_name='Benutzer_innenname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='library',
|
|
||||||
name='realname',
|
|
||||||
field=models.CharField(default='', help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', max_length=200, null=True, verbose_name='Realname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='library',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('BIB', '<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Zugang_zu_Fachliteratur#Bibliotheksstipendium" target="_blank" rel="noopener">Bibliotheksstipendium</a>'), ('ELIT', '<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Zugang_zu_Fachliteratur#eLiteraturstipendium" target="_blank" rel="noopener">eLiteraturstipendium</a>'), ('MAIL', '<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/E-Mail-Adressen_und_Visitenkarten#E-Mail-Adressen" target="_blank" rel="noopener">E-Mail-Adresse</a>'), ('IFG', '<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Gebührenerstattungen_für_Behördenanfragen" target="_blank" rel="noopener">Kostenübernahme IFG-Anfrage</a>'), ('LIT', '<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Zugang_zu_Fachliteratur#Literaturstipendium" target="_blank" rel="noopener">Literaturstipendium</a>'), ('LIST', '<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/E-Mail-Adressen_und_Visitenkarten#Mailinglisten" target="_blank" rel="noopener">Mailingliste</a>'), ('TRAV', '<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Reisekostenerstattungen" target="_blank" rel="noopener">Reisekosten</a>'), ('SOFT', '<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Software-Stipendien" target="_blank" rel="noopener">Softwarestipendium</a>'), ('VIS', '<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/E-Mail-Adressen_und_Visitenkarten#Visitenkarten" target="_blank" rel="noopener">Visitenkarten</a>')], default='BIB', max_length=4),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='library',
|
|
||||||
name='username',
|
|
||||||
field=models.CharField(help_text='Wikimedia Benutzer_innenname', max_length=200, null=True, verbose_name='Benutzer_innenname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='list',
|
|
||||||
name='realname',
|
|
||||||
field=models.CharField(default='', help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', max_length=200, null=True, verbose_name='Realname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='list',
|
|
||||||
name='username',
|
|
||||||
field=models.CharField(help_text='Wikimedia Benutzer_innenname', max_length=200, null=True, verbose_name='Benutzer_innenname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='literature',
|
|
||||||
name='realname',
|
|
||||||
field=models.CharField(default='', help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', max_length=200, null=True, verbose_name='Realname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='literature',
|
|
||||||
name='username',
|
|
||||||
field=models.CharField(help_text='Wikimedia Benutzer_innenname', max_length=200, null=True, verbose_name='Benutzer_innenname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='project',
|
|
||||||
name='realname',
|
|
||||||
field=models.CharField(default='', help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', max_length=200, null=True, verbose_name='Realname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='travel',
|
|
||||||
name='realname',
|
|
||||||
field=models.CharField(default='', help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', max_length=200, null=True, verbose_name='Realname'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='travel',
|
|
||||||
name='username',
|
|
||||||
field=models.CharField(help_text='Wikimedia Benutzer_innenname', max_length=200, null=True, verbose_name='Benutzer_innenname'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
# Generated by Django 5.2.5 on 2025-08-20 10:01
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('input', '0097_alter_realname_and_username'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ELiterature',
|
|
||||||
fields=[
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'proxy': True,
|
|
||||||
'indexes': [],
|
|
||||||
'constraints': [],
|
|
||||||
},
|
|
||||||
bases=('input.library',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Software',
|
|
||||||
fields=[
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'proxy': True,
|
|
||||||
'indexes': [],
|
|
||||||
'constraints': [],
|
|
||||||
},
|
|
||||||
bases=('input.library',),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='library',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('BIB', '<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Zugang_zu_Fachliteratur#Bibliotheksstipendium" target="_blank" rel="noopener">Bibliotheksstipendium</a>'), ('ELIT', '<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Zugang_zu_Fachliteratur#eLiteraturstipendium" target="_blank" rel="noopener">eLiteraturstipendium</a>'), ('SOFT', '<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Software-Stipendien" target="_blank" rel="noopener">Softwarestipendium</a>')], default='BIB', max_length=4),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
# Generated by Django 5.2.5 on 2025-08-26 11:28
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('input', '0098_add_eliterature_and_software_proxies'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='businesscard',
|
|
||||||
name='terms_accepted',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Nutzungsbedingungen zugestimmt'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='email',
|
|
||||||
name='terms_accepted',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Nutzungsbedingungen zugestimmt'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='list',
|
|
||||||
name='terms_accepted',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Nutzungsbedingungen zugestimmt'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='literature',
|
|
||||||
name='terms_accepted',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Nutzungsbedingungen zugestimmt'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
# Generated by Django 5.2.5 on 2025-10-15 13:02
|
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
from input.utils.migrations import get_queryset
|
|
||||||
|
|
||||||
DEFAULT_PROJECT_CATEGORIES = [
|
|
||||||
'Erstellung und Weiterentwicklung von Inhalten für die Wikimedia-Projekte',
|
|
||||||
'Aufklärung über die Wikimedia-Projekte',
|
|
||||||
'Formate zur Ansprache, Gewinnung und Bindung von Ehrenamtlichen für die Wikimedia-Projekte',
|
|
||||||
'Beteiligung von Menschen, die einen erschwerten Zugang zum Engagement in den Wikimedia-Projekten haben',
|
|
||||||
'Vernetzung und Austausch innerhalb der Communitys oder zwischen den Communitys und externen Partner*innen',
|
|
||||||
'Vermittlung von Kompetenzen, die die ehrenamtliche Arbeit stärken',
|
|
||||||
'Stärkung einer respektvollen, konstruktiven Kommunikationskultur und der Wertschätzung in den Wikimedia-Projekten',
|
|
||||||
'Verbesserung der Selbstorganisation in Bezug auf interne Regeln, Strukturen und Prozesse der Wikimedia-Projektcommunitys',
|
|
||||||
'Ehrenamtliche Aktivitäten, die der Erstellung, Pflege und Weiterentwicklung von Tools oder sonstigen technischen Verbesserungen dienen',
|
|
||||||
]
|
|
||||||
|
|
||||||
DEFAULT_WIKIMEDIA_PROJECTS = [
|
|
||||||
'Wikipedia',
|
|
||||||
'Wikimedia Commons',
|
|
||||||
'Wikidata',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def create_default_objs(model, defaults, apps, schema_editor):
|
|
||||||
queryset = get_queryset(apps, schema_editor, 'input', model)
|
|
||||||
|
|
||||||
queryset.bulk_create([
|
|
||||||
queryset.model(name=name, order=order * 10)
|
|
||||||
for order, name in enumerate(defaults, 1)
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('input', '0099_add_terms_accepted'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ProjectCategory',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=200, verbose_name='Name')),
|
|
||||||
('order', models.PositiveIntegerField(verbose_name='Reihenfolge')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Projektkategorie',
|
|
||||||
'verbose_name_plural': 'Projektkategorien',
|
|
||||||
'ordering': ['order'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.RunPython(
|
|
||||||
code=partial(create_default_objs, 'ProjectCategory', DEFAULT_PROJECT_CATEGORIES),
|
|
||||||
reverse_code=migrations.RunPython.noop,
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='WikimediaProject',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=200, verbose_name='Name')),
|
|
||||||
('order', models.PositiveIntegerField(verbose_name='Reihenfolge')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Wikimedia-Projekt',
|
|
||||||
'verbose_name_plural': 'Wikimedia-Projekte',
|
|
||||||
'ordering': ['order'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.RunPython(
|
|
||||||
code=partial(create_default_objs, 'WikimediaProject', DEFAULT_WIKIMEDIA_PROJECTS),
|
|
||||||
reverse_code=migrations.RunPython.noop,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
# Generated by Django 5.2.5 on 2025-10-15 15:12
|
|
||||||
|
|
||||||
import input.models
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('input', '0100_projectcategory_wikimedia_project'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='categories',
|
|
||||||
field=input.models.ProjectCategoryField(related_name='projects', to='input.projectcategory', verbose_name='Projektkategorien'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='categories_other',
|
|
||||||
field=models.CharField(blank=True, max_length=200, verbose_name='Projektkategorien (Sonstiges)'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='wikimedia_projects',
|
|
||||||
field=input.models.ProjectCategoryField(related_name='projects', to='input.wikimediaproject', verbose_name='Wikimedia-Projekte'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='wikimedia_projects_other',
|
|
||||||
field=models.CharField(blank=True, max_length=200, verbose_name='Wikimedia-Projekte (Anderes)'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
# Generated by Django 5.2.5 on 2025-10-16 13:15
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('input', '0101_wikimedia_project_categories_and_other'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ProjectDeclined',
|
|
||||||
fields=[
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Projekt (abgelehnt)',
|
|
||||||
'verbose_name_plural': 'Projekte (abgelehnt)',
|
|
||||||
'proxy': True,
|
|
||||||
'indexes': [],
|
|
||||||
'constraints': [],
|
|
||||||
},
|
|
||||||
bases=('input.project',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ProjectRequest',
|
|
||||||
fields=[
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Projekt (beantragt)',
|
|
||||||
'verbose_name_plural': 'Projekte (beantragt)',
|
|
||||||
'proxy': True,
|
|
||||||
'indexes': [],
|
|
||||||
'constraints': [],
|
|
||||||
},
|
|
||||||
bases=('input.project',),
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='project',
|
|
||||||
options={'verbose_name': 'Projekt', 'verbose_name_plural': 'Projekte'},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
# Generated by Django 5.2.5 on 2025-10-08 10:37
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('input', '0102_project_request_declined'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='businesscard',
|
|
||||||
name='request_url',
|
|
||||||
field=models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='email',
|
|
||||||
name='request_url',
|
|
||||||
field=models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='ifg',
|
|
||||||
name='request_url',
|
|
||||||
field=models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='library',
|
|
||||||
name='request_url',
|
|
||||||
field=models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='list',
|
|
||||||
name='request_url',
|
|
||||||
field=models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='literature',
|
|
||||||
name='request_url',
|
|
||||||
field=models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
# Generated by Django 5.2.5 on 2025-10-16 14:39
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('input', '0103_add_request_url'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='project',
|
|
||||||
name='account',
|
|
||||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=models.CASCADE, to='input.account'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='project',
|
|
||||||
name='granted_date',
|
|
||||||
field=models.DateField(blank=True, null=True, verbose_name='Bewilligt am'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='project',
|
|
||||||
name='granted_from',
|
|
||||||
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Bewilligt von'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
# Generated by Django 5.2.5 on 2025-11-07 15:26
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('input', '0104_alter_project_required_fields'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='account',
|
|
||||||
options={'verbose_name': 'Kostenstelle', 'verbose_name_plural': 'Kostenstellen'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='businesscard',
|
|
||||||
options={'verbose_name': 'Visitenkarte', 'verbose_name_plural': 'Visitenkarten'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='eliterature',
|
|
||||||
options={'verbose_name': 'eLiteraturstipendium', 'verbose_name_plural': 'eLiteraturstipendien'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='email',
|
|
||||||
options={'verbose_name': 'E-Mail-Adresse', 'verbose_name_plural': 'E-Mail-Adressen'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='honorarycertificate',
|
|
||||||
options={'verbose_name': 'Bescheinigung', 'verbose_name_plural': 'Bescheinigungen'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='ifg',
|
|
||||||
options={'verbose_name': 'IFG-Anfrage', 'verbose_name_plural': 'IFG-Anfragen'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='library',
|
|
||||||
options={'verbose_name': 'Bibliotheksstipendium', 'verbose_name_plural': 'Bibliotheksstipendien'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='list',
|
|
||||||
options={'verbose_name': 'Mailingliste', 'verbose_name_plural': 'Mailinglisten'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='literature',
|
|
||||||
options={'verbose_name': 'Literaturstipendium', 'verbose_name_plural': 'Literaturstipendien'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='software',
|
|
||||||
options={'verbose_name': 'Softwarestipendium', 'verbose_name_plural': 'Softwarestipendien'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='travel',
|
|
||||||
options={'verbose_name': 'Reisekosten', 'verbose_name_plural': 'Reisekosten'},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.2.5 on 2025-11-07 15:43
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('input', '0105_add_verbose_names'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='username',
|
|
||||||
field=models.CharField(help_text='Wikimedia Benutzer_innenname', max_length=200, blank=True, verbose_name='Benutzer_innenname'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# Generated by Django 5.2.5 on 2025-11-10 10:42
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('input', '0106_project_username'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='project',
|
|
||||||
name='categories_other',
|
|
||||||
field=models.TextField(blank=True, verbose_name='Projektkategorien (Sonstiges)'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='project',
|
|
||||||
name='wikimedia_projects_other',
|
|
||||||
field=models.TextField(blank=True, verbose_name='Wikimedia-Projekte (Anderes)'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
622
input/models.py
622
input/models.py
|
|
@ -1,55 +1,21 @@
|
||||||
from contextlib import suppress
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
from django import forms
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.signals import pre_save
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.forms import ModelMultipleChoiceField, CheckboxSelectMultiple
|
|
||||||
from django.utils.functional import cached_property, classproperty
|
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
|
|
||||||
from foerderbarometer.constants import *
|
from .settings import ACCOUNTS
|
||||||
|
|
||||||
|
|
||||||
EMAIL_STATES = {
|
EMAIL_STATES = {'NONE': 'noch keine Mail versendet',
|
||||||
'NONE': 'noch keine Mail versendet',
|
|
||||||
'INF': 'die Benachrichtigung zur Projektabschlussmail wurde versendet',
|
'INF': 'die Benachrichtigung zur Projektabschlussmail wurde versendet',
|
||||||
'CLOSE': 'die Projektabschlussmail wurde versendet',
|
'CLOSE': 'die Projektabschlussmail wurde versendet',
|
||||||
'END': 'alle automatischen Mails, auch surveyMail, wurden versendet',
|
'END': 'alle automatischen Mails, auch surveyMail, wurden versendet'}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TermsConsentMixin(models.Model):
|
|
||||||
"""Abstract mixin to add a terms_accepted field for documenting user consent."""
|
|
||||||
|
|
||||||
terms_accepted = models.BooleanField(default=False, verbose_name='Nutzungsbedingungen zugestimmt')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class RequestUrlMixin(models.Model):
|
|
||||||
"""
|
|
||||||
Abstract mixin for adding an OTRS request URL field to admin models.
|
|
||||||
|
|
||||||
This field stores a direct link to the related OTRS ticket.
|
|
||||||
Note: OTRS links may contain semicolons, which must not be URL-encoded.
|
|
||||||
"""
|
|
||||||
|
|
||||||
request_url = models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class Volunteer(models.Model):
|
class Volunteer(models.Model):
|
||||||
realname = models.CharField(max_length=200, null=True, verbose_name='Realname',
|
realname = models.CharField(max_length=200, null=True, verbose_name="Realname",
|
||||||
help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', default='')
|
help_text="Bitte gib deinen Vornamen und deinen Nachnamen ein.", default='')
|
||||||
email = models.EmailField(max_length=200, null=True, verbose_name='E-Mail-Adresse',
|
email = models.EmailField(max_length=200, null=True, verbose_name='E-Mail-Adresse',
|
||||||
help_text=mark_safe('Bitte gib deine E-Mail-Adresse ein, damit dich<br>Wikimedia Deutschland bei Rückfragen oder für<br>die Zusage kontaktieren kann.'))
|
help_text=format_html('Bitte gib deine E-Mail-Adresse ein, damit dich<br>Wikimedia Deutschland bei Rückfragen oder für<br>die Zusage kontaktieren kann.'))
|
||||||
|
|
||||||
# the following Fields are not supposed to be edited by users
|
# the following Fields are not supposed to be edited by users
|
||||||
|
|
||||||
|
|
@ -59,6 +25,7 @@ class Volunteer(models.Model):
|
||||||
mail_state = models.CharField(max_length=6, choices=EMAIL_STATES.items(), default='NONE')
|
mail_state = models.CharField(max_length=6, choices=EMAIL_STATES.items(), default='NONE')
|
||||||
survey_mail_send = models.BooleanField(default=False, verbose_name='Keine Umfragemail schicken')
|
survey_mail_send = models.BooleanField(default=False, verbose_name='Keine Umfragemail schicken')
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_granted(cl, key, b):
|
def set_granted(cl, key, b):
|
||||||
obj = cl.objects.get(pk=key)
|
obj = cl.objects.get(pk=key)
|
||||||
|
|
@ -70,16 +37,17 @@ class Volunteer(models.Model):
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Extern(Volunteer):
|
class Extern(Volunteer):
|
||||||
''' abstract basis class for all data entered by extern volunteers '''
|
''' abstract basis class for all data entered by extern volunteers '''
|
||||||
|
|
||||||
username = models.CharField(max_length=200, null=True, verbose_name='Benutzer_innenname',
|
username = models.CharField(max_length=200, null=True, verbose_name='Benutzer_innenname',
|
||||||
help_text=mark_safe('Wikimedia Benutzer_innenname'))
|
help_text=format_html("Wikimedia Benutzer_innenname"))
|
||||||
|
|
||||||
# the following Fields are not supposed to be edited by users
|
# the following Fields are not supposed to be edited by users
|
||||||
service_id = models.CharField(max_length=15, null=True, blank=True)
|
service_id = models.CharField(max_length=15, null=True, blank=True)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self,*args,**kwargs):
|
||||||
# we don't call save with args/kwargs to avoid UNIQUE CONSTRAINT errors
|
# we don't call save with args/kwargs to avoid UNIQUE CONSTRAINT errors
|
||||||
# but maybe there is a better solution?
|
# but maybe there is a better solution?
|
||||||
super().save()
|
super().save()
|
||||||
|
|
@ -89,470 +57,263 @@ class Extern(Volunteer):
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
class ConcreteExtern(Extern):
|
class ConcreteExtern(Extern):
|
||||||
''' needed because we can't initiate abstract base classes in the view'''
|
''' needed because we can't initiate abstract base classes in the view'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Account(models.Model):
|
class Account(models.Model):
|
||||||
code = models.CharField('Kostenstelle', max_length=5, default='DEF', null=False, primary_key=True)
|
code = models.CharField('Kostenstelle', max_length=5, default="DEF",
|
||||||
|
null=False, primary_key = True)
|
||||||
description = models.CharField('Beschreibung', max_length=60, default='NO DESCRIPTION')
|
description = models.CharField('Beschreibung', max_length=60, default='NO DESCRIPTION')
|
||||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = 'Kostenstelle'
|
|
||||||
verbose_name_plural = 'Kostenstellen'
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.code} {self.description}'
|
return f"{self.code} {self.description}"
|
||||||
|
|
||||||
@property
|
|
||||||
def has_subaccounts(self):
|
|
||||||
return self.code == '21111'
|
|
||||||
|
|
||||||
|
|
||||||
class BaseProjectCategory(models.Model):
|
|
||||||
OTHER: str
|
|
||||||
|
|
||||||
name = models.CharField('Name', max_length=200)
|
|
||||||
order = models.PositiveIntegerField('Reihenfolge')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
ordering = ['order']
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def project_count(self):
|
|
||||||
return self.projects.count()
|
|
||||||
|
|
||||||
@classproperty
|
|
||||||
def other(cls):
|
|
||||||
return cls(id=0, name=cls.OTHER)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectCategory(BaseProjectCategory):
|
|
||||||
OTHER = 'Sonstiges'
|
|
||||||
|
|
||||||
class Meta(BaseProjectCategory.Meta):
|
|
||||||
verbose_name = 'Projektkategorie'
|
|
||||||
verbose_name_plural = 'Projektkategorien'
|
|
||||||
|
|
||||||
|
|
||||||
class WikimediaProject(BaseProjectCategory):
|
|
||||||
OTHER = 'Anderes'
|
|
||||||
|
|
||||||
class Meta(BaseProjectCategory.Meta):
|
|
||||||
verbose_name = 'Wikimedia-Projekt'
|
|
||||||
verbose_name_plural = 'Wikimedia-Projekte'
|
|
||||||
|
|
||||||
|
|
||||||
class ProductCategoryChoiceIterator(ModelMultipleChoiceField.iterator):
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
yield from ModelMultipleChoiceField.iterator.__iter__(self)
|
|
||||||
yield f'{self.field.other.id}', self.field.other.name
|
|
||||||
|
|
||||||
|
|
||||||
class ProductCategoryFormField(ModelMultipleChoiceField):
|
|
||||||
widget = CheckboxSelectMultiple
|
|
||||||
iterator = ProductCategoryChoiceIterator
|
|
||||||
|
|
||||||
def __init__(self, *, other, **kwargs):
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self.other = other
|
|
||||||
|
|
||||||
def _check_values(self, value, *, other=False):
|
|
||||||
with suppress(TypeError):
|
|
||||||
value = set(value)
|
|
||||||
|
|
||||||
if other := f'{self.other.id}' in value:
|
|
||||||
value.remove(f'{self.other.id}')
|
|
||||||
|
|
||||||
queryset = super()._check_values(value)
|
|
||||||
|
|
||||||
if other:
|
|
||||||
return [*queryset, self.other]
|
|
||||||
|
|
||||||
return list(queryset)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectCategoryField(models.ManyToManyField):
|
|
||||||
|
|
||||||
def __init__(self, to, **kwargs):
|
|
||||||
kwargs['to'] = to
|
|
||||||
kwargs['related_name'] = 'projects'
|
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
|
|
||||||
self.other_field = models.TextField(blank=True)
|
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name, **kwargs):
|
|
||||||
super().contribute_to_class(cls, name, **kwargs)
|
|
||||||
|
|
||||||
model, other_field = self.remote_field.model, self.other_field
|
|
||||||
|
|
||||||
if not isinstance(model, str):
|
|
||||||
self.verbose_name = self._verbose_name = verbose_name = model._meta.verbose_name_plural
|
|
||||||
other_field.verbose_name = other_field._verbose_name = f'{verbose_name} ({model.OTHER})'
|
|
||||||
|
|
||||||
other_field.contribute_to_class(cls, f'{name}_other')
|
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
|
||||||
kwargs['form_class'] = ProductCategoryFormField
|
|
||||||
kwargs['other'] = self.remote_field.model.other
|
|
||||||
|
|
||||||
return super().formfield(**kwargs)
|
|
||||||
|
|
||||||
def save_form_data(self, instance, data):
|
|
||||||
data = list(data)
|
|
||||||
|
|
||||||
with suppress(ValueError):
|
|
||||||
data.remove(self.remote_field.model.other)
|
|
||||||
|
|
||||||
return super().save_form_data(instance, data)
|
|
||||||
|
|
||||||
|
|
||||||
class Project(Volunteer):
|
class Project(Volunteer):
|
||||||
username = models.CharField(max_length=200, blank=True, verbose_name='Benutzer_innenname',
|
|
||||||
help_text=mark_safe('Wikimedia Benutzer_innenname'))
|
|
||||||
end_mail_send = models.BooleanField(default=False, verbose_name='Keine Projektabschlussmail schicken')
|
end_mail_send = models.BooleanField(default=False, verbose_name='Keine Projektabschlussmail schicken')
|
||||||
name = models.CharField(max_length=200, verbose_name='Name des Projekts')
|
name = models.CharField(max_length=200, verbose_name='Name des Projekts')
|
||||||
description = models.CharField(max_length=500, verbose_name='Kurzbeschreibung', null=True)
|
description = models.CharField(max_length=500, verbose_name="Kurzbeschreibung", null=True)
|
||||||
start = models.DateField('Startdatum', null=True)
|
start = models.DateField('Startdatum', null=True)
|
||||||
end = models.DateField('Erwartetes Projektende', null=True)
|
end = models.DateField('Erwartetes Projektende', null=True)
|
||||||
otrs = models.URLField(max_length=300, null=True, verbose_name='OTRS-Link')
|
otrs = models.URLField(max_length=300, null=True, verbose_name='OTRS-Link')
|
||||||
plan = models.URLField(max_length=2000, null=True, blank=True, verbose_name='Link zum Förderplan')
|
plan = models.URLField(max_length=2000, null=True, blank=True, verbose_name="Link zum Förderplan")
|
||||||
page = models.URLField(max_length=2000, null=True, blank=True, verbose_name='Link zur Projektseite')
|
page = models.URLField(max_length=2000, null=True, blank=True, verbose_name="Link zur Projektseite")
|
||||||
urls = models.CharField(max_length=2000, null=True, blank=True, verbose_name='Weitere Links')
|
urls = models.CharField(max_length=2000, null=True, blank=True, verbose_name="Weitere Links")
|
||||||
group = models.CharField(max_length=2000, null=True, blank=True, verbose_name='Mitorganisierende')
|
group = models.CharField(max_length=2000, null=True, blank=True, verbose_name="Mitorganisierende")
|
||||||
location = models.CharField(max_length=2000, null=True, blank=True, verbose_name='Ort/Adresse/Location')
|
location = models.CharField(max_length=2000, null=True, blank=True, verbose_name="Ort/Adresse/Location")
|
||||||
participants_estimated = models.IntegerField(blank=True, null=True, verbose_name='Teilnehmende angefragt')
|
participants_estimated = models.IntegerField(blank=True, null=True, verbose_name='Teilnehmende angefragt')
|
||||||
participants_real = models.IntegerField(blank=True, null=True, verbose_name='Teilnehmende ausgezählt')
|
participants_real = models.IntegerField(blank=True, null=True, verbose_name='Teilnehmende ausgezählt')
|
||||||
insurance = models.BooleanField(default=False, verbose_name='Haftpflichtversicherung')
|
insurance = models.BooleanField(default=False, verbose_name='Haftpflichtversicherung')
|
||||||
insurance_technic = models.BooleanField(default=False, verbose_name='Technikversicherung Ausland')
|
insurance_technic = models.BooleanField(default=False, verbose_name='Technikversicherung Ausland')
|
||||||
support = models.CharField(max_length=300, blank=True, null=True, verbose_name='Betreuungsperson und Vertretung')
|
support = models.CharField(max_length=300, blank=True, null=True, verbose_name='Betreuungsperson und Vertretung')
|
||||||
cost = models.IntegerField(blank=True, null=True, verbose_name='Kosten')
|
cost = models.IntegerField(blank=True, null=True, verbose_name='Kosten')
|
||||||
account = models.ForeignKey('Account', on_delete=models.CASCADE, blank=True, null=True, to_field='code', db_constraint=False)
|
account = models.ForeignKey('Account', on_delete=models.CASCADE, null=True, to_field='code', db_constraint = False)
|
||||||
granted_date = models.DateField(blank=True, null=True, verbose_name='Bewilligt am')
|
granted_from = models.CharField(max_length=100,null=True,verbose_name='Bewilligt von')
|
||||||
granted_from = models.CharField(max_length=100, blank=True, null=True, verbose_name='Bewilligt von')
|
notes = models.TextField(max_length=1000,null=True,blank=True,verbose_name='Anmerkungen')
|
||||||
notes = models.TextField(max_length=1000, null=True, blank=True, verbose_name='Anmerkungen')
|
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
||||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
|
||||||
|
|
||||||
categories = ProjectCategoryField(ProjectCategory)
|
|
||||||
wikimedia_projects = ProjectCategoryField(WikimediaProject)
|
|
||||||
|
|
||||||
# the following Fields are not supposed to be edited by users
|
# the following Fields are not supposed to be edited by users
|
||||||
pid = models.CharField(max_length=15, null=True, blank=True)
|
pid = models.CharField(max_length=15, null=True, blank=True)
|
||||||
status = models.CharField(max_length=3,choices=(('RUN', 'läuft'),('END','beendet'),('NOT','nicht stattgefunden')),default='RUN')
|
status = models.CharField(max_length=3,choices=(('RUN', 'läuft'),('END','beendet'),('NOT','nicht stattgefunden')),default='RUN')
|
||||||
finance_id = models.CharField(max_length=15, null= True, blank=True)
|
finance_id = models.CharField(max_length=15, null= True, blank=True)
|
||||||
project_of_year = models.IntegerField(default=0)
|
project_of_year = models.IntegerField(default=0)
|
||||||
end_quartal = models.CharField(max_length=15, null=True, blank=True, verbose_name='Quartal Projekt Ende')
|
end_quartal = models.CharField(max_length=15, null=True, blank=True, verbose_name="Quartal Projekt Ende")
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = 'Projekt'
|
|
||||||
verbose_name_plural = 'Projekte'
|
|
||||||
|
|
||||||
def __str__(self):
|
def save(self,*args,**kwargs):
|
||||||
return f'{self.pid or self.id} {self.name}'
|
|
||||||
|
|
||||||
def save(self, *, using=None, **kwargs):
|
generate_finance_id=False
|
||||||
kwargs['using'] = using
|
|
||||||
|
|
||||||
if self.end:
|
'''we generate the autogenerated fields here'''
|
||||||
self.end_quartal = f'Q{self.end.month // 4 + 1}'
|
# we don't call save with args/kwargs to avoid UNIQUE CONSTRAINT errors
|
||||||
|
# but maybe there is a better solution?
|
||||||
|
|
||||||
|
if not self.pk:
|
||||||
|
print ("NO PK THERE");
|
||||||
|
generate_finance_id=True
|
||||||
|
super().save()
|
||||||
else:
|
else:
|
||||||
self.end_quartal = ''
|
orig = type(self).objects.get(pk=self.pk) # Originaldaten aus der DB abrufen
|
||||||
|
if orig.start.year != self.start.year:
|
||||||
|
generate_finance_id=True
|
||||||
|
if orig.account.code != self.account.code:
|
||||||
|
if str(self.account.code) == '21111':
|
||||||
|
generate_finance_id=True
|
||||||
|
else:
|
||||||
|
self.finance_id = str(self.account.code)
|
||||||
|
|
||||||
if not self.account:
|
|
||||||
self.finance_id = ''
|
|
||||||
self.project_of_year = 0
|
|
||||||
|
|
||||||
return super().save(**kwargs)
|
|
||||||
|
|
||||||
if self.should_generate_finance_id():
|
|
||||||
self.generate_finance_id()
|
|
||||||
|
|
||||||
super().save(**kwargs)
|
|
||||||
|
if generate_finance_id:
|
||||||
|
print ("MUST GENERATE FINANCE ID")
|
||||||
|
year = self.start.year
|
||||||
|
projects = Project.objects.filter(start__year=year)
|
||||||
|
if not projects:
|
||||||
|
self.project_of_year = 1
|
||||||
|
#self.pid = str(self.start.year) + '-' + str(self.account.code) + str(self.project_of_year).zfill(3)
|
||||||
|
else:
|
||||||
|
# get the project of year number of latest entry
|
||||||
|
projects = projects.order_by("-project_of_year")[0]
|
||||||
|
# add one to value of latest entry
|
||||||
|
self.project_of_year = int(projects.project_of_year) + 1
|
||||||
|
# self.pid = str(self.start.year) + '-' + str(self.account.code) + str(self.project_of_year).zfill(3)
|
||||||
|
|
||||||
|
|
||||||
|
if str(self.account.code) == '21111':
|
||||||
|
self.finance_id = str(self.account.code) + '-' + str(self.project_of_year).zfill(3)
|
||||||
|
else:
|
||||||
|
self.finance_id = str(self.account.code)
|
||||||
|
|
||||||
|
# print (("Current PID",self.pid))
|
||||||
|
|
||||||
if not self.pid:
|
if not self.pid:
|
||||||
self.pid = f'{self.account.code}{self.id:08d}'
|
self.pid = str(self.account.code) + str(self.pk).zfill(8)
|
||||||
super().save(update_fields=['pid'], using=using)
|
# self.pid = str(self.account.code) + str(self.pk).zfill(3)
|
||||||
|
print (("Hallo Leute! Ich save jetzt mal MIT PID DANN!!!",self.pid))
|
||||||
def should_generate_finance_id(self):
|
|
||||||
if self.id is None:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if not self.finance_id:
|
|
||||||
return True
|
|
||||||
|
|
||||||
start, account_id = type(self).objects.values_list('start', 'account').get(id=self.id)
|
|
||||||
|
|
||||||
return not (self.start.year == start.year and self.account_id == account_id)
|
|
||||||
|
|
||||||
def generate_finance_id(self):
|
|
||||||
"""
|
|
||||||
This is an improved version of the old code for generating a finance id.
|
|
||||||
There is still no protection by constraints against duplicate finance ids!
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = Project.objects.exclude(id=self.id).filter(start__year=self.start.year)
|
|
||||||
max_project_of_year = queryset.aggregate(max=models.Max('project_of_year')).get('max') or 0
|
|
||||||
|
|
||||||
self.project_of_year = project_of_year = max_project_of_year + 1
|
|
||||||
|
|
||||||
if self.account.has_subaccounts:
|
|
||||||
self.finance_id = f'{self.account.code}-{project_of_year:03d}'
|
|
||||||
else:
|
|
||||||
self.finance_id = self.account.code
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
if (self.start and self.end) and (self.end < self.start):
|
|
||||||
raise forms.ValidationError({
|
|
||||||
'end': [
|
|
||||||
forms.ValidationError('Das erwartete Projektende muss nach dem Startdatum liegen.'),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectRequest(Project):
|
# generation of field quartals
|
||||||
|
if self.end.month in [1, 2, 3]:
|
||||||
|
self.end_quartal = 'Q1'
|
||||||
|
if self.end.month in [4, 5, 6]:
|
||||||
|
self.end_quartal = 'Q2'
|
||||||
|
if self.end.month in [7, 8, 9]:
|
||||||
|
self.end_quartal = 'Q3'
|
||||||
|
if self.end.month in [10, 11, 12]:
|
||||||
|
self.end_quartal = 'Q4'
|
||||||
|
|
||||||
class Meta:
|
super().save()
|
||||||
proxy = True
|
|
||||||
verbose_name = 'Projekt (beantragt)'
|
|
||||||
verbose_name_plural = 'Projekte (beantragt)'
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectDeclined(Project):
|
def __str__(self):
|
||||||
|
return f"{self.pid} {self.name}"
|
||||||
class Meta:
|
|
||||||
proxy = True
|
|
||||||
verbose_name = 'Projekt (abgelehnt)'
|
|
||||||
verbose_name_plural = 'Projekte (abgelehnt)'
|
|
||||||
|
|
||||||
|
|
||||||
class Intern(Volunteer):
|
class Intern(Volunteer):
|
||||||
'''abstract base class for data entry from /intern (except Project)'''
|
'''abstract base class for data entry from /intern (except Project)'''
|
||||||
request_url = models.URLField(max_length=2000, verbose_name='Antrag (URL)')
|
request_url = models.URLField(max_length=2000, verbose_name='Antrag (URL)')
|
||||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
class ConcreteVolunteer(Volunteer):
|
class ConcreteVolunteer(Volunteer):
|
||||||
''' needed because we can't initiate abstract base classes in the view'''
|
''' needed because we can't initiate abstract base classes in the view'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HonoraryCertificate(Intern):
|
class HonoraryCertificate(Intern):
|
||||||
''' this class is also used for accreditations '''
|
''' this class is also used for accreditations '''
|
||||||
|
|
||||||
project = models.ForeignKey(Project, null=True, blank=True, on_delete=models.SET_NULL)
|
project = models.ForeignKey(Project, null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = 'Bescheinigung'
|
|
||||||
verbose_name_plural = 'Bescheinigungen'
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'Bescheinigung für {self.realname}'
|
return "Certificate for " + self.realname
|
||||||
|
|
||||||
|
|
||||||
TRANSPORT_CHOICES = {
|
TRANSPORT_CHOICES = {'BAHN': 'Bahn',
|
||||||
'BAHN': 'Bahn',
|
|
||||||
'NONE': 'Keine Fahrtkosten',
|
'NONE': 'Keine Fahrtkosten',
|
||||||
'OTHER': 'Sonstiges (mit Begründung)',
|
'OTHER': 'Sonstiges (mit Begründung)'}
|
||||||
}
|
|
||||||
|
|
||||||
PAYEDBY_CHOICES = {
|
PAYEDBY_CHOICES = {'WMDE': 'WMDE',
|
||||||
'WMDE': 'WMDE',
|
'REQU': 'Antragstellender Mensch'}
|
||||||
'REQU': 'Antragstellender Mensch',
|
|
||||||
}
|
|
||||||
|
|
||||||
HOTEL_CHOICES = {
|
HOTEL_CHOICES = {'TRUE': format_html('Hotelzimmer benötigt'),
|
||||||
'TRUE': mark_safe('Hotelzimmer benötigt'),
|
'FALSE': format_html('Kein Hotelzimmer benötigt')
|
||||||
'FALSE': mark_safe('Kein Hotelzimmer benötigt'),
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
class Travel(Extern):
|
class Travel(Extern):
|
||||||
# project variable is now null true and blank true, which means it can be saved without project id to be later on filled out by admins
|
# project variable is now null true and blank true, which means it can be saved without project id to be later on filled out by admins
|
||||||
project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True, blank=True)
|
project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
project_name = models.CharField(max_length=50, null=True, blank=True, verbose_name='Projektname')
|
project_name = models.CharField(max_length=50, null=True, blank=True, verbose_name='Projektname:')
|
||||||
transport = models.CharField(max_length=5, choices=TRANSPORT_CHOICES.items(), default='BAHN', verbose_name='Transportmittel')
|
transport = models.CharField(max_length=5, choices=TRANSPORT_CHOICES.items(), default='BAHN', verbose_name='Transportmittel:')
|
||||||
other_transport = models.CharField(max_length=200, null=True, blank=True, verbose_name='Sonstige Transportmittel (mit Begründung)')
|
other_transport = models.CharField(max_length=200, null=True, blank=True, verbose_name='Sonstige Transportmittel (mit Begründung)')
|
||||||
travelcost = models.CharField(max_length=10, default='0', verbose_name='Fahrtkosten')
|
travelcost = models.CharField(max_length=10, default="0", verbose_name='Fahrtkosten')
|
||||||
checkin = models.DateField(blank=True, null=True, verbose_name='Anreise')
|
checkin = models.DateField(blank=True, null=True, verbose_name='Anreise')
|
||||||
checkout = models.DateField(blank=True, null=True, verbose_name='Abreise')
|
checkout = models.DateField(blank=True, null=True, verbose_name='Abreise')
|
||||||
payed_for_hotel_by = models.CharField(max_length=4, choices=PAYEDBY_CHOICES.items(), blank=True, null=True, verbose_name='Kostenauslage Hotel durch')
|
payed_for_hotel_by = models.CharField(max_length=4, choices=PAYEDBY_CHOICES.items(), blank=True, null=True, verbose_name='Kostenauslage Hotel durch')
|
||||||
payed_for_travel_by = models.CharField(max_length=4, choices=PAYEDBY_CHOICES.items(), blank=True, null=True, verbose_name='Kostenauslage Fahrt durch')
|
payed_for_travel_by = models.CharField(max_length=4, choices=PAYEDBY_CHOICES.items(), blank=True, null=True, verbose_name='Kostenauslage Fahrt durch')
|
||||||
hotel = models.CharField(max_length=10, choices=HOTEL_CHOICES.items(), verbose_name='Hotelzimmer benötigt')
|
hotel = models.CharField(max_length=10, choices=HOTEL_CHOICES.items(), verbose_name='Hotelzimmer benötigt:')
|
||||||
notes = models.TextField(max_length=1000, blank=True, verbose_name='Anmerkungen')
|
notes = models.TextField(max_length=1000, blank=True, verbose_name='Anmerkungen')
|
||||||
request_url = models.URLField(max_length=2000, verbose_name='Antrag (URL)')
|
request_url = models.URLField(max_length=2000, verbose_name='Antrag (URL)')
|
||||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
||||||
project_end = models.DateField(blank=True, null=True, verbose_name='Projektende')
|
project_end = models.DateField(blank=True, null=True, verbose_name='Projektende')
|
||||||
# use content type model to get the end date for the project foreign key
|
# use content type model to get the end date for the project foreign key
|
||||||
project_end_quartal = models.CharField(max_length=15, null=True, blank=True, verbose_name='Quartal Projekt Ende')
|
project_end_quartal = models.CharField(max_length=15, null=True, blank=True, verbose_name="Quartal Projekt Ende")
|
||||||
|
|
||||||
class Meta:
|
from django.db.models.signals import pre_save
|
||||||
verbose_name = 'Reisekosten'
|
from django.dispatch import receiver
|
||||||
verbose_name_plural = 'Reisekosten'
|
|
||||||
|
|
||||||
def __str__(self):
|
@receiver(pre_save, sender=Travel, dispatch_uid="get_project_end")
|
||||||
return f'Reisekosten für {self.realname}'
|
def getProjectEnd(sender, instance, **kwargs):
|
||||||
|
#instance.project_end = instance.project.end
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
if (self.checkin and self.checkout) and (self.checkout < self.checkin):
|
|
||||||
raise forms.ValidationError({
|
|
||||||
'checkout': [
|
|
||||||
forms.ValidationError('Das Datum der Abreise muss nach dem Datum der Anreise liegen.'),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=Travel, dispatch_uid='get_project_end')
|
|
||||||
def get_project_end(sender, instance, **kwargs):
|
|
||||||
if instance.project:
|
if instance.project:
|
||||||
instance.project_end = instance.project.end
|
instance.project_end = instance.project.end
|
||||||
instance.project_end_quartal = instance.project.end_quartal
|
instance.project_end_quartal = instance.project.end_quartal
|
||||||
|
|
||||||
|
# using pre save instead
|
||||||
|
# def save(self,*args,**kwargs):
|
||||||
|
# '''we generate the autogenerated fields here'''
|
||||||
|
# # we don't call save with args/kwargs to avoid UNIQUE CONSTRAINT errors
|
||||||
|
# # but maybe there is a better solution?
|
||||||
|
# intern_notes
|
||||||
|
# project_end = self.checkout
|
||||||
|
# super(Travel, self).save(*args,**kwargs)
|
||||||
|
|
||||||
# abstract base class for Library and IFG
|
#abstract base class for Library and IFG
|
||||||
class Grant(RequestUrlMixin, Extern):
|
class Grant(Extern):
|
||||||
cost = models.CharField(max_length=10, verbose_name='Kosten',
|
cost = models.CharField(max_length=10, verbose_name='Kosten',
|
||||||
help_text='Bitte gib die ungefähr zu erwartenden Kosten in Euro an.')
|
help_text="Bitte gib die ungefähr zu erwartenden Kosten in Euro an.")
|
||||||
notes = models.TextField(max_length=1000, blank=True, verbose_name='Anmerkungen',
|
notes = models.TextField(max_length=1000, blank=True, verbose_name='Anmerkungen',
|
||||||
help_text='Bitte gib an, wofür Du das Stipendium verwenden willst.')
|
help_text="Bitte gib an wofür Du das Stipendium verwenden willst.")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
def type_link(path, label):
|
TYPE_CHOICES = {'BIB': format_html('<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Zugang_zu_Fachliteratur#Bibliotheksstipendium" target="_blank" rel="noopener">Bibliotheksstipendium</a>'),
|
||||||
return format_html(
|
'ELIT': format_html('<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Zugang_zu_Fachliteratur#eLiteraturstipendium" target="_blank" rel="noopener">eLiteraturstipendium</a>'),
|
||||||
format_string='<a href="{href}" target="_blank" rel="noopener">{label}</a>',
|
'MAIL': format_html('<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/E-Mail-Adressen_und_Visitenkarten#E-Mail-Adressen" target="_blank" rel="noopener">E-Mail-Adresse</a>'),
|
||||||
href=f'https://de.wikipedia.org/wiki/Wikipedia:Förderung/{path}',
|
'IFG': format_html('<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Gebührenerstattungen_für_Behördenanfragen" target="_blank" rel="noopener">Kostenübernahme IFG-Anfrage</a>'),
|
||||||
label=label,
|
'LIT': format_html('<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Zugang_zu_Fachliteratur#Literaturstipendium" target="_blank" rel="noopener">Literaturstipendium</a>'),
|
||||||
)
|
'LIST': format_html('<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/E-Mail-Adressen_und_Visitenkarten#Mailinglisten" target="_blank" rel="noopener">Mailingliste</a>'),
|
||||||
|
'TRAV': format_html('<a href="https://de.wikipedia.org/wiki/Wikipedia:F%C3%B6rderung/Reisekostenerstattungen" target="_blank" rel="noopener">Reisekosten</a>'),
|
||||||
|
'SOFT': format_html('<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Software-Stipendien" target="_blank" rel="noopener">Softwarestipendium</a>'),
|
||||||
TYPE_CHOICES = {
|
'VIS': format_html('<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/E-Mail-Adressen_und_Visitenkarten#Visitenkarten" target="_blank" rel="noopener">Visitenkarten</a>'),
|
||||||
TYPE_BIB: type_link('Zugang_zu_Fachliteratur#Bibliotheksstipendium', 'Bibliotheksstipendium'),
|
}
|
||||||
TYPE_ELIT: type_link('Zugang_zu_Fachliteratur#eLiteraturstipendium', 'eLiteraturstipendium'),
|
|
||||||
TYPE_MAIL: type_link('E-Mail-Adressen_und_Visitenkarten#E-Mail-Adressen', 'E-Mail-Adresse'),
|
|
||||||
TYPE_IFG: type_link('Gebührenerstattungen_für_Behördenanfragen', 'Kostenübernahme IFG-Anfrage'),
|
|
||||||
TYPE_LIT: type_link('Zugang_zu_Fachliteratur#Literaturstipendium', 'Literaturstipendium'),
|
|
||||||
TYPE_LIST: type_link('E-Mail-Adressen_und_Visitenkarten#Mailinglisten', 'Mailingliste'),
|
|
||||||
TYPE_TRAV: type_link('Reisekostenerstattungen', 'Reisekosten'),
|
|
||||||
TYPE_SOFT: type_link('Software-Stipendien', 'Softwarestipendium'),
|
|
||||||
TYPE_VIS: type_link('E-Mail-Adressen_und_Visitenkarten#Visitenkarten', 'Visitenkarten'),
|
|
||||||
TYPE_PROJ: type_link('Projektplanung', 'Projektförderung unter 1000 EUR'),
|
|
||||||
}
|
|
||||||
|
|
||||||
LIBRARY_TYPES = TYPE_BIB, TYPE_ELIT, TYPE_SOFT
|
|
||||||
LIBRARY_TYPE_CHOICES = [(choice, TYPE_CHOICES[choice]) for choice in LIBRARY_TYPES]
|
|
||||||
|
|
||||||
|
|
||||||
# same model is used for Library, ELitStip and Software!
|
# same model is used for Library, ELitStip and Software!
|
||||||
class Library(Grant):
|
class Library(Grant):
|
||||||
TYPE = TYPE_BIB
|
|
||||||
LIBRARY_LABEL = 'Bibliothek'
|
|
||||||
LIBRARY_HELP_TEXT = 'Für welche Bibliothek gilt das Stipendium?'
|
|
||||||
DURATION_HELP_TEXT = mark_safe('In welchem Zeitraum möchtest du recherchieren oder<br>wie lange ist der Bibliotheksausweis gültig?')
|
|
||||||
|
|
||||||
type = models.CharField(max_length=4, choices=LIBRARY_TYPE_CHOICES, default=TYPE_BIB)
|
type = models.CharField(
|
||||||
|
max_length=4,
|
||||||
|
choices=TYPE_CHOICES.items(), #attention: actually only BIB, ELIT, SOFT should be used here
|
||||||
|
default='BIB',
|
||||||
|
)
|
||||||
library = models.CharField(max_length=200)
|
library = models.CharField(max_length=200)
|
||||||
duration = models.CharField(max_length=100, verbose_name='Dauer')
|
duration = models.CharField(max_length=100, verbose_name="Dauer")
|
||||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = 'Bibliotheksstipendium'
|
|
||||||
verbose_name_plural = 'Bibliotheksstipendien'
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.library
|
return self.library
|
||||||
|
|
||||||
def save(self, **kwargs):
|
|
||||||
self.type = self.TYPE
|
|
||||||
|
|
||||||
return super().save(**kwargs)
|
SELFBUY_CHOICES = {'TRUE': format_html('Ich möchte das Werk selbst kaufen und per Kostenerstattung bei Wikimedia Deutschland abrechnen.'),
|
||||||
|
'FALSE': format_html('Ich möchte, dass Wikimedia Deutschland das Werk für mich kauft'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ELiterature(Library):
|
class Literature(Grant):
|
||||||
TYPE = TYPE_ELIT
|
|
||||||
LIBRARY_LABEL = 'Datenbank/Online-Ressource'
|
|
||||||
LIBRARY_HELP_TEXT = 'Für welche Datenbank/Online-Ressource gilt das Stipendium?'
|
|
||||||
DURATION_HELP_TEXT = 'Wie lange gilt der Zugang?'
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
proxy = True
|
|
||||||
verbose_name = 'eLiteraturstipendium'
|
|
||||||
verbose_name_plural = 'eLiteraturstipendien'
|
|
||||||
|
|
||||||
|
|
||||||
class Software(Library):
|
|
||||||
TYPE = TYPE_SOFT
|
|
||||||
LIBRARY_LABEL = 'Software'
|
|
||||||
LIBRARY_HELP_TEXT = 'Für welche Software gilt das Stipendium?'
|
|
||||||
DURATION_HELP_TEXT = 'Wie lange gilt die Lizenz?'
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
proxy = True
|
|
||||||
verbose_name = 'Softwarestipendium'
|
|
||||||
verbose_name_plural = 'Softwarestipendien'
|
|
||||||
|
|
||||||
|
|
||||||
SELFBUY_CHOICES = {
|
|
||||||
'TRUE': mark_safe('Ich möchte das Werk selbst kaufen und per Kostenerstattung bei Wikimedia Deutschland abrechnen.'),
|
|
||||||
'FALSE': mark_safe('Ich möchte, dass Wikimedia Deutschland das Werk für mich kauft'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Literature(TermsConsentMixin, Grant):
|
|
||||||
info = models.CharField(max_length=500, verbose_name='Informationen zum Werk',
|
info = models.CharField(max_length=500, verbose_name='Informationen zum Werk',
|
||||||
help_text=mark_safe('Bitte gib alle Informationen zum benötigten Werk an,<br>\
|
help_text=format_html("Bitte gib alle Informationen zum benötigten Werk an,<br>\
|
||||||
die eine eindeutige Identifizierung ermöglichen (Autor, Titel, Verlag, ISBN, ...)'))
|
die eine eindeutige Identifizierung ermöglichen (Autor, Titel, Verlag, ISBN, ...)"))
|
||||||
source = models.CharField(max_length=200, verbose_name='Bezugsquelle',
|
source = models.CharField(max_length=200, verbose_name='Bezugsquelle',
|
||||||
help_text='Bitte gib an, wo du das Werk kaufen möchtest.')
|
help_text="Bitte gib an, wo du das Werk kaufen möchtest.")
|
||||||
selfbuy = models.CharField( max_length=10, verbose_name='Selbstkauf?', choices=SELFBUY_CHOICES.items(), default='TRUE')
|
selfbuy = models.CharField( max_length=10, verbose_name='Selbstkauf?', choices=SELFBUY_CHOICES.items(), default='TRUE')
|
||||||
selfbuy_give_data = models.BooleanField(verbose_name=mark_safe('Datenweitergabe erlauben'), help_text=mark_safe('Ich stimme der Weitergabe meiner Daten (Name, Postadresse) an den von mir angegebenen Anbieter/Dienstleister zu.'))
|
selfbuy_give_data = models.BooleanField(verbose_name=format_html('Datenweitergabe erlauben'), help_text=format_html('Ich stimme der Weitergabe meiner Daten (Name, Postadresse) an den von mir angegebenen Anbieter/Dienstleister zu.'))
|
||||||
selfbuy_data = models.TextField(max_length=1000, verbose_name='Persönliche Daten sowie Adresse', default='',\
|
selfbuy_data = models.TextField(max_length=1000, verbose_name='Persönliche Daten sowie Adresse', default='',\
|
||||||
help_text=mark_safe('Bitte gib hier alle persönlichen Daten an, die wir benötigen, um das Werk<br>\
|
help_text=format_html("Bitte gib hier alle persönlichen Daten an, die wir benötigen, um das Werk<br>\
|
||||||
für dich zu kaufen und es dir anschließend zu schicken (z.B. Vorname Nachname, Anschrift, <br>\
|
für dich zu kaufen und es dir anschließend zu schicken (z.B. Vorname Nachname, Anschrift, <br>\
|
||||||
Telefonnummer, E-Mail-Adresse usw.). Trenne die einzelnen Angaben durch Zeilenumbrüche.'))
|
Telefonnummer, E-Mail-Adresse usw.). Trenne die einzelnen Angaben durch Zeilenumbrüche."))
|
||||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = 'Literaturstipendium'
|
|
||||||
verbose_name_plural = 'Literaturstipendien'
|
|
||||||
|
|
||||||
|
|
||||||
class IFG(Grant):
|
class IFG(Grant):
|
||||||
url = models.URLField(max_length=2000, verbose_name='URL',
|
url = models.URLField(max_length=2000, verbose_name="URL",
|
||||||
help_text='Bitte gib den Link zu deiner Anfrage bei Frag den Staat an.')
|
help_text="Bitte gib den Link zu deiner Anfrage bei Frag den Staat an.")
|
||||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = 'IFG-Anfrage'
|
|
||||||
verbose_name_plural = 'IFG-Anfragen'
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'IFG-Anfrage von {self.realname}'
|
return "IFG-Anfrage von " + self.realname
|
||||||
|
|
||||||
|
DOMAIN_CHOICES = {'PEDIA': '@wikipedia.de',
|
||||||
DOMAIN_CHOICES = {
|
|
||||||
'PEDIA': '@wikipedia.de',
|
|
||||||
'BOOKS': '@wikibooks.de',
|
'BOOKS': '@wikibooks.de',
|
||||||
'QUOTE': '@wikiquote.de',
|
'QUOTE': '@wikiquote.de',
|
||||||
'SOURCE': '@wikisource.de',
|
'SOURCE': '@wikisource.de',
|
||||||
'VERSITY': '@wikiversity.de',
|
'VERSITY': '@wikiversity.de',}
|
||||||
}
|
|
||||||
|
|
||||||
|
class Domain(Extern):
|
||||||
class Domain(RequestUrlMixin, Extern):
|
|
||||||
domain = models.CharField(max_length=10,
|
domain = models.CharField(max_length=10,
|
||||||
choices=DOMAIN_CHOICES.items(),
|
choices=DOMAIN_CHOICES.items(),
|
||||||
default='PEDIA')
|
default='PEDIA')
|
||||||
|
|
@ -560,47 +321,31 @@ class Domain(RequestUrlMixin, Extern):
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
MAIL_CHOICES = {'REALNAME': 'Vorname.Nachname',
|
||||||
MAIL_CHOICES = {
|
|
||||||
'REALNAME': 'Vorname.Nachname',
|
|
||||||
'USERNAME': 'Username',
|
'USERNAME': 'Username',
|
||||||
'OTHER': 'Sonstiges',
|
'OTHER': 'Sonstiges:'}
|
||||||
}
|
|
||||||
|
|
||||||
ADULT_CHOICES = {
|
ADULT_CHOICES = {'TRUE': format_html('Ich bin volljährig.'),
|
||||||
'TRUE': mark_safe('Ich bin volljährig.'),
|
'FALSE': format_html('Ich bin noch nicht volljährig.')
|
||||||
'FALSE': mark_safe('Ich bin noch nicht volljährig.'),
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
class Email(Domain):
|
||||||
class Email(TermsConsentMixin, Domain):
|
|
||||||
address = models.CharField(max_length=50,
|
address = models.CharField(max_length=50,
|
||||||
choices=MAIL_CHOICES.items(),
|
choices=MAIL_CHOICES.items(),
|
||||||
default='USERNAME', verbose_name='Adressbestandteil',
|
default='USERNAME', verbose_name='Adressbestandteil',
|
||||||
help_text=mark_safe('Bitte gib hier den gewünschten Adressbestandteil an,<br>der sich vor der Domain befinden soll.'))
|
help_text=format_html("Bitte gib hier den gewünschten Adressbestandteil an,<br>der sich vor der Domain befinden soll."))
|
||||||
|
|
||||||
other = models.CharField(max_length=50, blank=True, null=True, verbose_name='Sonstiges')
|
other = models.CharField(max_length=50,blank=True,null=True, verbose_name="Sonstiges")
|
||||||
adult = models.CharField(max_length=10, verbose_name='Volljährigkeit', choices=ADULT_CHOICES.items(), default='FALSE')
|
adult = models.CharField( max_length=10, verbose_name='Volljährigkeit', choices=ADULT_CHOICES.items(), default='FALSE')
|
||||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
||||||
|
|
||||||
class Meta:
|
class List(Domain):
|
||||||
verbose_name = 'E-Mail-Adresse'
|
|
||||||
verbose_name_plural = 'E-Mail-Adressen'
|
|
||||||
|
|
||||||
|
|
||||||
class List(TermsConsentMixin, Domain):
|
|
||||||
address = models.CharField(max_length=50, default='NO_ADDRESS',
|
address = models.CharField(max_length=50, default='NO_ADDRESS',
|
||||||
verbose_name='Adressbestandteil für Projektmailingliste',
|
verbose_name="Adressbestandteil für Projektmailingliste",
|
||||||
help_text=mark_safe('Bitte gib hier den gewünschten Adressbestandteil an,<br>der sich vor der Domain befinden soll.'))
|
help_text=format_html("Bitte gib hier den gewünschten Adressbestandteil an,<br>der sich vor der Domain befinden soll."))
|
||||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
||||||
|
|
||||||
class Meta:
|
PROJECT_CHOICE = {'PEDIA': 'Wikipedia',
|
||||||
verbose_name = 'Mailingliste'
|
|
||||||
verbose_name_plural = 'Mailinglisten'
|
|
||||||
|
|
||||||
|
|
||||||
PROJECT_CHOICE = {
|
|
||||||
'PEDIA': 'Wikipedia',
|
|
||||||
'SOURCE': 'Wikisource',
|
'SOURCE': 'Wikisource',
|
||||||
'BOOKS': 'Wikibooks',
|
'BOOKS': 'Wikibooks',
|
||||||
'QUOTE': 'Wikiquote',
|
'QUOTE': 'Wikiquote',
|
||||||
|
|
@ -608,53 +353,32 @@ PROJECT_CHOICE = {
|
||||||
'VOYAGE': 'Wikivoyage',
|
'VOYAGE': 'Wikivoyage',
|
||||||
'DATA': 'Wikidata',
|
'DATA': 'Wikidata',
|
||||||
'NEWS': 'Wikinews',
|
'NEWS': 'Wikinews',
|
||||||
'COMMONS': 'Wikimedia Commons',
|
'COMMONS': 'Wikimedia Commons'}
|
||||||
}
|
|
||||||
|
|
||||||
BC_VARIANT = {
|
BC_VARIANT = {'PIC': 'mit Bild',
|
||||||
'PIC': 'mit Bild',
|
'NOPIC': 'ohne Bild'}
|
||||||
'NOPIC': 'ohne Bild',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
class BusinessCard(Extern):
|
||||||
class BusinessCard(RequestUrlMixin, TermsConsentMixin, Extern):
|
|
||||||
project = models.CharField(max_length=20, choices=PROJECT_CHOICE.items(),
|
project = models.CharField(max_length=20, choices=PROJECT_CHOICE.items(),
|
||||||
default='PEDIA', verbose_name='Wikimedia-Projekt',
|
default='PEDIA', verbose_name='Wikimedia-Projekt',
|
||||||
help_text='Für welches Wikimedia-Projekt möchtest Du Visitenkarten?')
|
help_text='Für welches Wikimedia-Projekt möchtest Du Visitenkarten?')
|
||||||
|
|
||||||
data = models.TextField(max_length=1000, verbose_name='Persönliche Daten für die Visitenkarten', default='',
|
data = models.TextField(max_length=1000, verbose_name='Persönliche Daten für die Visitenkarten', default='',
|
||||||
help_text=mark_safe('Bitte gib hier alle persönlichen Daten an, und zwar genau so,<br>\
|
help_text=format_html("Bitte gib hier alle persönlichen Daten an, und zwar genau so,<br>\
|
||||||
wie sie (auch in der entsprechenden Reihenfolge) auf den Visitenkarten stehen sollen<br>\
|
wie sie (auch in der entsprechenden Reihenfolge) auf den Visitenkarten stehen sollen<br>\
|
||||||
(z.B. Vorname Nachname, Benutzer:/Benutzerin:, Benutzer-/-innenname, Anschrift,<br>\
|
(z.B. Vorname Nachname, Benutzer:/Benutzerin:, Benutzer-/-innenname, Anschrift,<br>\
|
||||||
Telefonnummer, E-Mail-Adresse usw.). Trenne die einzelnen Angaben durch Zeilenumbrüche.<br>\
|
Telefonnummer, E-Mail-Adresse usw.). Trenne die einzelnen Angaben durch Zeilenumbrüche.<br>\
|
||||||
Hinweis: Telefonnummern bilden wir üblicherweise im internationalen Format gemäß<br>\
|
Hinweis: Telefonnummern bilden wir üblicherweise im internationalen Format gemäß<br>\
|
||||||
DIN 5008 ab. Als anzugebende E-Mail-Adresse empfehlen wir dir eine Wikimedia-Projekt-<br>\
|
DIN 5008 ab. Als anzugebende E-Mail-Adresse empfehlen wir dir eine Wikimedia-Projekt-<br>\
|
||||||
Adresse, die du ebenfalls beantragen kannst, sofern du nicht bereits eine besitzt.'))
|
Adresse, die du ebenfalls beantragen kannst, sofern du nicht bereits eine besitzt."))
|
||||||
variant = models.CharField(max_length=5, choices=BC_VARIANT.items(),
|
variant = models.CharField(max_length=5, choices=BC_VARIANT.items(),
|
||||||
default='NOPIC', verbose_name='Variante',
|
default='NOPIC', verbose_name='Variante',
|
||||||
help_text=mark_safe('so sehen die Varianten aus: <a href="https://upload.wikimedia.org/wikipedia/commons/c/cd/Muster_Visitenkarten_WMDE_2018.jpg">\
|
help_text=format_html('so sehen die Varianten aus: <a href="https://upload.wikimedia.org/wikipedia/commons/c/cd/Muster_Visitenkarten_WMDE_2018.jpg">\
|
||||||
mit Bild</a> <a href="https://upload.wikimedia.org/wikipedia/commons/d/d3/Muster_Visitenkarte_WMDE.png">ohne Bild</a>'))
|
mit Bild</a> <a href="https://upload.wikimedia.org/wikipedia/commons/d/d3/Muster_Visitenkarte_WMDE.png">ohne Bild</a>' ))
|
||||||
|
|
||||||
url_of_pic = models.CharField(max_length=200, verbose_name='Url des Bildes', default='', help_text='Bitte gib die Wikimedia-Commons-URL des Bildes an.')
|
url_of_pic = models.CharField(max_length=200, verbose_name='Url des Bildes', default='', help_text="Bitte gib die Wikimedia-Commons-URL des Bildes an.")
|
||||||
|
|
||||||
sent_to = models.TextField(max_length=1000, verbose_name='Versandadresse',
|
sent_to = models.TextField(max_length=1000, verbose_name='Versandadresse',
|
||||||
default='', help_text='Bitte gib den Namen und die vollständige Adresse ein, an welche die Visitenkarten geschickt werden sollen.')
|
default='', help_text="Bitte gib den Namen und die vollständige Adresse ein, an welche die Visitenkarten geschickt werden sollen.")
|
||||||
send_data_to_print = models.BooleanField(default=False, verbose_name=mark_safe('Datenweitergabe erlauben'), help_text=mark_safe('Hiermit erlaube ich die Weitergabe meiner Daten (Name, Postadresse) an den von Wikimedia<br> Deutschland ausgewählten Dienstleister (z. B. <a href="wir-machen-druck.de">wir-machen-druck.de</a>) zum Zwecke des direkten <br> Versands der Druckerzeugnisse an mich.'))
|
send_data_to_print = models.BooleanField(default=False, verbose_name=format_html('Datenweitergabe erlauben'), help_text=format_html('Hiermit erlaube ich die Weitergabe meiner Daten (Name, Postadresse) an den von Wikimedia<br> Deutschland ausgewählten Dienstleister (z. B. <a href="wir-machen-druck.de">wir-machen-druck.de</a>) zum Zwecke des direkten <br> Versands der Druckerzeugnisse an mich.'))
|
||||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = 'Visitenkarte'
|
|
||||||
verbose_name_plural = 'Visitenkarten'
|
|
||||||
|
|
||||||
|
|
||||||
MODELS = {
|
|
||||||
TYPE_BIB: Library,
|
|
||||||
TYPE_ELIT: ELiterature,
|
|
||||||
TYPE_MAIL: Email,
|
|
||||||
TYPE_IFG: IFG,
|
|
||||||
TYPE_LIT: Literature,
|
|
||||||
TYPE_LIST: List,
|
|
||||||
TYPE_TRAV: Travel,
|
|
||||||
TYPE_SOFT: Software,
|
|
||||||
TYPE_VIS: BusinessCard,
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
# mail for IF-OTRS
|
||||||
|
IF_EMAIL = 'community@wikimedia.de'
|
||||||
|
#IF_EMAIL = 'test-luca-ext@wikimedia.de'
|
||||||
|
#SURVEY_EMAIL = 'christof.pins@wikimedia.de'
|
||||||
|
#SURVEY_EMAIL = 'luca.wulf@cannabinieri.de'
|
||||||
|
SURVEY_EMAIL = 'sandro.halank@wikimedia.de'
|
||||||
|
# prefix for urls
|
||||||
|
SURVEYPREFIX = 'https://wikimedia.sslsurvey.de/Foerderbarometer/?'
|
||||||
|
|
||||||
|
# some links
|
||||||
|
DATAPROTECTION = "https://www.wikimedia.de/datenschutz/#datenerfassung"
|
||||||
|
#FOERDERRICHTLINIEN = "https://de.wikipedia.org/wiki/Wikipedia:Wikimedia_Deutschland/F%C3%B6rderrichtlinien"
|
||||||
|
FOERDERRICHTLINIEN = "https://de.wikipedia.org/wiki/Wikipedia:Wikimedia_Deutschland/Richtlinie_zur_Förderung_der_Communitys"
|
||||||
|
|
||||||
|
NUTZUNGSBEDINGUNGEN = 'static/input/nutzungsbedingungen.html'
|
||||||
|
NUTZUNGSBEDINGUNGEN_EMAIL_SERVICE = 'static/input/nutzungsbedingungen-mail.pdf'
|
||||||
|
NUTZUNGSBEDINGUNGEN_MAILINGLISTEN = 'static/input/nutzungsbedingungen-mailinglisten.pdf'
|
||||||
|
NUTZUNGSBEDINGUNGEN_LITERATURSTIPENDIUM = 'static/input/nutzungsbedingungen-literaturstipendium.pdf'
|
||||||
|
NUTZUNGSBEDINGUNGEN_OTRS = 'static/input/2025_Nutzungsvereinbarung_OTRS.docx.pdf'
|
||||||
|
NUTZUNGSBEDINGUNGEN_VISITENKARTEN = 'static/input/nutzungsbedingungen-visitenkarten.pdf'
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'de'
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
USE_I18N = True
|
||||||
|
USE_L10N = True
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
ACCOUNTS ={ # "21103": '21103 Willkommen',
|
||||||
|
"DEF": 'DEFAULT VALUE, you hould not see this!',
|
||||||
|
"21111": '21111 Förderung (reaktiv)',
|
||||||
|
"21112": '21112 WikiCon',
|
||||||
|
# "21113": '21113 Wikimania/Unterstützung Ehrenamtliche',
|
||||||
|
"21115": '21115 Lokale Räume, Berlin',
|
||||||
|
"21116": '21116 Lokale Räume, Hamburg',
|
||||||
|
"21117": '21117 Lokale Räume, Hannover',
|
||||||
|
"21118": '21118 Lokale Räume, Köln',
|
||||||
|
"21119": '21119 Lokale Räume, München',
|
||||||
|
"21120": '21120 Lokale Räume, Fürth',
|
||||||
|
"21125": '21125 Lokale Räume, allgemein',
|
||||||
|
"21130": '21130 GLAM-Förderung',
|
||||||
|
"21131": '21131 Initiative Förderung',
|
||||||
|
# "21134": '21134 Größe',
|
||||||
|
# "21137": '21137 Beitragen',
|
||||||
|
# "21138": '21138 Vermittlung',
|
||||||
|
"21140": '21140 Wikipedia-Kampagne',
|
||||||
|
"21141": '21141 Wikipedia-Onboarding',
|
||||||
|
"21150": '21150 Fürsorge und Online-Kommunikationskultur',}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# teken from working oauth prototype as additional settings
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'oauth_demo.wsgi.application'
|
||||||
|
|
||||||
|
# OAuth Settings
|
||||||
|
OAUTH_URL_WHITELISTS = []
|
||||||
|
|
||||||
|
OAUTH_CLIENT_NAME = '<name-of-the-configured-wikimedia-app>'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
OAUTH_CLIENT = {
|
||||||
|
'client_id': '<client-application-key-of-wikimedia-app>',
|
||||||
|
'client_secret': '<client-application-secret-of-wikimedia-app>',
|
||||||
|
'access_token_url': 'https://meta.wikimedia.org/w/rest.php/oauth2/access_token',
|
||||||
|
'authorize_url': 'https://meta.wikimedia.org/w/rest.php/oauth2/authorize',
|
||||||
|
'api_base_url': 'https://meta.wikimedia.org/w/rest.php/oauth2/resource',
|
||||||
|
'redirect_uri': 'http://localhost:8000/oauth/callback',
|
||||||
|
'client_kwargs': {
|
||||||
|
'scope': 'basic',
|
||||||
|
'token_placement': 'header'
|
||||||
|
},
|
||||||
|
'userinfo_endpoint': 'resource/profile',
|
||||||
|
}
|
||||||
|
|
||||||
|
OAUTH_COOKIE_SESSION_ID = 'sso_session_id'
|
||||||
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
# mail for IF-OTRS
|
||||||
|
IF_EMAIL = 'community@wikimedia.de'
|
||||||
|
#IF_EMAIL = 'test-luca-ext@wikimedia.de'
|
||||||
|
#SURVEY_EMAIL = 'christof.pins@wikimedia.de'
|
||||||
|
#SURVEY_EMAIL = 'luca.wulf@cannabinieri.de'
|
||||||
|
SURVEY_EMAIL = 'sandro.halank@wikimedia.de'
|
||||||
|
# prefix for urls
|
||||||
|
SURVEYPREFIX = 'https://wikimedia.sslsurvey.de/Foerderbarometer/?'
|
||||||
|
|
||||||
|
# some links
|
||||||
|
DATAPROTECTION = "https://www.wikimedia.de/datenschutz/#datenerfassung"
|
||||||
|
FOERDERRICHTLINIEN = "https://de.wikipedia.org/wiki/Wikipedia:Wikimedia_Deutschland/F%C3%B6rderrichtlinien"
|
||||||
|
NUTZUNGSBEDINGUNGEN = 'static/input/nutzungsbedingungen.html'
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'de'
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
USE_I18N = True
|
||||||
|
USE_L10N = True
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
ACCOUNTS ={ # "21103": '21103 Willkommen',
|
||||||
|
"DEF": 'DEFAULT VALUE, you hould not see this!',
|
||||||
|
"21111": '21111 Förderung (reaktiv)',
|
||||||
|
"21112": '21112 WikiCon',
|
||||||
|
# "21113": '21113 Wikimania/Unterstützung Ehrenamtliche',
|
||||||
|
"21115": '21115 Lokale Räume, Berlin',
|
||||||
|
"21116": '21116 Lokale Räume, Hamburg',
|
||||||
|
"21117": '21117 Lokale Räume, Hannover',
|
||||||
|
"21118": '21118 Lokale Räume, Köln',
|
||||||
|
"21119": '21119 Lokale Räume, München',
|
||||||
|
"21120": '21120 Lokale Räume, Fürth',
|
||||||
|
"21125": '21125 Lokale Räume, allgemein',
|
||||||
|
"21130": '21130 GLAM-Förderung',
|
||||||
|
"21131": '21131 Initiative Förderung',
|
||||||
|
# "21134": '21134 Größe',
|
||||||
|
# "21137": '21137 Beitragen',
|
||||||
|
# "21138": '21138 Vermittlung',
|
||||||
|
"21140": '21140 Wikipedia-Kampagne',
|
||||||
|
"21141": '21141 Wikipedia-Onboarding',
|
||||||
|
"21150": '21150 Fürsorge und Online-Kommunikationskultur',}
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
ul > li {
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label.required::after {
|
|
||||||
content: ' *';
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spacer-15 {
|
|
||||||
height: 15%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spacer-5 {
|
|
||||||
height: 5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-centered {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
.page-centered .button-login {
|
|
||||||
width: 40vw;
|
|
||||||
height: 6vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 4vh;
|
|
||||||
margin: 0 auto;
|
|
||||||
background-color: #79AEC8;
|
|
||||||
color: #000000;
|
|
||||||
text-decoration: none;
|
|
||||||
padding: 10px 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-centered .button-login:hover {
|
|
||||||
background-color: #659DB8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-centered .button-login:focus-visible {
|
|
||||||
outline: 2px solid #000;
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
span.datetimeshortcuts > a:first-child {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
span.datetimeshortcuts {
|
span.datetimeshortcuts {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
@ -5,11 +12,3 @@ span.datetimeshortcuts {
|
||||||
span.datetimeshortcuts > a:nth-child(2) {
|
span.datetimeshortcuts > a:nth-child(2) {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.datetimeshortcuts > a:first-child {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarbox .calendar-shortcuts {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
.star {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wm-table {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wm-table.start {
|
|
||||||
td, th {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.applications {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.col-request {
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
.related-widget-wrapper div label {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
(function ($) {
|
|
||||||
$(function () {
|
|
||||||
$('#id_categories, #id_wikimedia_projects').each(function () {
|
|
||||||
const otherCheckbox = $(this).find('input[value=0]');
|
|
||||||
const otherInputSelector = '#'.concat(this.id, '_other');
|
|
||||||
const otherInput = $(otherInputSelector);
|
|
||||||
const otherLabelSelector = 'label'.concat('[for="', this.id, '_other"]');
|
|
||||||
const otherLabel = $(otherLabelSelector);
|
|
||||||
const otherTableRow = otherInput.parents('tr');
|
|
||||||
|
|
||||||
const toggle = function () {
|
|
||||||
const checked = otherCheckbox.prop('checked');
|
|
||||||
|
|
||||||
otherInput.prop('disabled', !checked);
|
|
||||||
otherInput.prop('required', checked);
|
|
||||||
otherLabel.toggleClass('required', checked);
|
|
||||||
otherLabel.css('opacity', checked ? 1 : 0.3);
|
|
||||||
otherTableRow.css('visibility', checked ? 'visible' : 'collapse');
|
|
||||||
|
|
||||||
if (checked) {
|
|
||||||
otherInput.focus();
|
|
||||||
} else {
|
|
||||||
otherInput.val('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
toggle();
|
|
||||||
|
|
||||||
otherCheckbox.on('change', toggle);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})(django.jQuery);
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
{% load static %}
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
|
|
||||||
<!-- Admin i18n -->
|
|
||||||
<script src="{% url 'jsi18n' %}"></script>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Admin Assets -->
|
|
||||||
<script src="{% static 'admin/js/core.js' %}"></script>
|
|
||||||
<script src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>
|
|
||||||
<script src="{% static 'admin/js/jquery.init.js' %}"></script>
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="{% static 'admin/css/base.css' %}">
|
|
||||||
<link rel="stylesheet" href="{% static 'admin/css/widgets.css' %}">
|
|
||||||
|
|
||||||
<!-- Project Styles -->
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/forms.css' %}">
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/base.css' %}">
|
|
||||||
|
|
||||||
{% block head_extra %}{% endblock %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{% include "input/partials/_header.html" %}
|
|
||||||
|
|
||||||
<main class="wm-main">
|
|
||||||
{% block pre_content %}{% endblock %}
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
{% block post_content %}{% endblock %}
|
|
||||||
</main>
|
|
||||||
|
|
||||||
{% include "input/partials/_footer.html" %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/admin/jsi18n/"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'admin/js/core.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'admin/js/jquery.init.js' %}"></script>
|
||||||
|
|
||||||
|
{{ form.media }}
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/widgets.css' %}" />
|
||||||
|
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<center>
|
||||||
|
<style>
|
||||||
|
ul > li {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
padding-left: 10;
|
||||||
|
}
|
||||||
|
label.required::after {
|
||||||
|
content: ' *';
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<img src="{% static 'input/logo.png' %}" />
|
||||||
|
|
||||||
|
<p>Schritt {{ wizard.steps.step1 }} von {{ wizard.steps.count }}</p>
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
{% if choice %}
|
||||||
|
Du hast {{choice}} ausgewählt.
|
||||||
|
{% endif %}
|
||||||
|
{{ wizard.management_form }}
|
||||||
|
{% if wizard.form.forms %}
|
||||||
|
{{ wizard.form.management_form }}
|
||||||
|
{% for form in wizard.form.forms %}
|
||||||
|
{{ form }}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
{{ wizard.form }}
|
||||||
|
{% endif %}
|
||||||
|
</table>
|
||||||
|
<p>
|
||||||
|
<span style="color: red">*</span> Pflichtfeld
|
||||||
|
<p>
|
||||||
|
{% if wizard.steps.prev %}
|
||||||
|
<button formnovalidate="formnovalidate" name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">Zurück</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if wizard.steps.current == wizard.steps.last %}
|
||||||
|
<button type="submit" value="{% trans "Weiter" %}">Absenden</button>
|
||||||
|
{% else %}
|
||||||
|
<button type="submit" value="{% trans "Weiter" %}">Weiter</button>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
<p>
|
||||||
|
<img src="https://upload.wikimedia.org/wikipedia/commons/c/c4/Figuren_klein.jpg"><p>
|
||||||
|
Eine Übersicht aller Förderangebote von Wikimedia Deutschland findest du im <a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Förderangebote">
|
||||||
|
Förderportal in der deutschsprachigen Wikipedia</a>.
|
||||||
|
<br>Für alle Fragen wende dich gern an das <a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Wikimedia_Deutschland">Team Communitys und Engagement</a>.
|
||||||
|
<p>
|
||||||
|
Für interessierte Hacker gibts auch den <a href="https://srcsrv.wikimedia.de/beba/foerderbarometer">Sourcecode</a> zum Formular und was damit passiert.
|
||||||
|
<p>
|
||||||
|
<a href="https://www.wikimedia.de/impressum/">Impressum</a>
|
||||||
|
</center>{% endblock %}
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
{% extends 'input/base.html' %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block head_extra %}
|
|
||||||
<title>Was möchtest du beantragen?</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<form class="page-centered" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<table class="wm-table start">
|
|
||||||
<tbody class="applications">
|
|
||||||
<tr>
|
|
||||||
<th class="col-request">Was möchtest du beantragen?</th>
|
|
||||||
<td>
|
|
||||||
{% for title, services in applications %}
|
|
||||||
<strong>{{ title }}</strong>
|
|
||||||
<ul>
|
|
||||||
{% for service in services %}
|
|
||||||
<li>
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="url" value="{% url 'extern' type=service.path %}" />
|
|
||||||
<span>{{ service.label|striptags }}</span>
|
|
||||||
</label>
|
|
||||||
<span>(<a href="{{ service.url }}" target="_blank">mehr erfahren</a>)</span>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<button type="submit">Beantragen</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
{% extends "input/base.html" %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block head_extra %}
|
|
||||||
<title>{{ type_label|striptags }}</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{{ form.media }}
|
|
||||||
|
|
||||||
<div class="page-centered">
|
|
||||||
<p>Du hast {{ type_label }} ausgewählt.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="post" class="wm-form" {% if form.is_multipart %}enctype="multipart/form-data"{% endif %}>
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
{% block pre_table %}{% endblock %}
|
|
||||||
|
|
||||||
<table class="wm-table">
|
|
||||||
{{ form.non_field_errors }}
|
|
||||||
{{ form.as_table }}
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{% block post_table %}{% endblock %}
|
|
||||||
|
|
||||||
<p class="page-centered"><span class="star">*</span> Pflichtfeld</p>
|
|
||||||
|
|
||||||
<div class="page-centered">
|
|
||||||
<button type="button" onclick="history.back()">Zurück</button>
|
|
||||||
<button type="submit">Absenden</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -8,7 +8,7 @@ Ende erreicht.<br><br>
|
||||||
|
|
||||||
Hier könnt ihr es in der Datenbank editieren:
|
Hier könnt ihr es in der Datenbank editieren:
|
||||||
<br><br>
|
<br><br>
|
||||||
<a href="{{URL_PREFIX}}/admin/input/project/{{project.pk}}/change">{{URL_PREFIX}}/admin/input/project/{{project.pk}}/change</a>
|
<a href="{{URLPREFIX}}/admin/input/project/{{project.pk}}/change">{{URLPREFIX}}/admin/input/project/{{project.pk}}/change</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
mit freundlichen Grüßen, Eure Lieblingsdatenbank
|
mit freundlichen Grüßen, Eure Lieblingsdatenbank
|
||||||
|
|
||||||
|
|
@ -5,6 +5,6 @@ Ende erreicht.
|
||||||
|
|
||||||
Hier könnt ihr es in der Datenbank editieren:
|
Hier könnt ihr es in der Datenbank editieren:
|
||||||
|
|
||||||
{{URL_PREFIX}}/admin/input/project/{{project.pk}}/change
|
{{URLPREFIX}}/admin/input/project/{{project.pk}}/change
|
||||||
|
|
||||||
mit freundlichen Grüßen, Eure Lieblingsdatenbank
|
mit freundlichen Grüßen, Eure Lieblingsdatenbank
|
||||||
|
|
@ -8,7 +8,7 @@ Ende erreicht.
|
||||||
<br><br>
|
<br><br>
|
||||||
Hier könnt ihr es in der Datenbank editieren:
|
Hier könnt ihr es in der Datenbank editieren:
|
||||||
<br><br>
|
<br><br>
|
||||||
<a href="{{URL_PREFIX}}/admin/input/project/{{project.pk}}/change">{{URL_PREFIX}}/admin/input/project/{{project.pk}}/change</a>
|
<a href="{{URLPREFIX}}/admin/input/project/{{project.pk}}/change">{{URLPREFIX}}/admin/input/project/{{project.pk}}/change</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
Projektorganisator*in wurde über den Projektabschluss informiert.
|
Projektorganisator*in wurde über den Projektabschluss informiert.
|
||||||
|
|
@ -5,7 +5,7 @@ Ende erreicht.
|
||||||
|
|
||||||
Hier könnt ihr es in der Datenbank editieren:
|
Hier könnt ihr es in der Datenbank editieren:
|
||||||
|
|
||||||
{{URL_PREFIX}}/admin/input/project/{{project.pk}}/change
|
{{URLPREFIX}}/admin/input/project/{{project.pk}}/change
|
||||||
|
|
||||||
|
|
||||||
Projektorganisator*in wurde über den Projektabschluss informiert.
|
Projektorganisator*in wurde über den Projektabschluss informiert.
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
Hallo Team Communitys und Engagement,
|
||||||
|
<br><br>
|
||||||
|
es gab einen neuen Antrag von {{data.realname}}.
|
||||||
|
<br><br>
|
||||||
|
Der Nutzer mit dem Username {{data.username}} ({{data.email}}) fragt ein_e {{data.typestring|striptags}} an.<br>
|
||||||
|
{% if data.choice in data.grant %}<br>
|
||||||
|
Vorraussichtliche Kosten: {{data.cost}}<br>
|
||||||
|
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}<br>
|
||||||
|
Domain: <a href="{{data.domain}}">{{data.domain}}</a><br>
|
||||||
|
Adressenbestandteil: {{data.address}} <br> {% endif %} {% if data.choice == 'BIB' %}
|
||||||
|
Bibliothek: {{data.library}}<br>
|
||||||
|
Dauer: {{data.duration}} <br> {% elif data.choice == 'ELIT' %}
|
||||||
|
Datenbank: {{data.library}}<br>
|
||||||
|
Dauer: {{data.duration}} <br> {% elif data.choice == 'SOFT' %}
|
||||||
|
Software: {{data.library}}<br>
|
||||||
|
Dauer: {{data.duration}} <br> {% elif data.choice == 'IFG'%}
|
||||||
|
Anfrage-URL: <a href="{{data.url}}">{{data.url}}</a> <br> {% elif data.choice == 'LIT'%}
|
||||||
|
Info zum Werk: {{data.info}}<br>
|
||||||
|
Bezugsquelle: {{data.source}} <br> {% elif data.choice == 'MAIL'%}
|
||||||
|
Adressenbestandteil frei gewählt: {{data.other}} <br> {% elif data.choice == 'VIS'%}
|
||||||
|
Wikimedia-Projekt: {{data.project}}<br>
|
||||||
|
Persönliche Daten: {{data.data}}<br>
|
||||||
|
Variante: {{data.variant}}<br>
|
||||||
|
Sendungsadrese: {{data.send_to}} <br> {% endif %}
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
Zum Eintrag in der Förderdatenbank:
|
||||||
|
{% if data.choice == 'BIB' %}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/library/{{data.pk}}/change">{{data.urlprefix}}/admin/input/library/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'ELIT'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/library/{{data.pk}}/change">{{data.urlprefix}}/admin/input/library/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'LIT'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/literature/{{data.pk}}/change">{{data.urlprefix}}/admin/input/literature/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'MAIL'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/email/{{data.pk}}/change">{{data.urlprefix}}/admin/input/email/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'IFG'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/ifg/{{data.pk}}/change">{{data.urlprefix}}/admin/input/ifg/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'LIST'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/list/{{data.pk}}/change">{{data.urlprefix}}/admin/input/list/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'TRAV'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/travel/{{data.pk}}/change">{{data.urlprefix}}/admin/input/travel/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'SOFT'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/library/{{data.pk}}/change">{{data.urlprefix}}/admin/input/library/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'VIS'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/businesscard/{{data.pk}}/change">{{data.urlprefix}}/admin/input/businesscard/{{data.pk}}/change</a>
|
||||||
|
{% endif %}
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
Zum Genehmigen hier klicken: <a href="{{data.urlprefix}}{% url 'authorize' data.choice data.pk %}">{{data.urlprefix}}{% url 'authorize' data.choice data.pk %}</a>
|
||||||
|
<br><br>
|
||||||
|
Zu Ablehnen hier klicken: <a href="{{data.urlprefix}}{% url 'deny' data.choice data.pk %}">{{data.urlprefix}}{% url 'deny' data.choice data.pk %}</a>
|
||||||
|
<br><br>
|
||||||
|
Stets zu Diensten, Deine Förderdatenbank
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
Hallo Team Communitys und Engagement,
|
||||||
|
|
||||||
|
es gab einen neuen Antrag von {{data.realname}}.
|
||||||
|
|
||||||
|
Der Nutzer mit dem Username {{data.username}} ({{data.email}}) fragt ein_e {{data.typestring|striptags}} an.
|
||||||
|
{% if data.choice in data.grant %}
|
||||||
|
Vorraussichtliche Kosten: {{data.cost}}
|
||||||
|
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}
|
||||||
|
Domain: {{data.domain}}
|
||||||
|
Adressenbestandteil: {{data.address}} {% endif %} {% if data.choice == 'BIB' %}
|
||||||
|
Bibliothek: {{data.library}}
|
||||||
|
Dauer: {{data.duration}} {% elif data.choice == 'ELIT' %}
|
||||||
|
Datenbank: {{data.library}}
|
||||||
|
Dauer: {{data.duration}} {% elif data.choice == 'SOFT' %}
|
||||||
|
Software: {{data.library}}
|
||||||
|
Dauer: {{data.duration}} {% elif data.choice == 'IFG'%}
|
||||||
|
Anfrage-URL: {{data.url}} {% elif data.choice == 'LIT'%}
|
||||||
|
Info zum Werk: {{data.info}}
|
||||||
|
Bezugsquelle: {{data.source}} {% elif data.choice == 'MAIL'%}
|
||||||
|
Adressenbestandteil frei gewählt: {{data.other}} {% elif data.choice == 'VIS'%}
|
||||||
|
Wikimedia-Projekt: {{data.project}}
|
||||||
|
Persönliche Daten: {{data.data}}
|
||||||
|
Variante: {{data.variant}}
|
||||||
|
Sendungsadrese: {{data.send_to}} {% endif %}
|
||||||
|
|
||||||
|
Zum Eintrag in der Förderdatenbank:
|
||||||
|
{% if data.choice == 'BIB' %}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/library/{{data.pk}}/change">{{data.urlprefix}}/admin/input/library/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'ELIT'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/library/{{data.pk}}/change">{{data.urlprefix}}/admin/input/library/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'LIT'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/literature/{{data.pk}}/change">{{data.urlprefix}}/admin/input/literature/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'MAIL'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/email/{{data.pk}}/change">{{data.urlprefix}}/admin/input/email/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'IFG'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/ifg/{{data.pk}}/change">{{data.urlprefix}}/admin/input/ifg/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'LIST'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/list/{{data.pk}}/change">{{data.urlprefix}}/admin/input/list/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'TRAV'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/travel/{{data.pk}}/change">{{data.urlprefix}}/admin/input/travel/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'SOFT'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/library/{{data.pk}}/change">{{data.urlprefix}}/admin/input/library/{{data.pk}}/change</a>
|
||||||
|
{% elif data.choice == 'VIS'%}
|
||||||
|
<a href="{{data.urlprefix}}/admin/input/businesscard/{{data.pk}}/change">{{data.urlprefix}}/admin/input/businesscard/{{data.pk}}/change</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
Zum Genehmigen hier klicken: {{data.urlprefix}}{% url 'authorize' data.choice data.pk %}
|
||||||
|
|
||||||
|
Zu Ablehnen hier klicken: {{data.urlprefix}}{% url 'deny' data.choice data.pk %}
|
||||||
|
|
||||||
|
Stets zu Diensten, Deine Förderdatenbank
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
Hallo {{data.realname}},
|
||||||
|
<br><br>
|
||||||
|
wir haben Deine Anfrage ({{data.typestring|striptags}}) erhalten.<br>
|
||||||
|
{% if data.choice in data.grant %}<br>
|
||||||
|
Vorraussichtliche Kosten: {{data.cost}}<br>
|
||||||
|
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}<br>
|
||||||
|
Domain: <a href="{{data.domain}}">{{data.domain}}</a><br>
|
||||||
|
Adressenbestandteil: {{data.address}} {% endif %} {% if data.choice == 'BIB' %}<br>
|
||||||
|
Bibliothek: {{data.library}}<br>
|
||||||
|
Dauer: {{data.duration}} {% elif data.choice == 'ELIT' %}<br>
|
||||||
|
Datenbank: {{data.library}}<br>
|
||||||
|
Dauer: {{data.duration}} {% elif data.choice == 'SOFT' %}<br>
|
||||||
|
Software: {{data.library}}<br>
|
||||||
|
Dauer: {{data.duration}} {% elif data.choice == 'IFG'%}<br>
|
||||||
|
Anfrage-URL: <a href="{{data.url}}">{{data.url}}</a> {% elif data.choice == 'LIT'%}<br>
|
||||||
|
Info zum Werk: {{data.info}}<br>
|
||||||
|
Bezugsquelle: {{data.source}} {% elif data.choice == 'MAIL'%}<br>
|
||||||
|
Adressenbestandteil frei gewählt: {{data.other}} {% elif data.choice == 'VIS'%}<br>
|
||||||
|
Wikimedia-Projekt: {{data.project}}<br>
|
||||||
|
Persönliche Daten: {{data.data}}<br>
|
||||||
|
Variante: {{data.variant}}<br>
|
||||||
|
Sendungsadrese: {{data.send_to}} {% endif %}<br>
|
||||||
|
<br><br>
|
||||||
|
Das Team Comunitys und Engagement wird sich um die Bearbeitung deiner Anfrage kümmern<br>
|
||||||
|
und sich in den nächsten Tagen bei dir melden. Solltest du Rückfragen haben,<br>
|
||||||
|
wende dich gern an <a href = "mailto: community@wikimedia.de">community@wikimedia.de</a>.<br>
|
||||||
|
<br><br>
|
||||||
|
Viele Grüße, dein freundliches aber komplett unmenschliches automatisches
|
||||||
|
Formularbeantwortungssystem.
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
Hallo {{data.realname}},
|
||||||
|
|
||||||
|
wir haben Deine Anfrage ({{data.typestring|striptags}}) erhalten.
|
||||||
|
{% if data.choice in data.grant %}
|
||||||
|
Vorraussichtliche Kosten: {{data.cost}}
|
||||||
|
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}
|
||||||
|
Domain: {{data.domain}}
|
||||||
|
Adressenbestandteil: {{data.address}} {% endif %} {% if data.choice == 'BIB' %}
|
||||||
|
Bibliothek: {{data.library}}
|
||||||
|
Dauer: {{data.duration}} {% elif data.choice == 'ELIT' %}
|
||||||
|
Datenbank: {{data.library}}
|
||||||
|
Dauer: {{data.duration}} {% elif data.choice == 'SOFT' %}
|
||||||
|
Software: {{data.library}}
|
||||||
|
Dauer: {{data.duration}} {% elif data.choice == 'IFG'%}
|
||||||
|
Anfrage-URL: {{data.url}} {% elif data.choice == 'LIT'%}
|
||||||
|
Info zum Werk: {{data.info}}
|
||||||
|
Bezugsquelle: {{data.source}} {% elif data.choice == 'MAIL'%}
|
||||||
|
Adressenbestandteil frei gewählt: {{data.other}} {% elif data.choice == 'VIS'%}
|
||||||
|
Wikimedia-Projekt: {{data.project}}
|
||||||
|
Persönliche Daten: {{data.data}}
|
||||||
|
Variante: {{data.variant}}
|
||||||
|
Sendungsadrese: {{data.send_to}} {% endif %}
|
||||||
|
|
||||||
|
Das Team Comunitys und Engagement wird sich um die Bearbeitung deiner Anfrage kümmern
|
||||||
|
und sich in den nächsten Tagen bei dir melden. Solltest du Rückfragen haben,
|
||||||
|
wende dich gern an community@wikimedia.de.
|
||||||
|
|
||||||
|
Viele Grüße, dein freundliches aber komplett unmenschliches automatisches
|
||||||
|
Formularbeantwortungssystem.
|
||||||
|
|
@ -1,38 +1,69 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/widgets.css' %}" />
|
||||||
|
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}"/>
|
{% csrf_token %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/widgets.css' %}"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/base.css' %}"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/button.css' %}">
|
|
||||||
|
|
||||||
<div class="page-centered">
|
|
||||||
<div class="spacer-5"></div>
|
<center>
|
||||||
<p role="heading" aria-level="1">
|
<style>
|
||||||
Herzlich willkommen im Förderanfrageportal von Wikimedia Deutschland!
|
ul > li {
|
||||||
</p>
|
list-style-type: none;
|
||||||
<div class="spacer-5"></div>
|
}
|
||||||
<p>
|
ul {
|
||||||
<img src="https://upload.wikimedia.org/wikipedia/commons/c/c4/Figuren_klein.jpg">
|
padding-left: 10;
|
||||||
</p>
|
}
|
||||||
<p>
|
label.required::after {
|
||||||
Um eine Unterstützungsleistung im Rahmen der Förderangebote anfragen zu können, verifiziere dich bitte mit
|
content: ' *';
|
||||||
deinem Wikimedia-Konto.
|
color: red;
|
||||||
<br>Weitere Informationen und Hintergründe findest du unter
|
}
|
||||||
<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Förderangebote">
|
.div15 {
|
||||||
|
height: 15%;
|
||||||
|
}
|
||||||
|
.div5 {
|
||||||
|
height: 5%;
|
||||||
|
}
|
||||||
|
.button1 {
|
||||||
|
width: 40vw;
|
||||||
|
height: 6vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 4vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="div5"></div>
|
||||||
|
<p>
|
||||||
|
Herzlich willkommen im Förderanfrageportal von Wikimedia Deutschland!
|
||||||
|
</p>
|
||||||
|
<div class="div5"></div>
|
||||||
|
<!-- <a href="http://fdb-devel.wikimedia.de/extern"style="float:right;padding-right:10%;">OAUTH</a>
|
||||||
|
<a href="http://fdb-devel.wikimedia.de/extern" style="float:left;padding-left:10%;">OAUTH</a> -->
|
||||||
|
<p>
|
||||||
|
<img src="https://upload.wikimedia.org/wikipedia/commons/c/c4/Figuren_klein.jpg"><p>
|
||||||
|
Um eine Unterstützungsleistung im Rahmen der Förderangebote anfragen zu können, verifiziere dich bitte mit deinem Wikimedia-Konto.
|
||||||
|
<br>Weitere Informationen und Hintergründe findest du unter
|
||||||
|
<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Förderangebote">
|
||||||
Förderportal</a> in der deutschsprachigen Wikipedia.
|
Förderportal</a> in der deutschsprachigen Wikipedia.
|
||||||
</p>
|
<p>
|
||||||
<div class="spacer-5"></div>
|
<div class="div5"></div>
|
||||||
<a href="/extern" class="button button-login">Anmelden</a>
|
<div class="button button1"><a href="/extern"><div class="button1_text">Anmelden</div></a></div>
|
||||||
<div class="spacer-5"></div>
|
<div class="div5"></div>
|
||||||
<div class="spacer-5"></div>
|
<div class="div5"></div>
|
||||||
<br>Für alle Fragen wende dich gern an das <a
|
<br>Für alle Fragen wende dich gern an das <a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Wikimedia_Deutschland">Team Communitys und Engagement</a>.
|
||||||
href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Wikimedia_Deutschland">Team Communitys und
|
<br>Für interessierte Hacker gibts auch den <a href="https://srcsrv.wikimedia.de/beba/foerderbarometer">Sourcecode</a> zum Formular und was damit passiert.
|
||||||
Engagement</a>.
|
<p>
|
||||||
<br>Für interessierte Hacker gibts auch den
|
|
||||||
<a href="https://srcsrv.wikimedia.de/beba/foerderbarometer">Sourcecode</a> zum Formular und was damit passiert.
|
|
||||||
<p>
|
|
||||||
<a href="https://www.wikimedia.de/impressum/">Impressum</a>
|
<a href="https://www.wikimedia.de/impressum/">Impressum</a>
|
||||||
</p>
|
<p>
|
||||||
</div>
|
</center>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
{% extends 'input/base.html' %}
|
|
||||||
|
|
||||||
{% block head_extra %}
|
|
||||||
<title>Projektförderung ab 1.000,— EUR</title>
|
|
||||||
<style>
|
|
||||||
.wm-main {
|
|
||||||
max-width: 80vw;
|
|
||||||
margin: 0 auto;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Projektförderung mit einer Gesamtsumme ab 1.000,— EUR</h1>
|
|
||||||
<p>Vielen Dank für dein Interesse an einer Projektförderung!<br>
|
|
||||||
Für Projektförderungen mit einer Gesamtsumme ab 1.000,— EUR ist ein öffentlicher Projektplan
|
|
||||||
erforderlich. Weitere Informationen zu diesem Prozess findest du unter
|
|
||||||
<a href="https://de.wikipedia.org/wiki/Wikipedia:F%C3%B6rderung/Projektplanung" target="_blank" rel="noopener">
|
|
||||||
Wikipedia:Förderung/Projektplanung</a>.<br>
|
|
||||||
Für Fragen steht dir das Team Community-Konferenzen & Förderung gern unter
|
|
||||||
<a href="mailto:community@wikimedia.de">community@wikimedia.de</a> zur Verfügung.
|
|
||||||
</p>
|
|
||||||
<div class="page-centered">
|
|
||||||
<button type="button" onclick="history.back()">Zurück</button>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
<div class="wm-footer page-centered">
|
|
||||||
<p>
|
|
||||||
<img src="https://upload.wikimedia.org/wikipedia/commons/c/c4/Figuren_klein.jpg" alt=""/>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Eine Übersicht aller Förderangebote von Wikimedia Deutschland findest du im
|
|
||||||
<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Förderangebote">Förderportal in der deutschsprachigen
|
|
||||||
Wikipedia</a>.
|
|
||||||
<br>
|
|
||||||
Für alle Fragen wende dich gern an das
|
|
||||||
<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Wikimedia_Deutschland">Team Communitys und
|
|
||||||
Engagement</a>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Für interessierte Hacker gibts auch den
|
|
||||||
<a href="https://srcsrv.wikimedia.de/beba/foerderbarometer">Sourcecode</a> zum Formular und was damit passiert.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="https://www.wikimedia.de/impressum/">Impressum</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
{% load static %}
|
|
||||||
<div class="wm-header page-centered">
|
|
||||||
<img src="{% static 'input/logo.png' %}" alt="Wikimedia Deutschland"/>
|
|
||||||
</div>
|
|
||||||
|
|
@ -34,7 +34,7 @@ Förderprogramme im Sinne der Communitys weiter zu verbessern. Wir freuen uns,
|
||||||
wenn du dir kurz die Zeit dafür nehmen würdest. Die Umfrage mit weiteren
|
wenn du dir kurz die Zeit dafür nehmen würdest. Die Umfrage mit weiteren
|
||||||
Informationen findest du unter dem folgenden Link:<br>
|
Informationen findest du unter dem folgenden Link:<br>
|
||||||
|
|
||||||
<a href="{{SURVEY_PREFIX}}{% if type == 'PRO' %}O{% else %}I{% endif %}=1&{{pid}}=1">{{SURVEY_PREFIX}}{% if type == 'PRO' %}O{% else %}I{% endif %}=1&{{pid}}=1</a><br><br>
|
<a href="{{SURVEYPREFIX}}{% if type == 'PRO' %}O{% else %}I{% endif %}=1&{{pid}}=1">{{SURVEYPREFIX}}{% if type == 'PRO' %}O{% else %}I{% endif %}=1&{{pid}}=1</a><br><br>
|
||||||
|
|
||||||
Da dies eine automatisch erzeugte Nachricht ist, wende dich bei Rückfragen zur Umfrage bitte an <a href="mailto: community@wikimedia.de">community@wikimedia.de</a>
|
Da dies eine automatisch erzeugte Nachricht ist, wende dich bei Rückfragen zur Umfrage bitte an <a href="mailto: community@wikimedia.de">community@wikimedia.de</a>
|
||||||
|
|
||||||
|
|
@ -32,6 +32,6 @@ Förderprogramme im Sinne der Communitys weiter zu verbessern. Wir freuen uns,
|
||||||
wenn du dir kurz die Zeit dafür nehmen würdest. Die Umfrage mit weiteren
|
wenn du dir kurz die Zeit dafür nehmen würdest. Die Umfrage mit weiteren
|
||||||
Informationen findest du unter dem folgenden Link:
|
Informationen findest du unter dem folgenden Link:
|
||||||
|
|
||||||
{{SURVEY_PREFIX}}{% if type == 'PRO' %}O{% else %}I{% endif %}=1&{{pid}}=1
|
{{SURVEYPREFIX}}{% if type == 'PRO' %}O{% else %}I{% endif %}=1&{{pid}}=1
|
||||||
|
|
||||||
Da dies eine automatisch erzeugte Nachricht ist, wende dich bei Rückfragen zur Umfrage bitte an community@wikimedia.de
|
Da dies eine automatisch erzeugte Nachricht ist, wende dich bei Rückfragen zur Umfrage bitte an community@wikimedia.de
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
<html lang="de">
|
|
||||||
<body>
|
|
||||||
<p>Hallo Team Community-Konferenzen & Förderung,</p>
|
|
||||||
|
|
||||||
<p>es gibt eine neue Anfrage von {{ data.realname }}.</p>
|
|
||||||
|
|
||||||
<p>{{ data.username|default:data.realname }} ({{ data.email }}) fragt an: {{ type_label }}</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
{% for label, value in form_data.items %}
|
|
||||||
{{ label }}: {{ value }} <br />
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>Zum Eintrag in der Förderdatenbank: <a href="{{ urls.admin }}">{{ urls.admin }}</a></p>
|
|
||||||
|
|
||||||
{% if urls.authorize %}
|
|
||||||
<p>Zum Genehmigen hier klicken: <a href="{{ urls.authorize }}">{{ urls.authorize }}</a></p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if urls.deny %}
|
|
||||||
<p>Zum Ablehnen hier klicken: <a href="{{ urls.deny }}">{{ urls.deny }}</a></p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
Hallo Team Community-Konferenzen & Förderung,
|
|
||||||
|
|
||||||
es gibt eine neue Anfrage von {{ data.realname }}.
|
|
||||||
|
|
||||||
{{ data.username|default:data.realname }} ({{ data.email }}) fragt an: {{ type_label }}
|
|
||||||
|
|
||||||
{% for label, value in form_data.items %}{{ label }}: {{ value }}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
Zum Eintrag in der Förderdatenbank: {{ urls.admin }}
|
|
||||||
{% if urls.authorize %}Zum Genehmigen hier klicken: {{ urls.authorize }}{% endif %}
|
|
||||||
{% if urls.deny %}Zum Ablehnen hier klicken: {{ urls.deny }}{% endif %}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
<html lang="de">
|
|
||||||
<body>
|
|
||||||
<p>Hallo {{ applicant_name }},</p>
|
|
||||||
|
|
||||||
<p>vielen Dank für deine Anfrage ({{ type_label }}), die bei uns eingegangen ist.</p>
|
|
||||||
|
|
||||||
Dies ist eine automatisch generierte E-Mail. Im Folgenden findest du deine Formulareingaben nochmals zu deiner Übersicht:<br>
|
|
||||||
|
|
||||||
{% for label, value in form_data.items %}
|
|
||||||
{{ label }}: {{ value }} <br />
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<p>Das Team Community-Konferenzen & Förderung wird sich um deine Anfrage kümmern und sich in den nächsten Tagen bei dir melden. Wenn du Fragen hast, wende dich gern jederzeit an <a href="mailto:community@wikimedia.de">community@wikimedia.de</a>.</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
--<br>
|
|
||||||
Wikimedia Deutschland e. V. | Tempelhofer Ufer 23–24 | 10963 Berlin<br>
|
|
||||||
Zentrale: +49 30 5771162-0<br>
|
|
||||||
<a href="https://wikimedia.de">wikimedia.de</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Unsere Vision ist eine Welt, in der alle Menschen am Wissen der Menschheit teilhaben, es nutzen und mehren können. Helfen Sie uns dabei!<br>
|
|
||||||
<a href="https://spenden.wikimedia.de">spenden.wikimedia.de</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>Wikimedia Deutschland – Gesellschaft zur Förderung Freien Wissens e. V. Eingetragen im Vereinsregister des Amtsgerichts Charlottenburg, VR 23855 B. Als gemeinnützig anerkannt durch das Finanzamt für Körperschaften I Berlin, Steuernummer 27/029/42207. Geschäftsführende Vorständin: Franziska Heine.</p>
|
|
||||||
|
|
||||||
<p>Datenschutzerklärung:<br>
|
|
||||||
Soweit Sie uns personenbezogene Daten mitteilen, verarbeiten wir diese Daten gemäß unserer <a href="https://www.wikimedia.de/datenschutz/">Datenschutzerklärung </a>.</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
Hallo {{ applicant_name }},
|
|
||||||
|
|
||||||
vielen Dank für deine Anfrage ({{type_label}}), die bei uns eingegangen ist.
|
|
||||||
|
|
||||||
{% for label, value in form_data.items %}{{ label }}: {{ value }}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
Das Team Community-Konferenzen & Förderung wird sich um deine Anfrage kümmern und sich in den nächsten Tagen bei dir melden. Wenn du Fragen hast, wende dich gern jederzeit an community@wikimedia.de.
|
|
||||||
|
|
||||||
--
|
|
||||||
|
|
||||||
Wikimedia Deutschland e. V. | Tempelhofer Ufer 23–24 | 10963 Berlin
|
|
||||||
Zentrale: +49 30 5771162-0
|
|
||||||
https://wikimedia.de
|
|
||||||
|
|
||||||
Unsere Vision ist eine Welt, in der alle Menschen am Wissen der Menschheit teilhaben, es nutzen und mehren können. Helfen Sie uns dabei!
|
|
||||||
https://spenden.wikimedia.de
|
|
||||||
|
|
||||||
Wikimedia Deutschland – Gesellschaft zur Förderung Freien Wissens e. V. Eingetragen im Vereinsregister des Amtsgerichts Charlottenburg, VR 23855 B. Als gemeinnützig anerkannt durch das Finanzamt für Körperschaften I Berlin, Steuernummer 27/029/42207. Geschäftsführende Vorständin: Franziska Heine.
|
|
||||||
|
|
||||||
Datenschutzerklärung:
|
|
||||||
Soweit Sie uns personenbezogene Daten mitteilen, verarbeiten wir diese Daten gemäß unserer Datenschutzerklärung (https://www.wikimedia.de/datenschutz/).
|
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
from django.test import TestCase, Client
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from .models import HonoraryCertificate, Project, Account, Literature
|
||||||
|
from .forms import LibraryForm
|
||||||
|
|
||||||
|
class TestWithoutLogin(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
#this setting supress an unwanted warning about missing root dir
|
||||||
|
settings.WHITENOISE_AUTOREFRESH = True
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
def test_set_granted(self):
|
||||||
|
'''test if the model function set_granted() works as intended'''
|
||||||
|
obj = HonoraryCertificate.objects.create(realname='hurzel',email='hurzel@web.de')
|
||||||
|
self.assertEqual(obj.granted,None)
|
||||||
|
HonoraryCertificate.set_granted(obj.pk, True)
|
||||||
|
obj2 = HonoraryCertificate.objects.get(pk=obj.pk)
|
||||||
|
self.assertEqual(obj2.granted,True)
|
||||||
|
|
||||||
|
def test_source_link(self):
|
||||||
|
'''test if link to source code is included in main page'''
|
||||||
|
response = self.client.get('', follow=True)
|
||||||
|
#print (response.content)
|
||||||
|
self.assertContains(response,'<a href="https://srcsrv.wikimedia.de/beba/foerderbarometer">Sourcecode</a>')
|
||||||
|
|
||||||
|
def test_access_denied(self):
|
||||||
|
'''test if /intern redirects to login page if not logged in'''
|
||||||
|
response = self.client.get('/intern', follow=True)
|
||||||
|
self.assertContains(response,'password')
|
||||||
|
|
||||||
|
def _postform(self, data, expected_form):
|
||||||
|
'''helper function to manage the Wizzard'''
|
||||||
|
response = self.client.post('/', data, follow=False)
|
||||||
|
self.assertEqual(200, self.response.status_code)
|
||||||
|
if not type(response) == HttpResponse:
|
||||||
|
if 'form' in response.context:
|
||||||
|
print('CONTENT')
|
||||||
|
print(response.content)
|
||||||
|
print('ITEMS')
|
||||||
|
print(response.items())
|
||||||
|
print('DATA')
|
||||||
|
print(data)
|
||||||
|
self.assertFalse(response.context['form'].errors)
|
||||||
|
else:
|
||||||
|
if expected_form:
|
||||||
|
print(response.context)
|
||||||
|
raise BaseException("NO FORM FOUND")
|
||||||
|
else:
|
||||||
|
self.assertContains(response,"Deine Anfrage wurde gesendet.")
|
||||||
|
self.assertEqual(
|
||||||
|
type(response.context['wizard']['form']),
|
||||||
|
expected_form
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _notest_bib(self): # renamed because not working
|
||||||
|
'''full run through the forms to check Bibliotheksstipendium'''
|
||||||
|
self.response = self.client.get('/')
|
||||||
|
self.assertEqual(200, self.response.status_code)
|
||||||
|
|
||||||
|
print("\n\nEINS EINS\n\n")
|
||||||
|
|
||||||
|
response = self._postform({
|
||||||
|
'extern_view-current_step': '0',
|
||||||
|
'0-realname': 'vladimir reiherzehe',
|
||||||
|
'0-email': 'vlre@wikimedia.de',
|
||||||
|
'0-username': 'stoffel',
|
||||||
|
'0-choice': 'BIB',
|
||||||
|
'0-check': True
|
||||||
|
}, LibraryForm)
|
||||||
|
|
||||||
|
print("\n\nZWEI ZWEI\n\n")
|
||||||
|
|
||||||
|
response = self._postform({
|
||||||
|
'extern_view-current_step': '1',
|
||||||
|
'1-cost': 'teuroooo!',
|
||||||
|
'1-duration': 'looooong',
|
||||||
|
'1-library': 'of congress',
|
||||||
|
}, None)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TestWithLogin(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
User.objects.create_superuser('testuser', 'nomail@nomail.com', 'testpasswd')
|
||||||
|
self.client = Client()
|
||||||
|
self.user = User.objects.create_user('vladimir', 'vladimir@reiherzehe.com', 'reiherzehe')
|
||||||
|
|
||||||
|
def test_access(self):
|
||||||
|
'''test if /intern gives an answer'''
|
||||||
|
self.assertEqual(self.client.login(username='testuser', password='testpasswd'), True)
|
||||||
|
response = self.client.get('/intern')
|
||||||
|
self.assertContains(response,'Übersicht aller Förderangebote')
|
||||||
|
|
||||||
|
def test_project_of_year(self):
|
||||||
|
''' test if the finance id is resettet ad start of year'''
|
||||||
|
acc = Account.objects.create()
|
||||||
|
acc.code='1234'
|
||||||
|
acc.description='blabla'
|
||||||
|
acc.save()
|
||||||
|
startdate = date(2022,1,1)
|
||||||
|
obj = Project.objects.create(account= acc, name='testproject', start=startdate)
|
||||||
|
self.assertEqual(obj.project_of_year,1)
|
||||||
|
|
||||||
|
obj2 = Project.objects.create(account= acc, name='testproject2', start=startdate)
|
||||||
|
self.assertEqual(obj2.project_of_year,2)
|
||||||
|
|
||||||
|
olddate = date(2021,12,31)
|
||||||
|
obj4 = Project.objects.create(account= acc, name='testproject2', start=olddate)
|
||||||
|
|
||||||
|
obj3 = Project.objects.create(account= acc, name='testproject2', start=startdate)
|
||||||
|
self.assertEqual(obj3.project_of_year,3)
|
||||||
|
|
||||||
|
def test_finance_id(self):
|
||||||
|
''' test if the finance counting is correct'''
|
||||||
|
acc = Account.objects.create(code='1234', description='blabla')
|
||||||
|
startdate = date(2022,1,1)
|
||||||
|
obj = Project.objects.create(account= acc, name='testproject', start=startdate)
|
||||||
|
self.assertEqual(obj.finance_id,"1234001")
|
||||||
|
|
||||||
|
obj2 = Project.objects.create(account= acc, name='testproject2', start=startdate)
|
||||||
|
self.assertEqual(obj2.finance_id,"1234002")
|
||||||
|
|
||||||
|
olddate = date(2021,12,31)
|
||||||
|
obj4 = Project.objects.create(account= acc, name='testproject2', start=olddate)
|
||||||
|
|
||||||
|
obj3 = Project.objects.create(account= acc, name='testproject2', start=startdate)
|
||||||
|
self.assertEqual(obj3.finance_id,"1234003")
|
||||||
|
|
||||||
|
# def test_pid(self):
|
||||||
|
# ''' test if the pid counting is correct '''
|
||||||
|
# acc = Account.objects.create(code='1234', description='blabla')
|
||||||
|
# startdate = date(2022,1,1)
|
||||||
|
# obj = Project.objects.create(account= acc, name='testproject', start=startdate)
|
||||||
|
# self.assertEqual(obj.pid,"1234001")
|
||||||
|
# self.assertEqual(obj.account.code,"1234")
|
||||||
|
#
|
||||||
|
# obj2 = Project.objects.create(account= acc, name='testproject2', start=startdate)
|
||||||
|
# self.assertEqual(obj2.pid,"1234002")
|
||||||
|
#
|
||||||
|
# olddate = date(2021,12,31)
|
||||||
|
# obj4 = Project.objects.create(account= acc, name='testproject2', start=olddate)
|
||||||
|
#
|
||||||
|
# obj3 = Project.objects.create(account= acc, name='testproject2', start=startdate)
|
||||||
|
# self.assertEqual(obj3.pid,"1234004")
|
||||||
|
|
||||||
|
def test_literature(self):
|
||||||
|
obj = Literature.objects.create(cost='100', notes='jolo')
|
||||||
|
self.assertEqual(obj.service_id,'Literature1')
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
from .admin import AdminTestCase
|
|
||||||
from .models import ModelTestCase
|
|
||||||
from .views import AuthenticatedViewTestCase, AnonymousViewTestCase
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
import datetime
|
|
||||||
|
|
||||||
from django.forms import model_to_dict
|
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
from input.models import (
|
|
||||||
Project,
|
|
||||||
ProjectRequest,
|
|
||||||
ProjectDeclined,
|
|
||||||
Library,
|
|
||||||
ELiterature,
|
|
||||||
Email,
|
|
||||||
IFG,
|
|
||||||
Literature,
|
|
||||||
List,
|
|
||||||
Travel,
|
|
||||||
Software,
|
|
||||||
BusinessCard, ProjectCategory, WikimediaProject, Account,
|
|
||||||
)
|
|
||||||
from input.utils.admin import admin_url
|
|
||||||
from input.utils.testing import request, create_superuser, login
|
|
||||||
|
|
||||||
|
|
||||||
class AdminTestCase(TestCase):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
cls.user = user = create_superuser('admin', first_name='Max', last_name='Mustermann')
|
|
||||||
|
|
||||||
cls.data = data = {
|
|
||||||
'realname': f'{user.first_name} {user.last_name}',
|
|
||||||
'email': user.email,
|
|
||||||
}
|
|
||||||
|
|
||||||
cls.objs = [
|
|
||||||
ProjectCategory.objects.order_by('?').first(),
|
|
||||||
WikimediaProject.objects.order_by('?').first(),
|
|
||||||
Project.objects.create(**data, granted=True),
|
|
||||||
ProjectRequest.objects.create(**data, granted=None),
|
|
||||||
ProjectDeclined.objects.create(**data, granted=False),
|
|
||||||
Library.objects.create(**data, library='Test'),
|
|
||||||
ELiterature.objects.create(**data, library='Test'),
|
|
||||||
Software.objects.create(**data, library='Test'),
|
|
||||||
Email.objects.create(**data),
|
|
||||||
IFG.objects.create(**data),
|
|
||||||
Literature.objects.create(**data, selfbuy_give_data=False),
|
|
||||||
List.objects.create(**data),
|
|
||||||
Travel.objects.create(**data),
|
|
||||||
BusinessCard.objects.create(**data),
|
|
||||||
]
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
login(self)
|
|
||||||
|
|
||||||
def test_changelists(self):
|
|
||||||
for obj in self.objs:
|
|
||||||
model = type(obj)
|
|
||||||
url = admin_url(model, 'changelist')
|
|
||||||
|
|
||||||
with self.subTest(model=model):
|
|
||||||
request(self, url)
|
|
||||||
|
|
||||||
def test_change_views(self):
|
|
||||||
for obj in self.objs:
|
|
||||||
model = type(obj)
|
|
||||||
url = admin_url(model, 'change', obj.id)
|
|
||||||
|
|
||||||
with self.subTest(model=model):
|
|
||||||
request(self, url)
|
|
||||||
|
|
||||||
def test_display_values(self):
|
|
||||||
for obj in self.objs:
|
|
||||||
model = type(obj)
|
|
||||||
|
|
||||||
with self.subTest(model=model):
|
|
||||||
self.assertTrue(f'{obj}')
|
|
||||||
|
|
||||||
def test_grant_project_request(self):
|
|
||||||
account = Account.objects.create(code='test')
|
|
||||||
category = ProjectCategory.objects.first()
|
|
||||||
wikimedia = WikimediaProject.objects.first()
|
|
||||||
obj = ProjectRequest.objects.create(
|
|
||||||
**self.data,
|
|
||||||
name='Test',
|
|
||||||
description='Test',
|
|
||||||
otrs='https://example.com',
|
|
||||||
granted=None,
|
|
||||||
start=datetime.date(2025, 1, 1),
|
|
||||||
end=datetime.date(2026, 1, 1),
|
|
||||||
)
|
|
||||||
|
|
||||||
obj.categories.add(category)
|
|
||||||
obj.wikimedia_projects.add(wikimedia)
|
|
||||||
|
|
||||||
url = admin_url(ProjectRequest, 'change', obj.id)
|
|
||||||
expected_url = admin_url(ProjectRequest, 'changelist')
|
|
||||||
data = {
|
|
||||||
**model_to_dict(obj),
|
|
||||||
'granted': True,
|
|
||||||
'granted_date': obj.start,
|
|
||||||
'granted_from': self.user.username,
|
|
||||||
'account': account.code,
|
|
||||||
'categories': [category.id],
|
|
||||||
'wikimedia_projects': [wikimedia.id],
|
|
||||||
}
|
|
||||||
|
|
||||||
for key in list(data):
|
|
||||||
if data[key] is None:
|
|
||||||
data.pop(key)
|
|
||||||
|
|
||||||
request(self, url, expected_url=expected_url, data=data)
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
from datetime import date
|
|
||||||
|
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
from input.models import HonoraryCertificate, Project, Account, Literature
|
|
||||||
|
|
||||||
|
|
||||||
class ModelTestCase(TestCase):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
cls.account = Account.objects.create(code='1234', description='blabla')
|
|
||||||
|
|
||||||
def assertLen(self, value, length, msg=None):
|
|
||||||
self.assertEqual(len(value), length, msg)
|
|
||||||
|
|
||||||
def assertStartsWith(self, value, start, msg=None):
|
|
||||||
self.assertTrue(f'{value}'.startswith(f'{start}'), msg)
|
|
||||||
|
|
||||||
def assertEndsWith(self, value, end, msg=None):
|
|
||||||
self.assertTrue(f'{value}'.endswith(f'{end}'), msg)
|
|
||||||
|
|
||||||
def test_set_granted(self):
|
|
||||||
""" test if the model function set_granted() works as intended """
|
|
||||||
obj = HonoraryCertificate.objects.create(realname='hurzel', email='hurzel@web.de')
|
|
||||||
self.assertEqual(obj.granted, None)
|
|
||||||
HonoraryCertificate.set_granted(obj.pk, True)
|
|
||||||
obj2 = HonoraryCertificate.objects.get(pk=obj.pk)
|
|
||||||
self.assertEqual(obj2.granted, True)
|
|
||||||
|
|
||||||
def test_project_of_year(self):
|
|
||||||
""" test if the finance id is resettet ad start of year """
|
|
||||||
acc = self.account
|
|
||||||
startdate = date(2022, 1, 1)
|
|
||||||
obj = Project.objects.create(account=acc, name='testproject', start=startdate)
|
|
||||||
self.assertEqual(obj.project_of_year, 1)
|
|
||||||
|
|
||||||
obj2 = Project.objects.create(account=acc, name='testproject2', start=startdate)
|
|
||||||
self.assertEqual(obj2.project_of_year, 2)
|
|
||||||
|
|
||||||
olddate = date(2021, 12, 31)
|
|
||||||
obj4 = Project.objects.create(account=acc, name='testproject2', start=olddate)
|
|
||||||
|
|
||||||
obj3 = Project.objects.create(account=acc, name='testproject2', start=startdate)
|
|
||||||
self.assertEqual(obj3.project_of_year, 3)
|
|
||||||
|
|
||||||
def test_finance_id(self):
|
|
||||||
""" test if the finance counting is correct """
|
|
||||||
acc = self.account
|
|
||||||
startdate = date(2022, 1, 1)
|
|
||||||
obj = Project.objects.create(account=acc, name='testproject', start=startdate)
|
|
||||||
self.assertEqual(obj.finance_id, "1234")
|
|
||||||
|
|
||||||
obj2 = Project.objects.create(account=acc, name='testproject2', start=startdate)
|
|
||||||
self.assertEqual(obj2.finance_id, "1234")
|
|
||||||
|
|
||||||
olddate = date(2021, 12, 31)
|
|
||||||
obj4 = Project.objects.create(account=acc, name='testproject2', start=olddate)
|
|
||||||
|
|
||||||
obj3 = Project.objects.create(account=acc, name='testproject2', start=startdate)
|
|
||||||
self.assertEqual(obj3.finance_id, "1234")
|
|
||||||
|
|
||||||
def test_financed_id_for_subaccounts(self):
|
|
||||||
account = Account.objects.create(code='21111', description='has subaccounts')
|
|
||||||
obj = Project.objects.create(account=account, name='test', start=date(2025, 1, 1))
|
|
||||||
|
|
||||||
self.assertEqual(obj.finance_id, f'{account.code}-001')
|
|
||||||
|
|
||||||
def test_finance_id_later(self):
|
|
||||||
obj = Project.objects.create(name='test', start=date(2025, 1, 1))
|
|
||||||
|
|
||||||
self.assertFalse(obj.finance_id)
|
|
||||||
|
|
||||||
obj.account = self.account
|
|
||||||
obj.save()
|
|
||||||
|
|
||||||
self.assertTrue(obj.finance_id)
|
|
||||||
|
|
||||||
def test_pid(self):
|
|
||||||
""" test if the pid counting is correct """
|
|
||||||
acc = self.account
|
|
||||||
startdate = date(2022, 1, 1)
|
|
||||||
obj = Project.objects.create(account=acc, name='testproject', start=startdate)
|
|
||||||
self.assertLen(obj.pid, len(acc.code) + 8)
|
|
||||||
self.assertStartsWith(obj.pid, acc.code)
|
|
||||||
self.assertEndsWith(obj.pid, obj.id)
|
|
||||||
|
|
||||||
obj2 = Project.objects.create(account=acc, name='testproject2', start=startdate)
|
|
||||||
self.assertEndsWith(obj2.pid, obj.id + 1)
|
|
||||||
|
|
||||||
olddate = date(2021, 12, 31)
|
|
||||||
Project.objects.create(account=acc, name='testproject2', start=olddate)
|
|
||||||
|
|
||||||
obj4 = Project.objects.create(account=acc, name='testproject2', start=startdate)
|
|
||||||
self.assertEndsWith(obj4.pid, obj.id + 3)
|
|
||||||
|
|
||||||
def test_literature(self):
|
|
||||||
obj = Literature.objects.create(cost='100', notes='jolo', selfbuy_give_data=False)
|
|
||||||
self.assertEqual(obj.service_id, f'Literature{obj.id}')
|
|
||||||
|
|
@ -1,197 +0,0 @@
|
||||||
import random
|
|
||||||
|
|
||||||
from django.forms import model_to_dict
|
|
||||||
from django.shortcuts import resolve_url
|
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
from foerderbarometer.constants import *
|
|
||||||
from input.models import Library, ProjectCategory, WikimediaProject
|
|
||||||
from input.utils.testing import create_superuser, login, request
|
|
||||||
from input.views import PROJECT_FUNDING, TYPES, ApplicationView
|
|
||||||
|
|
||||||
PATHS = {TYPES[path].code: path for path in TYPES}
|
|
||||||
PATHS[TYPE_PROJ] = PROJECT_FUNDING[0].path
|
|
||||||
CODES = list(PATHS)
|
|
||||||
|
|
||||||
|
|
||||||
class AnonymousViewTestCase(TestCase):
|
|
||||||
|
|
||||||
def test_index(self):
|
|
||||||
response = request(self, 'index')
|
|
||||||
|
|
||||||
self.assertContains(response, '<a href="https://srcsrv.wikimedia.de/beba/foerderbarometer">Sourcecode</a>')
|
|
||||||
|
|
||||||
def test_extern(self):
|
|
||||||
request(self, 'extern')
|
|
||||||
|
|
||||||
def test_extern_post(self):
|
|
||||||
code = random.choice(CODES)
|
|
||||||
url = self.helper_url(code)
|
|
||||||
|
|
||||||
request(self, 'extern', expected_url=url, data={'url': url})
|
|
||||||
|
|
||||||
def test_extern_invalid_url(self):
|
|
||||||
request(self, 'extern', data={'url': 'https://domain.not/allowed/to/be/redirected/'})
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_step_data(cls, choice, **data):
|
|
||||||
return {
|
|
||||||
'realname': 'Test',
|
|
||||||
'email': 'test@example.com',
|
|
||||||
'choice': choice,
|
|
||||||
'check': True,
|
|
||||||
**data,
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def helper_url(code):
|
|
||||||
return resolve_url('extern', type=PATHS[code])
|
|
||||||
|
|
||||||
def helper_extern_base(self, choice, text, data):
|
|
||||||
url = self.helper_url(choice)
|
|
||||||
|
|
||||||
response = request(self, url)
|
|
||||||
|
|
||||||
self.assertContains(response, text)
|
|
||||||
|
|
||||||
data = self.get_step_data(choice, **data)
|
|
||||||
|
|
||||||
return request(self, url, data=data)
|
|
||||||
|
|
||||||
def helper_extern(self, choice, text, data):
|
|
||||||
response = self.helper_extern_base(choice, text, data)
|
|
||||||
|
|
||||||
self.assertContains(response, 'Deine Anfrage wurde gesendet.')
|
|
||||||
|
|
||||||
def test_extern_types(self):
|
|
||||||
types = [
|
|
||||||
(TYPE_BIB, 'Bibliotheksausweis'),
|
|
||||||
(TYPE_ELIT, 'Online-Ressource'),
|
|
||||||
(TYPE_MAIL, 'Mailadresse beantragen'),
|
|
||||||
(TYPE_IFG, 'gewonnenen Informationen'),
|
|
||||||
(TYPE_LIT, 'Literatur verwenden'),
|
|
||||||
(TYPE_LIST, 'Mailingliste beantragen'),
|
|
||||||
(TYPE_TRAV, 'Transportmittel'),
|
|
||||||
(TYPE_SOFT, 'Lizenz'),
|
|
||||||
(TYPE_VIS, 'DIN 5008'),
|
|
||||||
(TYPE_PROJ, 'Projektförderung'),
|
|
||||||
]
|
|
||||||
|
|
||||||
for code, text in types:
|
|
||||||
with self.subTest(type=code):
|
|
||||||
url = self.helper_url(code)
|
|
||||||
response = request(self, url)
|
|
||||||
|
|
||||||
self.assertContains(response, text)
|
|
||||||
|
|
||||||
def test_extern_travel(self):
|
|
||||||
self.helper_extern(TYPE_TRAV, 'Transportmittel', {
|
|
||||||
'project_name': 'Test',
|
|
||||||
'transport': 'BAHN',
|
|
||||||
'travelcost': 10,
|
|
||||||
'checkin': '2025-01-01',
|
|
||||||
'checkout': '2025-01-02',
|
|
||||||
'hotel': 'TRUE',
|
|
||||||
'notes': '',
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_extern_lit(self):
|
|
||||||
self.helper_extern(TYPE_LIT, 'Literatur verwenden', {
|
|
||||||
'cost': 20,
|
|
||||||
'info': 'Test',
|
|
||||||
'source': 'Test',
|
|
||||||
'notes': '',
|
|
||||||
'selfbuy': 'FALSE',
|
|
||||||
'selfbuy_data': 'Test',
|
|
||||||
'selfbuy_give_data': True,
|
|
||||||
'check': True,
|
|
||||||
'terms_accepted': True,
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_extern_lit_without_consent_fails(self):
|
|
||||||
response = self.helper_extern_base(TYPE_LIT, 'Literatur verwenden', {
|
|
||||||
'cost': 20,
|
|
||||||
'info': 'Test',
|
|
||||||
'source': 'Test',
|
|
||||||
'notes': '',
|
|
||||||
'selfbuy': 'TRUE',
|
|
||||||
'selfbuy_data': '',
|
|
||||||
'selfbuy_give_data': False,
|
|
||||||
'check': False,
|
|
||||||
})
|
|
||||||
|
|
||||||
self.assertContains(response, 'Dieses Feld ist zwingend erforderlich.')
|
|
||||||
|
|
||||||
def test_extern_bib(self):
|
|
||||||
self.helper_extern('BIB', 'Bibliotheksausweis', {
|
|
||||||
'cost': 20,
|
|
||||||
'library': 'Test',
|
|
||||||
'duration': 'Test',
|
|
||||||
'notes': '',
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_extern_proj(self):
|
|
||||||
category = ProjectCategory.objects.order_by('?').first()
|
|
||||||
wikimedia_project = WikimediaProject.objects.order_by('?').first()
|
|
||||||
|
|
||||||
self.helper_extern(TYPE_PROJ, 'Projektförderung', {
|
|
||||||
'name': 'Test',
|
|
||||||
'description': 'Test',
|
|
||||||
'categories': [category.id, 0],
|
|
||||||
'categories_other': 'Test',
|
|
||||||
'wikimedia_projects': [wikimedia_project.id, 0],
|
|
||||||
'wikimedia_projects_other': 'Test',
|
|
||||||
'start': '2025-01-01',
|
|
||||||
'end': '2025-01-02',
|
|
||||||
'participants_estimated': 1,
|
|
||||||
'cost': 20,
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_extern_invalid_code(self):
|
|
||||||
request(self, 'extern', args=['invalid'], status_code=404)
|
|
||||||
|
|
||||||
def test_unknown_name(self):
|
|
||||||
obj = Library(type=Library.TYPE)
|
|
||||||
data = model_to_dict(obj)
|
|
||||||
name = ApplicationView.get_recipient_name(obj, data)
|
|
||||||
|
|
||||||
self.assertEqual(name, 'Unbekannt')
|
|
||||||
|
|
||||||
|
|
||||||
class AuthenticatedViewTestCase(TestCase):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
cls.user = create_superuser('staff')
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
login(self)
|
|
||||||
|
|
||||||
def test_export(self):
|
|
||||||
request(self, 'export')
|
|
||||||
|
|
||||||
def helper_auth_deny(self, view, expected):
|
|
||||||
obj = Library.objects.create(library='Test')
|
|
||||||
|
|
||||||
request(self, view, args=[obj.type, obj.id])
|
|
||||||
|
|
||||||
obj.refresh_from_db(fields=['granted'])
|
|
||||||
|
|
||||||
self.assertEqual(obj.granted, expected)
|
|
||||||
|
|
||||||
def helper_auth_deny_error(self, view):
|
|
||||||
response = request(self, view, args=['TEST', 1])
|
|
||||||
|
|
||||||
self.assertContains(response, 'ERROR')
|
|
||||||
|
|
||||||
def test_authorize(self):
|
|
||||||
self.helper_auth_deny('authorize', True)
|
|
||||||
|
|
||||||
def test_authorize_error(self):
|
|
||||||
self.helper_auth_deny_error('authorize')
|
|
||||||
|
|
||||||
def test_deny(self):
|
|
||||||
self.helper_auth_deny('deny', False)
|
|
||||||
|
|
||||||
def test_deny_error(self):
|
|
||||||
self.helper_auth_deny_error('deny')
|
|
||||||
|
|
@ -1,28 +1,14 @@
|
||||||
from django.urls import path, include
|
from django.urls import path
|
||||||
from django.views.i18n import JavaScriptCatalog
|
from .views import ExternView, index, done, authorize, deny, InternView, export
|
||||||
|
from django.contrib import admin
|
||||||
from .views import (
|
|
||||||
index,
|
|
||||||
done,
|
|
||||||
export,
|
|
||||||
authorize,
|
|
||||||
deny,
|
|
||||||
ApplicationView,
|
|
||||||
ApplicationStartView,
|
|
||||||
ProjectFundingInfoView,
|
|
||||||
)
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', index, name='index'),
|
path('', index, name='index'),
|
||||||
|
path('extern', ExternView.as_view(), name='extern'),
|
||||||
|
# path('intern', InternView.as_view(), name='intern'),
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
path('saved', done, name='done'),
|
path('saved', done, name='done'),
|
||||||
path('export', export, name='export'),
|
path('export', export, name='export'),
|
||||||
path('authorize/<str:choice>/<int:pk>', authorize, name='authorize'),
|
path('authorize/<str:choice>/<int:pk>', authorize, name='authorize'),
|
||||||
path('deny/<str:choice>/<int:pk>', deny, name='deny'),
|
path('deny/<str:choice>/<int:pk>', deny, name='deny'),
|
||||||
path('extern/', include([
|
|
||||||
path('', ApplicationStartView.as_view(), name='extern'),
|
|
||||||
path('projektfoerderung-ab-1000/', ProjectFundingInfoView.as_view(), name='projektfoerderung-ab-1000'),
|
|
||||||
path('<slug:type>/', ApplicationView.as_view(), name='extern'),
|
|
||||||
])),
|
|
||||||
# JavaScript translations for date widgets, etc.
|
|
||||||
path('jsi18n/', JavaScriptCatalog.as_view(), name='jsi18n'),
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
from django.db.models import Model
|
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
|
|
||||||
def admin_url(model: type[Model], view: str, *args, site=None, **kwargs) -> str:
|
|
||||||
return reverse(admin_url_name(model, view, site=site), args=args, kwargs=kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def admin_url_name(model: type[Model], view: str, *, site=None) -> str:
|
|
||||||
namespace = (site or admin.site).name
|
|
||||||
view_name = admin_view_name(model, view)
|
|
||||||
|
|
||||||
return f'{namespace}:{view_name}'
|
|
||||||
|
|
||||||
|
|
||||||
def admin_view_name(model: type[Model], view: str) -> str:
|
|
||||||
return f'{model._meta.app_label}_{model._meta.model_name}_{view}'
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
YES_NO = {
|
|
||||||
'y': True,
|
|
||||||
'Y': True,
|
|
||||||
'yes': True,
|
|
||||||
'YES': True,
|
|
||||||
'n': False,
|
|
||||||
'N': False,
|
|
||||||
'no': False,
|
|
||||||
'NO': False,
|
|
||||||
}
|
|
||||||
|
|
||||||
ZERO_ONE = {
|
|
||||||
1: True,
|
|
||||||
'1': True,
|
|
||||||
0: False,
|
|
||||||
'0': False,
|
|
||||||
}
|
|
||||||
|
|
||||||
TRUE_FALSE = {
|
|
||||||
True: True,
|
|
||||||
'TRUE': True,
|
|
||||||
'True': True,
|
|
||||||
'true': True,
|
|
||||||
't': True,
|
|
||||||
False: False,
|
|
||||||
'FALSE': False,
|
|
||||||
'False': False,
|
|
||||||
'false': False,
|
|
||||||
'f': False,
|
|
||||||
None: False,
|
|
||||||
}
|
|
||||||
|
|
||||||
ON_OFF = {
|
|
||||||
'on': True,
|
|
||||||
'ON': True,
|
|
||||||
'off': False,
|
|
||||||
'OFF': False,
|
|
||||||
}
|
|
||||||
|
|
||||||
TRUTHY = {
|
|
||||||
**YES_NO,
|
|
||||||
**ZERO_ONE,
|
|
||||||
**TRUE_FALSE,
|
|
||||||
**ON_OFF,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def ask(question, default=False, truthy=None):
|
|
||||||
response = input(question).strip()
|
|
||||||
|
|
||||||
return (truthy or YES_NO).get(response, default)
|
|
||||||
|
|
||||||
|
|
||||||
confirm = ask
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
from typing import Iterable
|
|
||||||
|
|
||||||
|
|
||||||
def reorder_value(values: Iterable, value, *, after=None, before=None):
|
|
||||||
"""
|
|
||||||
Reorders a value after or before another value in the given list.
|
|
||||||
Does not work properly for duplicate or None values.
|
|
||||||
Raises ValueError when any of the values is not contained in the list.
|
|
||||||
"""
|
|
||||||
|
|
||||||
assert (after is None) != (before is None), 'Either after or before is needed but not both.'
|
|
||||||
|
|
||||||
values = list(values)
|
|
||||||
|
|
||||||
values.remove(value)
|
|
||||||
|
|
||||||
if after is None:
|
|
||||||
index = values.index(before)
|
|
||||||
else:
|
|
||||||
index = values.index(after) + 1
|
|
||||||
|
|
||||||
values.insert(index, value)
|
|
||||||
|
|
||||||
return values
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
from django.conf import settings
|
|
||||||
from django.core.mail import EmailMultiAlternatives
|
|
||||||
from django.template.loader import get_template
|
|
||||||
|
|
||||||
from input.models import Project
|
|
||||||
|
|
||||||
from .attachments import collect_and_attach
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'build_email',
|
|
||||||
'send_email',
|
|
||||||
'collect_and_attach',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def build_email(template_name: str, context: dict, subject: str, *recipients: str, **kwargs):
|
|
||||||
body = get_template(f'mails/{template_name}.txt').render(context)
|
|
||||||
html = get_template(f'mails/{template_name}.html').render(context)
|
|
||||||
|
|
||||||
kwargs.setdefault('from_email', settings.IF_EMAIL)
|
|
||||||
|
|
||||||
kwargs['subject'] = subject
|
|
||||||
kwargs['body'] = body
|
|
||||||
kwargs['to'] = recipients
|
|
||||||
|
|
||||||
email = EmailMultiAlternatives(**kwargs)
|
|
||||||
|
|
||||||
email.attach_alternative(html, 'text/html')
|
|
||||||
|
|
||||||
return email
|
|
||||||
|
|
||||||
|
|
||||||
def send_email(template_name: str, context: dict, subject: str, *recipients: str, fail_silently=False, **kwargs):
|
|
||||||
return build_email(template_name, context, subject, *recipients, **kwargs).send(fail_silently)
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
import os
|
|
||||||
import posixpath
|
|
||||||
import time
|
|
||||||
import mimetypes
|
|
||||||
|
|
||||||
from os import PathLike
|
|
||||||
from pathlib import Path
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
from urllib.request import urlretrieve
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.mail import EmailMultiAlternatives
|
|
||||||
|
|
||||||
from foerderbarometer.constants import *
|
|
||||||
|
|
||||||
PathList = list[Path]
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_dir(directory: PathLike) -> Path:
|
|
||||||
"""
|
|
||||||
Ensure that the given directory exists.
|
|
||||||
Creates it recursively if it doesn't.
|
|
||||||
"""
|
|
||||||
|
|
||||||
directory = Path(directory)
|
|
||||||
|
|
||||||
directory.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
return directory
|
|
||||||
|
|
||||||
|
|
||||||
def is_fresh(path: Path, ttl_seconds: int) -> bool:
|
|
||||||
"""
|
|
||||||
Check if the cached file exists and is still fresh within TTL.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
mtime = path.stat().st_mtime
|
|
||||||
except FileNotFoundError:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return time.time() - mtime < ttl_seconds
|
|
||||||
|
|
||||||
|
|
||||||
def get_attachment(url: str) -> Path:
|
|
||||||
filepath = urlparse(url).path
|
|
||||||
filename = posixpath.basename(filepath)
|
|
||||||
destination = ensure_dir(settings.MAIL_ATTACHMENT_CACHE_DIR) / filename
|
|
||||||
|
|
||||||
if is_fresh(destination, settings.MAIL_ATTACHMENT_TTL_SECONDS):
|
|
||||||
return destination
|
|
||||||
|
|
||||||
return download_attachment(url, destination)
|
|
||||||
|
|
||||||
|
|
||||||
def download_attachment(url: str, destination: Path) -> Path:
|
|
||||||
filepath = destination.with_suffix('.tmp')
|
|
||||||
|
|
||||||
try:
|
|
||||||
urlretrieve(url, filepath)
|
|
||||||
os.replace(filepath, destination)
|
|
||||||
finally:
|
|
||||||
filepath.unlink(missing_ok=True)
|
|
||||||
|
|
||||||
return destination
|
|
||||||
|
|
||||||
|
|
||||||
def collect_attachment_paths(recipient: str, type_code: str) -> PathList:
|
|
||||||
assert recipient in RECIPIENTS
|
|
||||||
assert type_code in TYPES
|
|
||||||
|
|
||||||
config = settings.MAIL_ATTACHMENT_URLS[recipient]
|
|
||||||
urls = [*config[TYPE_ALL], *config.get(type_code, [])]
|
|
||||||
|
|
||||||
return [get_attachment(url) for url in urls]
|
|
||||||
|
|
||||||
|
|
||||||
def get_mime_type(path: Path) -> str:
|
|
||||||
mime_type, encoding = mimetypes.guess_type(path)
|
|
||||||
|
|
||||||
return mime_type or 'application/octet-stream'
|
|
||||||
|
|
||||||
|
|
||||||
def attach_files(message: EmailMultiAlternatives, files: list[Path]):
|
|
||||||
"""
|
|
||||||
Attach files to the EmailMultiAlternatives message.
|
|
||||||
MIME type is guessed from path; falls back to application/octet-stream.
|
|
||||||
"""
|
|
||||||
|
|
||||||
for path in files:
|
|
||||||
mime_type = get_mime_type(path)
|
|
||||||
|
|
||||||
with open(path, 'rb') as fp:
|
|
||||||
message.attach(path.name, fp.read(), mime_type)
|
|
||||||
|
|
||||||
|
|
||||||
def collect_and_attach(email: EmailMultiAlternatives, recipient: str, type_code: str):
|
|
||||||
return attach_files(email, collect_attachment_paths(recipient, type_code))
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
def get_queryset(apps, schema_editor, *model):
|
|
||||||
return apps.get_model(*model).objects.using(schema_editor.connection.alias)
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import os
|
|
||||||
|
|
||||||
from .confirmation import TRUTHY
|
|
||||||
|
|
||||||
|
|
||||||
def env(key, default=None, parser=None):
|
|
||||||
value = os.environ.get(key)
|
|
||||||
|
|
||||||
if value is None:
|
|
||||||
return default
|
|
||||||
|
|
||||||
if parser is None:
|
|
||||||
if default is None:
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
parser = type(default)
|
|
||||||
|
|
||||||
if parser is bool:
|
|
||||||
return truthy(value, default)
|
|
||||||
|
|
||||||
return parser(value)
|
|
||||||
|
|
||||||
|
|
||||||
def truthy(value, default=False):
|
|
||||||
return TRUTHY.get(value, default)
|
|
||||||
|
|
||||||
|
|
||||||
def password_validators(*validators):
|
|
||||||
return list(_parse_password_validators(validators))
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_password_validators(validators):
|
|
||||||
for validator in validators:
|
|
||||||
if isinstance(validator, (tuple, list)):
|
|
||||||
validator, options = validator
|
|
||||||
else:
|
|
||||||
validator, options = validator, {}
|
|
||||||
|
|
||||||
if '.' not in validator:
|
|
||||||
validator = 'django.contrib.auth.password_validation.%s' % validator
|
|
||||||
|
|
||||||
yield dict(NAME=validator, OPTIONS=options)
|
|
||||||
|
|
@ -1,152 +0,0 @@
|
||||||
from typing import Any, Iterable, Mapping, Union, Tuple, Protocol
|
|
||||||
|
|
||||||
from django.apps import apps
|
|
||||||
from django.conf import settings
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
|
||||||
from django.http.response import HttpResponseRedirectBase, StreamingHttpResponse
|
|
||||||
from django.shortcuts import resolve_url
|
|
||||||
from django.template import Context
|
|
||||||
from django.template.response import TemplateResponse
|
|
||||||
from django.test import Client, SimpleTestCase
|
|
||||||
from django.urls import reverse, ResolverMatch
|
|
||||||
from django.utils.http import urlencode
|
|
||||||
|
|
||||||
FormData = dict
|
|
||||||
JSONDict = dict
|
|
||||||
JSONList = list
|
|
||||||
RequestData = Union[FormData, JSONDict, JSONList]
|
|
||||||
QueryParams = Union[Mapping[str, Any], Iterable[Tuple[str, Any]]]
|
|
||||||
|
|
||||||
|
|
||||||
class TestClientResponse(Protocol):
|
|
||||||
client: Client
|
|
||||||
request: HttpRequest
|
|
||||||
templates: list
|
|
||||||
context: Context
|
|
||||||
resolver_match: ResolverMatch
|
|
||||||
|
|
||||||
def json(self) -> Union[JSONList, JSONDict]: ...
|
|
||||||
|
|
||||||
|
|
||||||
Response = Union[
|
|
||||||
HttpResponse,
|
|
||||||
HttpResponseRedirectBase,
|
|
||||||
StreamingHttpResponse,
|
|
||||||
TemplateResponse,
|
|
||||||
TestClientResponse,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectWithGetAbsoluteURLMethod(Protocol):
|
|
||||||
|
|
||||||
def get_absolute_url(self) -> str: ...
|
|
||||||
|
|
||||||
|
|
||||||
URL = Union[str, ObjectWithGetAbsoluteURLMethod]
|
|
||||||
URLArgs = Union[tuple, list]
|
|
||||||
URLKwargs = dict
|
|
||||||
|
|
||||||
|
|
||||||
def get_url(url: URL, args: URLArgs = None, kwargs: URLKwargs = None) -> str:
|
|
||||||
"""
|
|
||||||
Helper to reverse the given url name.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if args or kwargs:
|
|
||||||
return reverse(url, args=args, kwargs=kwargs)
|
|
||||||
|
|
||||||
return resolve_url(url)
|
|
||||||
|
|
||||||
|
|
||||||
def get_handler(test_case: SimpleTestCase, method: str = None, data=None):
|
|
||||||
if data:
|
|
||||||
method = str.lower(method or 'POST')
|
|
||||||
else:
|
|
||||||
method = str.lower(method or 'GET')
|
|
||||||
|
|
||||||
return getattr(test_case.client, method)
|
|
||||||
|
|
||||||
|
|
||||||
def request(
|
|
||||||
test_case: SimpleTestCase,
|
|
||||||
url: URL,
|
|
||||||
status_code: int = None,
|
|
||||||
expected_url: URL = None,
|
|
||||||
args: URLArgs = None,
|
|
||||||
kwargs: URLKwargs = None,
|
|
||||||
headers: dict = None,
|
|
||||||
msg: str = None,
|
|
||||||
query_params: QueryParams = None,
|
|
||||||
method: str = None,
|
|
||||||
data: RequestData = None,
|
|
||||||
**options,
|
|
||||||
) -> Response:
|
|
||||||
"""
|
|
||||||
A helper to make a request with the test case's http client.
|
|
||||||
|
|
||||||
The given args and kwargs are used to reverse the url
|
|
||||||
but not the expected url. When expected url needs
|
|
||||||
args/kwargs pass an absolute url instead.
|
|
||||||
|
|
||||||
All additional kwargs are passed as post parameters.
|
|
||||||
When posting without parameters just pass post=True.
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = data or options or None
|
|
||||||
handler = get_handler(test_case, method, data)
|
|
||||||
url = get_url(url, args, kwargs)
|
|
||||||
|
|
||||||
if query_params:
|
|
||||||
url = f'{url}?%s' % urlencode(query_params, doseq=True)
|
|
||||||
|
|
||||||
headers = headers or {}
|
|
||||||
status_code = status_code or 200
|
|
||||||
response = handler(url, data=data, **headers)
|
|
||||||
msg = msg or getattr(response, 'content', None)
|
|
||||||
|
|
||||||
if expected_url:
|
|
||||||
test_case.assertRedirects(
|
|
||||||
response=response,
|
|
||||||
expected_url=get_url(expected_url),
|
|
||||||
target_status_code=status_code,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
test_case.assertEqual(response.status_code, status_code, msg=msg)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def login(test_case: SimpleTestCase, user=None, password: str = None) -> bool:
|
|
||||||
"""
|
|
||||||
Logs in the user trying to use the raw password or the given password.
|
|
||||||
Force logs in the user when no password is found.
|
|
||||||
"""
|
|
||||||
|
|
||||||
user = user or getattr(test_case, 'user')
|
|
||||||
password = password or getattr(user, 'raw_password', password)
|
|
||||||
|
|
||||||
if password is None:
|
|
||||||
return test_case.client.force_login(user=user) or True
|
|
||||||
|
|
||||||
return test_case.client.login(username=user.username, password=password)
|
|
||||||
|
|
||||||
|
|
||||||
def create_user(username: str, *, model=None, **kwargs):
|
|
||||||
model = model or apps.get_model(settings.AUTH_USER_MODEL)
|
|
||||||
password = kwargs.setdefault('password', 'P4sSW0rD')
|
|
||||||
|
|
||||||
kwargs.setdefault('email', f'{username}@test.case')
|
|
||||||
kwargs.setdefault(model.USERNAME_FIELD, username)
|
|
||||||
|
|
||||||
user = model.objects.create_user(**kwargs)
|
|
||||||
|
|
||||||
user.raw_password = password
|
|
||||||
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
def create_superuser(username: str, **kwargs):
|
|
||||||
kwargs['is_superuser'] = True
|
|
||||||
kwargs['is_staff'] = True
|
|
||||||
|
|
||||||
return create_user(username, **kwargs)
|
|
||||||
552
input/views.py
552
input/views.py
|
|
@ -1,373 +1,301 @@
|
||||||
import datetime
|
from datetime import date
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from smtplib import SMTPException
|
from smtplib import SMTPException
|
||||||
from typing import Optional
|
|
||||||
from urllib.parse import urljoin
|
|
||||||
|
|
||||||
from django.forms import ChoiceField, Field
|
from django.shortcuts import render
|
||||||
from django.shortcuts import render, redirect
|
from django.forms import modelformset_factory
|
||||||
from django.http import HttpResponse, Http404
|
from django.http import HttpResponse
|
||||||
from django.urls import reverse
|
from formtools.wizard.views import CookieWizardView
|
||||||
from django.utils.choices import flatten_choices
|
from django.core.mail import send_mail, BadHeaderError, EmailMultiAlternatives
|
||||||
from django.utils.formats import date_format
|
|
||||||
from django.utils.functional import cached_property
|
|
||||||
from django.utils.http import url_has_allowed_host_and_scheme
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
from django.utils.text import get_text_list
|
|
||||||
from django.core.mail import BadHeaderError
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.template.loader import get_template
|
||||||
|
from django.template import Context
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.views.generic import TemplateView
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.views.generic.edit import FormView
|
from django.utils.html import format_html
|
||||||
from django.utils.html import strip_tags
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from input.utils.admin import admin_url_name
|
from .forms import ProjectForm, ExternForm, LibraryForm, IFGForm, LiteratureForm,\
|
||||||
from input.utils.mail import build_email, collect_and_attach
|
HonoraryCertificateForm, InternForm, TravelForm, EmailForm,\
|
||||||
|
ListForm, BusinessCardForm, INTERN_CHOICES
|
||||||
|
from .models import Project, TYPE_CHOICES, Library, Literature, Travel, IFG, BusinessCard, Email, List
|
||||||
|
from .settings import IF_EMAIL
|
||||||
|
|
||||||
from .forms import (
|
def auth_deny(choice,pk,auth):
|
||||||
BaseApplicationForm,
|
if choice in ('BIB', 'ELIT', 'SOFT'):
|
||||||
ProjectForm,
|
Library.set_granted(pk,auth)
|
||||||
LibraryForm,
|
elif choice == 'LIT':
|
||||||
ELiteratureForm,
|
Literature.set_granted(pk,auth)
|
||||||
SoftwareForm,
|
elif choice == 'IFG':
|
||||||
IFGForm,
|
IFG.set_granted(pk,auth)
|
||||||
LiteratureForm,
|
elif choice == 'TRAV':
|
||||||
TravelForm,
|
Travel.set_granted(pk,auth)
|
||||||
EmailForm,
|
elif choice == 'VIS':
|
||||||
ListForm,
|
BusinessCard.set_granted(pk,auth)
|
||||||
BusinessCardForm,
|
elif choice == 'MAIL':
|
||||||
)
|
Email.set_granted(pk,auth)
|
||||||
from .models import (
|
elif choice == 'LIST':
|
||||||
MODELS,
|
List.set_granted(pk,auth)
|
||||||
LIBRARY_TYPES,
|
else:
|
||||||
TYPE_CHOICES,
|
|
||||||
TYPE_BIB,
|
|
||||||
TYPE_ELIT,
|
|
||||||
TYPE_IFG,
|
|
||||||
TYPE_LIT,
|
|
||||||
TYPE_LIST,
|
|
||||||
TYPE_MAIL,
|
|
||||||
TYPE_PROJ,
|
|
||||||
TYPE_SOFT,
|
|
||||||
TYPE_TRAV,
|
|
||||||
TYPE_VIS,
|
|
||||||
Project,
|
|
||||||
ProductCategoryFormField,
|
|
||||||
)
|
|
||||||
|
|
||||||
HELP_TEXTS = {
|
|
||||||
TYPE_IFG: {
|
|
||||||
'notes': (
|
|
||||||
'Bitte gib an, wie die gewonnenen Informationen den<br>'
|
|
||||||
'Wikimedia-Projekten zugute kommen sollen.'
|
|
||||||
)
|
|
||||||
},
|
|
||||||
TYPE_MAIL: {
|
|
||||||
'domain': (
|
|
||||||
'Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>'
|
|
||||||
'möchtest du eine Mailadresse beantragen?'
|
|
||||||
)
|
|
||||||
},
|
|
||||||
TYPE_LIT: {
|
|
||||||
'notes': 'Bitte gib an, wofür du die Literatur verwenden möchtest.'
|
|
||||||
},
|
|
||||||
TYPE_LIST: {
|
|
||||||
'domain': (
|
|
||||||
'Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>'
|
|
||||||
'möchtest du eine Mailingliste beantragen?'
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ApplicationType:
|
|
||||||
code: str
|
|
||||||
path: str
|
|
||||||
form_class: type[BaseApplicationForm]
|
|
||||||
link: str
|
|
||||||
label: Optional[str] = None
|
|
||||||
help_texts: Optional[dict] = None
|
|
||||||
|
|
||||||
def __post_init__(self):
|
|
||||||
if self.label is None:
|
|
||||||
self.label = TYPE_CHOICES[self.code]
|
|
||||||
|
|
||||||
if self.help_texts is None: # pragma: no branch
|
|
||||||
self.help_texts = HELP_TEXTS.get(self.code)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def url(self):
|
|
||||||
return f'https://de.wikipedia.org/wiki/Wikipedia:F%C3%B6rderung/{self.link}'
|
|
||||||
|
|
||||||
|
|
||||||
PROJECT_FUNDING = [
|
|
||||||
ApplicationType(TYPE_PROJ, 'projektfoerderung', ProjectForm, 'Projektplanung',
|
|
||||||
'Projektförderung mit einer Gesamtsumme unter 1.000,— EUR'),
|
|
||||||
ApplicationType(TYPE_PROJ, 'projektfoerderung-ab-1000', ProjectForm, 'Projektplanung',
|
|
||||||
'Projektförderung mit einer Gesamtsumme ab 1.000,— EUR'),
|
|
||||||
]
|
|
||||||
|
|
||||||
SERVICES = [
|
|
||||||
ApplicationType(TYPE_BIB, 'bibliotheksstipendium', LibraryForm, 'Zugang_zu_Fachliteratur#Bibliotheksstipendium'),
|
|
||||||
ApplicationType(TYPE_ELIT, 'eliteraturstipendium', ELiteratureForm, 'Zugang_zu_Fachliteratur#eLiteraturstipendium'),
|
|
||||||
ApplicationType(TYPE_MAIL, 'email', EmailForm, 'E-Mail-Adressen_und_Visitenkarten#E-Mail-Adressen'),
|
|
||||||
ApplicationType(TYPE_IFG, 'ifg', IFGForm, 'Geb%C3%BChrenerstattungen_f%C3%BCr_Beh%C3%B6rdenanfragen'),
|
|
||||||
ApplicationType(TYPE_LIT, 'literaturstipendium', LiteratureForm, 'Zugang_zu_Fachliteratur#Literaturstipendium'),
|
|
||||||
ApplicationType(TYPE_LIST, 'mailingliste', ListForm, 'E-Mail-Adressen_und_Visitenkarten#Mailinglisten'),
|
|
||||||
ApplicationType(TYPE_TRAV, 'reisekosten', TravelForm, 'Reisekostenerstattungen'),
|
|
||||||
ApplicationType(TYPE_SOFT, 'softwarestipendium', SoftwareForm, 'Software-Stipendien'),
|
|
||||||
ApplicationType(TYPE_VIS, 'visitenkarten', BusinessCardForm, 'E-Mail-Adressen_und_Visitenkarten#Visitenkarten'),
|
|
||||||
]
|
|
||||||
|
|
||||||
APPLICATIONS = [
|
|
||||||
('Projektförderung', PROJECT_FUNDING),
|
|
||||||
('Serviceleistungen', SERVICES),
|
|
||||||
]
|
|
||||||
|
|
||||||
TYPES = {info.path: info for info in PROJECT_FUNDING + SERVICES}
|
|
||||||
|
|
||||||
|
|
||||||
def auth_deny(choice, pk, auth):
|
|
||||||
if choice not in MODELS:
|
|
||||||
return HttpResponse(f'ERROR! UNKNOWN CHOICE TYPE! {choice}')
|
return HttpResponse(f'ERROR! UNKNOWN CHOICE TYPE! {choice}')
|
||||||
|
return False
|
||||||
MODELS[choice].set_granted(pk, auth)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def export(request):
|
def export(request):
|
||||||
'''export the project database to a csv'''
|
'''export the project database to a csv'''
|
||||||
return HttpResponse('WE WANT CSV!')
|
return HttpResponse('WE WANT CSV!')
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def authorize(request, choice, pk):
|
def authorize(request, choice, pk):
|
||||||
'''If IF grant a support they click a link in a mail which leads here.
|
'''If IF grant a support they click a link in a mail which leads here.
|
||||||
We write the granted field in the database here and set a timestamp.'''
|
We write the granted field in the database here and set a timestamp.'''
|
||||||
|
|
||||||
if ret := auth_deny(choice, pk, True):
|
ret = auth_deny(choice, pk, True)
|
||||||
|
if ret:
|
||||||
return ret
|
return ret
|
||||||
else:
|
else:
|
||||||
return HttpResponse(f'AUTHORIZED! choice: {choice}, pk: {pk}')
|
return HttpResponse(f"AUTHORIZED! choice: {choice}, pk: {pk}")
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def deny(request, choice, pk):
|
def deny(request, choice, pk):
|
||||||
'''If IF denies a support they click a link in a mail which leads here
|
'''If IF denies a support they click a link in a mail which leads here
|
||||||
We write the granted field in the database here.'''
|
We write the granted field in the database here.'''
|
||||||
|
|
||||||
if ret := auth_deny(choice, pk, False):
|
ret = auth_deny(choice, pk, False)
|
||||||
|
if ret:
|
||||||
return ret
|
return ret
|
||||||
else:
|
else:
|
||||||
return HttpResponse(f'DENIED! choice: {choice}, pk: {pk}')
|
return HttpResponse(f"DENIED! choice: {choice}, pk: {pk}")
|
||||||
|
|
||||||
|
|
||||||
def done(request):
|
def done(request):
|
||||||
return HttpResponse(
|
return HttpResponse("Deine Anfrage wurde gesendet. Du erhältst in Kürze eine E-Mail-Benachrichtigung mit deinen Angaben. Für alle Fragen kontaktiere bitte das Team Communitys und Engagement unter community@wikimedia.de.")
|
||||||
'Deine Anfrage wurde gesendet. Du erhältst in Kürze eine E-Mail-Benachrichtigung mit deinen Angaben. Für alle Fragen kontaktiere bitte das Team Communitys und Engagement unter community@wikimedia.de.')
|
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
return render(request, 'input/index.html')
|
return render(request, 'input/index.html')
|
||||||
|
|
||||||
|
class InternView(LoginRequiredMixin, CookieWizardView):
|
||||||
|
'''This View is for WMDE-employees only'''
|
||||||
|
|
||||||
class ApplicationStartView(TemplateView):
|
template_name = 'input/extern.html'
|
||||||
template_name = 'input/forms/extern.html'
|
form_list = [InternForm, ProjectForm]
|
||||||
extra_context = {'applications': APPLICATIONS}
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def get_form(self, step=None, data=None, files=None):
|
||||||
if url := request.POST.get('url'):
|
'''this function determines which part of the multipart form is
|
||||||
if url_has_allowed_host_and_scheme(url, None):
|
displayed next'''
|
||||||
return redirect(url)
|
|
||||||
|
|
||||||
return self.get(request, *args, **kwargs)
|
if step is None:
|
||||||
|
step = self.steps.current
|
||||||
|
print ("get_form() step " + step)
|
||||||
class ProjectFundingInfoView(TemplateView):
|
|
||||||
template_name = 'input/info_project_funding_gt_1000.html'
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationView(FormView):
|
|
||||||
"""
|
|
||||||
View for all application types.
|
|
||||||
|
|
||||||
- Renders the generic form template.
|
|
||||||
- Handles saving the submitted form to the database.
|
|
||||||
- Adds extra fields from the session or request type if needed.
|
|
||||||
- Applies optional help_text overrides for certain fields.
|
|
||||||
- Sends confirmation mail to the applicant.
|
|
||||||
- Sends notification mail to the internal IF address.
|
|
||||||
- Returns the "done" response after successful processing.
|
|
||||||
"""
|
|
||||||
|
|
||||||
template_name = 'input/forms/form_generic.html'
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def type_info(self) -> ApplicationType:
|
|
||||||
type_path = self.kwargs['type']
|
|
||||||
|
|
||||||
if type_info := TYPES.get(type_path):
|
|
||||||
return type_info
|
|
||||||
|
|
||||||
raise Http404(f'"{type_path}" existiert nicht.')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def type_code(self):
|
|
||||||
return self.type_info.code
|
|
||||||
|
|
||||||
@property
|
|
||||||
def form_class(self):
|
|
||||||
return self.type_info.form_class
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
return super().get_context_data(**kwargs, type_label=self.type_info.label)
|
|
||||||
|
|
||||||
def get_form(self, form_class=None):
|
|
||||||
"""Return the form instance and inject custom help_texts if defined for this type."""
|
|
||||||
form = super().get_form(form_class)
|
|
||||||
|
|
||||||
# Apply help_text overrides if defined for this type_code
|
|
||||||
if help_texts := self.type_info.help_texts:
|
|
||||||
for field, text in help_texts.items():
|
|
||||||
if field in form.fields:
|
|
||||||
form.fields[field].help_text = mark_safe(text)
|
|
||||||
|
|
||||||
|
if step == '1':
|
||||||
|
prev_data = self.get_cleaned_data_for_step('0')
|
||||||
|
choice = prev_data.get('choice')
|
||||||
|
print(f'choice detection: {INTERN_CHOICES[choice]}')
|
||||||
|
if choice == 'HON':
|
||||||
|
form = HonoraryCertificateForm(data)
|
||||||
|
elif choice == 'PRO':
|
||||||
|
form = ProjectForm(data)
|
||||||
|
elif choice == 'TRAV':
|
||||||
|
form = TravelForm(data)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f'ERROR! UNKNOWN FORMTYPE {choice} in InternView')
|
||||||
|
self.choice = choice
|
||||||
|
else:
|
||||||
|
form = super().get_form(step, data, files)
|
||||||
|
form.fields['realname'].help_text = format_html("Vor- und Zuname (Realname), Wer hat das Projekt beantragt?<br>\
|
||||||
|
Wer ist Hauptansprechperson? Bei WMDE-MAs immer „(WMDE)“,<br>\
|
||||||
|
bei externen Partnern „(PART)“ hinzufügen.")
|
||||||
return form
|
return form
|
||||||
|
|
||||||
def form_valid(self, form):
|
def get_context_data(self, **kwargs):
|
||||||
"""
|
context = super().get_context_data(**kwargs)
|
||||||
Process a valid form submission:
|
if hasattr(self, 'choice'):
|
||||||
- Enrich form data (e.g., set type_code, handle special rules).
|
context["choice"] = INTERN_CHOICES[self.choice]
|
||||||
- Save the model instance and related data.
|
return context
|
||||||
- Send confirmation and notification mails.
|
|
||||||
- Return the "done" response.
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = self.prepare_data(form)
|
def done(self, form_list, **kwargs):
|
||||||
obj = self.save_obj(form, data)
|
print('InternView.done() reached')
|
||||||
|
# gather data from all forms
|
||||||
|
data = {}
|
||||||
|
for form in form_list:
|
||||||
|
data = {**data, **form.cleaned_data}
|
||||||
|
|
||||||
if response := self.send_mail(obj, data):
|
if data['choice'] == 'LIT':
|
||||||
return response
|
if data['selfbuy'] == 'TRUE':
|
||||||
|
data['selfbuy_give_data'] = 'False'
|
||||||
|
|
||||||
|
# write data to database
|
||||||
|
form = form.save(commit=False)
|
||||||
|
# we have to copy the data from the first form here
|
||||||
|
# this is ugly code. how can we copy this without explicit writing?
|
||||||
|
# i found no way to access the ModelForm.Meta.exclude-tupel
|
||||||
|
form.realname = data['realname']
|
||||||
|
# form.username = data['username']
|
||||||
|
form.email = data['email']
|
||||||
|
form.granted = True
|
||||||
|
form.granted_date = date.today()
|
||||||
|
|
||||||
|
if data['choice'] == 'LIT':
|
||||||
|
form.selfbuy_give_data = data['selfbuy_give_data']
|
||||||
|
|
||||||
|
form.save()
|
||||||
|
|
||||||
return done(self.request)
|
return done(self.request)
|
||||||
|
|
||||||
def prepare_data(self, form):
|
# these where used as labels in the second form TYPE_CHOICES is used for the first form and the
|
||||||
# Collect cleaned data and mark the current type
|
# text above the second form. only used for BIB, SOFT, ELIT in the moment
|
||||||
return {**form.cleaned_data, 'choice': self.type_code}
|
LABEL_CHOICES = {'BIB': format_html('Bibliothek'),
|
||||||
|
'ELIT': format_html('Datenbank/Online-Ressource'),
|
||||||
|
'MAIL': format_html('E-Mail-Adresse'),
|
||||||
|
'IFG': format_html('Kostenübernahme IFG-Anfrage'),
|
||||||
|
'LIT': format_html('Literaturstipendium'),
|
||||||
|
'LIST': format_html('Mailingliste'),
|
||||||
|
'TRAV': format_html('Reisekosten'),
|
||||||
|
'SOFT': format_html('Software'),
|
||||||
|
'VIS': format_html('Visitenkarten'),
|
||||||
|
}
|
||||||
|
|
||||||
def save_obj(self, form, data):
|
HELP_CHOICES = {'BIB': format_html("In welchem Zeitraum möchtest du recherchieren oder<br>wie lange ist der Bibliotheksausweis gültig?"),
|
||||||
# Save model instance
|
'ELIT': "Wie lange gilt der Zugang?",
|
||||||
|
'SOFT': "Wie lange gilt die Lizenz?",
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExternView(CookieWizardView):
|
||||||
|
'''This View is for Volunteers'''
|
||||||
|
|
||||||
|
template_name = "input/extern.html"
|
||||||
|
form_list = [ExternForm, LibraryForm]
|
||||||
|
|
||||||
|
def get_form(self, step=None, data=None, files=None):
|
||||||
|
'''this function determines which part of the multipart form is
|
||||||
|
displayed next'''
|
||||||
|
|
||||||
|
if step is None:
|
||||||
|
step = self.steps.current
|
||||||
|
print ("get_form() step " + step)
|
||||||
|
|
||||||
|
if step == '1':
|
||||||
|
prev_data = self.get_cleaned_data_for_step('0')
|
||||||
|
choice = prev_data.get('choice')
|
||||||
|
print(f'choice detection in ExternView: {TYPE_CHOICES[choice]}')
|
||||||
|
if choice == 'IFG':
|
||||||
|
form = IFGForm(data)
|
||||||
|
form.fields['notes'].help_text = format_html("Bitte gib an, wie die gewonnenen Informationen den<br>Wikimedia-Projekten zugute kommen sollen.")
|
||||||
|
elif choice in ('BIB', 'SOFT', 'ELIT'):
|
||||||
|
form = LibraryForm(data)
|
||||||
|
form.fields['library'].label = LABEL_CHOICES[choice]
|
||||||
|
form.fields['library'].help_text = f"Für welche {LABEL_CHOICES[choice]} gilt das Stipendium?"
|
||||||
|
form.fields['duration'].help_text = HELP_CHOICES[choice]
|
||||||
|
elif choice == 'MAIL':
|
||||||
|
form = EmailForm(data)
|
||||||
|
form.fields['domain'].help_text = format_html("Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>möchtest du eine Mailadresse beantragen?")
|
||||||
|
elif choice == 'LIT':
|
||||||
|
form = LiteratureForm(data)
|
||||||
|
form.fields['notes'].help_text = "Bitte gib an, wofür du die Literatur verwenden möchtest."
|
||||||
|
elif choice == 'VIS':
|
||||||
|
form = BusinessCardForm(data)
|
||||||
|
elif choice == 'LIST':
|
||||||
|
form = ListForm(data)
|
||||||
|
form.fields['domain'].help_text = format_html("Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>möchtest du eine Mailingliste beantragen?")
|
||||||
|
elif choice == 'TRAV':
|
||||||
|
form = TravelForm(data)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f'ERROR! UNKNOWN FORMTYPE {choice} in ExternView')
|
||||||
|
self.choice = choice
|
||||||
|
else:
|
||||||
|
form = super().get_form(step, data, files)
|
||||||
|
return form
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
if hasattr(self, 'choice'):
|
||||||
|
context["choice"] = TYPE_CHOICES[self.choice]
|
||||||
|
return context
|
||||||
|
|
||||||
|
def done(self, form_list, **kwargs):
|
||||||
|
print('ExternView.done() reached')
|
||||||
|
# gather data from all forms
|
||||||
|
data = {}
|
||||||
|
for form in form_list:
|
||||||
|
data = {**data, **form.cleaned_data}
|
||||||
|
|
||||||
|
data['username'] = self.request.session['user']['username']
|
||||||
|
|
||||||
|
if data['choice'] == 'LIT':
|
||||||
|
if data['selfbuy'] == 'TRUE':
|
||||||
|
data['selfbuy_give_data'] = 'False'
|
||||||
|
|
||||||
|
# write data to database
|
||||||
modell = form.save(commit=False)
|
modell = form.save(commit=False)
|
||||||
|
# we have to copy the data from the first form here
|
||||||
|
# this is a bit ugly code. can we copy this without explicit writing?
|
||||||
|
|
||||||
# Username from session if present
|
if data['choice'] == 'LIT':
|
||||||
if user := self.request.session.get('user'):
|
|
||||||
modell.username = user.get('username')
|
|
||||||
|
|
||||||
# Copy common fields if provided by the form
|
|
||||||
if 'realname' in data:
|
|
||||||
modell.realname = data['realname']
|
|
||||||
|
|
||||||
if 'email' in data:
|
|
||||||
modell.email = data['email']
|
|
||||||
|
|
||||||
# Set model.type for specific request types
|
|
||||||
if self.type_code in LIBRARY_TYPES:
|
|
||||||
modell.type = self.type_code
|
|
||||||
|
|
||||||
# Literature-specific extra field
|
|
||||||
if self.type_code == TYPE_LIT and 'selfbuy_give_data' in data:
|
|
||||||
modell.selfbuy_give_data = data['selfbuy_give_data']
|
modell.selfbuy_give_data = data['selfbuy_give_data']
|
||||||
|
|
||||||
modell.save()
|
modell.realname = data['realname']
|
||||||
|
modell.username = data['username']
|
||||||
|
modell.email = data['email']
|
||||||
|
# write type of form in some cases
|
||||||
|
if data['choice'] in ('BIB', 'ELIT', 'SOFT'):
|
||||||
|
modell.type = data['choice']
|
||||||
|
|
||||||
if hasattr(form, 'save_m2m'):
|
form.save()
|
||||||
form.save_m2m()
|
|
||||||
|
|
||||||
return modell
|
# add some data to context for mail templates
|
||||||
|
data['pk'] = modell.pk
|
||||||
def send_mail(self, obj, data):
|
data['urlprefix'] = settings.URLPREFIX
|
||||||
# Prepare minimal mail context and send mails
|
data['grant'] = ('LIT', 'SOFT', 'ELIT', 'BIB', 'IFG')
|
||||||
|
data['DOMAIN'] = ('MAIL', 'LIST')
|
||||||
context = self.get_email_context(obj, data)
|
data['typestring'] = TYPE_CHOICES[data['choice']]
|
||||||
applicant_subject = 'Deine Förderanfrage bei Wikimedia Deutschland'
|
|
||||||
staff_subject = 'Anfrage {type_label} von {applicant_name}'.format(**context)
|
|
||||||
|
|
||||||
|
# we need to send the following mails here:
|
||||||
|
context = { 'data': data }
|
||||||
try:
|
try:
|
||||||
self.send_email('applicant', 'ifg_volunteer_mail', applicant_subject, data['email'], context)
|
# - mail with entered data to the Volunteer
|
||||||
self.send_email('staff', 'if_mail', staff_subject, settings.IF_EMAIL, context)
|
|
||||||
|
txt_mail_template1 = get_template('input/ifg_volunteer_mail.txt')
|
||||||
|
html_mail_template1 = get_template('input/ifg_volunteer_mail.html')
|
||||||
|
|
||||||
|
subject1, from_email1, to1 = 'Formular ausgefüllt', IF_EMAIL, data['email']
|
||||||
|
text_content1 = txt_mail_template1.render(context)
|
||||||
|
html_content1 = html_mail_template1.render(context)
|
||||||
|
msg1 = EmailMultiAlternatives(subject1, text_content1, from_email1, [to1])
|
||||||
|
msg1.attach_alternative(html_content1, "text/html")
|
||||||
|
msg1.send()
|
||||||
|
#print('ifg volunteer mail would have been sent')
|
||||||
|
#send_mail(
|
||||||
|
# 'Formular ausgefüllt',
|
||||||
|
# txt_mail_template1.render(context),
|
||||||
|
# IF_EMAIL,
|
||||||
|
# [data['email']],
|
||||||
|
# fail_silently=False)
|
||||||
|
## - mail to IF with link to accept/decline
|
||||||
|
|
||||||
|
txt_mail_template = get_template('input/if_mail.txt')
|
||||||
|
html_mail_template = get_template('input/if_mail.html')
|
||||||
|
|
||||||
|
subject, from_email, to = 'Formular ausgefüllt', IF_EMAIL, IF_EMAIL
|
||||||
|
text_content = txt_mail_template.render(context)
|
||||||
|
html_content = html_mail_template.render(context)
|
||||||
|
msg2 = EmailMultiAlternatives(subject, text_content, from_email, [to])
|
||||||
|
msg2.attach_alternative(html_content, "text/html")
|
||||||
|
msg2.send()
|
||||||
|
#print('if mail would have been sent')
|
||||||
|
#send_mail(
|
||||||
|
# 'Formular ausgefüllt',
|
||||||
|
# txt_mail_template.render(context),
|
||||||
|
# IF_EMAIL,
|
||||||
|
# [IF_EMAIL],
|
||||||
|
# fail_silently=False)
|
||||||
|
## raise SMTPException("testing pupose only")
|
||||||
|
|
||||||
except BadHeaderError:
|
except BadHeaderError:
|
||||||
obj.delete()
|
modell.delete()
|
||||||
return HttpResponse('Invalid header found. Data not saved!')
|
return HttpResponse('Invalid header found. Data not saved!')
|
||||||
except SMTPException:
|
except SMTPException:
|
||||||
obj.delete()
|
modell.delete()
|
||||||
return HttpResponse('Error in sending mails (probably wrong address?). Data not saved!')
|
return HttpResponse('Error in sending mails (probably wrong adress?). Data not saved!')
|
||||||
|
|
||||||
def get_email_context(self, obj, data):
|
|
||||||
return {
|
|
||||||
'data': data,
|
|
||||||
'urls': self.get_urls(obj),
|
|
||||||
'form_data': self.get_form_data(obj, data),
|
|
||||||
'applicant_name': self.get_recipient_name(obj, data),
|
|
||||||
'type_label': self.sanitize_label(self.type_info.label),
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_urls(self, obj, **urls):
|
return done(self.request)
|
||||||
urls['admin'] = self.get_absolute_url(admin_url_name(obj, 'change'), obj.id)
|
|
||||||
|
|
||||||
if isinstance(obj, Project):
|
|
||||||
urls['authorize'] = urls['deny'] = None
|
|
||||||
else:
|
|
||||||
urls['authorize'] = self.get_absolute_url('authorize', self.type_info.code, obj.id)
|
|
||||||
urls['deny'] = self.get_absolute_url('deny', self.type_info.code, obj.id)
|
|
||||||
|
|
||||||
return urls
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_absolute_url(view, *args):
|
|
||||||
return urljoin(settings.EMAIL_URL_PREFIX, reverse(view, args=args))
|
|
||||||
|
|
||||||
def get_form_data(self, obj, data):
|
|
||||||
return {
|
|
||||||
self.sanitize_label(field.label): self.format_value(field.field, field.initial)
|
|
||||||
for field in self.type_info.form_class(initial=data)
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def sanitize_label(label: str):
|
|
||||||
label = strip_tags(label)
|
|
||||||
words = str.split(label)
|
|
||||||
|
|
||||||
return ' '.join(words)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def format_value(field: Field, value):
|
|
||||||
if isinstance(field, ProductCategoryFormField):
|
|
||||||
value = get_text_list(value, 'und')
|
|
||||||
elif isinstance(field, ChoiceField):
|
|
||||||
choices = flatten_choices(field.choices)
|
|
||||||
value = dict(choices).get(value, value)
|
|
||||||
elif isinstance(value, bool):
|
|
||||||
value = '✓' if value else '✗'
|
|
||||||
elif isinstance(value, datetime.date):
|
|
||||||
value = date_format(value)
|
|
||||||
elif value in field.empty_values:
|
|
||||||
value = '–'
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
def send_email(self, kind, template_name, subject, recipient, context, *, fail_silently=False):
|
|
||||||
email = build_email(template_name, context, subject, recipient)
|
|
||||||
|
|
||||||
collect_and_attach(email, kind, self.type_code)
|
|
||||||
|
|
||||||
return email.send(fail_silently)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_recipient_name(obj, data):
|
|
||||||
for field in 'username', 'realname', 'email':
|
|
||||||
if name := getattr(obj, field, None) or data.get(field):
|
|
||||||
return name
|
|
||||||
|
|
||||||
return 'Unbekannt'
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,20 @@
|
||||||
Authlib==1.6.1
|
asgiref==3.2.10
|
||||||
Django==5.2.5
|
Authlib==1.2.1
|
||||||
gunicorn==23.0.0
|
certifi==2023.7.22
|
||||||
mysqlclient==2.2.7
|
cffi==1.16.0
|
||||||
python-dotenv==1.1.1
|
chardet==5.2.0
|
||||||
whitenoise==6.9.0
|
charset-normalizer==3.3.0
|
||||||
|
cryptography==41.0.4
|
||||||
|
Django==3.1.2
|
||||||
|
django-formtools==2.4
|
||||||
|
gunicorn==20.0.4
|
||||||
|
idna==3.4
|
||||||
|
mysqlclient==2.1.1
|
||||||
|
pycparser==2.21
|
||||||
|
pytz==2023.3.post1
|
||||||
|
requests==2.31.0
|
||||||
|
six==1.16.0
|
||||||
|
sqlparse==0.4.3
|
||||||
|
typing_extensions==4.8.0
|
||||||
|
urllib3==2.0.6
|
||||||
|
whitenoise==6.2.0
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue