Compare commits

...

10 Commits

Author SHA1 Message Date
Oliver Zander 8abc051a59 full width related labels for all product change forms 2025-11-21 13:01:25 +00:00
Oliver Zander b156ee0067 fixed form data for project funding 2025-11-21 13:01:15 +00:00
Oliver Zander 85cb2368cc fixed validation 2025-11-21 13:01:15 +00:00
Oliver Zander f9898ee9b5 removed unnecessary code 2025-11-21 13:01:15 +00:00
Oliver Zander d8b8f28442 improved literature form validation 2025-11-21 13:01:15 +00:00
Oliver Zander 73cd0ff906 properly clean selfbuy data 2025-11-21 13:01:15 +00:00
Oliver Zander 20e5be26c0 added test for failed extern redirect 2025-11-21 13:01:15 +00:00
Oliver Zander 803c94137d fixed typing for help texts in application type 2025-11-21 13:01:15 +00:00
Oliver Zander c0a98d09cd added test for unknown applicant name 2025-11-21 13:01:15 +00:00
Oliver Zander f5c9b0c76c WM-18: improved mails & added full form data 2025-11-21 13:01:15 +00:00
10 changed files with 190 additions and 199 deletions

View File

@ -157,6 +157,9 @@ class BaseProjectAdmin(admin.ModelAdmin):
class Media: class Media:
js = ('dropdown/js/otrs_link.js',) js = ('dropdown/js/otrs_link.js',)
css = {
'all': ['css/full-width-related-labels.css'],
}
granted: bool granted: bool

View File

@ -5,6 +5,7 @@ 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 django.utils.translation import gettext_lazy as trans
from .models import ( from .models import (
Project, Project,
@ -263,10 +264,6 @@ class TermsForm(BaseApplicationForm):
class LiteratureForm(TermsForm): class LiteratureForm(TermsForm):
terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_LITERATURSTIPENDIUM terms_accepted_url = settings.NUTZUNGSBEDINGUNGEN_LITERATURSTIPENDIUM
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['selfbuy_give_data'].required = True
class Meta: class Meta:
model = Literature model = Literature
fields = [ fields = [
@ -285,6 +282,24 @@ class LiteratureForm(TermsForm):
class Media: class Media:
js = ('dropdown/js/literature.js',) js = ('dropdown/js/literature.js',)
def clean(self):
cleaned_data = TermsForm.clean(self)
if self.errors:
return cleaned_data
if cleaned_data['selfbuy'] == 'TRUE':
cleaned_data['selfbuy_data'] = ''
cleaned_data['selfbuy_give_data'] = False
return cleaned_data
for field in 'selfbuy_data', 'selfbuy_give_data':
if not cleaned_data.get(field):
self.add_error(field, trans('This field is required.'))
return cleaned_data
ADULT_CHOICES = { ADULT_CHOICES = {
'TRUE': mark_safe('Ich bin volljährig.'), 'TRUE': mark_safe('Ich bin volljährig.'),

View File

@ -0,0 +1,3 @@
.related-widget-wrapper div label {
width: auto;
}

View File

@ -1,10 +0,0 @@
{% extends 'admin/change_form.html' %}
{% block extrastyle %}
{{ block.super }}
<style>
.related-widget-wrapper div label {
width: auto;
}
</style>
{% endblock %}

View File

@ -1,60 +1,26 @@
<html> <html lang="de">
<body> <body>
<p>Hallo Team Community-Konferenzen &amp; Förderung,</p> <p>Hallo Team Community-Konferenzen &amp; Förderung,</p>
<p>es gibt eine neue Anfrage von {{ data.realname }}.</p> <p>es gibt eine neue Anfrage von {{ data.realname }}.</p>
<p>{{ data.username|default:data.realname }} ({{ data.email }}) fragt an: {{ data.type_label|striptags }}</p> <p>{{ data.username|default:data.realname }} ({{ data.email }}) fragt an: {{ type_label }}</p>
{% if data.choice in data.grant %}<br> <p>
Vorraussichtliche Kosten: {{data.cost}}<br> {% for label, value in form_data.items %}
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}<br> {{ label }}: {{ value }} <br />
Domain: <a href="{{data.domain}}">{{data.domain}}</a><br> {% endfor %}
Adressenbestandteil: {{data.address}} <br> {% endif %} {% if data.choice == 'BIB' %} </p>
Bibliothek: {{data.library}}<br>
Dauer: {{data.duration}} <br> {% elif data.choice == 'ELIT' %}
Datenbank: {{data.library}}<br>
Dauer: {{data.duration}} <br> {% elif data.choice == 'SOFT' %}
Software: {{data.library}}<br>
Dauer: {{data.duration}} <br> {% elif data.choice == 'IFG'%}
Anfrage-URL: <a href="{{data.url}}">{{data.url}}</a> <br> {% elif data.choice == 'LIT'%}
Info zum Werk: {{data.info}}<br>
Bezugsquelle: {{data.source}} <br> {% elif data.choice == 'MAIL'%}
Adressenbestandteil frei gewählt: {{data.other}} <br> {% elif data.choice == 'VIS'%}
Wikimedia-Projekt: {{data.project}}<br>
Persönliche Daten: {{data.data}}<br>
Variante: {{data.variant}}<br>
Sendungsadrese: {{data.send_to}} <br> {% endif %}
<p>Zum Eintrag in der Förderdatenbank: <p>Zum Eintrag in der Förderdatenbank: <a href="{{ urls.admin }}">{{ urls.admin }}</a></p>
{% if data.choice == 'BIB' %}
<a href="{{data.url_prefix}}/admin/input/library/{{data.pk}}/change">{{data.url_prefix}}/admin/input/library/{{data.pk}}/change</a> {% if urls.authorize %}
{% elif data.choice == 'ELIT'%} <p>Zum Genehmigen hier klicken: <a href="{{ urls.authorize }}">{{ urls.authorize }}</a></p>
<a href="{{data.url_prefix}}/admin/input/library/{{data.pk}}/change">{{data.url_prefix}}/admin/input/library/{{data.pk}}/change</a>
{% elif data.choice == 'LIT'%}
<a href="{{data.url_prefix}}/admin/input/literature/{{data.pk}}/change">{{data.url_prefix}}/admin/input/literature/{{data.pk}}/change</a>
{% elif data.choice == 'MAIL'%}
<a href="{{data.url_prefix}}/admin/input/email/{{data.pk}}/change">{{data.url_prefix}}/admin/input/email/{{data.pk}}/change</a>
{% elif data.choice == 'IFG'%}
<a href="{{data.url_prefix}}/admin/input/ifg/{{data.pk}}/change">{{data.url_prefix}}/admin/input/ifg/{{data.pk}}/change</a>
{% elif data.choice == 'LIST'%}
<a href="{{data.url_prefix}}/admin/input/list/{{data.pk}}/change">{{data.url_prefix}}/admin/input/list/{{data.pk}}/change</a>
{% elif data.choice == 'TRAV'%}
<a href="{{data.url_prefix}}/admin/input/travel/{{data.pk}}/change">{{data.url_prefix}}/admin/input/travel/{{data.pk}}/change</a>
{% elif data.choice == 'SOFT'%}
<a href="{{data.url_prefix}}/admin/input/library/{{data.pk}}/change">{{data.url_prefix}}/admin/input/library/{{data.pk}}/change</a>
{% elif data.choice == 'VIS'%}
<a href="{{data.url_prefix}}/admin/input/businesscard/{{data.pk}}/change">{{data.url_prefix}}/admin/input/businesscard/{{data.pk}}/change</a>
{% endif %} {% endif %}
</p>
<p>Zum Genehmigen hier klicken: {% if urls.deny %}
<a href="{{data.url_prefix}}{% url 'authorize' data.choice data.pk %}">{{data.url_prefix}}{% url 'authorize' data.choice data.pk %}</a> <p>Zum Ablehnen hier klicken: <a href="{{ urls.deny }}">{{ urls.deny }}</a></p>
</p> {% endif %}
<p>Zum Ablehnen hier klicken:
<a href="{{data.url_prefix}}{% url 'deny' data.choice data.pk %}">{{data.url_prefix}}{% url 'deny' data.choice data.pk %}</a>
</p>
</body> </body>
</html> </html>

View File

@ -1,52 +1,12 @@
Hallo Team Communitys und Engagement, Hallo Team Community-Konferenzen & Förderung,
es gab einen neuen Antrag von {{data.realname}}. es gibt eine neue Anfrage von {{ data.realname }}.
Der Nutzer mit dem Username {{data.username}} ({{data.email}}) fragt ein_e {{data.type_label|striptags}} an. {{ data.username|default:data.realname }} ({{ data.email }}) fragt an: {{ type_label }}
{% if data.choice in data.grant %}
Vorraussichtliche Kosten: {{data.cost}}
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}
Domain: {{data.domain}}
Adressenbestandteil: {{data.address}} {% endif %} {% if data.choice == 'BIB' %}
Bibliothek: {{data.library}}
Dauer: {{data.duration}} {% elif data.choice == 'ELIT' %}
Datenbank: {{data.library}}
Dauer: {{data.duration}} {% elif data.choice == 'SOFT' %}
Software: {{data.library}}
Dauer: {{data.duration}} {% elif data.choice == 'IFG'%}
Anfrage-URL: {{data.url}} {% elif data.choice == 'LIT'%}
Info zum Werk: {{data.info}}
Bezugsquelle: {{data.source}} {% elif data.choice == 'MAIL'%}
Adressenbestandteil frei gewählt: {{data.other}} {% elif data.choice == 'VIS'%}
Wikimedia-Projekt: {{data.project}}
Persönliche Daten: {{data.data}}
Variante: {{data.variant}}
Sendungsadrese: {{data.send_to}} {% endif %}
Zum Eintrag in der Förderdatenbank: {% for label, value in form_data.items %}{{ label }}: {{ value }}
{% if data.choice == 'BIB' %} {% endfor %}
<a href="{{data.url_prefix}}/admin/input/library/{{data.pk}}/change">{{data.url_prefix}}/admin/input/library/{{data.pk}}/change</a>
{% elif data.choice == 'ELIT'%}
<a href="{{data.url_prefix}}/admin/input/library/{{data.pk}}/change">{{data.url_prefix}}/admin/input/library/{{data.pk}}/change</a>
{% elif data.choice == 'LIT'%}
<a href="{{data.url_prefix}}/admin/input/literature/{{data.pk}}/change">{{data.url_prefix}}/admin/input/literature/{{data.pk}}/change</a>
{% elif data.choice == 'MAIL'%}
<a href="{{data.url_prefix}}/admin/input/email/{{data.pk}}/change">{{data.url_prefix}}/admin/input/email/{{data.pk}}/change</a>
{% elif data.choice == 'IFG'%}
<a href="{{data.url_prefix}}/admin/input/ifg/{{data.pk}}/change">{{data.url_prefix}}/admin/input/ifg/{{data.pk}}/change</a>
{% elif data.choice == 'LIST'%}
<a href="{{data.url_prefix}}/admin/input/list/{{data.pk}}/change">{{data.url_prefix}}/admin/input/list/{{data.pk}}/change</a>
{% elif data.choice == 'TRAV'%}
<a href="{{data.url_prefix}}/admin/input/travel/{{data.pk}}/change">{{data.url_prefix}}/admin/input/travel/{{data.pk}}/change</a>
{% elif data.choice == 'SOFT'%}
<a href="{{data.url_prefix}}/admin/input/library/{{data.pk}}/change">{{data.url_prefix}}/admin/input/library/{{data.pk}}/change</a>
{% elif data.choice == 'VIS'%}
<a href="{{data.url_prefix}}/admin/input/businesscard/{{data.pk}}/change">{{data.url_prefix}}/admin/input/businesscard/{{data.pk}}/change</a>
{% endif %}
Zum Eintrag in der Förderdatenbank: {{ urls.admin }}
Zum Genehmigen hier klicken: {{data.url_prefix}}{% url 'authorize' data.choice data.pk %} {% if urls.authorize %}Zum Genehmigen hier klicken: {{ urls.authorize }}{% endif %}
{% if urls.deny %}Zum Ablehnen hier klicken: {{ urls.deny }}{% endif %}
Zu Ablehnen hier klicken: {{data.url_prefix}}{% url 'deny' data.choice data.pk %}
Stets zu Diensten, Deine Förderdatenbank

View File

@ -1,29 +1,14 @@
<html> <html lang="de">
<body> <body>
<p>Hallo {{ data.username|default:data.realname }},</p> <p>Hallo {{ applicant_name }},</p>
<p>vielen Dank für deine Anfrage ({{ data.type_label|striptags }}), die bei uns eingegangen ist.</p> <p>vielen Dank für deine Anfrage ({{ type_label }}), die bei uns eingegangen ist.</p>
Dies ist eine automatisch generierte E-Mail. Im Folgenden findest du deine Formulareingaben nochmals zu deiner Übersicht:<br> Dies ist eine automatisch generierte E-Mail. Im Folgenden findest du deine Formulareingaben nochmals zu deiner Übersicht:<br>
{% if data.choice in data.grant %}<br>
Vorraussichtliche Kosten: {{data.cost}}<br> {% for label, value in form_data.items %}
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}<br> {{ label }}: {{ value }} <br />
Domain: <a href="{{data.domain}}">{{data.domain}}</a><br> {% endfor %}
Adressenbestandteil: {{data.address}} {% endif %} {% if data.choice == 'BIB' %}<br>
Bibliothek: {{data.library}}<br>
Dauer: {{data.duration}} {% elif data.choice == 'ELIT' %}<br>
Datenbank: {{data.library}}<br>
Dauer: {{data.duration}} {% elif data.choice == 'SOFT' %}<br>
Software: {{data.library}}<br>
Dauer: {{data.duration}} {% elif data.choice == 'IFG'%}<br>
Anfrage-URL: <a href="{{data.url}}">{{data.url}}</a> {% elif data.choice == 'LIT'%}<br>
Info zum Werk: {{data.info}}<br>
Bezugsquelle: {{data.source}} {% elif data.choice == 'MAIL'%}<br>
Adressenbestandteil frei gewählt: {{data.other}} {% elif data.choice == 'VIS'%}<br>
Wikimedia-Projekt: {{data.project}}<br>
Persönliche Daten: {{data.data}}<br>
Variante: {{data.variant}}<br>
Sendungsadrese: {{data.send_to}} {% endif %}<br>
<p>Das Team Community-Konferenzen &amp; Förderung wird sich um deine Anfrage kümmern und sich in den nächsten Tagen bei dir melden. Wenn du Fragen hast, wende dich gern jederzeit an <a href="mailto:community@wikimedia.de">community@wikimedia.de</a>.</p> <p>Das Team Community-Konferenzen &amp; Förderung wird sich um deine Anfrage kümmern und sich in den nächsten Tagen bei dir melden. Wenn du Fragen hast, wende dich gern jederzeit an <a href="mailto:community@wikimedia.de">community@wikimedia.de</a>.</p>
@ -44,4 +29,4 @@ Sendungsadrese: {{data.send_to}} {% endif %}<br>
<p>Datenschutzerklärung:<br> <p>Datenschutzerklärung:<br>
Soweit Sie uns personenbezogene Daten mitteilen, verarbeiten wir diese Daten gemäß unserer <a href="https://www.wikimedia.de/datenschutz/">Datenschutzerklärung </a>.</p> Soweit Sie uns personenbezogene Daten mitteilen, verarbeiten wir diese Daten gemäß unserer <a href="https://www.wikimedia.de/datenschutz/">Datenschutzerklärung </a>.</p>
</body> </body>
</html> </html>

View File

@ -1,29 +1,22 @@
Hallo {{data.realname}}, Hallo {{ applicant_name }},
wir haben Deine Anfrage ({{data.type_label|striptags}}) erhalten. vielen Dank für deine Anfrage ({{type_label}}), die bei uns eingegangen ist.
{% if data.choice in data.grant %}
Vorraussichtliche Kosten: {{data.cost}}
Anmerkungen: {{data.notes}} {% endif %} {% if data.choice in data.domain %}
Domain: {{data.domain}}
Adressenbestandteil: {{data.address}} {% endif %} {% if data.choice == 'BIB' %}
Bibliothek: {{data.library}}
Dauer: {{data.duration}} {% elif data.choice == 'ELIT' %}
Datenbank: {{data.library}}
Dauer: {{data.duration}} {% elif data.choice == 'SOFT' %}
Software: {{data.library}}
Dauer: {{data.duration}} {% elif data.choice == 'IFG'%}
Anfrage-URL: {{data.url}} {% elif data.choice == 'LIT'%}
Info zum Werk: {{data.info}}
Bezugsquelle: {{data.source}} {% elif data.choice == 'MAIL'%}
Adressenbestandteil frei gewählt: {{data.other}} {% elif data.choice == 'VIS'%}
Wikimedia-Projekt: {{data.project}}
Persönliche Daten: {{data.data}}
Variante: {{data.variant}}
Sendungsadrese: {{data.send_to}} {% endif %}
Das Team Comunitys und Engagement wird sich um die Bearbeitung deiner Anfrage kümmern {% for label, value in form_data.items %}{{ label }}: {{ value }}
und sich in den nächsten Tagen bei dir melden. Solltest du Rückfragen haben, {% endfor %}
wende dich gern an community@wikimedia.de.
Viele Grüße, dein freundliches aber komplett unmenschliches automatisches Das Team Community-Konferenzen & Förderung wird sich um deine Anfrage kümmern und sich in den nächsten Tagen bei dir melden. Wenn du Fragen hast, wende dich gern jederzeit an community@wikimedia.de.
Formularbeantwortungssystem.
--
Wikimedia Deutschland e. V. | Tempelhofer Ufer 2324 | 10963 Berlin
Zentrale: +49 30 5771162-0
https://wikimedia.de
Unsere Vision ist eine Welt, in der alle Menschen am Wissen der Menschheit teilhaben, es nutzen und mehren können. Helfen Sie uns dabei!
https://spenden.wikimedia.de
Wikimedia Deutschland Gesellschaft zur Förderung Freien Wissens e. V. Eingetragen im Vereinsregister des Amtsgerichts Charlottenburg, VR 23855 B. Als gemeinnützig anerkannt durch das Finanzamt für Körperschaften I Berlin, Steuernummer 27/029/42207. Geschäftsführende Vorständin: Franziska Heine.
Datenschutzerklärung:
Soweit Sie uns personenbezogene Daten mitteilen, verarbeiten wir diese Daten gemäß unserer Datenschutzerklärung (https://www.wikimedia.de/datenschutz/).

View File

@ -1,13 +1,16 @@
import random import random
from django.forms import model_to_dict
from django.shortcuts import resolve_url from django.shortcuts import resolve_url
from django.test import TestCase from django.test import TestCase
from input.models import Library, TYPE_PROJ from foerderbarometer.constants import *
from input.models import Library, ProjectCategory, WikimediaProject
from input.utils.testing import create_superuser, login, request from input.utils.testing import create_superuser, login, request
from input.views import TYPES from input.views import PROJECT_FUNDING, TYPES, ApplicationView
PATHS = {TYPES[path].code: path for path in TYPES} PATHS = {TYPES[path].code: path for path in TYPES}
PATHS[TYPE_PROJ] = PROJECT_FUNDING[0].path
CODES = list(PATHS) CODES = list(PATHS)
@ -27,6 +30,9 @@ class AnonymousViewTestCase(TestCase):
request(self, 'extern', expected_url=url, data={'url': url}) request(self, 'extern', expected_url=url, data={'url': url})
def test_extern_invalid_url(self):
request(self, 'extern', data={'url': 'https://domain.not/allowed/to/be/redirected/'})
@classmethod @classmethod
def get_step_data(cls, choice, **data): def get_step_data(cls, choice, **data):
return { return {
@ -59,15 +65,15 @@ class AnonymousViewTestCase(TestCase):
def test_extern_types(self): def test_extern_types(self):
types = [ types = [
('BIB', 'Bibliotheksausweis'), (TYPE_BIB, 'Bibliotheksausweis'),
('ELIT', 'Online-Ressource'), (TYPE_ELIT, 'Online-Ressource'),
('MAIL', 'Mailadresse beantragen'), (TYPE_MAIL, 'Mailadresse beantragen'),
('IFG', 'gewonnenen Informationen'), (TYPE_IFG, 'gewonnenen Informationen'),
('LIT', 'Literatur verwenden'), (TYPE_LIT, 'Literatur verwenden'),
('LIST', 'Mailingliste beantragen'), (TYPE_LIST, 'Mailingliste beantragen'),
('TRAV', 'Transportmittel'), (TYPE_TRAV, 'Transportmittel'),
('SOFT', 'Lizenz'), (TYPE_SOFT, 'Lizenz'),
('VIS', 'DIN 5008'), (TYPE_VIS, 'DIN 5008'),
(TYPE_PROJ, 'Projektförderung'), (TYPE_PROJ, 'Projektförderung'),
] ]
@ -79,7 +85,7 @@ class AnonymousViewTestCase(TestCase):
self.assertContains(response, text) self.assertContains(response, text)
def test_extern_travel(self): def test_extern_travel(self):
self.helper_extern('TRAV', 'Transportmittel', { self.helper_extern(TYPE_TRAV, 'Transportmittel', {
'project_name': 'Test', 'project_name': 'Test',
'transport': 'BAHN', 'transport': 'BAHN',
'travelcost': 10, 'travelcost': 10,
@ -90,27 +96,27 @@ class AnonymousViewTestCase(TestCase):
}) })
def test_extern_lit(self): def test_extern_lit(self):
self.helper_extern('LIT', 'Literatur verwenden', { self.helper_extern(TYPE_LIT, 'Literatur verwenden', {
'cost': 20, 'cost': 20,
'info': 'Test', 'info': 'Test',
'source': 'Test', 'source': 'Test',
'notes': '', 'notes': '',
'selfbuy': 'TRUE', 'selfbuy': 'FALSE',
'selfbuy_data': 'NONE', 'selfbuy_data': 'Test',
'selfbuy_give_data': True, 'selfbuy_give_data': True,
'check': True, 'check': True,
'terms_accepted': True, 'terms_accepted': True,
}) })
def test_extern_lit_without_consent_fails(self): def test_extern_lit_without_consent_fails(self):
response = self.helper_extern_base('LIT', 'Literatur verwenden', { response = self.helper_extern_base(TYPE_LIT, 'Literatur verwenden', {
'cost': 20, 'cost': 20,
'info': 'Test', 'info': 'Test',
'source': 'Test', 'source': 'Test',
'notes': '', 'notes': '',
'selfbuy': 'TRUE', 'selfbuy': 'TRUE',
'selfbuy_data': 'NONE', 'selfbuy_data': '',
'selfbuy_give_data': True, 'selfbuy_give_data': False,
'check': False, 'check': False,
}) })
@ -124,9 +130,32 @@ class AnonymousViewTestCase(TestCase):
'notes': '', 'notes': '',
}) })
def test_extern_proj(self):
category = ProjectCategory.objects.order_by('?').first()
wikimedia_project = WikimediaProject.objects.order_by('?').first()
self.helper_extern(TYPE_PROJ, 'Projektförderung', {
'name': 'Test',
'description': 'Test',
'categories': [category.id, 0],
'categories_other': 'Test',
'wikimedia_projects': [wikimedia_project.id, 0],
'wikimedia_projects_other': 'Test',
'start': '2025-01-01',
'end': '2025-01-02',
'participants_estimated': 1,
'cost': 20,
})
def test_extern_invalid_code(self): def test_extern_invalid_code(self):
request(self, 'extern', args=['invalid'], status_code=404) request(self, 'extern', args=['invalid'], status_code=404)
def test_unknown_name(self):
obj = Library(type=Library.TYPE)
data = model_to_dict(obj)
name = ApplicationView.get_recipient_name(obj, data)
self.assertEqual(name, 'Unbekannt')
class AuthenticatedViewTestCase(TestCase): class AuthenticatedViewTestCase(TestCase):

View File

@ -1,12 +1,20 @@
import datetime
from dataclasses import dataclass from dataclasses import dataclass
from smtplib import SMTPException from smtplib import SMTPException
from typing import Optional from typing import Optional
from urllib.parse import urljoin
from django.forms import ChoiceField, Field
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.urls import reverse
from django.utils.choices import flatten_choices
from django.utils.formats import date_format
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.http import url_has_allowed_host_and_scheme from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.text import get_text_list
from django.core.mail import BadHeaderError from django.core.mail import BadHeaderError
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
@ -14,6 +22,7 @@ from django.views.generic import TemplateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django.utils.html import strip_tags from django.utils.html import strip_tags
from input.utils.admin import admin_url_name
from input.utils.mail import build_email, collect_and_attach from input.utils.mail import build_email, collect_and_attach
from .forms import ( from .forms import (
@ -43,6 +52,8 @@ from .models import (
TYPE_SOFT, TYPE_SOFT,
TYPE_TRAV, TYPE_TRAV,
TYPE_VIS, TYPE_VIS,
Project,
ProductCategoryFormField,
) )
HELP_TEXTS = { HELP_TEXTS = {
@ -76,13 +87,13 @@ class ApplicationType:
form_class: type[BaseApplicationForm] form_class: type[BaseApplicationForm]
link: str link: str
label: Optional[str] = None label: Optional[str] = None
help_texts: Optional[str] = None help_texts: Optional[dict] = None
def __post_init__(self): def __post_init__(self):
if self.label is None: if self.label is None:
self.label = TYPE_CHOICES[self.code] self.label = TYPE_CHOICES[self.code]
if self.help_texts is None: if self.help_texts is None: # pragma: no branch
self.help_texts = HELP_TEXTS.get(self.code) self.help_texts = HELP_TEXTS.get(self.code)
@property @property
@ -243,14 +254,7 @@ class ApplicationView(FormView):
def prepare_data(self, form): def prepare_data(self, form):
# Collect cleaned data and mark the current type # Collect cleaned data and mark the current type
return {**form.cleaned_data, 'choice': self.type_code}
data = {**form.cleaned_data, 'choice': self.type_code}
# Special rule for literature applications
if self.type_code == TYPE_LIT and data.get('selfbuy') == 'TRUE':
data['selfbuy_give_data'] = 'False'
return data
def save_obj(self, form, data): def save_obj(self, form, data):
# Save model instance # Save model instance
@ -286,19 +290,9 @@ class ApplicationView(FormView):
def send_mail(self, obj, data): def send_mail(self, obj, data):
# Prepare minimal mail context and send mails # Prepare minimal mail context and send mails
type_label_html = self.type_info.label context = self.get_email_context(obj, data)
type_label_plain = strip_tags(type_label_html)
data['pk'] = obj.pk
data['url_prefix'] = settings.EMAIL_URL_PREFIX
data['type_label'] = type_label_html
context = {'data': data}
applicant_name = self.get_recipient_name(obj, data)
applicant_subject = 'Deine Förderanfrage bei Wikimedia Deutschland' applicant_subject = 'Deine Förderanfrage bei Wikimedia Deutschland'
staff_subject = 'Anfrage {type_label} von {applicant_name}'.format(**context)
staff_subject = f'Anfrage {type_label_plain} von {applicant_name}'
try: try:
self.send_email('applicant', 'ifg_volunteer_mail', applicant_subject, data['email'], context) self.send_email('applicant', 'ifg_volunteer_mail', applicant_subject, data['email'], context)
@ -310,6 +304,59 @@ class ApplicationView(FormView):
obj.delete() obj.delete()
return HttpResponse('Error in sending mails (probably wrong address?). Data not saved!') return HttpResponse('Error in sending mails (probably wrong address?). Data not saved!')
def get_email_context(self, obj, data):
return {
'data': data,
'urls': self.get_urls(obj),
'form_data': self.get_form_data(obj, data),
'applicant_name': self.get_recipient_name(obj, data),
'type_label': self.sanitize_label(self.type_info.label),
}
def get_urls(self, obj, **urls):
urls['admin'] = self.get_absolute_url(admin_url_name(obj, 'change'), obj.id)
if isinstance(obj, Project):
urls['authorize'] = urls['deny'] = None
else:
urls['authorize'] = self.get_absolute_url('authorize', self.type_info.code, obj.id)
urls['deny'] = self.get_absolute_url('deny', self.type_info.code, obj.id)
return urls
@staticmethod
def get_absolute_url(view, *args):
return urljoin(settings.EMAIL_URL_PREFIX, reverse(view, args=args))
def get_form_data(self, obj, data):
return {
self.sanitize_label(field.label): self.format_value(field.field, field.initial)
for field in self.type_info.form_class(initial=data)
}
@staticmethod
def sanitize_label(label: str):
label = strip_tags(label)
words = str.split(label)
return ' '.join(words)
@staticmethod
def format_value(field: Field, value):
if isinstance(field, ProductCategoryFormField):
value = get_text_list(value, 'und')
elif isinstance(field, ChoiceField):
choices = flatten_choices(field.choices)
value = dict(choices).get(value, value)
elif isinstance(value, bool):
value = '' if value else ''
elif isinstance(value, datetime.date):
value = date_format(value)
elif value in field.empty_values:
value = ''
return value
def send_email(self, kind, template_name, subject, recipient, context, *, fail_silently=False): def send_email(self, kind, template_name, subject, recipient, context, *, fail_silently=False):
email = build_email(template_name, context, subject, recipient) email = build_email(template_name, context, subject, recipient)