mirror of
https://github.com/pretix/pretix.git
synced 2026-05-09 15:54:03 +00:00
Cart expiry handling, checkout link
This commit is contained in:
@@ -9,6 +9,24 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% include "pretixpresale/event/fragment_cart.html" with cart=cart event=request.event editable=True %}
|
{% include "pretixpresale/event/fragment_cart.html" with cart=cart event=request.event editable=True %}
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="col-md-6 col-xs-12">
|
||||||
|
{% if cart.minutes_left > 0 %}
|
||||||
|
<em>{% blocktrans trimmed with minutes=cart.minutes_left %}
|
||||||
|
The items in your cart are reserved for you for {{ minutes }} minutes.
|
||||||
|
{% endblocktrans %}</em>
|
||||||
|
{% else %}
|
||||||
|
<em>{% trans "The items in your cart are no longer reserved for you." %}</em>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 col-md-offset-2 col-xs-12">
|
||||||
|
<a class="btn btn-block btn-primary btn-lg"
|
||||||
|
href="{% url "presale:event.checkout.start" organizer=request.event.organizer.slug event=request.event.slug %}">
|
||||||
|
<i class="fa fa-shopping-cart"></i> {% trans "Proceed with checkout" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ from django.conf.urls import patterns, url, include
|
|||||||
|
|
||||||
import pretix.presale.views.event
|
import pretix.presale.views.event
|
||||||
import pretix.presale.views.cart
|
import pretix.presale.views.cart
|
||||||
|
import pretix.presale.views.checkout
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns(
|
urlpatterns = patterns(
|
||||||
'',
|
'',
|
||||||
@@ -11,6 +13,7 @@ urlpatterns = patterns(
|
|||||||
url(r'^$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'),
|
url(r'^$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'),
|
||||||
url(r'^cart/add$', pretix.presale.views.cart.CartAdd.as_view(), name='event.cart.add'),
|
url(r'^cart/add$', pretix.presale.views.cart.CartAdd.as_view(), name='event.cart.add'),
|
||||||
url(r'^cart/remove$', pretix.presale.views.cart.CartRemove.as_view(), name='event.cart.remove'),
|
url(r'^cart/remove$', pretix.presale.views.cart.CartRemove.as_view(), name='event.cart.remove'),
|
||||||
|
url(r'^checkout$', pretix.presale.views.checkout.CheckoutStart.as_view(), name='event.checkout.start'),
|
||||||
)
|
)
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.utils.timezone import now
|
||||||
|
|
||||||
from pretix.base.models import CartPosition
|
from pretix.base.models import CartPosition
|
||||||
|
|
||||||
@@ -49,6 +51,10 @@ class CartDisplayMixin(CartMixin):
|
|||||||
return {
|
return {
|
||||||
'positions': positions,
|
'positions': positions,
|
||||||
'total': sum(p.total for p in positions),
|
'total': sum(p.total for p in positions),
|
||||||
|
'minutes_left': (
|
||||||
|
max(min(p.expires for p in positions) - now(), timedelta()).seconds // 60
|
||||||
|
if positions else 0
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,14 @@ class CartActionMixin(CartMixin):
|
|||||||
return False
|
return False
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
def _re_add_position(self, items, position):
|
||||||
|
for i, tup in enumerate(items):
|
||||||
|
if tup[0] == position.item_id and tup[1] == position.variation_id:
|
||||||
|
items[i] = (tup[0], tup[1], tup[2] + 1)
|
||||||
|
return items
|
||||||
|
items.append((position.item_id, position.variation_id, 1))
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
class CartRemove(EventViewMixin, CartActionMixin, View):
|
class CartRemove(EventViewMixin, CartActionMixin, View):
|
||||||
|
|
||||||
@@ -94,6 +102,25 @@ class CartAdd(EventViewMixin, CartActionMixin, View):
|
|||||||
_("You cannot select more than %d items per order") % self.event.max_items_per_order)
|
_("You cannot select more than %d items per order") % self.event.max_items_per_order)
|
||||||
return redirect(self.get_failure_url())
|
return redirect(self.get_failure_url())
|
||||||
|
|
||||||
|
# Extend this user's cart session to 30 minutes from now to ensure all items in the
|
||||||
|
# cart expire at the same time
|
||||||
|
qw = Q(session=self.get_session_key())
|
||||||
|
if self.request.user.is_authenticated():
|
||||||
|
qw |= Q(user=self.request.user)
|
||||||
|
|
||||||
|
# We can extend the reservation of items which are not yet expired without
|
||||||
|
# risk
|
||||||
|
CartPosition.objects.current.filter(
|
||||||
|
qw & Q(event=self.request.event) & Q(expires__gt=now())
|
||||||
|
).update(expires=now() + timedelta(minutes=30))
|
||||||
|
|
||||||
|
# For items that are already expired, we have to delete and re-add them, as they might
|
||||||
|
# be no longer available. Sorry!
|
||||||
|
for cp in CartPosition.objects.current.filter(
|
||||||
|
qw & Q(event=self.request.event) & Q(expires__lte=now())):
|
||||||
|
items = self._re_add_position(items, cp)
|
||||||
|
cp.delete()
|
||||||
|
|
||||||
# Fetch items from the database
|
# Fetch items from the database
|
||||||
items_cache = {
|
items_cache = {
|
||||||
i.identity: i for i
|
i.identity: i for i
|
||||||
@@ -110,14 +137,6 @@ class CartAdd(EventViewMixin, CartActionMixin, View):
|
|||||||
).select_related("item", "item__event").prefetch_related("quotas", "values", "values__prop")
|
).select_related("item", "item__event").prefetch_related("quotas", "values", "values__prop")
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extend this user's cart session to 30 minutes from now to ensure all items in the
|
|
||||||
# cart expire at the same time
|
|
||||||
qw = Q(session=self.get_session_key())
|
|
||||||
if self.request.user.is_authenticated():
|
|
||||||
qw |= Q(user=self.request.user)
|
|
||||||
CartPosition.objects.current.filter(
|
|
||||||
qw & Q(event=self.request.event)).update(expires=now() + timedelta(minutes=30))
|
|
||||||
|
|
||||||
# Process the request itself
|
# Process the request itself
|
||||||
msg_some_unavailable = False
|
msg_some_unavailable = False
|
||||||
for i in items:
|
for i in items:
|
||||||
|
|||||||
23
src/pretix/presale/views/checkout.py
Normal file
23
src/pretix/presale/views/checkout.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from django.contrib import messages
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.views.generic import View
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from pretix.presale.views import EventViewMixin, CartDisplayMixin
|
||||||
|
|
||||||
|
|
||||||
|
class CheckoutStart(EventViewMixin, CartDisplayMixin, View):
|
||||||
|
|
||||||
|
def get_failure_url(self):
|
||||||
|
return reverse('presale:event.index', kwargs={
|
||||||
|
'event': self.request.event.slug,
|
||||||
|
'organizer': self.request.event.organizer.slug,
|
||||||
|
})
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
cart = self.get_cart()
|
||||||
|
if not cart['positions']:
|
||||||
|
messages.error(self.request,
|
||||||
|
_("Your cart is empty") % self.event.max_items_per_order)
|
||||||
|
return redirect(self.get_failure_url())
|
||||||
Reference in New Issue
Block a user