forked from beba/foerderbarometer
Merge branch 'feature/terms-consent' into 'cosmocode'
Store user agreement to terms See merge request wikimedia/foerderbarometer!4
This commit is contained in:
commit
aa5c981872
|
|
@ -119,7 +119,7 @@ AUTH_PASSWORD_VALIDATORS = password_validators(
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
LANGUAGE_CODE = env('LANGUAGE_CODE', 'en-us')
|
LANGUAGE_CODE = env('LANGUAGE_CODE', 'de')
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
TIME_ZONE = env('TIME_ZONE', 'UTC')
|
TIME_ZONE = env('TIME_ZONE', 'UTC')
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class ProjectAdmin(admin.ModelAdmin):
|
||||||
class BusinessCardAdmin(admin.ModelAdmin):
|
class BusinessCardAdmin(admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date', 'project')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date', 'project')
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'project')
|
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'project', 'terms_accepted')
|
||||||
list_display_links = ('realname', 'service_id')
|
list_display_links = ('realname', 'service_id')
|
||||||
# action = ['export_as_csv']
|
# action = ['export_as_csv']
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
|
|
@ -69,7 +69,7 @@ class BusinessCardAdmin(admin.ModelAdmin):
|
||||||
class LiteratureAdmin(admin.ModelAdmin):
|
class LiteratureAdmin(admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date')
|
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
|
||||||
list_display_links = ('realname', 'service_id')
|
list_display_links = ('realname', 'service_id')
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
readonly_fields = ['service_id']
|
readonly_fields = ['service_id']
|
||||||
|
|
@ -142,7 +142,7 @@ class TravelAdmin(admin.ModelAdmin):
|
||||||
class EmailAdmin(admin.ModelAdmin):
|
class EmailAdmin(admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date')
|
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
|
||||||
list_display_links = ('realname', 'service_id')
|
list_display_links = ('realname', 'service_id')
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
radio_fields = {'adult': admin.VERTICAL}
|
radio_fields = {'adult': admin.VERTICAL}
|
||||||
|
|
@ -155,7 +155,7 @@ class EmailAdmin(admin.ModelAdmin):
|
||||||
class ListAdmin(admin.ModelAdmin):
|
class ListAdmin(admin.ModelAdmin):
|
||||||
save_as = True
|
save_as = True
|
||||||
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
search_fields = ('realname', 'service_id', 'granted', 'granted_date')
|
||||||
list_display = ('realname', 'service_id', 'granted', 'granted_date')
|
list_display = ('realname', 'service_id', 'granted', 'granted_date', 'terms_accepted')
|
||||||
list_display_links = ('realname', 'service_id')
|
list_display_links = ('realname', 'service_id')
|
||||||
date_hierarchy = 'granted_date'
|
date_hierarchy = 'granted_date'
|
||||||
readonly_fields = ['service_id']
|
readonly_fields = ['service_id']
|
||||||
|
|
|
||||||
|
|
@ -156,22 +156,16 @@ class CheckForm(FdbForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['check'] = BooleanField(
|
|
||||||
required=True,
|
# Check if the model field 'terms_accepted' is present
|
||||||
label=format_html(
|
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",
|
"Ich stimme den <a href='{}'>Nutzungsbedingungen</a> zu",
|
||||||
self.termstoaccept
|
self.termstoaccept
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""Baseclass for all classes which need a check for Nutzungsbedingungen"""
|
|
||||||
# def __init__(self, *args, **kwargs):
|
|
||||||
# check = BooleanField(required=True,
|
|
||||||
# label=format_html("Ich stimme den <a href='{}'>Nutzungsbedingungen</a> zu",
|
|
||||||
# termstoaccept))
|
|
||||||
# NUTZUNGSBEDINGUNGEN))
|
|
||||||
|
|
||||||
|
|
||||||
class LiteratureForm(CheckForm):
|
class LiteratureForm(CheckForm):
|
||||||
|
|
@ -182,7 +176,7 @@ class LiteratureForm(CheckForm):
|
||||||
self.fields['selfbuy_give_data'].required = True
|
self.fields['selfbuy_give_data'].required = True
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Literature
|
model = Literature
|
||||||
fields = ['cost', 'info', 'source', 'notes', 'selfbuy', 'selfbuy_data', 'selfbuy_give_data']
|
fields = ['cost', 'info', 'source', 'notes', 'selfbuy', 'selfbuy_data', 'selfbuy_give_data', 'terms_accepted']
|
||||||
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/literature.js',)
|
js = ('dropdown/js/literature.js',)
|
||||||
|
|
@ -209,7 +203,7 @@ class EmailForm(CheckForm):
|
||||||
# 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']
|
fields = ['domain', 'address', 'other', 'adult', 'terms_accepted']
|
||||||
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
||||||
class Media:
|
class Media:
|
||||||
js = ('dropdown/js/mail.js',)
|
js = ('dropdown/js/mail.js',)
|
||||||
|
|
@ -227,7 +221,7 @@ class BusinessCardForm(CheckForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = BusinessCard
|
model = BusinessCard
|
||||||
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
exclude = ['intern_notes', 'survey_mail_send', 'mail_state']
|
||||||
fields = ['project', 'data', 'variant', 'url_of_pic', 'send_data_to_print', 'sent_to']
|
fields = ['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',)
|
||||||
|
|
||||||
|
|
@ -236,5 +230,5 @@ class ListForm(CheckForm):
|
||||||
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_MAILINGLISTEN
|
termstoaccept = settings.NUTZUNGSBEDINGUNGEN_MAILINGLISTEN
|
||||||
class Meta:
|
class Meta:
|
||||||
model = List
|
model = List
|
||||||
fields = ['domain', 'address']
|
fields = ['domain', 'address', 'terms_accepted']
|
||||||
exclude = ['intern_notes', 'survey_mail_send','mail_state']
|
exclude = ['intern_notes', 'survey_mail_send','mail_state']
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Generated by Django 5.2.5 on 2025-08-26 11:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('input', '0098_add_eliterature_and_software_proxies'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='businesscard',
|
||||||
|
name='terms_accepted',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Nutzungsbedingungen zugestimmt'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='email',
|
||||||
|
name='terms_accepted',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Nutzungsbedingungen zugestimmt'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='list',
|
||||||
|
name='terms_accepted',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Nutzungsbedingungen zugestimmt'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='literature',
|
||||||
|
name='terms_accepted',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Nutzungsbedingungen zugestimmt'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -9,6 +9,16 @@ EMAIL_STATES = {'NONE': 'noch keine Mail versendet',
|
||||||
'CLOSE': 'die Projektabschlussmail wurde versendet',
|
'CLOSE': 'die Projektabschlussmail wurde versendet',
|
||||||
'END': 'alle automatischen Mails, auch surveyMail, wurden versendet'}
|
'END': 'alle automatischen Mails, auch surveyMail, wurden versendet'}
|
||||||
|
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
class Volunteer(models.Model):
|
class Volunteer(models.Model):
|
||||||
realname = models.CharField(max_length=200, null=True, verbose_name="Realname",
|
realname = models.CharField(max_length=200, null=True, verbose_name="Realname",
|
||||||
help_text="Bitte gib deinen Vornamen und deinen Nachnamen ein.", default='')
|
help_text="Bitte gib deinen Vornamen und deinen Nachnamen ein.", default='')
|
||||||
|
|
@ -323,7 +333,7 @@ SELFBUY_CHOICES = {'TRUE': mark_safe('Ich möchte das Werk selbst kaufen und per
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Literature(Grant):
|
class Literature(TermsConsentMixin, Grant):
|
||||||
info = models.CharField(max_length=500, verbose_name='Informationen zum Werk',
|
info = models.CharField(max_length=500, verbose_name='Informationen zum Werk',
|
||||||
help_text=mark_safe("Bitte gib alle Informationen zum benötigten Werk an,<br>\
|
help_text=mark_safe("Bitte gib alle Informationen zum benötigten Werk an,<br>\
|
||||||
die eine eindeutige Identifizierung ermöglichen (Autor, Titel, Verlag, ISBN, ...)"))
|
die eine eindeutige Identifizierung ermöglichen (Autor, Titel, Verlag, ISBN, ...)"))
|
||||||
|
|
@ -367,7 +377,7 @@ ADULT_CHOICES = {'TRUE': mark_safe('Ich bin volljährig.'),
|
||||||
'FALSE': mark_safe('Ich bin noch nicht volljährig.')
|
'FALSE': mark_safe('Ich bin noch nicht volljährig.')
|
||||||
}
|
}
|
||||||
|
|
||||||
class Email(Domain):
|
class Email(TermsConsentMixin, Domain):
|
||||||
address = models.CharField(max_length=50,
|
address = models.CharField(max_length=50,
|
||||||
choices=MAIL_CHOICES.items(),
|
choices=MAIL_CHOICES.items(),
|
||||||
default='USERNAME', verbose_name='Adressbestandteil',
|
default='USERNAME', verbose_name='Adressbestandteil',
|
||||||
|
|
@ -377,7 +387,7 @@ class Email(Domain):
|
||||||
adult = models.CharField( max_length=10, verbose_name='Volljährigkeit', choices=ADULT_CHOICES.items(), default='FALSE')
|
adult = models.CharField( max_length=10, verbose_name='Volljährigkeit', choices=ADULT_CHOICES.items(), default='FALSE')
|
||||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
||||||
|
|
||||||
class List(Domain):
|
class List(TermsConsentMixin, Domain):
|
||||||
address = models.CharField(max_length=50, default='NO_ADDRESS',
|
address = models.CharField(max_length=50, default='NO_ADDRESS',
|
||||||
verbose_name="Adressbestandteil für Projektmailingliste",
|
verbose_name="Adressbestandteil für Projektmailingliste",
|
||||||
help_text=mark_safe("Bitte gib hier den gewünschten Adressbestandteil an,<br>der sich vor der Domain befinden soll."))
|
help_text=mark_safe("Bitte gib hier den gewünschten Adressbestandteil an,<br>der sich vor der Domain befinden soll."))
|
||||||
|
|
@ -396,7 +406,7 @@ PROJECT_CHOICE = {'PEDIA': 'Wikipedia',
|
||||||
BC_VARIANT = {'PIC': 'mit Bild',
|
BC_VARIANT = {'PIC': 'mit Bild',
|
||||||
'NOPIC': 'ohne Bild'}
|
'NOPIC': 'ohne Bild'}
|
||||||
|
|
||||||
class BusinessCard(Extern):
|
class BusinessCard(TermsConsentMixin, Extern):
|
||||||
project = models.CharField(max_length=20, choices=PROJECT_CHOICE.items(),
|
project = models.CharField(max_length=20, choices=PROJECT_CHOICE.items(),
|
||||||
default='PEDIA', verbose_name='Wikimedia-Projekt',
|
default='PEDIA', verbose_name='Wikimedia-Projekt',
|
||||||
help_text='Für welches Wikimedia-Projekt möchtest Du Visitenkarten?')
|
help_text='Für welches Wikimedia-Projekt möchtest Du Visitenkarten?')
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ class AnonymousViewTestCase(TestCase):
|
||||||
def test_index(self):
|
def test_index(self):
|
||||||
response = request(self, 'index')
|
response = request(self, 'index')
|
||||||
|
|
||||||
self.assertContains(response,'<a href="https://srcsrv.wikimedia.de/beba/foerderbarometer">Sourcecode</a>')
|
self.assertContains(response, '<a href="https://srcsrv.wikimedia.de/beba/foerderbarometer">Sourcecode</a>')
|
||||||
|
|
||||||
def test_extern(self):
|
def test_extern(self):
|
||||||
request(self, 'extern')
|
request(self, 'extern')
|
||||||
|
|
@ -131,3 +131,32 @@ class AuthenticatedViewTestCase(TestCase):
|
||||||
|
|
||||||
def test_deny_error(self):
|
def test_deny_error(self):
|
||||||
self.helper_auth_deny_error('deny')
|
self.helper_auth_deny_error('deny')
|
||||||
|
|
||||||
|
|
||||||
|
class TermsConsentViewTests(AnonymousViewTestCase):
|
||||||
|
|
||||||
|
def test_extern_lit(self):
|
||||||
|
"""
|
||||||
|
Negative case: Literature form without terms_accepted must not succeed
|
||||||
|
and should display a required field error.
|
||||||
|
"""
|
||||||
|
# Step 1
|
||||||
|
resp = request(self, 'extern', data=self.get_first_step_data('LIT'))
|
||||||
|
self.assertContains(resp, 'Literatur verwenden')
|
||||||
|
|
||||||
|
# Step 2: submit without terms_accepted
|
||||||
|
payload = self.get_step_data(1, {
|
||||||
|
'cost': 20,
|
||||||
|
'info': 'Test',
|
||||||
|
'source': 'Test',
|
||||||
|
'notes': '',
|
||||||
|
'selfbuy': 'TRUE',
|
||||||
|
'selfbuy_data': 'NONE',
|
||||||
|
'selfbuy_give_data': 'on', # checkbox checked
|
||||||
|
# terms_accepted intentionally omitted
|
||||||
|
})
|
||||||
|
resp = request(self, 'extern', data=payload)
|
||||||
|
|
||||||
|
# Expect to remain on step 2 with required field error
|
||||||
|
self.assertNotContains(resp, 'Deine Anfrage wurde gesendet.')
|
||||||
|
self.assertContains(resp, 'Dieses Feld ist zwingend erforderlich.')
|
||||||
Loading…
Reference in New Issue