foerderbarometer/input/forms.py

382 lines
12 KiB
Python
Executable File

from django import forms
from django.conf import settings
from django.contrib.admin.widgets import AdminDateWidget
from django.forms import ModelForm, ChoiceField, RadioSelect, BooleanField, CharField, EmailField
from django.forms.renderers import DjangoTemplates
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from .models import (
TYPE_CHOICES,
Project,
ProjectCategory,
WikimediaProject,
ConcreteVolunteer,
ConcreteExtern,
IFG,
Library,
ELiterature,
Software,
HonoraryCertificate,
Travel,
Email,
Literature,
List,
BusinessCard,
)
class TableFormRenderer(DjangoTemplates):
form_template_name = 'django/forms/table.html'
class FdbForm(ModelForm):
'''this base class provides the required css class for all forms'''
required_css_class = 'required'
class CommonOrderMixin(forms.Form):
"""
Ensures a consistent field order for all forms that inherit from this mixin.
The goal is to always render:
- "realname" and "email" fields at the top,
- all other fields in the middle (in the order Django provides),
- "check" (terms/privacy checkbox) at the bottom.
This keeps the UX consistent across all forms without repeating field_order logic.
"""
# Fields that should always appear first in the form
field_order_head = ('realname', 'email')
# Fields that should always appear last in the form
field_order_tail = ('check',) # rename if your checkbox field is called differently
def __init__(self, *args, **kwargs):
"""
Override the default form initialization to reorder fields.
The method:
1. Put the 'head' fields first.
2. Put all other fields in the middle.
3. Put the 'tail' fields last.
Non-existing fields are ignored by `order_fields`.
"""
super().__init__(*args, **kwargs)
ordered = {*self.field_order_head, *self.field_order_tail}
self.order_fields([
*self.field_order_head,
*[field for field in self.fields if field not in ordered],
*self.field_order_tail,
])
class ExternForm(FdbForm):
choice = ChoiceField(choices=TYPE_CHOICES.items(), widget=RadioSelect,
label='Was möchtest Du beantragen?')
check = BooleanField(required=True,
label=format_html("Ich stimme den <a href='{}' target='_blank' rel='noopener'>Datenschutzbestimmungen</a> und der<br> <a href='{}' target='_blank' rel='noopener'>Richtlinie zur Förderung der Communitys</a> zu",
settings.DATAPROTECTION, settings.FOERDERRICHTLINIEN))
class Meta:
model = ConcreteExtern
exclude = ('username', 'granted', 'granted_date', 'survey_mail_send', 'service_id', 'survey_mail_date', 'mail_state')
INTERN_CHOICES = {
'PRO': 'Projektsteckbrief',
'HON': 'Ehrenamtsbescheinigung, Akkreditierung oder Redaktionsbestätigung',
'TRAV': 'Reisekostenerstattung',
}
class InternForm(FdbForm):
choice = ChoiceField(choices=INTERN_CHOICES.items(), widget=RadioSelect,
label='Was möchtest Du eingeben?')
class Meta:
model = ConcreteVolunteer
exclude = ('granted', 'granted_date', 'survey_mail_send', 'survey_mail_date', 'mail_state')
class BaseApplicationForm(FdbForm):
"""
Base form for all external applications.
- Adds standard fields that must appear in every form:
* realname (applicant's full name)
* email (contact address for notifications)
* check (confirmation of data protection and funding policy)
- Ensures consistency across all application types.
"""
realname = CharField(
label='Realname',
required=True,
help_text='Bitte gib deinen Vor- und Nachnamen ein.'
)
email = EmailField(
label='E-Mail-Adresse',
required=True,
help_text='Bitte gib deine E-Mail-Adresse ein, damit dich <br> Wikimedia Deutschland bei Rückfragen oder für <br> die Zusage kontaktieren kann.'
)
check = BooleanField(required=True,
label=format_html(
"Ich stimme den <a href='{}' target='_blank' rel='noopener'>Datenschutzbestimmungen</a> und der<br> <a href='{}' target='_blank' rel='noopener'>Richtlinie zur Förderung der Communitys</a> zu",
settings.DATAPROTECTION, settings.FOERDERRICHTLINIEN))
class BaseProjectForm(ModelForm):
categories = {
'categories': ProjectCategory,
'wikimedia_projects': WikimediaProject,
}
class Media:
js = ('dropdown/js/otrs_link.js', 'js/project-categories.js')
def clean(self):
cleaned_data = ModelForm.clean(self)
if self.errors:
return cleaned_data
for field, model in self.categories.items():
field_other = f'{field}_other'
values = cleaned_data[field]
if model.other in values:
if not cleaned_data[field_other]:
self.add_error(field_other, f'Bitte geben Sie einen Wert an oder deselektieren Sie "{model.OTHER}".')
else:
cleaned_data[field_other] = ''
return cleaned_data
class ProjectForm(CommonOrderMixin, BaseProjectForm, BaseApplicationForm):
OPTIONAL_FIELDS = {
'categories_other',
'wikimedia_projects_other',
'page',
'group',
'location',
'insurance',
'notes',
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in set(self.fields) - self.OPTIONAL_FIELDS:
self.fields[field].required = True
class Meta:
model = Project
fields = [
'name',
'description',
'categories',
'categories_other',
'wikimedia_projects',
'wikimedia_projects_other',
'start',
'end',
'participants_estimated',
'page',
'group',
'location',
'cost',
'insurance',
'notes',
]
widgets = {
'start': AdminDateWidget,
'end': AdminDateWidget,
}
class Media:
css = {
'all': ('css/dateFieldNoNowShortcutInTravels.css',)
}
HOTEL_CHOICES = {
'TRUE': mark_safe('Hotelzimmer benötigt'),
'FALSE': mark_safe('Kein Hotelzimmer benötigt'),
}
class TravelForm(BaseApplicationForm, CommonOrderMixin):
# TODO: add some javascript to show/hide other-field
hotel = ChoiceField(label='Hotelzimmer benötigt:', choices=HOTEL_CHOICES.items(), widget=RadioSelect())
# this is the code, to change required to false if needed
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['project_name'].required = True
self.fields['transport'].required = True
self.fields['travelcost'].required = True
self.fields['checkin'].required = True
self.fields['checkout'].required = True
self.fields['hotel'].required = True
class Meta:
model = Travel
fields = ['project_name', 'transport', 'travelcost', 'checkin', 'checkout', 'hotel', 'notes']
exclude = ('granted', 'granted_date', 'survey_mail_send', 'realname', 'email', 'survey_mail_date', 'project',
'request_url', 'payed_for_hotel_by', 'payed_for_travel_by', 'intern_notes', 'mail_state')
widgets = {
'checkin': AdminDateWidget,
'checkout': AdminDateWidget,
}
class Media:
js = ('dropdown/js/otrs_link.js',)
css = {
'all': ('css/dateFieldNoNowShortcutInTravels.css',)
}
class LibraryForm(BaseApplicationForm, CommonOrderMixin):
class Meta:
model = Library
fields = ['cost', 'library', 'duration', 'notes', 'survey_mail_send']
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['library'].label = self._meta.model.LIBRARY_LABEL
self.fields['library'].help_text = self._meta.model.LIBRARY_HELP_TEXT
self.fields['duration'].help_text = self._meta.model.DURATION_HELP_TEXT
class ELiteratureForm(LibraryForm):
class Meta(LibraryForm.Meta):
model = ELiterature
class SoftwareForm(LibraryForm):
class Meta(LibraryForm.Meta):
model = Software
class HonoraryCertificateForm(FdbForm):
class Meta:
model = HonoraryCertificate
fields = ['request_url', 'project']
exclude = ['intern_notes']
class Media:
js = ('dropdown/js/otrs_link.js',)
class IFGForm(BaseApplicationForm, CommonOrderMixin):
class Meta:
model = IFG
fields = ['cost', 'url', 'notes']
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
class CheckForm(FdbForm):
termstoaccept = settings.NUTZUNGSBEDINGUNGEN
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Check if the model field 'terms_accepted' is present
if 'terms_accepted' in self.fields:
# Make the field required (HTML5 validation)
self.fields['terms_accepted'].required = True
# Set custom label with link to terms
self.fields['terms_accepted'].label = format_html(
"Ich stimme den <a href='{}'>Nutzungsbedingungen</a> zu",
self.termstoaccept
)
class LiteratureForm(BaseApplicationForm, CommonOrderMixin):
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_LITERATURSTIPENDIUM
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['selfbuy_give_data'].required = True
class Meta:
model = Literature
fields = ['cost', 'info', 'source', 'notes', 'selfbuy', 'selfbuy_data', 'selfbuy_give_data', 'terms_accepted']
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
class Media:
js = ('dropdown/js/literature.js',)
ADULT_CHOICES = {
'TRUE': mark_safe('Ich bin volljährig.'),
'FALSE': mark_safe('Ich bin noch nicht volljährig.'),
}
class EmailForm(BaseApplicationForm, CommonOrderMixin):
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_EMAIL_SERVICE
# this is the code, to change required to false if needed
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['adult'].required = True
self.fields['other'].required = True
adult = ChoiceField(label='Volljährigkeit', choices=ADULT_CHOICES.items(), widget=RadioSelect())
# TODO: add some javascript to show/hide other-field
class Meta:
model = Email
fields = ['domain', 'address', 'other', 'adult', 'terms_accepted']
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
class Media:
js = ('dropdown/js/mail.js',)
class BusinessCardForm(BaseApplicationForm, CommonOrderMixin):
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_VISITENKARTEN
# this is the code, to change required to false if needed
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['url_of_pic'].required = True
self.fields['send_data_to_print'].required = True
class Meta:
model = BusinessCard
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
fields = ['project', 'data', 'variant', 'url_of_pic', 'send_data_to_print', 'sent_to', 'terms_accepted']
class Media:
js = ('dropdown/js/businessCard.js',)
class ListForm(BaseApplicationForm, CommonOrderMixin):
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_MAILINGLISTEN
class Meta:
model = List
fields = ['domain', 'address', 'terms_accepted']
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['address'].initial = ''