mirror of
https://github.com/pretix/pretix.git
synced 2026-08-09 10:37:50 +00:00
First implementation of adding things to your cart
This commit is contained in:
@@ -723,7 +723,7 @@ class Item(Versionable):
|
||||
self._get_all_variations_cache = result
|
||||
return result
|
||||
|
||||
def get_all_available_variations(self):
|
||||
def get_all_available_variations(self, use_cache: bool=False):
|
||||
"""
|
||||
This method returns a list of all variations which are theoretically
|
||||
possible for sale. It DOES call all activated restriction plugins, and it
|
||||
@@ -740,6 +740,9 @@ class Item(Versionable):
|
||||
prefetch_related('properties', 'variations__values__prop')
|
||||
when retrieving Item objects you are going to use this method on.
|
||||
"""
|
||||
if use_cache and hasattr(self, '_get_all_available_variations_cache'):
|
||||
return self._get_all_available_variations_cache
|
||||
|
||||
from .signals import determine_availability
|
||||
if self.properties.count() == 0:
|
||||
variations = [VariationDict()]
|
||||
@@ -775,6 +778,7 @@ class Item(Versionable):
|
||||
and response[i]['price'] < var['price']:
|
||||
var['price'] = response[i]['price']
|
||||
|
||||
self._get_all_available_variations_cache = variations
|
||||
return variations
|
||||
|
||||
def availability(self):
|
||||
@@ -787,6 +791,31 @@ class Item(Versionable):
|
||||
'but call this on their ItemVariation objects')
|
||||
return min([q.availability() for q in self.quotas.all()])
|
||||
|
||||
def execute_restrictions(self):
|
||||
"""
|
||||
This method is used to determine whether this ItemVariation is restricted
|
||||
in sale by any restriction plugins.
|
||||
It returns False, if the item is unavailable or the item's price, if it is
|
||||
available.
|
||||
"""
|
||||
if self.properties.count() > 0:
|
||||
raise ValueError('Do not call this directly on items which have properties '
|
||||
'but call this on their ItemVariation objects')
|
||||
from .signals import determine_availability
|
||||
vd = VariationDict()
|
||||
responses = determine_availability.send(
|
||||
self.event, item=self,
|
||||
variations=[vd], context=None,
|
||||
cache=self.event.get_cache()
|
||||
)
|
||||
price = self.default_price
|
||||
for receiver, response in responses:
|
||||
if 'available' in response[0] and not response[0]['available']:
|
||||
return False
|
||||
elif 'price' in response[0] and response[0]['price'] < price:
|
||||
price = response[0]['price']
|
||||
return price
|
||||
|
||||
|
||||
class ItemVariation(Versionable):
|
||||
"""
|
||||
@@ -841,11 +870,40 @@ class ItemVariation(Versionable):
|
||||
|
||||
def availability(self):
|
||||
"""
|
||||
This method is used to determine whether this Item is currently available
|
||||
for sale. It may return any of the return codes of Quota.availability()
|
||||
This method is used to determine whether this ItemVariation is currently
|
||||
available for sale in terms of quotas. It may return any of the return codes
|
||||
of Quota.availability()
|
||||
"""
|
||||
return min([q.availability() for q in self.quotas.all()])
|
||||
|
||||
def to_variation_dict(self):
|
||||
vd = VariationDict()
|
||||
for v in self.values.all():
|
||||
vd[v.prop.identity] = v
|
||||
vd['variation'] = self
|
||||
return vd
|
||||
|
||||
def execute_restrictions(self):
|
||||
"""
|
||||
This method is used to determine whether this ItemVariation is restricted
|
||||
in sale by any restriction plugins.
|
||||
It returns False, if the item is unavailable or the item's price, if it is
|
||||
available.
|
||||
"""
|
||||
from .signals import determine_availability
|
||||
responses = determine_availability.send(
|
||||
self.item.event, item=self.item,
|
||||
variations=[self.to_variation_dict()], context=None,
|
||||
cache=self.item.event.get_cache()
|
||||
)
|
||||
price = self.default_price if self.default_price is not None else self.item.default_price
|
||||
for receiver, response in responses:
|
||||
if 'available' in response[0] and not response[0]['available']:
|
||||
return False
|
||||
elif 'price' in response[0] and response[0]['price'] < price:
|
||||
price = response[0]['price']
|
||||
return price
|
||||
|
||||
|
||||
class VariationsField(VersionedManyToManyField):
|
||||
"""
|
||||
@@ -1028,7 +1086,7 @@ class Quota(Versionable):
|
||||
& quotalookup
|
||||
).count()
|
||||
if paid_orders >= self.size:
|
||||
return (Quota.AVAILABILITY_GONE, 0)
|
||||
return Quota.AVAILABILITY_GONE, 0
|
||||
|
||||
pending_valid_orders = OrderPosition.objects.filter(
|
||||
Q(order__status=Order.STATUS_PENDING)
|
||||
@@ -1036,16 +1094,31 @@ class Quota(Versionable):
|
||||
& quotalookup
|
||||
).count()
|
||||
if (paid_orders + pending_valid_orders) >= self.size:
|
||||
return (Quota.AVAILABILITY_ORDERED, 0)
|
||||
return Quota.AVAILABILITY_ORDERED, 0
|
||||
|
||||
valid_cart_positions = CartPosition.objects.filter(
|
||||
Q(expires__gte=now())
|
||||
& quotalookup
|
||||
).count()
|
||||
if (paid_orders + pending_valid_orders + valid_cart_positions) >= self.size:
|
||||
return (Quota.AVAILABILITY_RESERVED, 0)
|
||||
return Quota.AVAILABILITY_RESERVED, 0
|
||||
|
||||
return (Quota.AVAILABILITY_OK, self.size - paid_orders - pending_valid_orders - valid_cart_positions)
|
||||
return Quota.AVAILABILITY_OK, self.size - paid_orders - pending_valid_orders - valid_cart_positions
|
||||
|
||||
def lock(self):
|
||||
"""
|
||||
Issue a lock on this quota so nobody can take tickets from this quota until
|
||||
you release the lock
|
||||
"""
|
||||
pass
|
||||
|
||||
def release(self, force=False):
|
||||
"""
|
||||
Release a lock placed by lock(). If the parameter force is not set,
|
||||
the lock will only be released if it was issued in _this_ python
|
||||
representation of the database object.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Order(Versionable):
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<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_{{ var.variation.identity }}">
|
||||
name="variation_{{ item.identity }}_{{ var.variation.identity }}">
|
||||
</div>
|
||||
{% else %}
|
||||
{% include "pretixpresale/event/fragment_availability.html" with avail=var.cached_availability.0 %}
|
||||
|
||||
@@ -1,27 +1,142 @@
|
||||
from datetime import timedelta
|
||||
import uuid
|
||||
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.timezone import now
|
||||
from django.views.generic import View
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .event import EventViewMixin
|
||||
from pretixbase.models import Item, ItemVariation, Quota, CartPosition
|
||||
|
||||
|
||||
class CartActionMixin:
|
||||
|
||||
def get_success_url(self):
|
||||
def get_next_url(self):
|
||||
if "next" in self.request.GET and '://' not in self.request.GET:
|
||||
return self.request.GET.get('next')
|
||||
else:
|
||||
return reverse('presale:event.index', kwargs={
|
||||
'event': self.request.event.slug,
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
})
|
||||
})
|
||||
|
||||
def get_success_url(self):
|
||||
return self.get_next_url()
|
||||
|
||||
def get_failure_url(self):
|
||||
return self.get_next_url()
|
||||
|
||||
def get_session_key(self):
|
||||
if 'cart_key' in self.request.session:
|
||||
return self.request.session.get('cart_key')
|
||||
key = str(uuid.uuid4())
|
||||
self.request.session['cart_key'] = key
|
||||
return key
|
||||
|
||||
|
||||
class CartAdd(EventViewMixin, CartActionMixin, View):
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
messages.error(self.request, _('Cart is not yet implemented'))
|
||||
print("hi")
|
||||
# Parse input
|
||||
items = []
|
||||
for key, value in self.request.POST.items():
|
||||
if value.strip() == '':
|
||||
continue
|
||||
if key.startswith('item_'):
|
||||
try:
|
||||
items.append((key.split("_")[1], None, int(value)))
|
||||
except ValueError:
|
||||
messages.error(self.request, _('Please only enter numbers.'))
|
||||
return redirect(self.get_failure_url())
|
||||
elif key.startswith('variation_'):
|
||||
try:
|
||||
items.append((key.split("_")[1], key.split("_")[2], int(value)))
|
||||
except ValueError:
|
||||
messages.error(self.request, _('Please only enter numbers.'))
|
||||
return redirect(self.get_failure_url())
|
||||
|
||||
if sum(i[2] for i in items) > self.request.event.max_items_per_order:
|
||||
# TODO: Plurals
|
||||
messages.error(self.request,
|
||||
_("You cannot select more than %d items per order") % self.event.max_items_per_order)
|
||||
return redirect(self.get_failure_url())
|
||||
|
||||
# items is now a list of tuples of the form
|
||||
# (item id, variation id or None, number)
|
||||
|
||||
# Fetch items from the database
|
||||
items_cache = {
|
||||
i.identity: i for i
|
||||
in Item.objects.filter(
|
||||
event=self.request.event,
|
||||
id__in=[i[0] for i in items]
|
||||
).prefetch_related("quotas")
|
||||
}
|
||||
variations_cache = {
|
||||
v.identity: v for v
|
||||
in ItemVariation.objects.filter(
|
||||
item__event=self.request.event,
|
||||
id__in=[i[1] for i in items if i[1] is not None]
|
||||
).select_related("item", "item__event").prefetch_related("quotas", "values", "values__prop")
|
||||
}
|
||||
|
||||
# Process the request itself
|
||||
msg_some_unavailable = False
|
||||
for i in items:
|
||||
if i[0] not in items_cache or (i[1] is not None and i[1] not in variations_cache):
|
||||
messages.error(self.request, _('You selected an item which is not available for sale.'))
|
||||
return redirect(self.get_failure_url())
|
||||
item = items_cache[i[0]]
|
||||
variation = variations_cache[i[1]] if i[1] is not None else None
|
||||
price = item.execute_restrictions() if variation is None else variation.execute_restrictions()
|
||||
|
||||
if price is False:
|
||||
msg_some_unavailable = True
|
||||
messages.error(self.request,
|
||||
_('Some of the items you selected were no longer available. '
|
||||
'Please see below for details.'))
|
||||
continue
|
||||
|
||||
quotas = list(item.quotas.all()) if variation is None else list(variation.quotas.all())
|
||||
quota_ok = i[2]
|
||||
try:
|
||||
for quota in quotas:
|
||||
quota.lock()
|
||||
avail = quota.availability()
|
||||
if avail[0] != Quota.AVAILABILITY_OK:
|
||||
if not msg_some_unavailable:
|
||||
msg_some_unavailable = True
|
||||
messages.error(self.request,
|
||||
_('Some of the items you selected were no longer available. '
|
||||
'Please see below for details.'))
|
||||
quota_ok = 0
|
||||
break
|
||||
elif avail[1] < i[2]:
|
||||
if not msg_some_unavailable:
|
||||
msg_some_unavailable = True
|
||||
messages.error(self.request,
|
||||
_('Some of the items you selected were no longer available in '
|
||||
'the quantity you selected. Please see below for details.'))
|
||||
quota_ok = min(quota_ok, avail[1])
|
||||
|
||||
for k in range(quota_ok):
|
||||
CartPosition.objects.create(
|
||||
event=self.request.event,
|
||||
session=self.get_session_key(),
|
||||
user=(self.request.user if self.request.user.is_authenticated() else None),
|
||||
item=item,
|
||||
variation=variation,
|
||||
price=price,
|
||||
expires=now() + timedelta(minutes=30)
|
||||
)
|
||||
finally:
|
||||
for quota in quotas:
|
||||
quota.release()
|
||||
|
||||
if not msg_some_unavailable:
|
||||
messages.success(self.request, _('The items have been successfully added to your cart.'))
|
||||
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
Reference in New Issue
Block a user