from smtplib import SMTPException from typing import NamedTuple from django.shortcuts import render from django.http import HttpResponse, Http404 from django.utils.functional import cached_property from django.utils.safestring import mark_safe from django.core.mail import BadHeaderError 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 django.utils.html import strip_tags from input.utils.mail import collect_attachment_paths, attach_files, build_email from .forms import ( BaseApplicationForm, ProjectForm, LibraryForm, ELiteratureForm, SoftwareForm, IFGForm, LiteratureForm, TravelForm, EmailForm, ListForm, BusinessCardForm, ) from .models import ( MODELS, LIBRARY_TYPES, TYPE_CHOICES, TYPE_BIB, TYPE_ELIT, TYPE_IFG, TYPE_LIT, TYPE_LIST, TYPE_MAIL, TYPE_PROJ, TYPE_SOFT, TYPE_TRAV, TYPE_VIS, ) HELP_TEXTS = { TYPE_IFG: { 'notes': ( 'Bitte gib an, wie die gewonnenen Informationen den
' 'Wikimedia-Projekten zugute kommen sollen.' ) }, TYPE_MAIL: { 'domain': ( 'Mit welcher Domain, bzw. für welches Wikimedia-Projekt,
' 'möchtest du eine Mailadresse beantragen?' ) }, TYPE_LIT: { 'notes': 'Bitte gib an, wofür du die Literatur verwenden möchtest.' }, TYPE_LIST: { 'domain': ( 'Mit welcher Domain, bzw. für welches Wikimedia-Projekt,
' 'möchtest du eine Mailingliste beantragen?' ) }, } 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 = [ 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} 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) @login_required def export(request): '''export the project database to a csv''' return HttpResponse('WE WANT CSV!') @login_required def authorize(request, choice, pk): '''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.''' if ret := auth_deny(choice, pk, True): return ret else: return HttpResponse(f'AUTHORIZED! choice: {choice}, pk: {pk}') @login_required def deny(request, choice, pk): '''If IF denies a support they click a link in a mail which leads here We write the granted field in the database here.''' if ret := auth_deny(choice, pk, False): return ret else: return HttpResponse(f'DENIED! choice: {choice}, pk: {pk}') 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.') def index(request): return render(request, 'input/index.html') class ApplicationStartView(TemplateView): template_name = 'input/forms/extern.html' extra_context = {'services': SERVICES} class ProjectFundingInfoView(TemplateView): template_name = 'input/info_project_funding_gt_1000.html' class ApplicationView(FormView): """ 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. """ template_name = 'input/forms/form_generic.html' @cached_property def type_info(self) -> ApplicationType: type_path = self.kwargs['type'] if type_info := TYPES.get(type_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): 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 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 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) # 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 LIBRARY_TYPES: modell.type = self.type_code # Literature-specific extra field 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 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 data['type_label'] = type_label_html context = {'data': data} 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}' 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) except BadHeaderError: obj.delete() return HttpResponse('Invalid header found. Data not saved!') except SMTPException: obj.delete() return HttpResponse('Error in sending mails (probably wrong address?). Data not saved!') def send_email(self, kind, template_name, subject, recipient, context, *, fail_silently=False): email = build_email(template_name, context, subject, recipient) applicant_files = collect_attachment_paths(kind, self.type_code) attach_files(email, applicant_files) 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'