Compare commits

..

No commits in common. "master" and "simpleauth" have entirely different histories.

38 changed files with 584 additions and 768 deletions

131
README.md
View File

@ -1,79 +1,52 @@
# eva # eva
A simple tool for on- and offboarding people in a mid sized organisation. A simple tool for on- and offboarding people in a mid sized organisation.
"EVA" is an german acronym for "Eintritt, Veränderung, Austritt", "EVA" is an german acronym for "Eintritt, Veränderung, Austritt",
meaning "Onboarding, Change, Offboarding" meaning "Onboarding, Change, Offboarding"
# development # development
- install gettext for instance via `apt install gettext` for translations - set up a virtual environment with virtualenvwrapper or some other
- set up a virtual environment with virtualenvwrapper or some other environment managing tool environment managing tool
- use this environment and do
``` - use this environment and do
pip install django django-multiselectfield django-formtools django-allauth
``` pip install django django-multiselectfield django-formtools
- clone this repository
- `ln -sr eva/settings_development.py eva/settings.py` - clone this repository
- initialise your database with `python manage.py migrate`
- start your development server with `python manage.py runserver` - initialise your database with
# oauth python manage.py migrate
- You need to add oauth information in the django backend via .../admin in "Social Accounts" - start your development server with
# production python manage.py runserver
- you can use gunicorn as server for example instead of the django development server. # production
- we use whitenoise for serving static files
- we still use the development SQLITE database from django - you can use gunicorn as server for example instead of the django development server.
do the following in the project main directory: - we use whitenoise for serving static files
```
ln -sr eva/settings_production.py eva/settings.py - we still use the development SQLITE database from django
```
edit /secrets.json to contain something similar to do the following in the project main directory:
```
{ ln -sr eva/settings_production.py eva/settings.py
"SECRET_KEY": "THIS IS ANOTHER SECRET!"
} edit /secrets.json to contain something similar to
```
run the following commands: {
``` "SECRET_KEY": "THIS IS ANOTHER SECRET!"
python3 manage.py migrate }
python3 manage.py collectstatic
django-admin compilemessages run the following commands:
```
python3 manage.py migrate
As root create a file `/etc/systemd/system` (it's already deployed by puppet when the corresponding manifest is applied): python3 manage.py collectstatic
``` server starts with
# /etc/systemd/system/eva.service
# nohup gunicorn --forwarded-allow-ips="*" -b '0:8000' eva.wsgi 2&> logfile &
[Unit]
Description=gunicorn EVA daemon
After=network.target
[Service]
Type=simple
User=eva
Group=eva
RuntimeDirectory=eva
WorkingDirectory=/home/eva/eva
Environment=PYTHONUNBUFFERED=TRUE
ExecStart=/usr/bin/gunicorn --forwarded-allow-ips='*' -b '0:8000' eva.wsgi
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target
```
Adapt the paths in the file accordingly. Then, still as root, run:
```
systemctl enable --now eva.service
```
This enables the service to start at boot time and starts it immediately now. The daemon logs to the journal. You can see the last 10 lines by running `systemctl status eva.service` or the whole log by running `journalctl -u eva.service`. The usual switches to manipulate that output are available.

8
TODO
View File

@ -1,8 +0,0 @@
* switch to django 4
minor stuff:
* remove dot before "Nextcloud" at login page.
* add Testmode warning at login screen

View File

@ -11,7 +11,6 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
""" """
from pathlib import Path from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
@ -46,11 +45,6 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.nextcloud',
'multiselectfield', 'multiselectfield',
'formtools', 'formtools',
] ]
@ -59,7 +53,6 @@ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
@ -121,7 +114,7 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/ # https://docs.djangoproject.com/en/3.1/topics/i18n/
LANGUAGE_CODE = 'de' LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC' TIME_ZONE = 'UTC'
@ -131,31 +124,8 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
LOCALE_PATHS = ( os.path.join(BASE_DIR, 'locale'), )
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/ # https://docs.djangoproject.com/en/3.1/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = '/static/'
# settings needed for allauth
SOCIALACCOUNT_PROVIDERS = {
'nextcloud': {
'SERVER': 'https://develwolke.wikimedia.de/',
#'SERVER': 'https://wolke.wikimedia.de',
#'SERVER': 'https://cloud.bucky.uber.space/'
}
}
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
)
SITE_ID = 1
ACCOUNT_EMAIL_VERIFICATION = 'none'
# ACCOUNT_EMAIL_REQUIRED = True
LOGIN_REDIRECT_URL = 'home'
ACCOUNT_LOGOUT_ON_GET = True

View File

@ -25,6 +25,9 @@ EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = '10.0.6.25' EMAIL_HOST = '10.0.6.25'
EMAIL_PORT = '25' EMAIL_PORT = '25'
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# get secrets # get secrets
with open(os.path.join(BASE_DIR, 'secrets.json')) as secrets_file: with open(os.path.join(BASE_DIR, 'secrets.json')) as secrets_file:
secrets = json.load(secrets_file) secrets = json.load(secrets_file)
@ -47,11 +50,14 @@ SECRET_KEY = get_secret('SECRET_KEY')
DEBUG = False DEBUG = False
# send mails only to debug mode adress even if in production # send mails only to debug mode adress even if in production
MAILTEST = False MAILTEST = True
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
# SECRET_KEY = 'g%+i6+gkwt3zz@+k-5x1dtstuw4)&qd$lxd^bt2oswy5e1#dul'
STATIC_ROOT = BASE_DIR / 'staticfiles' STATIC_ROOT = BASE_DIR / 'staticfiles'
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS = ['*']
@ -67,11 +73,6 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.nextcloud',
'multiselectfield', 'multiselectfield',
'formtools', 'formtools',
] ]
@ -81,7 +82,6 @@ MIDDLEWARE = [
'whitenoise.middleware.WhiteNoiseMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
@ -142,7 +142,7 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/ # https://docs.djangoproject.com/en/3.1/topics/i18n/
LANGUAGE_CODE = 'de' LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC' TIME_ZONE = 'UTC'
@ -152,29 +152,8 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
LOCALE_PATHS = ( os.path.join(BASE_DIR, 'locale'), )
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/ # https://docs.djangoproject.com/en/3.1/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = '/static/'
# settings needed for allauth
SOCIALACCOUNT_PROVIDERS = {
'nextcloud': {
'SERVER': 'https://wolke.wikimedia.de/',
}
}
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
)
SITE_ID = 1
ACCOUNT_EMAIL_VERIFICATION = 'none'
# ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_LOGOUT_ON_GET = True
LOGIN_REDIRECT_URL = 'home'

View File

@ -19,6 +19,5 @@ from django.urls import path, include
urlpatterns = [ urlpatterns = [
path('', include("evapp.urls")), path('', include("evapp.urls")),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('accounts/', include('allauth.urls')), path('accounts/', include('django.contrib.auth.urls')),
] ]

View File

@ -29,35 +29,28 @@ class PersonalForm(EvaForm):
class Meta: class Meta:
model = Employee model = Employee
fields = ['firstname', 'lastname', 'department', 'team', ] fields = ['usermail', 'firstname', 'lastname', 'intern', 'department', 'team', ]
class WorkingForm(EvaForm): class WorkingForm(EvaForm):
def clean(self): def clean(self):
data = self.cleaned_data data = self.cleaned_data
if data['works_in_gs'] and data['desk'] is None: if not data['remote'] and data['desk'] is None:
raise ValidationError('Wer nicht remote arbeitet braucht einen Schreibtisch!') raise ValidationError('Wer nicht remote arbeitet braucht einen Schreibtisch!')
return data return data
class Meta: class Meta:
model = Employee model = Employee
fields = ['firstdate_employment', 'firstdate_presence', 'jobdescription_german', fields = ['firstdate_employment', 'firstdate_presence', 'jobdescription_german',
'jobdescription_english', 'works_in_gs', 'desk',] 'jobdescription_english', 'remote', 'desk',]
widgets = {'firstdate_employment': DateInput(attrs={'type': 'date'}), widgets = {'firstdate_employment': DateInput(attrs={'type': 'date'}),
'firstdate_presence': DateInput(attrs={'type': 'date'}),} 'firstdate_presence': DateInput(attrs={'type': 'date'}),}
class ITForm(EvaForm): class ITForm(EvaForm):
def clean(self):
data = self.cleaned_data
if data['vendor'] == 'MAC' and data['os'] != 'MOS':
raise ValidationError('Ein MAC sollte Mac OS installiert haben')
return data
class Meta: class Meta:
model = Employee model = Employee
fields = [ fields = [
'vendor', 'os', 'keyboard', 'screen', 'mobile', 'comment', 'vendor', 'os', 'keyboard', 'screen', 'mobile', 'sim', 'comment',
'language', 'accounts', 'lists', 'rebu2go' ] 'language', 'accounts', 'lists', 'rebu2go' ]
class OfficeForm(EvaForm): class OfficeForm(EvaForm):

View File

@ -1,4 +1,4 @@
# Generated by Django 3.1.4 on 2021-09-13 12:41 # Generated by Django 3.1.4 on 2020-12-23 12:14
from django.db import migrations, models from django.db import migrations, models
import multiselectfield.db.fields import multiselectfield.db.fields
@ -18,27 +18,23 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('firstname', models.CharField(max_length=50, verbose_name='Vorname')), ('firstname', models.CharField(max_length=50, verbose_name='Vorname')),
('lastname', models.CharField(max_length=50, verbose_name='Nachname')), ('lastname', models.CharField(max_length=50, verbose_name='Nachname')),
('department', models.CharField(choices=[('PROG', 'Programme'), ('SOFT', 'Softwareentwicklung'), ('CENT', 'Central'), ('VOR', 'Vorstand')], max_length=5, verbose_name='Bereich')), ('email', models.CharField(max_length=50, verbose_name='E-Mail-Adresse')),
('department', models.CharField(choices=[('PROG', 'Programme'), ('SOFT', 'Softwareentwicklung'), ('CENT', 'Central'), ('VOR', 'Vorstand')], max_length=5)),
('team', models.CharField(blank=True, max_length=20, null=True)), ('team', models.CharField(blank=True, max_length=20, null=True)),
('firstdate_employment', models.DateField(null=True, verbose_name='Erster Arbeitstag')), ('firstdate_employment', models.DateField(null=True, verbose_name='erster Arbeitstag')),
('firstdate_presence', models.DateField(null=True, verbose_name='Erster Tag der Anwesenheit in der Geschäftsstelle')), ('firstdate_presence', models.DateField(null=True, verbose_name='erster Tag der Anwesenheit in der Geschäftsstelle')),
('jobdescription_german', models.CharField(max_length=100, null=True, verbose_name='Stellenbezeichnung(deutsch)')), ('jobdescription_german', models.CharField(max_length=100, null=True, verbose_name='Stellenbeschreibung(deutsch)')),
('jobdescription_english', models.CharField(max_length=100, null=True, verbose_name='Job description(english)')), ('jobdescription_english', models.CharField(max_length=100, null=True, verbose_name='job description(english)')),
('remote', models.BooleanField(default=True, verbose_name='Braucht Arbeitsplatz in der Geschäftsstelle?')), ('desk', models.CharField(max_length=100, null=True, verbose_name='Wo soll der Arbeitsplatz sein?')),
('desk', models.CharField(blank=True, max_length=100, null=True, verbose_name='Wo soll der Arbeitsplatz sein?')), ('laptop', models.CharField(choices=[('14', '14", unser Standardgerät'), ('12', '12,5", geeignet für Vielreisende')], default='14', max_length=2)),
('vendor', models.CharField(choices=[('STANDARD', 'Dell Latitude'), ('LENOVO', 'Lenovo Thinkpad'), ('MAC', 'Mac (nur für Grafiker_innen)')], default='STANDARD', max_length=8, verbose_name='Hersteller')), ('os', models.CharField(choices=[('UBU', 'Ubuntu (Standard)'), ('WIN', 'Windows (bitte Begründung angeben)')], default='UBU', max_length=3)),
('os', models.CharField(choices=[('UBU', 'Ubuntu (Standard)'), ('WIN', 'Windows (bitte Begründung angeben)'), ('MOS', 'Mac OS (nur wenn Mac gewählt)')], default='UBU', max_length=3, verbose_name='Betriebssystem')), ('screen', models.BooleanField(default=False, verbose_name='zusätzlicher Monitor? Einer ist standard.')),
('screen', models.BooleanField(default=False, verbose_name='Zusätzlicher Monitor? Einer ist standard.')), ('mobile', models.CharField(default='NO', max_length=6)),
('mobile', models.BooleanField(default=False, max_length=6, verbose_name='Diensttelefon (Handy)')), ('landline', models.BooleanField(default=True, verbose_name='Festnetztelefon')),
('keyboard', models.CharField(choices=[('DE', 'Deutsch'), ('US', 'USA'), ('OT', 'Anderes (Bitte unten angeben)')], default='DE', max_length=2, verbose_name='Tastaturlayout')),
('comment', models.TextField(blank=True, max_length=500, null=True, verbose_name='zusätzliche IT-Anforderungen')), ('comment', models.TextField(blank=True, max_length=500, null=True, verbose_name='zusätzliche IT-Anforderungen')),
('language', models.CharField(choices=[('GER', 'Deutsch'), ('ENG', 'English')], default='GER', max_length=3, verbose_name='Sprache')), ('language', models.CharField(choices=[('GER', 'deutsch'), ('ENG', 'english')], default='GER', max_length=3)),
('accounts', multiselectfield.db.fields.MultiSelectField(blank=True, choices=[('OTRSWMDE', 'OTRS Ticketsystem'), ('CIVIC1', 'Civic CRM (allgemein)'), ('CIVIC2', 'Civic CRM (Mailings, impliziert allgemein)'), ('WEB', 'www.wikimedia.de (edit)'), ('BLOG', 'blog.wikimedia.de (edit)'), ('FORUM', 'forum.wikimedia.de')], max_length=37, null=True, verbose_name='Zusätzliche Accounts')), ('accounts', multiselectfield.db.fields.MultiSelectField(choices=[('OTRSWMDE', 'OTRS (WMDE)'), ('OTRSFUND', 'OTRS (Fundraising)'), ('CIVIC1', 'Civic CRM (allgemein)'), ('CIVIC2', 'Civic CRM (Mailings, impliziert allgemein)'), ('FUND', 'Fundraising Netzlaufwerk'), ('WEB', 'www.wikimedia.de (edit)'), ('BLOG', 'blog.wikimedia.de (edit)'), ('FORUM', 'forum.wikimedia.de')], max_length=51, null=True)),
('lists', models.CharField(blank=True, max_length=100, null=True, verbose_name='Zusätzliche Mailinglisten')), ('lists', models.CharField(max_length=100, null=True)),
('rebu2go', models.BooleanField(default=False, verbose_name='Rebu2Go-Zugang benötigt?')),
('transponder', models.CharField(choices=[('NORM', 'Allgemeiner Transponder'), ('SPECIAL', 'Besondere Schließungen (bitte angeben)'), ('NOTRANS', 'Kein Transponder')], default='NORM', max_length=7)),
('special', models.TextField(blank=True, max_length=500, null=True, verbose_name='Besondere Schließungen hier eintragen')),
('post_office_box', models.BooleanField(default=True, verbose_name='Postfach am Empfang benötigt?')),
], ],
), ),
] ]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.1.4 on 2020-12-23 12:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='employee',
name='post_office_box',
field=models.BooleanField(default=True, verbose_name='Postfach am Empfang benötigt?'),
),
migrations.AddField(
model_name='employee',
name='transponder',
field=models.CharField(choices=[('NORM', 'allgemeiner Transponder'), ('SPECIAL', 'besondere Schließungen (bitte angeben)'), ('NO', 'Kein Transponder')], default='NORM', max_length=7),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.1.4 on 2021-09-14 10:55
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('evapp', '0001_initial'),
]
operations = [
migrations.RenameField(
model_name='employee',
old_name='remote',
new_name='works_in_gs',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.4 on 2021-01-14 13:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0002_auto_20201223_1223'),
]
operations = [
migrations.AlterField(
model_name='employee',
name='transponder',
field=models.CharField(choices=[('NORM', 'allgemeiner Transponder'), ('SPECIAL', 'besondere Schließungen (bitte angeben)'), ('NOTRANS', 'Kein Transponder')], default='NORM', max_length=7),
),
]

View File

@ -1,28 +0,0 @@
# Generated by Django 3.1.4 on 2022-02-08 09:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0002_auto_20210914_1055'),
]
operations = [
migrations.AlterField(
model_name='employee',
name='department',
field=models.CharField(choices=[('PROG', 'Programme'), ('SOFT', 'Softwareentwicklung'), ('CENT', 'Central'), ('KOMEV', 'Kommunikation und Events'), ('VOR', 'Vorstand')], max_length=5, verbose_name='Bereich'),
),
migrations.AlterField(
model_name='employee',
name='team',
field=models.CharField(blank=True, max_length=50, null=True),
),
migrations.AlterField(
model_name='employee',
name='works_in_gs',
field=models.BooleanField(default=True, verbose_name='Braucht Arbeitsplatz in der Geschäftsstelle?)'),
),
]

View File

@ -1,24 +0,0 @@
# Generated by Django 4.2.4 on 2023-08-11 10:28
from django.db import migrations, models
import multiselectfield.db.fields
class Migration(migrations.Migration):
dependencies = [
('evapp', '0003_auto_20220208_0955'),
]
operations = [
migrations.AlterField(
model_name='employee',
name='accounts',
field=multiselectfield.db.fields.MultiSelectField(blank=True, choices=[('OTRSWMDE', 'OTRS Ticketsystem'), ('CIVIC1', 'Civic CRM (allgemein)'), ('CIVIC2', 'Civic CRM (Mailings, impliziert allgemein)'), ('WEB', 'www.wikimedia.de (edit)'), ('BLOG', 'blog.wikimedia.de (edit)'), ('FORUM', 'forum.wikimedia.de')], max_length=10, null=True, verbose_name='Zusätzliche Accounts'),
),
migrations.AlterField(
model_name='employee',
name='department',
field=models.CharField(choices=[('COENG', 'Communitys & Engagement'), ('SOFT', 'Softwareentwicklung'), ('CENT', 'Central'), ('KOMAD', 'Kommunikation & Advocacy'), ('VOR', 'Vorstand')], max_length=5, verbose_name='Bereich'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.4 on 2021-02-08 11:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0003_auto_20210114_1357'),
]
operations = [
migrations.AddField(
model_name='employee',
name='intern',
field=models.BooleanField(default=True, verbose_name='Interne_r Mitarbeiter_in?'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 4.2.4 on 2023-08-17 11:08
from django.db import migrations
import multiselectfield.db.fields
class Migration(migrations.Migration):
dependencies = [
('evapp', '0004_alter_employee_accounts_alter_employee_department'),
]
operations = [
migrations.AlterField(
model_name='employee',
name='accounts',
field=multiselectfield.db.fields.MultiSelectField(blank=True, choices=[('OTRSWMDE', 'OTRS Ticketsystem'), ('CIVIC1', 'Civic CRM (allgemein)'), ('CIVIC2', 'Civic CRM (Mailings, impliziert allgemein)'), ('WEB', 'www.wikimedia.de (edit)'), ('BLOG', 'blog.wikimedia.de (edit)'), ('FORUM', 'forum.wikimedia.de')], max_length=40, null=True, verbose_name='Zusätzliche Accounts'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.4 on 2021-02-08 13:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0004_employee_intern'),
]
operations = [
migrations.AddField(
model_name='employee',
name='vendor',
field=models.CharField(choices=[('STANDARD', 'Dell Latitude'), ('LENOVO', 'Lenovo Thinkpad'), ('MAC', 'Mac (nur in Ausnahmefällen)')], default='STANDARD', max_length=8),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 3.1.4 on 2021-02-09 09:39
from django.db import migrations, models
import multiselectfield.db.fields
class Migration(migrations.Migration):
dependencies = [
('evapp', '0005_employee_vendor'),
]
operations = [
migrations.AddField(
model_name='employee',
name='rebu2go',
field=models.BooleanField(default=False, verbose_name='Rebu2Go-Zugang benötigt?'),
),
migrations.AlterField(
model_name='employee',
name='accounts',
field=multiselectfield.db.fields.MultiSelectField(choices=[('OTRSWMDE', 'OTRS Ticketsystem'), ('CIVIC1', 'Civic CRM (allgemein)'), ('CIVIC2', 'Civic CRM (Mailings, impliziert allgemein)'), ('ZEDA', 'Zeda Nextcloud'), ('WEB', 'www.wikimedia.de (edit)'), ('BLOG', 'blog.wikimedia.de (edit)'), ('FORUM', 'forum.wikimedia.de')], max_length=42, null=True),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.1.4 on 2021-02-09 11:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0006_auto_20210209_0939'),
]
operations = [
migrations.AddField(
model_name='employee',
name='remote',
field=models.BooleanField(default=False, verbose_name='Braucht keinen Arbeitsplatz weil Home-Office'),
),
migrations.AlterField(
model_name='employee',
name='desk',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Wo soll der Arbeitsplatz sein?'),
),
]

View File

@ -0,0 +1,53 @@
# Generated by Django 3.1.4 on 2021-03-01 10:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0007_auto_20210209_1146'),
]
operations = [
migrations.AddField(
model_name='employee',
name='special',
field=models.TextField(blank=True, max_length=500, null=True, verbose_name='Besondere Schließungen hier eintragen'),
),
migrations.AlterField(
model_name='employee',
name='firstdate_employment',
field=models.DateField(null=True, verbose_name='Erster Arbeitstag'),
),
migrations.AlterField(
model_name='employee',
name='firstdate_presence',
field=models.DateField(null=True, verbose_name='Erster Tag der Anwesenheit in der Geschäftsstelle'),
),
migrations.AlterField(
model_name='employee',
name='jobdescription_english',
field=models.CharField(max_length=100, null=True, verbose_name='Job description(english)'),
),
migrations.AlterField(
model_name='employee',
name='language',
field=models.CharField(choices=[('GER', 'Deutsch'), ('ENG', 'English')], default='GER', max_length=3),
),
migrations.AlterField(
model_name='employee',
name='laptop',
field=models.CharField(choices=[('14', '14", uUser Standardgerät'), ('12', '12,5", Geeignet für Vielreisende')], default='14', max_length=2),
),
migrations.AlterField(
model_name='employee',
name='screen',
field=models.BooleanField(default=False, verbose_name='Zusätzlicher Monitor? Einer ist standard.'),
),
migrations.AlterField(
model_name='employee',
name='transponder',
field=models.CharField(choices=[('NORM', 'Allgemeiner Transponder'), ('SPECIAL', 'Besondere Schließungen (bitte angeben)'), ('NOTRANS', 'Kein Transponder')], default='NORM', max_length=7),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 3.1.4 on 2021-03-15 14:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0008_auto_20210301_1053'),
]
operations = [
migrations.AddField(
model_name='employee',
name='usermail',
field=models.EmailField(default='bestechefin@wikimedia.de', max_length=50, verbose_name='Deine Mailadresse'),
),
migrations.AlterField(
model_name='employee',
name='email',
field=models.EmailField(max_length=50, verbose_name='E-Mail-Adresse ders Mitarbeitenden'),
),
migrations.AlterField(
model_name='employee',
name='laptop',
field=models.CharField(choices=[('14', '14", Unser Standardgerät'), ('12', '12,5", Geeignet für Vielreisende')], default='14', max_length=2),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.4 on 2021-03-25 08:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0009_auto_20210315_1408'),
]
operations = [
migrations.AlterField(
model_name='employee',
name='jobdescription_german',
field=models.CharField(max_length=100, null=True, verbose_name='Stellenbezeichnung(deutsch)'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.1.4 on 2021-03-29 12:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0010_auto_20210325_0813'),
]
operations = [
migrations.AddField(
model_name='employee',
name='sim',
field=models.BooleanField(default=False, verbose_name='Mobilfunkvertrag'),
),
migrations.AddField(
model_name='employee',
name='sim2',
field=models.BooleanField(default=False, verbose_name='Zweite Sim (für Laptop zB)'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.4 on 2021-03-30 08:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0011_auto_20210329_1254'),
]
operations = [
migrations.AddField(
model_name='employee',
name='bvg',
field=models.BooleanField(default=True, verbose_name='Bekommt eine BVG-Karte, weil in Berlin tätig'),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.1.4 on 2021-04-01 08:30
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('evapp', '0012_employee_bvg'),
]
operations = [
migrations.RemoveField(
model_name='employee',
name='bvg',
),
]

View File

@ -0,0 +1,38 @@
# Generated by Django 3.1.4 on 2021-05-04 08:42
from django.db import migrations, models
import multiselectfield.db.fields
class Migration(migrations.Migration):
dependencies = [
('evapp', '0013_remove_employee_bvg'),
]
operations = [
migrations.RemoveField(
model_name='employee',
name='sim2',
),
migrations.AlterField(
model_name='employee',
name='accounts',
field=multiselectfield.db.fields.MultiSelectField(blank=True, choices=[('OTRSWMDE', 'OTRS Ticketsystem'), ('CIVIC1', 'Civic CRM (allgemein)'), ('CIVIC2', 'Civic CRM (Mailings, impliziert allgemein)'), ('WEB', 'www.wikimedia.de (edit)'), ('BLOG', 'blog.wikimedia.de (edit)'), ('FORUM', 'forum.wikimedia.de')], max_length=37, null=True, verbose_name='Zusätzliche Accounts'),
),
migrations.AlterField(
model_name='employee',
name='lists',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Zusätzliche Mailinglisten'),
),
migrations.AlterField(
model_name='employee',
name='mobile',
field=models.BooleanField(default=False, max_length=6, verbose_name='Handy benötigt?'),
),
migrations.AlterField(
model_name='employee',
name='vendor',
field=models.CharField(choices=[('STANDARD', 'Dell Latitude'), ('LENOVO', 'Lenovo Thinkpad'), ('MAC', 'Mac (nur in Ausnahmefällen)')], default='STANDARD', max_length=8, verbose_name='Hersteller'),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.1.4 on 2021-05-04 09:43
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('evapp', '0014_auto_20210504_0842'),
]
operations = [
migrations.RemoveField(
model_name='employee',
name='email',
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 3.1.4 on 2021-05-04 10:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0015_remove_employee_email'),
]
operations = [
migrations.RemoveField(
model_name='employee',
name='landline',
),
migrations.AddField(
model_name='employee',
name='keyboard',
field=models.CharField(choices=[('DE', 'Deutsch'), ('US', 'USA'), ('OT', 'Anderes (Bitte unten angeben)')], default='DE', max_length=2, verbose_name='Tastaturlayout'),
),
migrations.AlterField(
model_name='employee',
name='department',
field=models.CharField(choices=[('PROG', 'Programme'), ('SOFT', 'Softwareentwicklung'), ('CENT', 'Central'), ('VOR', 'Vorstand')], max_length=5, verbose_name='Bereich'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.4 on 2021-05-04 12:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0016_auto_20210504_1040'),
]
operations = [
migrations.AlterField(
model_name='employee',
name='os',
field=models.CharField(choices=[('UBU', 'Ubuntu (Standard)'), ('WIN', 'Windows (bitte Begründung angeben)')], default='UBU', max_length=3, verbose_name='Betriebssystem'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.4 on 2021-05-04 12:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0017_auto_20210504_1224'),
]
operations = [
migrations.AlterField(
model_name='employee',
name='usermail',
field=models.EmailField(default='bestechefin@wikimedia.de', max_length=50, verbose_name='Deine Mailadresse (Ansprechpartner_in)'),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 3.1.4 on 2021-05-04 13:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0018_auto_20210504_1251'),
]
operations = [
migrations.RemoveField(
model_name='employee',
name='laptop',
),
migrations.AlterField(
model_name='employee',
name='vendor',
field=models.CharField(choices=[('STANDARD', 'Dell Latitude'), ('LENOVO', 'Lenovo Thinkpad'), ('MAC', 'Mac (nur für Grafiker_innen)')], default='STANDARD', max_length=8, verbose_name='Hersteller'),
),
]

View File

@ -1,25 +1,22 @@
from django.db import models from django.db import models
from multiselectfield import MultiSelectField from multiselectfield import MultiSelectField
from django.utils.translation import gettext_lazy as _
# ATTENTION!!! # ATTENTION!!!
# No key should be used twice in any of these dicts because of the # No key should be used twice in any of these dicts because of the
# suboptimal implementation in views.EvaFormView.beautify_data() # suboptimal implementation in views.EvaFormView.beautify_data()
# #
DEPARTMENT_CHOICES = {'COENG': _('Communitys & Engagement'), DEPARTMENT_CHOICES = {'PROG': 'Programme',
'SOFT': _('Softwareentwicklung'), 'SOFT': 'Softwareentwicklung',
'CENT': 'Central', 'CENT': 'Central',
'KOMAD': _('Kommunikation & Advocacy'), 'VOR': 'Vorstand',}
'VOR': _('Vorstand'),}
VENDOR_CHOICES = {'STANDARD': 'Dell Latitude', VENDOR_CHOICES = {'STANDARD': 'Dell Latitude',
'LENOVO': 'Lenovo Thinkpad', 'LENOVO': 'Lenovo Thinkpad',
'MAC': _('Mac (nur für Grafiker_innen)')} 'MAC': 'Mac (nur für Grafiker_innen)'}
OS_CHOICES = {'UBU': 'Ubuntu (Standard)', OS_CHOICES = {'UBU': 'Ubuntu (Standard)',
'WIN': _('Windows (bitte Begründung angeben)'), 'WIN': 'Windows (bitte Begründung angeben)',}
'MOS': _('Mac OS (nur wenn Mac gewählt)')}
LANG_CHOICES = {'GER': 'Deutsch', LANG_CHOICES = {'GER': 'Deutsch',
@ -27,54 +24,54 @@ LANG_CHOICES = {'GER': 'Deutsch',
KEYBOARD_CHOICES = {'DE': 'Deutsch', KEYBOARD_CHOICES = {'DE': 'Deutsch',
'US': 'USA', 'US': 'USA',
'OT': _('Anderes (Bitte unten angeben)')} 'OT': 'Anderes (Bitte unten angeben)'}
ACCOUNT_CHOICES = {'OTRSWMDE': 'OTRS Ticketsystem', ACCOUNT_CHOICES = {'OTRSWMDE': 'OTRS Ticketsystem',
'CIVIC1': _('Civic CRM (allgemein)'), 'CIVIC1': 'Civic CRM (allgemein)',
'CIVIC2': _("Civic CRM (Mailings, impliziert allgemein)"), 'CIVIC2': "Civic CRM (Mailings, impliziert allgemein)",
'WEB': 'www.wikimedia.de (edit)', 'WEB': 'www.wikimedia.de (edit)',
'BLOG': 'blog.wikimedia.de (edit)', 'BLOG': 'blog.wikimedia.de (edit)',
'FORUM': 'forum.wikimedia.de', 'FORUM': 'forum.wikimedia.de',
} }
TRANSPONDER_CHOICES = {'NORM': _('Allgemeiner Transponder'), TRANSPONDER_CHOICES = {'NORM': 'Allgemeiner Transponder',
'SPECIAL': _('Besondere Schließungen (bitte angeben)'), 'SPECIAL': 'Besondere Schließungen (bitte angeben)',
'NOTRANS': _('Kein Transponder'),} 'NOTRANS': 'Kein Transponder',}
class Employee(models.Model): class Employee(models.Model):
# email adress of user. should not be necessary if we use openauth one day # email adress of user. should not be necessary if we use openauth one day
# usermail = models.EmailField(max_length=50, verbose_name="Deine Mailadresse (Ansprechpartner_in)", default='bestechefin@wikimedia.de') usermail = models.EmailField(max_length=50, verbose_name="Deine Mailadresse (Ansprechpartner_in)", default='bestechefin@wikimedia.de')
# personal data # personal data
firstname = models.CharField(max_length=50, verbose_name=_("Vorname")) firstname = models.CharField(max_length=50, verbose_name="Vorname")
lastname = models.CharField(max_length=50, verbose_name=_("Nachname")) lastname = models.CharField(max_length=50, verbose_name="Nachname")
# intern = models.BooleanField(verbose_name='Interne_r Mitarbeiter_in?', default=True) intern = models.BooleanField(verbose_name='Interne_r Mitarbeiter_in?', default=True)
department = models.CharField(max_length=5, choices=DEPARTMENT_CHOICES.items(), verbose_name=_('Bereich')) department = models.CharField(max_length=5, choices=DEPARTMENT_CHOICES.items(), verbose_name='Bereich')
team = models.CharField(max_length=50, null=True, blank=True) # TODO? better with choices? team = models.CharField(max_length=20, null=True, blank=True) # TODO? better with choices?
# general work related stuff # general work related stuff
firstdate_employment = models.DateField(null=True, verbose_name=_("Erster Arbeitstag")) firstdate_employment = models.DateField(null=True, verbose_name="Erster Arbeitstag")
firstdate_presence = models.DateField(null=True, verbose_name=_("Erster Tag der Anwesenheit in der Geschäftsstelle")) firstdate_presence = models.DateField(null=True, verbose_name="Erster Tag der Anwesenheit in der Geschäftsstelle")
jobdescription_german = models.CharField(null=True, max_length=100, verbose_name="Stellenbezeichnung(deutsch)") jobdescription_german = models.CharField(null=True, max_length=100, verbose_name="Stellenbezeichnung(deutsch)")
jobdescription_english = models.CharField(null=True, max_length=100, verbose_name="Job description(english)") jobdescription_english = models.CharField(null=True, max_length=100, verbose_name="Job description(english)")
works_in_gs = models.BooleanField(verbose_name=_('Braucht Arbeitsplatz in der Geschäftsstelle?)'), default=True) remote = models.BooleanField(verbose_name='Braucht keinen Arbeitsplatz weil Home-Office', default=False)
desk = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("Wo soll der Arbeitsplatz sein?")) desk = models.CharField(max_length=100, null=True, blank=True, verbose_name="Wo soll der Arbeitsplatz sein?")
# IT related stuff # IT related stuff
vendor = models.CharField(max_length=8, choices=VENDOR_CHOICES.items(), default='STANDARD', verbose_name=_('Hersteller')) vendor = models.CharField(max_length=8, choices=VENDOR_CHOICES.items(), default='STANDARD', verbose_name='Hersteller')
os = models.CharField(max_length=3, choices=OS_CHOICES.items(), default='UBU', verbose_name=_('Betriebssystem')) os = models.CharField(max_length=3, choices=OS_CHOICES.items(), default='UBU', verbose_name='Betriebssystem')
screen = models.BooleanField(default=False, verbose_name=_('Zusätzlicher Monitor? Einer ist standard.')) screen = models.BooleanField(default=False, verbose_name='Zusätzlicher Monitor? Einer ist standard.')
mobile = models.BooleanField(max_length=6, default=False, verbose_name=_('Diensttelefon (Handy)')) mobile = models.BooleanField(max_length=6, default=False, verbose_name='Handy benötigt?')
# sim = models.BooleanField(default=False, verbose_name="Mobilfunkvertrag") sim = models.BooleanField(default=False, verbose_name="Mobilfunkvertrag")
keyboard = models.CharField(max_length=2, choices=KEYBOARD_CHOICES.items(), default='DE', verbose_name=_("Tastaturlayout")) keyboard = models.CharField(max_length=2, choices=KEYBOARD_CHOICES.items(), default='DE', verbose_name="Tastaturlayout")
comment = models.TextField(max_length=500, null=True, blank=True, verbose_name=_("zusätzliche IT-Anforderungen")) comment = models.TextField(max_length=500, null=True, blank=True, verbose_name="zusätzliche IT-Anforderungen")
language = models.CharField(max_length=3, choices=LANG_CHOICES.items(), default="GER", verbose_name=_("Sprache")) language = models.CharField(max_length=3, choices=LANG_CHOICES.items(), default="GER")
accounts = MultiSelectField(choices=ACCOUNT_CHOICES.items(), max_length=40, null=True, blank=True, verbose_name=_("Zusätzliche Accounts")) accounts = MultiSelectField(choices=ACCOUNT_CHOICES.items(), null=True, blank=True, verbose_name="Zusätzliche Accounts")
lists = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("Zusätzliche Mailinglisten")) lists = models.CharField(max_length=100, null=True, blank=True, verbose_name="Zusätzliche Mailinglisten")
rebu2go = models.BooleanField(verbose_name=_("Rebu2Go-Zugang benötigt?"), default=False) rebu2go = models.BooleanField(verbose_name="Rebu2Go-Zugang benötigt?", default=False)
# office related stuff # office related stuff
transponder = models.CharField(max_length=7, choices=TRANSPONDER_CHOICES.items(), default='NORM') transponder = models.CharField(max_length=7, choices=TRANSPONDER_CHOICES.items(), default='NORM')
special = models.TextField(max_length=500, null=True, blank=True, verbose_name=_("Besondere Schließungen hier eintragen")) special = models.TextField(max_length=500, null=True, blank=True, verbose_name="Besondere Schließungen hier eintragen")
post_office_box = models.BooleanField(default=True, verbose_name=_('Postfach am Empfang benötigt?')) post_office_box = models.BooleanField(default=True, verbose_name='Postfach am Empfang benötigt?')

View File

@ -2,11 +2,10 @@
ONLY_ONBOARDING = True ONLY_ONBOARDING = True
# sender mail adress also used for MAILTEST mode # sender mail adress also used for MAILTEST mode
EVA_MAIL = 'it-support@wikimedia.de' EVA_MAIL = 'benni.baermann@wikimedia.de'
# these Fields should be included in every mail # these Fields should be included in every mail
BASIC_DATA = ['firstname', 'lastname', 'firstdate_employment', 'firstdate_presence', BASIC_DATA = ['usermail', 'firstname', 'lastname', 'firstdate_employment', 'firstdate_presence',]
'jobdescription_german', 'jobdescription_english',]
# for every department: 'MAIL' => mail adress, 'DATA': additional fields to include # for every department: 'MAIL' => mail adress, 'DATA': additional fields to include
MAILS = { MAILS = {
@ -14,31 +13,31 @@ MAILS = {
'MAIL': 'wmde-it@wikimedia.de', 'MAIL': 'wmde-it@wikimedia.de',
'DATA': [ 'DATA': [
'laptop', 'os', 'comment', 'email', 'landline', 'lists', 'mobile', 'laptop', 'os', 'comment', 'email', 'landline', 'lists', 'mobile',
'department', 'accounts', 'language', 'screen', 'works_in_gs', 'desk', 'department', 'accounts', 'language', 'screen', 'remote', 'desk',
'keyboard',
], ],
}, },
'OFFICE': { 'OFFICE': {
'MAIL': 'office@wikimedia.de', 'MAIL': 'office@wikimedia.de',
'DATA': [ 'DATA': [
'transponder', 'special', 'post_office_box', 'mobile', 'transponder', 'special', 'post_office_box', 'sim', 'sim2',
'works_in_gs', 'desk', 'remote', 'desk',
], ],
}, },
'KOMM': { 'KOMM': {
'MAIL': 'presse@wikimedia.de', 'MAIL': 'presse@wikimedia.de',
'DATA': [ 'DATA': [
'department', 'team', 'department', 'team',
'jobdescription_german', 'jobdescription_english',
], ],
}, },
'CENTRAL': { 'CENTRAL': {
'MAIL': 'anna.noelte@wikimedia.de', 'MAIL': 'eileen.miedtank@wikimedia.de',
'DATA': [ 'DATA': [
'department', 'team', 'language', 'mobile', 'rebu2go' 'department', 'team', 'language', 'sim', 'sim2', 'rebu2go'
], ],
}, },
'HR': { 'HR': {
'MAIL': 'personal@wikimedia.de', 'MAIL': 'personal@wikimedia.de, eileen.miedtank@wikimedia.de',
'DATA': [ 'DATA': [
'department', 'team', 'language', 'department', 'team', 'language',
] ]
@ -48,11 +47,6 @@ MAILS = {
'DATA': [ 'DATA': [
'team', 'department', 'language', 'team', 'department', 'language',
] ]
},
'FINANCE': {
'MAIL': 'claudia.langrock@wikimedia.de',
'DATA': [
'rebu2go'
]
} }
} }

View File

@ -1,27 +0,0 @@
{% load i18n %}
{% load static %}
<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 account socialaccount %}
{% block content %}
<center>
<img src="{% static 'evapp/logo.png' %}" />
<h1>
E (V A) - Eintritt, (Veränderung, Austritt)<p>
</h1>
Bitte via Wolke einloggen:
{% include "socialaccount/snippets/provider_list.html" with process="login" %}
</center>
{% endblock %}

View File

@ -1,5 +1,4 @@
{% load i18n %}
{% autoescape off %} {% autoescape off %}
{% for key, value in data.items %}{% if key == 'laptop' %} {{ key }}: {{ value | safe}}{% else %} {% for key, value in data.items %}{% if key == 'laptop' %} {{ key }}: {{ value | safe}}{% else %}
{% trans key %}: {{ value }}{% endif %}{% endfor %} {{ key }}: {{ value }}{% endif %}{% endfor %}
{% endautoescape %} {% endautoescape %}

View File

@ -1,7 +1,3 @@
{% load i18n %}
(english below)
Hallo! Hallo!
Es gibt einen Neuzugang bei Wikimedia! Hier ( https://wiki.wikimedia.de/wiki/Onboarding ) kannst Du nachsehen, Es gibt einen Neuzugang bei Wikimedia! Hier ( https://wiki.wikimedia.de/wiki/Onboarding ) kannst Du nachsehen,
@ -10,22 +6,4 @@ die Du dafür brauchst:
{% include 'evapp/dataloop.txt' %} {% include 'evapp/dataloop.txt' %}
Wenn Du Fragen hast, melde Dich bei {{contact}}.
Grüße, Deine E.V.A. Grüße, Deine E.V.A.
-------------------------
{% language 'en' %}
Hi!
There is a new employee at Wikimedia! Here ( https://wiki.wikimedia.de/wiki/Onboarding ) you can see, which
steps are now necessary for your department.
All Data you need for this:
{% include 'evapp/dataloop.txt' %}
If you have any questions please write to {{contact}}.
Regards, Your E.V.A.
{% endlanguage %}

View File

@ -11,12 +11,7 @@
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}" /> <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 'admin/css/widgets.css' %}" />
{% load socialaccount %}
{% if user.is_authenticated %}
{% block content %} {% block content %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<center> <center>
<style> <style>
ul > li { ul > li {
@ -32,23 +27,20 @@
</style> </style>
<img src="{% static 'evapp/logo.png' %}" /> <img src="{% static 'evapp/logo.png' %}" />
{% if TESTMODE %}
<h1 style="background-color:red;color:white">{% translate "WARNUNG! Test-MODUS aktiviert. Es werden keine Mails verschickt!" %}</h1>
{% endif %}
<h1> <h1>
E (V A) - Eintritt, (Veränderung, Austritt)<p> E (V A) - Eintritt, (Veränderung, Austritt)<p>
</h1>{% translate "Du bist eingeloggt als" %} {{ user.email }} </h1>
<h2> <h2>
<p> {% translate "Schritt" %} {{ wizard.steps.step1 }} {% translate "von" %} {{ wizard.steps.count }}</p> <p>Schritt {{ wizard.steps.step1 }} von {{ wizard.steps.count }}</p>
<p>{% if wizard.steps.step1 == 1 %} <p>{% if wizard.steps.step1 == 1 %}
{% translate "Angaben zur Person" %} {% endif %} Angaben zur Person {% endif %}
{% if choice == 'IN' %} {% if choice == 'IN' %}
{% if wizard.steps.step1 == 2 %} {% if wizard.steps.step1 == 2 %}
{% translate "Angaben zum neuen Arbeitsverhältnis" %} Angaben zum neuen Arbeitsverhältnis
{% elif wizard.steps.step1 == 3 %} {% elif wizard.steps.step1 == 3 %}
{% translate "IT-relevante Angaben" %} IT-relevante Angaben
{% elif wizard.steps.step1 == 4 %} {% elif wizard.steps.step1 == 4 %}
{% translate "Office-relevante Angaben" %} Office-relevante Angaben
{% endif %} {% endif %}
{% else %} {% else %}
{% if wizard.steps.step1 == 2 %} {% if wizard.steps.step1 == 2 %}
@ -56,7 +48,7 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if datatable == True %} {% if datatable == True %}
{% translate "Bestätigungsschritt" %} Bestätigungsschritt
{% endif %} {% endif %}
</p> </p>
</h2> </h2>
@ -87,21 +79,17 @@
{% endif %} {% endif %}
</table> </table>
<p> <p>
<span style="color: red">*</span> {% translate "Pflichtfeld" %} <span style="color: red">*</span> Pflichtfeld
<p> <p>
{% if wizard.steps.prev %} {% if wizard.steps.prev %}
<button formnovalidate="formnovalidate" name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">{% translate "Zurück" %}</button> <button formnovalidate="formnovalidate" name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">Zurück</button>
{% endif %} {% endif %}
{% if datatable == True %} {% if datatable == True %}
<button type="submit" value="{% trans "Weiter" %}">{% translate "Abschicken" %}</button> <button type="submit" value="{% trans "Weiter" %}">Abschicken</button>
{% else %} {% else %}
<button type="submit" value="{% trans "Weiter" %}">{% translate "Weiter" %}</button> <button type="submit" value="{% trans "Weiter" %}">Weiter</button>
{% endif %} {% endif %}
</form> </form>
<p> <p>
<a href="{% url 'account_logout' %}">{% translate "logout" %}</a>
</center> </center>
{% endblock %} {% endblock %}
{% else %}
<a href="{% provider_login_url 'nextcloud' %}">{% translate "Bitte einloggen!" %}</a>
{% endif %}

View File

@ -1,127 +1,3 @@
from django.test import TestCase from django.test import TestCase
from django.test import Client
from django.contrib.auth.models import User
from django.conf import settings
from django.http import HttpResponse
from django.core import mail
from django.utils import translation
from .forms import ITForm, WorkingForm, OfficeForm, DummyForm # Create your tests here.
class LoginTestCase(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user('vladimir', 'vladimir@reiherzehe.com', 'reiherzehe')
self.client.login(username='vladimir', password='reiherzehe')
self.response = self.client.get('/')
def testLogin(self):
self.assertContains(self.response, 'Du bist eingeloggt als vladimir@reiherzehe.com', status_code=200)
response_en = self.client.get('/', HTTP_ACCEPT_LANGUAGE='en-us')
self.assertContains(response_en, 'You are logged in as vladimir@reiherzehe.com', status_code=200)
self.assertContains(response_en, 'Firstname', status_code=200)
response_en = self.client.get('/', HTTP_ACCEPT_LANGUAGE='en')
self.assertContains(response_en, 'You are logged in as vladimir@reiherzehe.com', status_code=200)
self.assertContains(response_en, 'Firstname', status_code=200)
def testDebugWarning(self):
with self.settings(DEBUG=True):
self.response = self.client.get('/') # we need to do it again with DEBUG = True
self.assertContains(self.response, "WARNUNG! Test-MODUS aktiviert. Es werden keine Mails verschickt!", status_code=200)
with self.settings(DEBUG=False) and self.settings(MAILTEST=False):
self.response = self.client.get('/') # we need to do it again with DEBUG = False
self.assertNotContains(self.response, "WARNUNG! Test-MODUS aktiviert. Es werden keine Mails verschickt!", status_code=200)
def _postform(self, data, expected_form):
'''helper function to manage the Wizzard'''
response = self.client.post('/', data, follow=True)
# print(type(response))
self.assertEqual(200, self.response.status_code)
if not type(response) == HttpResponse:
if 'form' in response.context:
self.assertFalse(response.context['form'].errors)
else:
raise "NO FORM FOUND"
self.assertEqual(
type(response.context['wizard']['form']),
expected_form
)
return response
def test_department(self):
self.assertContains(self.response, 'Programme', status_code=200)
self.assertContains(self.response, 'Kommunikation und Events', status_code=200)
def test_wizzard_in(self):
''' this test goes through the whole onboarding process of the EvaFormView from start to end '''
self.assertEqual(200, self.response.status_code)
response = self._postform({
'eva_form_view-current_step': '0',
'0-firstname': 'Ara',
'0-lastname': 'Seva',
'0-department': 'CENT',
'0-team': 'Community Communications',
'0-choice': 'IN',
}, WorkingForm)
response = self._postform({
'eva_form_view-current_step': '1',
'1-firstdate_employment': '2021-01-01',
'1-firstdate_presence': '2021-01-01',
'1-jobdescription_german': 'hau drauf',
'1-jobdescription_english': 'und schluss',
'1-works_in_gs': False
}, ITForm)
response = self._postform({
'eva_form_view-current_step': '2',
'2-vendor': 'STANDARD',
'2-os': 'UBU',
'2-keyboard': 'DE',
'2-language': 'GER'
}, OfficeForm)
response = self._postform({
'eva_form_view-current_step': '3',
'3-transponder': 'NORM'
}, DummyForm)
response = self._postform({
'eva_form_view-current_step': '5',
}, DummyForm)
def test_mail(self):
self.test_wizzard_in()
# print(mail.outbox[0].body)
self.assertGreater(len(mail.outbox), 2)
self.assertIn("Vorname", mail.outbox[0].body)
self.assertIn("Firstname", mail.outbox[0].body)
for i in (0,1,3):
self.assertIn("Handy", mail.outbox[i].body)
self.assertIn("Ara Seva", mail.outbox[0].subject)
class NoLoginTestCase(TestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.get('/')
self.assertEqual(response.status_code, 302)
response2 = self.client.get(response.url)
self.assertContains( response2, 'Bitte via Wolke einloggen:', status_code=200)
class ITFORMTestCase(TestCase):
def test_mac(self):
form = ITForm(data={"vendor": 'MAC', 'os': 'UBU'})
#print (form.errors)
self.assertEqual(form.non_field_errors(), ['Ein MAC sollte Mac OS installiert haben'])
def test_ubu(self):
form = ITForm(data={"vendor": 'STANDARD', 'os': 'UBU'})
#print (form.errors)
self.assertNotEqual(form.non_field_errors(), ['Ein MAC sollte Mac OS installiert haben'])

View File

@ -10,7 +10,6 @@ from formtools.wizard.views import CookieWizardView
from django.shortcuts import render from django.shortcuts import render
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.translation import gettext_lazy as _
from .models import Employee, DEPARTMENT_CHOICES, OS_CHOICES, VENDOR_CHOICES, \ from .models import Employee, DEPARTMENT_CHOICES, OS_CHOICES, VENDOR_CHOICES, \
LANG_CHOICES, ACCOUNT_CHOICES, TRANSPONDER_CHOICES, KEYBOARD_CHOICES LANG_CHOICES, ACCOUNT_CHOICES, TRANSPONDER_CHOICES, KEYBOARD_CHOICES
@ -19,7 +18,7 @@ from .forms import PersonalForm, WorkingForm, ITForm, OfficeForm, DummyForm,\
from .settings import MAILS, EVA_MAIL, BASIC_DATA, ONLY_ONBOARDING from .settings import MAILS, EVA_MAIL, BASIC_DATA, ONLY_ONBOARDING
def success(request): def success(request):
return HttpResponse(f"Vielen Dank! Du hast E.V.A. erfolgreich ausgefüllt. Die Mails an die Abteilungen wurden versendet. Kopien gehen an {request.user.email}.") return HttpResponse("Vielen Dank! Du hast E.V.A. erfolgreich ausgefüllt. Die Mails an die Abteilungen wurden versendet.")
def long_process(wizard): def long_process(wizard):
'''this method is called via urls.py to determine if a form is part of the IN-Process''' '''this method is called via urls.py to determine if a form is part of the IN-Process'''
@ -29,19 +28,19 @@ def long_process(wizard):
return True return True
else: else:
data = wizard.get_cleaned_data_for_step('0') or {} data = wizard.get_cleaned_data_for_step('0') or {}
# print(data) print(data)
if data.get('choice') != 'CHANGE': if data.get('choice') != 'CHANGE':
wizard.set_choice('IN') wizard.set_choice('IN')
# print('PROZESS IN') print('PROZESS IN')
return True return True
else: else:
wizard.set_choice('CHANGE') wizard.set_choice('CHANGE')
# print('PROZESS NOT IN') print('PROZESS NOT IN')
return False return False
def change_process(wizard): def change_process(wizard):
''' this method is called via urls.py to determine if the form is part of the change process''' ''' this method is called via urls.py to determine if the form is part of the change process'''
# print('CHANGE PROZESS') print('CHANGE PROZESS')
return not long_process(wizard) return not long_process(wizard)
@ -60,10 +59,10 @@ class EvaFormView(LoginRequiredMixin, CookieWizardView):
(first, *_) = data['firstname'].split(maxsplit=1) (first, *_) = data['firstname'].split(maxsplit=1)
(last, *_) = data['lastname'].split(maxsplit=1) (last, *_) = data['lastname'].split(maxsplit=1)
name = first + '.' + last name = first + '.' + last
#if not data['intern']: if not data['intern']:
# mail = name + '_ext@wikimedia.de' mail = name + '_ext@wikimedia.de'
#else: else:
mail = name + '@wikimedia.de' mail = name + '@wikimedia.de'
data['email'] = mail data['email'] = mail
def get_all_cleaned_data(self): def get_all_cleaned_data(self):
@ -73,7 +72,7 @@ class EvaFormView(LoginRequiredMixin, CookieWizardView):
data = super().get_all_cleaned_data() data = super().get_all_cleaned_data()
self.generate_email(data) self.generate_email(data)
# print("delete CHOICE FROM DATA") print("delete CHOICE FROM DATA")
if 'choice' in data: if 'choice' in data:
del data['choice'] del data['choice']
return data return data
@ -84,10 +83,8 @@ class EvaFormView(LoginRequiredMixin, CookieWizardView):
#print('GETCONTEXT') #print('GETCONTEXT')
context = super().get_context_data(form=form, **kwargs) context = super().get_context_data(form=form, **kwargs)
testmode = settings.DEBUG or settings.MAILTEST
context.update({'choice': self.choice, context.update({'choice': self.choice,
'choice_string': TYPE_CHOICES[self.choice], 'choice_string': TYPE_CHOICES[self.choice]})
'TESTMODE': testmode})
# deliver context for forms if we are in the last step # deliver context for forms if we are in the last step
if (self.steps.step1 == 5 or (self.choice != 'IN' and self.steps.step1 == 3)): if (self.steps.step1 == 5 or (self.choice != 'IN' and self.steps.step1 == 3)):
@ -96,7 +93,7 @@ class EvaFormView(LoginRequiredMixin, CookieWizardView):
return context return context
def get_form_instance(self,step): def get_form_instance(self,step):
''' this method assures, that we use the same model instance for all steps''' ''' this method makes shure, that we use the same model instance for all steps'''
if self.instance == None: if self.instance == None:
self.instance = Employee() self.instance = Employee()
@ -115,15 +112,12 @@ class EvaFormView(LoginRequiredMixin, CookieWizardView):
# send data to departments # send data to departments
for dep in MAILS: for dep in MAILS:
response = self.send_mail_to_department(dep) self.send_mail_to_department(dep)
if not settings.DEBUG: if not settings.DEBUG:
self.instance.delete() self.instance.delete()
if response: return HttpResponseRedirect('success')
return response
else:
return HttpResponseRedirect('success')
def send_mail_to_department(self, department): def send_mail_to_department(self, department):
@ -131,45 +125,36 @@ class EvaFormView(LoginRequiredMixin, CookieWizardView):
print(f'send mail to department {department}...') print(f'send mail to department {department}...')
contact = self.request.user.email
data = self.get_all_cleaned_data() data = self.get_all_cleaned_data()
# some data should be in every mail # some data should be in every mail
newdata = {k: v for k, v in data.items() if (k in BASIC_DATA)} newdata = {k: v for k, v in data.items() if (k in BASIC_DATA)}
# only the relevant data should be in the context # only the relevant data should be in the context
newdata.update({k: v for k, v in data.items() if (k in MAILS[department]['DATA'])}) newdata.update({k: v for k, v in data.items() if (k in MAILS[department]['DATA'])})
context = {'data': self.beautify_data(newdata), 'contact': contact} context = {'data': self.beautify_data(newdata)}
firstname = data['firstname']
lastname = data['lastname']
try: try:
mail_template = get_template(f'evapp/department_mail.txt') mail_template = get_template(f'evapp/department_mail.txt')
if settings.MAILTEST: if settings.MAILTEST:
send_mail( send_mail(
f'EVA: Neuzugang {firstname} {lastname} (MAILTEST)', 'EVA: Neuzugang',
mail_template.render(context), mail_template.render(context),
EVA_MAIL, EVA_MAIL,
[EVA_MAIL, contact], [EVA_MAIL, self.instance.usermail],
fail_silently=False) fail_silently=False)
else: else:
send_mail( send_mail(
f'EVA: Neuzugang {firstname} {lastname}', 'EVA: Neuzugang',
mail_template.render(context), mail_template.render(context),
EVA_MAIL, EVA_MAIL,
[MAILS[department]['MAIL'], contact], [MAILS[department]['MAIL'], self.instance.usermail],
fail_silently=False) fail_silently=False)
except BadHeaderError as error: except BadHeaderError:
print(error)
self.instance.delete() self.instance.delete()
return HttpResponse(f'{error}<p>Invalid header found. Data not saved!') return HttpResponse('Invalid header found. Data not saved!')
except SMTPException as error: except SMTPException:
print(error)
self.instance.delete() self.instance.delete()
return HttpResponse(f'{error}<p>Error in sending mails (propably wrong adress?). Data not saved!') return HttpResponse('Error in sending mails (propably wrong adress?). Data not saved!')
except Exception as error:
print(error)
# self.instance.delete()
return HttpResponse(f'{error}<p>Error in sending mails. Data not saved! Please contact ' + EVA_MAIL)
return False
def beautify_data(self, data): def beautify_data(self, data):
''' # use long form for contextdata instead of short form if available ''' # use long form for contextdata instead of short form if available

View File

@ -1,208 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-02-07 14:44+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: evapp/models.py:10
msgid "Programme"
msgstr "Programs"
#: evapp/models.py:11
msgid "Softwareentwicklung"
msgstr "Software development"
#: evapp/models.py:13
msgid "Kommunikation und Events"
msgstr "Communication and events"
#: evapp/models.py:14
msgid "Vorstand"
msgstr "Board"
#: evapp/models.py:18
msgid "Mac (nur für Grafiker_innen)"
msgstr "Mac (for graphic designer only)"
#: evapp/models.py:21
msgid "Windows (bitte Begründung angeben)"
msgstr "Windows (please give justification)"
#: evapp/models.py:22
msgid "Mac OS (nur wenn Mac gewählt)"
msgstr "Mac OS (only if you choose Mac)"
#: evapp/models.py:30
msgid "Anderes (Bitte unten angeben)"
msgstr "Others (please note below)"
#: evapp/models.py:33
msgid "Civic CRM (allgemein)"
msgstr "Civic CRM (regular)"
#: evapp/models.py:34
msgid "Civic CRM (Mailings, impliziert allgemein)"
msgstr "Civic CRM (mailings, regular implied)"
#: evapp/models.py:40
msgid "Allgemeiner Transponder"
msgstr "Regular Transponder"
#: evapp/models.py:41
msgid "Besondere Schließungen (bitte angeben)"
msgstr "Special Doors (please note below)"
#: evapp/models.py:42
msgid "Kein Transponder"
msgstr "No transponder"
#: evapp/models.py:50 evapp/templates/evapp/department_mail.txt:26
msgid "Vorname"
msgstr "Firstname"
#: evapp/models.py:51
msgid "Nachname"
msgstr "Lastname"
#: evapp/models.py:53
msgid "Bereich"
msgstr "Department"
#: evapp/models.py:57
msgid "Erster Arbeitstag"
msgstr "First workday"
#: evapp/models.py:58
msgid "Erster Tag der Anwesenheit in der Geschäftsstelle"
msgstr "First day in the office"
#: evapp/models.py:61
msgid "Braucht Arbeitsplatz in der Geschäftsstelle?)"
msgstr "Needs a working place in the office"
#: evapp/models.py:62
msgid "Wo soll der Arbeitsplatz sein?"
msgstr "Where should the desk be?"
#: evapp/models.py:65
msgid "Hersteller"
msgstr "Manufacterer"
#: evapp/models.py:66
msgid "Betriebssystem"
msgstr "Operating system"
#: evapp/models.py:67
msgid "Zusätzlicher Monitor? Einer ist standard."
msgstr "Additional monitor. One is standard."
#: evapp/models.py:68
msgid "Diensttelefon (Handy)"
msgstr "work phone (mobile)"
#: evapp/models.py:70
msgid "Tastaturlayout"
msgstr "Keyboard layout"
#: evapp/models.py:71
msgid "zusätzliche IT-Anforderungen"
msgstr "Additional IT requirements"
#: evapp/models.py:72
msgid "Sprache"
msgstr "Language"
#: evapp/models.py:73
msgid "Zusätzliche Accounts"
msgstr "Additional accounts"
#: evapp/models.py:74
msgid "Zusätzliche Mailinglisten"
msgstr "additional mailing lists"
#: evapp/models.py:75
msgid "Rebu2Go-Zugang benötigt?"
msgstr "Needs Rebu2Go account"
#: evapp/models.py:79
msgid "Besondere Schließungen hier eintragen"
msgstr "Special access for doors"
#: evapp/models.py:80
msgid "Postfach am Empfang benötigt?"
msgstr "Needs mailbox at reception"
#: evapp/templates/evapp/employee_form.html:36
msgid "WARNUNG! Test-MODUS aktiviert. Es werden keine Mails verschickt!"
msgstr "ATTENTION! Test mode activated. No mails will be send."
#: evapp/templates/evapp/employee_form.html:40
msgid "Du bist eingeloggt als"
msgstr "You are logged in as"
#: evapp/templates/evapp/employee_form.html:42
msgid "Schritt"
msgstr "Step"
#: evapp/templates/evapp/employee_form.html:42
msgid "von"
msgstr "from"
#: evapp/templates/evapp/employee_form.html:44
msgid "Angaben zur Person"
msgstr "Personal Data"
#: evapp/templates/evapp/employee_form.html:47
msgid "Angaben zum neuen Arbeitsverhältnis"
msgstr "Details of the employment"
#: evapp/templates/evapp/employee_form.html:49
msgid "IT-relevante Angaben"
msgstr "IT-relevant details"
#: evapp/templates/evapp/employee_form.html:51
msgid "Office-relevante Angaben"
msgstr "Office relevant details"
#: evapp/templates/evapp/employee_form.html:59
msgid "Bestätigungsschritt"
msgstr "Confirmation step"
#: evapp/templates/evapp/employee_form.html:91
msgid "Pflichtfeld"
msgstr "Required field"
#: evapp/templates/evapp/employee_form.html:94
msgid "Zurück"
msgstr "Back"
#: evapp/templates/evapp/employee_form.html:97
#: evapp/templates/evapp/employee_form.html:99
msgid "Weiter"
msgstr "Next"
#: evapp/templates/evapp/employee_form.html:97
msgid "Abschicken"
msgstr "Send"
#: evapp/templates/evapp/employee_form.html:103
msgid "logout"
msgstr "logout"
#: evapp/templates/evapp/employee_form.html:107
msgid "Bitte einloggen!"
msgstr "Please log in!"