forked from beba/foerderbarometer
cleaned up forms & fixed missing terms field
This commit is contained in:
parent
1dbd38dc4a
commit
864df9613a
249
input/forms.py
249
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 <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):
|
||||
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.'
|
||||
)
|
||||
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))
|
||||
"""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
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
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 <a href="{}">Nutzungsbedingungen</a> 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 <a href='{}'>Nutzungsbedingungen</a> zu",
|
||||
self.termstoaccept
|
||||
)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Reference in New Issue