simplified application views

This commit is contained in:
Oliver Zander 2025-10-14 11:39:58 +02:00
parent 84ef809705
commit a7d3df7b39
9 changed files with 115 additions and 117 deletions

View File

@ -21,15 +21,9 @@
<strong>Serviceleistungen</strong> <strong>Serviceleistungen</strong>
<ul> <ul>
<li><a href="{% url 'bibliotheksstipendium' %}">Bibliotheksstipendium</a></li> {% for info in types %}
<li><a href="{% url 'eliteraturstipendium' %}">eLiteraturstipendium</a></li> <li><a href="{% url 'extern' type=info.path %}">{{ info.label|striptags }}</a></li>
<li><a href="{% url 'email' %}">E-Mail-Adresse</a></li> {% endfor %}
<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> </ul>
</td> </td>
</tr> </tr>

View File

@ -5,7 +5,7 @@
{{ form.media }} {{ form.media }}
<div class="page-centered"> <div class="page-centered">
<p>Du hast {{ typestring }} ausgewählt.</p> <p>Du hast {{ type_label }} ausgewählt.</p>
</div> </div>
<form method="post" class="wm-form" {% if form.is_multipart %}enctype="multipart/form-data"{% endif %}> <form method="post" class="wm-form" {% if form.is_multipart %}enctype="multipart/form-data"{% endif %}>

View File

@ -4,7 +4,7 @@ Hallo Team Communitys und Engagement,
<br><br> <br><br>
es gab einen neuen Antrag von {{data.realname}}. es gab einen neuen Antrag von {{data.realname}}.
<br><br> <br><br>
Der Nutzer mit dem Username {{data.username}} ({{data.email}}) fragt ein_e {{data.typestring|striptags}} an.<br> Der Nutzer mit dem Username {{data.username}} ({{data.email}}) fragt ein_e {{data.type_label|striptags}} an.<br>
{% if data.choice in data.grant %}<br> {% if data.choice in data.grant %}<br>
Vorraussichtliche Kosten: {{data.cost}}<br> Vorraussichtliche Kosten: {{data.cost}}<br>
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}<br> Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}<br>

View File

@ -2,7 +2,7 @@ Hallo Team Communitys und Engagement,
es gab einen neuen Antrag von {{data.realname}}. es gab einen neuen Antrag von {{data.realname}}.
Der Nutzer mit dem Username {{data.username}} ({{data.email}}) fragt ein_e {{data.typestring|striptags}} an. Der Nutzer mit dem Username {{data.username}} ({{data.email}}) fragt ein_e {{data.type_label|striptags}} an.
{% if data.choice in data.grant %} {% if data.choice in data.grant %}
Vorraussichtliche Kosten: {{data.cost}} Vorraussichtliche Kosten: {{data.cost}}
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %} Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}

View File

@ -2,7 +2,7 @@
<body> <body>
Hallo {{data.realname}}, Hallo {{data.realname}},
<br><br> <br><br>
wir haben Deine Anfrage ({{data.typestring|striptags}}) erhalten.<br> wir haben Deine Anfrage ({{data.type_label|striptags}}) erhalten.<br>
{% if data.choice in data.grant %}<br> {% if data.choice in data.grant %}<br>
Vorraussichtliche Kosten: {{data.cost}}<br> Vorraussichtliche Kosten: {{data.cost}}<br>
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}<br> Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}<br>

View File

@ -1,6 +1,6 @@
Hallo {{data.realname}}, Hallo {{data.realname}},
wir haben Deine Anfrage ({{data.typestring|striptags}}) erhalten. wir haben Deine Anfrage ({{data.type_label|striptags}}) erhalten.
{% if data.choice in data.grant %} {% if data.choice in data.grant %}
Vorraussichtliche Kosten: {{data.cost}} Vorraussichtliche Kosten: {{data.cost}}
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %} Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}

View File

@ -1,41 +1,28 @@
from django.urls import path from django.urls import path, include
from django.views.generic import TemplateView
from django.views.i18n import JavaScriptCatalog from django.views.i18n import JavaScriptCatalog
from .views import ( from .views import (
index, done, export, authorize, deny, index,
TravelApplicationView, IFGApplicationView, EmailApplicationView, done,
LiteratureApplicationView, ListApplicationView, BusinessCardApplicationView, export,
LibraryApplicationView, ELiteratureApplicationView, SoftwareApplicationView, authorize,
deny,
ApplicationView,
ApplicationStartView,
ProjectInfoView,
) )
urlpatterns = [ urlpatterns = [
path('', index, name='index'), path('', index, name='index'),
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'),
path('extern/', include([
# Static info page for project funding above 1000 EUR path('', ApplicationStartView.as_view(), name='extern'),
path('extern/info/projektfoerderung-ab-1000/', path('info/projektfoerderung-ab-1000/', ProjectInfoView.as_view(), name='info-foerderprojekt-ab-1000'),
TemplateView.as_view(template_name='input/info_project_funding_gt_1000.html'), path('<slug:type>/', ApplicationView.as_view(), name='extern'),
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. # JavaScript translations for date widgets, etc.
path('jsi18n/', JavaScriptCatalog.as_view(), name='jsi18n'), path('jsi18n/', JavaScriptCatalog.as_view(), name='jsi18n'),
] ]

View File

@ -1,17 +1,19 @@
from smtplib import SMTPException from smtplib import SMTPException
from typing import NamedTuple
from django.shortcuts import render from django.shortcuts import render
from django.http import HttpResponse from django.http import HttpResponse, Http404
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from formtools.wizard.views import CookieWizardView
from django.core.mail import BadHeaderError, EmailMultiAlternatives 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 import TemplateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from .forms import ( from .forms import (
ExternForm, BaseApplicationForm,
LibraryForm, LibraryForm,
ELiteratureForm, ELiteratureForm,
SoftwareForm, SoftwareForm,
@ -22,31 +24,38 @@ from .forms import (
ListForm, ListForm,
BusinessCardForm, BusinessCardForm,
) )
from .models import TYPE_CHOICES, MODELS, TYPE_BIB, TYPE_ELIT, TYPE_SOFT from .models import (
MODELS,
LIBRARY_FORMS = { LIBRARY_TYPES,
TYPE_BIB: LibraryForm, TYPE_CHOICES,
TYPE_ELIT: ELiteratureForm, TYPE_BIB,
TYPE_SOFT: SoftwareForm, TYPE_ELIT,
} TYPE_IFG,
TYPE_LIT,
TYPE_LIST,
TYPE_MAIL,
TYPE_SOFT,
TYPE_TRAV,
TYPE_VIS,
)
HELP_TEXTS = { HELP_TEXTS = {
'IFG': { TYPE_IFG: {
'notes': ( 'notes': (
'Bitte gib an, wie die gewonnenen Informationen den<br>' 'Bitte gib an, wie die gewonnenen Informationen den<br>'
'Wikimedia-Projekten zugute kommen sollen.' 'Wikimedia-Projekten zugute kommen sollen.'
) )
}, },
'MAIL': { TYPE_MAIL: {
'domain': ( 'domain': (
'Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>' 'Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>'
'möchtest du eine Mailadresse beantragen?' 'möchtest du eine Mailadresse beantragen?'
) )
}, },
'LIT': { TYPE_LIT: {
'notes': 'Bitte gib an, wofür du die Literatur verwenden möchtest.' 'notes': 'Bitte gib an, wofür du die Literatur verwenden möchtest.'
}, },
'LIST': { TYPE_LIST: {
'domain': ( 'domain': (
'Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>' 'Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>'
'möchtest du eine Mailingliste beantragen?' 'möchtest du eine Mailingliste beantragen?'
@ -55,6 +64,37 @@ HELP_TEXTS = {
} }
class ApplicationType(NamedTuple):
code: str
path: str
form_class: type[BaseApplicationForm]
@property
def label(self):
return TYPE_CHOICES[self.code]
@property
def model(self):
return MODELS[self.code]
@property
def help_texts(self):
return HELP_TEXTS.get(self.code)
TYPES = [
ApplicationType(TYPE_BIB, 'bibliotheksstipendium', LibraryForm),
ApplicationType(TYPE_ELIT, 'eliteraturstipendium', ELiteratureForm),
ApplicationType(TYPE_MAIL, 'email', EmailForm),
ApplicationType(TYPE_IFG, 'ifg', IFGForm),
ApplicationType(TYPE_LIT, 'literaturstipendium', LiteratureForm),
ApplicationType(TYPE_LIST, 'mailingliste', ListForm),
ApplicationType(TYPE_TRAV, 'reisekosten', TravelForm),
ApplicationType(TYPE_SOFT, 'softwarestipendium', SoftwareForm),
ApplicationType(TYPE_VIS, 'visitenkarten', BusinessCardForm),
]
def auth_deny(choice, pk, auth): def auth_deny(choice, pk, auth):
if choice not in MODELS: if choice not in MODELS:
return HttpResponse(f'ERROR! UNKNOWN CHOICE TYPE! {choice}') return HttpResponse(f'ERROR! UNKNOWN CHOICE TYPE! {choice}')
@ -99,11 +139,19 @@ def index(request):
return render(request, 'input/index.html') return render(request, 'input/index.html')
class BaseApplicationView(FormView): class ApplicationStartView(TemplateView):
""" template_name = 'input/forms/extern.html'
Base view for all application types. extra_context = {'types': TYPES}
class ProjectInfoView(TemplateView):
template_name = 'input/info_project_funding_gt_1000.html'
class ApplicationView(FormView):
"""
View for all application types.
- Each application type (travel, literature, email, etc.) gets its own subclass.
- Renders the generic form template. - Renders the generic form template.
- Handles saving the submitted form to the database. - Handles saving the submitted form to the database.
- Adds extra fields from the session or request type if needed. - Adds extra fields from the session or request type if needed.
@ -112,22 +160,37 @@ class BaseApplicationView(FormView):
- Sends notification mail to the internal IF address. - Sends notification mail to the internal IF address.
- Returns the "done" response after successful processing. - Returns the "done" response after successful processing.
""" """
template_name = 'input/forms/form_generic.html' template_name = 'input/forms/form_generic.html'
type_code: str = ''
@cached_property
def type_info(self) -> ApplicationType:
type_path = self.kwargs['type']
for type_info in TYPES:
if type_path == type_info.path:
return type_info
raise Http404(f'"{type_path}" existiert nicht.')
@property
def type_code(self):
return self.type_info.code
@property
def form_class(self):
return self.type_info.form_class
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Add the human-readable type string (from TYPE_CHOICES) to the template context.""" return super().get_context_data(**kwargs, type_label=self.type_info.label)
ctx = super().get_context_data(**kwargs)
ctx['typestring'] = TYPE_CHOICES.get(self.type_code, self.type_code)
return ctx
def get_form(self, form_class=None): def get_form(self, form_class=None):
"""Return the form instance and inject custom help_texts if defined for this type.""" """Return the form instance and inject custom help_texts if defined for this type."""
form = super().get_form(form_class) form = super().get_form(form_class)
# Apply help_text overrides if defined for this type_code # Apply help_text overrides if defined for this type_code
if self.type_code in HELP_TEXTS: if help_texts := self.type_info.help_texts:
for field, text in HELP_TEXTS[self.type_code].items(): for field, text in help_texts.items():
if field in form.fields: if field in form.fields:
form.fields[field].help_text = mark_safe(text) form.fields[field].help_text = mark_safe(text)
@ -147,7 +210,7 @@ class BaseApplicationView(FormView):
data['choice'] = self.type_code data['choice'] = self.type_code
# Special rule for literature applications # Special rule for literature applications
if self.type_code == 'LIT' and data.get('selfbuy') == 'TRUE': if self.type_code == TYPE_LIT and data.get('selfbuy') == 'TRUE':
data['selfbuy_give_data'] = 'False' data['selfbuy_give_data'] = 'False'
# Save model instance # Save model instance
@ -164,11 +227,11 @@ class BaseApplicationView(FormView):
modell.email = data['email'] modell.email = data['email']
# Set model.type for specific request types # Set model.type for specific request types
if self.type_code in ('BIB', 'ELIT', 'SOFT'): if self.type_code in LIBRARY_TYPES:
modell.type = self.type_code modell.type = self.type_code
# Literature-specific extra field # Literature-specific extra field
if self.type_code == 'LIT' and 'selfbuy_give_data' in data: if self.type_code == TYPE_LIT and 'selfbuy_give_data' in data:
modell.selfbuy_give_data = data['selfbuy_give_data'] modell.selfbuy_give_data = data['selfbuy_give_data']
modell.save() modell.save()
@ -178,7 +241,7 @@ class BaseApplicationView(FormView):
# Prepare minimal mail context and send mails # Prepare minimal mail context and send mails
data['pk'] = modell.pk data['pk'] = modell.pk
data['url_prefix'] = settings.EMAIL_URL_PREFIX data['url_prefix'] = settings.EMAIL_URL_PREFIX
data['typestring'] = TYPE_CHOICES.get(self.type_code, self.type_code) data['type_label'] = self.type_info.label
context = {'data': data} context = {'data': data}
try: try:
@ -208,48 +271,3 @@ class BaseApplicationView(FormView):
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'

View File

@ -1,6 +1,5 @@
Authlib==1.6.1 Authlib==1.6.1
Django==5.2.5 Django==5.2.5
django-formtools==2.5.1
gunicorn==23.0.0 gunicorn==23.0.0
mysqlclient==2.2.7 mysqlclient==2.2.7
python-dotenv==1.1.1 python-dotenv==1.1.1