Enforce start and end time of presale (#15)

This commit is contained in:
Raphael Michel
2015-04-16 17:40:08 +02:00
parent 1532b3f1ee
commit 14c582d11e
9 changed files with 209 additions and 90 deletions

View File

@@ -482,6 +482,20 @@ class Event(Versionable):
""" """
return SettingsProxy(self, type=EventSetting, parent=self.organizer) 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): class EventPermission(Versionable):
""" """

View File

@@ -41,6 +41,14 @@ DEFAULTS = {
'default': 'True', 'default': 'True',
'type': bool 'type': bool
}, },
'presale_start_show_date': {
'default': 'True',
'type': bool
},
'show_items_outside_presale_period': {
'default': 'True',
'type': bool
},
'timezone': { 'timezone': {
'default': settings.TIME_ZONE, 'default': settings.TIME_ZONE,
'type': str 'type': str

View File

@@ -28,7 +28,9 @@
<fieldset> <fieldset>
<legend>{% trans "Timeline" %}</legend> <legend>{% trans "Timeline" %}</legend>
{% bootstrap_field form.presale_start layout="horizontal" %} {% 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 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_days layout="horizontal" %}
{% bootstrap_field sform.payment_term_last layout="horizontal" %} {% bootstrap_field sform.payment_term_last layout="horizontal" %}
{% bootstrap_field sform.payment_term_accept_late layout="horizontal" %} {% bootstrap_field sform.payment_term_accept_late layout="horizontal" %}

View File

@@ -55,6 +55,16 @@ class EventSettingsForm(SettingsForm):
label='Payment term in days', label='Payment term in days',
help_text=_("The number of days after placing an order the user has to pay to preserve his reservation."), 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( payment_term_last = forms.DateTimeField(
label='Last date of payments', label='Last date of payments',
help_text=_("The last date any payments are accepted. This has precedence over the number of " help_text=_("The last date any payments are accepted. This has precedence over the number of "

View File

@@ -30,97 +30,118 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<form method="post" {% if not event.presale_is_running %}
action="{% url "presale:event.cart.add" organizer=request.event.organizer.slug event=request.event.slug %}?next={{ request.path_info|urlencode }}"> <div class="alert alert-info">
{% csrf_token %} {% if event.presale_has_ended %}
{% for tup in items_by_category %} {% blocktrans trimmed %}
<section> The presale period for this event is over.
{% if tup.0 %}<h3>{{ tup.0.name }}</h3>{% endif %} {% endblocktrans %}
{% for item in tup.1 %} {% elif event.settings.presale_start_show_date %}
{% if item.has_variations %} {% blocktrans trimmed with date=event.presale_start|date time=event.presale_start|time %}
<div class="item-with-variations"> The presale for this event will start on {{ date }} at {{ time }}.
<div class="row-fluid product-row headline"> {% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
The presale for this event has not yet started.
{% endblocktrans %}
{% endif %}
</div>
{% endif %}
{% if event.presale_is_running or event.settings.show_items_outside_presale_period %}
<form method="post"
action="{% url "presale:event.cart.add" organizer=request.event.organizer.slug event=request.event.slug %}?next={{ request.path_info|urlencode }}">
{% csrf_token %}
{% for tup in items_by_category %}
<section>
{% if tup.0 %}<h3>{{ tup.0.name }}</h3>{% endif %}
{% for item in tup.1 %}
{% if item.has_variations %}
<div class="item-with-variations">
<div class="row-fluid product-row headline">
<div class="col-md-8 col-xs-12">
<a href="javascript:void();" data-toggle="variations">
<strong>{{ item.name }}</strong>
</a>
{% if item.short_description %}<p>{{ item.short_description }}</p>{% endif %}
</div>
<div class="col-md-2 col-xs-6 price">
{% blocktrans trimmed with minprice=item.min_price|floatformat:2 currency=event.currency %}
from {{ currency }} {{ minprice }}
{% endblocktrans %}
</div>
<div class="col-md-2 col-xs-6 availability-box">
<a href="javascript:void();" data-toggle="variations" class="js-only">
{% trans "Show variants" %}
</a>
</div>
<div class="clearfix"></div>
</div>
<div class="variations">
{% for var in item.available_variations %}
<div class="row-fluid product-row variation">
<div class="col-md-8 col-xs-12">
{{ var }}
</div>
<div class="col-md-2 col-xs-6 price">
{{ event.currency }} {{ var.price|floatformat:2 }}
{% if item.tax_rate %}
<br /><small>{% blocktrans trimmed with rate=item.tax_rate %}
incl. {{ rate }}% taxes
{% endblocktrans %}</small>
{% endif %}
</div>
{% if var.cached_availability.0 == 100 %}
<div class="col-md-2 col-xs-6 availability-box available">
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
max="{{ var.cached_availability.1 }}"
name="variation_{{ item.identity }}_{{ var.variation.identity }}">
</div>
{% else %}
{% include "pretixpresale/event/fragment_availability.html" with avail=var.cached_availability.0 %}
{% endif %}
<div class="clearfix"></div>
</div>
{% endfor %}
</div>
</div>
{% else %}
<div class="row-fluid product-row simple">
<div class="col-md-8 col-xs-12"> <div class="col-md-8 col-xs-12">
<a href="javascript:void();" data-toggle="variations"> <strong>{{ item.name }}</strong>
<strong>{{ item.name }}</strong>
</a>
{% if item.short_description %}<p>{{ item.short_description }}</p>{% endif %} {% if item.short_description %}<p>{{ item.short_description }}</p>{% endif %}
</div> </div>
<div class="col-md-2 col-xs-6 price"> <div class="col-md-2 col-xs-6 price">
{% blocktrans trimmed with minprice=item.min_price|floatformat:2 currency=event.currency %} {{ event.currency }} {{ item.price }}
from {{ currency }} {{ minprice }} {% if item.tax_rate %}
{% endblocktrans %} <br /><small>{% blocktrans trimmed with rate=item.tax_rate %}
</div> incl. {{ rate }}% taxes
<div class="col-md-2 col-xs-6 availability-box"> {% endblocktrans %}</small>
<a href="javascript:void();" data-toggle="variations" class="js-only"> {% endif %}
{% trans "Show variants" %}
</a>
</div> </div>
{% if item.cached_availability.0 == 100 %}
<div class="col-md-2 col-xs-6 availability-box available">
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
max="{{ item.cached_availability.1 }}" name="item_{{ item.identity }}">
</div>
{% else %}
{% include "pretixpresale/event/fragment_availability.html" with avail=item.cached_availability.0 %}
{% endif %}
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
<div class="variations"> {% endif %}
{% for var in item.available_variations %} {% endfor %}
<div class="row-fluid product-row variation"> </section>
<div class="col-md-8 col-xs-12"> {% endfor %}
{{ var }} {% if event.presale_is_running %}
</div> <div class="row-fluid checkout-button-row">
<div class="col-md-2 col-xs-6 price"> <div class="col-md-4 col-md-offset-8">
{{ event.currency }} {{ var.price|floatformat:2 }} <button class="btn btn-block btn-primary btn-lg" type="submit">
{% if item.tax_rate %} <i class="fa fa-shopping-cart"></i> {% trans "Add to cart" %}
<br /><small>{% blocktrans trimmed with rate=item.tax_rate %} </button>
incl. {{ rate }}% taxes </div>
{% endblocktrans %}</small> <div class="clearfix"></div>
{% endif %} </div>
</div> {% endif %}
{% if var.cached_availability.0 == 100 %} </form>
<div class="col-md-2 col-xs-6 availability-box available"> {% endif %}
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
max="{{ var.cached_availability.1 }}"
name="variation_{{ item.identity }}_{{ var.variation.identity }}">
</div>
{% else %}
{% include "pretixpresale/event/fragment_availability.html" with avail=var.cached_availability.0 %}
{% endif %}
<div class="clearfix"></div>
</div>
{% endfor %}
</div>
</div>
{% else %}
<div class="row-fluid product-row simple">
<div class="col-md-8 col-xs-12">
<strong>{{ item.name }}</strong>
{% if item.short_description %}<p>{{ item.short_description }}</p>{% endif %}
</div>
<div class="col-md-2 col-xs-6 price">
{{ event.currency }} {{ item.price }}
{% if item.tax_rate %}
<br /><small>{% blocktrans trimmed with rate=item.tax_rate %}
incl. {{ rate }}% taxes
{% endblocktrans %}</small>
{% endif %}
</div>
{% if item.cached_availability.0 == 100 %}
<div class="col-md-2 col-xs-6 availability-box available">
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
max="{{ item.cached_availability.1 }}" name="item_{{ item.identity }}">
</div>
{% else %}
{% include "pretixpresale/event/fragment_availability.html" with avail=item.cached_availability.0 %}
{% endif %}
<div class="clearfix"></div>
</div>
{% endif %}
{% endfor %}
</section>
{% endfor %}
<div class="row-fluid checkout-button-row">
<div class="col-md-4 col-md-offset-8">
<button class="btn btn-block btn-primary btn-lg" type="submit">
<i class="fa fa-shopping-cart"></i> {% trans "Add to cart" %}
</button>
</div>
<div class="clearfix"></div>
</div>
</form>
{% endblock %} {% endblock %}

View File

@@ -4,8 +4,10 @@ from django.contrib.auth.views import redirect_to_login
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponseBadRequest, HttpResponseForbidden
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import CartPosition from pretix.base.models import CartPosition
from pretix.base.signals import register_payment_providers from pretix.base.signals import register_payment_providers

View File

@@ -99,6 +99,13 @@ class CartAdd(EventViewMixin, CartActionMixin, View):
self.msg_some_unavailable = False self.msg_some_unavailable = False
def post(self, request, *args, **kwargs): 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() self.items = self._items_from_post_data()
# We do not use EventLoginRequiredMixin here, as we want to store stuff into the # We do not use EventLoginRequiredMixin here, as we want to store stuff into the

View File

@@ -355,10 +355,11 @@ class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, Ch
quota_ok = False quota_ok = False
break break
if quota_ok: if quota_ok:
cp = cp.clone() if not self.request.event.presale_end or now() < self.request.event.presale_end:
cartpos[i] = cp cp = cp.clone()
cp.expires = now() + timedelta(minutes=self.request.event.settings.get('reservation_time', as_type=int)) cartpos[i] = cp
cp.save() cp.expires = now() + timedelta(minutes=self.request.event.settings.get('reservation_time', as_type=int))
cp.save()
else: else:
cp.delete() # Sorry! cp.delete() # Sorry!
if not self.msg_some_unavailable: # Everything went well if not self.msg_some_unavailable: # Everything went well

View File

@@ -1,6 +1,7 @@
import datetime import datetime
import time import time
from django.test import TestCase 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 pretix.base.models import Item, Organizer, Event, ItemCategory, Quota, Property, PropertyValue, ItemVariation, User
from tests.base import BrowserTest from tests.base import BrowserTest
@@ -186,3 +187,56 @@ class LoginTest(EventTestMixin, TestCase):
'/%s/%s/login' % (self.orga.slug, self.event.slug), '/%s/%s/login' % (self.orga.slug, self.event.slug),
) )
self.assertEqual(response.status_code, 200) 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)