diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index ffa5e4630..c52838041 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -305,7 +305,7 @@ Your {event} team""")) class SettingsProxy: """ - This objects allows convenient access to settings stored in the + This object allows convenient access to settings stored in the EventSettings/OrganizerSettings database model. It exposes all settings as properties and it will do all the nasty inheritance and defaults stuff for you. @@ -442,7 +442,7 @@ class SettingsProxy: def set(self, key: str, value: Any) -> None: """ - Stores a setting to the database of this object. + Stores a setting to the database of its object. """ if key in self._cache(): s = self._cache()[key] diff --git a/src/pretix/base/signals.py b/src/pretix/base/signals.py index a8103e48e..324b05797 100644 --- a/src/pretix/base/signals.py +++ b/src/pretix/base/signals.py @@ -122,3 +122,9 @@ be everything between a minute and a day. The actions you perform should be idempotent, i.e. it should not make a difference if this is sent out more often than expected. """ + +register_global_settings = django.dispatch.Signal() +""" +All plugins that are installed may send fields for the global settings form, as +an OrderedDict of (setting name, form field). +""" diff --git a/src/pretix/control/forms/global_settings.py b/src/pretix/control/forms/global_settings.py new file mode 100644 index 000000000..7c073b43b --- /dev/null +++ b/src/pretix/control/forms/global_settings.py @@ -0,0 +1,42 @@ +from collections import OrderedDict + +from django.utils.translation import ugettext_lazy as _ + +from pretix.base.forms import SettingsForm +from pretix.base.i18n import I18nFormField, I18nTextInput +from pretix.base.models.settings import GlobalSetting +from pretix.base.settings import SettingsProxy +from pretix.base.signals import register_global_settings + + +class GlobalSettingsObject: + def __init__(self): + self.settings = SettingsProxy(self, type=GlobalSetting) + self.setting_objects = GlobalSetting.objects + self.slug = 'GLOBALSETTINGS' + + +class GlobalSettingsForm(SettingsForm): + def __init__(self, *args, **kwargs): + self.obj = GlobalSettingsObject() + super().__init__(*args, obj=self.obj, **kwargs) + + self.fields = OrderedDict([ + ('footer_text', I18nFormField( + widget=I18nTextInput, + required=False, + label=_("Additional footer text"), + help_text=_("Will be included as additional text in the footer, site-wide.") + )), + ('footer_link', I18nFormField( + widget=I18nTextInput, + required=False, + label=_("Additional footer link"), + help_text=_("Will be included as the link in the additional footer text.") + )) + ]) + responses = register_global_settings.send(self) + for r, response in responses: + for key, value in response.items(): + # We need to be this explicit, since OrderedDict.update does not retain ordering + self.fields[key] = value diff --git a/src/pretix/control/permissions.py b/src/pretix/control/permissions.py index ef5708063..8b24f810a 100644 --- a/src/pretix/control/permissions.py +++ b/src/pretix/control/permissions.py @@ -81,7 +81,7 @@ def organizer_permission_required(permission): class OrganizerPermissionRequiredMixin: """ - This mixin is equivalent to the event_permission_required view decorator but + This mixin is equivalent to the organizer_permission_required view decorator but is in a form suitable for class-based views. """ permission = '' @@ -90,3 +90,33 @@ class OrganizerPermissionRequiredMixin: def as_view(cls, **initkwargs): view = super(OrganizerPermissionRequiredMixin, cls).as_view(**initkwargs) return organizer_permission_required(cls.permission)(view) + + +def administrator_permission_required(permission): + """ + This view decorator rejects all requests with a 403 response which are not from + users with the is_superuser flag. + """ + def decorator(function): + def wrapper(request, *args, **kw): + if not request.user.is_authenticated: # NOQA + # just a double check, should not ever happen + raise PermissionDenied() + if not request.user.is_superuser: + raise PermissionDenied(_('You do not have permission to view this content.')) + return function(request, *args, **kw) + return wrapper + return decorator + + +class AdministratorPermissionRequiredMixin: + """ + This mixin is equivalent to the administrator_permission_required view decorator but + is in a form suitable for class-based views. + """ + permission = '' + + @classmethod + def as_view(cls, **initkwargs): + view = super(AdministratorPermissionRequiredMixin, cls).as_view(**initkwargs) + return administrator_permission_required(cls.permission)(view) diff --git a/src/pretix/control/templates/pretixcontrol/base.html b/src/pretix/control/templates/pretixcontrol/base.html index ef9190faa..ae9f7ab4f 100644 --- a/src/pretix/control/templates/pretixcontrol/base.html +++ b/src/pretix/control/templates/pretixcontrol/base.html @@ -101,6 +101,14 @@ {% trans "Dashboard" %} + {% if request.user.is_superuser %} +
  • + + + {% trans "Global settings" %} + +
  • + {% endif %}
  • diff --git a/src/pretix/control/templates/pretixcontrol/global_settings.html b/src/pretix/control/templates/pretixcontrol/global_settings.html new file mode 100644 index 000000000..e596cd034 --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/global_settings.html @@ -0,0 +1,18 @@ +{% extends "pretixcontrol/base.html" %} +{% load i18n %} +{% load bootstrap3 %} + +{% block title %}{% trans "Global settings" %}{% endblock %} +{% block content %} +

    {% trans "Global settings" %}

    +
    + {% csrf_token %} + {% bootstrap_form_errors form %} + {% bootstrap_form form %} +
    + +
    +
    +{% endblock %} diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index 1e857db87..c15a35852 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -1,8 +1,8 @@ from django.conf.urls import include, url from pretix.control.views import ( - auth, dashboards, event, help, item, main, orders, organizer, user, - vouchers, + auth, dashboards, event, global_settings, help, item, main, orders, + organizer, user, vouchers, ) urlpatterns = [ @@ -13,6 +13,7 @@ urlpatterns = [ url(r'^forgot$', auth.Forgot.as_view(), name='auth.forgot'), url(r'^forgot/recover$', auth.Recover.as_view(), name='auth.forgot.recover'), url(r'^$', dashboards.user_index, name='index'), + url(r'^settings/$', global_settings.GlobalSettingsView.as_view(), name='global-settings'), url(r'^reauth/$', user.ReauthView.as_view(), name='user.reauth'), url(r'^settings$', user.UserSettings.as_view(), name='user.settings'), url(r'^settings/2fa/$', user.User2FAMainView.as_view(), name='user.settings.2fa'), diff --git a/src/pretix/control/views/global_settings.py b/src/pretix/control/views/global_settings.py new file mode 100644 index 000000000..a9b7584f9 --- /dev/null +++ b/src/pretix/control/views/global_settings.py @@ -0,0 +1,17 @@ +from django.views.generic import FormView + +from pretix.control.forms.global_settings import GlobalSettingsForm +from pretix.control.permissions import AdministratorPermissionRequiredMixin + + +class GlobalSettingsView(AdministratorPermissionRequiredMixin, FormView): + template_name = 'pretixcontrol/global_settings.html' + form_class = GlobalSettingsForm + + def form_valid(self, form): + form.save() + return super().form_valid(form) + + def get_success_url(self): + from django.shortcuts import reverse + return reverse('control:global-settings')