From a2af3db771a8198c93b6b52d80e0a8b30c27f332 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 21 Oct 2015 16:31:58 +0200 Subject: [PATCH] Added the option of unlimited quotas --- .../migrations/0002_auto_20151021_1412.py | 19 +++++++++++++++++++ src/pretix/base/models/items.py | 14 +++++++++++--- src/pretix/base/models/orders.py | 9 +++++---- src/pretix/base/services/cart.py | 2 +- .../items/fragment_quota_availability.html | 8 ++++++-- src/tests/base/test_models.py | 13 +++++++++++++ 6 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 src/pretix/base/migrations/0002_auto_20151021_1412.py 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):