2020-11-16 14:53:43 +00:00
from smtplib import SMTPException
2025-10-14 09:39:58 +00:00
from typing import NamedTuple
2020-10-26 11:34:09 +00:00
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
2025-08-19 14:08:48 +00:00
from django . utils . safestring import mark_safe
from django . core . mail import BadHeaderError , EmailMultiAlternatives
2020-10-07 13:07:02 +00:00
from django . template . loader import get_template
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
from input . mail_attachments import collect_attachment_paths , attach_files
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-08-20 10:06:43 +00:00
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 )
2025-10-15 10:20:47 +00:00
PROJECT_FUNDING = [
2025-10-16 10:16:08 +00:00
ApplicationType ( TYPE_PROJ , ' projektfoerderung ' , ProjectForm ) ,
2025-10-15 10:20:47 +00:00
]
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 ) ,
]
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-10-15 10:20:47 +00:00
extra_context = { ' services ' : SERVICES }
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
data [ ' pk ' ] = obj . pk
2025-09-01 11:24:59 +00:00
data [ ' url_prefix ' ] = settings . EMAIL_URL_PREFIX
2025-10-14 09:39:58 +00:00
data [ ' type_label ' ] = self . type_info . label
2025-09-01 11:24:59 +00:00
context = { ' data ' : data }
2025-08-31 21:41:11 +00:00
try :
# Mail to applicant
2025-09-01 11:24:59 +00:00
txt1 = get_template ( ' input/ifg_volunteer_mail.txt ' ) . render ( context )
html1 = get_template ( ' input/ifg_volunteer_mail.html ' ) . render ( context )
2025-08-31 21:41:11 +00:00
msg1 = EmailMultiAlternatives (
2025-10-07 17:46:25 +00:00
' Deine Förderanfrage bei Wikimedia Deutschland ' , txt1 , settings . IF_EMAIL , [ data [ ' email ' ] ]
2025-08-31 21:41:11 +00:00
)
2025-09-01 11:24:59 +00:00
msg1 . attach_alternative ( html1 , ' text/html ' )
2025-10-07 17:46:25 +00:00
applicant_files = collect_attachment_paths ( kind = ' applicant ' , choice = self . type_code )
attach_files ( msg1 , applicant_files )
2023-02-27 17:09:29 +00:00
msg1 . send ( )
2025-10-17 09:42:06 +00:00
type_label_html = self . type_info . label
type_label_plain = strip_tags ( type_label_html )
2025-08-31 21:41:11 +00:00
# Mail to IF
2025-09-01 11:24:59 +00:00
txt2 = get_template ( ' input/if_mail.txt ' ) . render ( context )
html2 = get_template ( ' input/if_mail.html ' ) . render ( context )
2025-10-17 09:42:06 +00:00
applicant_name = self . get_recipient_name ( obj , data )
2025-08-31 21:41:11 +00:00
msg2 = EmailMultiAlternatives (
2025-10-17 09:42:06 +00:00
f ' Anfrage { type_label_plain } von { applicant_name } ' , txt2 , settings . IF_EMAIL , [ settings . IF_EMAIL ]
2025-08-31 21:41:11 +00:00
)
2025-09-01 11:24:59 +00:00
msg2 . attach_alternative ( html2 , ' text/html ' )
2025-10-07 17:46:25 +00:00
staff_files = collect_attachment_paths ( kind = ' staff ' , choice = self . type_code )
attach_files ( msg2 , staff_files )
2023-02-27 17:09:29 +00:00
msg2 . send ( )
2025-08-18 14:32:31 +00:00
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-09-01 11:24:59 +00:00
return HttpResponse ( ' Error in sending mails (probably wrong adress?). Data not saved! ' )
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 '