From a84beef269e4770bbbb5bff28088d22bc47f2de3 Mon Sep 17 00:00:00 2001 From: luelista Date: Wed, 21 May 2025 17:02:21 +0200 Subject: [PATCH] [A11y] Cart renewal (#5109) Allow customers to extend their cart reservation up to 11 times the configured reservation time --- .../0280_cartposition_max_extend.py | 18 + src/pretix/base/models/orders.py | 5 +- src/pretix/base/services/cart.py | 81 ++- .../pretixpresale/event/fragment_cart.html | 24 +- src/pretix/presale/urls.py | 1 + src/pretix/presale/views/cart.py | 16 +- src/pretix/static/pretixpresale/js/ui/cart.js | 1 + src/tests/presale/test_cart.py | 477 +++++++++++++----- 8 files changed, 469 insertions(+), 154 deletions(-) create mode 100644 src/pretix/base/migrations/0280_cartposition_max_extend.py diff --git a/src/pretix/base/migrations/0280_cartposition_max_extend.py b/src/pretix/base/migrations/0280_cartposition_max_extend.py new file mode 100644 index 0000000000..16024151f2 --- /dev/null +++ b/src/pretix/base/migrations/0280_cartposition_max_extend.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.20 on 2025-05-14 14:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0279_discount_event_date_from_discount_event_date_until'), + ] + + operations = [ + migrations.AddField( + model_name='cartposition', + name='max_extend', + field=models.DateTimeField(null=True), + ), + ] diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index 7ac8beff17..9db278422a 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -3098,7 +3098,10 @@ class CartPosition(AbstractPosition): verbose_name=_("Expiration date"), db_index=True ) - + max_extend = models.DateTimeField( + verbose_name=_("Limit for extending expiration date"), + null=True + ) tax_rate = models.DecimalField( max_digits=7, decimal_places=2, default=Decimal('0.00'), verbose_name=_('Tax rate') diff --git a/src/pretix/base/services/cart.py b/src/pretix/base/services/cart.py index d3e359013d..14099b8c1f 100644 --- a/src/pretix/base/services/cart.py +++ b/src/pretix/base/services/cart.py @@ -45,6 +45,7 @@ from django.conf import settings from django.core.exceptions import ValidationError from django.db import DatabaseError, transaction from django.db.models import Count, Exists, IntegerField, OuterRef, Q, Value +from django.db.models.aggregates import Min from django.dispatch import receiver from django.utils.timezone import make_aware, now from django.utils.translation import ( @@ -275,7 +276,10 @@ class CartManager: } def __init__(self, event: Event, cart_id: str, sales_channel: SalesChannel, - invoice_address: InvoiceAddress=None, widget_data=None, expiry=None): + invoice_address: InvoiceAddress=None, widget_data=None, reservation_time: timedelta=None): + """ + Creates a new CartManager for an event. + """ self.event = event self.cart_id = cart_id self.real_now_dt = now() @@ -286,11 +290,17 @@ class CartManager: self._subevents_cache = {} self._variations_cache = {} self._seated_cache = {} - self._expiry = None - self._explicit_expiry = expiry self.invoice_address = invoice_address self._widget_data = widget_data or {} self._sales_channel = sales_channel + self.num_extended_positions = 0 + + if reservation_time: + self._reservation_time = reservation_time + else: + self._reservation_time = timedelta(minutes=self.event.settings.get('reservation_time', as_type=int)) + self._expiry = self.real_now_dt + self._reservation_time + self._max_expiry_extend = self.real_now_dt + (self._reservation_time * 11) @property def positions(self): @@ -305,14 +315,6 @@ class CartManager: self._seated_cache[item, subevent] = item.seat_category_mappings.filter(subevent=subevent).exists() return self._seated_cache[item, subevent] - def _calculate_expiry(self): - if self._explicit_expiry: - self._expiry = self._explicit_expiry - else: - self._expiry = self.real_now_dt + timedelta( - minutes=self.event.settings.get('reservation_time', as_type=int) - ) - def _check_presale_dates(self): if self.event.presale_start and time_machine_now(self.real_now_dt) < self.event.presale_start: raise CartError(error_messages['not_started']) @@ -329,9 +331,27 @@ class CartManager: raise CartError(error_messages['payment_ended']) def _extend_expiry_of_valid_existing_positions(self): + # real_now_dt is initialized at CartManager instantiation, so it's slightly in the past. Add a small + # delta to reduce risk of extending already expired CartPositions. + padded_now_dt = self.real_now_dt + timedelta(seconds=5) + + # Make sure we do not extend past the max_extend timestamp, allowing users to extend their valid positions up + # to 11 times the reservation time. If we add new positions to the cart while valid positions exist, the new + # positions' reservation will also be limited to max_extend of the oldest position. + # Only after all positions expire, an ExtendOperation may reset max_extend to another 11x reservation_time. + max_extend_existing = self.positions.filter(expires__gt=padded_now_dt).aggregate(m=Min('max_extend'))['m'] + if max_extend_existing: + self._expiry = min(self._expiry, max_extend_existing) + self._max_expiry_extend = max_extend_existing + # Extend this user's cart session to ensure all items in the cart expire at the same time # We can extend the reservation of items which are not yet expired without risk - self.positions.filter(expires__gt=self.real_now_dt).update(expires=self._expiry) + if self._expiry > padded_now_dt: + self.num_extended_positions += self.positions.filter( + expires__gt=padded_now_dt, expires__lt=self._expiry, + ).update( + expires=self._expiry, + ) def _delete_out_of_timeframe(self): err = None @@ -1246,6 +1266,7 @@ class CartManager: item=op.item, variation=op.variation, expires=self._expiry, + max_extend=self._max_expiry_extend, cart_id=self.cart_id, voucher=op.voucher, addon_to=op.addon_to if op.addon_to else None, @@ -1294,7 +1315,9 @@ class CartManager: event=self.event, item=b.item, variation=b.variation, - expires=self._expiry, cart_id=self.cart_id, + expires=self._expiry, + max_extend=self._max_expiry_extend, + cart_id=self.cart_id, voucher=None, addon_to=cp, subevent=b.subevent, @@ -1321,12 +1344,14 @@ class CartManager: op.position.delete() elif available_count == 1: op.position.expires = self._expiry + op.position.max_extend = self._max_expiry_extend op.position.listed_price = op.listed_price op.position.price_after_voucher = op.price_after_voucher # op.position.price will be updated by recompute_final_prices_and_taxes() if op.position.pk not in deleted_positions: try: - op.position.save(force_update=True, update_fields=['expires', 'listed_price', 'price_after_voucher']) + op.position.save(force_update=True, update_fields=['expires', 'max_extend', 'listed_price', 'price_after_voucher']) + self.num_extended_positions += 1 except DatabaseError: # Best effort... The position might have been deleted in the meantime! pass @@ -1416,14 +1441,11 @@ class CartManager: def commit(self): self._check_presale_dates() self._check_max_cart_size() - self._calculate_expiry() err = self._delete_out_of_timeframe() err = self.extend_expired_positions() or err err = err or self._check_min_per_voucher() - self.real_now_dt = now() - self._extend_expiry_of_valid_existing_positions() err = self._perform_operations() or err self.recompute_final_prices_and_taxes() @@ -1632,6 +1654,31 @@ 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() + return cm.num_extended_positions + 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/templates/pretixpresale/event/fragment_cart.html b/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html index c9dd890505..02cacd02c4 100644 --- a/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html +++ b/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html @@ -492,15 +492,21 @@
{% if not cart.is_ordered %} -

- {% if cart.minutes_left > 0 or cart.seconds_left > 0 %} - {% blocktrans trimmed with minutes=cart.minutes_left %} - The items in your cart are reserved for you for {{ minutes }} minutes. - {% endblocktrans %} - {% else %} - {% trans "The items in your cart are no longer reserved for you. You can still complete your order as long as they’re available." %} - {% endif %} -

+
+ {% csrf_token %} + + {% if cart.minutes_left > 0 or cart.seconds_left > 0 %} + {% blocktrans trimmed with minutes=cart.minutes_left %} + The items in your cart are reserved for you for {{ minutes }} minutes. + {% endblocktrans %} + {% else %} + {% trans "The items in your cart are no longer reserved for you. You can still complete your order as long as they’re available." %} + {% endif %} + + +
{% else %}

{% trans "Overview of your ordered products." %}

{% endif %} 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..f99eac0436 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,20 @@ 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): + if value > 0: + 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') diff --git a/src/pretix/static/pretixpresale/js/ui/cart.js b/src/pretix/static/pretixpresale/js/ui/cart.js index 98a1925581..bdb4b14c52 100644 --- a/src/pretix/static/pretixpresale/js/ui/cart.js +++ b/src/pretix/static/pretixpresale/js/ui/cart.js @@ -55,6 +55,7 @@ var cart = { pad(diff_minutes.toString(), 2) + ':' + pad(diff_seconds.toString(), 2) ); } + $("#cart-extend-button").toggle(diff_minutes < 3); }, init: function () { diff --git a/src/tests/presale/test_cart.py b/src/tests/presale/test_cart.py index d1d7a901ff..da78c46285 100644 --- a/src/tests/presale/test_cart.py +++ b/src/tests/presale/test_cart.py @@ -37,6 +37,7 @@ import json from datetime import timedelta from decimal import Decimal +import freezegun from bs4 import BeautifulSoup from django.conf import settings from django.test import TestCase @@ -96,6 +97,8 @@ class CartTestMixin: self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug)) self.session_key = get_cart_session_key(self.client, self.event) + self.event.settings.set('reservation_time', 30) + self.cart_reservation_time = timedelta(minutes=self.event.settings.get('reservation_time', as_type=int)) class CartTest(CartTestMixin, TestCase): @@ -989,7 +992,7 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) self.event.settings.max_items_per_order = 5 response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { @@ -1006,7 +1009,7 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) self.event.settings.max_items_per_order = settings.PRETIX_MAX_ORDER_SIZE + 100 response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { @@ -1023,7 +1026,7 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) self.event.settings.max_items_per_order = 5 response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { @@ -1084,7 +1087,7 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '2', @@ -1102,7 +1105,7 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '2', @@ -1225,22 +1228,102 @@ class CartTest(CartTestMixin, TestCase): self.assertEqual(objs[0].price, 23) def test_renew_in_time(self): + start_time = now() - timedelta(minutes=20) + expires = start_time + self.cart_reservation_time + max_extend = start_time + 11 * self.cart_reservation_time with scopes_disabled(): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=expires, max_extend=max_extend ) self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'variation_%d_%d' % (self.shirt.id, self.shirt_red.id): '1' }, follow=True) cp.refresh_from_db() self.assertGreater(cp.expires, now() + timedelta(minutes=10)) + self.assertEqual(cp.max_extend, max_extend) + + def test_autorenew_with_max_extend(self): + start_time = datetime.datetime(2024, 1, 1, 10, 00, 00, tzinfo=datetime.timezone.utc) + with freezegun.freeze_time(start_time): + expires = start_time + self.cart_reservation_time + max_extend = start_time + 2 * self.cart_reservation_time + with scopes_disabled(): + cp = CartPosition.objects.create( + event=self.event, cart_id=self.session_key, item=self.ticket, + price=23, expires=expires, max_extend=max_extend + ) + with freezegun.freeze_time(start_time + timedelta(minutes=20)): + self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { + 'item_%d' % (self.ticket.id,): '1' + }, follow=True) + cp.refresh_from_db() + with scopes_disabled(): + positions = list(CartPosition.objects.filter(cart_id=cp.cart_id)) + self.assertEqual(len(positions), 2) + for pos in positions: + self.assertEqual(pos.expires, start_time + timedelta(minutes=20) + self.cart_reservation_time) + self.assertEqual(pos.max_extend, max_extend) + with freezegun.freeze_time(start_time + timedelta(minutes=45)): + self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { + 'item_%d' % (self.ticket.id,): '1' + }, follow=True) + cp.refresh_from_db() + with scopes_disabled(): + positions = list(CartPosition.objects.filter(cart_id=cp.cart_id)) + self.assertEqual(len(positions), 3) + for pos in positions: + self.assertEqual(pos.expires, max_extend) + self.assertEqual(pos.max_extend, max_extend) + with freezegun.freeze_time(start_time + timedelta(days=1)): + self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { + 'item_%d' % (self.ticket.id,): '1' + }, follow=True) + cp.refresh_from_db() + with scopes_disabled(): + positions = list(CartPosition.objects.filter(cart_id=cp.cart_id)) + self.assertEqual(len(positions), 4) + for pos in positions: + self.assertEqual(pos.expires, start_time + timedelta(days=1) + self.cart_reservation_time) + self.assertEqual(pos.max_extend, start_time + timedelta(days=1) + 11 * self.cart_reservation_time) + + def test_extend_with_max_extend(self): + start_time = datetime.datetime(2024, 1, 1, 10, 00, 00, tzinfo=datetime.timezone.utc) + with freezegun.freeze_time(start_time): + expires = start_time + self.cart_reservation_time + max_extend = start_time + 2 * self.cart_reservation_time + with scopes_disabled(): + cp = CartPosition.objects.create( + event=self.event, cart_id=self.session_key, item=self.ticket, + price=23, expires=expires, max_extend=max_extend + ) + with freezegun.freeze_time(start_time + timedelta(minutes=20)): + self.client.post('/%s/%s/cart/extend' % (self.orga.slug, self.event.slug), { + }, follow=True) + cp.refresh_from_db() + self.assertEqual(cp.expires, start_time + timedelta(minutes=20) + self.cart_reservation_time) + self.assertEqual(cp.max_extend, max_extend) + with freezegun.freeze_time(start_time + timedelta(minutes=45)): + self.client.post('/%s/%s/cart/extend' % (self.orga.slug, self.event.slug), { + }, follow=True) + cp.refresh_from_db() + self.assertEqual(cp.expires, max_extend) + self.assertEqual(cp.max_extend, max_extend) + with freezegun.freeze_time(start_time + timedelta(minutes=65)): + self.client.post('/%s/%s/cart/extend' % (self.orga.slug, self.event.slug), { + }, follow=True) + cp.refresh_from_db() + self.assertEqual(cp.expires, start_time + timedelta(minutes=65) + self.cart_reservation_time) + self.assertEqual(cp.max_extend, start_time + timedelta(minutes=65) + 11 * self.cart_reservation_time) def test_renew_expired_successfully(self): + start_time = now() - timedelta(minutes=40) + expires = start_time + self.cart_reservation_time + max_extend = start_time + 11 * self.cart_reservation_time with scopes_disabled(): cp1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() - timedelta(minutes=10) + price=23, expires=expires, max_extend=max_extend ) self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'variation_%d_%d' % (self.shirt.id, self.shirt_red.id): '1' @@ -1250,13 +1333,14 @@ class CartTest(CartTestMixin, TestCase): self.assertEqual(obj.item, self.ticket) self.assertIsNone(obj.variation) self.assertEqual(obj.price, 23) - self.assertGreater(obj.expires, now()) + self.assertGreater(obj.expires, now() + timedelta(minutes=10)) + self.assertGreater(obj.max_extend, now() + 11 * self.cart_reservation_time - timedelta(minutes=10)) def test_renew_questions(self): with scopes_disabled(): cr1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() - timedelta(minutes=10) + price=23, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) q1 = Question.objects.create( event=self.event, question='Age', type=Question.TYPE_NUMBER, @@ -1279,7 +1363,7 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): cp1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() - timedelta(minutes=10) + price=23, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '1', @@ -1289,6 +1373,61 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): self.assertFalse(CartPosition.objects.filter(id=cp1.id).exists()) + def test_expired_renew_fails_partially_and_add_succeeds(self): + start_time = datetime.datetime(2024, 1, 1, 10, 00, 00, tzinfo=datetime.timezone.utc) + expires = start_time + self.cart_reservation_time + max_extend = start_time + 11 * self.cart_reservation_time + self.quota_tickets.size = 0 + self.quota_tickets.save() + with scopes_disabled(): + cp1 = CartPosition.objects.create( + event=self.event, cart_id=self.session_key, item=self.ticket, + price=23, expires=expires, max_extend=max_extend + ) + cp2 = CartPosition.objects.create( + event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue, + price=23, expires=expires, max_extend=max_extend + ) + with freezegun.freeze_time(max_extend + timedelta(hours=1)): + response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { + 'variation_%d_%d' % (self.shirt.id, self.shirt_red.id): '1', + }, follow=True) + doc = BeautifulSoup(response.rendered_content, "lxml") + self.assertIn('no longer available', doc.select('.alert-danger')[0].text) + with scopes_disabled(): + self.assertFalse(CartPosition.objects.filter(id=cp1.id).exists()) + cp2.refresh_from_db() + self.assertEqual(cp2.expires, now() + self.cart_reservation_time) + self.assertEqual(cp2.max_extend, now() + 11 * self.cart_reservation_time) + cp3 = CartPosition.objects.get(variation_id=self.shirt_red.id) + self.assertEqual(cp3.expires, now() + self.cart_reservation_time) + self.assertEqual(cp3.max_extend, now() + 11 * self.cart_reservation_time) + + def test_expired_cart_extend_fails_partially(self): + start_time = datetime.datetime(2024, 1, 1, 10, 00, 00, tzinfo=datetime.timezone.utc) + max_extend = start_time + 11 * self.cart_reservation_time + self.quota_tickets.size = 0 + self.quota_tickets.save() + with scopes_disabled(): + cp1 = CartPosition.objects.create( + event=self.event, cart_id=self.session_key, item=self.ticket, + price=23, expires=max_extend, max_extend=max_extend + ) + cp2 = CartPosition.objects.create( + event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue, + price=23, expires=max_extend, max_extend=max_extend + ) + with freezegun.freeze_time(max_extend + timedelta(hours=1)): + response = self.client.post('/%s/%s/cart/extend' % (self.orga.slug, self.event.slug), { + }, follow=True) + doc = BeautifulSoup(response.rendered_content, "lxml") + self.assertIn('no longer available', doc.select('.alert-danger')[0].text) + with scopes_disabled(): + self.assertFalse(CartPosition.objects.filter(id=cp1.id).exists()) + cp2.refresh_from_db() + self.assertEqual(cp2.expires, now() + self.cart_reservation_time) + self.assertEqual(cp2.max_extend, now() + 11 * self.cart_reservation_time) + def test_subevent_renew_expired_successfully(self): self.event.has_subevents = True self.event.save() @@ -1300,7 +1439,9 @@ class CartTest(CartTestMixin, TestCase): self.quota_shirts.save() cp1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() - timedelta(minutes=10), subevent=se + price=23, + expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + subevent=se ) self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'variation_%d_%d' % (self.shirt.id, self.shirt_red.id): '1', @@ -1324,7 +1465,9 @@ class CartTest(CartTestMixin, TestCase): self.quota_tickets.save() cp1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() - timedelta(minutes=10), subevent=se + price=23, + expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + subevent=se ) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'item_%d' % self.ticket.id: '1', @@ -1339,7 +1482,7 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) response = self.client.post('/%s/%s/cart/remove' % (self.orga.slug, self.event.slug), { 'id': cp.pk @@ -1355,15 +1498,15 @@ class CartTest(CartTestMixin, TestCase): redeemed=1, min_usages=3, max_usages=10) CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, voucher=v, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) cp2 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, voucher=v, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) cp3 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, voucher=v, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) self.client.post('/%s/%s/cart/remove' % (self.orga.slug, self.event.slug), { 'id': cp3.pk @@ -1384,11 +1527,11 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) response = self.client.post('/%s/%s/cart/remove' % (self.orga.slug, self.event.slug), { 'id': cp.pk @@ -1402,7 +1545,7 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_red, - price=14, expires=now() + timedelta(minutes=10) + price=14, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) response = self.client.post('/%s/%s/cart/remove' % (self.orga.slug, self.event.slug), { 'id': cp.pk @@ -1416,7 +1559,7 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): cp = CartPosition.objects.create( event=self.event, cart_id='invalid', item=self.shirt, variation=self.shirt_red, - price=14, expires=now() + timedelta(minutes=10) + price=14, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) response = self.client.post('/%s/%s/cart/remove' % (self.orga.slug, self.event.slug), { 'id': cp.pk @@ -1428,11 +1571,11 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) response = self.client.post('/%s/%s/cart/remove' % (self.orga.slug, self.event.slug), { 'id': cp.pk @@ -1446,15 +1589,15 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_red, - price=14, expires=now() + timedelta(minutes=10) + price=14, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) response = self.client.post('/%s/%s/cart/clear' % (self.orga.slug, self.event.slug), {}, follow=True) doc = BeautifulSoup(response.rendered_content, "lxml") @@ -1467,7 +1610,8 @@ class CartTest(CartTestMixin, TestCase): v = Voucher.objects.create(item=self.ticket, event=self.event, valid_until=now() - timedelta(days=1)) cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() - timedelta(minutes=10), voucher=v + price=23, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + voucher=v ) self.client.post('/%s/%s/cart/remove' % (self.orga.slug, self.event.slug), { 'id': cp.pk @@ -1495,7 +1639,8 @@ class CartTest(CartTestMixin, TestCase): v = Voucher.objects.create(item=self.ticket, event=self.event, block_quota=True) cp1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, expires=now() - timedelta(minutes=10), voucher=v + price=23, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + voucher=v ) self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'variation_%d_%d' % (self.shirt.id, self.shirt_red.id): '1', @@ -2110,7 +2255,8 @@ class CartTest(CartTestMixin, TestCase): v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, max_usages=2, redeemed=1) CartPosition.objects.create( - expires=now() - timedelta(minutes=10), item=self.ticket, voucher=v, price=Decimal('12.00'), + expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, voucher=v, price=Decimal('12.00'), event=self.event, cart_id=self.session_key ) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { @@ -2128,7 +2274,8 @@ class CartTest(CartTestMixin, TestCase): v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, max_usages=2, redeemed=1) CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, voucher=v, price=Decimal('12.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, voucher=v, price=Decimal('12.00'), event=self.event, cart_id='other' ) response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { @@ -2146,7 +2293,8 @@ class CartTest(CartTestMixin, TestCase): v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, max_usages=2, redeemed=1) CartPosition.objects.create( - expires=now() - timedelta(minutes=10), item=self.ticket, voucher=v, price=Decimal('12.00'), + expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, voucher=v, price=Decimal('12.00'), event=self.event, cart_id='other' ) self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { @@ -2161,11 +2309,13 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): cp1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, listed_price=23, price_after_voucher=23, expires=now() + timedelta(minutes=10) + price=23, listed_price=23, price_after_voucher=23, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) cp2 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue, - price=15, listed_price=15, price_after_voucher=15, expires=now() + timedelta(minutes=10) + price=15, listed_price=15, price_after_voucher=15, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) v = Voucher.objects.create( event=self.event, item=self.ticket, price_mode='set', value=Decimal('4.00') @@ -2185,11 +2335,13 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): cp1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, listed_price=23, price_after_voucher=23, expires=now() + timedelta(minutes=10) + price=23, listed_price=23, price_after_voucher=23, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) cp2 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue, - price=150, listed_price=150, price_after_voucher=150, expires=now() + timedelta(minutes=10) + price=150, listed_price=150, price_after_voucher=150, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) v = Voucher.objects.create( event=self.event, price_mode='set', value=Decimal('4.00'), max_usages=100, redeemed=99 @@ -2210,11 +2362,13 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): cp1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, listed_price=23, price_after_voucher=23, expires=now() + timedelta(minutes=10) + price=23, listed_price=23, price_after_voucher=23, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) cp2 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue, - price=150, listed_price=150, price_after_voucher=150, expires=now() + timedelta(minutes=10) + price=150, listed_price=150, price_after_voucher=150, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) v = Voucher.objects.create( event=self.event, price_mode='set', quota=self.quota_all, value=Decimal('4.00'), max_usages=100 @@ -2235,11 +2389,13 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): cp1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, listed_price=23, price_after_voucher=23, expires=now() + timedelta(minutes=10) + price=23, listed_price=23, price_after_voucher=23, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) cp2 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue, - price=150, listed_price=150, price_after_voucher=150, expires=now() + timedelta(minutes=10) + price=150, listed_price=150, price_after_voucher=150, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) v = Voucher.objects.create( event=self.event, price_mode='set', quota=self.quota_all, value=Decimal('4.00'), max_usages=100, @@ -2274,14 +2430,17 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): cp1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, listed_price=23, price_after_voucher=23, expires=now() + timedelta(minutes=10) + price=23, listed_price=23, price_after_voucher=23, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) v2 = Voucher.objects.create( event=self.event, price_mode='set', quota=self.quota_all, value=Decimal('8.00'), max_usages=100 ) cp2 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue, - price=8, expires=now() + timedelta(minutes=10), voucher=v2 + price=8, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + voucher=v2 ) v = Voucher.objects.create( event=self.event, price_mode='set', quota=self.quota_all, value=Decimal('4.00'), max_usages=100 @@ -2327,11 +2486,13 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): cp1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, listed_price=23, price_after_voucher=23, expires=now() + timedelta(minutes=10) + price=23, listed_price=23, price_after_voucher=23, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) cp2 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue, - price=15, listed_price=15, price_after_voucher=15, expires=now() + timedelta(minutes=10) + price=15, listed_price=15, price_after_voucher=15, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) v = Voucher.objects.create( event=self.event, price_mode='set', value=Decimal('40.00'), max_usages=100, @@ -2351,11 +2512,13 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): cp1 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, listed_price=23, price_after_voucher=23, expires=now() + timedelta(minutes=10) + price=23, listed_price=23, price_after_voucher=23, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) cp2 = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue, - price=15, listed_price=15, price_after_voucher=15, expires=now() + timedelta(minutes=10) + price=15, listed_price=15, price_after_voucher=15, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) v = Voucher.objects.create( event=self.event, price_mode='set', value=Decimal('40.00'), max_usages=100, redeemed=100 @@ -2374,11 +2537,12 @@ class CartTest(CartTestMixin, TestCase): with scopes_disabled(): CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=23, listed_price=23, price_after_voucher=23, expires=now() + timedelta(minutes=10) + price=23, listed_price=23, price_after_voucher=23, + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue, - price=8, expires=now() + timedelta(minutes=10), + price=8, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, ) gc = self.orga.issued_gift_cards.create(secret="GIFTCARD", currency=self.event.currency) gc.transactions.create(value=Decimal("12.24"), acceptor=self.orga) @@ -2491,7 +2655,8 @@ class CartAddonTest(CartTestMixin, TestCase): self.addon1.price_included = True self.addon1.save() cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) @@ -2512,11 +2677,13 @@ class CartAddonTest(CartTestMixin, TestCase): self.addon1.price_included = True self.addon1.save() cp1 = CartPosition.objects.create( - expires=now() - timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) cp2 = CartPosition.objects.create( - expires=now() - timedelta(minutes=10), item=self.workshop1, price=Decimal('0.00'), + expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.workshop1, price=Decimal('0.00'), event=self.event, cart_id=self.session_key, addon_to=cp1 ) self.cm.extend_expired_positions() @@ -2533,11 +2700,13 @@ class CartAddonTest(CartTestMixin, TestCase): self.quota_tickets.size = 0 self.quota_tickets.save() cp1 = CartPosition.objects.create( - expires=now() - timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) CartPosition.objects.create( - expires=now() - timedelta(minutes=10), item=self.workshop1, price=Decimal('0.00'), + expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.workshop1, price=Decimal('0.00'), event=self.event, cart_id=self.session_key, addon_to=cp1 ) self.cm.extend_expired_positions() @@ -2550,7 +2719,8 @@ class CartAddonTest(CartTestMixin, TestCase): self.addon1.price_included = True self.addon1.save() cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) @@ -2575,7 +2745,8 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_cart_set_simple_addon(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) @@ -2599,7 +2770,8 @@ class CartAddonTest(CartTestMixin, TestCase): self.workshopquota.subevent = se self.workshopquota.save() cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key, subevent=se ) @@ -2625,7 +2797,8 @@ class CartAddonTest(CartTestMixin, TestCase): self.workshopquota.subevent = se2 self.workshopquota.save() cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key, subevent=se ) @@ -2641,7 +2814,8 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_wrong_category(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) self.workshop1.category = self.category @@ -2658,7 +2832,8 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_invalid_parent(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id='other' ) with self.assertRaises(CartError): @@ -2674,7 +2849,8 @@ class CartAddonTest(CartTestMixin, TestCase): def test_no_quota_for_addon(self): self.workshopquota.delete() cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) with self.assertRaises(CartError): @@ -2689,7 +2865,8 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_unknown_addon_item(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) with self.assertRaises(CartError): @@ -2704,11 +2881,13 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_duplicate_items_for_other_cp(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) cp2 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) self.cm.set_addons([ @@ -2730,7 +2909,8 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_multi_allowed(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) self.addon1.max_count = 2 @@ -2754,7 +2934,8 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_number_exceeds_max(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) self.addon1.max_count = 2 @@ -2777,7 +2958,8 @@ class CartAddonTest(CartTestMixin, TestCase): self.workshopquota.size = 1 self.workshopquota.save() cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) self.addon1.max_count = 2 @@ -2800,7 +2982,8 @@ class CartAddonTest(CartTestMixin, TestCase): self.workshop3.free_price = True self.workshop3.save() cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) self.addon1.max_count = 5 @@ -2838,7 +3021,8 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_change_number(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) self.addon1.max_count = 5 @@ -2882,7 +3066,8 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_no_duplicate_items_for_same_cp(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) self.addon1.max_count = 2 @@ -2917,7 +3102,8 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_addon_max_count(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) with self.assertRaises(CartError): @@ -2952,7 +3138,8 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_addon_min_count(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) self.addon1.min_count = 2 @@ -2983,11 +3170,13 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_remove_with_addons(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) cp2 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.workshop1, price=Decimal('12.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.workshop1, price=Decimal('12.00'), event=self.event, cart_id=self.session_key, addon_to=cp1 ) self.cm.remove_item(cp1.pk) @@ -2998,11 +3187,13 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_remove_addons(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) cp2 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.workshop1, price=Decimal('12.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.workshop1, price=Decimal('12.00'), event=self.event, cart_id=self.session_key, addon_to=cp1 ) self.cm.set_addons([]) @@ -3012,11 +3203,13 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_remove_addons_below_min(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) cp2 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.workshop1, price=Decimal('12.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.workshop1, price=Decimal('12.00'), event=self.event, cart_id=self.session_key, addon_to=cp1 ) self.addon1.min_count = 1 @@ -3029,11 +3222,13 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_change_product(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.workshop1, price=Decimal('12.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.workshop1, price=Decimal('12.00'), event=self.event, cart_id=self.session_key, addon_to=cp1 ) self.cm.set_addons([ @@ -3051,11 +3246,13 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_unchanged(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.workshop1, price=Decimal('12.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.workshop1, price=Decimal('12.00'), event=self.event, cart_id=self.session_key, addon_to=cp1 ) self.cm.set_addons([ @@ -3071,7 +3268,8 @@ class CartAddonTest(CartTestMixin, TestCase): def test_exceed_max(self): self.event.settings.max_items_per_order = 1 cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) self.cm.set_addons([ @@ -3086,7 +3284,8 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_sold_out(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) self.workshopquota.size = 0 @@ -3104,11 +3303,13 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_sold_out_unchanged(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.workshop1, price=Decimal('12.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.workshop1, price=Decimal('12.00'), event=self.event, cart_id=self.session_key, addon_to=cp1 ) self.workshopquota.size = 0 @@ -3125,19 +3326,23 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_sold_out_swap_addons(self): cp1 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.workshop1, price=Decimal('12.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.workshop1, price=Decimal('12.00'), event=self.event, cart_id=self.session_key, addon_to=cp1 ) cp2 = CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) CartPosition.objects.create( - expires=now() + timedelta(minutes=10), item=self.workshop2, price=Decimal('12.00'), + expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.workshop2, price=Decimal('12.00'), event=self.event, cart_id=self.session_key, addon_to=cp2 ) self.workshopquota.size = 0 @@ -3163,11 +3368,13 @@ class CartAddonTest(CartTestMixin, TestCase): @classscope(attr='orga') def test_expand_expired(self): cp1 = CartPosition.objects.create( - expires=now() - timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'), + expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('23.00'), event=self.event, cart_id=self.session_key ) cp2 = CartPosition.objects.create( - expires=now() - timedelta(minutes=10), item=self.workshop1, price=Decimal('12.00'), + expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.workshop1, price=Decimal('12.00'), event=self.event, cart_id=self.session_key, addon_to=cp1 ) self.cm.extend_expired_positions() @@ -3183,7 +3390,8 @@ class CartAddonTest(CartTestMixin, TestCase): v = Voucher.objects.create(item=self.ticket, value=Decimal('20.00'), event=self.event, price_mode='set', valid_until=now() + timedelta(days=2), max_usages=999, redeemed=0) cp1 = CartPosition.objects.create( - expires=now() - timedelta(minutes=10), item=self.ticket, price=Decimal('21.50'), + expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + item=self.ticket, price=Decimal('21.50'), event=self.event, cart_id=self.session_key, voucher=v ) self.cm.extend_expired_positions() @@ -3647,11 +3855,12 @@ class CartBundleTest(CartTestMixin, TestCase): def test_extend_keep_price(self): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=21.5, expires=now() - timedelta(minutes=10) + price=21.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) b = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.5, expires=now() - timedelta(minutes=10), is_bundled=True + price=1.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=True ) self.cm.commit() cp.refresh_from_db() @@ -3663,11 +3872,12 @@ class CartBundleTest(CartTestMixin, TestCase): def test_extend_designated_price_changed(self): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=21.5, expires=now() - timedelta(minutes=10) + price=21.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) b = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.5, expires=now() - timedelta(minutes=10), is_bundled=True + price=1.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=True ) self.bundle1.designated_price = Decimal('2.00') self.bundle1.save() @@ -3681,11 +3891,12 @@ class CartBundleTest(CartTestMixin, TestCase): def test_extend_designated_price_changed_beyond_base_price(self): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=21.5, expires=now() - timedelta(minutes=10) + price=21.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) b = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.5, expires=now() - timedelta(minutes=10), is_bundled=True + price=1.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=True ) self.bundle1.designated_price = Decimal('40.00') self.bundle1.save() @@ -3699,11 +3910,12 @@ class CartBundleTest(CartTestMixin, TestCase): def test_voucher_apply_multiple(self): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=21.5, expires=now() + timedelta(minutes=10) + price=21.5, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) b = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.5, expires=now() + timedelta(minutes=10), is_bundled=True + price=1.5, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=True ) v = Voucher.objects.create( event=self.event, price_mode='set', value=Decimal('4.00'), max_usages=100 @@ -3720,11 +3932,12 @@ class CartBundleTest(CartTestMixin, TestCase): def test_voucher_apply_multiple_reduce_beyond_designated_price(self): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=21.5, expires=now() + timedelta(minutes=10) + price=21.5, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) b = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.5, expires=now() + timedelta(minutes=10), is_bundled=True + price=1.5, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=True ) v = Voucher.objects.create( event=self.event, price_mode='set', value=Decimal('0.00'), max_usages=100 @@ -3745,11 +3958,12 @@ class CartBundleTest(CartTestMixin, TestCase): self.trans.save() cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=21.5, expires=now() + timedelta(minutes=10) + price=21.5, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) b = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.5, expires=now() + timedelta(minutes=10), is_bundled=True + price=1.5, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=True ) v = Voucher.objects.create( event=self.event, price_mode='set', value=Decimal('0.00'), max_usages=100 @@ -3766,15 +3980,17 @@ class CartBundleTest(CartTestMixin, TestCase): def test_voucher_apply_affect_bundled(self): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=21.5, expires=now() + timedelta(minutes=10) + price=21.5, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) a = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=2.5, expires=now() + timedelta(minutes=10), is_bundled=False + price=2.5, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=False ) b = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.5, expires=now() + timedelta(minutes=10), is_bundled=True + price=1.5, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=True ) v = Voucher.objects.create( event=self.event, price_mode='set', value=Decimal('0.00'), max_usages=100, @@ -3794,15 +4010,17 @@ class CartBundleTest(CartTestMixin, TestCase): def test_voucher_apply_affect_addons(self): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=21.5, expires=now() + timedelta(minutes=10) + price=21.5, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) a = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.5, expires=now() + timedelta(minutes=10), is_bundled=False + price=1.5, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=False ) b = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.5, expires=now() + timedelta(minutes=10), is_bundled=True + price=1.5, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=True ) v = Voucher.objects.create( event=self.event, price_mode='set', value=Decimal('0.00'), max_usages=100, @@ -3822,11 +4040,12 @@ class CartBundleTest(CartTestMixin, TestCase): def test_extend_base_price_changed(self): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=21.5, expires=now() - timedelta(minutes=10) + price=21.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) b = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.5, expires=now() - timedelta(minutes=10), is_bundled=True + price=1.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=True ) self.ticket.default_price = Decimal('25.00') self.ticket.save() @@ -3840,15 +4059,17 @@ class CartBundleTest(CartTestMixin, TestCase): def test_extend_bundled_and_addon(self): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=21.5, expires=now() - timedelta(minutes=10) + price=21.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) a = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.5, expires=now() - timedelta(minutes=10), is_bundled=False + price=1.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=False ) b = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.5, expires=now() - timedelta(minutes=10), is_bundled=True + price=1.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=True ) self.cm.commit() cp.refresh_from_db() @@ -3873,11 +4094,12 @@ class CartBundleTest(CartTestMixin, TestCase): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=21.5, expires=now() - timedelta(minutes=10) + price=21.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) a = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.5, expires=now() - timedelta(minutes=10), is_bundled=True + price=1.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=True ) self.cm.invoice_address = ia self.cm.recompute_final_prices_and_taxes() @@ -3913,11 +4135,12 @@ class CartBundleTest(CartTestMixin, TestCase): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=21.5, expires=now() - timedelta(minutes=10) + price=21.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) a = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.5, expires=now() - timedelta(minutes=10), is_bundled=True + price=1.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=True ) self.cm.invoice_address = ia self.cm.recompute_final_prices_and_taxes() @@ -4048,11 +4271,12 @@ class CartBundleTest(CartTestMixin, TestCase): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=21.5, expires=now() - timedelta(minutes=10), + price=21.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, ) a = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.47, expires=now() - timedelta(minutes=10), is_bundled=True, tax_rate=Decimal('5.00') + price=1.47, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=True, tax_rate=Decimal('5.00') ) self.cm.invoice_address = ia self.cm.recompute_final_prices_and_taxes() @@ -4097,11 +4321,12 @@ class CartBundleTest(CartTestMixin, TestCase): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, - price=21.68, expires=now() - timedelta(minutes=10), tax_rate=Decimal('20.00') + price=21.68, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, tax_rate=Decimal('20.00') ) a = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp, - price=1.47, expires=now() - timedelta(minutes=10), is_bundled=True, tax_rate=Decimal('5.00') + price=1.47, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time, + is_bundled=True, tax_rate=Decimal('5.00') ) self.cm.invoice_address = ia self.cm.recompute_final_prices_and_taxes() @@ -4342,7 +4567,7 @@ class CartSeatingTest(CartTestMixin, TestCase): def test_add_with_seat_to_cart_twice(self): CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, seat=self.seat_a1, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) with scopes_disabled(): objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event)) @@ -4358,7 +4583,7 @@ class CartSeatingTest(CartTestMixin, TestCase): def test_add_used_seat_to_cart(self): CartPosition.objects.create( event=self.event, cart_id='aaa', item=self.ticket, seat=self.seat_a1, - price=23, expires=now() + timedelta(minutes=10) + price=23, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { 'seat_%d' % self.ticket.id: self.seat_a1.seat_guid, @@ -4372,7 +4597,7 @@ class CartSeatingTest(CartTestMixin, TestCase): with scopes_disabled(): cp = CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, seat=self.seat_a1, - price=21.5, expires=now() - timedelta(minutes=10) + price=21.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) self.cm.commit() cp.refresh_from_db() @@ -4383,11 +4608,11 @@ class CartSeatingTest(CartTestMixin, TestCase): with scopes_disabled(): CartPosition.objects.create( event=self.event, cart_id=self.session_key, item=self.ticket, seat=self.seat_a1, - price=21.5, expires=now() - timedelta(minutes=10) + price=21.5, expires=now() - timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) CartPosition.objects.create( event=self.event, cart_id='secondcart', item=self.ticket, seat=self.seat_a1, - price=21.5, expires=now() + timedelta(minutes=10) + price=21.5, expires=now() + timedelta(minutes=10), max_extend=now() + 10 * self.cart_reservation_time ) with self.assertRaises(CartError): self.cm.commit()