First implementation of adding things to your cart

This commit is contained in:
Raphael Michel
2015-02-12 18:51:47 +01:00
parent 260266f2d9
commit 0026309b85
3 changed files with 200 additions and 12 deletions
+80 -7
View File
@@ -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 %}
+119 -4
View File
@@ -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())