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>
<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>
{% for info in types %}
<li><a href="{% url 'extern' type=info.path %}">{{ info.label|striptags }}</a></li>
{% endfor %}
</ul>
</td>
</tr>

View File

@ -5,7 +5,7 @@
{{ form.media }}
<div class="page-centered">
<p>Du hast {{ typestring }} ausgewählt.</p>
<p>Du hast {{ type_label }} ausgewählt.</p>
</div>
<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>
es gab einen neuen Antrag von {{data.realname}}.
<br><br>
Der Nutzer mit dem Username {{data.username}} ({{data.email}}) fragt ein_e {{data.typestring|striptags}} an.<br>
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>
Vorraussichtliche Kosten: {{data.cost}}<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}}.
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 %}
Vorraussichtliche Kosten: {{data.cost}}
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}

View File

@ -2,7 +2,7 @@
<body>
Hallo {{data.realname}},
<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>
Vorraussichtliche Kosten: {{data.cost}}<br>
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}<br>

View File

@ -1,6 +1,6 @@
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 %}
Vorraussichtliche Kosten: {{data.cost}}
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}

View File

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

View File

@ -1,17 +1,19 @@
from smtplib import SMTPException
from typing import NamedTuple
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 formtools.wizard.views import CookieWizardView
from django.core.mail import BadHeaderError, EmailMultiAlternatives
from django.template.loader import get_template
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.views.generic import TemplateView
from django.views.generic.edit import FormView
from .forms import (
ExternForm,
BaseApplicationForm,
LibraryForm,
ELiteratureForm,
SoftwareForm,
@ -22,31 +24,38 @@ from .forms import (
ListForm,
BusinessCardForm,
)
from .models import TYPE_CHOICES, MODELS, TYPE_BIB, TYPE_ELIT, TYPE_SOFT
LIBRARY_FORMS = {
TYPE_BIB: LibraryForm,
TYPE_ELIT: ELiteratureForm,
TYPE_SOFT: SoftwareForm,
}
from .models import (
MODELS,
LIBRARY_TYPES,
TYPE_CHOICES,
TYPE_BIB,
TYPE_ELIT,
TYPE_IFG,
TYPE_LIT,
TYPE_LIST,
TYPE_MAIL,
TYPE_SOFT,
TYPE_TRAV,
TYPE_VIS,
)
HELP_TEXTS = {
'IFG': {
TYPE_IFG: {
'notes': (
'Bitte gib an, wie die gewonnenen Informationen den<br>'
'Wikimedia-Projekten zugute kommen sollen.'
)
},
'MAIL': {
TYPE_MAIL: {
'domain': (
'Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>'
'möchtest du eine Mailadresse beantragen?'
)
},
'LIT': {
TYPE_LIT: {
'notes': 'Bitte gib an, wofür du die Literatur verwenden möchtest.'
},
'LIST': {
TYPE_LIST: {
'domain': (
'Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>'
'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):
if choice not in MODELS:
return HttpResponse(f'ERROR! UNKNOWN CHOICE TYPE! {choice}')
@ -99,11 +139,19 @@ def index(request):
return render(request, 'input/index.html')
class BaseApplicationView(FormView):
"""
Base view for all application types.
class ApplicationStartView(TemplateView):
template_name = 'input/forms/extern.html'
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.
- Handles saving the submitted form to the database.
- 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.
- Returns the "done" response after successful processing.
"""
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):
"""Add the human-readable type string (from TYPE_CHOICES) to the template context."""
ctx = super().get_context_data(**kwargs)
ctx['typestring'] = TYPE_CHOICES.get(self.type_code, self.type_code)
return ctx
return super().get_context_data(**kwargs, type_label=self.type_info.label)
def get_form(self, form_class=None):
"""Return the form instance and inject custom help_texts if defined for this type."""
form = super().get_form(form_class)
# Apply help_text overrides if defined for this type_code
if self.type_code in HELP_TEXTS:
for field, text in HELP_TEXTS[self.type_code].items():
if help_texts := self.type_info.help_texts:
for field, text in help_texts.items():
if field in form.fields:
form.fields[field].help_text = mark_safe(text)
@ -147,7 +210,7 @@ class BaseApplicationView(FormView):
data['choice'] = self.type_code
# 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'
# Save model instance
@ -164,11 +227,11 @@ class BaseApplicationView(FormView):
modell.email = data['email']
# 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
# 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.save()
@ -178,7 +241,7 @@ class BaseApplicationView(FormView):
# Prepare minimal mail context and send mails
data['pk'] = modell.pk
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}
try:
@ -208,48 +271,3 @@ class BaseApplicationView(FormView):
return HttpResponse('Error in sending mails (probably wrong adress?). Data not saved!')
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
Django==5.2.5
django-formtools==2.5.1
gunicorn==23.0.0
mysqlclient==2.2.7
python-dotenv==1.1.1