diff --git a/doc/development/api/general.rst b/doc/development/api/general.rst index 308de0702a..fb76050c3b 100644 --- a/doc/development/api/general.rst +++ b/doc/development/api/general.rst @@ -12,7 +12,7 @@ Core .. automodule:: pretix.base.signals :members: periodic_task, event_live_issues, event_copy_data, email_filter, register_notification_types, - item_copy_data, register_sales_channels, register_global_settings + item_copy_data, register_sales_channels, register_global_settings, quota_availability Order events """""""""""" diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 7c51005eb3..0119bd45c1 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -23,6 +23,7 @@ from i18nfield.fields import I18nCharField, I18nTextField from pretix.base.models import fields from pretix.base.models.base import LoggedModel from pretix.base.models.tax import TaxedPrice +from pretix.base.signals import quota_availability from .event import Event, SubEvent @@ -1312,6 +1313,9 @@ class Quota(LoggedModel): return _cache[self.pk] now_dt = now_dt or now() res = self._availability(now_dt, count_waitinglist) + for recv, resp in quota_availability.send(sender=self.event, quota=self, result=res, + count_waitinglist=count_waitinglist): + res = resp self.event.cache.delete('item_quota_cache') rewrite_cache = count_waitinglist and ( diff --git a/src/pretix/base/signals.py b/src/pretix/base/signals.py index 7adfdfb905..a13bd706e4 100644 --- a/src/pretix/base/signals.py +++ b/src/pretix/base/signals.py @@ -524,3 +524,20 @@ a ``subevent`` argument which might be none and you are expected to return a lis ``pretix.base.timeline.TimelineEvent``, which is a ``namedtuple`` with the fields ``event``, ``subevent``, ``datetime``, ``description`` and ``edit_url``. """ + + +quota_availability = EventPluginSignal( + providing_args=['quota', 'result', 'count_waitinglist'] +) +""" +This signal allows you to modify the availability of a quota. You are passed the ``quota`` and an +``availability`` result calculated by pretix code or other plugins. ``availability`` is a tuple +with the first entry being one of the ``Quota.AVAILABILITY_*`` constants and the second entry being +the number of available tickets (or ``None`` for unlimited). You are expected to return a value +of the same time. The parameter ``count_waitinglists`` specifies whether waiting lists should be taken +into account. + +**Warning: Use this signal with great caution, it allows you to screw up the performance of the +system really bad.** Also, keep in mind that your response is subject to caching and out-of-date +quotas might be used for display (not for actual order processing). +""" diff --git a/src/pretix/control/templates/pretixcontrol/items/quota.html b/src/pretix/control/templates/pretixcontrol/items/quota.html index 42a619ae51..4c01075480 100644 --- a/src/pretix/control/templates/pretixcontrol/items/quota.html +++ b/src/pretix/control/templates/pretixcontrol/items/quota.html @@ -51,6 +51,13 @@ {% endfor %} + {% if has_plugins > 0 %} +
+ {% blocktrans trimmed with num=quota_overbooked %} + A plugin is active that might modify the actual result of this quota from what you see here. + {% endblocktrans %} +
+ {% endif %} {% if quota_overbooked > 0 %}
{% blocktrans trimmed with num=quota_overbooked %} diff --git a/src/pretix/control/views/item.py b/src/pretix/control/views/item.py index 90c5cdbd1a..04cbd24d3c 100644 --- a/src/pretix/control/views/item.py +++ b/src/pretix/control/views/item.py @@ -25,6 +25,7 @@ from pretix.base.models import ( from pretix.base.models.event import SubEvent from pretix.base.models.items import ItemAddOn, ItemBundle from pretix.base.services.tickets import invalidate_cache +from pretix.base.signals import quota_availability from pretix.control.forms.item import ( CategoryForm, ItemAddOnForm, ItemAddOnsFormSet, ItemBundleForm, ItemBundleFormSet, ItemCreateForm, ItemUpdateForm, ItemVariationForm, @@ -666,6 +667,17 @@ class QuotaView(ChartContainingView, DetailView): ctx['quota_table_rows'] = list(data) ctx['quota_overbooked'] = sum_values - self.object.size if self.object.size is not None else 0 + ctx['has_plugins'] = False + res = ( + Quota.AVAILABILITY_GONE if self.object.size is not None and self.object.size - sum_values <= 0 else + Quota.AVAILABILITY_OK, + self.object.size - sum_values if self.object.size is not None else None + ) + for recv, resp in quota_availability.send(sender=self.request.event, quota=self.object, result=res, + count_waitinglist=True): + if resp != res: + ctx['has_plugins'] = True + ctx['has_ignore_vouchers'] = Voucher.objects.filter( Q(allow_ignore_quota=True) & Q(Q(valid_until__isnull=True) | Q(valid_until__gte=now())) &