diff --git a/src/pretix/api/serializers/event.py b/src/pretix/api/serializers/event.py index c6fef3d69c..4276b756b9 100644 --- a/src/pretix/api/serializers/event.py +++ b/src/pretix/api/serializers/event.py @@ -747,6 +747,7 @@ class EventSettingsSerializer(SettingsSerializer): 'max_items_per_order', 'reservation_time', 'contact_mail', + 'contact_url', 'show_variations_expanded', 'hide_sold_out', 'meta_noindex', diff --git a/src/pretix/api/serializers/organizer.py b/src/pretix/api/serializers/organizer.py index 9df859701b..c62b91a3d8 100644 --- a/src/pretix/api/serializers/organizer.py +++ b/src/pretix/api/serializers/organizer.py @@ -576,6 +576,7 @@ class OrganizerSettingsSerializer(SettingsSerializer): 'customer_accounts_require_login_for_order_access', 'invoice_regenerate_allowed', 'contact_mail', + 'contact_url', 'imprint_url', 'organizer_info_text', 'event_list_type', diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index 53fbbfda37..ae679d186c 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -2284,6 +2284,17 @@ DEFAULTS = { help_text=_("We'll show this publicly to allow attendees to contact you.") ) }, + 'contact_url': { + 'default': None, + 'type': str, + 'serializer_class': serializers.URLField, + 'form_class': forms.URLField, + 'form_kwargs': dict( + label=_("Contact URL"), + help_text=_("If you set this, the footer contact link will point here instead of using the email address above. " + "Please note that you still need to add a contact email address that will be shared with all emails you send.") + ) + }, 'imprint_url': { 'default': None, 'type': str, diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 588ee56a82..5d86c0f943 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -625,6 +625,7 @@ class EventSettingsForm(EventSettingsValidationMixin, FormPlaceholderMixin, Sett 'max_items_per_order', 'reservation_time', 'contact_mail', + 'contact_url', 'show_variations_expanded', 'hide_sold_out', 'meta_noindex', @@ -1904,6 +1905,12 @@ class QuickSetupForm(I18nForm): required=False, help_text=_("We'll show this publicly to allow attendees to contact you.") ) + contact_url = forms.URLField( + label=_("Contact URL"), + required=False, + help_text=_("If you set this, the footer contact link will point here instead of using the email address above. " + "Please note that you still need to add a contact email address that will be shared with all emails you send.") + ) total_quota = forms.IntegerField( label=_("Total capacity"), min_value=0, diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py index 0078562506..2bafd2d420 100644 --- a/src/pretix/control/forms/organizer.py +++ b/src/pretix/control/forms/organizer.py @@ -604,6 +604,7 @@ class OrganizerSettingsForm(SettingsForm): 'customer_accounts_require_login_for_order_access', 'invoice_regenerate_allowed', 'contact_mail', + 'contact_url', 'imprint_url', 'organizer_info_text', 'event_list_type', diff --git a/src/pretix/control/templates/pretixcontrol/event/quick_setup.html b/src/pretix/control/templates/pretixcontrol/event/quick_setup.html index 571c1c5e3d..733ebd08a1 100644 --- a/src/pretix/control/templates/pretixcontrol/event/quick_setup.html +++ b/src/pretix/control/templates/pretixcontrol/event/quick_setup.html @@ -193,6 +193,7 @@ {% endblocktrans %}

{% bootstrap_field form.contact_mail layout="control" %} + {% bootstrap_field form.contact_url layout="control" %} {% bootstrap_field form.imprint_url layout="control" %} diff --git a/src/pretix/control/templates/pretixcontrol/event/settings.html b/src/pretix/control/templates/pretixcontrol/event/settings.html index 8d9a46f732..8077e57189 100644 --- a/src/pretix/control/templates/pretixcontrol/event/settings.html +++ b/src/pretix/control/templates/pretixcontrol/event/settings.html @@ -30,6 +30,7 @@ {% bootstrap_field form.date_admission layout="control" %} {% bootstrap_field form.currency layout="control" %} {% bootstrap_field sform.contact_mail layout="control" %} + {% bootstrap_field sform.contact_url layout="control" %} {% bootstrap_field sform.imprint_url layout="control" %} {% bootstrap_field form.is_public layout="control" %} {% bootstrap_field form.all_sales_channels layout="control" %} diff --git a/src/pretix/control/templates/pretixcontrol/organizers/edit.html b/src/pretix/control/templates/pretixcontrol/organizers/edit.html index 960c6fb4f5..0c2090d38e 100644 --- a/src/pretix/control/templates/pretixcontrol/organizers/edit.html +++ b/src/pretix/control/templates/pretixcontrol/organizers/edit.html @@ -34,6 +34,7 @@ {% endif %} {% bootstrap_field sform.imprint_url layout="control" %} {% bootstrap_field sform.contact_mail layout="control" %} + {% bootstrap_field sform.contact_url layout="control" %} {% bootstrap_field sform.organizer_info_text layout="control" %} {% bootstrap_field sform.event_team_provisioning layout="control" %} {% if sform.allowed_restricted_plugins %} diff --git a/src/pretix/presale/templates/pretixpresale/event/base.html b/src/pretix/presale/templates/pretixpresale/event/base.html index b3f566c837..660ce73868 100644 --- a/src/pretix/presale/templates/pretixpresale/event/base.html +++ b/src/pretix/presale/templates/pretixpresale/event/base.html @@ -219,8 +219,8 @@ {% endif %} {% endblock %} {% block footernav %} - {% if request.event.settings.contact_mail %} -
  • {% trans "Contact" %}
  • + {% if request.event.settings.contact_url or request.event.settings.contact_mail %} +
  • {% trans "Contact" %}
  • {% endif %} {% if request.event.settings.privacy_url %}
  • {% trans "Privacy policy" %}
  • diff --git a/src/pretix/presale/templates/pretixpresale/organizers/base.html b/src/pretix/presale/templates/pretixpresale/organizers/base.html index da77df7cd2..2f9affeae6 100644 --- a/src/pretix/presale/templates/pretixpresale/organizers/base.html +++ b/src/pretix/presale/templates/pretixpresale/organizers/base.html @@ -97,8 +97,10 @@ {% endblock %} {% endblock %} {% block footernav %} - {% if not request.event and request.organizer.settings.contact_mail %} -
  • {% trans "Contact" %}
  • + {% if not request.event %} + {% if request.organizer.settings.contact_url or request.organizer.settings.contact_mail %} +
  • {% trans "Contact" %}
  • + {% endif %} {% endif %} {% if not request.event and request.organizer.settings.privacy_url %}
  • {% trans "Privacy policy" %}
  • diff --git a/src/tests/api/test_events.py b/src/tests/api/test_events.py index fd19291855..3787216fc7 100644 --- a/src/tests/api/test_events.py +++ b/src/tests/api/test_events.py @@ -1413,11 +1413,13 @@ def test_event_create_with_seating_maps(token_client, organizer, event, meta_pro @pytest.mark.django_db def test_get_event_settings(token_client, organizer, event): event.settings.imprint_url = "https://example.org" + event.settings.contact_url = "https://example.org/contact" resp = token_client.get( '/api/v1/organizers/{}/events/{}/settings/'.format(organizer.slug, event.slug), ) assert resp.status_code == 200 assert resp.data['imprint_url'] == "https://example.org" + assert resp.data['contact_url'] == "https://example.org/contact" assert resp.data['seating_allow_blocked_seats_for_channel'] == [] resp = token_client.get( @@ -1443,9 +1445,11 @@ def test_patch_event_settings(token_client, organizer, event, team): team.save() organizer.settings.imprint_url = 'https://example.org' + organizer.settings.contact_url = 'https://example.org/contact' resp = token_client.patch( '/api/v1/organizers/{}/events/{}/settings/'.format(organizer.slug, event.slug), { + 'contact_url': 'https://example.com/contact', 'imprint_url': 'https://example.com', 'confirm_texts': [ { @@ -1458,10 +1462,12 @@ def test_patch_event_settings(token_client, organizer, event, team): format='json' ) assert resp.status_code == 200 + assert resp.data['contact_url'] == "https://example.com/contact" assert resp.data['imprint_url'] == "https://example.com" assert resp.data['seating_allow_blocked_seats_for_channel'] == ['web'] assert not resp.data['reusable_media_active'] event.settings.flush() + assert event.settings.contact_url == 'https://example.com/contact' assert event.settings.imprint_url == 'https://example.com' assert event.settings.seating_allow_blocked_seats_for_channel == ['web'] assert not event.settings.reusable_media_active @@ -1471,6 +1477,7 @@ def test_patch_event_settings(token_client, organizer, event, team): resp = token_client.patch( '/api/v1/organizers/{}/events/{}/settings/'.format(organizer.slug, event.slug), { + 'contact_url': 'https://example.com/contact', 'imprint_url': 'https://example.com', 'confirm_texts': [ { @@ -1513,13 +1520,16 @@ def test_patch_event_settings(token_client, organizer, event, team): resp = token_client.patch( '/api/v1/organizers/{}/events/{}/settings/'.format(organizer.slug, event.slug), { + 'contact_url': None, 'imprint_url': None, }, format='json' ) assert resp.status_code == 200 + assert resp.data['contact_url'] == "https://example.org/contact" assert resp.data['imprint_url'] == "https://example.org" event.settings.flush() + assert event.settings.contact_url == 'https://example.org/contact' assert event.settings.imprint_url == 'https://example.org' resp = token_client.put( @@ -1583,6 +1593,18 @@ def test_patch_event_settings(token_client, organizer, event, team): @pytest.mark.django_db def test_patch_event_settings_validation(token_client, organizer, event): + resp = token_client.patch( + '/api/v1/organizers/{}/events/{}/settings/'.format(organizer.slug, event.slug), + { + 'contact_url': 'invalid', + }, + format='json' + ) + assert resp.status_code == 400 + assert resp.data == { + 'contact_url': ['Enter a valid URL.'] + } + resp = token_client.patch( '/api/v1/organizers/{}/events/{}/settings/'.format(organizer.slug, event.slug), { diff --git a/src/tests/api/test_organizers.py b/src/tests/api/test_organizers.py index 2109e8de18..1ec00a5ad5 100644 --- a/src/tests/api/test_organizers.py +++ b/src/tests/api/test_organizers.py @@ -148,6 +148,27 @@ def test_patch_settings(token_client, organizer): ) assert resp.status_code == 400 + resp = token_client.patch( + '/api/v1/organizers/{}/settings/'.format(organizer.slug), + { + 'contact_url': 'invalid' + }, + format='json' + ) + assert resp.status_code == 400 + + resp = token_client.patch( + '/api/v1/organizers/{}/settings/'.format(organizer.slug), + { + 'contact_url': 'https://example.org/contact', + }, + format='json' + ) + assert resp.status_code == 200 + assert resp.data['contact_url'] == 'https://example.org/contact' + organizer.settings.flush() + assert organizer.settings.contact_url == 'https://example.org/contact' + resp = token_client.patch( '/api/v1/organizers/{}/settings/'.format(organizer.slug), { diff --git a/src/tests/presale/test_event.py b/src/tests/presale/test_event.py index 5ffd28db39..70dade2bed 100644 --- a/src/tests/presale/test_event.py +++ b/src/tests/presale/test_event.py @@ -81,6 +81,16 @@ class EventMiddlewareTest(EventTestMixin, SoupTest): doc = self.get_doc('/%s/%s/' % (self.orga.slug, self.event.slug)) self.assertIn(str(self.event.name), doc.find("h1").text) + def test_footer_contact_url_overrides_mailto(self): + self.event.settings.contact_mail = 'orga@example.org' + self.event.settings.contact_url = 'https://example.org/contact' + + doc = self.get_doc('/%s/%s/' % (self.orga.slug, self.event.slug)) + contact_link = doc.find('a', string='Contact') + + self.assertIsNotNone(contact_link) + self.assertTrue(contact_link['href'].startswith('/redirect/?url=https%3A//example.org/contact')) + def test_no_session_cookie_set_on_event_index_view(self): resp = self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug)) self.assertEqual(resp.status_code, 200) diff --git a/src/tests/presale/test_organizer_page.py b/src/tests/presale/test_organizer_page.py index ead641455d..263d97f51f 100644 --- a/src/tests/presale/test_organizer_page.py +++ b/src/tests/presale/test_organizer_page.py @@ -46,6 +46,17 @@ def test_organizer_page_shown(env, client): assert 'MRMCD e.V.' in r.rendered_content +@pytest.mark.django_db +def test_organizer_footer_contact_url_overrides_mailto(env, client): + env[0].settings.contact_mail = 'orga@example.org' + env[0].settings.contact_url = 'https://example.org/contact' + + r = client.get('/mrmcd/') + + assert '/redirect/?url=https%3A//example.org/contact' in r.rendered_content + assert 'mailto:orga@example.org' not in r.rendered_content + + @pytest.mark.django_db def test_public_event_on_page(env, client): env[1].is_public = True