mirror of
https://github.com/pretix/pretix.git
synced 2026-05-03 14:54:04 +00:00
Allow quotas to "close" when once full (#1344)
* Model * Some UI * API and logging * Permission check * Add tests * Move option around
This commit is contained in:
@@ -20,12 +20,22 @@ size integer The size of the
|
|||||||
items list of integers List of item IDs this quota acts on.
|
items list of integers List of item IDs this quota acts on.
|
||||||
variations list of integers List of item variation IDs this quota acts on.
|
variations list of integers List of item variation IDs this quota acts on.
|
||||||
subevent integer ID of the date inside an event series this quota belongs to (or ``null``).
|
subevent integer ID of the date inside an event series this quota belongs to (or ``null``).
|
||||||
|
close_when_sold_out boolean If ``true``, the quota will "close" as soon as it is
|
||||||
|
sold out once. Even if tickets become available again,
|
||||||
|
they will not be sold unless the quota is set to open
|
||||||
|
again.
|
||||||
|
closed boolean Whether the quota is currently closed (see above
|
||||||
|
field).
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
.. versionchanged:: 1.10
|
.. versionchanged:: 1.10
|
||||||
|
|
||||||
The write operations ``POST``, ``PATCH``, ``PUT``, and ``DELETE`` have been added.
|
The write operations ``POST``, ``PATCH``, ``PUT``, and ``DELETE`` have been added.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
|
||||||
|
The attributes ``close_when_sold_out`` and ``closed`` have been added.
|
||||||
|
|
||||||
|
|
||||||
Endpoints
|
Endpoints
|
||||||
---------
|
---------
|
||||||
@@ -61,7 +71,9 @@ Endpoints
|
|||||||
"size": 200,
|
"size": 200,
|
||||||
"items": [1, 2],
|
"items": [1, 2],
|
||||||
"variations": [1, 4, 5, 7],
|
"variations": [1, 4, 5, 7],
|
||||||
"subevent": null
|
"subevent": null,
|
||||||
|
"close_when_sold_out": false,
|
||||||
|
"closed": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -102,7 +114,9 @@ Endpoints
|
|||||||
"size": 200,
|
"size": 200,
|
||||||
"items": [1, 2],
|
"items": [1, 2],
|
||||||
"variations": [1, 4, 5, 7],
|
"variations": [1, 4, 5, 7],
|
||||||
"subevent": null
|
"subevent": null,
|
||||||
|
"close_when_sold_out": false,
|
||||||
|
"closed": false
|
||||||
}
|
}
|
||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer to fetch
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
@@ -130,7 +144,9 @@ Endpoints
|
|||||||
"size": 200,
|
"size": 200,
|
||||||
"items": [1, 2],
|
"items": [1, 2],
|
||||||
"variations": [1, 4, 5, 7],
|
"variations": [1, 4, 5, 7],
|
||||||
"subevent": null
|
"subevent": null,
|
||||||
|
"close_when_sold_out": false,
|
||||||
|
"closed": false
|
||||||
}
|
}
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
@@ -147,7 +163,9 @@ Endpoints
|
|||||||
"size": 200,
|
"size": 200,
|
||||||
"items": [1, 2],
|
"items": [1, 2],
|
||||||
"variations": [1, 4, 5, 7],
|
"variations": [1, 4, 5, 7],
|
||||||
"subevent": null
|
"subevent": null,
|
||||||
|
"close_when_sold_out": false,
|
||||||
|
"closed": false
|
||||||
}
|
}
|
||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer of the event/item to create a quota for
|
:param organizer: The ``slug`` field of the organizer of the event/item to create a quota for
|
||||||
@@ -200,7 +218,9 @@ Endpoints
|
|||||||
1,
|
1,
|
||||||
2
|
2
|
||||||
],
|
],
|
||||||
"subevent": null
|
"subevent": null,
|
||||||
|
"close_when_sold_out": false,
|
||||||
|
"closed": false
|
||||||
}
|
}
|
||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer to modify
|
:param organizer: The ``slug`` field of the organizer to modify
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ class QuotaSerializer(I18nAwareModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Quota
|
model = Quota
|
||||||
fields = ('id', 'name', 'size', 'items', 'variations', 'subevent')
|
fields = ('id', 'name', 'size', 'items', 'variations', 'subevent', 'closed', 'close_when_sold_out')
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
data = super().validate(data)
|
data = super().validate(data)
|
||||||
|
|||||||
@@ -473,6 +473,19 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
|
|||||||
# This costs us a few cycles on save, but avoids thousands of lines in our log.
|
# This costs us a few cycles on save, but avoids thousands of lines in our log.
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if original_data['closed'] is True and serializer.instance.closed is False:
|
||||||
|
serializer.instance.log_action(
|
||||||
|
'pretix.event.quota.opened',
|
||||||
|
user=self.request.user,
|
||||||
|
auth=self.request.auth,
|
||||||
|
)
|
||||||
|
elif original_data['closed'] is False and serializer.instance.closed is True:
|
||||||
|
serializer.instance.log_action(
|
||||||
|
'pretix.event.quota.closed',
|
||||||
|
user=self.request.user,
|
||||||
|
auth=self.request.auth,
|
||||||
|
)
|
||||||
|
|
||||||
serializer.instance.log_action(
|
serializer.instance.log_action(
|
||||||
'pretix.event.quota.changed',
|
'pretix.event.quota.changed',
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
|
|||||||
26
src/pretix/base/migrations/0128_auto_20190715_1510.py
Normal file
26
src/pretix/base/migrations/0128_auto_20190715_1510.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 2.2.1 on 2019-07-15 15:10
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import pretix.base.models.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pretixbase', '0127_auto_20190711_0705'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='quota',
|
||||||
|
name='close_when_sold_out',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='quota',
|
||||||
|
name='closed',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -518,6 +518,11 @@ class Event(EventMixin, LoggedModel):
|
|||||||
vars = list(q.variations.all())
|
vars = list(q.variations.all())
|
||||||
q.pk = None
|
q.pk = None
|
||||||
q.event = self
|
q.event = self
|
||||||
|
q.cached_availability_state = None
|
||||||
|
q.cached_availability_number = None
|
||||||
|
q.cached_availability_paid_orders = None
|
||||||
|
q.cached_availability_time = None
|
||||||
|
q.closed = False
|
||||||
q.save()
|
q.save()
|
||||||
for i in items:
|
for i in items:
|
||||||
if i.pk in item_map:
|
if i.pk in item_map:
|
||||||
|
|||||||
@@ -1269,6 +1269,15 @@ class Quota(LoggedModel):
|
|||||||
cached_availability_paid_orders = models.PositiveIntegerField(null=True, blank=True)
|
cached_availability_paid_orders = models.PositiveIntegerField(null=True, blank=True)
|
||||||
cached_availability_time = models.DateTimeField(null=True, blank=True)
|
cached_availability_time = models.DateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
|
close_when_sold_out = models.BooleanField(
|
||||||
|
verbose_name=_('Close this quota permanently once it is sold out'),
|
||||||
|
help_text=_('If you enable this, when the quota is sold out once, no more tickets will be sold, '
|
||||||
|
'even if tickets become available again through cancellations or expiring orders. Of course, '
|
||||||
|
'you can always re-open it manually.'),
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
closed = models.BooleanField(default=False)
|
||||||
|
|
||||||
objects = ScopedManager(organizer='event__organizer')
|
objects = ScopedManager(organizer='event__organizer')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -1334,6 +1343,11 @@ class Quota(LoggedModel):
|
|||||||
count_waitinglist=count_waitinglist):
|
count_waitinglist=count_waitinglist):
|
||||||
res = resp
|
res = resp
|
||||||
|
|
||||||
|
if res[0] <= Quota.AVAILABILITY_ORDERED and self.close_when_sold_out and not self.closed:
|
||||||
|
self.closed = True
|
||||||
|
self.save(update_fields=['closed'])
|
||||||
|
self.log_action('pretix.event.quota.closed')
|
||||||
|
|
||||||
self.event.cache.delete('item_quota_cache')
|
self.event.cache.delete('item_quota_cache')
|
||||||
rewrite_cache = count_waitinglist and (
|
rewrite_cache = count_waitinglist and (
|
||||||
not self.cache_is_hot(now_dt) or res[0] > self.cached_availability_state
|
not self.cache_is_hot(now_dt) or res[0] > self.cached_availability_state
|
||||||
@@ -1358,8 +1372,11 @@ class Quota(LoggedModel):
|
|||||||
_cache['_count_waitinglist'] = count_waitinglist
|
_cache['_count_waitinglist'] = count_waitinglist
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _availability(self, now_dt: datetime=None, count_waitinglist=True):
|
def _availability(self, now_dt: datetime=None, count_waitinglist=True, ignore_closed=False):
|
||||||
now_dt = now_dt or now()
|
now_dt = now_dt or now()
|
||||||
|
if self.closed and not ignore_closed:
|
||||||
|
return Quota.AVAILABILITY_ORDERED, 0
|
||||||
|
|
||||||
size_left = self.size
|
size_left = self.size
|
||||||
if size_left is None:
|
if size_left is None:
|
||||||
return Quota.AVAILABILITY_OK, None
|
return Quota.AVAILABILITY_OK, None
|
||||||
|
|||||||
@@ -169,7 +169,8 @@ class QuotaForm(I18nModelForm):
|
|||||||
fields = [
|
fields = [
|
||||||
'name',
|
'name',
|
||||||
'size',
|
'size',
|
||||||
'subevent'
|
'subevent',
|
||||||
|
'close_when_sold_out'
|
||||||
]
|
]
|
||||||
field_classes = {
|
field_classes = {
|
||||||
'subevent': SafeModelChoiceField,
|
'subevent': SafeModelChoiceField,
|
||||||
|
|||||||
@@ -263,6 +263,8 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
|||||||
'pretix.event.quota.added': _('The quota has been added.'),
|
'pretix.event.quota.added': _('The quota has been added.'),
|
||||||
'pretix.event.quota.deleted': _('The quota has been deleted.'),
|
'pretix.event.quota.deleted': _('The quota has been deleted.'),
|
||||||
'pretix.event.quota.changed': _('The quota has been changed.'),
|
'pretix.event.quota.changed': _('The quota has been changed.'),
|
||||||
|
'pretix.event.quota.closed': _('The quota has closed.'),
|
||||||
|
'pretix.event.quota.opened': _('The quota has been re-opened.'),
|
||||||
'pretix.event.category.added': _('The category has been added.'),
|
'pretix.event.category.added': _('The category has been added.'),
|
||||||
'pretix.event.category.deleted': _('The category has been deleted.'),
|
'pretix.event.category.deleted': _('The category has been deleted.'),
|
||||||
'pretix.event.category.changed': _('The category has been changed.'),
|
'pretix.event.category.changed': _('The category has been changed.'),
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% if availability.0 == 10 %}
|
{% if closed %}
|
||||||
|
<span class="label label-danger">{% trans "Closed" %}</span>
|
||||||
|
{% elif availability.0 == 10 %}
|
||||||
<span class="label label-warning">{% trans "Sold out (pending orders)" %}</span>
|
<span class="label label-warning">{% trans "Sold out (pending orders)" %}</span>
|
||||||
{% elif availability.0 == 100 %}
|
{% elif availability.0 == 100 %}
|
||||||
{% if availability.1 != None %}
|
{% if availability.1 != None %}
|
||||||
|
|||||||
@@ -20,6 +20,27 @@
|
|||||||
<span class="fa fa-calendar"></span> {{ quota.subevent.name }} – {{ quota.subevent.get_date_range_display }}
|
<span class="fa fa-calendar"></span> {{ quota.subevent.name }} – {{ quota.subevent.get_date_range_display }}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if quota.closed %}
|
||||||
|
{% if closed_and_sold_out %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<button type="submit" class="btn btn-default pull-right" name="disable" value="true">
|
||||||
|
{% trans "Open quota and disable closing" %}
|
||||||
|
</button>
|
||||||
|
{% trans "This quota is sold out and closed. Even if tickets become available e.g. through cancellations, they will not become available again unless you manually re-open the quota on this page." %}
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<button type="submit" class="btn btn-primary pull-right" name="reopen" value="true">{% trans "Open quota" %}</button>
|
||||||
|
{% trans "This quota is closed since it has been sold out before. Tickets are theoretically available, but will not be sold unless you manually re-open the quota." %}
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
<div class="row" id="quota-stats">
|
<div class="row" id="quota-stats">
|
||||||
<div class="col-md-5 col-xs-12">
|
<div class="col-md-5 col-xs-12">
|
||||||
<legend>{% trans "Usage overview" %}</legend>
|
<legend>{% trans "Usage overview" %}</legend>
|
||||||
@@ -30,7 +51,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-5 col-xs-12">
|
<div class="col-md-5 col-xs-12">
|
||||||
<legend>{% trans "Availability calculation" %}</legend>
|
<legend>{% trans "Availability calculation" %}</legend>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-9">{% trans "Total quota" %}</div>
|
<div class="col-xs-9">{% trans "Total quota" %}</div>
|
||||||
<div class="col-xs-3 text-right">
|
<div class="col-xs-3 text-right">
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
{% if form.subevent %}
|
{% if form.subevent %}
|
||||||
{% bootstrap_field form.subevent layout="control" %}
|
{% bootstrap_field form.subevent layout="control" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
<legend>{% trans "Items" %}</legend>
|
<legend>{% trans "Items" %}</legend>
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
@@ -35,6 +37,10 @@
|
|||||||
</p>
|
</p>
|
||||||
{% bootstrap_field form.itemvars layout="control" %}
|
{% bootstrap_field form.itemvars layout="control" %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>{% trans "Advanced options" %}</legend>
|
||||||
|
{% bootstrap_field form.close_when_sold_out layout="control" %}
|
||||||
|
</fieldset>
|
||||||
<div class="form-group submit-group">
|
<div class="form-group submit-group">
|
||||||
<button type="submit" class="btn btn-primary btn-save">
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
{% trans "Save" %}
|
{% trans "Save" %}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
<td>{{ q.subevent.name }} – {{ q.subevent.get_date_range_display }}</td>
|
<td>{{ q.subevent.name }} – {{ q.subevent.get_date_range_display }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td>{% if q.size == None %}Unlimited{% else %}{{ q.size }}{% endif %}</td>
|
<td>{% if q.size == None %}Unlimited{% else %}{{ q.size }}{% endif %}</td>
|
||||||
<td>{% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.availability %}</td>
|
<td>{% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.availability closed=q.closed %}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
<a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||||
<a href="{% url "control:event.items.quotas.delete" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
<a href="{% url "control:event.items.quotas.delete" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Count, F, Prefetch, Q
|
from django.db.models import Count, F, Prefetch, Q
|
||||||
@@ -684,6 +685,8 @@ class QuotaView(ChartContainingView, DetailView):
|
|||||||
Q(Q(self.object._position_lookup) | Q(quota=self.object)) &
|
Q(Q(self.object._position_lookup) | Q(quota=self.object)) &
|
||||||
Q(redeemed__lt=F('max_usages'))
|
Q(redeemed__lt=F('max_usages'))
|
||||||
).exists()
|
).exists()
|
||||||
|
if self.object.closed:
|
||||||
|
ctx['closed_and_sold_out'] = self.object._availability(ignore_closed=True)[0] <= Quota.AVAILABILITY_ORDERED
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
@@ -695,6 +698,32 @@ class QuotaView(ChartContainingView, DetailView):
|
|||||||
except Quota.DoesNotExist:
|
except Quota.DoesNotExist:
|
||||||
raise Http404(_("The requested quota does not exist."))
|
raise Http404(_("The requested quota does not exist."))
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
if not request.user.has_event_permission(request.organizer, request.event, 'can_change_items', request):
|
||||||
|
raise PermissionDenied()
|
||||||
|
quota = self.get_object()
|
||||||
|
if 'reopen' in request.POST:
|
||||||
|
quota.closed = False
|
||||||
|
quota.save(update_fields=['closed'])
|
||||||
|
quota.log_action('pretix.event.quota.opened', user=request.user)
|
||||||
|
messages.success(request, _('The quota has been re-opened.'))
|
||||||
|
if 'disable' in request.POST:
|
||||||
|
quota.closed = False
|
||||||
|
quota.close_when_sold_out = False
|
||||||
|
quota.save(update_fields=['closed', 'close_when_sold_out'])
|
||||||
|
quota.log_action('pretix.event.quota.opened', user=request.user)
|
||||||
|
quota.log_action(
|
||||||
|
'pretix.event.quota.changed', user=self.request.user, data={
|
||||||
|
'close_when_sold_out': False
|
||||||
|
}
|
||||||
|
)
|
||||||
|
messages.success(request, _('The quota has been re-opened and will not close again.'))
|
||||||
|
return redirect(reverse('control:event.items.quotas.show', kwargs={
|
||||||
|
'organizer': self.request.event.organizer.slug,
|
||||||
|
'event': self.request.event.slug,
|
||||||
|
'quota': quota.pk
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
class QuotaUpdate(EventPermissionRequiredMixin, UpdateView):
|
class QuotaUpdate(EventPermissionRequiredMixin, UpdateView):
|
||||||
model = Quota
|
model = Quota
|
||||||
|
|||||||
@@ -1386,7 +1386,9 @@ TEST_QUOTA_RES = {
|
|||||||
"size": 200,
|
"size": 200,
|
||||||
"items": [],
|
"items": [],
|
||||||
"variations": [],
|
"variations": [],
|
||||||
"subevent": None
|
"subevent": None,
|
||||||
|
"close_when_sold_out": False,
|
||||||
|
"closed": False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1593,6 +1595,30 @@ def test_quota_update(token_client, organizer, event, quota, item):
|
|||||||
assert quota.all_logentries().count() == 1
|
assert quota.all_logentries().count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_quota_update_closed(token_client, organizer, event, quota, item):
|
||||||
|
resp = token_client.patch(
|
||||||
|
'/api/v1/organizers/{}/events/{}/quotas/{}/'.format(organizer.slug, event.slug, quota.pk),
|
||||||
|
{
|
||||||
|
"closed": True,
|
||||||
|
},
|
||||||
|
format='json'
|
||||||
|
)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
with scopes_disabled():
|
||||||
|
quota = Quota.objects.get(pk=resp.data['id'])
|
||||||
|
assert quota.all_logentries().filter(action_type="pretix.event.quota.closed").count() == 1
|
||||||
|
resp = token_client.patch(
|
||||||
|
'/api/v1/organizers/{}/events/{}/quotas/{}/'.format(organizer.slug, event.slug, quota.pk),
|
||||||
|
{
|
||||||
|
"closed": False,
|
||||||
|
},
|
||||||
|
format='json'
|
||||||
|
)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert quota.all_logentries().filter(action_type="pretix.event.quota.opened").count() == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_quota_update_unchanged(token_client, organizer, event, quota, item):
|
def test_quota_update_unchanged(token_client, organizer, event, quota, item):
|
||||||
resp = token_client.patch(
|
resp = token_client.patch(
|
||||||
|
|||||||
@@ -483,6 +483,24 @@ class QuotaTestCase(BaseQuotaTestCase):
|
|||||||
self.event.has_subevents = False
|
self.event.has_subevents = False
|
||||||
self.event.save()
|
self.event.save()
|
||||||
|
|
||||||
|
@classscope(attr='o')
|
||||||
|
def test_close_when_full_on_calculation(self):
|
||||||
|
self.quota.close_when_sold_out = True
|
||||||
|
self.quota.size = 0
|
||||||
|
self.quota.save()
|
||||||
|
assert not self.quota.closed
|
||||||
|
self.quota.availability()
|
||||||
|
self.quota.refresh_from_db()
|
||||||
|
assert self.quota.closed
|
||||||
|
assert self.quota.all_logentries().filter(action_type="pretix.event.quota.closed").exists()
|
||||||
|
|
||||||
|
@classscope(attr='o')
|
||||||
|
def test_closed_reports_as_sold_out(self):
|
||||||
|
self.quota.closed = True
|
||||||
|
self.quota.size = 100
|
||||||
|
self.quota.save()
|
||||||
|
assert self.quota.availability() == (Quota.AVAILABILITY_ORDERED, 0)
|
||||||
|
|
||||||
|
|
||||||
class BundleQuotaTestCase(BaseQuotaTestCase):
|
class BundleQuotaTestCase(BaseQuotaTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -338,6 +338,28 @@ class QuotaTest(ItemFormTest):
|
|||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
assert not Quota.objects.filter(id=c.id).exists()
|
assert not Quota.objects.filter(id=c.id).exists()
|
||||||
|
|
||||||
|
def test_reopen(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
c = Quota.objects.create(event=self.event1, name="Full house", size=500,
|
||||||
|
close_when_sold_out=True, closed=True)
|
||||||
|
self.post_doc('/control/event/%s/%s/quotas/%s/' % (self.orga1.slug, self.event1.slug, c.id),
|
||||||
|
{'reopen': 'true'})
|
||||||
|
with scopes_disabled():
|
||||||
|
c.refresh_from_db()
|
||||||
|
assert not c.closed
|
||||||
|
assert c.close_when_sold_out
|
||||||
|
|
||||||
|
def test_reopen_and_disable(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
c = Quota.objects.create(event=self.event1, name="Full house", size=500,
|
||||||
|
close_when_sold_out=True, closed=True)
|
||||||
|
self.post_doc('/control/event/%s/%s/quotas/%s/' % (self.orga1.slug, self.event1.slug, c.id),
|
||||||
|
{'disable': 'true'})
|
||||||
|
with scopes_disabled():
|
||||||
|
c.refresh_from_db()
|
||||||
|
assert not c.closed
|
||||||
|
assert not c.close_when_sold_out
|
||||||
|
|
||||||
|
|
||||||
class ItemsTest(ItemFormTest):
|
class ItemsTest(ItemFormTest):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user