From 14c582d11e025156a71103889f81465ad7607d15 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Thu, 16 Apr 2015 17:40:08 +0200 Subject: [PATCH] Enforce start and end time of presale (#15) --- src/pretix/base/models.py | 14 ++ src/pretix/base/settings.py | 8 + .../pretixcontrol/event/settings.html | 2 + src/pretix/control/views/event.py | 10 + .../templates/pretixpresale/event/index.html | 193 ++++++++++-------- src/pretix/presale/views/__init__.py | 2 + src/pretix/presale/views/cart.py | 7 + src/pretix/presale/views/checkout.py | 9 +- src/tests/presale/test_event.py | 54 +++++ 9 files changed, 209 insertions(+), 90 deletions(-) diff --git a/src/pretix/base/models.py b/src/pretix/base/models.py index 7ba232143..2f5fb09db 100644 --- a/src/pretix/base/models.py +++ b/src/pretix/base/models.py @@ -482,6 +482,20 @@ class Event(Versionable): """ return SettingsProxy(self, type=EventSetting, parent=self.organizer) + @property + def presale_has_ended(self): + if self.presale_end and now() > self.presale_end: + return True + return False + + @property + def presale_is_running(self): + if self.presale_start and now() < self.presale_start: + return False + if self.presale_end and now() > self.presale_end: + return False + return True + class EventPermission(Versionable): """ diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index 92e5ed967..cbbf4cc0f 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -41,6 +41,14 @@ DEFAULTS = { 'default': 'True', 'type': bool }, + 'presale_start_show_date': { + 'default': 'True', + 'type': bool + }, + 'show_items_outside_presale_period': { + 'default': 'True', + 'type': bool + }, 'timezone': { 'default': settings.TIME_ZONE, 'type': str diff --git a/src/pretix/control/templates/pretixcontrol/event/settings.html b/src/pretix/control/templates/pretixcontrol/event/settings.html index 30f07b1c5..36630fad7 100644 --- a/src/pretix/control/templates/pretixcontrol/event/settings.html +++ b/src/pretix/control/templates/pretixcontrol/event/settings.html @@ -28,7 +28,9 @@
{% trans "Timeline" %} {% bootstrap_field form.presale_start layout="horizontal" %} + {% bootstrap_field sform.presale_start_show_date layout="horizontal" %} {% bootstrap_field form.presale_end layout="horizontal" %} + {% bootstrap_field sform.show_items_outside_presale_period layout="horizontal" %} {% bootstrap_field sform.payment_term_days layout="horizontal" %} {% bootstrap_field sform.payment_term_last layout="horizontal" %} {% bootstrap_field sform.payment_term_accept_late layout="horizontal" %} diff --git a/src/pretix/control/views/event.py b/src/pretix/control/views/event.py index 8dea6bab9..71dcbd52c 100644 --- a/src/pretix/control/views/event.py +++ b/src/pretix/control/views/event.py @@ -55,6 +55,16 @@ class EventSettingsForm(SettingsForm): label='Payment term in days', help_text=_("The number of days after placing an order the user has to pay to preserve his reservation."), ) + show_items_outside_presale_period = forms.BooleanField( + label=_("Show items outside presale period"), + help_text=_("Show item details before presale has started and after presale has ended"), + required=False + ) + presale_start_show_date = forms.BooleanField( + label=_("Show start date"), + help_text=_("Show the presale start date before presale has started"), + required=False + ) payment_term_last = forms.DateTimeField( label='Last date of payments', help_text=_("The last date any payments are accepted. This has precedence over the number of " diff --git a/src/pretix/presale/templates/pretixpresale/event/index.html b/src/pretix/presale/templates/pretixpresale/event/index.html index 6bfee2f36..a750bd873 100644 --- a/src/pretix/presale/templates/pretixpresale/event/index.html +++ b/src/pretix/presale/templates/pretixpresale/event/index.html @@ -30,97 +30,118 @@ {% endif %} -
- {% csrf_token %} - {% for tup in items_by_category %} -
- {% if tup.0 %}

{{ tup.0.name }}

{% endif %} - {% for item in tup.1 %} - {% if item.has_variations %} -
-
+ {% if not event.presale_is_running %} +
+ {% if event.presale_has_ended %} + {% blocktrans trimmed %} + The presale period for this event is over. + {% endblocktrans %} + {% elif event.settings.presale_start_show_date %} + {% blocktrans trimmed with date=event.presale_start|date time=event.presale_start|time %} + The presale for this event will start on {{ date }} at {{ time }}. + {% endblocktrans %} + {% else %} + {% blocktrans trimmed %} + The presale for this event has not yet started. + {% endblocktrans %} + {% endif %} +
+ {% endif %} + {% if event.presale_is_running or event.settings.show_items_outside_presale_period %} + + {% csrf_token %} + {% for tup in items_by_category %} +
+ {% if tup.0 %}

{{ tup.0.name }}

{% endif %} + {% for item in tup.1 %} + {% if item.has_variations %} +
+
+
+ + {{ item.name }} + + {% if item.short_description %}

{{ item.short_description }}

{% endif %} +
+
+ {% blocktrans trimmed with minprice=item.min_price|floatformat:2 currency=event.currency %} + from {{ currency }} {{ minprice }} + {% endblocktrans %} +
+ +
+
+
+ {% for var in item.available_variations %} +
+
+ {{ var }} +
+
+ {{ event.currency }} {{ var.price|floatformat:2 }} + {% if item.tax_rate %} +
{% blocktrans trimmed with rate=item.tax_rate %} + incl. {{ rate }}% taxes + {% endblocktrans %} + {% endif %} +
+ {% if var.cached_availability.0 == 100 %} +
+ +
+ {% else %} + {% include "pretixpresale/event/fragment_availability.html" with avail=var.cached_availability.0 %} + {% endif %} +
+
+ {% endfor %} +
+
+ {% else %} +
- - {{ item.name }} - + {{ item.name }} {% if item.short_description %}

{{ item.short_description }}

{% endif %}
- {% blocktrans trimmed with minprice=item.min_price|floatformat:2 currency=event.currency %} - from {{ currency }} {{ minprice }} - {% endblocktrans %} -
-
- - {% trans "Show variants" %} - + {{ event.currency }} {{ item.price }} + {% if item.tax_rate %} +
{% blocktrans trimmed with rate=item.tax_rate %} + incl. {{ rate }}% taxes + {% endblocktrans %} + {% endif %}
+ {% if item.cached_availability.0 == 100 %} +
+ +
+ {% else %} + {% include "pretixpresale/event/fragment_availability.html" with avail=item.cached_availability.0 %} + {% endif %}
-
- {% for var in item.available_variations %} -
-
- {{ var }} -
-
- {{ event.currency }} {{ var.price|floatformat:2 }} - {% if item.tax_rate %} -
{% blocktrans trimmed with rate=item.tax_rate %} - incl. {{ rate }}% taxes - {% endblocktrans %} - {% endif %} -
- {% if var.cached_availability.0 == 100 %} -
- -
- {% else %} - {% include "pretixpresale/event/fragment_availability.html" with avail=var.cached_availability.0 %} - {% endif %} -
-
- {% endfor %} -
-
- {% else %} -
-
- {{ item.name }} - {% if item.short_description %}

{{ item.short_description }}

{% endif %} -
-
- {{ event.currency }} {{ item.price }} - {% if item.tax_rate %} -
{% blocktrans trimmed with rate=item.tax_rate %} - incl. {{ rate }}% taxes - {% endblocktrans %} - {% endif %} -
- {% if item.cached_availability.0 == 100 %} -
- -
- {% else %} - {% include "pretixpresale/event/fragment_availability.html" with avail=item.cached_availability.0 %} - {% endif %} -
-
- {% endif %} - {% endfor %} -
- {% endfor %} -
-
- -
-
-
-
+ {% endif %} + {% endfor %} + + {% endfor %} + {% if event.presale_is_running %} +
+
+ +
+
+
+ {% endif %} + + {% endif %} {% endblock %} \ No newline at end of file diff --git a/src/pretix/presale/views/__init__.py b/src/pretix/presale/views/__init__.py index 7223c86e8..02609cdad 100644 --- a/src/pretix/presale/views/__init__.py +++ b/src/pretix/presale/views/__init__.py @@ -4,8 +4,10 @@ from django.contrib.auth.views import redirect_to_login from django.core.urlresolvers import reverse from django.db.models import Q +from django.http import HttpResponseBadRequest, HttpResponseForbidden from django.utils.functional import cached_property from django.utils.timezone import now +from django.utils.translation import ugettext_lazy as _ from pretix.base.models import CartPosition from pretix.base.signals import register_payment_providers diff --git a/src/pretix/presale/views/cart.py b/src/pretix/presale/views/cart.py index b87cdc569..8e298cb41 100644 --- a/src/pretix/presale/views/cart.py +++ b/src/pretix/presale/views/cart.py @@ -99,6 +99,13 @@ class CartAdd(EventViewMixin, CartActionMixin, View): self.msg_some_unavailable = False def post(self, request, *args, **kwargs): + if request.event.presale_start and now() < request.event.presale_start: + messages.error(request, _('The presale period not yet started.')) + return redirect(self.get_failure_url()) + if request.event.presale_end and now() > request.event.presale_end: + messages.error(request, _('The presale period has ended.')) + return redirect(self.get_failure_url()) + self.items = self._items_from_post_data() # We do not use EventLoginRequiredMixin here, as we want to store stuff into the diff --git a/src/pretix/presale/views/checkout.py b/src/pretix/presale/views/checkout.py index 94c6d3dd4..584e9906d 100644 --- a/src/pretix/presale/views/checkout.py +++ b/src/pretix/presale/views/checkout.py @@ -355,10 +355,11 @@ class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, Ch quota_ok = False break if quota_ok: - cp = cp.clone() - cartpos[i] = cp - cp.expires = now() + timedelta(minutes=self.request.event.settings.get('reservation_time', as_type=int)) - cp.save() + if not self.request.event.presale_end or now() < self.request.event.presale_end: + cp = cp.clone() + cartpos[i] = cp + cp.expires = now() + timedelta(minutes=self.request.event.settings.get('reservation_time', as_type=int)) + cp.save() else: cp.delete() # Sorry! if not self.msg_some_unavailable: # Everything went well diff --git a/src/tests/presale/test_event.py b/src/tests/presale/test_event.py index b9a56ead8..97c85f195 100644 --- a/src/tests/presale/test_event.py +++ b/src/tests/presale/test_event.py @@ -1,6 +1,7 @@ import datetime import time from django.test import TestCase +from django.utils.timezone import now from pretix.base.models import Item, Organizer, Event, ItemCategory, Quota, Property, PropertyValue, ItemVariation, User from tests.base import BrowserTest @@ -186,3 +187,56 @@ class LoginTest(EventTestMixin, TestCase): '/%s/%s/login' % (self.orga.slug, self.event.slug), ) self.assertEqual(response.status_code, 200) + + +class DeadlineTest(EventTestMixin, TestCase): + + def setUp(self): + super().setUp() + + def test_not_yet_started(self): + self.event.presale_start = now() + datetime.timedelta(days=1) + 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('checkout-button-row', response.rendered_content) + response = self.client.post( + '/%s/%s/cart/add' % (self.orga.slug, self.event.slug), + follow=True + ) + self.assertIn('alert-danger', response.rendered_content) + self.assertIn('not yet started', response.rendered_content) + + def test_over(self): + self.event.presale_end = now() - datetime.timedelta(days=1) + 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('checkout-button-row', response.rendered_content) + response = self.client.post( + '/%s/%s/cart/add' % (self.orga.slug, self.event.slug), + follow=True + ) + self.assertIn('alert-danger', response.rendered_content) + self.assertIn('is over', response.rendered_content) + + def test_in_time(self): + self.event.presale_start = now() - datetime.timedelta(days=1) + self.event.presale_end = now() + datetime.timedelta(days=1) + self.event.save() + response = self.client.get( + '/%s/%s/' % (self.orga.slug, self.event.slug) + ) + self.assertEqual(response.status_code, 200) + self.assertNotIn('alert-info', response.rendered_content) + self.assertIn('checkout-button-row', response.rendered_content) + response = self.client.post( + '/%s/%s/cart/add' % (self.orga.slug, self.event.slug) + ) + self.assertNotEqual(response.status_code, 403)