Compare commits

...

1 Commits

Author SHA1 Message Date
Mira Weller
e4abfb0bdf start implementing time machine mode 2024-03-06 13:18:23 +01:00
12 changed files with 96 additions and 34 deletions

View File

@@ -80,6 +80,15 @@ from .organizer import Organizer, Team
logger = logging.getLogger(__name__)
def annotate_with_time_based_properties(events_or_subevents, now_dt):
print("annotate_with_time_based_properties", now_dt)
for e_s in events_or_subevents:
if e_s:
e_s.presale_is_running = e_s.presale_is_running_by_time(now_dt)
e_s.presale_has_ended = e_s.presale_has_ended_by_time(now_dt)
return events_or_subevents
class EventMixin:
def clean(self):
if self.presale_start and self.presale_end and self.presale_start > self.presale_end:
@@ -229,17 +238,17 @@ class EventMixin:
else:
return self.presale_end
@property
def presale_has_ended(self):
def presale_has_ended_by_time(self, now_dt: datetime=None):
"""
Is true, when ``presale_end`` is set and in the past.
"""
now_dt = now_dt or now()
if self.effective_presale_end:
return now() > self.effective_presale_end
return now_dt > self.effective_presale_end
elif self.date_to:
return now() > self.date_to
return now_dt > self.date_to
else:
return now().astimezone(self.timezone).date() > self.date_from.astimezone(self.timezone).date()
return now_dt.astimezone(self.timezone).date() > self.date_from.astimezone(self.timezone).date()
@property
def effective_presale_start(self):
@@ -253,15 +262,15 @@ class EventMixin:
else:
return self.presale_start
@property
def presale_is_running(self):
def presale_is_running_by_time(self, now_dt: datetime=None):
"""
Is true, when ``presale_end`` is not set or in the future and ``presale_start`` is not
set or in the past.
"""
if self.effective_presale_start and now() < self.effective_presale_start:
now_dt = now_dt or now()
if self.effective_presale_start and now_dt < self.effective_presale_start:
return False
return not self.presale_has_ended
return not self.presale_has_ended_by_time(now_dt)
@property
def event_microdata(self):
@@ -683,12 +692,12 @@ class Event(EventMixin, LoggedModel):
return qs_annotated
@property
def presale_has_ended(self):
def presale_has_ended_by_time(self, now_dt: datetime = None):
now_dt = now_dt or now()
if self.has_subevents:
return self.presale_end and now() > self.presale_end
return self.presale_end and now_dt > self.presale_end
else:
return super().presale_has_ended
return super().presale_has_ended_by_time(now_dt)
def delete_all_orders(self, really=False):
from .checkin import Checkin

View File

@@ -545,6 +545,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
)
if getattr(self.request, 'customer', None) else None
),
now_dt=self.request.now_dt,
)
item_cache[ckey] = items
else:

View File

@@ -59,6 +59,7 @@ class WaitingListForm(forms.ModelForm):
)
if customer else None
),
now_dt=request.now_dt,
)
for i in items:
if not i.allow_waitinglist:

View File

@@ -32,8 +32,11 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
from dateutil.parser import parse
from django.template.response import TemplateResponse
from django.urls import resolve
from django.utils.timezone import now
from django_scopes import scope
from pretix.base.channels import WebshopSalesChannel
@@ -79,3 +82,27 @@ class EventMiddleware:
response = response.render()
return response
class TimeMachineMiddleware:
def __init__(self, get_response=None):
self.get_response = get_response
super().__init__()
def __call__(self, request):
if hasattr(request, 'event') and hasattr(request, '_namespace') and request._namespace == 'presale' and \
'time_machine' in request.COOKIES and \
request.user.has_event_permission(request.organizer, request.event, 'can_change_event_settings', request):
print("setting now_dt from cookie")
request.now_dt = parse(request.COOKIES['time_machine'])
request.now_dt_is_fake = True
else:
print("setting now_dt to now",
"hasevent?",hasattr(request, 'event') ,
"namespace?",hasattr(request, '_namespace') and request._namespace,
"cookies?",request.COOKIES)
request.now_dt = now()
return self.get_response(request)

View File

@@ -33,6 +33,17 @@
</div>
</div>
{% endif %}
{% if request.now_dt_is_fake %}
<div class="offline-banner">
<div class="container">
<span class="fa fa-user-secret" aria-hidden="true"></span>
{% trans "You are currently using the time machine. The ticket shop is rendered as if it were" %} {{ request.now_dt }}
<a href="#">
{% trans "Go back to current time" %}
</a>
</div>
</div>
{% endif %}
<div class="container page-header-links {% if event.settings.theme_color_background|upper != "#FFFFFF" or event_logo_image_large %}page-header-links-outside{% endif %}">
{% if event.settings.locales|length > 1 or request.organizer.settings.customer_accounts %}
{% if event.settings.theme_color_background|upper != "#FFFFFF" or event_logo_image_large %}

View File

@@ -569,6 +569,7 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView
testmode=self.request.event.testmode
) if getattr(self.request, 'customer', None) else None
),
now_dt=self.request.now_dt,
)
# Calculate how many options the user still has. If there is only one option, we can

View File

@@ -55,7 +55,7 @@ class CheckoutView(View):
messages.error(request, _("Your cart is empty"))
return self.redirect(self.get_index_url(self.request))
if not request.event.presale_is_running:
if not request.event.presale_is_running_by_time(request.now_dt):
messages.error(request, _("The booking period for this event is over or has not yet started."))
return self.redirect(self.get_index_url(self.request))

View File

@@ -64,7 +64,7 @@ from pretix.base.channels import get_all_sales_channels
from pretix.base.models import (
ItemVariation, Quota, SeatCategoryMapping, Voucher,
)
from pretix.base.models.event import Event, SubEvent
from pretix.base.models.event import Event, SubEvent, annotate_with_time_based_properties
from pretix.base.models.items import (
ItemAddOn, ItemBundle, SubEventItem, SubEventItemVariation,
)
@@ -105,7 +105,8 @@ def item_group_by_category(items):
def get_grouped_items(event, subevent=None, voucher=None, channel='web', require_seat=0, base_qs=None, allow_addons=False,
quota_cache=None, filter_items=None, filter_categories=None, memberships=None,
ignore_hide_sold_out_for_item_ids=None):
ignore_hide_sold_out_for_item_ids=None, now_dt: datetime=None):
now_dt = now_dt or now()
base_qs_set = base_qs is not None
base_qs = base_qs if base_qs is not None else event.items
@@ -119,8 +120,8 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
requires_seat = Value(0, output_field=IntegerField())
variation_q = (
Q(Q(available_from__isnull=True) | Q(available_from__lte=now()) | Q(available_from_mode='info')) &
Q(Q(available_until__isnull=True) | Q(available_until__gte=now()) | Q(available_until_mode='info'))
Q(Q(available_from__isnull=True) | Q(available_from__lte=now_dt) | Q(available_from_mode='info')) &
Q(Q(available_until__isnull=True) | Q(available_until__gte=now_dt) | Q(available_until_mode='info'))
)
if not voucher or not voucher.show_hidden_items:
variation_q &= Q(hide_without_voucher=False)
@@ -137,8 +138,8 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
subevent_disabled=Exists(
SubEventItemVariation.objects.filter(
Q(disabled=True)
| (Exact(OuterRef('available_from_mode'), 'hide') & Q(available_from__gt=now()))
| (Exact(OuterRef('available_until_mode'), 'hide') & Q(available_until__lt=now())),
| (Exact(OuterRef('available_from_mode'), 'hide') & Q(available_from__gt=now_dt))
| (Exact(OuterRef('available_until_mode'), 'hide') & Q(available_until__lt=now_dt)),
variation_id=OuterRef('pk'),
subevent=subevent,
)
@@ -209,8 +210,8 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
subevent_disabled=Exists(
SubEventItem.objects.filter(
Q(disabled=True)
| (Exact(OuterRef('available_from_mode'), 'hide') & Q(available_from__gt=now()))
| (Exact(OuterRef('available_until_mode'), 'hide') & Q(available_until__lt=now())),
| (Exact(OuterRef('available_from_mode'), 'hide') & Q(available_from__gt=now_dt))
| (Exact(OuterRef('available_until_mode'), 'hide') & Q(available_until__lt=now_dt)),
item_id=OuterRef('pk'),
subevent=subevent,
)
@@ -306,7 +307,7 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
item._remove = True
continue
item.current_unavailability_reason = item.unavailability_reason(has_voucher=voucher, subevent=subevent)
item.current_unavailability_reason = item.unavailability_reason(now_dt=now_dt, has_voucher=voucher, subevent=subevent)
item.description = str(item.description)
for recv, resp in item_description.send(sender=event, item=item, variation=None, subevent=subevent):
@@ -422,7 +423,7 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require
if not display_add_to_cart:
display_add_to_cart = not item.requires_seat and var.order_max > 0
var.current_unavailability_reason = var.unavailability_reason(has_voucher=voucher, subevent=subevent)
var.current_unavailability_reason = var.unavailability_reason(now_dt=now_dt, has_voucher=voucher, subevent=subevent)
item.original_price = (
item.tax(item.original_price, currency=event.currency, include_bundled=True,
@@ -535,6 +536,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
context['ev'] = self.subevent or self.request.event
context['subevent'] = self.subevent
annotate_with_time_based_properties([self.request.event, self.subevent], self.request.now_dt)
# Show voucher option if an event is selected and vouchers exist
vouchers_exist = self.request.event.cache.get('vouchers_exist')
@@ -543,10 +545,10 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
self.request.event.cache.set('vouchers_exist', vouchers_exist)
context['show_vouchers'] = context['vouchers_exist'] = vouchers_exist and (
(self.request.event.has_subevents and not self.subevent) or
context['ev'].presale_is_running
context['ev'].presale_is_running_by_time(self.request.now_dt)
)
context['allow_waitinglist'] = self.request.event.settings.waiting_list_enabled and context['ev'].presale_is_running
context['allow_waitinglist'] = self.request.event.settings.waiting_list_enabled and context['ev'].presale_is_running_by_time(self.request.now_dt)
if not self.request.event.has_subevents or self.subevent:
# Fetch all items
@@ -562,6 +564,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
testmode=self.request.event.testmode
) if getattr(self.request, 'customer', None) else None
),
now_dt=self.request.now_dt
)
context['waitinglist_seated'] = False
@@ -612,7 +615,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
context['show_cart'] = (
context['cart']['positions'] and (
self.request.event.has_subevents or self.request.event.presale_is_running
self.request.event.has_subevents or self.request.event.presale_is_running_by_time(self.request.now_dt)
)
)
if self.request.event.settings.redirect_to_checkout_directly:
@@ -679,6 +682,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
limit_before, after, ebd, set(), self.request.event,
self.kwargs.get('cart_namespace'),
voucher,
now_dt=self.request.now_dt,
)
# Hide names of subevents in event series where it is always the same. No need to show the name of the museum thousands of times
@@ -738,6 +742,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
limit_before, after, ebd, set(), self.request.event,
self.kwargs.get('cart_namespace'),
voucher,
now_dt=self.request.now_dt,
)
# Hide names of subevents in event series where it is always the same. No need to show the name of the museum thousands of times
@@ -776,7 +781,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
future_only=self.request.event.settings.event_calendar_future_only
)
else:
context['subevent_list'] = self.request.event.subevents_sorted(
context['subevent_list'] = annotate_with_time_based_properties(self.request.event.subevents_sorted(
filter_qs_by_attr(
self.request.event.subevents_annotated(
self.request.sales_channel.identifier,
@@ -784,7 +789,8 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
).using(settings.DATABASE_REPLICA),
self.request
)
)
), self.request.now_dt)
if self.request.event.settings.event_list_available_only and not voucher:
context['subevent_list'] = [
se for se in context['subevent_list']

View File

@@ -1311,7 +1311,8 @@ class OrderChangeMixin:
)
if self.order.customer else None
),
ignore_hide_sold_out_for_item_ids={k[0] for k in current_addon_products.keys()}
ignore_hide_sold_out_for_item_ids={k[0] for k in current_addon_products.keys()},
now_dt=self.request.now_dt,
)
item_cache[ckey] = items
else:

View File

@@ -63,6 +63,7 @@ from pretix.base.i18n import language
from pretix.base.models import (
Event, EventMetaValue, Organizer, Quota, SubEvent, SubEventMetaValue,
)
from pretix.base.models.event import annotate_with_time_based_properties
from pretix.base.services.quotas import QuotaAvailability
from pretix.helpers.compat import date_fromisocalendar
from pretix.helpers.daterange import daterange
@@ -549,14 +550,16 @@ def add_events_for_days(request, baseqs, before, after, ebd, timezones):
})
def add_subevents_for_days(qs, before, after, ebd, timezones, event=None, cart_namespace=None, voucher=None):
def add_subevents_for_days(qs, before, after, ebd, timezones, event=None, cart_namespace=None, voucher=None, now_dt=None):
print("add_subevents_for_days", now_dt)
now_dt = now_dt or now()
qs = qs.filter(active=True, is_public=True).filter(
Q(Q(date_to__gte=before) & Q(date_from__lte=after)) |
Q(Q(date_to__isnull=True) & Q(date_from__gte=before) & Q(date_from__lte=after))
).order_by(
'date_from'
)
qs = annotate_with_time_based_properties(qs, now_dt)
quotas_to_compute = []
for se in qs:
if se.presale_is_running:

View File

@@ -256,6 +256,7 @@ class WidgetAPIProductList(EventListMixin, View):
testmode=self.request.event.testmode
) if getattr(self.request, 'customer', None) else None
),
now_dt=self.request.now_dt,
)
grps = []
@@ -409,11 +410,11 @@ class WidgetAPIProductList(EventListMixin, View):
availability['color'] = 'none'
availability['text'] = gettext('More info')
availability['reason'] = 'unknown'
elif ev.presale_is_running:
elif ev.presale_is_running_by_time(self.request.now_dt):
availability['color'] = 'green'
availability['text'] = gettext('Book now')
availability['reason'] = 'ok'
elif ev.presale_has_ended:
elif ev.presale_has_ended_by_time(self.request.now_dt):
availability['color'] = 'red'
availability['text'] = gettext('Sale over')
availability['reason'] = 'over'

View File

@@ -443,6 +443,7 @@ MIDDLEWARE = [
'pretix.base.middleware.LocaleMiddleware',
'pretix.base.middleware.SecurityMiddleware',
'pretix.presale.middleware.EventMiddleware',
'pretix.presale.middleware.TimeMachineMiddleware',
'pretix.api.middleware.ApiScopeMiddleware',
]