diff --git a/input/forms.py b/input/forms.py
index 3688344..62b0b8b 100755
--- a/input/forms.py
+++ b/input/forms.py
@@ -1,23 +1,19 @@
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 import ModelForm
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,
@@ -27,108 +23,33 @@ from .models import (
class TableFormRenderer(DjangoTemplates):
+ """
+ Set in settings as the default form renderer.
+ """
+
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 RadioField(forms.ChoiceField):
+ widget = forms.RadioSelect
-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):
+class BaseApplicationForm(ModelForm):
"""
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_css_class = 'required'
+
+ check = forms.BooleanField(
required=True,
- help_text='Bitte gib deinen Vor- und Nachnamen ein.'
+ label=format_html(
+ """Ich stimme den Datenschutzbestimmungen und der
+ Richtlinie zur Förderung der Communitys zu.""",
+ settings.DATAPROTECTION,
+ settings.FOERDERRICHTLINIEN
+ ),
)
- 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))
PROJECT_COST_GT_1000_MESSAGE = format_html(
@@ -166,7 +87,7 @@ class BaseProjectForm(ModelForm):
return cleaned_data
-class ProjectForm(CommonOrderMixin, BaseProjectForm, BaseApplicationForm):
+class ProjectForm(BaseProjectForm, BaseApplicationForm):
OPTIONAL_FIELDS = {
'categories_other',
'wikimedia_projects_other',
@@ -186,6 +107,8 @@ class ProjectForm(CommonOrderMixin, BaseProjectForm, BaseApplicationForm):
class Meta:
model = Project
fields = [
+ 'realname',
+ 'email',
'name',
'description',
'categories',
@@ -227,10 +150,10 @@ HOTEL_CHOICES = {
}
-class TravelForm(BaseApplicationForm, CommonOrderMixin):
+class TravelForm(BaseApplicationForm):
# TODO: add some javascript to show/hide other-field
- hotel = ChoiceField(label='Hotelzimmer benötigt:', choices=HOTEL_CHOICES.items(), widget=RadioSelect())
+ hotel = RadioField(label='Hotelzimmer benötigt', choices=HOTEL_CHOICES)
# this is the code, to change required to false if needed
def __init__(self, *args, **kwargs):
@@ -244,9 +167,17 @@ class TravelForm(BaseApplicationForm, CommonOrderMixin):
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')
+ fields = [
+ 'realname',
+ 'email',
+ 'project_name',
+ 'transport',
+ 'travelcost',
+ 'checkin',
+ 'checkout',
+ 'hotel',
+ 'notes',
+ ]
widgets = {
'checkin': AdminDateWidget,
'checkout': AdminDateWidget,
@@ -259,12 +190,18 @@ class TravelForm(BaseApplicationForm, CommonOrderMixin):
}
-class LibraryForm(BaseApplicationForm, CommonOrderMixin):
+class LibraryForm(BaseApplicationForm):
class Meta:
model = Library
- fields = ['cost', 'library', 'duration', 'notes', 'survey_mail_send']
- exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
+ fields = [
+ 'realname',
+ 'email',
+ 'cost',
+ 'library',
+ 'duration',
+ 'notes',
+ ]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -286,43 +223,32 @@ class SoftwareForm(LibraryForm):
model = Software
-class HonoraryCertificateForm(FdbForm):
+class IFGForm(BaseApplicationForm):
- 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']
+ fields = [
+ 'realname',
+ 'email',
+ 'cost',
+ 'url',
+ 'notes',
+ ]
-class CheckForm(FdbForm):
- termstoaccept = settings.NUTZUNGSBEDINGUNGEN
+class TermsForm(BaseApplicationForm):
+ terms_accepted_label = 'Ich stimme den Nutzungsbedingungen zu.'
+ terms_accepted_url = 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
- )
+ self.fields['terms_accepted'].required = True
+ self.fields['terms_accepted'].label = format_html(self.terms_accepted_label, self.terms_accepted_url)
-class LiteratureForm(BaseApplicationForm, CommonOrderMixin):
- termstoaccept = settings.NUTZUNGSBEDINGUNGEN_LITERATURSTIPENDIUM
+class LiteratureForm(TermsForm):
+ terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_LITERATURSTIPENDIUM
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -330,8 +256,18 @@ class LiteratureForm(BaseApplicationForm, CommonOrderMixin):
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']
+ fields = [
+ 'realname',
+ 'email',
+ 'cost',
+ 'info',
+ 'source',
+ 'notes',
+ 'selfbuy',
+ 'selfbuy_data',
+ 'selfbuy_give_data',
+ 'terms_accepted',
+ ]
class Media:
js = ('dropdown/js/literature.js',)
@@ -343,8 +279,8 @@ ADULT_CHOICES = {
}
-class EmailForm(BaseApplicationForm, CommonOrderMixin):
- termstoaccept = settings.NUTZUNGSBEDINGUNGEN_EMAIL_SERVICE
+class EmailForm(TermsForm):
+ terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_EMAIL_SERVICE
# this is the code, to change required to false if needed
def __init__(self, *args, **kwargs):
@@ -352,20 +288,27 @@ class EmailForm(BaseApplicationForm, CommonOrderMixin):
self.fields['adult'].required = True
self.fields['other'].required = True
- adult = ChoiceField(label='Volljährigkeit', choices=ADULT_CHOICES.items(), widget=RadioSelect())
+ adult = RadioField(label='Volljährigkeit', choices=ADULT_CHOICES)
# 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']
+ fields = [
+ 'realname',
+ 'email',
+ 'domain',
+ 'address',
+ 'other',
+ 'adult',
+ 'terms_accepted',
+ ]
class Media:
js = ('dropdown/js/mail.js',)
-class BusinessCardForm(BaseApplicationForm, CommonOrderMixin):
- termstoaccept = settings.NUTZUNGSBEDINGUNGEN_VISITENKARTEN
+class BusinessCardForm(TermsForm):
+ terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_VISITENKARTEN
# this is the code, to change required to false if needed
def __init__(self, *args, **kwargs):
@@ -375,20 +318,34 @@ class BusinessCardForm(BaseApplicationForm, CommonOrderMixin):
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']
+ fields = [
+ 'realname',
+ 'email',
+ '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 ListForm(TermsForm):
+ terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_MAILINGLISTEN
class Meta:
model = List
- fields = ['domain', 'address', 'terms_accepted']
- exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
+ fields = [
+ 'realname',
+ 'email',
+ 'domain',
+ 'address',
+ 'terms_accepted',
+ ]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
diff --git a/input/models.py b/input/models.py
index 5193d7f..8a92f3c 100755
--- a/input/models.py
+++ b/input/models.py
@@ -25,7 +25,7 @@ EMAIL_STATES = {
class TermsConsentMixin(models.Model):
"""Abstract mixin to add a terms_accepted field for documenting user consent."""
- terms_accepted = models.BooleanField(default=False, verbose_name="Nutzungsbedingungen zugestimmt")
+ terms_accepted = models.BooleanField(default=False, verbose_name='Nutzungsbedingungen zugestimmt')
class Meta:
abstract = True
diff --git a/input/tests/views.py b/input/tests/views.py
index c7fab8f..4cf1bab 100644
--- a/input/tests/views.py
+++ b/input/tests/views.py
@@ -90,6 +90,7 @@ class AnonymousViewTestCase(TestCase):
'selfbuy_data': 'NONE',
'selfbuy_give_data': True,
'check': True,
+ 'terms_accepted': True,
})
def test_extern_lit_without_consent_fails(self):