2025-11-07 13:48:54 +00:00
from dataclasses import dataclass
2020-11-16 14:53:43 +00:00
from smtplib import SMTPException
2025-11-07 13:48:54 +00:00
from typing import Optional
2020-10-26 11:34:09 +00:00
2025-11-07 13:48:54 +00:00
from django . shortcuts import render , redirect
2025-10-14 09:39:58 +00:00
from django . http import HttpResponse , Http404
from django . utils . functional import cached_property
2025-11-07 13:48:54 +00:00
from django . utils . http import url_has_allowed_host_and_scheme
2025-08-19 14:08:48 +00:00
from django . utils . safestring import mark_safe
2025-10-17 12:51:18 +00:00
from django . core . mail import BadHeaderError
2025-08-19 07:45:14 +00:00
from django . conf import settings
2020-10-28 14:49:18 +00:00
from django . contrib . auth . decorators import login_required
2025-10-14 09:39:58 +00:00
from django . views . generic import TemplateView
2025-08-31 21:41:11 +00:00
from django . views . generic . edit import FormView
2025-10-07 17:46:25 +00:00
from django . utils . html import strip_tags
2025-10-17 10:06:23 +00:00
2025-10-17 15:31:51 +00:00
from input . utils . mail import build_email , collect_and_attach
2020-09-22 10:21:05 +00:00
2025-08-20 10:06:43 +00:00
from . forms import (
2025-10-14 09:39:58 +00:00
BaseApplicationForm ,
2025-10-16 10:16:08 +00:00
ProjectForm ,
2025-08-20 10:06:43 +00:00
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 ,
2025-10-16 10:16:08 +00:00
TYPE_PROJ ,
2025-10-14 09:39:58 +00:00
TYPE_SOFT ,
TYPE_TRAV ,
TYPE_VIS ,
)
2025-08-20 10:06:43 +00:00
2025-08-31 21:41:11 +00:00
HELP_TEXTS = {
2025-10-14 09:39:58 +00:00
TYPE_IFG : {
2025-09-01 11:24:59 +00:00
' notes ' : (
' Bitte gib an, wie die gewonnenen Informationen den<br> '
' Wikimedia-Projekten zugute kommen sollen. '
2025-08-31 21:41:11 +00:00
)
} ,
2025-10-14 09:39:58 +00:00
TYPE_MAIL : {
2025-09-01 11:24:59 +00:00
' domain ' : (
' Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br> '
' möchtest du eine Mailadresse beantragen? '
2025-08-31 21:41:11 +00:00
)
} ,
2025-10-14 09:39:58 +00:00
TYPE_LIT : {
2025-09-01 11:24:59 +00:00
' notes ' : ' Bitte gib an, wofür du die Literatur verwenden möchtest. '
2025-08-31 21:41:11 +00:00
} ,
2025-10-14 09:39:58 +00:00
TYPE_LIST : {
2025-09-01 11:24:59 +00:00
' domain ' : (
' Mit welcher Domain, bzw. für welches Wikimedia-Projekt,<br> '
' möchtest du eine Mailingliste beantragen? '
2025-08-31 21:41:11 +00:00
)
} ,
}
2025-11-07 13:48:54 +00:00
@dataclass
class ApplicationType :
2025-10-14 09:39:58 +00:00
code : str
path : str
form_class : type [ BaseApplicationForm ]
2025-11-07 13:48:54 +00:00
link : str
label : Optional [ str ] = None
help_texts : Optional [ str ] = None
2025-10-14 09:39:58 +00:00
2025-11-07 13:48:54 +00:00
def __post_init__ ( self ) :
if self . label is None :
self . label = TYPE_CHOICES [ self . code ]
if self . help_texts is None :
self . help_texts = HELP_TEXTS . get ( self . code )
2025-10-14 09:39:58 +00:00
@property
2025-11-07 13:48:54 +00:00
def url ( self ) :
return f ' https://de.wikipedia.org/wiki/Wikipedia:F%C3%B6rderung/ { self . link } '
2025-10-14 09:39:58 +00:00
2025-10-15 10:20:47 +00:00
PROJECT_FUNDING = [
2025-11-07 13:48:54 +00:00
ApplicationType ( TYPE_PROJ , ' projektfoerderung ' , ProjectForm , ' Projektplanung ' ,
' Projektförderung mit einer Gesamtsumme unter 1.000,— EUR ' ) ,
ApplicationType ( TYPE_PROJ , ' projektfoerderung-ab-1000 ' , ProjectForm , ' Projektplanung ' ,
' Projektförderung mit einer Gesamtsumme ab 1.000,— EUR ' ) ,
2025-10-15 10:20:47 +00:00
]
SERVICES = [
2025-11-07 13:48:54 +00:00
ApplicationType ( TYPE_BIB , ' bibliotheksstipendium ' , LibraryForm , ' Zugang_zu_Fachliteratur#Bibliotheksstipendium ' ) ,
ApplicationType ( TYPE_ELIT , ' eliteraturstipendium ' , ELiteratureForm , ' Zugang_zu_Fachliteratur#eLiteraturstipendium ' ) ,
ApplicationType ( TYPE_MAIL , ' email ' , EmailForm , ' E-Mail-Adressen_und_Visitenkarten#E-Mail-Adressen ' ) ,
ApplicationType ( TYPE_IFG , ' ifg ' , IFGForm , ' Geb % C3 % BChrenerstattungen_f % C3 % BCr_Beh % C3 % B6rdenanfragen ' ) ,
ApplicationType ( TYPE_LIT , ' literaturstipendium ' , LiteratureForm , ' Zugang_zu_Fachliteratur#Literaturstipendium ' ) ,
ApplicationType ( TYPE_LIST , ' mailingliste ' , ListForm , ' E-Mail-Adressen_und_Visitenkarten#Mailinglisten ' ) ,
ApplicationType ( TYPE_TRAV , ' reisekosten ' , TravelForm , ' Reisekostenerstattungen ' ) ,
ApplicationType ( TYPE_SOFT , ' softwarestipendium ' , SoftwareForm , ' Software-Stipendien ' ) ,
ApplicationType ( TYPE_VIS , ' visitenkarten ' , BusinessCardForm , ' E-Mail-Adressen_und_Visitenkarten#Visitenkarten ' ) ,
]
BLOCKS = [
( ' Projektförderung ' , PROJECT_FUNDING ) ,
( ' Serviceleistungen ' , SERVICES ) ,
2025-10-14 09:39:58 +00:00
]
2025-10-15 10:20:47 +00:00
TYPES = { info . path : info for info in PROJECT_FUNDING + SERVICES }
2025-10-14 09:39:58 +00:00
2025-08-20 10:06:43 +00:00
def auth_deny ( choice , pk , auth ) :
if choice not in MODELS :
2021-01-04 10:24:20 +00:00
return HttpResponse ( f ' ERROR! UNKNOWN CHOICE TYPE! { choice } ' )
2025-08-20 10:06:43 +00:00
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
2020-10-28 14:49:18 +00:00
@login_required
2020-10-20 06:39:00 +00:00
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 ) :
2020-10-27 12:47:46 +00:00
return ret
2020-10-20 07:35:04 +00:00
else :
2025-09-01 11:24:59 +00:00
return HttpResponse ( f ' AUTHORIZED! choice: { choice } , pk: { pk } ' )
2020-10-20 07:35:04 +00:00
2025-08-21 08:08:38 +00:00
2020-10-28 14:49:18 +00:00
@login_required
2020-10-20 06:39:00 +00:00
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 . '''
2020-10-21 07:22:53 +00:00
2025-08-21 08:42:55 +00:00
if ret := auth_deny ( choice , pk , False ) :
2020-10-27 12:47:46 +00:00
return ret
2020-10-20 07:35:04 +00:00
else :
2025-09-01 11:24:59 +00:00
return HttpResponse ( f ' DENIED! choice: { choice } , pk: { pk } ' )
2020-10-20 07:35:04 +00:00
2020-10-20 06:06:36 +00:00
2020-09-29 09:08:16 +00:00
def done ( request ) :
2025-08-31 21:41:11 +00:00
return HttpResponse (
2025-09-01 11:24:59 +00:00
' 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
2024-01-07 14:13:21 +00:00
def index ( request ) :
return render ( request , ' input/index.html ' )
2020-10-28 14:49:18 +00:00
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 '
2025-11-07 13:48:54 +00:00
extra_context = { ' blocks ' : BLOCKS }
def post ( self , request , * args , * * kwargs ) :
if url := request . POST . get ( ' url ' ) :
if url_has_allowed_host_and_scheme ( url , None ) :
return redirect ( url )
return self . get ( request , * args , * * kwargs )
2025-10-14 09:39:58 +00:00
2025-10-15 10:20:47 +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-08-31 21:41:11 +00:00
"""
2025-10-14 09:39:58 +00:00
View for all application types .
2025-08-31 21:41:11 +00:00
- 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
2025-09-01 11:24:59 +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 ' ]
2025-10-15 10:20:47 +00:00
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-10-05 13:10:23 +00:00
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
2025-08-31 21:41:11 +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 )
2023-02-27 17:09:29 +00:00
2025-08-31 21:41:11 +00:00
# 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 ( ) :
2025-08-31 21:41:11 +00:00
if field in form . fields :
form . fields [ field ] . help_text = mark_safe ( text )
2025-08-18 14:33:04 +00:00
2025-08-31 21:41:11 +00:00
return form
2023-02-27 17:09:29 +00:00
2025-08-31 21:41:11 +00:00
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 .
"""
2020-10-05 11:35:28 +00:00
2025-10-17 09:42:06 +00:00
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 ) :
2025-08-31 21:41:11 +00:00
# Collect cleaned data and mark the current type
2025-10-17 09:42:06 +00:00
data = { * * form . cleaned_data , ' choice ' : self . type_code }
2020-10-20 06:06:36 +00:00
2025-08-31 21:41:11 +00:00
# Special rule for literature applications
2025-10-14 09:39:58 +00:00
if self . type_code == TYPE_LIT and data . get ( ' selfbuy ' ) == ' TRUE ' :
2025-09-01 11:24:59 +00:00
data [ ' selfbuy_give_data ' ] = ' False '
2025-08-31 21:41:11 +00:00
2025-10-17 09:42:06 +00:00
return data
def save_obj ( self , form , data ) :
2025-08-31 21:41:11 +00:00
# Save model instance
2025-10-17 09:42:06 +00:00
2025-08-31 21:41:11 +00:00
modell = form . save ( commit = False )
2025-08-18 14:32:31 +00:00
2025-08-31 21:41:11 +00:00
# Username from session if present
2025-09-01 11:33:17 +00:00
if user := self . request . session . get ( ' user ' ) :
2025-09-01 11:24:59 +00:00
modell . username = user . get ( ' username ' )
2025-08-18 14:32:31 +00:00
2025-08-31 21:41:11 +00:00
# Copy common fields if provided by the form
2025-09-01 11:24:59 +00:00
if ' realname ' in data :
modell . realname = data [ ' realname ' ]
2025-10-17 09:42:06 +00:00
2025-09-01 11:24:59 +00:00
if ' email ' in data :
modell . email = data [ ' email ' ]
2025-08-31 21:41:11 +00:00
# Set model.type for specific request types
2025-10-14 09:39:58 +00:00
if self . type_code in LIBRARY_TYPES :
2025-08-31 21:41:11 +00:00
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 :
2025-09-01 11:24:59 +00:00
modell . selfbuy_give_data = data [ ' selfbuy_give_data ' ]
2025-08-31 21:41:11 +00:00
modell . save ( )
2025-10-17 09:42:06 +00:00
2025-09-01 11:24:59 +00:00
if hasattr ( form , ' save_m2m ' ) :
2025-08-31 21:41:11 +00:00
form . save_m2m ( )
2025-10-17 09:42:06 +00:00
return modell
def send_mail ( self , obj , data ) :
2025-08-31 21:41:11 +00:00
# Prepare minimal mail context and send mails
2025-10-17 09:42:06 +00:00
2025-10-17 10:06:23 +00:00
type_label_html = self . type_info . label
type_label_plain = strip_tags ( type_label_html )
2025-10-17 09:42:06 +00:00
data [ ' pk ' ] = obj . pk
2025-09-01 11:24:59 +00:00
data [ ' url_prefix ' ] = settings . EMAIL_URL_PREFIX
2025-10-17 10:06:23 +00:00
data [ ' type_label ' ] = type_label_html
2025-09-01 11:24:59 +00:00
context = { ' data ' : data }
2025-08-31 21:41:11 +00:00
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 :
2025-10-17 09:42:06 +00:00
obj . delete ( )
2025-09-01 11:24:59 +00:00
return HttpResponse ( ' Invalid header found. Data not saved! ' )
2020-11-16 14:53:43 +00:00
except SMTPException :
2025-10-17 09:42:06 +00:00
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 10:06:23 +00:00
2025-10-17 15:31:51 +00:00
collect_and_attach ( email , kind , self . type_code )
2025-10-17 10:06:23 +00:00
2025-10-17 12:51:18 +00:00
return email . send ( fail_silently )
2020-10-07 11:15:00 +00:00
2025-10-17 09:42:06 +00:00
@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 '