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
|
||||
/secrets.json
|
||||
/staticfiles
|
||||
# /foerderbarometer/settings.py
|
||||
# /foerderbarometer/*settings*
|
||||
/input/settings.py
|
||||
/nohup.out
|
||||
/logfile
|
||||
*~
|
||||
|
|
@ -90,10 +93,6 @@ target/
|
|||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# IDEA
|
||||
/*.iml
|
||||
/.idea
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
|
|
@ -140,6 +139,3 @@ dmypy.json
|
|||
|
||||
# Pyre type checker
|
||||
.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.
|
||||
|
||||
## manual development setup
|
||||
## installation and development setup
|
||||
|
||||
cp .env.develop.example .env
|
||||
|
||||
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.
|
||||
ln -sr foerderbarometer/settings_development.py foerderbarometer/settings.py
|
||||
|
||||
build the database with
|
||||
|
||||
|
|
@ -25,26 +21,9 @@ run the development server with
|
|||
access via
|
||||
|
||||
http://localhost:8000/
|
||||
http://localhost:8000/intern/ (login required)
|
||||
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
|
||||
|
||||
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"
|
||||
|
||||
## 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.
|
||||
|
||||
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:
|
||||
|
||||
* `TYPE_BIB` Bibliotheksstipendium
|
||||
* `TYPE_ELIT` eLiteraturstipendium
|
||||
* `TYPE_SOFT` Softwarestipendium
|
||||
* `TYPE_MAIL` E-Mail-Adresse
|
||||
* `TYPE_IFG` Kostenübernahme IFG-Anfrage
|
||||
* `TYPE_LIT` Literaturstipendium
|
||||
* `TYPE_LIST` Mailingliste
|
||||
* `TYPE_TRAV` Reisekosten
|
||||
* `TYPE_VIS` Visitenkarten
|
||||
* `TYPE_PROJ` Projektförderung
|
||||
|
||||
For further details see `foerderbarometer/settings.py`
|
||||
asgiref==3.2.10
|
||||
Django==3.1.2
|
||||
django-formtools==2.4
|
||||
gunicorn==20.0.4
|
||||
mysqlclient==2.1.1
|
||||
sqlparse==0.4.3
|
||||
whitenoise==6.2.0
|
||||
asgiref==3.2.10
|
||||
Authlib==1.2.1
|
||||
certifi==2023.7.22
|
||||
cffi==1.16.0
|
||||
chardet==5.2.0
|
||||
charset-normalizer==3.3.0
|
||||
cryptography==41.0.4
|
||||
idna==3.4
|
||||
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
|
||||
|
||||
|
|
@ -81,26 +65,16 @@ run some tests with
|
|||
|
||||
## 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
|
||||
EMAIL_HOST_USER
|
||||
EMAIL_HOST_PASSWORD
|
||||
OAUTH_CLIENT_NAME
|
||||
OAUTH_CLIENT_ID
|
||||
OAUTH_CLIENT_SECRET
|
||||
{
|
||||
"DATABASE_PASSWORD": "THIS IS TOP SECRET!",
|
||||
"SECRET_KEY": "THIS IS ANOTHER SECRET!"
|
||||
}
|
||||
|
||||
amend database variables to .env according to your database setup (tested with MariaDB 10.0.36), e.g.
|
||||
|
||||
DATABASE_NAME
|
||||
DATABASE_USER
|
||||
DATABASE_HOST
|
||||
DATABASE_PORT
|
||||
|
||||
for a full set of all possible env vars have a look at foerderbarometer/settings.py
|
||||
edit foerderbarometer/settings_production.py according to your database setup (tested with MariaDB 10.0.36)
|
||||
|
||||
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
|
||||
|
||||
from django.contrib import admin
|
||||
from django.db import models
|
||||
from django.http import HttpResponse
|
||||
|
||||
from input.utils.list import reorder_value
|
||||
|
||||
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
|
||||
from .models import Account, Project, HonoraryCertificate, Library, IFG, Travel,\
|
||||
Email, BusinessCard, List, Literature
|
||||
|
||||
|
||||
def export_as_csv(self, request, queryset):
|
||||
|
||||
meta = self.model._meta
|
||||
field_names = [field.name for field in meta.fields]
|
||||
|
||||
|
|
@ -57,160 +22,46 @@ def export_as_csv(self, request, queryset):
|
|||
|
||||
return response
|
||||
|
||||
|
||||
export_as_csv.short_description = "Ausgewähltes zu CSV exportieren"
|
||||
|
||||
admin.site.add_action(export_as_csv)
|
||||
|
||||
|
||||
@admin.register(ProjectCategory, WikimediaProject)
|
||||
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):
|
||||
@admin.register(Project)
|
||||
class ProjectAdmin(admin.ModelAdmin):
|
||||
save_as = True
|
||||
form = ProjectAdminForm
|
||||
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')
|
||||
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'
|
||||
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:
|
||||
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)
|
||||
class BusinessCardAdmin(WMDEAdmin):
|
||||
class BusinessCardAdmin(admin.ModelAdmin):
|
||||
save_as = True
|
||||
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')
|
||||
# action = ['export_as_csv']
|
||||
date_hierarchy = 'granted_date'
|
||||
readonly_fields = ['service_id']
|
||||
|
||||
class Media:
|
||||
js = ('dropdown/js/base.js', 'dropdown/js/otrs_link.js')
|
||||
|
||||
js = ('dropdown/js/base.js',)
|
||||
|
||||
@admin.register(Literature)
|
||||
class LiteratureAdmin(WMDEAdmin):
|
||||
class LiteratureAdmin(admin.ModelAdmin):
|
||||
save_as = True
|
||||
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')
|
||||
date_hierarchy = 'granted_date'
|
||||
readonly_fields = ['service_id']
|
||||
|
||||
class Media:
|
||||
js = ('dropdown/js/otrs_link.js',)
|
||||
|
||||
|
||||
@admin.register(Account)
|
||||
class AccountAdmin(admin.ModelAdmin):
|
||||
|
|
@ -221,43 +72,23 @@ class AccountAdmin(admin.ModelAdmin):
|
|||
class HonoraryCertificateAdmin(admin.ModelAdmin):
|
||||
save_as = True
|
||||
search_fields = ['realname', 'granted', 'project__name', 'project__pid']
|
||||
list_display = ('realname', 'granted', 'project')
|
||||
list_display = ('realname', 'granted','project')
|
||||
date_hierarchy = 'granted_date'
|
||||
autocomplete_fields = ['project']
|
||||
|
||||
class Media:
|
||||
js = ('dropdown/js/otrs_link.js',)
|
||||
|
||||
|
||||
@admin.register(Library, ELiterature, Software)
|
||||
class LibraryAdmin(WMDEAdmin):
|
||||
@admin.register(Library)
|
||||
class LibraryAdmin(admin.ModelAdmin):
|
||||
save_as = True
|
||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
||||
list_display = ('realname', 'service_id', 'granted', 'granted_date')
|
||||
list_display_links = ('realname', 'service_id')
|
||||
date_hierarchy = 'granted_date'
|
||||
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)
|
||||
class IFGAdmin(WMDEAdmin):
|
||||
class IFGAdmin(admin.ModelAdmin):
|
||||
save_as = True
|
||||
search_fields = ('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'
|
||||
readonly_fields = ['service_id']
|
||||
|
||||
class Media:
|
||||
js = ('dropdown/js/otrs_link.js',)
|
||||
|
||||
|
||||
@admin.register(Travel)
|
||||
class TravelAdmin(WMDEAdmin):
|
||||
class TravelAdmin(admin.ModelAdmin):
|
||||
save_as = True
|
||||
search_fields = ['realname', 'service_id', 'granted_date', 'project__name', 'project__pid']
|
||||
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'project_end', 'project',
|
||||
'project_end_quartal')
|
||||
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'project_end', 'project', 'project_end_quartal')
|
||||
list_display_links = ('realname', 'project')
|
||||
date_hierarchy = 'project_end'
|
||||
readonly_fields = ('project_end_quartal', 'project_end')
|
||||
autocomplete_fields = ['project']
|
||||
readonly_fields = ['service_id', 'project_end', 'project_end_quartal']
|
||||
readonly_fields = ['service_id']
|
||||
|
||||
class Media:
|
||||
js = ('dropdown/js/otrs_link.js',)
|
||||
|
||||
|
||||
@admin.register(Email)
|
||||
class EmailAdmin(WMDEAdmin):
|
||||
class EmailAdmin(admin.ModelAdmin):
|
||||
save_as = True
|
||||
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')
|
||||
date_hierarchy = 'granted_date'
|
||||
radio_fields = {'adult': admin.VERTICAL}
|
||||
readonly_fields = ['service_id']
|
||||
|
||||
class Media:
|
||||
js = ('dropdown/js/base.js', 'dropdown/js/otrs_link.js')
|
||||
js = ('dropdown/js/base.js',)
|
||||
|
||||
|
||||
@admin.register(List)
|
||||
class ListAdmin(WMDEAdmin):
|
||||
class ListAdmin(admin.ModelAdmin):
|
||||
save_as = True
|
||||
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')
|
||||
date_hierarchy = 'granted_date'
|
||||
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()
|
||||
398
input/forms.py
398
input/forms.py
|
|
@ -1,314 +1,164 @@
|
|||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.forms import ModelForm, DateField, ChoiceField, RadioSelect, BooleanField
|
||||
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.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as trans
|
||||
|
||||
from .models import (
|
||||
Project,
|
||||
ProjectCategory,
|
||||
WikimediaProject,
|
||||
IFG,
|
||||
Library,
|
||||
ELiterature,
|
||||
Software,
|
||||
Travel,
|
||||
Email,
|
||||
Literature,
|
||||
List,
|
||||
BusinessCard,
|
||||
)
|
||||
from .models import Project, Volunteer, ConcreteVolunteer, Extern, ConcreteExtern, IFG, Library, TYPE_CHOICES,\
|
||||
HonoraryCertificate, Travel, Email, Literature, List,\
|
||||
BusinessCard
|
||||
from .settings import DATAPROTECTION, FOERDERRICHTLINIEN, NUTZUNGSBEDINGUNGEN
|
||||
|
||||
from . import settings
|
||||
|
||||
|
||||
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'
|
||||
|
||||
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(
|
||||
"""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
|
||||
# start = DateField(widget=AdminDateWidget())
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = [
|
||||
'realname',
|
||||
'email',
|
||||
'name',
|
||||
'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,
|
||||
}
|
||||
exclude = ('pid', 'project_of_year', 'finance_id','granted', 'granted_date', 'realname', 'email',\
|
||||
'end_mail_send', 'status', 'persons', 'survey_mail_date', 'mail_state')
|
||||
widgets = {'start': AdminDateWidget(),
|
||||
'end': AdminDateWidget(),}
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('css/dateFieldNoNowShortcutInTravels.css',)
|
||||
js = ('dropdown/js/otrs_link.js',)
|
||||
|
||||
|
||||
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:
|
||||
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):
|
||||
class TravelForm(FdbForm):
|
||||
# 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
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['project_name'].required = True
|
||||
self.fields['transport'].required = True
|
||||
self.fields['travelcost'].required = True
|
||||
self.fields['travelcost'].initial = None
|
||||
self.fields['checkin'].required = True
|
||||
self.fields['checkout'].required = True
|
||||
self.fields['hotel'].required = True
|
||||
|
||||
class Meta:
|
||||
model = Travel
|
||||
fields = [
|
||||
'realname',
|
||||
'email',
|
||||
'project_name',
|
||||
'transport',
|
||||
'travelcost',
|
||||
'checkin',
|
||||
'checkout',
|
||||
'hotel',
|
||||
'notes',
|
||||
]
|
||||
labels = {
|
||||
'checkin': 'Datum der Anreise',
|
||||
'checkout': 'Datum der Abreise',
|
||||
}
|
||||
widgets = {
|
||||
'checkin': AdminDateWidget,
|
||||
'checkout': AdminDateWidget,
|
||||
}
|
||||
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' )
|
||||
widgets = {'checkin': AdminDateWidget(),
|
||||
'checkout': AdminDateWidget(),}
|
||||
fields = ['project_name', 'transport', 'travelcost', 'checkin', 'checkout', 'hotel', 'notes']
|
||||
hotel = ChoiceField(label='Hotelzimmer benötigt:', choices=HOTEL_CHOICES.items(), widget=RadioSelect())
|
||||
|
||||
class Media:
|
||||
js = ('dropdown/js/otrs_link.js',)
|
||||
css = {
|
||||
'all': ('css/dateFieldNoNowShortcutInTravels.css',)
|
||||
'all': ('css/dateFieldNoNowShortcutInTravels.css',)
|
||||
}
|
||||
|
||||
|
||||
class LibraryForm(BaseApplicationForm):
|
||||
class LibraryForm(FdbForm):
|
||||
|
||||
class Meta:
|
||||
model = Library
|
||||
fields = [
|
||||
'realname',
|
||||
'email',
|
||||
'cost',
|
||||
'library',
|
||||
'duration',
|
||||
'notes',
|
||||
]
|
||||
labels = {
|
||||
'cost': 'Kosten in Euro',
|
||||
}
|
||||
fields = ['cost', 'library', 'duration', 'notes', 'survey_mail_send']
|
||||
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
class HonoraryCertificateForm(FdbForm):
|
||||
|
||||
self.fields['library'].label = self._meta.model.LIBRARY_LABEL
|
||||
self.fields['library'].help_text = self._meta.model.LIBRARY_HELP_TEXT
|
||||
self.fields['duration'].help_text = self._meta.model.DURATION_HELP_TEXT
|
||||
class Meta:
|
||||
model = HonoraryCertificate
|
||||
fields = ['request_url', 'project']
|
||||
exclude = ['intern_notes']
|
||||
class Media:
|
||||
js = ('dropdown/js/otrs_link.js',)
|
||||
|
||||
|
||||
class ELiteratureForm(LibraryForm):
|
||||
|
||||
class Meta(LibraryForm.Meta):
|
||||
model = ELiterature
|
||||
|
||||
|
||||
class SoftwareForm(LibraryForm):
|
||||
|
||||
class Meta(LibraryForm.Meta):
|
||||
model = Software
|
||||
|
||||
|
||||
class IFGForm(BaseApplicationForm):
|
||||
|
||||
class IFGForm(FdbForm):
|
||||
class Meta:
|
||||
model = IFG
|
||||
fields = [
|
||||
'realname',
|
||||
'email',
|
||||
'cost',
|
||||
'url',
|
||||
'notes',
|
||||
]
|
||||
fields = ['cost', 'url', 'notes']
|
||||
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
||||
|
||||
|
||||
class TermsForm(BaseApplicationForm):
|
||||
terms_accepted_label = 'Ich stimme den <a href="{}">Nutzungsbedingungen</a> zu.'
|
||||
terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN
|
||||
class CheckForm(FdbForm):
|
||||
termstoaccept = NUTZUNGSBEDINGUNGEN
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['terms_accepted'].required = True
|
||||
self.fields['terms_accepted'].label = format_html(self.terms_accepted_label, self.terms_accepted_url)
|
||||
self.fields['check'] = BooleanField(
|
||||
required=True,
|
||||
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:
|
||||
model = Literature
|
||||
fields = [
|
||||
'realname',
|
||||
'email',
|
||||
'cost',
|
||||
'info',
|
||||
'source',
|
||||
'notes',
|
||||
'selfbuy',
|
||||
'selfbuy_data',
|
||||
'selfbuy_give_data',
|
||||
'terms_accepted',
|
||||
]
|
||||
|
||||
fields = ['cost', 'info', 'source', 'notes', 'selfbuy', 'selfbuy_data', 'selfbuy_give_data']
|
||||
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
||||
class Media:
|
||||
js = ('dropdown/js/literature.js',)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = TermsForm.clean(self)
|
||||
|
||||
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 = {'TRUE': format_html('Ich bin volljährig.'),
|
||||
'FALSE': format_html('Ich bin noch nicht volljährig.')
|
||||
}
|
||||
|
||||
|
||||
ADULT_CHOICES = {
|
||||
'TRUE': mark_safe('Ich bin volljährig.'),
|
||||
'FALSE': mark_safe('Ich bin noch nicht volljährig.'),
|
||||
}
|
||||
class EmailForm(CheckForm):
|
||||
|
||||
|
||||
class EmailForm(TermsForm):
|
||||
terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_EMAIL_SERVICE
|
||||
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_EMAIL_SERVICE
|
||||
|
||||
# this is the code, to change required to false if needed
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
@ -316,28 +166,22 @@ class EmailForm(TermsForm):
|
|||
self.fields['adult'].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
|
||||
class Meta:
|
||||
model = Email
|
||||
fields = [
|
||||
'realname',
|
||||
'email',
|
||||
'domain',
|
||||
'address',
|
||||
'other',
|
||||
'adult',
|
||||
'terms_accepted',
|
||||
]
|
||||
|
||||
fields = ['domain', 'address', 'other', 'adult']
|
||||
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
||||
class Media:
|
||||
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
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
|
@ -346,36 +190,16 @@ class BusinessCardForm(TermsForm):
|
|||
|
||||
class Meta:
|
||||
model = BusinessCard
|
||||
fields = [
|
||||
'realname',
|
||||
'email',
|
||||
'project',
|
||||
'data',
|
||||
'variant',
|
||||
'url_of_pic',
|
||||
'send_data_to_print',
|
||||
'sent_to',
|
||||
'terms_accepted',
|
||||
]
|
||||
|
||||
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
||||
fields = ['project', 'data', 'variant', 'url_of_pic', 'send_data_to_print', 'sent_to']
|
||||
class Media:
|
||||
js = ('dropdown/js/businessCard.js',)
|
||||
|
||||
|
||||
class ListForm(TermsForm):
|
||||
terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_MAILINGLISTEN
|
||||
|
||||
class ListForm(CheckForm):
|
||||
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_MAILINGLISTEN
|
||||
class Meta:
|
||||
model = List
|
||||
fields = [
|
||||
'realname',
|
||||
'email',
|
||||
'domain',
|
||||
'address',
|
||||
'terms_accepted',
|
||||
]
|
||||
fields = ['domain', 'address']
|
||||
exclude = ['intern_notes', 'survey_mail_send','mail_state']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['address'].initial = ''
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
from datetime import date, timedelta
|
||||
import sys
|
||||
|
||||
from django.core.management import CommandError
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.mail import BadHeaderError
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.template.loader import get_template
|
||||
from django.core.mail import send_mail, BadHeaderError, EmailMessage
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.conf import settings
|
||||
|
||||
from input.models import Project, Library, HonoraryCertificate, Travel, Email,\
|
||||
BusinessCard, List, IFG, Literature
|
||||
from input.utils.mail import send_email
|
||||
|
||||
from input.settings import IF_EMAIL, SURVEYPREFIX, SURVEY_EMAIL
|
||||
|
||||
class Command(BaseCommand):
|
||||
''' mails will be sent here:
|
||||
''' mails will be send here:
|
||||
|
||||
- two weeks after confirmation of support for volunteer (/extern) send link
|
||||
with surveylink
|
||||
|
|
@ -33,12 +34,16 @@ class Command(BaseCommand):
|
|||
'type': type,
|
||||
'name': name,
|
||||
'pid': pid,
|
||||
'SURVEY_PREFIX': settings.SURVEY_PREFIX, }
|
||||
|
||||
subject = 'Dein Feedback zur Förderung durch Wikimedia Deutschland'
|
||||
|
||||
'SURVEYPREFIX': SURVEYPREFIX, }
|
||||
txt_mail_template = get_template('input/survey_mail.txt')
|
||||
html_mail_template = get_template('input/survey_mail.html')
|
||||
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')
|
||||
|
||||
#survey_mail = EmailMessage('Dein Feedback zur Förderung durch Wikimedia Deutschland',
|
||||
|
|
@ -47,8 +52,8 @@ class Command(BaseCommand):
|
|||
# [email],
|
||||
# bcc=[SURVEY_EMAIL])
|
||||
#survey_mail.send(fail_silently=False)
|
||||
except BadHeaderError as error:
|
||||
raise CommandError(f'Invalid header found: {error}')
|
||||
except BadHeaderError:
|
||||
return HttpResponse('Invalid header found.')
|
||||
|
||||
print(f'send surveylinkemail to {email}...')
|
||||
|
||||
|
|
@ -66,14 +71,21 @@ class Command(BaseCommand):
|
|||
.exclude(end_mail_send = True)\
|
||||
.filter(mail_state = 'NONE')
|
||||
|
||||
subject = 'Projektende erreicht'
|
||||
recipient = settings.IF_EMAIL
|
||||
txt_mail_template = get_template('input/if_end_of_project.txt')
|
||||
html_mail_template = get_template('input/if_end_of_project.html')
|
||||
|
||||
|
||||
for project in old:
|
||||
context = {'project': project, 'URL_PREFIX': settings.EMAIL_URL_PREFIX}
|
||||
context = {'project': project}
|
||||
context['URLPREFIX'] = settings.URLPREFIX
|
||||
|
||||
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')
|
||||
|
||||
#send_mail('Projektende erreicht',
|
||||
|
|
@ -99,19 +111,33 @@ class Command(BaseCommand):
|
|||
approved_end = Project.objects.filter(status = 'END')\
|
||||
.exclude(end_mail_send = True)\
|
||||
.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
|
||||
|
||||
for project in approved_end:
|
||||
context = {'project': project, 'URL_PREFIX': settings.EMAIL_URL_PREFIX}
|
||||
context = {'project': project}
|
||||
context['URLPREFIX'] = settings.URLPREFIX
|
||||
|
||||
|
||||
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')
|
||||
|
||||
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')
|
||||
|
||||
#send_mail('Projektende erreicht',
|
||||
|
|
@ -143,24 +169,38 @@ class Command(BaseCommand):
|
|||
.exclude(end_mail_send = True)\
|
||||
.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
|
||||
|
||||
for project in approved_notHappened:
|
||||
context = {'project': project, 'URL_PREFIX': settings.EMAIL_URL_PREFIX}
|
||||
|
||||
context = {'project': project}
|
||||
context['URLPREFIX'] = settings.URLPREFIX
|
||||
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')
|
||||
|
||||
|
||||
#send_mail('Projektende erreicht',
|
||||
# mail_template.render(context),
|
||||
# IF_EMAIL,
|
||||
# [project.email],
|
||||
# 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')
|
||||
|
||||
#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'''
|
||||
lastdate = date.today() - timedelta(days=14)
|
||||
|
||||
models = Email, BusinessCard, List
|
||||
types = 'MAIL', 'VIS', 'LIST'
|
||||
|
||||
for model, typ in zip(models, types):
|
||||
supported = model.objects.filter(granted=True)\
|
||||
typefield = ('MAIL','VIS','LIST')
|
||||
count = 0
|
||||
for c in ('Email', 'BusinessCard', 'List'):
|
||||
# get class via string
|
||||
supported = getattr(sys.modules[__name__], c).objects.filter(granted=True)\
|
||||
.filter(granted_date__lt = lastdate)\
|
||||
.exclude(survey_mail_send=True)\
|
||||
.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):
|
||||
|
|
|
|||
|
|
@ -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.django_client import OAuth
|
||||
from authlib.oauth2.rfc6749 import OAuth2Token
|
||||
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from django.conf import settings
|
||||
|
||||
from input.models import Extern
|
||||
|
||||
from foerderbarometer import settings
|
||||
from input import views
|
||||
from input import models
|
||||
|
||||
class OAuthMiddleware(MiddlewareMixin):
|
||||
|
||||
|
|
@ -16,7 +14,7 @@ class OAuthMiddleware(MiddlewareMixin):
|
|||
self.oauth = OAuth()
|
||||
|
||||
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 == '/':
|
||||
return self.get_response(request)
|
||||
if settings.OAUTH_URL_WHITELISTS is not None:
|
||||
|
|
@ -39,12 +37,12 @@ class OAuthMiddleware(MiddlewareMixin):
|
|||
self.clear_session(request)
|
||||
request.session['token'] = sso_client.authorize_access_token(request)
|
||||
# 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:
|
||||
redirect_uri = request.session.pop('redirect_uri', None)
|
||||
if redirect_uri is not None:
|
||||
return redirect(redirect_uri)
|
||||
return redirect('extern')
|
||||
return redirect(views.ExternView)
|
||||
|
||||
if request.session.get('token', None) is not None:
|
||||
current_user = self.get_current_user(sso_client, request)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class Migration(migrations.Migration):
|
|||
('survey_mail_send', models.BooleanField(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)),
|
||||
('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)),
|
||||
],
|
||||
options={
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='email',
|
||||
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(
|
||||
model_name='list',
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='email',
|
||||
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(
|
||||
model_name='email',
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='ifg',
|
||||
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(
|
||||
model_name='library',
|
||||
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(
|
||||
model_name='library',
|
||||
|
|
@ -33,7 +33,7 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='literature',
|
||||
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(
|
||||
model_name='project',
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='travel',
|
||||
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(
|
||||
model_name='library',
|
||||
|
|
|
|||
|
|
@ -13,6 +13,6 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='travel',
|
||||
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(
|
||||
model_name='travel',
|
||||
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(
|
||||
model_name='travel',
|
||||
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)'),
|
||||
),
|
||||
]
|
||||
654
input/models.py
654
input/models.py
|
|
@ -1,55 +1,21 @@
|
|||
from contextlib import suppress
|
||||
from datetime import date
|
||||
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
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.safestring import mark_safe
|
||||
|
||||
from foerderbarometer.constants import *
|
||||
from .settings import ACCOUNTS
|
||||
|
||||
|
||||
EMAIL_STATES = {
|
||||
'NONE': 'noch keine Mail versendet',
|
||||
'INF': 'die Benachrichtigung zur Projektabschlussmail wurde versendet',
|
||||
'CLOSE': 'die Projektabschlussmail wurde 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
|
||||
|
||||
EMAIL_STATES = {'NONE': 'noch keine Mail versendet',
|
||||
'INF': 'die Benachrichtigung zur Projektabschlussmail wurde versendet',
|
||||
'CLOSE': 'die Projektabschlussmail wurde versendet',
|
||||
'END': 'alle automatischen Mails, auch surveyMail, wurden versendet'}
|
||||
|
||||
class Volunteer(models.Model):
|
||||
realname = models.CharField(max_length=200, null=True, verbose_name='Realname',
|
||||
help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', default='')
|
||||
realname = models.CharField(max_length=200, null=True, verbose_name="Realname",
|
||||
help_text="Bitte gib deinen Vornamen und deinen Nachnamen ein.", default='')
|
||||
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
|
||||
|
||||
|
|
@ -59,6 +25,7 @@ class Volunteer(models.Model):
|
|||
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')
|
||||
|
||||
|
||||
@classmethod
|
||||
def set_granted(cl, key, b):
|
||||
obj = cl.objects.get(pk=key)
|
||||
|
|
@ -70,16 +37,17 @@ class Volunteer(models.Model):
|
|||
abstract = True
|
||||
|
||||
|
||||
|
||||
class Extern(Volunteer):
|
||||
''' abstract basis class for all data entered by extern volunteers '''
|
||||
|
||||
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
|
||||
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
|
||||
# but maybe there is a better solution?
|
||||
super().save()
|
||||
|
|
@ -89,470 +57,263 @@ class Extern(Volunteer):
|
|||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class ConcreteExtern(Extern):
|
||||
''' needed because we can't initiate abstract base classes in the view'''
|
||||
pass
|
||||
|
||||
|
||||
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')
|
||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Kostenstelle'
|
||||
verbose_name_plural = 'Kostenstellen'
|
||||
|
||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
||||
def __str__(self):
|
||||
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)
|
||||
|
||||
return f"{self.code} {self.description}"
|
||||
|
||||
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')
|
||||
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)
|
||||
end = models.DateField('Erwartetes Projektende', null=True)
|
||||
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')
|
||||
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')
|
||||
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')
|
||||
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")
|
||||
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")
|
||||
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_real = models.IntegerField(blank=True, null=True, verbose_name='Teilnehmende ausgezählt')
|
||||
insurance = models.BooleanField(default=False, verbose_name='Haftpflichtversicherung')
|
||||
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')
|
||||
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)
|
||||
granted_date = models.DateField(blank=True, null=True, verbose_name='Bewilligt am')
|
||||
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')
|
||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
||||
account = models.ForeignKey('Account', on_delete=models.CASCADE, null=True, to_field='code', db_constraint = False)
|
||||
granted_from = models.CharField(max_length=100,null=True,verbose_name='Bewilligt von')
|
||||
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")
|
||||
|
||||
categories = ProjectCategoryField(ProjectCategory)
|
||||
wikimedia_projects = ProjectCategoryField(WikimediaProject)
|
||||
|
||||
# the following Fields are not supposed to be edited by users
|
||||
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')
|
||||
finance_id = models.CharField(max_length=15, null= True, blank=True)
|
||||
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):
|
||||
return f'{self.pid or self.id} {self.name}'
|
||||
def save(self,*args,**kwargs):
|
||||
|
||||
def save(self, *, using=None, **kwargs):
|
||||
kwargs['using'] = using
|
||||
generate_finance_id=False
|
||||
|
||||
if self.end:
|
||||
self.end_quartal = f'Q{self.end.month // 4 + 1}'
|
||||
'''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?
|
||||
|
||||
if not self.pk:
|
||||
print ("NO PK THERE");
|
||||
generate_finance_id=True
|
||||
super().save()
|
||||
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:
|
||||
self.pid = f'{self.account.code}{self.id:08d}'
|
||||
super().save(update_fields=['pid'], using=using)
|
||||
|
||||
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.'),
|
||||
],
|
||||
})
|
||||
self.pid = str(self.account.code) + str(self.pk).zfill(8)
|
||||
# self.pid = str(self.account.code) + str(self.pk).zfill(3)
|
||||
print (("Hallo Leute! Ich save jetzt mal MIT PID DANN!!!",self.pid))
|
||||
|
||||
|
||||
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:
|
||||
proxy = True
|
||||
verbose_name = 'Projekt (beantragt)'
|
||||
verbose_name_plural = 'Projekte (beantragt)'
|
||||
super().save()
|
||||
|
||||
|
||||
class ProjectDeclined(Project):
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = 'Projekt (abgelehnt)'
|
||||
verbose_name_plural = 'Projekte (abgelehnt)'
|
||||
def __str__(self):
|
||||
return f"{self.pid} {self.name}"
|
||||
|
||||
|
||||
class Intern(Volunteer):
|
||||
'''abstract base class for data entry from /intern (except Project)'''
|
||||
request_url = models.URLField(max_length=2000, verbose_name='Antrag (URL)')
|
||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class ConcreteVolunteer(Volunteer):
|
||||
''' needed because we can't initiate abstract base classes in the view'''
|
||||
pass
|
||||
|
||||
|
||||
class HonoraryCertificate(Intern):
|
||||
''' this class is also used for accreditations '''
|
||||
|
||||
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):
|
||||
return f'Bescheinigung für {self.realname}'
|
||||
return "Certificate for " + self.realname
|
||||
|
||||
|
||||
TRANSPORT_CHOICES = {
|
||||
'BAHN': 'Bahn',
|
||||
'NONE': 'Keine Fahrtkosten',
|
||||
'OTHER': 'Sonstiges (mit Begründung)',
|
||||
}
|
||||
TRANSPORT_CHOICES = {'BAHN': 'Bahn',
|
||||
'NONE': 'Keine Fahrtkosten',
|
||||
'OTHER': 'Sonstiges (mit Begründung)'}
|
||||
|
||||
PAYEDBY_CHOICES = {
|
||||
'WMDE': 'WMDE',
|
||||
'REQU': 'Antragstellender Mensch',
|
||||
}
|
||||
PAYEDBY_CHOICES = {'WMDE': 'WMDE',
|
||||
'REQU': 'Antragstellender Mensch'}
|
||||
|
||||
HOTEL_CHOICES = {
|
||||
'TRUE': mark_safe('Hotelzimmer benötigt'),
|
||||
'FALSE': mark_safe('Kein Hotelzimmer benötigt'),
|
||||
}
|
||||
HOTEL_CHOICES = {'TRUE': format_html('Hotelzimmer benötigt'),
|
||||
'FALSE': format_html('Kein Hotelzimmer benötigt')
|
||||
}
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
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 = 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')
|
||||
transport = models.CharField(max_length=5, choices=TRANSPORT_CHOICES.items(), default='BAHN', verbose_name='Transportmittel')
|
||||
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:')
|
||||
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')
|
||||
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_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')
|
||||
request_url = models.URLField(max_length=2000, verbose_name='Antrag (URL)')
|
||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
||||
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
|
||||
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:
|
||||
verbose_name = 'Reisekosten'
|
||||
verbose_name_plural = 'Reisekosten'
|
||||
from django.db.models.signals import pre_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
def __str__(self):
|
||||
return f'Reisekosten für {self.realname}'
|
||||
@receiver(pre_save, sender=Travel, dispatch_uid="get_project_end")
|
||||
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:
|
||||
instance.project_end = instance.project.end
|
||||
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
|
||||
class Grant(RequestUrlMixin, Extern):
|
||||
#abstract base class for Library and IFG
|
||||
class Grant(Extern):
|
||||
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',
|
||||
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:
|
||||
abstract = True
|
||||
|
||||
|
||||
def type_link(path, label):
|
||||
return format_html(
|
||||
format_string='<a href="{href}" target="_blank" rel="noopener">{label}</a>',
|
||||
href=f'https://de.wikipedia.org/wiki/Wikipedia:Förderung/{path}',
|
||||
label=label,
|
||||
)
|
||||
|
||||
|
||||
TYPE_CHOICES = {
|
||||
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]
|
||||
|
||||
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>'),
|
||||
'ELIT': format_html('<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Zugang_zu_Fachliteratur#eLiteraturstipendium" target="_blank" rel="noopener">eLiteraturstipendium</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>'),
|
||||
'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>'),
|
||||
'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>'),
|
||||
'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>'),
|
||||
}
|
||||
|
||||
# same model is used for Library, ELitStip and Software!
|
||||
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)
|
||||
duration = models.CharField(max_length=100, verbose_name='Dauer')
|
||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Bibliotheksstipendium'
|
||||
verbose_name_plural = 'Bibliotheksstipendien'
|
||||
|
||||
duration = models.CharField(max_length=100, verbose_name="Dauer")
|
||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
||||
def __str__(self):
|
||||
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):
|
||||
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):
|
||||
class Literature(Grant):
|
||||
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>\
|
||||
die eine eindeutige Identifizierung ermöglichen (Autor, Titel, Verlag, ISBN, ...)'))
|
||||
help_text=format_html("Bitte gib alle Informationen zum benötigten Werk an,<br>\
|
||||
die eine eindeutige Identifizierung ermöglichen (Autor, Titel, Verlag, ISBN, ...)"))
|
||||
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_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='',\
|
||||
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>\
|
||||
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')
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Literaturstipendium'
|
||||
verbose_name_plural = 'Literaturstipendien'
|
||||
|
||||
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")
|
||||
|
||||
class IFG(Grant):
|
||||
url = models.URLField(max_length=2000, verbose_name='URL',
|
||||
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')
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'IFG-Anfrage'
|
||||
verbose_name_plural = 'IFG-Anfragen'
|
||||
url = models.URLField(max_length=2000, verbose_name="URL",
|
||||
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")
|
||||
|
||||
def __str__(self):
|
||||
return f'IFG-Anfrage von {self.realname}'
|
||||
return "IFG-Anfrage von " + self.realname
|
||||
|
||||
DOMAIN_CHOICES = {'PEDIA': '@wikipedia.de',
|
||||
'BOOKS': '@wikibooks.de',
|
||||
'QUOTE': '@wikiquote.de',
|
||||
'SOURCE': '@wikisource.de',
|
||||
'VERSITY': '@wikiversity.de',}
|
||||
|
||||
DOMAIN_CHOICES = {
|
||||
'PEDIA': '@wikipedia.de',
|
||||
'BOOKS': '@wikibooks.de',
|
||||
'QUOTE': '@wikiquote.de',
|
||||
'SOURCE': '@wikisource.de',
|
||||
'VERSITY': '@wikiversity.de',
|
||||
}
|
||||
|
||||
|
||||
class Domain(RequestUrlMixin, Extern):
|
||||
class Domain(Extern):
|
||||
domain = models.CharField(max_length=10,
|
||||
choices=DOMAIN_CHOICES.items(),
|
||||
default='PEDIA')
|
||||
|
|
@ -560,101 +321,64 @@ class Domain(RequestUrlMixin, Extern):
|
|||
class Meta:
|
||||
abstract = True
|
||||
|
||||
MAIL_CHOICES = {'REALNAME': 'Vorname.Nachname',
|
||||
'USERNAME': 'Username',
|
||||
'OTHER': 'Sonstiges:'}
|
||||
|
||||
MAIL_CHOICES = {
|
||||
'REALNAME': 'Vorname.Nachname',
|
||||
'USERNAME': 'Username',
|
||||
'OTHER': 'Sonstiges',
|
||||
}
|
||||
ADULT_CHOICES = {'TRUE': format_html('Ich bin volljährig.'),
|
||||
'FALSE': format_html('Ich bin noch nicht volljährig.')
|
||||
}
|
||||
|
||||
ADULT_CHOICES = {
|
||||
'TRUE': mark_safe('Ich bin volljährig.'),
|
||||
'FALSE': mark_safe('Ich bin noch nicht volljährig.'),
|
||||
}
|
||||
|
||||
|
||||
class Email(TermsConsentMixin, Domain):
|
||||
class Email(Domain):
|
||||
address = models.CharField(max_length=50,
|
||||
choices=MAIL_CHOICES.items(),
|
||||
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.'))
|
||||
choices=MAIL_CHOICES.items(),
|
||||
default='USERNAME', verbose_name='Adressbestandteil',
|
||||
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')
|
||||
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')
|
||||
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')
|
||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'E-Mail-Adresse'
|
||||
verbose_name_plural = 'E-Mail-Adressen'
|
||||
|
||||
|
||||
class List(TermsConsentMixin, Domain):
|
||||
class List(Domain):
|
||||
address = models.CharField(max_length=50, default='NO_ADDRESS',
|
||||
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.'))
|
||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
|
||||
verbose_name="Adressbestandteil für Projektmailingliste",
|
||||
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")
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Mailingliste'
|
||||
verbose_name_plural = 'Mailinglisten'
|
||||
PROJECT_CHOICE = {'PEDIA': 'Wikipedia',
|
||||
'SOURCE': 'Wikisource',
|
||||
'BOOKS': 'Wikibooks',
|
||||
'QUOTE': 'Wikiquote',
|
||||
'VERSITY': 'Wikiversity',
|
||||
'VOYAGE': 'Wikivoyage',
|
||||
'DATA': 'Wikidata',
|
||||
'NEWS': 'Wikinews',
|
||||
'COMMONS': 'Wikimedia Commons'}
|
||||
|
||||
BC_VARIANT = {'PIC': 'mit Bild',
|
||||
'NOPIC': 'ohne Bild'}
|
||||
|
||||
PROJECT_CHOICE = {
|
||||
'PEDIA': 'Wikipedia',
|
||||
'SOURCE': 'Wikisource',
|
||||
'BOOKS': 'Wikibooks',
|
||||
'QUOTE': 'Wikiquote',
|
||||
'VERSITY': 'Wikiversity',
|
||||
'VOYAGE': 'Wikivoyage',
|
||||
'DATA': 'Wikidata',
|
||||
'NEWS': 'Wikinews',
|
||||
'COMMONS': 'Wikimedia Commons',
|
||||
}
|
||||
|
||||
BC_VARIANT = {
|
||||
'PIC': 'mit Bild',
|
||||
'NOPIC': 'ohne Bild',
|
||||
}
|
||||
|
||||
|
||||
class BusinessCard(RequestUrlMixin, TermsConsentMixin, Extern):
|
||||
class BusinessCard(Extern):
|
||||
project = models.CharField(max_length=20, choices=PROJECT_CHOICE.items(),
|
||||
default='PEDIA', verbose_name='Wikimedia-Projekt',
|
||||
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='',
|
||||
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>\
|
||||
(z.B. Vorname Nachname, Benutzer:/Benutzerin:, Benutzer-/-innenname, Anschrift,<br>\
|
||||
Telefonnummer, E-Mail-Adresse usw.). Trenne die einzelnen Angaben durch Zeilenumbrüche.<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>\
|
||||
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(),
|
||||
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">\
|
||||
mit Bild</a> <a href="https://upload.wikimedia.org/wikipedia/commons/d/d3/Muster_Visitenkarte_WMDE.png">ohne Bild</a>'))
|
||||
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>' ))
|
||||
|
||||
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',
|
||||
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.'))
|
||||
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,
|
||||
}
|
||||
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=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")
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
|
@ -5,11 +12,3 @@ span.datetimeshortcuts {
|
|||
span.datetimeshortcuts > a:nth-child(2) {
|
||||
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:
|
||||
<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>
|
||||
mit freundlichen Grüßen, Eure Lieblingsdatenbank
|
||||
|
||||
|
|
@ -5,6 +5,6 @@ Ende erreicht.
|
|||
|
||||
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
|
||||
|
|
@ -8,7 +8,7 @@ Ende erreicht.
|
|||
<br><br>
|
||||
Hier könnt ihr es in der Datenbank editieren:
|
||||
<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>
|
||||
|
||||
Projektorganisator*in wurde über den Projektabschluss informiert.
|
||||
|
|
@ -5,7 +5,7 @@ Ende erreicht.
|
|||
|
||||
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.
|
||||
|
|
@ -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 %}
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}"/>
|
||||
<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' %}">
|
||||
{% csrf_token %}
|
||||
|
||||
|
||||
<center>
|
||||
<style>
|
||||
ul > li {
|
||||
list-style-type: none;
|
||||
}
|
||||
ul {
|
||||
padding-left: 10;
|
||||
}
|
||||
label.required::after {
|
||||
content: ' *';
|
||||
color: red;
|
||||
}
|
||||
.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.
|
||||
<p>
|
||||
<div class="div5"></div>
|
||||
<div class="button button1"><a href="/extern"><div class="button1_text">Anmelden</div></a></div>
|
||||
<div class="div5"></div>
|
||||
<div class="div5"></div>
|
||||
<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>.
|
||||
<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>
|
||||
<p>
|
||||
</center>
|
||||
|
||||
|
||||
<div class="page-centered">
|
||||
<div class="spacer-5"></div>
|
||||
<p role="heading" aria-level="1">
|
||||
Herzlich willkommen im Förderanfrageportal von Wikimedia Deutschland!
|
||||
</p>
|
||||
<div class="spacer-5"></div>
|
||||
<p>
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/c/c4/Figuren_klein.jpg">
|
||||
</p>
|
||||
<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.
|
||||
</p>
|
||||
<div class="spacer-5"></div>
|
||||
<a href="/extern" class="button button-login">Anmelden</a>
|
||||
<div class="spacer-5"></div>
|
||||
<div class="spacer-5"></div>
|
||||
<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>.
|
||||
<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>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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>
|
||||
|
||||
|
|
@ -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
|
||||
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
|
||||
|
|
@ -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.views.i18n import JavaScriptCatalog
|
||||
|
||||
from .views import (
|
||||
index,
|
||||
done,
|
||||
export,
|
||||
authorize,
|
||||
deny,
|
||||
ApplicationView,
|
||||
ApplicationStartView,
|
||||
ProjectFundingInfoView,
|
||||
)
|
||||
from django.urls import path
|
||||
from .views import ExternView, index, done, authorize, deny, InternView, export
|
||||
from django.contrib import admin
|
||||
|
||||
urlpatterns = [
|
||||
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('export', export, name='export'),
|
||||
path('authorize/<str:choice>/<int:pk>', authorize, name='authorize'),
|
||||
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 dataclasses import dataclass
|
||||
from datetime import date
|
||||
from smtplib import SMTPException
|
||||
from typing import Optional
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.forms import ChoiceField, Field
|
||||
from django.shortcuts import render, redirect
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.urls import reverse
|
||||
from django.utils.choices import flatten_choices
|
||||
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.shortcuts import render
|
||||
from django.forms import modelformset_factory
|
||||
from django.http import HttpResponse
|
||||
from formtools.wizard.views import CookieWizardView
|
||||
from django.core.mail import send_mail, BadHeaderError, EmailMultiAlternatives
|
||||
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.views.generic import TemplateView
|
||||
from django.views.generic.edit import FormView
|
||||
from django.utils.html import strip_tags
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from input.utils.admin import admin_url_name
|
||||
from input.utils.mail import build_email, collect_and_attach
|
||||
from .forms import ProjectForm, ExternForm, LibraryForm, IFGForm, LiteratureForm,\
|
||||
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 (
|
||||
BaseApplicationForm,
|
||||
ProjectForm,
|
||||
LibraryForm,
|
||||
ELiteratureForm,
|
||||
SoftwareForm,
|
||||
IFGForm,
|
||||
LiteratureForm,
|
||||
TravelForm,
|
||||
EmailForm,
|
||||
ListForm,
|
||||
BusinessCardForm,
|
||||
)
|
||||
from .models import (
|
||||
MODELS,
|
||||
LIBRARY_TYPES,
|
||||
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:
|
||||
def auth_deny(choice,pk,auth):
|
||||
if choice in ('BIB', 'ELIT', 'SOFT'):
|
||||
Library.set_granted(pk,auth)
|
||||
elif choice == 'LIT':
|
||||
Literature.set_granted(pk,auth)
|
||||
elif choice == 'IFG':
|
||||
IFG.set_granted(pk,auth)
|
||||
elif choice == 'TRAV':
|
||||
Travel.set_granted(pk,auth)
|
||||
elif choice == 'VIS':
|
||||
BusinessCard.set_granted(pk,auth)
|
||||
elif choice == 'MAIL':
|
||||
Email.set_granted(pk,auth)
|
||||
elif choice == 'LIST':
|
||||
List.set_granted(pk,auth)
|
||||
else:
|
||||
return HttpResponse(f'ERROR! UNKNOWN CHOICE TYPE! {choice}')
|
||||
|
||||
MODELS[choice].set_granted(pk, auth)
|
||||
|
||||
return False
|
||||
|
||||
@login_required
|
||||
def export(request):
|
||||
'''export the project database to a csv'''
|
||||
return HttpResponse('WE WANT CSV!')
|
||||
|
||||
|
||||
@login_required
|
||||
def authorize(request, choice, pk):
|
||||
'''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.'''
|
||||
|
||||
if ret := auth_deny(choice, pk, True):
|
||||
ret = auth_deny(choice, pk, True)
|
||||
if ret:
|
||||
return ret
|
||||
else:
|
||||
return HttpResponse(f'AUTHORIZED! choice: {choice}, pk: {pk}')
|
||||
|
||||
return HttpResponse(f"AUTHORIZED! choice: {choice}, pk: {pk}")
|
||||
|
||||
@login_required
|
||||
def deny(request, choice, pk):
|
||||
'''If IF denies a support they click a link in a mail which leads 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
|
||||
else:
|
||||
return HttpResponse(f'DENIED! choice: {choice}, pk: {pk}')
|
||||
return HttpResponse(f"DENIED! choice: {choice}, pk: {pk}")
|
||||
|
||||
|
||||
def done(request):
|
||||
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.')
|
||||
|
||||
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.")
|
||||
|
||||
def index(request):
|
||||
return render(request, 'input/index.html')
|
||||
|
||||
class InternView(LoginRequiredMixin, CookieWizardView):
|
||||
'''This View is for WMDE-employees only'''
|
||||
|
||||
class ApplicationStartView(TemplateView):
|
||||
template_name = 'input/forms/extern.html'
|
||||
extra_context = {'applications': APPLICATIONS}
|
||||
template_name = 'input/extern.html'
|
||||
form_list = [InternForm, ProjectForm]
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if url := request.POST.get('url'):
|
||||
if url_has_allowed_host_and_scheme(url, None):
|
||||
return redirect(url)
|
||||
def get_form(self, step=None, data=None, files=None):
|
||||
'''this function determines which part of the multipart form is
|
||||
displayed next'''
|
||||
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
|
||||
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 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: {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
|
||||
|
||||
def form_valid(self, form):
|
||||
"""
|
||||
Process a valid form submission:
|
||||
- Enrich form data (e.g., set type_code, handle special rules).
|
||||
- Save the model instance and related data.
|
||||
- Send confirmation and notification mails.
|
||||
- Return the "done" response.
|
||||
"""
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if hasattr(self, 'choice'):
|
||||
context["choice"] = INTERN_CHOICES[self.choice]
|
||||
return context
|
||||
|
||||
data = self.prepare_data(form)
|
||||
obj = self.save_obj(form, data)
|
||||
def done(self, form_list, **kwargs):
|
||||
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):
|
||||
return response
|
||||
if data['choice'] == 'LIT':
|
||||
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)
|
||||
|
||||
def prepare_data(self, form):
|
||||
# Collect cleaned data and mark the current type
|
||||
return {**form.cleaned_data, 'choice': self.type_code}
|
||||
# these where used as labels in the second form TYPE_CHOICES is used for the first form and the
|
||||
# text above the second form. only used for BIB, SOFT, ELIT in the moment
|
||||
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):
|
||||
# Save model instance
|
||||
HELP_CHOICES = {'BIB': format_html("In welchem Zeitraum möchtest du recherchieren oder<br>wie lange ist der Bibliotheksausweis gültig?"),
|
||||
'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)
|
||||
# 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 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:
|
||||
if data['choice'] == 'LIT':
|
||||
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_m2m()
|
||||
form.save()
|
||||
|
||||
return modell
|
||||
|
||||
def send_mail(self, obj, data):
|
||||
# Prepare minimal mail context and send mails
|
||||
|
||||
context = self.get_email_context(obj, data)
|
||||
applicant_subject = 'Deine Förderanfrage bei Wikimedia Deutschland'
|
||||
staff_subject = 'Anfrage {type_label} von {applicant_name}'.format(**context)
|
||||
# add some data to context for mail templates
|
||||
data['pk'] = modell.pk
|
||||
data['urlprefix'] = settings.URLPREFIX
|
||||
data['grant'] = ('LIT', 'SOFT', 'ELIT', 'BIB', 'IFG')
|
||||
data['DOMAIN'] = ('MAIL', 'LIST')
|
||||
data['typestring'] = TYPE_CHOICES[data['choice']]
|
||||
|
||||
# we need to send the following mails here:
|
||||
context = { 'data': data }
|
||||
try:
|
||||
self.send_email('applicant', 'ifg_volunteer_mail', applicant_subject, data['email'], context)
|
||||
self.send_email('staff', 'if_mail', staff_subject, settings.IF_EMAIL, context)
|
||||
# - mail with entered data to the Volunteer
|
||||
|
||||
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:
|
||||
obj.delete()
|
||||
modell.delete()
|
||||
return HttpResponse('Invalid header found. Data not saved!')
|
||||
except SMTPException:
|
||||
obj.delete()
|
||||
return HttpResponse('Error in sending mails (probably wrong address?). Data not saved!')
|
||||
modell.delete()
|
||||
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):
|
||||
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'
|
||||
return done(self.request)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,20 @@
|
|||
Authlib==1.6.1
|
||||
Django==5.2.5
|
||||
gunicorn==23.0.0
|
||||
mysqlclient==2.2.7
|
||||
python-dotenv==1.1.1
|
||||
whitenoise==6.9.0
|
||||
asgiref==3.2.10
|
||||
Authlib==1.2.1
|
||||
certifi==2023.7.22
|
||||
cffi==1.16.0
|
||||
chardet==5.2.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