Compare commits

...

8 Commits

72 changed files with 2262 additions and 42 deletions

View File

@ -39,6 +39,9 @@ ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
'home',
'offboarding',
'veränderung',
'evapp.apps.EvappConfig',
'django.contrib.admin',
'django.contrib.auth',
@ -65,7 +68,8 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
'allauth.account.middleware.AccountMiddleware'
]
ROOT_URLCONF = 'eva.urls'
@ -159,3 +163,4 @@ ACCOUNT_EMAIL_VERIFICATION = 'none'
# ACCOUNT_EMAIL_REQUIRED = True
LOGIN_REDIRECT_URL = 'home'
ACCOUNT_LOGOUT_ON_GET = True
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

View File

@ -17,7 +17,10 @@ from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('', include("evapp.urls")),
path('onboarding/', include('evapp.urls')),
path('', include("home.urls")),
path('offboarding/', include("offboarding.urls")),
path('veränderung/', include("veränderung.urls")),
path('admin/', admin.site.urls),
path('accounts/', include('allauth.urls')),

View File

@ -1,6 +1,10 @@
# import Django´s admin interface module
from django.contrib import admin
# import the Employee model from the current app
from .models import Employee
# Register the Employee model with the Django admin site
# This makes the Employee model manageable through the admin interface
admin.site.register([
Employee,
])

View File

@ -1,5 +1,7 @@
# import django´s base AppConfig class
from django.apps import AppConfig
# define the configuration class for the 'evapp' application
class EvappConfig(AppConfig):
# give the app a name
name = 'evapp'

View File

@ -22,15 +22,13 @@ class EvaForm(ModelForm):
TYPE_CHOICES = {'IN': 'Eintritt', 'CHANGE': 'Veränderung', 'OUT': 'Austritt'}
# Form to capture basic personal and department-related information
class PersonalForm(EvaForm):
# TODO: comment this back in to use implementation of change or exit process
# choice = ChoiceField(choices=TYPE_CHOICES.items(), widget=RadioSelect,
# label='Welcher Prozess soll angestoßen werden?')
class Meta:
model = Employee
fields = ['firstname', 'lastname', 'department', 'team', ]
fields = ['firstname', 'lastname', 'department', 'team', 'add_to_wikimediade',]
# Form to capture working conditions and job description
class WorkingForm(EvaForm):
def clean(self):
@ -48,17 +46,11 @@ class WorkingForm(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:
model = Employee
fields = [
'vendor', 'os', 'keyboard', 'screen', 'mobile', 'comment',
'language', 'accounts', 'lists', 'rebu2go' ]
'framework', 'os', 'keyboard', 'mobile', 'landline',
'comment', 'language', 'accounts', 'lists', 'rebu2go' ]
class OfficeForm(EvaForm):
class Meta:

View File

@ -20,6 +20,7 @@ class Migration(migrations.Migration):
('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')),
('team', models.CharField(blank=True, max_length=20, null=True)),
('add_to_wikimediade', models.BooleanField(default=False, verbose_name='Soll auf wikimedia.de irgendwo stehen?')),
('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')),
('jobdescription_german', models.CharField(max_length=100, null=True, verbose_name='Stellenbezeichnung(deutsch)')),
@ -30,9 +31,10 @@ class Migration(migrations.Migration):
('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.')),
('mobile', models.BooleanField(default=False, max_length=6, verbose_name='Diensttelefon (Handy)')),
('landline', models.BooleanField(default=False, verbose_name='Festnetznummer (Sipgate)')),
('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')),
('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, verbose_name='Sprache für Onboarding')),
('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')),
('lists', models.CharField(blank=True, max_length=100, null=True, verbose_name='Zusätzliche Mailinglisten')),
('rebu2go', models.BooleanField(default=False, verbose_name='Rebu2Go-Zugang benötigt?')),

View File

@ -0,0 +1,32 @@
# Generated by Django 4.2.16 on 2024-11-26 09:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0005_alter_employee_accounts'),
]
operations = [
migrations.RemoveField(
model_name='employee',
name='vendor',
),
migrations.AddField(
model_name='employee',
name='framework',
field=models.CharField(blank=True, max_length=300, null=True, verbose_name='Möchten Sie vom Standard des Frameworks abweichen, und wenn ja, warum?'),
),
migrations.AlterField(
model_name='employee',
name='add_to_wikimediade',
field=models.CharField(choices=[('NEIN', 'Nein'), ('JA', 'Ja')], default=True, max_length=5, verbose_name='Soll die Person bei Ansprechpartner*innen auf der WMDE-Webseite mit aufgenommen werden?'),
),
migrations.AlterField(
model_name='employee',
name='os',
field=models.CharField(choices=[('FED', 'Fedora (Standard)'), ('WIN', 'Windows (bitte Begründung angeben)'), ('MOS', 'Mac OS (nur wenn Mac gewählt)'), ('UBU', 'Ubuntu')], default='FED', max_length=3, verbose_name='Betriebssystem'),
),
]

View File

@ -0,0 +1,32 @@
# Generated by Django 4.2.17 on 2024-12-17 14:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0006_remove_employee_vendor_employee_framework_and_more'),
]
operations = [
migrations.RemoveField(
model_name='employee',
name='screen',
),
migrations.AlterField(
model_name='employee',
name='add_to_wikimediade',
field=models.CharField(choices=[('NEIN', 'Nein'), ('JA', 'Ja')], max_length=5, verbose_name='Soll die Person bei Ansprechpartner*innen auf der WMDE-Webseite mit aufgenommen werden?'),
),
migrations.AlterField(
model_name='employee',
name='framework',
field=models.CharField(blank=True, max_length=300, null=True, verbose_name='Möchten Sie vom Standard des Frameworks (Laptop) abweichen, und wenn ja, warum?'),
),
migrations.AlterField(
model_name='employee',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]

View File

@ -13,14 +13,14 @@ DEPARTMENT_CHOICES = {'COENG': _('Communitys & Engagement'),
'KOMAD': _('Kommunikation & Advocacy'),
'VOR': _('Vorstand'),}
VENDOR_CHOICES = {'STANDARD': 'Dell Latitude',
'LENOVO': 'Lenovo Thinkpad',
'MAC': _('Mac (nur für Grafiker_innen)')}
#VENDOR_CHOICES = {'STANDARD': 'Dell Latitude',
# 'LENOVO': 'Lenovo Thinkpad',
# 'MAC': _('Mac (nur für Grafiker_innen)')}
OS_CHOICES = {'UBU': 'Ubuntu (Standard)',
OS_CHOICES = {'FED': 'Fedora (Standard)',
'WIN': _('Windows (bitte Begründung angeben)'),
'MOS': _('Mac OS (nur wenn Mac gewählt)')}
'MOS': _('Mac OS (nur wenn Mac gewählt)'),
'UBU': _('Ubuntu')}
LANG_CHOICES = {'GER': 'Deutsch',
'ENG': 'English',}
@ -41,6 +41,9 @@ TRANSPONDER_CHOICES = {'NORM': _('Allgemeiner Transponder'),
'SPECIAL': _('Besondere Schließungen (bitte angeben)'),
'NOTRANS': _('Kein Transponder'),}
JANEIN_CHOICES = {'NEIN': ('Nein'),
'JA': _('Ja'),}
class Employee(models.Model):
# email adress of user. should not be necessary if we use openauth one day
@ -52,6 +55,7 @@ class Employee(models.Model):
# intern = models.BooleanField(verbose_name='Interne_r Mitarbeiter_in?', default=True)
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?
add_to_wikimediade = models.CharField(max_length=5, choices=JANEIN_CHOICES.items(), verbose_name=_("Soll die Person bei Ansprechpartner*innen auf der WMDE-Webseite mit aufgenommen werden?"))
# general work related stuff
firstdate_employment = models.DateField(null=True, verbose_name=_("Erster Arbeitstag"))
@ -62,14 +66,16 @@ class Employee(models.Model):
desk = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("Wo soll der Arbeitsplatz sein?"))
# IT related stuff
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'))
screen = models.BooleanField(default=False, verbose_name=_('Zusätzlicher Monitor? Einer ist standard.'))
#vendor = models.CharField(max_length=8, choices=VENDOR_CHOICES.items(), default='STANDARD', verbose_name=_('Hersteller'))
framework = models.CharField(max_length=300, null=True, blank=True, verbose_name=_("Möchten Sie vom Standard des Frameworks (Laptop) abweichen, und wenn ja, warum?"))
os = models.CharField(max_length=3, choices=OS_CHOICES.items(), default='FED', verbose_name=_('Betriebssystem'))
#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)'))
landline = models.BooleanField(default = False, verbose_name=_('Festnetznummer (Sipgate)'))
# sim = models.BooleanField(default=False, verbose_name="Mobilfunkvertrag")
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"))
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", verbose_name=_("Sprache für Onboarding"))
accounts = MultiSelectField(choices=ACCOUNT_CHOICES.items(), max_length=40, null=True, blank=True, verbose_name=_("Zusätzliche Accounts"))
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)

View File

@ -9,6 +9,7 @@ BASIC_DATA = ['firstname', 'lastname', 'firstdate_employment', 'firstdate_presen
'jobdescription_german', 'jobdescription_english',]
# for every department: 'MAIL' => mail adress, 'DATA': additional fields to include
# also one copy with all fields to the person filling the form.
MAILS = {
'IT': {
'MAIL': 'wmde-it@wikimedia.de',
@ -28,7 +29,7 @@ MAILS = {
'KOMM': {
'MAIL': 'presse@wikimedia.de',
'DATA': [
'department', 'team',
'department', 'team', 'add_to_wikimediade'
],
},
'CENTRAL': {
@ -43,16 +44,18 @@ MAILS = {
'department', 'team', 'language',
]
},
'DIRECTORAT': {
'MAIL': 'ricarda.busse@wikimedia.de',
'DATA': [
'team', 'department', 'language',
]
},
'FINANCE': {
'MAIL': 'claudia.langrock@wikimedia.de',
'DATA': [
'rebu2go'
]
},
'SUBMITTER': {
'MAIL': 'submitter@wikimedia.de',
'DATA': [
'department', 'team', 'add_to_wikimediade', 'remote', 'desk', 'vendor',
'os', 'screen', 'mobile', 'landline', 'keyboard', 'comment', 'language',
'accounts', 'lists', 'rebu2go', 'transponder', 'special', 'post_office_box'
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -1,12 +1,17 @@
{% load i18n %}
{% load static %}
<!-- Load core JavaScript files needed for admin functionality -->
<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>
<!-- Render any JavaScript or CSS required by form widgets -->
{{ form.media }}
<!-- Load admin base and widgets stylesheets -->
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}" />
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/widgets.css' %}" />
@ -14,12 +19,17 @@
{% block content %}
<center>
<!-- Display logo image -->
<img src="{% static 'evapp/logo.png' %}" />
<!-- Main heading for the page -->
<h1>
E (V A) - Eintritt, (Veränderung, Austritt)<p>
</h1>
<!-- Instruction text for users to log in via cloud (social login) -->
Bitte via Wolke einloggen:
<!-- Include the social login providers list -->
{% include "socialaccount/snippets/provider_list.html" with process="login" %}
</center>

View File

@ -38,6 +38,7 @@
<h1>
E (V A) - Eintritt, (Veränderung, Austritt)<p>
</h1>{% translate "Du bist eingeloggt als" %} {{ user.email }}
<h2>
<p> {% translate "Schritt" %} {{ wizard.steps.step1 }} {% translate "von" %} {{ wizard.steps.count }}</p>
<p>{% if wizard.steps.step1 == 1 %}
@ -60,13 +61,15 @@
{% endif %}
</p>
</h2>
{% if datatable == True %}
<table>
<table id="toggle-heading">
{% for key, value in data.items %}
<tr><th>{{ key }}</th><th>{{ value }}</th></tr>
{% endfor %}
</table>
{% endif %}
<form action="" method="post">
{% csrf_token %}
<table>

View File

@ -12,8 +12,8 @@ from django.conf import settings
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, \
LANG_CHOICES, ACCOUNT_CHOICES, TRANSPONDER_CHOICES, KEYBOARD_CHOICES
from .models import Employee, DEPARTMENT_CHOICES, OS_CHOICES, \
LANG_CHOICES, ACCOUNT_CHOICES, TRANSPONDER_CHOICES, KEYBOARD_CHOICES, JANEIN_CHOICES
from .forms import PersonalForm, WorkingForm, ITForm, OfficeForm, DummyForm,\
ChangeForm, TYPE_CHOICES
from .settings import MAILS, EVA_MAIL, BASIC_DATA, ONLY_ONBOARDING
@ -141,21 +141,29 @@ class EvaFormView(LoginRequiredMixin, CookieWizardView):
context = {'data': self.beautify_data(newdata), 'contact': contact}
firstname = data['firstname']
lastname = data['lastname']
firstday = data['firstdate_employment']
try:
mail_template = get_template(f'evapp/department_mail.txt')
if settings.MAILTEST:
send_mail(
f'EVA: Neuzugang {firstname} {lastname} (MAILTEST)',
f'EVA: Neuzugang {firstname} {lastname} {firstday} (MAILTEST)',
mail_template.render(context),
EVA_MAIL,
[EVA_MAIL, contact],
[EVA_MAIL],
fail_silently=False)
elif department != "SUBMITTER":
send_mail(
f'EVA: Neuzugang {firstname} {lastname} {firstday}',
mail_template.render(context),
EVA_MAIL,
[MAILS[department]['MAIL']],
fail_silently=False)
else:
send_mail(
f'EVA: Neuzugang {firstname} {lastname}',
f'EVA: Neuzugang {firstname} {lastname} {firstday}',
mail_template.render(context),
EVA_MAIL,
[MAILS[department]['MAIL'], contact],
[contact],
fail_silently=False)
except BadHeaderError as error:
print(error)
@ -181,7 +189,7 @@ class EvaFormView(LoginRequiredMixin, CookieWizardView):
# update values in data dictionary with keys from *_CHOICES if present there
choices = {**DEPARTMENT_CHOICES, **TRANSPONDER_CHOICES,
**OS_CHOICES, **LANG_CHOICES, **VENDOR_CHOICES, **KEYBOARD_CHOICES}
**OS_CHOICES, **LANG_CHOICES, **KEYBOARD_CHOICES}
data.update({k:choices[v] for k,v in data.items() \
if isinstance(v,collections.abc.Hashable) \
and v in choices})

0
home/__init__.py Normal file
View File

3
home/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

9
home/apps.py Normal file
View File

@ -0,0 +1,9 @@
# import django´s base Appconfig class
from django.apps import AppConfig
# configuration class for the 'home' application
class HomeConfig(AppConfig):
# set the default type of primary key field for models in this app
default_auto_field = 'django.db.models.BigAutoField'
# give the application a name
name = 'home'

View File

3
home/models.py Normal file
View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@ -0,0 +1,97 @@
<!DOCTYPE html>
{% load i18n %}
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8"> <!-- Set character encoding -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Button Navigation</title>
<style>
/* Flex container to align items in a row */
.container-wrapper {
display: flex;
justify-content: center; /* Centers the images horizontally */
align-items: flex-start; /* Aligns items to the top */
gap: 20px; /* Adds spacing between images */
margin-top: 50px; /* Adjust margin */
}
/* Container needed to position the button. Adjust the width as needed */
.container {
position: relative;
width: 30%;
}
/* Make the image responsive */
.container img {
width: 50%;
height: auto;
}
/* Style the button and place it in the middle of the container/image */
.container .btn {
position: absolute;
top: 120%;
left: 50%;
transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
background-color: #555;
color: white;
font-size: 16px;
padding: 12px 24px;
border: none;
cursor: pointer;
border-radius: 5px;
}
.container .btn:hover {
background-color: black;
}
body {
font-family: Arial, sans-serif;
text-align: center;
margin-top: 175px;
}
</style>
</head>
<body>
<h1>Choose Your Destination</h1>
<div class="container-wrapper">
<!-- the first container with image and button for onboarding -->
<div class="container">
<img src="{% static 'evapp/onboarding1.png' %}"/>
<button class="btn" onclick="window.location.href='/onboarding';">Eintritt</button>
</div>
<!-- the second container with image and button for onboarding -->
<div class="container">
<img src="{% static 'evapp/veränderung.png' %}"/>
<button class="btn" onclick="window.location.href='/veränderung';">Veränderung</button>
</div>
<!-- the third container with image and button for onboarding -->
<div class="container">
<img src="{% static 'evapp/offboarding.png' %}"/>
<button class="btn" onclick="window.location.href='/offboarding';">Austritt</button>
</div>
</div>
<!--
<div>
<img src="{% static 'evapp/onboarding1.png' %}" width="100" height="100" align="right" />
<img src="{% static 'evapp/offboarding.png' %}" width="100" height="100" align="right" />
<img src="{% static 'evapp/veränderung.png' %}" width="100" height="100" align="right" />
</div>
<button onclick="window.location.href='/onboarding';">Onboarding</button>
<button onclick="window.location.href='/offboarding';">Offboarding</button>
<button onclick="window.location.href='/veränderung';">Veränderung</button>-->
</body>
</html>

3
home/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

9
home/urls.py Normal file
View File

@ -0,0 +1,9 @@
# home/urls.py
from django.urls import path
from . import views # import views from the current app
# Define the URL patterns for the 'home' app
urlpatterns = [
path('', views.index, name='home-index'), # Map the URL '' to the index view with the name 'home-index'
]

15
home/views.py Normal file
View File

@ -0,0 +1,15 @@
from django.shortcuts import render
# Create your views here.
# Import the render function from Django to render templates
from django.shortcuts import render
def index(request):
# Context dictionary to pass dynamic data to the template
context = {
'title': 'Welcome to Pycouse!',
'author': 'Brian',
}
# Render the 'index.html' template located in the 'home' directory
# Pass the context dictionary to the template for dynamic content
return render(request, 'home/index.html', context)

0
offboarding/__init__.py Normal file
View File

6
offboarding/admin.py Normal file
View File

@ -0,0 +1,6 @@
from django.contrib import admin
from .models import Employee
admin.site.register([
Employee,
])

5
offboarding/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class EvappConfig(AppConfig):
name = 'offboarding'

67
offboarding/forms.py Normal file
View File

@ -0,0 +1,67 @@
from django.db import models
from django.forms import ModelForm, DateInput, Form, ChoiceField, RadioSelect
from django.core.exceptions import ValidationError
from .models import Employee
# class EmployeeForm(ModelForm):
# class Meta:
# model = Employee
# fields = '__all__'
# widgets = {'firstdate_employment': DateInput(attrs={'type': 'date'}),
# 'firstdate_presence': DateInput(attrs={'type': 'date'}),}
class DummyForm(ModelForm):
class Meta:
model = Employee
fields = []
class EvaForm(ModelForm):
'''this base class provides the required css class for all forms'''
required_css_class = 'required'
TYPE_CHOICES = {'IN': 'Eintritt', 'CHANGE': 'Veränderung', 'OUT': 'Austritt'}
class PersonalForm(EvaForm):
# TODO: comment this back in to use implementation of change or exit process
# choice = ChoiceField(choices=TYPE_CHOICES.items(), widget=RadioSelect,
# label='Welcher Prozess soll angestoßen werden?')
class Meta:
model = Employee
fields = ['firstname', 'lastname', 'department', 'team', 'add_to_wikimediade',]
class WorkingForm(EvaForm):
def clean(self):
data = self.cleaned_data
if data['works_in_gs'] and data['desk'] is None:
raise ValidationError('Wer nicht remote arbeitet braucht einen Schreibtisch!')
return data
class Meta:
model = Employee
fields = ['firstdate_employment', 'firstdate_presence', 'jobdescription_german',
'jobdescription_english', 'works_in_gs', 'desk',]
widgets = {'firstdate_employment': DateInput(attrs={'type': 'date'}),
'firstdate_presence': DateInput(attrs={'type': 'date'}),}
class ITForm(EvaForm):
class Meta:
model = Employee
fields = [
'framework', 'os', 'keyboard', 'mobile', 'landline',
'comment', 'language', 'accounts', 'lists', 'rebu2go' ]
class OfficeForm(EvaForm):
class Meta:
model = Employee
fields = ['transponder', 'special', 'post_office_box',]
class ChangeForm(EvaForm):
class Meta:
model = Employee
fields = ['firstdate_employment', 'jobdescription_german', 'jobdescription_english',
'desk', 'comment', 'accounts', 'lists', 'transponder']
widgets = {'firstdate_employment': DateInput(attrs={'type': 'date'}),}

View File

@ -0,0 +1,46 @@
# Generated by Django 3.1.4 on 2021-09-13 12:41
from django.db import migrations, models
import multiselectfield.db.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Employee',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('firstname', models.CharField(max_length=50, verbose_name='Vorname')),
('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')),
('team', models.CharField(blank=True, max_length=20, null=True)),
('add_to_wikimediade', models.BooleanField(default=False, verbose_name='Soll auf wikimedia.de irgendwo stehen?')),
('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')),
('jobdescription_german', models.CharField(max_length=100, null=True, verbose_name='Stellenbezeichnung(deutsch)')),
('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(blank=True, max_length=100, null=True, verbose_name='Wo soll der Arbeitsplatz sein?')),
('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)'), ('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.')),
('mobile', models.BooleanField(default=False, max_length=6, verbose_name='Diensttelefon (Handy)')),
('landline', models.BooleanField(default=False, verbose_name='Festnetznummer (Sipgate)')),
('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')),
('language', models.CharField(choices=[('GER', 'Deutsch'), ('ENG', 'English')], default='GER', max_length=3, verbose_name='Sprache für Onboarding')),
('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')),
('lists', models.CharField(blank=True, max_length=100, null=True, verbose_name='Zusätzliche Mailinglisten')),
('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,18 @@
# 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,28 @@
# 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

@ -0,0 +1,24 @@
# 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,19 @@
# 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,32 @@
# Generated by Django 4.2.16 on 2024-11-26 09:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0005_alter_employee_accounts'),
]
operations = [
migrations.RemoveField(
model_name='employee',
name='vendor',
),
migrations.AddField(
model_name='employee',
name='framework',
field=models.CharField(blank=True, max_length=300, null=True, verbose_name='Möchten Sie vom Standard des Frameworks abweichen, und wenn ja, warum?'),
),
migrations.AlterField(
model_name='employee',
name='add_to_wikimediade',
field=models.CharField(choices=[('NEIN', 'Nein'), ('JA', 'Ja')], default=True, max_length=5, verbose_name='Soll die Person bei Ansprechpartner*innen auf der WMDE-Webseite mit aufgenommen werden?'),
),
migrations.AlterField(
model_name='employee',
name='os',
field=models.CharField(choices=[('FED', 'Fedora (Standard)'), ('WIN', 'Windows (bitte Begründung angeben)'), ('MOS', 'Mac OS (nur wenn Mac gewählt)'), ('UBU', 'Ubuntu')], default='FED', max_length=3, verbose_name='Betriebssystem'),
),
]

View File

@ -0,0 +1,32 @@
# Generated by Django 4.2.17 on 2024-12-17 14:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0006_remove_employee_vendor_employee_framework_and_more'),
]
operations = [
migrations.RemoveField(
model_name='employee',
name='screen',
),
migrations.AlterField(
model_name='employee',
name='add_to_wikimediade',
field=models.CharField(choices=[('NEIN', 'Nein'), ('JA', 'Ja')], max_length=5, verbose_name='Soll die Person bei Ansprechpartner*innen auf der WMDE-Webseite mit aufgenommen werden?'),
),
migrations.AlterField(
model_name='employee',
name='framework',
field=models.CharField(blank=True, max_length=300, null=True, verbose_name='Möchten Sie vom Standard des Frameworks (Laptop) abweichen, und wenn ja, warum?'),
),
migrations.AlterField(
model_name='employee',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]

View File

86
offboarding/models.py Normal file
View File

@ -0,0 +1,86 @@
from django.db import models
from multiselectfield import MultiSelectField
from django.utils.translation import gettext_lazy as _
# ATTENTION!!!
# No key should be used twice in any of these dicts because of the
# suboptimal implementation in views.EvaFormView.beautify_data()
#
DEPARTMENT_CHOICES = {'COENG': _('Communitys & Engagement'),
'SOFT': _('Softwareentwicklung'),
'CENT': 'Central',
'KOMAD': _('Kommunikation & Advocacy'),
'VOR': _('Vorstand'),}
#VENDOR_CHOICES = {'STANDARD': 'Dell Latitude',
# 'LENOVO': 'Lenovo Thinkpad',
# 'MAC': _('Mac (nur für Grafiker_innen)')}
OS_CHOICES = {'FED': 'Fedora (Standard)',
'WIN': _('Windows (bitte Begründung angeben)'),
'MOS': _('Mac OS (nur wenn Mac gewählt)'),
'UBU': _('Ubuntu')}
LANG_CHOICES = {'GER': 'Deutsch',
'ENG': 'English',}
KEYBOARD_CHOICES = {'DE': 'Deutsch',
'US': 'USA',
'OT': _('Anderes (Bitte unten angeben)')}
ACCOUNT_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',
}
TRANSPONDER_CHOICES = {'NORM': _('Allgemeiner Transponder'),
'SPECIAL': _('Besondere Schließungen (bitte angeben)'),
'NOTRANS': _('Kein Transponder'),}
JANEIN_CHOICES = {'NEIN': ('Nein'),
'JA': _('Ja'),}
class Employee(models.Model):
# 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')
# personal data
firstname = models.CharField(max_length=50, verbose_name=_("Vorname"))
lastname = models.CharField(max_length=50, verbose_name=_("Nachname"))
# intern = models.BooleanField(verbose_name='Interne_r Mitarbeiter_in?', default=True)
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?
add_to_wikimediade = models.CharField(max_length=5, choices=JANEIN_CHOICES.items(), verbose_name=_("Soll die Person bei Ansprechpartner*innen auf der WMDE-Webseite mit aufgenommen werden?"))
# general work related stuff
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"))
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)")
works_in_gs = models.BooleanField(verbose_name=_('Braucht Arbeitsplatz in der Geschäftsstelle?)'), default=True)
desk = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("Wo soll der Arbeitsplatz sein?"))
# IT related stuff
#vendor = models.CharField(max_length=8, choices=VENDOR_CHOICES.items(), default='STANDARD', verbose_name=_('Hersteller'))
framework = models.CharField(max_length=300, null=True, blank=True, verbose_name=_("Möchten Sie vom Standard des Frameworks (Laptop) abweichen, und wenn ja, warum?"))
os = models.CharField(max_length=3, choices=OS_CHOICES.items(), default='FED', verbose_name=_('Betriebssystem'))
#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)'))
landline = models.BooleanField(default = False, verbose_name=_('Festnetznummer (Sipgate)'))
# sim = models.BooleanField(default=False, verbose_name="Mobilfunkvertrag")
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"))
language = models.CharField(max_length=3, choices=LANG_CHOICES.items(), default="GER", verbose_name=_("Sprache für Onboarding"))
accounts = MultiSelectField(choices=ACCOUNT_CHOICES.items(), max_length=40, null=True, blank=True, verbose_name=_("Zusätzliche Accounts"))
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)
# office related stuff
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"))
post_office_box = models.BooleanField(default=True, verbose_name=_('Postfach am Empfang benötigt?'))

61
offboarding/settings.py Normal file
View File

@ -0,0 +1,61 @@
# temporary setting while change and exit is not yet fully implemented
ONLY_ONBOARDING = True
# sender mail adress also used for MAILTEST mode
EVA_MAIL = 'it-support@wikimedia.de'
# these Fields should be included in every mail
BASIC_DATA = ['firstname', 'lastname', 'firstdate_employment', 'firstdate_presence',
'jobdescription_german', 'jobdescription_english',]
# for every department: 'MAIL' => mail adress, 'DATA': additional fields to include
# also one copy with all fields to the person filling the form.
MAILS = {
'IT': {
'MAIL': 'wmde-it@wikimedia.de',
'DATA': [
'laptop', 'os', 'comment', 'email', 'landline', 'lists', 'mobile',
'department', 'accounts', 'language', 'screen', 'works_in_gs', 'desk',
'keyboard',
],
},
'OFFICE': {
'MAIL': 'office@wikimedia.de',
'DATA': [
'transponder', 'special', 'post_office_box', 'mobile',
'works_in_gs', 'desk',
],
},
'KOMM': {
'MAIL': 'presse@wikimedia.de',
'DATA': [
'department', 'team', 'add_to_wikimediade'
],
},
'CENTRAL': {
'MAIL': 'anna.noelte@wikimedia.de',
'DATA': [
'department', 'team', 'language', 'mobile', 'rebu2go'
],
},
'HR': {
'MAIL': 'personal@wikimedia.de',
'DATA': [
'department', 'team', 'language',
]
},
'FINANCE': {
'MAIL': 'claudia.langrock@wikimedia.de',
'DATA': [
'rebu2go'
]
},
'SUBMITTER': {
'MAIL': 'submitter@wikimedia.de',
'DATA': [
'department', 'team', 'add_to_wikimediade', 'remote', 'desk', 'vendor',
'os', 'screen', 'mobile', 'landline', 'keyboard', 'comment', 'language',
'accounts', 'lists', 'rebu2go', 'transponder', 'special', 'post_office_box'
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1,27 @@
{% 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

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

View File

@ -0,0 +1,31 @@
{% load i18n %}
(english below)
Hallo!
Es gibt einen Neuzugang bei Wikimedia! Hier ( https://wiki.wikimedia.de/wiki/Onboarding ) kannst Du nachsehen,
welche Schritte jetzt für Deine Abteilung nötig werden. Im Folgenden alle Daten,
die Du dafür brauchst:
{% include 'evapp/dataloop.txt' %}
Wenn Du Fragen hast, melde Dich bei {{contact}}.
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

@ -0,0 +1,110 @@
{% 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 socialaccount %}
{% if user.is_authenticated %}
{% block content %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<center>
<style>
ul > li {
list-style-type: none;
}
ul {
padding-left: 10;
}
label.required::after {
content: ' *';
color: red;
}
</style>
<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>
E (V A) - Austritt, (Veränderung, Austritt)<p>
</h1>{% translate "Du bist eingeloggt als" %} {{ user.email }}
<h2>
<p> {% translate "Schritt" %} {{ wizard.steps.step1 }} {% translate "von" %} {{ wizard.steps.count }}</p>
<p>{% if wizard.steps.step1 == 1 %}
{% translate "Angaben zur Person" %} {% endif %}
{% if choice == 'IN' %}
{% if wizard.steps.step1 == 2 %}
{% translate "Angaben zum neuen Arbeitsverhältnis" %}
{% elif wizard.steps.step1 == 3 %}
{% translate "IT-relevante Angaben" %}
{% elif wizard.steps.step1 == 4 %}
{% translate "Office-relevante Angaben" %}
{% endif %}
{% else %}
{% if wizard.steps.step1 == 2 %}
Veränderungsrelevante Angaben
{% endif %}
{% endif %}
{% if datatable == True %}
{% translate "Bestätigungsschritt" %}
{% endif %}
</p>
</h2>
{% if datatable == True %}
<table id="toggle-heading">
{% for key, value in data.items %}
<tr><th>{{ key }}</th><th>{{ value }}</th></tr>
{% endfor %}
</table>
{% endif %}
<form action="" method="post">
{% csrf_token %}
<table>
{% comment %}
comment this back in if you want to use CHANGE and EXIT process
{% if wizard.steps.step1 > 1 %}
Du hast den Prozess "{{choice_string}}" ausgewählt.
{% endif %}
{% endcomment %}
{{ 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> {% translate "Pflichtfeld" %}
<p>
{% if wizard.steps.prev %}
<button formnovalidate="formnovalidate" name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">{% translate "Zurück" %}</button>
{% endif %}
{% if datatable == True %}
<button type="submit" value="{% trans "Weiter" %}">{% translate "Abschicken" %}</button>
{% else %}
<button type="submit" value="{% trans "Weiter" %}">{% translate "Weiter" %}</button>
{% endif %}
</form>
<p>
<a href="{% url 'account_logout' %}">{% translate "logout" %}</a>
</center>
{% endblock %}
{% else %}
<a href="{% provider_login_url 'nextcloud' %}">{% translate "Bitte einloggen!" %}</a>
{% endif %}

View File

@ -0,0 +1,35 @@
{% block content %}
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
{% if next %}
{% if user.is_authenticated %}
<p>Your account doesn't have access to this page. To proceed,
please login with an account that has access.</p>
{% else %}
<p>Please login to see this page.</p>
{% endif %}
{% endif %}
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<table>
<tr>
<td>{{ form.username.label_tag }}</td>
<td>{{ form.username }}</td>
</tr>
<tr>
<td>{{ form.password.label_tag }}</td>
<td>{{ form.password }}</td>
</tr>
</table>
<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next }}" />
</form>
{# Assumes you setup the password_reset view in your URLconf #}
<p><a href="{% url 'password_reset' %}">Lost password?</a></p>
{% endblock %}

127
offboarding/tests.py Normal file
View File

@ -0,0 +1,127 @@
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
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'])

13
offboarding/urls.py Normal file
View File

@ -0,0 +1,13 @@
from django.urls import path
from .views import EvaFormView, success, long_process, change_process
urlpatterns = [
path('', EvaFormView.as_view(condition_dict = {'1': long_process,
'2': long_process,
'3': long_process,
'4': change_process,}),
name='evaform'),
path('success', success, name='success')
]

217
offboarding/views.py Normal file
View File

@ -0,0 +1,217 @@
from smtplib import SMTPException
import collections
from django.views.generic.edit import CreateView
from django.urls import reverse
from django.http import HttpResponse, HttpResponseRedirect
from django.core.mail import send_mail, BadHeaderError
from django.template.loader import get_template
from formtools.wizard.views import CookieWizardView
from django.shortcuts import render
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.translation import gettext_lazy as _
from .models import Employee, DEPARTMENT_CHOICES, OS_CHOICES, \
LANG_CHOICES, ACCOUNT_CHOICES, TRANSPONDER_CHOICES, KEYBOARD_CHOICES, JANEIN_CHOICES
from .forms import PersonalForm, WorkingForm, ITForm, OfficeForm, DummyForm,\
ChangeForm, TYPE_CHOICES
from .settings import MAILS, EVA_MAIL, BASIC_DATA, ONLY_ONBOARDING
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}.")
def long_process(wizard):
'''this method is called via urls.py to determine if a form is part of the IN-Process'''
if ONLY_ONBOARDING:
wizard.set_choice('IN')
return True
else:
data = wizard.get_cleaned_data_for_step('0') or {}
# print(data)
if data.get('choice') != 'CHANGE':
wizard.set_choice('IN')
# print('PROZESS IN')
return True
else:
wizard.set_choice('CHANGE')
# print('PROZESS NOT IN')
return False
def change_process(wizard):
''' this method is called via urls.py to determine if the form is part of the change process'''
# print('CHANGE PROZESS')
return not long_process(wizard)
class EvaFormView(LoginRequiredMixin, CookieWizardView):
template_name = 'offboarding/employee_form.html'
form_list = [PersonalForm, WorkingForm, ITForm, OfficeForm, ChangeForm, DummyForm]
instance = None
choice = 'IN'
# maybe we dont need this, if *_process() would be class methods,
# but unsure if this would work fine with the entries in urls.py
def set_choice(self, c):
self.choice = c
def generate_email(self, data):
(first, *_) = data['firstname'].split(maxsplit=1)
(last, *_) = data['lastname'].split(maxsplit=1)
name = first + '.' + last
#if not data['intern']:
# mail = name + '_ext@wikimedia.de'
#else:
mail = name + '@wikimedia.de'
data['email'] = mail
def get_all_cleaned_data(self):
'''this method deletes data which is only used temporary and is not in the modell,
it also changes the mail adress of the employee in some circumstances'''
data = super().get_all_cleaned_data()
self.generate_email(data)
# print("delete CHOICE FROM DATA")
if 'choice' in data:
del data['choice']
return data
def get_context_data(self, form, **kwargs):
'''this method is called to give context data to the template'''
#print('GETCONTEXT')
context = super().get_context_data(form=form, **kwargs)
testmode = settings.DEBUG or settings.MAILTEST
context.update({'choice': self.choice,
'choice_string': TYPE_CHOICES[self.choice],
'TESTMODE': testmode})
# 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)):
context.update({'data': self.beautify_data(self.get_all_cleaned_data()),
'datatable': True,})
return context
def get_form_instance(self,step):
''' this method assures, that we use the same model instance for all steps'''
if self.instance == None:
self.instance = Employee()
return self.instance
def done(self, form_list, **kwargs):
'''this method is called from CookieWizardView after all forms are filled'''
print ('INSTANCE_DICT')
print(self.instance_dict)
# save data to database
for form in form_list:
form.save()
# send data to departments
for dep in MAILS:
response = self.send_mail_to_department(dep)
if not settings.DEBUG:
self.instance.delete()
if response:
return response
else:
return HttpResponseRedirect('success')
def send_mail_to_department(self, department):
'send a mail to the given department with the nececcary notifications'
print(f'send mail to department {department}...')
contact = self.request.user.email
data = self.get_all_cleaned_data()
# some data should be in every mail
newdata = {k: v for k, v in data.items() if (k in BASIC_DATA)}
# 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'])})
context = {'data': self.beautify_data(newdata), 'contact': contact}
firstname = data['firstname']
lastname = data['lastname']
firstday = data['firstdate_employment']
try:
mail_template = get_template(f'evapp/department_mail.txt')
if settings.MAILTEST:
send_mail(
f'EVA: Neuzugang {firstname} {lastname} {firstday} (MAILTEST)',
mail_template.render(context),
EVA_MAIL,
[EVA_MAIL],
fail_silently=False)
elif department != "SUBMITTER":
send_mail(
f'EVA: Neuzugang {firstname} {lastname} {firstday}',
mail_template.render(context),
EVA_MAIL,
[MAILS[department]['MAIL']],
fail_silently=False)
else:
send_mail(
f'EVA: Neuzugang {firstname} {lastname} {firstday}',
mail_template.render(context),
EVA_MAIL,
[contact],
fail_silently=False)
except BadHeaderError as error:
print(error)
self.instance.delete()
return HttpResponse(f'{error}<p>Invalid header found. Data not saved!')
except SMTPException as error:
print(error)
self.instance.delete()
return HttpResponse(f'{error}<p>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):
''' # use long form for contextdata instead of short form if available
#
# ATTENTION!
# This implementation works only for unique keys over all of these dicts from model.py
#
'''
# update values in data dictionary with keys from *_CHOICES if present there
choices = {**DEPARTMENT_CHOICES, **TRANSPONDER_CHOICES,
**OS_CHOICES, **LANG_CHOICES, **KEYBOARD_CHOICES}
data.update({k:choices[v] for k,v in data.items() \
if isinstance(v,collections.abc.Hashable) \
and v in choices})
# replace values in accounts array from *_CHOICES
if 'accounts' in data:
data['accounts'] = [ACCOUNT_CHOICES[c] for c in data['accounts']]
# replace keys in data dictionary with verbose_name
# a bit ugly workaround here: we need to store 'email' away, because it es not in the modell
mail = ''
if 'email' in data:
mail = data.pop('email')
newdata = {self.instance._meta.get_field(k).verbose_name.title() : v for k,v in data.items()}
if mail:
newdata['Email'] = mail
# translate booleans
newdata.update({k:'Ja' for k,v in newdata.items() if isinstance(v,bool) and v == True})
newdata.update({k:'Nein' for k,v in newdata.items() if isinstance(v,bool) and v == False})
# handle some special data types
newdata.update({k:'' for k,v in newdata.items() if v == None})
newdata.update({k:'' for k,v in newdata.items() if v == []})
return newdata

1
settings.py Symbolic link
View File

@ -0,0 +1 @@
settings_development.py

0
veränderung/__init__.py Normal file
View File

6
veränderung/admin.py Normal file
View File

@ -0,0 +1,6 @@
from django.contrib import admin
from .models import Employee
admin.site.register([
Employee,
])

5
veränderung/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class EvappConfig(AppConfig):
name = 'veränderung'

67
veränderung/forms.py Normal file
View File

@ -0,0 +1,67 @@
from django.db import models
from django.forms import ModelForm, DateInput, Form, ChoiceField, RadioSelect
from django.core.exceptions import ValidationError
from .models import Employee
# class EmployeeForm(ModelForm):
# class Meta:
# model = Employee
# fields = '__all__'
# widgets = {'firstdate_employment': DateInput(attrs={'type': 'date'}),
# 'firstdate_presence': DateInput(attrs={'type': 'date'}),}
class DummyForm(ModelForm):
class Meta:
model = Employee
fields = []
class EvaForm(ModelForm):
'''this base class provides the required css class for all forms'''
required_css_class = 'required'
TYPE_CHOICES = {'IN': 'Eintritt', 'CHANGE': 'Veränderung', 'OUT': 'Austritt'}
class PersonalForm(EvaForm):
# TODO: comment this back in to use implementation of change or exit process
# choice = ChoiceField(choices=TYPE_CHOICES.items(), widget=RadioSelect,
# label='Welcher Prozess soll angestoßen werden?')
class Meta:
model = Employee
fields = ['firstname', 'lastname', 'department', 'team', 'add_to_wikimediade',]
class WorkingForm(EvaForm):
def clean(self):
data = self.cleaned_data
if data['works_in_gs'] and data['desk'] is None:
raise ValidationError('Wer nicht remote arbeitet braucht einen Schreibtisch!')
return data
class Meta:
model = Employee
fields = ['firstdate_employment', 'firstdate_presence', 'jobdescription_german',
'jobdescription_english', 'works_in_gs', 'desk',]
widgets = {'firstdate_employment': DateInput(attrs={'type': 'date'}),
'firstdate_presence': DateInput(attrs={'type': 'date'}),}
class ITForm(EvaForm):
class Meta:
model = Employee
fields = [
'framework', 'os', 'keyboard', 'mobile', 'landline',
'comment', 'language', 'accounts', 'lists', 'rebu2go' ]
class OfficeForm(EvaForm):
class Meta:
model = Employee
fields = ['transponder', 'special', 'post_office_box',]
class ChangeForm(EvaForm):
class Meta:
model = Employee
fields = ['firstdate_employment', 'jobdescription_german', 'jobdescription_english',
'desk', 'comment', 'accounts', 'lists', 'transponder']
widgets = {'firstdate_employment': DateInput(attrs={'type': 'date'}),}

View File

@ -0,0 +1,46 @@
# Generated by Django 3.1.4 on 2021-09-13 12:41
from django.db import migrations, models
import multiselectfield.db.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Employee',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('firstname', models.CharField(max_length=50, verbose_name='Vorname')),
('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')),
('team', models.CharField(blank=True, max_length=20, null=True)),
('add_to_wikimediade', models.BooleanField(default=False, verbose_name='Soll auf wikimedia.de irgendwo stehen?')),
('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')),
('jobdescription_german', models.CharField(max_length=100, null=True, verbose_name='Stellenbezeichnung(deutsch)')),
('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(blank=True, max_length=100, null=True, verbose_name='Wo soll der Arbeitsplatz sein?')),
('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)'), ('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.')),
('mobile', models.BooleanField(default=False, max_length=6, verbose_name='Diensttelefon (Handy)')),
('landline', models.BooleanField(default=False, verbose_name='Festnetznummer (Sipgate)')),
('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')),
('language', models.CharField(choices=[('GER', 'Deutsch'), ('ENG', 'English')], default='GER', max_length=3, verbose_name='Sprache für Onboarding')),
('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')),
('lists', models.CharField(blank=True, max_length=100, null=True, verbose_name='Zusätzliche Mailinglisten')),
('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,18 @@
# 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,28 @@
# 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

@ -0,0 +1,24 @@
# 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,19 @@
# 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,32 @@
# Generated by Django 4.2.16 on 2024-11-26 09:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0005_alter_employee_accounts'),
]
operations = [
migrations.RemoveField(
model_name='employee',
name='vendor',
),
migrations.AddField(
model_name='employee',
name='framework',
field=models.CharField(blank=True, max_length=300, null=True, verbose_name='Möchten Sie vom Standard des Frameworks abweichen, und wenn ja, warum?'),
),
migrations.AlterField(
model_name='employee',
name='add_to_wikimediade',
field=models.CharField(choices=[('NEIN', 'Nein'), ('JA', 'Ja')], default=True, max_length=5, verbose_name='Soll die Person bei Ansprechpartner*innen auf der WMDE-Webseite mit aufgenommen werden?'),
),
migrations.AlterField(
model_name='employee',
name='os',
field=models.CharField(choices=[('FED', 'Fedora (Standard)'), ('WIN', 'Windows (bitte Begründung angeben)'), ('MOS', 'Mac OS (nur wenn Mac gewählt)'), ('UBU', 'Ubuntu')], default='FED', max_length=3, verbose_name='Betriebssystem'),
),
]

View File

@ -0,0 +1,32 @@
# Generated by Django 4.2.17 on 2024-12-17 14:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evapp', '0006_remove_employee_vendor_employee_framework_and_more'),
]
operations = [
migrations.RemoveField(
model_name='employee',
name='screen',
),
migrations.AlterField(
model_name='employee',
name='add_to_wikimediade',
field=models.CharField(choices=[('NEIN', 'Nein'), ('JA', 'Ja')], max_length=5, verbose_name='Soll die Person bei Ansprechpartner*innen auf der WMDE-Webseite mit aufgenommen werden?'),
),
migrations.AlterField(
model_name='employee',
name='framework',
field=models.CharField(blank=True, max_length=300, null=True, verbose_name='Möchten Sie vom Standard des Frameworks (Laptop) abweichen, und wenn ja, warum?'),
),
migrations.AlterField(
model_name='employee',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]

View File

86
veränderung/models.py Normal file
View File

@ -0,0 +1,86 @@
from django.db import models
from multiselectfield import MultiSelectField
from django.utils.translation import gettext_lazy as _
# ATTENTION!!!
# No key should be used twice in any of these dicts because of the
# suboptimal implementation in views.EvaFormView.beautify_data()
#
DEPARTMENT_CHOICES = {'COENG': _('Communitys & Engagement'),
'SOFT': _('Softwareentwicklung'),
'CENT': 'Central',
'KOMAD': _('Kommunikation & Advocacy'),
'VOR': _('Vorstand'),}
#VENDOR_CHOICES = {'STANDARD': 'Dell Latitude',
# 'LENOVO': 'Lenovo Thinkpad',
# 'MAC': _('Mac (nur für Grafiker_innen)')}
OS_CHOICES = {'FED': 'Fedora (Standard)',
'WIN': _('Windows (bitte Begründung angeben)'),
'MOS': _('Mac OS (nur wenn Mac gewählt)'),
'UBU': _('Ubuntu')}
LANG_CHOICES = {'GER': 'Deutsch',
'ENG': 'English',}
KEYBOARD_CHOICES = {'DE': 'Deutsch',
'US': 'USA',
'OT': _('Anderes (Bitte unten angeben)')}
ACCOUNT_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',
}
TRANSPONDER_CHOICES = {'NORM': _('Allgemeiner Transponder'),
'SPECIAL': _('Besondere Schließungen (bitte angeben)'),
'NOTRANS': _('Kein Transponder'),}
JANEIN_CHOICES = {'NEIN': ('Nein'),
'JA': _('Ja'),}
class Employee(models.Model):
# 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')
# personal data
firstname = models.CharField(max_length=50, verbose_name=_("Vorname"))
lastname = models.CharField(max_length=50, verbose_name=_("Nachname"))
# intern = models.BooleanField(verbose_name='Interne_r Mitarbeiter_in?', default=True)
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?
add_to_wikimediade = models.CharField(max_length=5, choices=JANEIN_CHOICES.items(), verbose_name=_("Soll die Person bei Ansprechpartner*innen auf der WMDE-Webseite mit aufgenommen werden?"))
# general work related stuff
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"))
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)")
works_in_gs = models.BooleanField(verbose_name=_('Braucht Arbeitsplatz in der Geschäftsstelle?)'), default=True)
desk = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("Wo soll der Arbeitsplatz sein?"))
# IT related stuff
#vendor = models.CharField(max_length=8, choices=VENDOR_CHOICES.items(), default='STANDARD', verbose_name=_('Hersteller'))
framework = models.CharField(max_length=300, null=True, blank=True, verbose_name=_("Möchten Sie vom Standard des Frameworks (Laptop) abweichen, und wenn ja, warum?"))
os = models.CharField(max_length=3, choices=OS_CHOICES.items(), default='FED', verbose_name=_('Betriebssystem'))
#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)'))
landline = models.BooleanField(default = False, verbose_name=_('Festnetznummer (Sipgate)'))
# sim = models.BooleanField(default=False, verbose_name="Mobilfunkvertrag")
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"))
language = models.CharField(max_length=3, choices=LANG_CHOICES.items(), default="GER", verbose_name=_("Sprache für Onboarding"))
accounts = MultiSelectField(choices=ACCOUNT_CHOICES.items(), max_length=40, null=True, blank=True, verbose_name=_("Zusätzliche Accounts"))
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)
# office related stuff
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"))
post_office_box = models.BooleanField(default=True, verbose_name=_('Postfach am Empfang benötigt?'))

61
veränderung/settings.py Normal file
View File

@ -0,0 +1,61 @@
# temporary setting while change and exit is not yet fully implemented
ONLY_ONBOARDING = True
# sender mail adress also used for MAILTEST mode
EVA_MAIL = 'it-support@wikimedia.de'
# these Fields should be included in every mail
BASIC_DATA = ['firstname', 'lastname', 'firstdate_employment', 'firstdate_presence',
'jobdescription_german', 'jobdescription_english',]
# for every department: 'MAIL' => mail adress, 'DATA': additional fields to include
# also one copy with all fields to the person filling the form.
MAILS = {
'IT': {
'MAIL': 'wmde-it@wikimedia.de',
'DATA': [
'laptop', 'os', 'comment', 'email', 'landline', 'lists', 'mobile',
'department', 'accounts', 'language', 'screen', 'works_in_gs', 'desk',
'keyboard',
],
},
'OFFICE': {
'MAIL': 'office@wikimedia.de',
'DATA': [
'transponder', 'special', 'post_office_box', 'mobile',
'works_in_gs', 'desk',
],
},
'KOMM': {
'MAIL': 'presse@wikimedia.de',
'DATA': [
'department', 'team', 'add_to_wikimediade'
],
},
'CENTRAL': {
'MAIL': 'anna.noelte@wikimedia.de',
'DATA': [
'department', 'team', 'language', 'mobile', 'rebu2go'
],
},
'HR': {
'MAIL': 'personal@wikimedia.de',
'DATA': [
'department', 'team', 'language',
]
},
'FINANCE': {
'MAIL': 'claudia.langrock@wikimedia.de',
'DATA': [
'rebu2go'
]
},
'SUBMITTER': {
'MAIL': 'submitter@wikimedia.de',
'DATA': [
'department', 'team', 'add_to_wikimediade', 'remote', 'desk', 'vendor',
'os', 'screen', 'mobile', 'landline', 'keyboard', 'comment', 'language',
'accounts', 'lists', 'rebu2go', 'transponder', 'special', 'post_office_box'
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1,27 @@
{% 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

@ -0,0 +1,35 @@
{% block content %}
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
{% if next %}
{% if user.is_authenticated %}
<p>Your account doesn't have access to this page. To proceed,
please login with an account that has access.</p>
{% else %}
<p>Please login to see this page.</p>
{% endif %}
{% endif %}
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<table>
<tr>
<td>{{ form.username.label_tag }}</td>
<td>{{ form.username }}</td>
</tr>
<tr>
<td>{{ form.password.label_tag }}</td>
<td>{{ form.password }}</td>
</tr>
</table>
<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next }}" />
</form>
{# Assumes you setup the password_reset view in your URLconf #}
<p><a href="{% url 'password_reset' %}">Lost password?</a></p>
{% endblock %}

View File

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

View File

@ -0,0 +1,31 @@
{% load i18n %}
(english below)
Hallo!
Es gibt einen Neuzugang bei Wikimedia! Hier ( https://wiki.wikimedia.de/wiki/Onboarding ) kannst Du nachsehen,
welche Schritte jetzt für Deine Abteilung nötig werden. Im Folgenden alle Daten,
die Du dafür brauchst:
{% include 'evapp/dataloop.txt' %}
Wenn Du Fragen hast, melde Dich bei {{contact}}.
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

@ -0,0 +1,110 @@
{% 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 socialaccount %}
{% if user.is_authenticated %}
{% block content %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<center>
<style>
ul > li {
list-style-type: none;
}
ul {
padding-left: 10;
}
label.required::after {
content: ' *';
color: red;
}
</style>
<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>
E (V A) - Veränderung, (Veränderung, Austritt)<p>
</h1>{% translate "Du bist eingeloggt als" %} {{ user.email }}
<h2>
<p> {% translate "Schritt" %} {{ wizard.steps.step1 }} {% translate "von" %} {{ wizard.steps.count }}</p>
<p>{% if wizard.steps.step1 == 1 %}
{% translate "Angaben zur Person" %} {% endif %}
{% if choice == 'IN' %}
{% if wizard.steps.step1 == 2 %}
{% translate "Angaben zum neuen Arbeitsverhältnis" %}
{% elif wizard.steps.step1 == 3 %}
{% translate "IT-relevante Angaben" %}
{% elif wizard.steps.step1 == 4 %}
{% translate "Office-relevante Angaben" %}
{% endif %}
{% else %}
{% if wizard.steps.step1 == 2 %}
Veränderungsrelevante Angaben
{% endif %}
{% endif %}
{% if datatable == True %}
{% translate "Bestätigungsschritt" %}
{% endif %}
</p>
</h2>
{% if datatable == True %}
<table id="toggle-heading">
{% for key, value in data.items %}
<tr><th>{{ key }}</th><th>{{ value }}</th></tr>
{% endfor %}
</table>
{% endif %}
<form action="" method="post">
{% csrf_token %}
<table>
{% comment %}
comment this back in if you want to use CHANGE and EXIT process
{% if wizard.steps.step1 > 1 %}
Du hast den Prozess "{{choice_string}}" ausgewählt.
{% endif %}
{% endcomment %}
{{ 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> {% translate "Pflichtfeld" %}
<p>
{% if wizard.steps.prev %}
<button formnovalidate="formnovalidate" name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">{% translate "Zurück" %}</button>
{% endif %}
{% if datatable == True %}
<button type="submit" value="{% trans "Weiter" %}">{% translate "Abschicken" %}</button>
{% else %}
<button type="submit" value="{% trans "Weiter" %}">{% translate "Weiter" %}</button>
{% endif %}
</form>
<p>
<a href="{% url 'account_logout' %}">{% translate "logout" %}</a>
</center>
{% endblock %}
{% else %}
<a href="{% provider_login_url 'nextcloud' %}">{% translate "Bitte einloggen!" %}</a>
{% endif %}

127
veränderung/tests.py Normal file
View File

@ -0,0 +1,127 @@
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
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'])

13
veränderung/urls.py Normal file
View File

@ -0,0 +1,13 @@
from django.urls import path
from .views import EvaFormView, success, long_process, change_process
urlpatterns = [
path('', EvaFormView.as_view(condition_dict = {'1': long_process,
'2': long_process,
'3': long_process,
'4': change_process,}),
name='evaform'),
path('success', success, name='success')
]

217
veränderung/views.py Normal file
View File

@ -0,0 +1,217 @@
from smtplib import SMTPException
import collections
from django.views.generic.edit import CreateView
from django.urls import reverse
from django.http import HttpResponse, HttpResponseRedirect
from django.core.mail import send_mail, BadHeaderError
from django.template.loader import get_template
from formtools.wizard.views import CookieWizardView
from django.shortcuts import render
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.translation import gettext_lazy as _
from .models import Employee, DEPARTMENT_CHOICES, OS_CHOICES, \
LANG_CHOICES, ACCOUNT_CHOICES, TRANSPONDER_CHOICES, KEYBOARD_CHOICES, JANEIN_CHOICES
from .forms import PersonalForm, WorkingForm, ITForm, OfficeForm, DummyForm,\
ChangeForm, TYPE_CHOICES
from .settings import MAILS, EVA_MAIL, BASIC_DATA, ONLY_ONBOARDING
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}.")
def long_process(wizard):
'''this method is called via urls.py to determine if a form is part of the IN-Process'''
if ONLY_ONBOARDING:
wizard.set_choice('IN')
return True
else:
data = wizard.get_cleaned_data_for_step('0') or {}
# print(data)
if data.get('choice') != 'CHANGE':
wizard.set_choice('IN')
# print('PROZESS IN')
return True
else:
wizard.set_choice('CHANGE')
# print('PROZESS NOT IN')
return False
def change_process(wizard):
''' this method is called via urls.py to determine if the form is part of the change process'''
# print('CHANGE PROZESS')
return not long_process(wizard)
class EvaFormView(LoginRequiredMixin, CookieWizardView):
template_name = 'veränderung/employee_form.html'
form_list = [PersonalForm, WorkingForm, ITForm, OfficeForm, ChangeForm, DummyForm]
instance = None
choice = 'IN'
# maybe we dont need this, if *_process() would be class methods,
# but unsure if this would work fine with the entries in urls.py
def set_choice(self, c):
self.choice = c
def generate_email(self, data):
(first, *_) = data['firstname'].split(maxsplit=1)
(last, *_) = data['lastname'].split(maxsplit=1)
name = first + '.' + last
#if not data['intern']:
# mail = name + '_ext@wikimedia.de'
#else:
mail = name + '@wikimedia.de'
data['email'] = mail
def get_all_cleaned_data(self):
'''this method deletes data which is only used temporary and is not in the modell,
it also changes the mail adress of the employee in some circumstances'''
data = super().get_all_cleaned_data()
self.generate_email(data)
# print("delete CHOICE FROM DATA")
if 'choice' in data:
del data['choice']
return data
def get_context_data(self, form, **kwargs):
'''this method is called to give context data to the template'''
#print('GETCONTEXT')
context = super().get_context_data(form=form, **kwargs)
testmode = settings.DEBUG or settings.MAILTEST
context.update({'choice': self.choice,
'choice_string': TYPE_CHOICES[self.choice],
'TESTMODE': testmode})
# 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)):
context.update({'data': self.beautify_data(self.get_all_cleaned_data()),
'datatable': True,})
return context
def get_form_instance(self,step):
''' this method assures, that we use the same model instance for all steps'''
if self.instance == None:
self.instance = Employee()
return self.instance
def done(self, form_list, **kwargs):
'''this method is called from CookieWizardView after all forms are filled'''
print ('INSTANCE_DICT')
print(self.instance_dict)
# save data to database
for form in form_list:
form.save()
# send data to departments
for dep in MAILS:
response = self.send_mail_to_department(dep)
if not settings.DEBUG:
self.instance.delete()
if response:
return response
else:
return HttpResponseRedirect('success')
def send_mail_to_department(self, department):
'send a mail to the given department with the nececcary notifications'
print(f'send mail to department {department}...')
contact = self.request.user.email
data = self.get_all_cleaned_data()
# some data should be in every mail
newdata = {k: v for k, v in data.items() if (k in BASIC_DATA)}
# 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'])})
context = {'data': self.beautify_data(newdata), 'contact': contact}
firstname = data['firstname']
lastname = data['lastname']
firstday = data['firstdate_employment']
try:
mail_template = get_template(f'evapp/department_mail.txt')
if settings.MAILTEST:
send_mail(
f'EVA: Neuzugang {firstname} {lastname} {firstday} (MAILTEST)',
mail_template.render(context),
EVA_MAIL,
[EVA_MAIL],
fail_silently=False)
elif department != "SUBMITTER":
send_mail(
f'EVA: Neuzugang {firstname} {lastname} {firstday}',
mail_template.render(context),
EVA_MAIL,
[MAILS[department]['MAIL']],
fail_silently=False)
else:
send_mail(
f'EVA: Neuzugang {firstname} {lastname} {firstday}',
mail_template.render(context),
EVA_MAIL,
[contact],
fail_silently=False)
except BadHeaderError as error:
print(error)
self.instance.delete()
return HttpResponse(f'{error}<p>Invalid header found. Data not saved!')
except SMTPException as error:
print(error)
self.instance.delete()
return HttpResponse(f'{error}<p>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):
''' # use long form for contextdata instead of short form if available
#
# ATTENTION!
# This implementation works only for unique keys over all of these dicts from model.py
#
'''
# update values in data dictionary with keys from *_CHOICES if present there
choices = {**DEPARTMENT_CHOICES, **TRANSPONDER_CHOICES,
**OS_CHOICES, **LANG_CHOICES, **KEYBOARD_CHOICES}
data.update({k:choices[v] for k,v in data.items() \
if isinstance(v,collections.abc.Hashable) \
and v in choices})
# replace values in accounts array from *_CHOICES
if 'accounts' in data:
data['accounts'] = [ACCOUNT_CHOICES[c] for c in data['accounts']]
# replace keys in data dictionary with verbose_name
# a bit ugly workaround here: we need to store 'email' away, because it es not in the modell
mail = ''
if 'email' in data:
mail = data.pop('email')
newdata = {self.instance._meta.get_field(k).verbose_name.title() : v for k,v in data.items()}
if mail:
newdata['Email'] = mail
# translate booleans
newdata.update({k:'Ja' for k,v in newdata.items() if isinstance(v,bool) and v == True})
newdata.update({k:'Nein' for k,v in newdata.items() if isinstance(v,bool) and v == False})
# handle some special data types
newdata.update({k:'' for k,v in newdata.items() if v == None})
newdata.update({k:'' for k,v in newdata.items() if v == []})
return newdata