foerderbarometer/input/views.py

305 lines
9.0 KiB
Python
Raw Normal View History

from smtplib import SMTPException
2025-10-14 09:39:58 +00:00
from typing import NamedTuple
2020-09-21 12:27:16 +00:00
from django.shortcuts import render
2025-10-14 09:39:58 +00:00
from django.http import HttpResponse, Http404
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe
2025-10-17 12:51:18 +00:00
from django.core.mail import BadHeaderError
from django.conf import settings
from django.contrib.auth.decorators import login_required
2025-10-14 09:39:58 +00:00
from django.views.generic import TemplateView
from django.views.generic.edit import FormView
from django.utils.html import strip_tags
2025-10-17 10:06:23 +00:00
2025-10-17 12:51:18 +00:00
from input.utils.mail import collect_attachment_paths, attach_files, build_email
2020-09-22 10:21:05 +00:00
from .forms import (
2025-10-14 09:39:58 +00:00
BaseApplicationForm,
ProjectForm,
LibraryForm,
ELiteratureForm,
SoftwareForm,
IFGForm,
LiteratureForm,
TravelForm,
EmailForm,
ListForm,
BusinessCardForm,
)
2025-10-14 09:39:58 +00:00
from .models import (
MODELS,
LIBRARY_TYPES,
TYPE_CHOICES,
TYPE_BIB,
TYPE_ELIT,
TYPE_IFG,
TYPE_LIT,
TYPE_LIST,
TYPE_MAIL,
TYPE_PROJ,
2025-10-14 09:39:58 +00:00
TYPE_SOFT,
TYPE_TRAV,
TYPE_VIS,
)
HELP_TEXTS = {
2025-10-14 09:39:58 +00:00
TYPE_IFG: {
'notes': (
'Bitte gib an, wie die gewonnenen Informationen den<br>'
'Wikimedia-Projekten zugute kommen sollen.'
)
},
2025-10-14 09:39:58 +00:00
TYPE_MAIL: {
'domain': (
'Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>'
'möchtest du eine Mailadresse beantragen?'
)
},
2025-10-14 09:39:58 +00:00
TYPE_LIT: {
'notes': 'Bitte gib an, wofür du die Literatur verwenden möchtest.'
},
2025-10-14 09:39:58 +00:00
TYPE_LIST: {
'domain': (
'Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br>'
'möchtest du eine Mailingliste beantragen?'
)
},
}
2025-10-14 09:39:58 +00:00
class ApplicationType(NamedTuple):
code: str
path: str
form_class: type[BaseApplicationForm]
@property
def label(self):
return TYPE_CHOICES[self.code]
@property
def help_texts(self):
return HELP_TEXTS.get(self.code)
PROJECT_FUNDING = [
ApplicationType(TYPE_PROJ, 'projektfoerderung', ProjectForm),
]
SERVICES = [
2025-10-14 09:39:58 +00:00
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),
]
TYPES = {info.path: info for info in PROJECT_FUNDING + SERVICES}
2025-10-14 09:39:58 +00:00
def auth_deny(choice, pk, auth):
if choice not in MODELS:
return HttpResponse(f'ERROR! UNKNOWN CHOICE TYPE! {choice}')
MODELS[choice].set_granted(pk, auth)
2025-08-21 08:08:38 +00:00
2020-11-19 14:55:10 +00:00
@login_required
def export(request):
'''export the project database to a csv'''
return HttpResponse('WE WANT CSV!')
2021-01-04 09:44:03 +00:00
2025-08-21 08:08:38 +00:00
@login_required
def authorize(request, choice, pk):
2020-10-21 07:54:12 +00:00
'''If IF grant a support they click a link in a mail which leads here.
We write the granted field in the database here and set a timestamp.'''
2025-08-21 08:42:55 +00:00
if ret := auth_deny(choice, pk, True):
return ret
else:
return HttpResponse(f'AUTHORIZED! choice: {choice}, pk: {pk}')
2025-08-21 08:08:38 +00:00
@login_required
def deny(request, choice, pk):
2020-10-21 07:54:12 +00:00
'''If IF denies a support they click a link in a mail which leads here
We write the granted field in the database here.'''
2025-08-21 08:42:55 +00:00
if ret := auth_deny(choice, pk, False):
return ret
else:
return HttpResponse(f'DENIED! choice: {choice}, pk: {pk}')
2020-09-29 09:08:16 +00:00
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.')
2020-09-30 12:26:08 +00:00
2025-08-21 08:02:19 +00:00
def index(request):
return render(request, 'input/index.html')
2020-11-18 15:03:27 +00:00
2025-10-14 09:39:58 +00:00
class ApplicationStartView(TemplateView):
template_name = 'input/forms/extern.html'
extra_context = {'services': SERVICES}
2025-10-14 09:39:58 +00:00
class ProjectFundingInfoView(TemplateView):
2025-10-14 09:39:58 +00:00
template_name = 'input/info_project_funding_gt_1000.html'
class ApplicationView(FormView):
"""
2025-10-14 09:39:58 +00:00
View for all application types.
- Renders the generic form template.
- Handles saving the submitted form to the database.
- Adds extra fields from the session or request type if needed.
- Applies optional help_text overrides for certain fields.
- Sends confirmation mail to the applicant.
- Sends notification mail to the internal IF address.
- Returns the "done" response after successful processing.
"""
2025-10-14 09:39:58 +00:00
template_name = 'input/forms/form_generic.html'
2025-10-14 09:39:58 +00:00
@cached_property
def type_info(self) -> ApplicationType:
type_path = self.kwargs['type']
if type_info := TYPES.get(type_path):
return type_info
2025-10-14 09:39:58 +00:00
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
2020-11-18 11:05:18 +00:00
def get_context_data(self, **kwargs):
2025-10-14 09:39:58 +00:00
return super().get_context_data(**kwargs, type_label=self.type_info.label)
2025-08-18 14:32:31 +00:00
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
2025-10-14 09:39:58 +00:00
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)
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.
"""
data = self.prepare_data(form)
obj = self.save_obj(form, data)
if response := self.send_mail(obj, data):
return response
return done(self.request)
def prepare_data(self, form):
# Collect cleaned data and mark the current type
data = {**form.cleaned_data, 'choice': self.type_code}
# Special rule for literature applications
2025-10-14 09:39:58 +00:00
if self.type_code == TYPE_LIT and data.get('selfbuy') == 'TRUE':
data['selfbuy_give_data'] = 'False'
return data
def save_obj(self, form, data):
# Save model instance
modell = form.save(commit=False)
2025-08-18 14:32:31 +00:00
# Username from session if present
if user := self.request.session.get('user'):
modell.username = user.get('username')
2025-08-18 14:32:31 +00:00
# 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
2025-10-14 09:39:58 +00:00
if self.type_code in LIBRARY_TYPES:
modell.type = self.type_code
# Literature-specific extra field
2025-10-14 09:39:58 +00:00
if self.type_code == TYPE_LIT and 'selfbuy_give_data' in data:
modell.selfbuy_give_data = data['selfbuy_give_data']
modell.save()
if hasattr(form, 'save_m2m'):
form.save_m2m()
return modell
def send_mail(self, obj, data):
# Prepare minimal mail context and send mails
2025-10-17 10:06:23 +00:00
type_label_html = self.type_info.label
type_label_plain = strip_tags(type_label_html)
data['pk'] = obj.pk
data['url_prefix'] = settings.EMAIL_URL_PREFIX
2025-10-17 10:06:23 +00:00
data['type_label'] = type_label_html
context = {'data': data}
2025-10-17 10:06:23 +00:00
applicant_name = self.get_recipient_name(obj, data)
applicant_subject = 'Deine Förderanfrage bei Wikimedia Deutschland'
staff_subject = f'Anfrage {type_label_plain} von {applicant_name}'
2025-08-18 14:32:31 +00:00
2025-10-17 10:06:23 +00:00
try:
self.send_email('applicant', 'ifg_volunteer_mail', applicant_subject, data['email'], context)
self.send_email('staff', 'if_mail', staff_subject, settings.IF_EMAIL, context)
2020-10-08 10:38:49 +00:00
except BadHeaderError:
obj.delete()
return HttpResponse('Invalid header found. Data not saved!')
except SMTPException:
obj.delete()
2025-10-17 10:06:23 +00:00
return HttpResponse('Error in sending mails (probably wrong address?). Data not saved!')
2025-10-17 12:51:18 +00:00
def send_email(self, kind, template_name, subject, recipient, context, *, fail_silently=False):
email = build_email(template_name, context, subject, recipient)
2025-10-17 15:22:52 +00:00
applicant_files = collect_attachment_paths(kind, self.type_code)
2025-10-17 10:06:23 +00:00
attach_files(email, applicant_files)
2025-10-17 12:51:18 +00:00
return email.send(fail_silently)
@staticmethod
def get_recipient_name(obj, data):
for field in 'username', 'realname', 'email':
if name := getattr(obj, field, None) or data.get(field):
return name
return 'Unbekannt'