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 import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.admin.widgets import AdminDateWidget
|
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.forms.renderers import DjangoTemplates
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
TYPE_CHOICES,
|
|
||||||
Project,
|
Project,
|
||||||
ProjectCategory,
|
ProjectCategory,
|
||||||
WikimediaProject,
|
WikimediaProject,
|
||||||
ConcreteVolunteer,
|
|
||||||
ConcreteExtern,
|
|
||||||
IFG,
|
IFG,
|
||||||
Library,
|
Library,
|
||||||
ELiterature,
|
ELiterature,
|
||||||
Software,
|
Software,
|
||||||
HonoraryCertificate,
|
|
||||||
Travel,
|
Travel,
|
||||||
Email,
|
Email,
|
||||||
Literature,
|
Literature,
|
||||||
|
|
@ -27,108 +23,33 @@ from .models import (
|
||||||
|
|
||||||
|
|
||||||
class TableFormRenderer(DjangoTemplates):
|
class TableFormRenderer(DjangoTemplates):
|
||||||
|
"""
|
||||||
|
Set in settings as the default form renderer.
|
||||||
|
"""
|
||||||
|
|
||||||
form_template_name = 'django/forms/table.html'
|
form_template_name = 'django/forms/table.html'
|
||||||
|
|
||||||
|
|
||||||
class FdbForm(ModelForm):
|
class RadioField(forms.ChoiceField):
|
||||||
'''this base class provides the required css class for all forms'''
|
widget = forms.RadioSelect
|
||||||
required_css_class = 'required'
|
|
||||||
|
|
||||||
|
|
||||||
class CommonOrderMixin(forms.Form):
|
class BaseApplicationForm(ModelForm):
|
||||||
"""
|
|
||||||
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.
|
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,
|
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(
|
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",
|
"""Ich stimme den <a href="{}" target="_blank" rel="noopener">Datenschutzbestimmungen</a> und der<br>
|
||||||
settings.DATAPROTECTION, settings.FOERDERRICHTLINIEN))
|
<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(
|
PROJECT_COST_GT_1000_MESSAGE = format_html(
|
||||||
|
|
@ -166,7 +87,7 @@ class BaseProjectForm(ModelForm):
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
class ProjectForm(CommonOrderMixin, BaseProjectForm, BaseApplicationForm):
|
class ProjectForm(BaseProjectForm, BaseApplicationForm):
|
||||||
OPTIONAL_FIELDS = {
|
OPTIONAL_FIELDS = {
|
||||||
'categories_other',
|
'categories_other',
|
||||||
'wikimedia_projects_other',
|
'wikimedia_projects_other',
|
||||||
|
|
@ -186,6 +107,8 @@ class ProjectForm(CommonOrderMixin, BaseProjectForm, BaseApplicationForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Project
|
model = Project
|
||||||
fields = [
|
fields = [
|
||||||
|
'realname',
|
||||||
|
'email',
|
||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
'categories',
|
'categories',
|
||||||
|
|
@ -227,10 +150,10 @@ HOTEL_CHOICES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TravelForm(BaseApplicationForm, CommonOrderMixin):
|
class TravelForm(BaseApplicationForm):
|
||||||
# TODO: add some javascript to show/hide other-field
|
# 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
|
# this is the code, to change required to false if needed
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
@ -244,9 +167,17 @@ class TravelForm(BaseApplicationForm, CommonOrderMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Travel
|
model = Travel
|
||||||
fields = ['project_name', 'transport', 'travelcost', 'checkin', 'checkout', 'hotel', 'notes']
|
fields = [
|
||||||
exclude = ('granted', 'granted_date', 'survey_mail_send', 'realname', 'email', 'survey_mail_date', 'project',
|
'realname',
|
||||||
'request_url', 'payed_for_hotel_by', 'payed_for_travel_by', 'intern_notes', 'mail_state')
|
'email',
|
||||||
|
'project_name',
|
||||||
|
'transport',
|
||||||
|
'travelcost',
|
||||||
|
'checkin',
|
||||||
|
'checkout',
|
||||||
|
'hotel',
|
||||||
|
'notes',
|
||||||
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'checkin': AdminDateWidget,
|
'checkin': AdminDateWidget,
|
||||||
'checkout': AdminDateWidget,
|
'checkout': AdminDateWidget,
|
||||||
|
|
@ -259,12 +190,18 @@ class TravelForm(BaseApplicationForm, CommonOrderMixin):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class LibraryForm(BaseApplicationForm, CommonOrderMixin):
|
class LibraryForm(BaseApplicationForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Library
|
model = Library
|
||||||
fields = ['cost', 'library', 'duration', 'notes', 'survey_mail_send']
|
fields = [
|
||||||
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
'realname',
|
||||||
|
'email',
|
||||||
|
'cost',
|
||||||
|
'library',
|
||||||
|
'duration',
|
||||||
|
'notes',
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
@ -286,43 +223,32 @@ class SoftwareForm(LibraryForm):
|
||||||
model = Software
|
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:
|
class Meta:
|
||||||
model = IFG
|
model = IFG
|
||||||
fields = ['cost', 'url', 'notes']
|
fields = [
|
||||||
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
'realname',
|
||||||
|
'email',
|
||||||
|
'cost',
|
||||||
|
'url',
|
||||||
|
'notes',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class CheckForm(FdbForm):
|
class TermsForm(BaseApplicationForm):
|
||||||
termstoaccept = settings.NUTZUNGSBEDINGUNGEN
|
terms_accepted_label = 'Ich stimme den <a href="{}">Nutzungsbedingungen</a> zu.'
|
||||||
|
terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*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
|
self.fields['terms_accepted'].required = True
|
||||||
# Set custom label with link to terms
|
self.fields['terms_accepted'].label = format_html(self.terms_accepted_label, self.terms_accepted_url)
|
||||||
self.fields['terms_accepted'].label = format_html(
|
|
||||||
"Ich stimme den <a href='{}'>Nutzungsbedingungen</a> zu",
|
|
||||||
self.termstoaccept
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LiteratureForm(BaseApplicationForm, CommonOrderMixin):
|
class LiteratureForm(TermsForm):
|
||||||
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_LITERATURSTIPENDIUM
|
terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_LITERATURSTIPENDIUM
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
@ -330,8 +256,18 @@ class LiteratureForm(BaseApplicationForm, CommonOrderMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Literature
|
model = Literature
|
||||||
fields = ['cost', 'info', 'source', 'notes', 'selfbuy', 'selfbuy_data', 'selfbuy_give_data', 'terms_accepted']
|
fields = [
|
||||||
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
'realname',
|
||||||
|
'email',
|
||||||
|
'cost',
|
||||||
|
'info',
|
||||||
|
'source',
|
||||||
|
'notes',
|
||||||
|
'selfbuy',
|
||||||
|
'selfbuy_data',
|
||||||
|
'selfbuy_give_data',
|
||||||
|
'terms_accepted',
|
||||||
|
]
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/literature.js',)
|
js = ('dropdown/js/literature.js',)
|
||||||
|
|
@ -343,8 +279,8 @@ ADULT_CHOICES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class EmailForm(BaseApplicationForm, CommonOrderMixin):
|
class EmailForm(TermsForm):
|
||||||
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_EMAIL_SERVICE
|
terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_EMAIL_SERVICE
|
||||||
|
|
||||||
# this is the code, to change required to false if needed
|
# this is the code, to change required to false if needed
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
@ -352,20 +288,27 @@ class EmailForm(BaseApplicationForm, CommonOrderMixin):
|
||||||
self.fields['adult'].required = True
|
self.fields['adult'].required = True
|
||||||
self.fields['other'].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
|
# TODO: add some javascript to show/hide other-field
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Email
|
model = Email
|
||||||
fields = ['domain', 'address', 'other', 'adult', 'terms_accepted']
|
fields = [
|
||||||
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
'realname',
|
||||||
|
'email',
|
||||||
|
'domain',
|
||||||
|
'address',
|
||||||
|
'other',
|
||||||
|
'adult',
|
||||||
|
'terms_accepted',
|
||||||
|
]
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/mail.js',)
|
js = ('dropdown/js/mail.js',)
|
||||||
|
|
||||||
|
|
||||||
class BusinessCardForm(BaseApplicationForm, CommonOrderMixin):
|
class BusinessCardForm(TermsForm):
|
||||||
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_VISITENKARTEN
|
terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_VISITENKARTEN
|
||||||
|
|
||||||
# this is the code, to change required to false if needed
|
# this is the code, to change required to false if needed
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
@ -375,20 +318,34 @@ class BusinessCardForm(BaseApplicationForm, CommonOrderMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = BusinessCard
|
model = BusinessCard
|
||||||
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
fields = [
|
||||||
fields = ['project', 'data', 'variant', 'url_of_pic', 'send_data_to_print', 'sent_to', 'terms_accepted']
|
'realname',
|
||||||
|
'email',
|
||||||
|
'project',
|
||||||
|
'data',
|
||||||
|
'variant',
|
||||||
|
'url_of_pic',
|
||||||
|
'send_data_to_print',
|
||||||
|
'sent_to',
|
||||||
|
'terms_accepted',
|
||||||
|
]
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/businessCard.js',)
|
js = ('dropdown/js/businessCard.js',)
|
||||||
|
|
||||||
|
|
||||||
class ListForm(BaseApplicationForm, CommonOrderMixin):
|
class ListForm(TermsForm):
|
||||||
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_MAILINGLISTEN
|
terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_MAILINGLISTEN
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = List
|
model = List
|
||||||
fields = ['domain', 'address', 'terms_accepted']
|
fields = [
|
||||||
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
'realname',
|
||||||
|
'email',
|
||||||
|
'domain',
|
||||||
|
'address',
|
||||||
|
'terms_accepted',
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ EMAIL_STATES = {
|
||||||
class TermsConsentMixin(models.Model):
|
class TermsConsentMixin(models.Model):
|
||||||
"""Abstract mixin to add a terms_accepted field for documenting user consent."""
|
"""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:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ class AnonymousViewTestCase(TestCase):
|
||||||
'selfbuy_data': 'NONE',
|
'selfbuy_data': 'NONE',
|
||||||
'selfbuy_give_data': True,
|
'selfbuy_give_data': True,
|
||||||
'check': True,
|
'check': True,
|
||||||
|
'terms_accepted': True,
|
||||||
})
|
})
|
||||||
|
|
||||||
def test_extern_lit_without_consent_fails(self):
|
def test_extern_lit_without_consent_fails(self):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue