From c84d1706b4a62e786a548132d3982e8ac5e55b93 Mon Sep 17 00:00:00 2001 From: Mira Weller Date: Wed, 14 May 2025 17:29:50 +0200 Subject: [PATCH] add extend_cart_reservation task and CartExtendReservation view --- src/pretix/base/services/cart.py | 24 ++++++++++++++++++++++++ src/pretix/presale/urls.py | 1 + src/pretix/presale/views/cart.py | 15 ++++++++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/pretix/base/services/cart.py b/src/pretix/base/services/cart.py index 93111b0680..8ba518733c 100644 --- a/src/pretix/base/services/cart.py +++ b/src/pretix/base/services/cart.py @@ -1649,6 +1649,30 @@ def clear_cart(self, event: Event, cart_id: str=None, locale='en', sales_channel raise CartError(error_messages['busy']) +@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,)) +def extend_cart_reservation(self, event: Event, cart_id: str=None, locale='en', sales_channel='web', override_now_dt: datetime=None) -> None: + """ + Resets the expiry time of a cart to the configured reservation time of this event. + Limited to 11x the reservation time. + + :param event: The event ID in question + :param cart_id: The cart ID of the cart to modify + """ + with language(locale), time_machine_now_assigned(override_now_dt): + try: + sales_channel = event.organizer.sales_channels.get(identifier=sales_channel) + except SalesChannel.DoesNotExist: + raise CartError("Invalid sales channel.") + try: + try: + cm = CartManager(event=event, cart_id=cart_id, sales_channel=sales_channel) + cm.commit() + except LockTimeoutException: + self.retry() + except (MaxRetriesExceededError, LockTimeoutException): + raise CartError(error_messages['busy']) + + @app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,)) def set_cart_addons(self, event: Event, addons: List[dict], add_to_cart_items: List[dict], cart_id: str=None, locale='en', invoice_address: int=None, sales_channel='web', override_now_dt: datetime=None) -> None: diff --git a/src/pretix/presale/urls.py b/src/pretix/presale/urls.py index 10e8a2f843..e47f5a84e9 100644 --- a/src/pretix/presale/urls.py +++ b/src/pretix/presale/urls.py @@ -56,6 +56,7 @@ frame_wrapped_urls = [ re_path(r'^cart/remove$', pretix.presale.views.cart.CartRemove.as_view(), name='event.cart.remove'), re_path(r'^cart/voucher$', pretix.presale.views.cart.CartApplyVoucher.as_view(), name='event.cart.voucher'), re_path(r'^cart/clear$', pretix.presale.views.cart.CartClear.as_view(), name='event.cart.clear'), + re_path(r'^cart/extend$', pretix.presale.views.cart.CartExtendReservation.as_view(), name='event.cart.extend'), re_path(r'^cart/answer/(?P[^/]+)/$', pretix.presale.views.cart.AnswerDownload.as_view(), name='event.cart.download.answer'), diff --git a/src/pretix/presale/views/cart.py b/src/pretix/presale/views/cart.py index a823e6674a..59e1bc82fa 100644 --- a/src/pretix/presale/views/cart.py +++ b/src/pretix/presale/views/cart.py @@ -62,7 +62,7 @@ from pretix.base.models import ( ) from pretix.base.services.cart import ( CartError, add_items_to_cart, apply_voucher, clear_cart, error_messages, - remove_cart_position, + extend_cart_reservation, remove_cart_position, ) from pretix.base.timemachine import time_machine_now from pretix.base.views.tasks import AsyncAction @@ -537,6 +537,19 @@ class CartClear(EventViewMixin, CartActionMixin, AsyncAction, View): request.sales_channel.identifier, time_machine_now(default=None)) +@method_decorator(allow_frame_if_namespaced, 'dispatch') +class CartExtendReservation(EventViewMixin, CartActionMixin, AsyncAction, View): + task = extend_cart_reservation + known_errortypes = ['CartError'] + + def get_success_message(self, value): + return _('Your cart timeout was extended.') + + def post(self, request, *args, **kwargs): + return self.do(self.request.event.id, get_or_create_cart_id(self.request), translation.get_language(), + request.sales_channel.identifier, time_machine_now(default=None)) + + @method_decorator(allow_cors_if_namespaced, 'dispatch') @method_decorator(allow_frame_if_namespaced, 'dispatch') @method_decorator(iframe_entry_view_wrapper, 'dispatch')