forked from beba/foerderbarometer
Merge branch 'feature/replace-radios-with-links' into 'cosmocode'
refactor(forms): Split Views and Template Restructure See merge request wikimedia/foerderbarometer!5
This commit is contained in:
commit
38f6deee51
|
|
@ -1,9 +1,10 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.forms import ModelForm, ChoiceField, RadioSelect, BooleanField
|
from django.forms import ModelForm, ChoiceField, RadioSelect, BooleanField, CharField, EmailField
|
||||||
from django.contrib.admin.widgets import AdminDateWidget
|
from django.contrib.admin.widgets import AdminDateWidget
|
||||||
from django.forms.renderers import DjangoTemplates
|
from django.forms.renderers import DjangoTemplates
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
from django import forms
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
TYPE_CHOICES,
|
TYPE_CHOICES,
|
||||||
|
|
@ -33,7 +34,6 @@ class FdbForm(ModelForm):
|
||||||
|
|
||||||
|
|
||||||
class ProjectForm(FdbForm):
|
class ProjectForm(FdbForm):
|
||||||
|
|
||||||
# start = DateField(widget=AdminDateWidget())
|
# start = DateField(widget=AdminDateWidget())
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -47,6 +47,48 @@ class ProjectForm(FdbForm):
|
||||||
js = ('dropdown/js/otrs_link.js',)
|
js = ('dropdown/js/otrs_link.js',)
|
||||||
|
|
||||||
|
|
||||||
|
class CommonOrderMixin(forms.Form):
|
||||||
|
"""
|
||||||
|
Ensures a consistent field order for all forms that inherit from this mixin.
|
||||||
|
|
||||||
|
The goal is to always render:
|
||||||
|
- "realname" and "email" fields at the top,
|
||||||
|
- all other fields in the middle (in the order Django provides),
|
||||||
|
- "check" (terms/privacy checkbox) at the bottom.
|
||||||
|
|
||||||
|
This keeps the UX consistent across all forms without repeating field_order logic.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Fields that should always appear first in the form
|
||||||
|
field_order_head = ('realname', 'email')
|
||||||
|
|
||||||
|
# Fields that should always appear last in the form
|
||||||
|
field_order_tail = ('check',) # rename if your checkbox field is called differently
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Override the default form initialization to reorder fields.
|
||||||
|
The method:
|
||||||
|
1. Collects all existing field names.
|
||||||
|
2. Puts the 'head' fields first (if they exist in this form).
|
||||||
|
3. Keeps all other fields in the middle.
|
||||||
|
4. Puts the 'tail' fields last (if they exist in this form).
|
||||||
|
"""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
existing = list(self.fields.keys())
|
||||||
|
|
||||||
|
# Select only fields that actually exist in this form
|
||||||
|
head = [f for f in self.field_order_head if f in self.fields]
|
||||||
|
tail = [f for f in self.field_order_tail if f in self.fields]
|
||||||
|
|
||||||
|
# All other fields that are not explicitly in head or tail
|
||||||
|
middle = [f for f in existing if f not in (*head, *tail)]
|
||||||
|
|
||||||
|
# Apply the new order to the form fields
|
||||||
|
self.order_fields(head + middle + tail)
|
||||||
|
|
||||||
|
|
||||||
class ExternForm(FdbForm):
|
class ExternForm(FdbForm):
|
||||||
|
|
||||||
choice = ChoiceField(choices=TYPE_CHOICES.items(), widget=RadioSelect,
|
choice = ChoiceField(choices=TYPE_CHOICES.items(), widget=RadioSelect,
|
||||||
|
|
@ -79,7 +121,33 @@ HOTEL_CHOICES = {'TRUE': mark_safe('Hotelzimmer benötigt'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TravelForm(FdbForm):
|
class BaseApplicationForm(FdbForm):
|
||||||
|
"""
|
||||||
|
Base form for all external applications.
|
||||||
|
|
||||||
|
- Adds standard fields that must appear in every form:
|
||||||
|
* realname (applicant's full name)
|
||||||
|
* email (contact address for notifications)
|
||||||
|
* check (confirmation of data protection and funding policy)
|
||||||
|
- Ensures consistency across all application types.
|
||||||
|
"""
|
||||||
|
realname = CharField(
|
||||||
|
label='Realname',
|
||||||
|
required=True,
|
||||||
|
help_text='Bitte gib deinen Vor- und Nachnamen ein.'
|
||||||
|
)
|
||||||
|
email = EmailField(
|
||||||
|
label='E-Mail-Adresse',
|
||||||
|
required=True,
|
||||||
|
help_text='Bitte gib deine E-Mail-Adresse ein, damit dich <br> Wikimedia Deutschland bei Rückfragen oder für <br> die Zusage kontaktieren kann.'
|
||||||
|
)
|
||||||
|
check = BooleanField(required=True,
|
||||||
|
label=format_html(
|
||||||
|
"Ich stimme den <a href='{}' target='_blank' rel='noopener'>Datenschutzbestimmungen</a> und der<br> <a href='{}' target='_blank' rel='noopener'>Richtlinie zur Förderung der Communitys</a> zu",
|
||||||
|
settings.DATAPROTECTION, settings.FOERDERRICHTLINIEN))
|
||||||
|
|
||||||
|
|
||||||
|
class TravelForm(BaseApplicationForm, CommonOrderMixin):
|
||||||
# TODO: add some javascript to show/hide other-field
|
# TODO: add some javascript to show/hide other-field
|
||||||
|
|
||||||
# this is the code, to change required to false if needed
|
# this is the code, to change required to false if needed
|
||||||
|
|
@ -107,7 +175,7 @@ class TravelForm(FdbForm):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class LibraryForm(FdbForm):
|
class LibraryForm(BaseApplicationForm, CommonOrderMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Library
|
model = Library
|
||||||
|
|
@ -144,7 +212,7 @@ class HonoraryCertificateForm(FdbForm):
|
||||||
js = ('dropdown/js/otrs_link.js',)
|
js = ('dropdown/js/otrs_link.js',)
|
||||||
|
|
||||||
|
|
||||||
class IFGForm(FdbForm):
|
class IFGForm(BaseApplicationForm, CommonOrderMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IFG
|
model = IFG
|
||||||
fields = ['cost', 'url', 'notes']
|
fields = ['cost', 'url', 'notes']
|
||||||
|
|
@ -168,7 +236,7 @@ class CheckForm(FdbForm):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LiteratureForm(CheckForm):
|
class LiteratureForm(BaseApplicationForm, CommonOrderMixin):
|
||||||
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_LITERATURSTIPENDIUM
|
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_LITERATURSTIPENDIUM
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
@ -186,7 +254,7 @@ ADULT_CHOICES = {'TRUE': mark_safe('Ich bin volljährig.'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class EmailForm(CheckForm):
|
class EmailForm(BaseApplicationForm, CommonOrderMixin):
|
||||||
|
|
||||||
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_EMAIL_SERVICE
|
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_EMAIL_SERVICE
|
||||||
|
|
||||||
|
|
@ -210,7 +278,7 @@ class EmailForm(CheckForm):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BusinessCardForm(CheckForm):
|
class BusinessCardForm(BaseApplicationForm, CommonOrderMixin):
|
||||||
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_VISITENKARTEN
|
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_VISITENKARTEN
|
||||||
# this is the code, to change required to false if needed
|
# this is the code, to change required to false if needed
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
@ -226,7 +294,7 @@ class BusinessCardForm(CheckForm):
|
||||||
js = ('dropdown/js/businessCard.js',)
|
js = ('dropdown/js/businessCard.js',)
|
||||||
|
|
||||||
|
|
||||||
class ListForm(CheckForm):
|
class ListForm(BaseApplicationForm, CommonOrderMixin):
|
||||||
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_MAILINGLISTEN
|
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_MAILINGLISTEN
|
||||||
class Meta:
|
class Meta:
|
||||||
model = List
|
model = List
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
.star {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wm-table {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-request {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
{% load static %}
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<!-- Admin i18n -->
|
||||||
|
<script src="{% url 'jsi18n' %}"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Admin Assets -->
|
||||||
|
<script src="{% static 'admin/js/core.js' %}"></script>
|
||||||
|
<script src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>
|
||||||
|
<script src="{% static 'admin/js/jquery.init.js' %}"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{% static 'admin/css/base.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'admin/css/widgets.css' %}">
|
||||||
|
|
||||||
|
<!-- Project Styles -->
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'css/forms.css' %}">
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'css/base.css' %}">
|
||||||
|
|
||||||
|
{% block head_extra %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% include "input/partials/_header.html" %}
|
||||||
|
|
||||||
|
<main class="wm-main">
|
||||||
|
{% block pre_content %}{% endblock %}
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
{% block post_content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{% include "input/partials/_footer.html" %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
<script type="text/javascript" src="/admin/jsi18n/"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'admin/js/core.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'admin/js/jquery.init.js' %}"></script>
|
|
||||||
|
|
||||||
{{ form.media }}
|
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/widgets.css' %}" />
|
|
||||||
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<center>
|
|
||||||
<style>
|
|
||||||
ul > li {
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
padding-left: 10;
|
|
||||||
}
|
|
||||||
label.required::after {
|
|
||||||
content: ' *';
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<img src="{% static 'input/logo.png' %}" />
|
|
||||||
|
|
||||||
<p>Schritt {{ wizard.steps.step1 }} von {{ wizard.steps.count }}</p>
|
|
||||||
<form action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<table>
|
|
||||||
{% if choice %}
|
|
||||||
Du hast {{choice}} ausgewählt.
|
|
||||||
{% endif %}
|
|
||||||
{{ wizard.management_form }}
|
|
||||||
{% if wizard.form.forms %}
|
|
||||||
{{ wizard.form.management_form }}
|
|
||||||
{% for form in wizard.form.forms %}
|
|
||||||
{{ form }}
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
{{ wizard.form }}
|
|
||||||
{% endif %}
|
|
||||||
</table>
|
|
||||||
<p>
|
|
||||||
<span style="color: red">*</span> Pflichtfeld
|
|
||||||
<p>
|
|
||||||
{% if wizard.steps.prev %}
|
|
||||||
<button formnovalidate="formnovalidate" name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">Zurück</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if wizard.steps.current == wizard.steps.last %}
|
|
||||||
<button type="submit" value="{% trans "Weiter" %}">Absenden</button>
|
|
||||||
{% else %}
|
|
||||||
<button type="submit" value="{% trans "Weiter" %}">Weiter</button>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
<p>
|
|
||||||
<img src="https://upload.wikimedia.org/wikipedia/commons/c/c4/Figuren_klein.jpg"><p>
|
|
||||||
Eine Übersicht aller Förderangebote von Wikimedia Deutschland findest du im <a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Förderangebote">
|
|
||||||
Förderportal in der deutschsprachigen Wikipedia</a>.
|
|
||||||
<br>Für alle Fragen wende dich gern an das <a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Wikimedia_Deutschland">Team Communitys und Engagement</a>.
|
|
||||||
<p>
|
|
||||||
Für interessierte Hacker gibts auch den <a href="https://srcsrv.wikimedia.de/beba/foerderbarometer">Sourcecode</a> zum Formular und was damit passiert.
|
|
||||||
<p>
|
|
||||||
<a href="https://www.wikimedia.de/impressum/">Impressum</a>
|
|
||||||
</center>{% endblock %}
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
{% extends "input/base.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="page-centered">
|
||||||
|
<table class="wm-table">
|
||||||
|
<tr>
|
||||||
|
<th class="col-request">Was möchtest du beantragen?</th>
|
||||||
|
<td>
|
||||||
|
<strong>Projektförderung</strong>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="#">Projektförderung</a>
|
||||||
|
mit einer Gesamtsumme unter 1.000,— EUR
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'info-foerderprojekt-ab-1000' %}">Projektförderung</a>
|
||||||
|
mit einer Gesamtsumme ab 1.000,— EUR
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<strong>Serviceleistungen</strong>
|
||||||
|
<ul>
|
||||||
|
<li><a href="{% url 'bibliotheksstipendium' %}">Bibliotheksstipendium</a></li>
|
||||||
|
<li><a href="{% url 'eliteraturstipendium' %}">eLiteraturstipendium</a></li>
|
||||||
|
<li><a href="{% url 'email' %}">E-Mail-Adresse</a></li>
|
||||||
|
<li><a href="{% url 'ifg' %}">Kostenübernahme IFG-Anfrage</a></li>
|
||||||
|
<li><a href="{% url 'literatur' %}">Literaturstipendium</a></li>
|
||||||
|
<li><a href="{% url 'mailingliste' %}">Mailingliste</a></li>
|
||||||
|
<li><a href="{% url 'reisekosten' %}">Reisekosten</a></li>
|
||||||
|
<li><a href="{% url 'softwarestipendium' %}">Softwarestipendium</a></li>
|
||||||
|
<li><a href="{% url 'visitenkarten' %}">Visitenkarten</a></li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
{% extends "input/base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{{ form.media }}
|
||||||
|
|
||||||
|
<div class="page-centered">
|
||||||
|
<p>Du hast {{ typestring }} ausgewählt.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" class="wm-form" {% if form.is_multipart %}enctype="multipart/form-data"{% endif %}>
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{% block pre_table %}{% endblock %}
|
||||||
|
|
||||||
|
<table class="wm-table">
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% block post_table %}{% endblock %}
|
||||||
|
|
||||||
|
<p class="page-centered"><span class="star">*</span> Pflichtfeld</p>
|
||||||
|
|
||||||
|
<div class="page-centered">
|
||||||
|
<button type="button" onclick="history.back()">Zurück</button>
|
||||||
|
<button type="submit">Absenden</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Projektförderung ab 1.000,— EUR</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Projektförderung mit einer Gesamtsumme ab 1.000,— EUR</h1>
|
||||||
|
<p>Vielen Dank für dein Interesse an einer Projektförderung!<br>
|
||||||
|
Für Projektförderungen mit einer Gesamtsumme ab 1.000,— EUR ist ein öffentlicher Projektplan
|
||||||
|
erforderlich. Weitere Informationen zu diesem Prozess findest du unter
|
||||||
|
<a href="https://de.wikipedia.org/wiki/Wikipedia:F%C3%B6rderung/Projektplanung" target="_blank" rel="noopener">
|
||||||
|
Wikipedia:Förderung/Projektplanung</a>.<br>
|
||||||
|
Für Fragen steht dir das Team Community-Konferenzen & Förderung gern unter
|
||||||
|
<a href="mailto:community@wikimedia.de">community@wikimedia.de</a> zur Verfügung.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<div class="wm-footer page-centered">
|
||||||
|
<p>
|
||||||
|
<img src="https://upload.wikimedia.org/wikipedia/commons/c/c4/Figuren_klein.jpg" alt=""/>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Eine Übersicht aller Förderangebote von Wikimedia Deutschland findest du im
|
||||||
|
<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Förderangebote">Förderportal in der deutschsprachigen
|
||||||
|
Wikipedia</a>.
|
||||||
|
<br>
|
||||||
|
Für alle Fragen wende dich gern an das
|
||||||
|
<a href="https://de.wikipedia.org/wiki/Wikipedia:Förderung/Wikimedia_Deutschland">Team Communitys und
|
||||||
|
Engagement</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Für interessierte Hacker gibts auch den
|
||||||
|
<a href="https://srcsrv.wikimedia.de/beba/foerderbarometer">Sourcecode</a> zum Formular und was damit passiert.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="https://www.wikimedia.de/impressum/">Impressum</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
{% load static %}
|
||||||
|
<div class="wm-header page-centered">
|
||||||
|
<img src="{% static 'input/logo.png' %}" alt="Wikimedia Deutschland"/>
|
||||||
|
</div>
|
||||||
|
|
@ -1,12 +1,41 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from django.views.generic import TemplateView
|
||||||
from .views import ExternView, index, done, authorize, deny, export
|
from django.views.i18n import JavaScriptCatalog
|
||||||
|
from .views import (
|
||||||
|
index, done, export, authorize, deny,
|
||||||
|
TravelApplicationView, IFGApplicationView, EmailApplicationView,
|
||||||
|
LiteratureApplicationView, ListApplicationView, BusinessCardApplicationView,
|
||||||
|
LibraryApplicationView, ELiteratureApplicationView, SoftwareApplicationView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', index, name='index'),
|
path('', index, name='index'),
|
||||||
path('extern', ExternView.as_view(), name='extern'),
|
path(
|
||||||
|
'extern/',
|
||||||
|
TemplateView.as_view(template_name='input/forms/extern.html'),
|
||||||
|
name='extern',
|
||||||
|
),
|
||||||
path('saved', done, name='done'),
|
path('saved', done, name='done'),
|
||||||
path('export', export, name='export'),
|
path('export', export, name='export'),
|
||||||
path('authorize/<str:choice>/<int:pk>', authorize, name='authorize'),
|
path('authorize/<str:choice>/<int:pk>', authorize, name='authorize'),
|
||||||
path('deny/<str:choice>/<int:pk>', deny, name='deny'),
|
path('deny/<str:choice>/<int:pk>', deny, name='deny'),
|
||||||
|
|
||||||
|
# Static info page for project funding above 1000 EUR
|
||||||
|
path('extern/info/projektfoerderung-ab-1000/',
|
||||||
|
TemplateView.as_view(template_name='input/info_project_funding_gt_1000.html'),
|
||||||
|
name='info-foerderprojekt-ab-1000'),
|
||||||
|
|
||||||
|
# New single-page application views
|
||||||
|
path('extern/reisekosten/', TravelApplicationView.as_view(), name='reisekosten'),
|
||||||
|
path('extern/ifg/', IFGApplicationView.as_view(), name='ifg'),
|
||||||
|
path('extern/email/', EmailApplicationView.as_view(), name='email'),
|
||||||
|
path('extern/literaturstipendium/', LiteratureApplicationView.as_view(), name='literatur'),
|
||||||
|
path('extern/mailingliste/', ListApplicationView.as_view(), name='mailingliste'),
|
||||||
|
path('extern/visitenkarten/', BusinessCardApplicationView.as_view(), name='visitenkarten'),
|
||||||
|
path('extern/bibliotheksstipendium/', LibraryApplicationView.as_view(), name='bibliotheksstipendium'),
|
||||||
|
path('extern/eliteraturstipendium/', ELiteratureApplicationView.as_view(), name='eliteraturstipendium'),
|
||||||
|
path('extern/softwarestipendium/', SoftwareApplicationView.as_view(), name='softwarestipendium'),
|
||||||
|
|
||||||
|
# JavaScript translations for date widgets, etc.
|
||||||
|
path('jsi18n/', JavaScriptCatalog.as_view(), name='jsi18n'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
266
input/views.py
266
input/views.py
|
|
@ -8,6 +8,7 @@ from django.core.mail import BadHeaderError, EmailMultiAlternatives
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.views.generic.edit import FormView
|
||||||
|
|
||||||
from .forms import (
|
from .forms import (
|
||||||
ExternForm,
|
ExternForm,
|
||||||
|
|
@ -29,6 +30,30 @@ LIBRARY_FORMS = {
|
||||||
TYPE_SOFT: SoftwareForm,
|
TYPE_SOFT: SoftwareForm,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HELP_TEXTS = {
|
||||||
|
'IFG': {
|
||||||
|
'notes': (
|
||||||
|
'Bitte gib an, wie die gewonnenen Informationen den<br>'
|
||||||
|
'Wikimedia-Projekten zugute kommen sollen.'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
'MAIL': {
|
||||||
|
'domain': (
|
||||||
|
'Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>'
|
||||||
|
'möchtest du eine Mailadresse beantragen?'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
'LIT': {
|
||||||
|
'notes': 'Bitte gib an, wofür du die Literatur verwenden möchtest.'
|
||||||
|
},
|
||||||
|
'LIST': {
|
||||||
|
'domain': (
|
||||||
|
'Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>'
|
||||||
|
'möchtest du eine Mailingliste beantragen?'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def auth_deny(choice, pk, auth):
|
def auth_deny(choice, pk, auth):
|
||||||
if choice not in MODELS:
|
if choice not in MODELS:
|
||||||
|
|
@ -51,7 +76,7 @@ def authorize(request, choice, pk):
|
||||||
if ret := auth_deny(choice, pk, True):
|
if ret := auth_deny(choice, pk, True):
|
||||||
return ret
|
return ret
|
||||||
else:
|
else:
|
||||||
return HttpResponse(f"AUTHORIZED! choice: {choice}, pk: {pk}")
|
return HttpResponse(f'AUTHORIZED! choice: {choice}, pk: {pk}')
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
|
@ -62,143 +87,118 @@ def deny(request, choice, pk):
|
||||||
if ret := auth_deny(choice, pk, False):
|
if ret := auth_deny(choice, pk, False):
|
||||||
return ret
|
return ret
|
||||||
else:
|
else:
|
||||||
return HttpResponse(f"DENIED! choice: {choice}, pk: {pk}")
|
return HttpResponse(f'DENIED! choice: {choice}, pk: {pk}')
|
||||||
|
|
||||||
|
|
||||||
def done(request):
|
def done(request):
|
||||||
return HttpResponse("Deine Anfrage wurde gesendet. Du erhältst in Kürze eine E-Mail-Benachrichtigung mit deinen Angaben. Für alle Fragen kontaktiere bitte das Team Communitys und Engagement unter community@wikimedia.de.")
|
return HttpResponse(
|
||||||
|
'Deine Anfrage wurde gesendet. Du erhältst in Kürze eine E-Mail-Benachrichtigung mit deinen Angaben. Für alle Fragen kontaktiere bitte das Team Communitys und Engagement unter community@wikimedia.de.')
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
return render(request, 'input/index.html')
|
return render(request, 'input/index.html')
|
||||||
|
|
||||||
|
|
||||||
class ExternView(CookieWizardView):
|
class BaseApplicationView(FormView):
|
||||||
'''This View is for Volunteers'''
|
"""
|
||||||
|
Base view for all application types.
|
||||||
|
|
||||||
template_name = "input/extern.html"
|
- Each application type (travel, literature, email, etc.) gets its own subclass.
|
||||||
form_list = [ExternForm, LibraryForm]
|
- Renders the generic form template.
|
||||||
|
- Handles saving the submitted form to the database.
|
||||||
def get_form(self, step=None, data=None, files=None):
|
- Adds extra fields from the session or request type if needed.
|
||||||
'''this function determines which part of the multipart form is
|
- Applies optional help_text overrides for certain fields.
|
||||||
displayed next'''
|
- Sends confirmation mail to the applicant.
|
||||||
|
- Sends notification mail to the internal IF address.
|
||||||
if step is None:
|
- Returns the "done" response after successful processing.
|
||||||
step = self.steps.current
|
"""
|
||||||
print ("get_form() step " + step)
|
template_name = 'input/forms/form_generic.html'
|
||||||
|
type_code: str = ''
|
||||||
if step == '1':
|
|
||||||
prev_data = self.get_cleaned_data_for_step('0')
|
|
||||||
choice = prev_data.get('choice')
|
|
||||||
print(f'choice detection in ExternView: {TYPE_CHOICES[choice]}')
|
|
||||||
if choice == 'IFG':
|
|
||||||
form = IFGForm(data)
|
|
||||||
form.fields['notes'].help_text = mark_safe("Bitte gib an, wie die gewonnenen Informationen den<br>Wikimedia-Projekten zugute kommen sollen.")
|
|
||||||
elif choice in LIBRARY_FORMS:
|
|
||||||
form = LIBRARY_FORMS[choice](data)
|
|
||||||
elif choice == 'MAIL':
|
|
||||||
form = EmailForm(data)
|
|
||||||
form.fields['domain'].help_text = mark_safe("Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>möchtest du eine Mailadresse beantragen?")
|
|
||||||
elif choice == 'LIT':
|
|
||||||
form = LiteratureForm(data)
|
|
||||||
form.fields['notes'].help_text = "Bitte gib an, wofür du die Literatur verwenden möchtest."
|
|
||||||
elif choice == 'VIS':
|
|
||||||
form = BusinessCardForm(data)
|
|
||||||
elif choice == 'LIST':
|
|
||||||
form = ListForm(data)
|
|
||||||
form.fields['domain'].help_text = mark_safe("Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>möchtest du eine Mailingliste beantragen?")
|
|
||||||
elif choice == 'TRAV':
|
|
||||||
form = TravelForm(data)
|
|
||||||
else: # pragma: no cover
|
|
||||||
raise RuntimeError(f'ERROR! UNKNOWN FORMTYPE {choice} in ExternView')
|
|
||||||
self.choice = choice
|
|
||||||
else:
|
|
||||||
form = super().get_form(step, data, files)
|
|
||||||
return form
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
"""Add the human-readable type string (from TYPE_CHOICES) to the template context."""
|
||||||
if hasattr(self, 'choice'):
|
ctx = super().get_context_data(**kwargs)
|
||||||
context["choice"] = TYPE_CHOICES[self.choice]
|
ctx['typestring'] = TYPE_CHOICES.get(self.type_code, self.type_code)
|
||||||
return context
|
return ctx
|
||||||
|
|
||||||
def done(self, form_list, **kwargs):
|
def get_form(self, form_class=None):
|
||||||
print('ExternView.done() reached')
|
"""Return the form instance and inject custom help_texts if defined for this type."""
|
||||||
# gather data from all forms
|
form = super().get_form(form_class)
|
||||||
data = {}
|
|
||||||
for form in form_list:
|
|
||||||
data = {**data, **form.cleaned_data}
|
|
||||||
|
|
||||||
if data['choice'] == 'LIT':
|
# Apply help_text overrides if defined for this type_code
|
||||||
if data['selfbuy'] == 'TRUE':
|
if self.type_code in HELP_TEXTS:
|
||||||
|
for field, text in HELP_TEXTS[self.type_code].items():
|
||||||
|
if field in form.fields:
|
||||||
|
form.fields[field].help_text = mark_safe(text)
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
"""
|
||||||
|
Process a valid form submission:
|
||||||
|
- Enrich form data (e.g., set type_code, handle special rules).
|
||||||
|
- Save the model instance and related data.
|
||||||
|
- Send confirmation and notification mails.
|
||||||
|
- Return the "done" response.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Collect cleaned data and mark the current type
|
||||||
|
data = form.cleaned_data.copy()
|
||||||
|
data['choice'] = self.type_code
|
||||||
|
|
||||||
|
# Special rule for literature applications
|
||||||
|
if self.type_code == 'LIT' and data.get('selfbuy') == 'TRUE':
|
||||||
data['selfbuy_give_data'] = 'False'
|
data['selfbuy_give_data'] = 'False'
|
||||||
|
|
||||||
# write data to database
|
# Save model instance
|
||||||
modell = form.save(commit=False)
|
modell = form.save(commit=False)
|
||||||
# we have to copy the data from the first form here
|
|
||||||
# this is a bit ugly code. can we copy this without explicit writing?
|
|
||||||
|
|
||||||
if data['choice'] == 'LIT':
|
# Username from session if present
|
||||||
|
if user := self.request.session.get('user'):
|
||||||
|
modell.username = user.get('username')
|
||||||
|
|
||||||
|
# Copy common fields if provided by the form
|
||||||
|
if 'realname' in data:
|
||||||
|
modell.realname = data['realname']
|
||||||
|
if 'email' in data:
|
||||||
|
modell.email = data['email']
|
||||||
|
|
||||||
|
# Set model.type for specific request types
|
||||||
|
if self.type_code in ('BIB', 'ELIT', 'SOFT'):
|
||||||
|
modell.type = self.type_code
|
||||||
|
|
||||||
|
# Literature-specific extra field
|
||||||
|
if self.type_code == 'LIT' and 'selfbuy_give_data' in data:
|
||||||
modell.selfbuy_give_data = data['selfbuy_give_data']
|
modell.selfbuy_give_data = data['selfbuy_give_data']
|
||||||
|
|
||||||
if user := self.request.session.get('user'):
|
modell.save()
|
||||||
modell.username = user['username']
|
if hasattr(form, 'save_m2m'):
|
||||||
|
form.save_m2m()
|
||||||
|
|
||||||
modell.realname = data['realname']
|
# Prepare minimal mail context and send mails
|
||||||
modell.email = data['email']
|
|
||||||
# write type of form in some cases
|
|
||||||
if data['choice'] in ('BIB', 'ELIT', 'SOFT'):
|
|
||||||
modell.type = data['choice']
|
|
||||||
|
|
||||||
form.save()
|
|
||||||
|
|
||||||
# add some data to context for mail templates
|
|
||||||
data['pk'] = modell.pk
|
data['pk'] = modell.pk
|
||||||
data['url_prefix'] = settings.EMAIL_URL_PREFIX
|
data['url_prefix'] = settings.EMAIL_URL_PREFIX
|
||||||
data['grant'] = ('LIT', 'SOFT', 'ELIT', 'BIB', 'IFG')
|
data['typestring'] = TYPE_CHOICES.get(self.type_code, self.type_code)
|
||||||
data['DOMAIN'] = ('MAIL', 'LIST')
|
|
||||||
data['typestring'] = TYPE_CHOICES[data['choice']]
|
|
||||||
|
|
||||||
# we need to send the following mails here:
|
|
||||||
context = {'data': data}
|
context = {'data': data}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# - mail with entered data to the Volunteer
|
# Mail to applicant
|
||||||
|
txt1 = get_template('input/ifg_volunteer_mail.txt').render(context)
|
||||||
txt_mail_template1 = get_template('input/ifg_volunteer_mail.txt')
|
html1 = get_template('input/ifg_volunteer_mail.html').render(context)
|
||||||
html_mail_template1 = get_template('input/ifg_volunteer_mail.html')
|
msg1 = EmailMultiAlternatives(
|
||||||
|
'Formular ausgefüllt', txt1, settings.IF_EMAIL, [data['email']]
|
||||||
subject1, from_email1, to1 = 'Formular ausgefüllt', settings.IF_EMAIL, data['email']
|
)
|
||||||
text_content1 = txt_mail_template1.render(context)
|
msg1.attach_alternative(html1, 'text/html')
|
||||||
html_content1 = html_mail_template1.render(context)
|
|
||||||
msg1 = EmailMultiAlternatives(subject1, text_content1, from_email1, [to1])
|
|
||||||
msg1.attach_alternative(html_content1, "text/html")
|
|
||||||
msg1.send()
|
msg1.send()
|
||||||
#print('ifg volunteer mail would have been sent')
|
|
||||||
#send_mail(
|
|
||||||
# 'Formular ausgefüllt',
|
|
||||||
# txt_mail_template1.render(context),
|
|
||||||
# IF_EMAIL,
|
|
||||||
# [data['email']],
|
|
||||||
# fail_silently=False)
|
|
||||||
## - mail to IF with link to accept/decline
|
|
||||||
|
|
||||||
txt_mail_template = get_template('input/if_mail.txt')
|
# Mail to IF
|
||||||
html_mail_template = get_template('input/if_mail.html')
|
txt2 = get_template('input/if_mail.txt').render(context)
|
||||||
|
html2 = get_template('input/if_mail.html').render(context)
|
||||||
subject, from_email, to = 'Formular ausgefüllt', settings.IF_EMAIL, settings.IF_EMAIL
|
msg2 = EmailMultiAlternatives(
|
||||||
text_content = txt_mail_template.render(context)
|
'Formular ausgefüllt', txt2, settings.IF_EMAIL, [settings.IF_EMAIL]
|
||||||
html_content = html_mail_template.render(context)
|
)
|
||||||
msg2 = EmailMultiAlternatives(subject, text_content, from_email, [to])
|
msg2.attach_alternative(html2, 'text/html')
|
||||||
msg2.attach_alternative(html_content, "text/html")
|
|
||||||
msg2.send()
|
msg2.send()
|
||||||
#print('if mail would have been sent')
|
|
||||||
#send_mail(
|
|
||||||
# 'Formular ausgefüllt',
|
|
||||||
# txt_mail_template.render(context),
|
|
||||||
# IF_EMAIL,
|
|
||||||
# [IF_EMAIL],
|
|
||||||
# fail_silently=False)
|
|
||||||
## raise SMTPException("testing pupose only")
|
|
||||||
|
|
||||||
except BadHeaderError:
|
except BadHeaderError:
|
||||||
modell.delete()
|
modell.delete()
|
||||||
|
|
@ -207,5 +207,49 @@ class ExternView(CookieWizardView):
|
||||||
modell.delete()
|
modell.delete()
|
||||||
return HttpResponse('Error in sending mails (probably wrong adress?). Data not saved!')
|
return HttpResponse('Error in sending mails (probably wrong adress?). Data not saved!')
|
||||||
|
|
||||||
|
|
||||||
return done(self.request)
|
return done(self.request)
|
||||||
|
|
||||||
|
|
||||||
|
class TravelApplicationView(BaseApplicationView):
|
||||||
|
form_class = TravelForm
|
||||||
|
type_code = 'TRAV'
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryApplicationView(BaseApplicationView):
|
||||||
|
form_class = LibraryForm
|
||||||
|
type_code = 'BIB'
|
||||||
|
|
||||||
|
|
||||||
|
class ELiteratureApplicationView(BaseApplicationView):
|
||||||
|
form_class = ELiteratureForm
|
||||||
|
type_code = 'ELIT'
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareApplicationView(BaseApplicationView):
|
||||||
|
form_class = SoftwareForm
|
||||||
|
type_code = 'SOFT'
|
||||||
|
|
||||||
|
|
||||||
|
class IFGApplicationView(BaseApplicationView):
|
||||||
|
form_class = IFGForm
|
||||||
|
type_code = 'IFG'
|
||||||
|
|
||||||
|
|
||||||
|
class EmailApplicationView(BaseApplicationView):
|
||||||
|
form_class = EmailForm
|
||||||
|
type_code = 'MAIL'
|
||||||
|
|
||||||
|
|
||||||
|
class LiteratureApplicationView(BaseApplicationView):
|
||||||
|
form_class = LiteratureForm
|
||||||
|
type_code = 'LIT'
|
||||||
|
|
||||||
|
|
||||||
|
class ListApplicationView(BaseApplicationView):
|
||||||
|
form_class = ListForm
|
||||||
|
type_code = 'LIST'
|
||||||
|
|
||||||
|
|
||||||
|
class BusinessCardApplicationView(BaseApplicationView):
|
||||||
|
form_class = BusinessCardForm
|
||||||
|
type_code = 'VIS'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue