diff --git a/src/pretix/base/migrations/0217_eventfooterlink_organizerfooterlink.py b/src/pretix/base/migrations/0217_eventfooterlink_organizerfooterlink.py new file mode 100644 index 0000000000..105e5ef503 --- /dev/null +++ b/src/pretix/base/migrations/0217_eventfooterlink_organizerfooterlink.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.12 on 2022-06-15 08:10 + +import django.db.models.deletion +import i18nfield.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0216_checkin_forced_sent'), + ] + + operations = [ + migrations.CreateModel( + name='OrganizerFooterLink', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), + ('label', i18nfield.fields.I18nCharField(max_length=200)), + ('url', models.URLField()), + ('organizer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='footer_links', to='pretixbase.organizer')), + ], + ), + migrations.CreateModel( + name='EventFooterLink', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)), + ('label', i18nfield.fields.I18nCharField(max_length=200)), + ('url', models.URLField()), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='footer_links', to='pretixbase.event')), + ], + ), + ] diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index ff0f86dbc7..c1a500c883 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -718,6 +718,11 @@ class Event(EventMixin, LoggedModel): self.save() self.log_action('pretix.object.cloned', data={'source': other.slug, 'source_id': other.pk}) + for fl in EventFooterLink.objects.filter(event=other): + fl.pk = None + fl.event = self + fl.save(force_insert=True) + tax_map = {} for t in other.tax_rules.all(): tax_map[t.pk] = t @@ -1612,3 +1617,25 @@ class SubEventMetaValue(LoggedModel): super().save(*args, **kwargs) if self.subevent: self.subevent.event.cache.clear() + + +class EventFooterLink(models.Model): + """ + A footer link assigned to an event. + """ + event = models.ForeignKey('Event', on_delete=models.CASCADE, related_name='footer_links') + label = I18nCharField( + max_length=200, + verbose_name=_("Link text"), + ) + url = models.URLField( + verbose_name=_("Link URL"), + ) + + def delete(self, *args, **kwargs): + super().delete(*args, **kwargs) + self.event.cache.clear() + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + self.event.cache.clear() diff --git a/src/pretix/base/models/organizer.py b/src/pretix/base/models/organizer.py index 96633995c5..9fb0ad89f2 100644 --- a/src/pretix/base/models/organizer.py +++ b/src/pretix/base/models/organizer.py @@ -46,6 +46,7 @@ from django.utils.crypto import get_random_string from django.utils.functional import cached_property from django.utils.timezone import get_current_timezone, make_aware, now from django.utils.translation import gettext_lazy as _ +from i18nfield.fields import I18nCharField from pretix.base.models.base import LoggedModel from pretix.base.validators import OrganizerSlugBanlistValidator @@ -464,3 +465,25 @@ class TeamAPIToken(models.Model): return self.get_events_with_any_permission() else: return self.team.organizer.events.none() + + +class OrganizerFooterLink(models.Model): + """ + A footer link assigned to an organizer. + """ + organizer = models.ForeignKey('Organizer', on_delete=models.CASCADE, related_name='footer_links') + label = I18nCharField( + max_length=200, + verbose_name=_("Link text"), + ) + url = models.URLField( + verbose_name=_("Link URL"), + ) + + def delete(self, *args, **kwargs): + super().delete(*args, **kwargs) + self.organizer.cache.clear() + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + self.organizer.cache.clear() diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 22aadcb416..4c479f259e 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -41,7 +41,9 @@ from django.conf import settings from django.core.exceptions import ValidationError from django.core.validators import validate_email from django.db.models import Prefetch, Q, prefetch_related_objects -from django.forms import CheckboxSelectMultiple, formset_factory +from django.forms import ( + CheckboxSelectMultiple, formset_factory, inlineformset_factory, +) from django.urls import reverse from django.utils.functional import cached_property from django.utils.html import escape @@ -58,7 +60,7 @@ from pretix.base.channels import get_all_sales_channels from pretix.base.email import get_available_placeholders from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm from pretix.base.models import Event, Organizer, TaxRule, Team -from pretix.base.models.event import EventMetaValue, SubEvent +from pretix.base.models.event import EventFooterLink, EventMetaValue, SubEvent from pretix.base.reldate import RelativeDateField, RelativeDateTimeField from pretix.base.settings import ( PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS, validate_event_settings, @@ -1484,3 +1486,25 @@ ConfirmTextFormset = formset_factory( formset=BaseConfirmTextFormSet, can_order=True, can_delete=True, extra=0 ) + + +class EventFooterLinkForm(I18nModelForm): + class Meta: + model = EventFooterLink + fields = ('label', 'url') + + +class BaseEventFooterLinkFormSet(I18nFormSetMixin, forms.BaseInlineFormSet): + def __init__(self, *args, **kwargs): + event = kwargs.pop('event', None) + if event: + kwargs['locales'] = event.settings.get('locales') + super().__init__(*args, **kwargs) + + +EventFooterLinkFormset = inlineformset_factory( + Event, EventFooterLink, + EventFooterLinkForm, + formset=BaseEventFooterLinkFormSet, + can_order=False, can_delete=True, extra=0 +) diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py index 576d5b07e6..d11be47a4d 100644 --- a/src/pretix/control/forms/organizer.py +++ b/src/pretix/control/forms/organizer.py @@ -39,12 +39,13 @@ from django import forms from django.conf import settings from django.core.exceptions import ValidationError from django.db.models import Q +from django.forms import inlineformset_factory from django.forms.utils import ErrorDict from django.utils.crypto import get_random_string from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _, pgettext_lazy from django_scopes.forms import SafeModelMultipleChoiceField -from i18nfield.forms import I18nFormField, I18nTextarea +from i18nfield.forms import I18nFormField, I18nFormSetMixin, I18nTextarea from phonenumber_field.formfields import PhoneNumberField from pytz import common_timezones @@ -60,6 +61,7 @@ from pretix.base.models import ( Customer, Device, EventMetaProperty, Gate, GiftCard, Membership, MembershipType, Organizer, Team, ) +from pretix.base.models.organizer import OrganizerFooterLink from pretix.base.settings import PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS from pretix.control.forms import ExtFileField, SplitDateTimeField from pretix.control.forms.event import ( @@ -682,3 +684,25 @@ class MembershipUpdateForm(forms.ModelForm): titles=self.instance.customer.organizer.settings.name_scheme_titles, label=_('Attendee name'), ) + + +class OrganizerFooterLinkForm(I18nModelForm): + class Meta: + model = OrganizerFooterLink + fields = ('label', 'url') + + +class BaseOrganizerFooterLinkFormSet(I18nFormSetMixin, forms.BaseInlineFormSet): + def __init__(self, *args, **kwargs): + organizer = kwargs.pop('organizer', None) + if organizer: + kwargs['locales'] = organizer.settings.get('locales') + super().__init__(*args, **kwargs) + + +OrganizerFooterLinkFormset = inlineformset_factory( + Organizer, OrganizerFooterLink, + OrganizerFooterLinkForm, + formset=BaseOrganizerFooterLinkFormSet, + can_order=False, can_delete=True, extra=0 +) diff --git a/src/pretix/control/logdisplay.py b/src/pretix/control/logdisplay.py index dd85f10ad0..e42349f4a0 100644 --- a/src/pretix/control/logdisplay.py +++ b/src/pretix/control/logdisplay.py @@ -314,6 +314,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs): 'pretix.object.cloned': _('This object has been created by cloning.'), 'pretix.organizer.changed': _('The organizer has been changed.'), 'pretix.organizer.settings': _('The organizer settings have been changed.'), + 'pretix.organizer.footerlinks.changed': _('The footer links have been changed.'), 'pretix.giftcards.acceptance.added': _('Gift card acceptance for another organizer has been added.'), 'pretix.giftcards.acceptance.removed': _('Gift card acceptance for another organizer has been removed.'), 'pretix.webhook.created': _('The webhook has been created.'), @@ -468,6 +469,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs): 'pretix.event.testmode.deactivated': _('The test mode has been disabled.'), 'pretix.event.added': _('The event has been created.'), 'pretix.event.changed': _('The event details have been changed.'), + 'pretix.event.footerlinks.changed': _('The footer links have been changed.'), 'pretix.event.question.option.added': _('An answer option has been added to the question.'), 'pretix.event.question.option.deleted': _('An answer option has been removed from the question.'), 'pretix.event.question.option.changed': _('An answer option has been changed.'), diff --git a/src/pretix/control/templates/pretixcontrol/event/settings.html b/src/pretix/control/templates/pretixcontrol/event/settings.html index 1c2784f653..92029cf9bc 100644 --- a/src/pretix/control/templates/pretixcontrol/event/settings.html +++ b/src/pretix/control/templates/pretixcontrol/event/settings.html @@ -69,7 +69,8 @@
- + {% trans "See invoice settings" %}
@@ -101,7 +103,8 @@- + {% trans "Manage questions" %}
@@ -232,10 +235,74 @@ {% bootstrap_field sform.display_net_prices layout="control" %} {% bootstrap_field sform.show_variations_expanded layout="control" %} {% bootstrap_field sform.hide_sold_out layout="control" %} - {% url "control:organizer.edit" organizer=request.organizer.slug as org_url %} - {% propagated request.event org_url "meta_noindex" %} - {% bootstrap_field sform.meta_noindex layout="control" %} - {% endpropagated %} + ++ {% blocktrans trimmed %} + These links will be shown in the footer of your ticket shop. You could + for example link your terms of service here. Your contact address, imprint, and privacy + policy will be linked automatically (if you configured them), so you do not need to add + them here. + {% endblocktrans %} +
++ +
++ {% blocktrans trimmed %} + These links will be shown in the footer of your ticket shop. You could + for example link your terms of service here. Your contact address, imprint, and privacy + policy will be linked automatically (if you configured them), so you do not need to add + them here. + {% endblocktrans %} +
++ {% blocktrans trimmed %} + The links you configure here will also be shown on all of your events. + {% endblocktrans %} +
++ +
+