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 Datenschutzbestimmungen und der
Richtlinie zur Förderung der Communitys 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
Wikimedia Deutschland bei Rückfragen oder für
die Zusage kontaktieren kann.' ) check = BooleanField(required=True, label=format_html( "Ich stimme den Datenschutzbestimmungen und der
Richtlinie zur Förderung der Communitys 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 Nutzungsbedingungen 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 = ''