From 67678e35bb102a1cdb57ac2a8a070e342e7afab4 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sun, 4 Feb 2018 14:14:49 +0100 Subject: [PATCH] Disable shop and waiting list after end of event --- src/pretix/base/models/event.py | 39 ++++++++++++----------- src/pretix/base/services/cart.py | 2 +- src/pretix/base/services/orders.py | 2 +- src/pretix/base/services/waitinglist.py | 6 +++- src/tests/base/test_models.py | 32 +++++++++++++++++++ src/tests/base/test_waitinglist.py | 17 ++++++++++ src/tests/plugins/paypal/test_checkout.py | 2 +- src/tests/plugins/stripe/test_checkout.py | 2 +- src/tests/presale/test_cart.py | 13 +++++++- src/tests/presale/test_checkout.py | 15 ++++++++- src/tests/presale/test_event.py | 22 ++++++++++++- 11 files changed, 126 insertions(+), 26 deletions(-) diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index a76604ed62..4dbe55c04d 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -43,7 +43,7 @@ class EventMixin: Returns a shorter formatted string containing the start date of the event with respect to the current locale and to the ``show_times`` setting. """ - tz = tz or pytz.timezone(self.settings.timezone) + tz = tz or self.timezone return _date( self.date_from.astimezone(tz), "SHORT_DATETIME_FORMAT" if self.settings.show_times and show_times else "DATE_FORMAT" @@ -55,7 +55,7 @@ class EventMixin: to the current locale and to the ``show_times`` setting. Returns an empty string if ``show_date_to`` is ``False``. """ - tz = tz or pytz.timezone(self.settings.timezone) + tz = tz or self.timezone if not self.settings.show_date_to or not self.date_to: return "" return _date( @@ -68,7 +68,7 @@ class EventMixin: Returns a formatted string containing the start date of the event with respect to the current locale and to the ``show_times`` setting. """ - tz = tz or pytz.timezone(self.settings.timezone) + tz = tz or self.timezone return _date( self.date_from.astimezone(tz), "DATETIME_FORMAT" if self.settings.show_times and show_times else "DATE_FORMAT" @@ -79,7 +79,7 @@ class EventMixin: Returns a formatted string containing the start time of the event, ignoring the ``show_times`` setting. """ - tz = tz or pytz.timezone(self.settings.timezone) + tz = tz or self.timezone return _date( self.date_from.astimezone(tz), "TIME_FORMAT" ) @@ -90,7 +90,7 @@ class EventMixin: to the current locale and to the ``show_times`` setting. Returns an empty string if ``show_date_to`` is ``False``. """ - tz = tz or pytz.timezone(self.settings.timezone) + tz = tz or self.timezone if not self.settings.show_date_to or not self.date_to: return "" return _date( @@ -104,19 +104,26 @@ class EventMixin: of the event with respect to the current locale and to the ``show_times`` and ``show_date_to`` settings. """ - tz = tz or pytz.timezone(self.settings.timezone) + tz = tz or self.timezone if not self.settings.show_date_to or not self.date_to: return _date(self.date_from.astimezone(tz), "DATE_FORMAT") return daterange(self.date_from.astimezone(tz), self.date_to.astimezone(tz)) + @property + def timezone(self): + return pytz.timezone(self.settings.timezone) + @property def presale_has_ended(self): """ Is true, when ``presale_end`` is set and in the past. """ - if self.presale_end and now() > self.presale_end: - return True - return False + if self.presale_end: + return now() > self.presale_end + elif self.date_to: + return now() > self.date_to + else: + return now().astimezone(self.timezone).date() > self.date_from.astimezone(self.timezone).date() @property def presale_is_running(self): @@ -126,9 +133,7 @@ class EventMixin: """ if self.presale_start and now() < self.presale_start: return False - if self.presale_end and now() > self.presale_end: - return False - return True + return not self.presale_has_ended @property def event_microdata(self): @@ -229,7 +234,8 @@ class Event(EventMixin, LoggedModel): presale_end = models.DateTimeField( null=True, blank=True, verbose_name=_("End of presale"), - help_text=_("Optional. No products will be sold after this date."), + help_text=_("Optional. No products will be sold after this date. If you do not set this value, the presale " + "will end after the end date of your event."), ) presale_start = models.DateTimeField( null=True, blank=True, @@ -323,10 +329,6 @@ class Event(EventMixin, LoggedModel): else: return get_connection(fail_silently=False) - @property - def timezone(self): - return pytz.timezone(self.settings.timezone) - @property def payment_term_last(self): """ @@ -590,7 +592,8 @@ class SubEvent(EventMixin, LoggedModel): presale_end = models.DateTimeField( null=True, blank=True, verbose_name=_("End of presale"), - help_text=_("Optional. No products will be sold after this date."), + help_text=_("Optional. No products will be sold after this date. If you do not set this value, the presale " + "will end after the end date of your event."), ) presale_start = models.DateTimeField( null=True, blank=True, diff --git a/src/pretix/base/services/cart.py b/src/pretix/base/services/cart.py index 58b0405971..24f3f22769 100644 --- a/src/pretix/base/services/cart.py +++ b/src/pretix/base/services/cart.py @@ -112,7 +112,7 @@ class CartManager: def _check_presale_dates(self): if self.event.presale_start and self.now_dt < self.event.presale_start: raise CartError(error_messages['not_started']) - if self.event.presale_end and self.now_dt > self.event.presale_end: + if self.event.presale_has_ended: raise CartError(error_messages['ended']) def _extend_expiry_of_valid_existing_positions(self): diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index 2460261953..e9b7f69106 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -321,7 +321,7 @@ class OrderError(LazyLocaleException): def _check_date(event: Event, now_dt: datetime): if event.presale_start and now_dt < event.presale_start: raise OrderError(error_messages['not_started']) - if event.presale_end and now_dt > event.presale_end: + if event.presale_has_ended: raise OrderError(error_messages['ended']) diff --git a/src/pretix/base/services/waitinglist.py b/src/pretix/base/services/waitinglist.py index 90a60545d8..4e03fdd17a 100644 --- a/src/pretix/base/services/waitinglist.py +++ b/src/pretix/base/services/waitinglist.py @@ -35,6 +35,10 @@ def assign_automatically(event_id: int, user_id: int=None, subevent_id: int=None if (wle.item, wle.variation) in gone: continue + ev = (wle.subevent or event) + if not ev.presale_is_running: + continue + quotas = (wle.variation.quotas.filter(subevent=wle.subevent) if wle.variation else wle.item.quotas.filter(subevent=wle.subevent)) @@ -66,5 +70,5 @@ def assign_automatically(event_id: int, user_id: int=None, subevent_id: int=None def process_waitinglist(sender, **kwargs): qs = Event.objects.prefetch_related('_settings_objects', 'organizer___settings_objects').select_related('organizer') for e in qs: - if e.settings.waiting_list_enabled and e.settings.waiting_list_auto: + if e.settings.waiting_list_enabled and e.settings.waiting_list_auto and e.presale_is_running: assign_automatically.apply_async(args=(e.pk,)) diff --git a/src/tests/base/test_models.py b/src/tests/base/test_models.py index 8c02be8a05..c8f134b460 100644 --- a/src/tests/base/test_models.py +++ b/src/tests/base/test_models.py @@ -1020,6 +1020,38 @@ class EventTest(TestCase): assert event2.checkin_lists.count() == 1 assert [i.pk for i in event2.checkin_lists.first().limit_products.all()] == [i1new.pk] + def test_presale_has_ended(self): + event = Event( + organizer=self.organizer, name='Download', slug='download', + date_from=now() + ) + assert not event.presale_has_ended + assert event.presale_is_running + + event.date_from = now().replace(hour=23, minute=59, second=59) + assert not event.presale_has_ended + assert event.presale_is_running + + event.date_from = now() - timedelta(days=1) + assert event.presale_has_ended + assert not event.presale_is_running + + event.date_to = now() + timedelta(days=1) + assert not event.presale_has_ended + assert event.presale_is_running + + event.date_to = now() - timedelta(days=1) + assert event.presale_has_ended + assert not event.presale_is_running + + event.presale_end = now() + timedelta(days=1) + assert not event.presale_has_ended + assert event.presale_is_running + + event.presale_end = now() - timedelta(days=1) + assert event.presale_has_ended + assert not event.presale_is_running + class SubEventTest(TestCase): @classmethod diff --git a/src/tests/base/test_waitinglist.py b/src/tests/base/test_waitinglist.py index a244caf09e..006801cb03 100644 --- a/src/tests/base/test_waitinglist.py +++ b/src/tests/base/test_waitinglist.py @@ -1,3 +1,5 @@ +from datetime import timedelta + from django.core import mail as djmail from django.test import TestCase from django.utils.timezone import now @@ -115,6 +117,21 @@ class WaitingListTestCase(TestCase): assert WaitingListEntry.objects.filter(voucher__isnull=True).count() == 10 assert Voucher.objects.count() == 10 + def test_send_periodic_event_over(self): + self.event.settings.set('waiting_list_enabled', True) + self.event.settings.set('waiting_list_auto', True) + self.event.presale_end = now() - timedelta(days=1) + self.event.save() + for i in range(5): + WaitingListEntry.objects.create( + event=self.event, item=self.item2, variation=self.var1, email='foo{}@bar.com'.format(i) + ) + process_waitinglist(None) + assert WaitingListEntry.objects.filter(voucher__isnull=True).count() == 5 + assert Voucher.objects.count() == 0 + self.event.presale_end = now() + timedelta(days=1) + self.event.save() + def test_send_periodic(self): self.event.settings.set('waiting_list_enabled', True) self.event.settings.set('waiting_list_auto', True) diff --git a/src/tests/plugins/paypal/test_checkout.py b/src/tests/plugins/paypal/test_checkout.py index 21472c0e74..932710f13c 100644 --- a/src/tests/plugins/paypal/test_checkout.py +++ b/src/tests/plugins/paypal/test_checkout.py @@ -14,7 +14,7 @@ def env(client): orga = Organizer.objects.create(name='CCC', slug='ccc') event = Event.objects.create( organizer=orga, name='30C3', slug='30c3', - date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc), + date_from=datetime.datetime(now().year + 1, 12, 26, tzinfo=datetime.timezone.utc), plugins='pretix.plugins.paypal', live=True ) diff --git a/src/tests/plugins/stripe/test_checkout.py b/src/tests/plugins/stripe/test_checkout.py index 8aaf6f6e97..11f58ae1ad 100644 --- a/src/tests/plugins/stripe/test_checkout.py +++ b/src/tests/plugins/stripe/test_checkout.py @@ -24,7 +24,7 @@ def env(client): orga = Organizer.objects.create(name='CCC', slug='ccc') event = Event.objects.create( organizer=orga, name='30C3', slug='30c3', - date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc), + date_from=datetime.datetime(now().year + 1, 12, 26, tzinfo=datetime.timezone.utc), plugins='pretix.plugins.stripe', live=True ) diff --git a/src/tests/presale/test_cart.py b/src/tests/presale/test_cart.py index e8e7f742fa..d79560e4b5 100644 --- a/src/tests/presale/test_cart.py +++ b/src/tests/presale/test_cart.py @@ -25,7 +25,7 @@ class CartTestMixin: self.orga = Organizer.objects.create(name='CCC', slug='ccc') self.event = Event.objects.create( organizer=self.orga, name='30C3', slug='30c3', - date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc), + date_from=datetime.datetime(now().year + 1, 12, 26, tzinfo=datetime.timezone.utc), live=True, plugins="pretix.plugins.banktransfer" ) @@ -68,6 +68,17 @@ class CartTest(CartTestMixin, TestCase): assert 'alert-danger' in response.rendered_content assert not CartPosition.objects.filter(cart_id=self.session_key, event=self.event).exists() + def test_after_event(self): + self.event.date_to = now() - timedelta(days=1) + self.event.save() + response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), { + 'item_%d' % self.ticket.id: '1' + }, follow=True) + self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug), + target_status_code=200) + assert 'alert-danger' in response.rendered_content + assert not CartPosition.objects.filter(cart_id=self.session_key, event=self.event).exists() + def test_before_presale(self): self.event.presale_start = now() + timedelta(days=1) self.event.save() diff --git a/src/tests/presale/test_checkout.py b/src/tests/presale/test_checkout.py index a4edd7ad06..d4b1cfc9cd 100644 --- a/src/tests/presale/test_checkout.py +++ b/src/tests/presale/test_checkout.py @@ -26,7 +26,7 @@ class CheckoutTestCase(TestCase): self.orga = Organizer.objects.create(name='CCC', slug='ccc') self.event = Event.objects.create( organizer=self.orga, name='30C3', slug='30c3', - date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc), + date_from=datetime.datetime(now().year + 1, 12, 26, tzinfo=datetime.timezone.utc), plugins='pretix.plugins.stripe,pretix.plugins.banktransfer', live=True ) @@ -1110,6 +1110,19 @@ class CheckoutTestCase(TestCase): self.assertEqual(len(doc.select(".alert-danger")), 1) self.assertEqual(CartPosition.objects.filter(cart_id=self.session_key).count(), 1) + def test_confirm_event_over(self): + self.event.date_to = now() - datetime.timedelta(days=1) + self.event.save() + CartPosition.objects.create( + event=self.event, cart_id=self.session_key, item=self.ticket, + price=23, expires=now() + timedelta(minutes=10) + ) + self._set_session('payment', 'banktransfer') + + response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True) + doc = BeautifulSoup(response.rendered_content, "lxml") + self.assertGreaterEqual(len(doc.select(".alert-danger")), 1) + def test_confirm_presale_over(self): self.event.presale_end = now() - datetime.timedelta(days=1) self.event.save() diff --git a/src/tests/presale/test_event.py b/src/tests/presale/test_event.py index e6b82ebf53..7c0cf55220 100644 --- a/src/tests/presale/test_event.py +++ b/src/tests/presale/test_event.py @@ -24,7 +24,7 @@ class EventTestMixin: self.orga = Organizer.objects.create(name='CCC', slug='ccc') self.event = Event.objects.create( organizer=self.orga, name='30C3', slug='30c3', - date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc), + date_from=datetime.datetime(now().year + 1, 12, 26, tzinfo=datetime.timezone.utc), live=True ) self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy') @@ -706,6 +706,26 @@ class DeadlineTest(EventTestMixin, TestCase): self.assertIn('alert-danger', response.rendered_content) self.assertIn('not yet started', response.rendered_content) + def test_event_over(self): + self.event.date_to = now() - datetime.timedelta(days=1) + self.event.presale_end = None + self.event.save() + response = self.client.get( + '/%s/%s/' % (self.orga.slug, self.event.slug) + ) + self.assertEqual(response.status_code, 200) + self.assertIn('alert-info', response.rendered_content) + self.assertNotIn('btn-add-to-cart', response.rendered_content) + response = self.client.post( + '/%s/%s/cart/add' % (self.orga.slug, self.event.slug), + { + 'item_%d' % self.item.id: '1' + }, + follow=True + ) + self.assertIn('alert-danger', response.rendered_content) + self.assertIn('is over', response.rendered_content) + def test_over(self): self.event.presale_end = now() - datetime.timedelta(days=1) self.event.save()