diff --git a/src/pretix/base/migrations/0002_auto_20151021_1412.py b/src/pretix/base/migrations/0002_auto_20151021_1412.py
new file mode 100644
index 0000000000..69380c7e0f
--- /dev/null
+++ b/src/pretix/base/migrations/0002_auto_20151021_1412.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('pretixbase', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='quota',
+ name='size',
+ field=models.PositiveIntegerField(help_text='Leave empty for an unlimited number of tickets.', verbose_name='Total capacity', blank=True, null=True),
+ ),
+ ]
diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py
index c4eed3359c..1c73308940 100644
--- a/src/pretix/base/models/items.py
+++ b/src/pretix/base/models/items.py
@@ -1,4 +1,5 @@
from itertools import product
+import sys
from django.db import models
from django.db.models import Q, Case, Count, Sum, When
@@ -315,7 +316,8 @@ class Item(Versionable):
if self.properties.count() > 0: # NOQA
raise ValueError('Do not call this directly on items which have properties '
'but call this on their ItemVariation objects')
- return min([q.availability() for q in self.quotas.all()])
+ return min([q.availability() for q in self.quotas.all()],
+ key=lambda s: (s[0], s[1] if s[1] is not None else sys.maxsize))
def check_restrictions(self):
"""
@@ -513,7 +515,8 @@ class ItemVariation(Versionable):
:returns: any of the return codes of :py:meth:`Quota.availability()`.
"""
- return min([q.availability() for q in self.quotas.all()])
+ return min([q.availability() for q in self.quotas.all()],
+ key=lambda s: (s[0], s[1] if s[1] is not None else sys.maxsize))
def to_variation_dict(self):
"""
@@ -769,7 +772,9 @@ class Quota(Versionable):
verbose_name=_("Name")
)
size = models.PositiveIntegerField(
- verbose_name=_("Total capacity")
+ verbose_name=_("Total capacity"),
+ null=True, blank=True,
+ help_text=_("Leave empty for an unlimited number of tickets.")
)
items = VersionedManyToManyField(
Item,
@@ -810,6 +815,9 @@ class Quota(Versionable):
and the second is the number of available tickets.
"""
size_left = self.size
+ if size_left is None:
+ return Quota.AVAILABILITY_OK, None
+
# TODO: Test for interference with old versions of Item-Quota-relations, etc.
# TODO: Prevent corner-cases like people having ordered an item before it got
# its first variationsadde
diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py
index bace17ed43..e24a09abeb 100644
--- a/src/pretix/base/models/orders.py
+++ b/src/pretix/base/models/orders.py
@@ -228,10 +228,11 @@ class Order(Versionable):
else:
# Use cached version
quota = quota_cache[quota.identity]
- quota.cached_availability -= 1
- if quota.cached_availability < 0:
- # This quota is sold out/currently unavailable, so do not sell this at all
- raise Quota.QuotaExceededException(error_messages['unavailable'])
+ if quota.cached_availability is not None:
+ quota.cached_availability -= 1
+ if quota.cached_availability < 0:
+ # This quota is sold out/currently unavailable, so do not sell this at all
+ raise Quota.QuotaExceededException(error_messages['unavailable'])
except Quota.QuotaExceededException as e:
return str(e)
return True
diff --git a/src/pretix/base/services/cart.py b/src/pretix/base/services/cart.py
index 5727548a49..a8cae4f349 100644
--- a/src/pretix/base/services/cart.py
+++ b/src/pretix/base/services/cart.py
@@ -104,7 +104,7 @@ def _add_items(event, items, session, expiry):
quota_ok = i[2]
for quota in quotas:
avail = quota.availability()
- if avail[1] < i[2]:
+ if avail[1] is not None and avail[1] < i[2]:
# This quota is not available or less than i[2] items are left, so we have to
# reduce the number of bought items
if avail[0] != Quota.AVAILABILITY_OK:
diff --git a/src/pretix/control/templates/pretixcontrol/items/fragment_quota_availability.html b/src/pretix/control/templates/pretixcontrol/items/fragment_quota_availability.html
index 988bbd5b1e..de455b9fbb 100644
--- a/src/pretix/control/templates/pretixcontrol/items/fragment_quota_availability.html
+++ b/src/pretix/control/templates/pretixcontrol/items/fragment_quota_availability.html
@@ -2,8 +2,12 @@
{% if availability.0 == 10 %}
{% trans "Sold out (pending orders)" %}
{% elif availability.0 == 100 %}
- {% blocktrans trimmed with num=availability.1 %}
- {{ num }} available{% endblocktrans %}
+ {% if availability.1 != None %}
+ {% blocktrans trimmed with num=availability.1 %}
+ {{ num }} available{% endblocktrans %}
+ {% else %}
+ {% trans "Unlimited" %}
+ {% endif %}
{% elif availability.0 == 20 %}
{% trans "Sold out (or reserved)" %}
{% elif availability.0 == 0 %}
diff --git a/src/tests/base/test_models.py b/src/tests/base/test_models.py
index acf99716d3..52c9d2a589 100644
--- a/src/tests/base/test_models.py
+++ b/src/tests/base/test_models.py
@@ -303,6 +303,19 @@ class QuotaTestCase(BaseQuotaTestCase):
quota2.save()
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_GONE, 0))
+ def test_unlimited(self):
+ self.quota.items.add(self.item1)
+ order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
+ expires=now() + timedelta(days=3),
+ total=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.check_quotas(), (Quota.AVAILABILITY_GONE, 0))
+
+ self.quota.size = None
+ self.quota.save()
+ self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, None))
+
class OrderTestCase(BaseQuotaTestCase):
def setUp(self):