diff --git a/src/pretix/base/timemachine.py b/src/pretix/base/timemachine.py index 56f76bc77..87cdeb2c8 100644 --- a/src/pretix/base/timemachine.py +++ b/src/pretix/base/timemachine.py @@ -49,7 +49,7 @@ class TimeMachineMiddleware: def __call__(self, request): if hasattr(request, 'event') and hasattr(request, '_namespace') and request._namespace == 'presale' and \ 'timemachine_now_dt' in request.session and \ - request.event.testmode and \ + request.event.testmode and request.user.is_authenticated and \ request.user.has_event_permission(request.organizer, request.event, 'can_change_event_settings', request): request.now_dt = parse(request.session['timemachine_now_dt']) request.now_dt_is_fake = True diff --git a/src/pretix/control/context.py b/src/pretix/control/context.py index 736887413..55849e0e3 100644 --- a/src/pretix/control/context.py +++ b/src/pretix/control/context.py @@ -106,7 +106,7 @@ def _default_context(request): else: ctx['complain_testmode_orders'] = False - if not request.event.live and ctx['has_domain']: + if (request.event.testmode or not request.event.live) and ctx['has_domain']: child_sess = request.session.get('child_session_{}'.format(request.event.pk)) s = SessionStore() if not child_sess or not s.exists(child_sess): diff --git a/src/pretix/control/templates/pretixcontrol/event/live.html b/src/pretix/control/templates/pretixcontrol/event/live.html index d7bbe228a..10a718539 100644 --- a/src/pretix/control/templates/pretixcontrol/event/live.html +++ b/src/pretix/control/templates/pretixcontrol/event/live.html @@ -124,35 +124,6 @@
- {% if request.event.testmode %} -
-
- {% trans "Time machine" %} -
-
-
- {% csrf_token %} - {% bootstrap_form_errors timemachine_form %} - -

{% trans "Test your shop as if it were a different date and time." %}

- - {% bootstrap_field timemachine_form.now_dt layout="control" show_label=False field_class="col-md-6" %} -
- - {% if request.session.timemachine_now_dt %} - - {% endif %} -
-
- -
-
-
- {% endif %}
diff --git a/src/pretix/control/templates/pretixcontrol/event/transfer_session.html b/src/pretix/control/templates/pretixcontrol/event/transfer_session.html new file mode 100644 index 000000000..7a85cff6f --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/event/transfer_session.html @@ -0,0 +1,9 @@ +{% load eventurl %} +{% load static %} +
+ + +
+ diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index 20fe27845..eb6b4a0a3 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -247,6 +247,7 @@ urlpatterns = [ re_path(r'^widgets.json$', dashboards.event_index_widgets_lazy, name='event.index.widgets'), re_path(r'^logs/embed$', dashboards.event_index_log_lazy, name='event.index.logs'), re_path(r'^live/$', event.EventLive.as_view(), name='event.live'), + re_path(r'^transfer_session/$', event.EventTransferSession.as_view(), name='event.transfer_session'), re_path(r'^logs/$', event.EventLog.as_view(), name='event.log'), re_path(r'^delete/$', event.EventDelete.as_view(), name='event.delete'), re_path(r'^comment/$', event.EventComment.as_view(), diff --git a/src/pretix/control/views/event.py b/src/pretix/control/views/event.py index f8b193ffb..9609f19de 100644 --- a/src/pretix/control/views/event.py +++ b/src/pretix/control/views/event.py @@ -46,8 +46,6 @@ from zoneinfo import ZoneInfo import bleach import qrcode import qrcode.image.svg -from dateutil import parser -from django import forms from django.apps import apps from django.conf import settings from django.contrib import messages @@ -65,7 +63,7 @@ from django.shortcuts import get_object_or_404, redirect from django.urls import reverse from django.utils.functional import cached_property from django.utils.http import url_has_allowed_host_and_scheme -from django.utils.timezone import get_current_timezone, now +from django.utils.timezone import now from django.utils.translation import gettext, gettext_lazy as _, gettext_noop from django.views.generic import FormView, ListView from django.views.generic.base import TemplateView, View @@ -94,12 +92,11 @@ from pretix.control.views.mailsetup import MailSettingsSetupView from pretix.control.views.user import RecentAuthenticationRequiredMixin from pretix.helpers.database import rolledback_transaction from pretix.multidomain.urlreverse import ( - build_absolute_uri, eventreverse, get_event_domain, + build_absolute_uri, get_event_domain, ) from pretix.plugins.stripe.payment import StripeSettingsHolder from pretix.presale.style import regenerate_css -from ...base.forms.widgets import SplitDateTimePickerWidget from ...base.i18n import language from ...base.models.items import ( Item, ItemCategory, ItemMetaProperty, Question, Quota, @@ -955,43 +952,21 @@ class EventPermissions(EventSettingsViewMixin, EventPermissionRequiredMixin, Tem template_name = 'pretixcontrol/event/permissions.html' -class TimemachineForm(forms.Form): - now_dt = forms.SplitDateTimeField( - label=_('Fake date time'), - widget=SplitDateTimePickerWidget(), - initial=lambda: now().astimezone(get_current_timezone()) - ) - - class EventLive(EventPermissionRequiredMixin, TemplateView): permission = 'can_change_event_settings' template_name = 'pretixcontrol/event/live.html' def setup(self, request, *args, **kwargs): super().setup(request, *args, **kwargs) - self.timemachine_form = TimemachineForm( - data=request.method == 'POST' and request.POST or None, - initial=( - {'now_dt': parser.parse(request.session.get('timemachine_now_dt', None))} - if request.session.get('timemachine_now_dt', None) else {} - ) - ) def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx['issues'] = self.request.event.live_issues ctx['actual_orders'] = self.request.event.orders.filter(testmode=False).exists() - ctx['timemachine_form'] = self.timemachine_form return ctx def post(self, request, *args, **kwargs): - if request.POST.get("timemachine_disable"): - del request.session['timemachine_now_dt'] - messages.success(self.request, _('Time machine disabled!')) - elif self.timemachine_form.is_valid(): - request.session['timemachine_now_dt'] = str(self.timemachine_form.cleaned_data['now_dt']) - return redirect(eventreverse(request.event, "presale:event.index")) - elif request.POST.get("live") == "true" and not self.request.event.live_issues: + if request.POST.get("live") == "true" and not self.request.event.live_issues: with transaction.atomic(): request.event.live = True request.event.save() @@ -1047,6 +1022,11 @@ class EventLive(EventPermissionRequiredMixin, TemplateView): }) +class EventTransferSession(EventPermissionRequiredMixin, TemplateView): + permission = 'can_change_event_settings' + template_name = 'pretixcontrol/event/transfer_session.html' + + class EventDelete(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixin, FormView): permission = 'can_change_event_settings' template_name = 'pretixcontrol/event/delete.html' diff --git a/src/pretix/presale/templates/pretixpresale/event/base.html b/src/pretix/presale/templates/pretixpresale/event/base.html index 461e85a98..b0dfef7b7 100644 --- a/src/pretix/presale/templates/pretixpresale/event/base.html +++ b/src/pretix/presale/templates/pretixpresale/event/base.html @@ -38,10 +38,10 @@
{% trans "You are currently using the time machine. The ticket shop is rendered as if it were" %} {{ request.now_dt }} - + {% trans "Change" %} -
+ {% csrf_token %}
@@ -126,6 +126,15 @@ {% if request.event.testmode %} {% if request.sales_channel.testmode_supported %}
+ {% if request.user.is_authenticated %} + + + + {% elif request.event_domain or request.organizer_domain %} + + + + {% endif %}

{% trans "Warning" context "alert-messages" %}: {% trans "This ticket shop is currently in test mode. Please do not perform any real purchases as your order might be deleted without notice." %}

@@ -137,6 +146,8 @@

{% endif %} + + {% endif %} {% if messages %} {% for message in messages %} diff --git a/src/pretix/presale/templates/pretixpresale/event/timemachine.html b/src/pretix/presale/templates/pretixpresale/event/timemachine.html new file mode 100644 index 000000000..cad932359 --- /dev/null +++ b/src/pretix/presale/templates/pretixpresale/event/timemachine.html @@ -0,0 +1,40 @@ +{% extends "pretixpresale/event/base.html" %} +{% load i18n %} +{% load l10n %} +{% load money %} +{% load eventurl %} +{% load eventsignal %} +{% load thumb %} +{% load rich_text %} +{% load bootstrap3 %} +{% block title %}{% trans "Time machine" %}{% endblock %} + +{% block content %} +
+
+ {% trans "Time machine" %} +
+
+
+ {% csrf_token %} + {% bootstrap_form_errors timemachine_form %} + +

{% trans "Test your shop as if it were a different date and time." %}

+ + {% bootstrap_field timemachine_form.now_dt layout="control" show_label=False field_class="col-md-6" %} +
+ + {% if request.session.timemachine_now_dt %} + + {% endif %} +
+
+ +
+
+
+{% endblock %} \ No newline at end of file diff --git a/src/pretix/presale/urls.py b/src/pretix/presale/urls.py index 1afd9dcb1..c412707dd 100644 --- a/src/pretix/presale/urls.py +++ b/src/pretix/presale/urls.py @@ -173,6 +173,8 @@ event_patterns = [ re_path(r'^(?P\d+)/widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(), name='event.widget.productlist'), + re_path(r'timemachine/$', pretix.presale.views.event.EventTimeMachine.as_view(), name='event.timemachine'), + # Account management is done on org level, but we at least need a logout re_path(r'^account/logout$', pretix.presale.views.customer.LogoutView.as_view(), name='organizer.customer.logout'), ] diff --git a/src/pretix/presale/utils.py b/src/pretix/presale/utils.py index 54a9b7c6c..40d45ee04 100644 --- a/src/pretix/presale/utils.py +++ b/src/pretix/presale/utils.py @@ -38,6 +38,9 @@ from importlib import import_module from urllib.parse import urljoin from django.conf import settings +from django.contrib.auth import ( + BACKEND_SESSION_KEY, SESSION_KEY, get_user_model, load_backend, +) from django.db.models import Q from django.http import Http404, HttpResponseForbidden from django.middleware.csrf import rotate_token @@ -218,6 +221,15 @@ def customer_logout(request): request._cached_customer = None +def _apply_user_from_session_data(request, sessiondata): + user_id = get_user_model()._meta.pk.to_python(sessiondata[SESSION_KEY]) + backend_path = sessiondata[BACKEND_SESSION_KEY] + if backend_path in settings.AUTHENTICATION_BACKENDS: + backend = load_backend(backend_path) + user = backend.get_user(user_id) + request.user = user + + @scope(organizer=None) def _detect_event(request, require_live=True, require_plugin=None): @@ -303,7 +315,7 @@ def _detect_event(request, require_live=True, require_plugin=None): # Restrict locales to the ones available for this event LocaleMiddleware(NotImplementedError).process_request(request) - if require_live and not request.event.live: + if require_live and (request.event.testmode or not request.event.live): can_access = ( url.url_name == 'event.auth' or ( @@ -320,8 +332,10 @@ def _detect_event(request, require_live=True, require_plugin=None): pass else: can_access = 'event_access' in parentdata + if can_access: + _apply_user_from_session_data(request, parentdata) - if not can_access: + if not can_access and not request.event.live: # Directly construct view instead of just calling `raise` since this case is so common that we # don't want it to show in our log files. template = loader.get_template("pretixpresale/event/offline.html") diff --git a/src/pretix/presale/views/event.py b/src/pretix/presale/views/event.py index 4dc33ba43..69deaaf56 100644 --- a/src/pretix/presale/views/event.py +++ b/src/pretix/presale/views/event.py @@ -42,18 +42,21 @@ from importlib import import_module from urllib.parse import urlencode import isoweek +from dateutil import parser +from django import forms from django.conf import settings +from django.contrib import messages from django.core.exceptions import PermissionDenied from django.db.models import ( Count, Exists, IntegerField, OuterRef, Prefetch, Q, Value, ) from django.db.models.lookups import Exact from django.http import Http404, HttpResponse -from django.shortcuts import get_object_or_404, render +from django.shortcuts import get_object_or_404, redirect, render from django.utils.decorators import method_decorator from django.utils.formats import get_format from django.utils.functional import SimpleLazyObject -from django.utils.timezone import now +from django.utils.timezone import get_current_timezone, now from django.utils.translation import gettext_lazy as _, pgettext_lazy from django.views import View from django.views.decorators.csrf import csrf_exempt @@ -78,6 +81,8 @@ from pretix.presale.views.organizer import ( filter_qs_by_attr, has_before_after, weeks_for_template, ) +from ...base.forms.widgets import SplitDateTimePickerWidget +from ...control.permissions import EventPermissionRequiredMixin from ...helpers.formats.en.formats import SHORT_MONTH_DAY_FORMAT, WEEK_FORMAT from ...helpers.http import redirect_to_url from . import ( @@ -918,3 +923,43 @@ class EventAuth(View): request.session['pretix_event_access_{}'.format(request.event.pk)] = parent return redirect_to_url(eventreverse(request.event, 'presale:event.index')) + + +class TimemachineForm(forms.Form): + now_dt = forms.SplitDateTimeField( + label=_('Fake date time'), + widget=SplitDateTimePickerWidget(), + initial=lambda: now().astimezone(get_current_timezone()) + ) + + +class EventTimeMachine(EventViewMixin, EventPermissionRequiredMixin, TemplateView): + permission = 'can_change_event_settings' + template_name = 'pretixpresale/event/timemachine.html' + + def setup(self, request, *args, **kwargs): + super().setup(request, *args, **kwargs) + self.timemachine_form = TimemachineForm( + data=request.method == 'POST' and request.POST or None, + initial=( + {'now_dt': parser.parse(request.session.get('timemachine_now_dt', None))} + if request.session.get('timemachine_now_dt', None) else {} + ) + ) + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + ctx['timemachine_form'] = self.timemachine_form + return ctx + + def post(self, request, *args, **kwargs): + if request.POST.get("timemachine_disable"): + del request.session['timemachine_now_dt'] + messages.success(self.request, _('Time machine disabled!')) + return redirect(self.get_success_url()) + elif self.timemachine_form.is_valid(): + request.session['timemachine_now_dt'] = str(self.timemachine_form.cleaned_data['now_dt']) + return redirect(eventreverse(request.event, "presale:event.index")) + + def get_success_url(self) -> str: + return eventreverse(self.request.event, 'presale:event.timemachine') diff --git a/src/pretix/static/pretixcontrol/js/send_form.js b/src/pretix/static/pretixcontrol/js/send_form.js new file mode 100644 index 000000000..01b3fda00 --- /dev/null +++ b/src/pretix/static/pretixcontrol/js/send_form.js @@ -0,0 +1 @@ +document.forms[0].submit();