Determine not only availability, but number of remaining tickets

This commit is contained in:
Raphael Michel
2015-02-12 10:37:29 +01:00
parent f6b4f726e1
commit 2ee7ddd9b2
4 changed files with 40 additions and 36 deletions
+14 -14
View File
@@ -781,7 +781,7 @@ class Item(Versionable):
if self.properties.count() > 0: if self.properties.count() > 0:
raise ValueError('Do not call this directly on items which have properties ' raise ValueError('Do not call this directly on items which have properties '
'but call this on their ItemVariation objects') 'but call this on their ItemVariation objects')
return max([q.availability() for q in self.quotas.all()]) return min([q.availability() for q in self.quotas.all()])
class ItemVariation(Versionable): class ItemVariation(Versionable):
@@ -840,7 +840,7 @@ class ItemVariation(Versionable):
This method is used to determine whether this Item is currently available 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() for sale. It may return any of the return codes of Quota.availability()
""" """
return max([q.availability() for q in self.quotas.all()]) return min([q.availability() for q in self.quotas.all()])
class VariationsField(VersionedManyToManyField): class VariationsField(VersionedManyToManyField):
@@ -929,7 +929,7 @@ class Quota(Versionable):
anything with quotas. This might confuse you otherwise. anything with quotas. This might confuse you otherwise.
http://docs.pretix.eu/en/latest/development/concepts.html#restriction-by-number http://docs.pretix.eu/en/latest/development/concepts.html#restriction-by-number
The AVAILABILITY_* constants represent varios states of an quota allowing The AVAILABILITY_* constants represent various states of an quota allowing
its items/variations being for sale. its items/variations being for sale.
AVAILABILITY_OK AVAILABILITY_OK
@@ -949,10 +949,10 @@ class Quota(Versionable):
This item is completely sold out. This item is completely sold out.
""" """
AVAILABILITY_GONE = 30 AVAILABILITY_GONE = 0
AVAILABILITY_ORDERED = 20 AVAILABILITY_ORDERED = 10
AVAILABILITY_RESERVED = 10 AVAILABILITY_RESERVED = 20
AVAILABILITY_OK = 0 AVAILABILITY_OK = 100
event = VersionedForeignKey( event = VersionedForeignKey(
Event, Event,
@@ -1000,9 +1000,9 @@ class Quota(Versionable):
def availability(self): def availability(self):
""" """
This method is used to determine whether Items or ItemVariations belonging This method is used to determine whether Items or ItemVariations belonging
to this quota should currently be available for sale. It returns one of the to this quota should currently be available for sale. It returns a tuple where
Quota.AVAILABILITY_ constants. 0 is returned if the item is available, a the first entry is one of the Quota.AVAILABILITY_ constants and the second
positive number depending on the reason, if not. is the number of available tickets.
""" """
# TODO: These lookups are highly inefficient. However, we'll wait with optimizing # TODO: These lookups are highly inefficient. However, we'll wait with optimizing
# until Django 1.8 is released, as the following feature might make it a # until Django 1.8 is released, as the following feature might make it a
@@ -1024,7 +1024,7 @@ class Quota(Versionable):
& quotalookup & quotalookup
).count() ).count()
if paid_orders >= self.size: if paid_orders >= self.size:
return Quota.AVAILABILITY_GONE return (Quota.AVAILABILITY_GONE, 0)
pending_valid_orders = OrderPosition.objects.filter( pending_valid_orders = OrderPosition.objects.filter(
Q(order__status=Order.STATUS_PENDING) Q(order__status=Order.STATUS_PENDING)
@@ -1032,16 +1032,16 @@ class Quota(Versionable):
& quotalookup & quotalookup
).count() ).count()
if (paid_orders + pending_valid_orders) >= self.size: if (paid_orders + pending_valid_orders) >= self.size:
return Quota.AVAILABILITY_ORDERED return (Quota.AVAILABILITY_ORDERED, 0)
valid_cart_positions = CartPosition.objects.filter( valid_cart_positions = CartPosition.objects.filter(
Q(expires__gte=now()) Q(expires__gte=now())
& quotalookup & quotalookup
).count() ).count()
if (paid_orders + pending_valid_orders + valid_cart_positions) >= self.size: if (paid_orders + pending_valid_orders + valid_cart_positions) >= self.size:
return Quota.AVAILABILITY_RESERVED return (Quota.AVAILABILITY_RESERVED, 0)
return Quota.AVAILABILITY_OK return (Quota.AVAILABILITY_OK, self.size - paid_orders - pending_valid_orders - valid_cart_positions)
class Order(Versionable): class Order(Versionable):
+20 -16
View File
@@ -202,7 +202,7 @@ class QuotaTestCase(TestCase):
def test_available(self): def test_available(self):
self.quota.items.add(self.item1) self.quota.items.add(self.item1)
self.assertEqual(self.item1.availability(), Quota.AVAILABILITY_OK) self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 2))
self.quota.items.add(self.item2) self.quota.items.add(self.item2)
self.quota.variations.add(self.var1) self.quota.variations.add(self.var1)
try: try:
@@ -210,7 +210,7 @@ class QuotaTestCase(TestCase):
self.assertTrue(False) self.assertTrue(False)
except: except:
pass pass
self.assertEqual(self.var1.availability(), Quota.AVAILABILITY_OK) self.assertEqual(self.var1.availability(), (Quota.AVAILABILITY_OK, 2))
def test_sold_out(self): def test_sold_out(self):
self.quota.items.add(self.item1) self.quota.items.add(self.item1)
@@ -219,19 +219,19 @@ class QuotaTestCase(TestCase):
total=4) total=4)
OrderPosition.objects.create(order=order, item=self.item1, price=2) OrderPosition.objects.create(order=order, item=self.item1, price=2)
OrderPosition.objects.create(order=order, item=self.item1, price=2) OrderPosition.objects.create(order=order, item=self.item1, price=2)
self.assertEqual(self.item1.availability(), Quota.AVAILABILITY_GONE) self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_GONE, 0))
self.quota.items.add(self.item2) self.quota.items.add(self.item2)
self.quota.variations.add(self.var1) self.quota.variations.add(self.var1)
self.quota.size = 3 self.quota.size = 3
self.quota.save() self.quota.save()
self.assertEqual(self.var1.availability(), Quota.AVAILABILITY_OK) self.assertEqual(self.var1.availability(), (Quota.AVAILABILITY_OK, 1))
order = Order.objects.create(event=self.event, status=Order.STATUS_PAID, order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
expires=now() + timedelta(days=3), expires=now() + timedelta(days=3),
total=4) total=4)
OrderPosition.objects.create(order=order, item=self.item2, variation=self.var1, price=2) OrderPosition.objects.create(order=order, item=self.item2, variation=self.var1, price=2)
self.assertEqual(self.var1.availability(), Quota.AVAILABILITY_GONE) self.assertEqual(self.var1.availability(), (Quota.AVAILABILITY_GONE, 0))
def test_ordered(self): def test_ordered(self):
self.quota.items.add(self.item1) self.quota.items.add(self.item1)
@@ -239,17 +239,17 @@ class QuotaTestCase(TestCase):
expires=now() + timedelta(days=3), expires=now() + timedelta(days=3),
total=4) total=4)
OrderPosition.objects.create(order=order, item=self.item1, price=2) OrderPosition.objects.create(order=order, item=self.item1, price=2)
self.assertEqual(self.item1.availability(), Quota.AVAILABILITY_OK) self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 1))
order = Order.objects.create(event=self.event, status=Order.STATUS_PENDING, order = Order.objects.create(event=self.event, status=Order.STATUS_PENDING,
expires=now() + timedelta(days=3), expires=now() + timedelta(days=3),
total=4) total=4)
OrderPosition.objects.create(order=order, item=self.item1, price=2) OrderPosition.objects.create(order=order, item=self.item1, price=2)
self.assertEqual(self.item1.availability(), Quota.AVAILABILITY_ORDERED) self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_ORDERED, 0))
order.expires = now() - timedelta(days=3) order.expires = now() - timedelta(days=3)
order.save() order.save()
self.assertEqual(self.item1.availability(), Quota.AVAILABILITY_OK) self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 1))
def test_reserved(self): def test_reserved(self):
self.quota.items.add(self.item1) self.quota.items.add(self.item1)
@@ -259,32 +259,36 @@ class QuotaTestCase(TestCase):
expires=now() + timedelta(days=3), expires=now() + timedelta(days=3),
total=4) total=4)
OrderPosition.objects.create(order=order, item=self.item1, price=2) OrderPosition.objects.create(order=order, item=self.item1, price=2)
self.assertEqual(self.item1.availability(), Quota.AVAILABILITY_OK) self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 2))
order = Order.objects.create(event=self.event, status=Order.STATUS_PENDING, order = Order.objects.create(event=self.event, status=Order.STATUS_PENDING,
expires=now() + timedelta(days=3), expires=now() + timedelta(days=3),
total=4) total=4)
OrderPosition.objects.create(order=order, item=self.item1, price=2) OrderPosition.objects.create(order=order, item=self.item1, price=2)
self.assertEqual(self.item1.availability(), Quota.AVAILABILITY_OK) self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 1))
cp = CartPosition.objects.create(event=self.event, item=self.item1, price=2, cp = CartPosition.objects.create(event=self.event, item=self.item1, price=2,
expires=now() + timedelta(days=3)) expires=now() + timedelta(days=3))
self.assertEqual(self.item1.availability(), Quota.AVAILABILITY_RESERVED) self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_RESERVED, 0))
cp.expires = now() - timedelta(days=3) cp.expires = now() - timedelta(days=3)
cp.save() cp.save()
self.assertEqual(self.item1.availability(), Quota.AVAILABILITY_OK) self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 1))
self.quota.items.add(self.item2) self.quota.items.add(self.item2)
self.quota.variations.add(self.var1) self.quota.variations.add(self.var1)
cp = CartPosition.objects.create(event=self.event, item=self.item2, variation=self.var1, cp = CartPosition.objects.create(event=self.event, item=self.item2, variation=self.var1,
price=2, expires=now() + timedelta(days=3)) price=2, expires=now() + timedelta(days=3))
self.assertEqual(self.item1.availability(), Quota.AVAILABILITY_RESERVED) self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_RESERVED, 0))
def test_multiple(self): def test_multiple(self):
self.quota.items.add(self.item1) self.quota.items.add(self.item1)
self.assertEqual(self.item1.availability(), Quota.AVAILABILITY_OK) self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 2))
quota2 = Quota.objects.create(event=self.event, name="Test 2", size=0) quota2 = Quota.objects.create(event=self.event, name="Test 2", size=1)
quota2.items.add(self.item1) quota2.items.add(self.item1)
self.assertEqual(self.item1.availability(), Quota.AVAILABILITY_GONE) self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_OK, 1))
quota2.size = 0
quota2.save()
self.assertEqual(self.item1.availability(), (Quota.AVAILABILITY_GONE, 0))
@@ -1,9 +1,9 @@
{% load i18n %} {% load i18n %}
{% if avail == 30 %} {% if avail == 0 %}
<div class="col-md-2 col-xs-6 availability-box gone"> <div class="col-md-2 col-xs-6 availability-box gone">
<strong>{% trans "SOLD OUT" %}</strong> <strong>{% trans "SOLD OUT" %}</strong>
</div> </div>
{% elif avail < 30 %} {% elif avail < 100 %}
<div class="col-md-2 col-xs-6 availability-box unavailable"> <div class="col-md-2 col-xs-6 availability-box unavailable">
<strong>{% trans "Unavailable" %}</strong><br /> <strong>{% trans "Unavailable" %}</strong><br />
<small> <small>
@@ -27,12 +27,12 @@
{% endblocktrans %}</small> {% endblocktrans %}</small>
{% endif %} {% endif %}
</div> </div>
{% if var.cached_availability == 0 %} {% if var.cached_availability.0 == 100 %}
<div class="col-md-2 col-xs-6 availability-box available"> <div class="col-md-2 col-xs-6 availability-box available">
<input type="number" class="form-control input-item-count" placeholder="0" min="0"> <input type="number" class="form-control input-item-count" placeholder="0" min="0">
</div> </div>
{% else %} {% else %}
{% include "pretixpresale/event/fragment_availability.html" with avail=var.cached_availability %} {% include "pretixpresale/event/fragment_availability.html" with avail=var.cached_availability.0 %}
{% endif %} {% endif %}
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
@@ -51,12 +51,12 @@
{% endblocktrans %}</small> {% endblocktrans %}</small>
{% endif %} {% endif %}
</div> </div>
{% if item.cached_availability == 0 %} {% if item.cached_availability.0 == 100 %}
<div class="col-md-2 col-xs-6 availability-box available"> <div class="col-md-2 col-xs-6 availability-box available">
<input type="number" class="form-control input-item-count" placeholder="0" min="0"> <input type="number" class="form-control input-item-count" placeholder="0" min="0">
</div> </div>
{% else %} {% else %}
{% include "pretixpresale/event/fragment_availability.html" with avail=item.cached_availability %} {% include "pretixpresale/event/fragment_availability.html" with avail=item.cached_availability.0 %}
{% endif %} {% endif %}
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>