mirror of
https://github.com/pretix/pretix.git
synced 2026-05-12 16:24:00 +00:00
More tests
This commit is contained in:
@@ -33,13 +33,13 @@
|
|||||||
# License for the specific language governing permissions and limitations under the License.
|
# License for the specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
from collections import namedtuple, Counter
|
from collections import Counter, namedtuple
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||||
from django.core.validators import EmailValidator
|
from django.core.validators import EmailValidator
|
||||||
from django.db.models import Max, Sum, Count, Q, F
|
from django.db.models import Count, F, Max
|
||||||
from django.db.models.functions import Upper
|
from django.db.models.functions import Upper
|
||||||
from django.forms.utils import ErrorDict
|
from django.forms.utils import ErrorDict
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -53,7 +53,7 @@ from pretix.base.forms import (
|
|||||||
)
|
)
|
||||||
from pretix.base.forms.widgets import format_placeholders_help_text
|
from pretix.base.forms.widgets import format_placeholders_help_text
|
||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
from pretix.base.models import Item, Voucher, Quota, SubEvent, ItemVariation
|
from pretix.base.models import Item, ItemVariation, Quota, SubEvent, Voucher
|
||||||
from pretix.base.services.locking import lock_objects
|
from pretix.base.services.locking import lock_objects
|
||||||
from pretix.base.services.quotas import QuotaAvailability
|
from pretix.base.services.quotas import QuotaAvailability
|
||||||
from pretix.control.forms import SplitDateTimeField, SplitDateTimePickerWidget
|
from pretix.control.forms import SplitDateTimeField, SplitDateTimePickerWidget
|
||||||
@@ -283,10 +283,10 @@ class VoucherBulkEditForm(VoucherForm):
|
|||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
# We skip the parent class because it's not suited for bulk editing and implement custom validation here.
|
# We skip the parent class because it's not suited for bulk editing and implement custom validation here.
|
||||||
# This does not validate everything we validate in VoucherForm. For example, we skip validation that one does
|
# This does not validate *everything* we validate in VoucherForm. For example, we skip validation that one does
|
||||||
# not create a voucher for an add-on product to save on complexity. This is a UX validation only anyways, since
|
# not create a voucher for an add-on product or that the seat matches the product to save on complexity.
|
||||||
# one could first create the voucher and then make the product an add-on product. However, we need to validate
|
# This is a UX validation only anyway, since one could first create the voucher and then make the product an
|
||||||
# everything that we don't want violated in the database.
|
# add-on product. However, we need to validate everything that we don't want violated in the database.
|
||||||
data = super(VoucherForm, self).clean()
|
data = super(VoucherForm, self).clean()
|
||||||
|
|
||||||
if self.prefix + "itemvar" in self.data.getlist('_bulk'):
|
if self.prefix + "itemvar" in self.data.getlist('_bulk'):
|
||||||
@@ -321,7 +321,7 @@ class VoucherBulkEditForm(VoucherForm):
|
|||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise ValidationError(_("Invalid product selected."))
|
raise ValidationError(_("Invalid product selected."))
|
||||||
|
|
||||||
if self.prefix + "max_usages" in self.data.getlist('_bulk'):
|
if self.prefix + "max_usages" in self.data.getlist('_bulk') and "max_usages" in data:
|
||||||
max_redeemed = self.queryset.aggregate(m=Max("redeemed"))["m"]
|
max_redeemed = self.queryset.aggregate(m=Max("redeemed"))["m"]
|
||||||
if data["max_usages"] < max_redeemed:
|
if data["max_usages"] < max_redeemed:
|
||||||
raise ValidationError(_(
|
raise ValidationError(_(
|
||||||
@@ -375,26 +375,26 @@ class VoucherBulkEditForm(VoucherForm):
|
|||||||
|
|
||||||
# Predict state after change
|
# Predict state after change
|
||||||
after_change = dict(current)
|
after_change = dict(current)
|
||||||
if self.prefix + "itemvar" in self.data.getlist('_bulk'):
|
if self.prefix + "itemvar" in self.data.getlist('_bulk') and "itemvar" in data:
|
||||||
after_change["item"] = data["item"]
|
after_change["item"] = data["item"]
|
||||||
after_change["variation"] = data["variation"]
|
after_change["variation"] = data["variation"]
|
||||||
after_change["quota"] = data["quota"]
|
after_change["quota"] = data["quota"]
|
||||||
if self.prefix + "subevent" in self.data.getlist('_bulk'):
|
if self.prefix + "subevent" in self.data.getlist('_bulk') and "subevent" in data:
|
||||||
after_change["subevent"] = data["subevent"]
|
after_change["subevent"] = data["subevent"]
|
||||||
if self.prefix + "max_usages" in self.data.getlist('_bulk'):
|
if self.prefix + "max_usages" in self.data.getlist('_bulk') and "max_usages" in data:
|
||||||
after_change["max_usages"] = data["max_usages"]
|
after_change["max_usages"] = data["max_usages"]
|
||||||
if self.prefix + "block_quota" in self.data.getlist('_bulk'):
|
if self.prefix + "block_quota" in self.data.getlist('_bulk') and "block_quota" in data:
|
||||||
after_change["block_quota"] = data["block_quota"]
|
after_change["block_quota"] = data["block_quota"]
|
||||||
if self.prefix + "valid_until" in self.data.getlist('_bulk'):
|
if self.prefix + "valid_until" in self.data.getlist('_bulk') and "valid_until" in data:
|
||||||
after_change["valid_until"] = data["valid_until"]
|
after_change["valid_until"] = data["valid_until"]
|
||||||
if self.prefix + "allow_ignore_quota" in self.data.getlist('_bulk'):
|
if self.prefix + "allow_ignore_quota" in self.data.getlist('_bulk') and "allow_ignore_quota" in data:
|
||||||
after_change["allow_ignore_quota"] = data["allow_ignore_quota"]
|
after_change["allow_ignore_quota"] = data["allow_ignore_quota"]
|
||||||
|
|
||||||
if after_change["quota"] and self.event.has_subevents and not after_change["subevent"]:
|
if after_change["quota"] and self.event.has_subevents and not after_change["subevent"]:
|
||||||
raise _("You cannot create a voucher that allows selection of a quota but has no date selected.")
|
raise ValidationError(_("You cannot create a voucher that allows selection of a quota but has no date selected."))
|
||||||
|
|
||||||
if after_change["quota"] and after_change["subevent"] and after_change["quota"].subevent_id != after_change["subevent"].pk:
|
if after_change["quota"] and after_change["subevent"] and after_change["quota"].subevent_id != after_change["subevent"].pk:
|
||||||
raise _("The selected quota does not match the selected subevent.")
|
raise ValidationError(_("The selected quota does not match the selected subevent."))
|
||||||
|
|
||||||
if after_change["block_quota"] and self.event.has_subevents and not after_change["subevent"]:
|
if after_change["block_quota"] and self.event.has_subevents and not after_change["subevent"]:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
@@ -410,7 +410,7 @@ class VoucherBulkEditForm(VoucherForm):
|
|||||||
# todo: is this the most useful way to do this?
|
# todo: is this the most useful way to do this?
|
||||||
continue
|
continue
|
||||||
|
|
||||||
will_be_valid = current["valid_until"] is None or current["valid_until"] >= now()
|
will_be_valid = after_change["valid_until"] is None or after_change["valid_until"] >= now()
|
||||||
new_quotas = set()
|
new_quotas = set()
|
||||||
if will_be_valid and after_change["block_quota"] and after_change["max_usages"] > current["redeemed"]:
|
if will_be_valid and after_change["block_quota"] and after_change["max_usages"] > current["redeemed"]:
|
||||||
if after_change["quota"]:
|
if after_change["quota"]:
|
||||||
@@ -428,7 +428,7 @@ class VoucherBulkEditForm(VoucherForm):
|
|||||||
else:
|
else:
|
||||||
new_quotas |= set(after_change["item"].quotas.filter(subevent=after_change["subevent"]))
|
new_quotas |= set(after_change["item"].quotas.filter(subevent=after_change["subevent"]))
|
||||||
|
|
||||||
new_amount = max(current["max_usages"] - current["redeemed"], 0)
|
new_amount = max(after_change["max_usages"] - after_change["redeemed"], 0) * current["c"]
|
||||||
if new_quotas != old_quotas or new_amount != old_amount:
|
if new_quotas != old_quotas or new_amount != old_amount:
|
||||||
for q in old_quotas:
|
for q in old_quotas:
|
||||||
quota_diff[q] -= old_amount
|
quota_diff[q] -= old_amount
|
||||||
@@ -441,7 +441,8 @@ class VoucherBulkEditForm(VoucherForm):
|
|||||||
qa.queue(*(q for q, v in quota_diff.items() if v > 0))
|
qa.queue(*(q for q, v in quota_diff.items() if v > 0))
|
||||||
qa.compute()
|
qa.compute()
|
||||||
|
|
||||||
if any(qa.results[q][0] != Quota.AVAILABILITY_OK or (qa.results[q][1] is not None and qa.results[q][1] < required) for q, required in quota_diff.items() if required > 0):
|
if any(qa.results[q][0] != Quota.AVAILABILITY_OK or (qa.results[q][1] is not None and qa.results[q][1] < required)
|
||||||
|
for q, required in quota_diff.items() if required > 0):
|
||||||
raise ValidationError(_(
|
raise ValidationError(_(
|
||||||
'There is no sufficient quota available to perform this change.'
|
'There is no sufficient quota available to perform this change.'
|
||||||
))
|
))
|
||||||
@@ -473,27 +474,27 @@ class VoucherBulkEditForm(VoucherForm):
|
|||||||
valid_until__lt=now(),
|
valid_until__lt=now(),
|
||||||
)
|
)
|
||||||
if self.event.has_subevents:
|
if self.event.has_subevents:
|
||||||
conflicts = currently_not_blocked_seats.exclude(
|
|
||||||
seat_id__in=self.event.free_seats.values("pk")
|
|
||||||
)
|
|
||||||
if conflicts:
|
|
||||||
raise ValidationError(_(
|
|
||||||
'This change cannot be completed because not all assigned seats of the vouchers are '
|
|
||||||
'still available'
|
|
||||||
))
|
|
||||||
else:
|
|
||||||
subevents = self.event.subevents.filter(pk__in=currently_not_blocked_seats.values_list("subevent"))
|
subevents = self.event.subevents.filter(pk__in=currently_not_blocked_seats.values_list("subevent"))
|
||||||
for se in subevents:
|
for se in subevents:
|
||||||
conflicts = currently_not_blocked_seats.filter(
|
conflicts = currently_not_blocked_seats.filter(
|
||||||
subevent=se
|
subevent=se
|
||||||
).exclude(
|
).exclude(
|
||||||
seat_id__in=se.free_seats.values("pk")
|
seat_id__in=se.free_seats().values("pk")
|
||||||
)
|
)
|
||||||
if conflicts:
|
if conflicts:
|
||||||
raise ValidationError(_(
|
raise ValidationError(_(
|
||||||
'This change cannot be completed because not all assigned seats of the vouchers are '
|
'This change cannot be completed because not all assigned seats of the vouchers are '
|
||||||
'still available'
|
'still available'
|
||||||
))
|
))
|
||||||
|
else:
|
||||||
|
conflicts = currently_not_blocked_seats.exclude(
|
||||||
|
seat_id__in=self.event.free_seats().values("pk")
|
||||||
|
)
|
||||||
|
if conflicts:
|
||||||
|
raise ValidationError(_(
|
||||||
|
'This change cannot be completed because not all assigned seats of the vouchers are '
|
||||||
|
'still available'
|
||||||
|
))
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ from django.conf import settings
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.exceptions import PermissionDenied, ValidationError
|
from django.core.exceptions import PermissionDenied, ValidationError
|
||||||
from django.db import connection, transaction
|
from django.db import connection, transaction
|
||||||
from django.db.models import Exists, OuterRef, Sum, Subquery, Count
|
from django.db.models import Count, Exists, OuterRef, Sum
|
||||||
from django.http import (
|
from django.http import (
|
||||||
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,
|
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,
|
||||||
JsonResponse,
|
JsonResponse,
|
||||||
@@ -55,7 +55,7 @@ from django.utils.safestring import mark_safe
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import (
|
from django.views.generic import (
|
||||||
CreateView, ListView, TemplateView, UpdateView, View, FormView,
|
CreateView, FormView, ListView, TemplateView, UpdateView, View,
|
||||||
)
|
)
|
||||||
from django_scopes import scopes_disabled
|
from django_scopes import scopes_disabled
|
||||||
|
|
||||||
@@ -70,7 +70,9 @@ from pretix.base.services.vouchers import vouchers_send
|
|||||||
from pretix.base.templatetags.rich_text import markdown_compile_email
|
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||||
from pretix.base.views.tasks import AsyncFormView
|
from pretix.base.views.tasks import AsyncFormView
|
||||||
from pretix.control.forms.filter import VoucherFilterForm, VoucherTagFilterForm
|
from pretix.control.forms.filter import VoucherFilterForm, VoucherTagFilterForm
|
||||||
from pretix.control.forms.vouchers import VoucherBulkForm, VoucherForm, VoucherBulkEditForm
|
from pretix.control.forms.vouchers import (
|
||||||
|
VoucherBulkEditForm, VoucherBulkForm, VoucherForm,
|
||||||
|
)
|
||||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||||
from pretix.control.signals import voucher_form_class
|
from pretix.control.signals import voucher_form_class
|
||||||
from pretix.control.views import PaginationMixin
|
from pretix.control.views import PaginationMixin
|
||||||
@@ -671,7 +673,6 @@ class VoucherBulkAction(VoucherQueryMixin, EventPermissionRequiredMixin, View):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class VoucherBulkUpdateView(VoucherQueryMixin, EventPermissionRequiredMixin, FormView):
|
class VoucherBulkUpdateView(VoucherQueryMixin, EventPermissionRequiredMixin, FormView):
|
||||||
template_name = 'pretixcontrol/vouchers/bulk_edit.html'
|
template_name = 'pretixcontrol/vouchers/bulk_edit.html'
|
||||||
permission = 'event.vouchers:write'
|
permission = 'event.vouchers:write'
|
||||||
@@ -757,7 +758,7 @@ class VoucherBulkUpdateView(VoucherQueryMixin, EventPermissionRequiredMixin, For
|
|||||||
data['_raw_bulk_data'] = self.request.POST.dict()
|
data['_raw_bulk_data'] = self.request.POST.dict()
|
||||||
for obj in self.get_queryset():
|
for obj in self.get_queryset():
|
||||||
log_entries.append(
|
log_entries.append(
|
||||||
obj.log_action('pretix.event.quota.changed', data=data, user=self.request.user, save=False)
|
obj.log_action('pretix.voucher.changed', data=data, user=self.request.user, save=False)
|
||||||
)
|
)
|
||||||
|
|
||||||
LogEntry.bulk_create_and_postprocess(log_entries)
|
LogEntry.bulk_create_and_postprocess(log_entries)
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ from django_scopes import scopes_disabled
|
|||||||
from tests.base import SoupTestMixin, extract_form_fields
|
from tests.base import SoupTestMixin, extract_form_fields
|
||||||
|
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
Event, Item, ItemVariation, Order, OrderPosition, Organizer, Quota, Team,
|
Event, Item, ItemVariation, Order, OrderPosition, Organizer, Quota,
|
||||||
User, Voucher,
|
SeatingPlan, Team, User, Voucher,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -135,49 +135,49 @@ class VoucherFormTest(SoupTestMixin, TransactionTestCase):
|
|||||||
def test_filter_status_valid(self):
|
def test_filter_status_valid(self):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
v = self.event.vouchers.create(item=self.ticket)
|
v = self.event.vouchers.create(item=self.ticket)
|
||||||
doc = self.client.get('/control/event/%s/%s/vouchers/?status=v' % (self.orga.slug, self.event.slug))
|
doc = self.client.get('/control/event/%s/%s/vouchers/?filter-status=v' % (self.orga.slug, self.event.slug))
|
||||||
assert v.code in doc.content.decode()
|
assert v.code in doc.content.decode()
|
||||||
v.redeemed = 1
|
v.redeemed = 1
|
||||||
v.save()
|
v.save()
|
||||||
doc = self.client.get('/control/event/%s/%s/vouchers/?status=v' % (self.orga.slug, self.event.slug))
|
doc = self.client.get('/control/event/%s/%s/vouchers/?filter-status=v' % (self.orga.slug, self.event.slug))
|
||||||
assert v.code not in doc.content.decode()
|
assert v.code not in doc.content.decode()
|
||||||
|
|
||||||
def test_filter_status_redeemed(self):
|
def test_filter_status_redeemed(self):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
v = self.event.vouchers.create(item=self.ticket, redeemed=1)
|
v = self.event.vouchers.create(item=self.ticket, redeemed=1)
|
||||||
doc = self.client.get('/control/event/%s/%s/vouchers/?status=r' % (self.orga.slug, self.event.slug))
|
doc = self.client.get('/control/event/%s/%s/vouchers/?filter-status=r' % (self.orga.slug, self.event.slug))
|
||||||
assert v.code in doc.content.decode()
|
assert v.code in doc.content.decode()
|
||||||
v.redeemed = 0
|
v.redeemed = 0
|
||||||
v.save()
|
v.save()
|
||||||
doc = self.client.get('/control/event/%s/%s/vouchers/?status=r' % (self.orga.slug, self.event.slug))
|
doc = self.client.get('/control/event/%s/%s/vouchers/?filter-status=r' % (self.orga.slug, self.event.slug))
|
||||||
assert v.code not in doc.content.decode()
|
assert v.code not in doc.content.decode()
|
||||||
|
|
||||||
def test_filter_status_expired(self):
|
def test_filter_status_expired(self):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
v = self.event.vouchers.create(item=self.ticket, valid_until=now() + datetime.timedelta(days=1))
|
v = self.event.vouchers.create(item=self.ticket, valid_until=now() + datetime.timedelta(days=1))
|
||||||
doc = self.client.get('/control/event/%s/%s/vouchers/?status=e' % (self.orga.slug, self.event.slug))
|
doc = self.client.get('/control/event/%s/%s/vouchers/?filter-status=e' % (self.orga.slug, self.event.slug))
|
||||||
assert v.code not in doc.content.decode()
|
assert v.code not in doc.content.decode()
|
||||||
v.valid_until = now() - datetime.timedelta(days=1)
|
v.valid_until = now() - datetime.timedelta(days=1)
|
||||||
v.save()
|
v.save()
|
||||||
doc = self.client.get('/control/event/%s/%s/vouchers/?status=e' % (self.orga.slug, self.event.slug))
|
doc = self.client.get('/control/event/%s/%s/vouchers/?filter-status=e' % (self.orga.slug, self.event.slug))
|
||||||
assert v.code in doc.content.decode()
|
assert v.code in doc.content.decode()
|
||||||
|
|
||||||
def test_filter_tag(self):
|
def test_filter_tag(self):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
self.event.vouchers.create(item=self.ticket, code='ABCDEFG', comment='Foo', tag='bar')
|
self.event.vouchers.create(item=self.ticket, code='ABCDEFG', comment='Foo', tag='bar')
|
||||||
doc = self.client.get('/control/event/%s/%s/vouchers/?tag=bar' % (self.orga.slug, self.event.slug))
|
doc = self.client.get('/control/event/%s/%s/vouchers/?filter-tag=bar' % (self.orga.slug, self.event.slug))
|
||||||
assert 'ABCDEFG' in doc.content.decode()
|
assert 'ABCDEFG' in doc.content.decode()
|
||||||
doc = self.client.get('/control/event/%s/%s/vouchers/?tag=baz' % (self.orga.slug, self.event.slug))
|
doc = self.client.get('/control/event/%s/%s/vouchers/?filter-tag=baz' % (self.orga.slug, self.event.slug))
|
||||||
assert 'ABCDEFG' not in doc.content.decode()
|
assert 'ABCDEFG' not in doc.content.decode()
|
||||||
|
|
||||||
def test_search_code(self):
|
def test_search_code(self):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
self.event.vouchers.create(item=self.ticket, code='ABCDEFG', comment='Foo')
|
self.event.vouchers.create(item=self.ticket, code='ABCDEFG', comment='Foo')
|
||||||
doc = self.client.get('/control/event/%s/%s/vouchers/?search=ABCDEFG' % (self.orga.slug, self.event.slug))
|
doc = self.client.get('/control/event/%s/%s/vouchers/?filter-search=ABCDEFG' % (self.orga.slug, self.event.slug))
|
||||||
assert 'ABCDEFG' in doc.content.decode()
|
assert 'ABCDEFG' in doc.content.decode()
|
||||||
doc = self.client.get('/control/event/%s/%s/vouchers/?search=Foo' % (self.orga.slug, self.event.slug))
|
doc = self.client.get('/control/event/%s/%s/vouchers/?filter-search=Foo' % (self.orga.slug, self.event.slug))
|
||||||
assert 'ABCDEFG' in doc.content.decode()
|
assert 'ABCDEFG' in doc.content.decode()
|
||||||
doc = self.client.get('/control/event/%s/%s/vouchers/?search=12345' % (self.orga.slug, self.event.slug))
|
doc = self.client.get('/control/event/%s/%s/vouchers/?filter-search=12345' % (self.orga.slug, self.event.slug))
|
||||||
assert 'ABCDEFG' not in doc.content.decode()
|
assert 'ABCDEFG' not in doc.content.decode()
|
||||||
|
|
||||||
def test_bulk_rng(self):
|
def test_bulk_rng(self):
|
||||||
@@ -851,11 +851,12 @@ class VoucherBulkEditFormTest(SoupTestMixin, TransactionTestCase):
|
|||||||
fields = extract_form_fields(doc)
|
fields = extract_form_fields(doc)
|
||||||
fields.update(data)
|
fields.update(data)
|
||||||
doc = self.post_doc(self.url, fields, follow=True)
|
doc = self.post_doc(self.url, fields, follow=True)
|
||||||
|
error_texts = [el.text for el in doc.select(".alert-danger, .has-error")]
|
||||||
if expect_error:
|
if expect_error:
|
||||||
assert doc.select(".alert-danger")
|
assert doc.select(".alert-danger")
|
||||||
assert any(expect_error in el.text for el in doc.select(".alert-danger"))
|
assert any(expect_error in t for t in error_texts), error_texts
|
||||||
else:
|
else:
|
||||||
assert doc.select(".alert-success")
|
assert doc.select(".alert-success"), error_texts
|
||||||
|
|
||||||
def test_change_itemvar_to_product(self):
|
def test_change_itemvar_to_product(self):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
@@ -902,6 +903,21 @@ class VoucherBulkEditFormTest(SoupTestMixin, TransactionTestCase):
|
|||||||
assert not v.variation
|
assert not v.variation
|
||||||
assert v.quota == self.quota_tickets
|
assert v.quota == self.quota_tickets
|
||||||
|
|
||||||
|
def test_change_itemvar_to_all(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
self.event.vouchers.create(quota=self.quota_tickets)
|
||||||
|
self.event.vouchers.create(item=self.ticket)
|
||||||
|
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkedititemvar'],
|
||||||
|
'bulkedit-itemvar': '',
|
||||||
|
})
|
||||||
|
with scopes_disabled():
|
||||||
|
for v in self.event.vouchers.all():
|
||||||
|
assert not v.item
|
||||||
|
assert not v.variation
|
||||||
|
assert not v.quota
|
||||||
|
|
||||||
def test_change_max_usages(self):
|
def test_change_max_usages(self):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
self.event.vouchers.create(quota=self.quota_tickets, max_usages=15, redeemed=4)
|
self.event.vouchers.create(quota=self.quota_tickets, max_usages=15, redeemed=4)
|
||||||
@@ -919,10 +935,11 @@ class VoucherBulkEditFormTest(SoupTestMixin, TransactionTestCase):
|
|||||||
for v in self.event.vouchers.all():
|
for v in self.event.vouchers.all():
|
||||||
assert v.max_usages == 4
|
assert v.max_usages == 4
|
||||||
|
|
||||||
def _requires_one_more_quota(self, data: dict, expect_error: str=None):
|
def _requires_one_more_quota(self, data: dict, quota=None, expect_error: str=None):
|
||||||
self._update_all(data, expect_error="no sufficient quota")
|
self._update_all(data, expect_error="no sufficient quota")
|
||||||
self.quota_tickets.size += 1
|
quota = quota or self.quota_tickets
|
||||||
self.quota_tickets.save()
|
quota.size += 1
|
||||||
|
quota.save()
|
||||||
self._update_all(data)
|
self._update_all(data)
|
||||||
|
|
||||||
def test_quota_check_change_item(self):
|
def test_quota_check_change_item(self):
|
||||||
@@ -937,10 +954,36 @@ class VoucherBulkEditFormTest(SoupTestMixin, TransactionTestCase):
|
|||||||
for v in self.event.vouchers.all():
|
for v in self.event.vouchers.all():
|
||||||
assert v.item == self.ticket
|
assert v.item == self.ticket
|
||||||
|
|
||||||
|
def test_quota_check_change_variation(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
self.event.vouchers.create(item=self.ticket, block_quota=True, max_usages=2, redeemed=1)
|
||||||
|
self.event.vouchers.create(item=self.ticket, block_quota=True, max_usages=3, redeemed=1)
|
||||||
|
self._requires_one_more_quota({
|
||||||
|
'_bulk': ['bulkedititemvar'],
|
||||||
|
'bulkedit-itemvar': f'{self.shirt.pk}-{self.shirt_red.pk}',
|
||||||
|
}, quota=self.quota_shirts)
|
||||||
|
with scopes_disabled():
|
||||||
|
for v in self.event.vouchers.all():
|
||||||
|
assert v.item == self.shirt
|
||||||
|
assert v.variation == self.shirt_red
|
||||||
|
|
||||||
|
def test_quota_check_change_item_with_variations(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
self.event.vouchers.create(item=self.ticket, block_quota=True, max_usages=2, redeemed=1)
|
||||||
|
self.event.vouchers.create(item=self.ticket, block_quota=True, max_usages=3, redeemed=1)
|
||||||
|
self._requires_one_more_quota({
|
||||||
|
'_bulk': ['bulkedititemvar'],
|
||||||
|
'bulkedit-itemvar': f'{self.shirt.pk}',
|
||||||
|
}, quota=self.quota_shirts)
|
||||||
|
with scopes_disabled():
|
||||||
|
for v in self.event.vouchers.all():
|
||||||
|
assert v.item == self.shirt
|
||||||
|
assert not v.variation
|
||||||
|
|
||||||
def test_quota_check_change_expired_to_valid(self):
|
def test_quota_check_change_expired_to_valid(self):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
self.event.vouchers.create(item=self.shirt, block_quota=True, max_usages=2)
|
self.event.vouchers.create(item=self.ticket, block_quota=True, max_usages=2)
|
||||||
self.event.vouchers.create(item=self.shirt, block_quota=True, max_usages=1, valid_until=now() - datetime.timedelta(days=1))
|
self.event.vouchers.create(item=self.ticket, block_quota=True, max_usages=1, valid_until=now() - datetime.timedelta(days=1))
|
||||||
self._requires_one_more_quota({
|
self._requires_one_more_quota({
|
||||||
'_bulk': ['bulkeditvalid_until'],
|
'_bulk': ['bulkeditvalid_until'],
|
||||||
'bulkedit-valid_until_0': '',
|
'bulkedit-valid_until_0': '',
|
||||||
@@ -952,20 +995,202 @@ class VoucherBulkEditFormTest(SoupTestMixin, TransactionTestCase):
|
|||||||
|
|
||||||
def test_quota_check_change_max_usages(self):
|
def test_quota_check_change_max_usages(self):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
self.event.vouchers.create(item=self.shirt, block_quota=True, max_usages=2)
|
self.event.vouchers.create(item=self.ticket, block_quota=True, max_usages=2)
|
||||||
self.event.vouchers.create(item=self.shirt, block_quota=True, max_usages=1, redeemed=1)
|
self.event.vouchers.create(item=self.ticket, block_quota=True, max_usages=1, redeemed=1)
|
||||||
self._requires_one_more_quota({
|
self._requires_one_more_quota({
|
||||||
'_bulk': ['bulkeditmax_usages'],
|
'_bulk': ['bulkeditmax_usages'],
|
||||||
'bulkedit-max_usages': '',
|
'bulkedit-max_usages': '2',
|
||||||
})
|
})
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
for v in self.event.vouchers.all():
|
for v in self.event.vouchers.all():
|
||||||
assert v.max_usages == 2
|
assert v.max_usages == 2
|
||||||
|
|
||||||
# test quota use existing credit
|
def test_quota_check_no_change(self):
|
||||||
# test quota changed subevent
|
with scopes_disabled():
|
||||||
# test quota changed subevent to mismatch quota
|
# Technically overbooked, but we don't have a diff in quota
|
||||||
# test quota changed subevent to none
|
self.event.vouchers.create(item=self.shirt, variation=self.shirt_red, block_quota=True)
|
||||||
# test quota changed block quota, ignore
|
self.event.vouchers.create(item=self.shirt, variation=self.shirt_red, block_quota=True)
|
||||||
# test change seat properties
|
self.event.vouchers.create(item=self.shirt, variation=self.shirt_red, block_quota=True)
|
||||||
# test seats still available after validity change
|
self._update_all({
|
||||||
|
'_bulk': ['bulkedititemvar'],
|
||||||
|
'bulkedit-itemvar': f'{self.shirt.pk}-{self.shirt_blue.pk}',
|
||||||
|
})
|
||||||
|
with scopes_disabled():
|
||||||
|
for v in self.event.vouchers.all():
|
||||||
|
assert v.variation == self.shirt_blue
|
||||||
|
|
||||||
|
def test_quota_check_change_subevent(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
self.event.has_subevents = True
|
||||||
|
self.event.save()
|
||||||
|
se1 = self.event.subevents.create(name="Foo", date_from=now())
|
||||||
|
se2 = self.event.subevents.create(name="Bar", date_from=now())
|
||||||
|
self.quota_tickets.subevent = se1
|
||||||
|
self.quota_tickets.save()
|
||||||
|
Quota.objects.create(event=self.event, subevent=se2, name='Tickets', size=3)
|
||||||
|
self.event.vouchers.create(item=self.ticket, block_quota=True, subevent=se2)
|
||||||
|
self.event.vouchers.create(item=self.ticket, block_quota=True, subevent=se2)
|
||||||
|
self.event.vouchers.create(item=self.ticket, block_quota=True, subevent=se2)
|
||||||
|
self._requires_one_more_quota({
|
||||||
|
'_bulk': ['bulkeditsubevent'],
|
||||||
|
'bulkedit-subevent': f'{se1.pk}',
|
||||||
|
})
|
||||||
|
with scopes_disabled():
|
||||||
|
for v in self.event.vouchers.all():
|
||||||
|
assert v.subevent == se1
|
||||||
|
|
||||||
|
def test_change_subevent_quota_invalid(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
self.event.has_subevents = True
|
||||||
|
self.event.save()
|
||||||
|
se1 = self.event.subevents.create(name="Foo", date_from=now())
|
||||||
|
se2 = self.event.subevents.create(name="Bar", date_from=now())
|
||||||
|
self.quota_tickets.subevent = se1
|
||||||
|
self.quota_tickets.save()
|
||||||
|
v1 = self.event.vouchers.create(quota=self.quota_tickets, block_quota=True, subevent=se1)
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkeditsubevent'],
|
||||||
|
'bulkedit-subevent': f'{se2.pk}',
|
||||||
|
}, expect_error="selected quota does not match the selected subevent")
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkeditsubevent'],
|
||||||
|
'bulkedit-subevent': '',
|
||||||
|
}, expect_error="has no date selected")
|
||||||
|
v1.quota = None
|
||||||
|
v1.item = self.ticket
|
||||||
|
v1.save()
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkeditsubevent'],
|
||||||
|
'bulkedit-subevent': '',
|
||||||
|
}, expect_error="If you want this voucher to block quota, you need to select a specific date")
|
||||||
|
with scopes_disabled():
|
||||||
|
for v in self.event.vouchers.all():
|
||||||
|
assert v.subevent == se1
|
||||||
|
|
||||||
|
def test_change_missing_itemvar_with_block_quota(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
self.event.vouchers.create(quota=self.quota_tickets, block_quota=True)
|
||||||
|
self.event.vouchers.create(quota=self.quota_tickets, block_quota=True)
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkedititemvar'],
|
||||||
|
'bulkedit-itemvar': '',
|
||||||
|
}, expect_error="You need to select a specific product or quota if this voucher should reserve")
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkedititemvar', 'bulkeditblock_quota'],
|
||||||
|
'bulkedit-itemvar': '',
|
||||||
|
'bulkedit-block_quota': '',
|
||||||
|
})
|
||||||
|
with scopes_disabled():
|
||||||
|
for v in self.event.vouchers.all():
|
||||||
|
assert not v.subevent
|
||||||
|
assert not v.block_quota
|
||||||
|
|
||||||
|
def test_change_subevent_and_quota(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
self.event.has_subevents = True
|
||||||
|
self.event.save()
|
||||||
|
se1 = self.event.subevents.create(name="Foo", date_from=now())
|
||||||
|
se2 = self.event.subevents.create(name="Bar", date_from=now())
|
||||||
|
self.quota_tickets.subevent = se1
|
||||||
|
self.quota_tickets.save()
|
||||||
|
q2 = Quota.objects.create(event=self.event, subevent=se2, name='Tickets', size=3)
|
||||||
|
self.event.vouchers.create(quota=self.quota_tickets, block_quota=True, subevent=se1)
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkedititemvar', 'bulkeditsubevent'],
|
||||||
|
'bulkedit-subevent': f'{se2.pk}',
|
||||||
|
'bulkedit-itemvar': f'q-{q2.pk}',
|
||||||
|
})
|
||||||
|
with scopes_disabled():
|
||||||
|
for v in self.event.vouchers.all():
|
||||||
|
assert v.subevent == se2
|
||||||
|
assert v.quota == q2
|
||||||
|
|
||||||
|
def test_quota_check_change_block_quota(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
self.event.vouchers.create(item=self.ticket, max_usages=3)
|
||||||
|
self._requires_one_more_quota({
|
||||||
|
'_bulk': ['bulkeditblock_quota'],
|
||||||
|
'bulkedit-block_quota': 'on',
|
||||||
|
})
|
||||||
|
with scopes_disabled():
|
||||||
|
for v in self.event.vouchers.all():
|
||||||
|
assert v.block_quota
|
||||||
|
|
||||||
|
def test_ignore_quota(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
self.event.vouchers.create(item=self.ticket, max_usages=3)
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkeditblock_quota', 'bulkeditallow_ignore_quota'],
|
||||||
|
'bulkedit-block_quota': 'on',
|
||||||
|
'bulkedit-allow_ignore_quota': 'on',
|
||||||
|
})
|
||||||
|
with scopes_disabled():
|
||||||
|
for v in self.event.vouchers.all():
|
||||||
|
assert v.block_quota
|
||||||
|
assert v.allow_ignore_quota
|
||||||
|
|
||||||
|
@scopes_disabled()
|
||||||
|
def _create_seat(self, **kwargs):
|
||||||
|
plan = SeatingPlan.objects.create(
|
||||||
|
name="Plan", organizer=self.orga, layout="{}"
|
||||||
|
)
|
||||||
|
self.event.seating_plan = plan
|
||||||
|
self.event.save()
|
||||||
|
return self.event.seats.create(seat_number="A1", product=self.ticket, seat_guid="A1", **kwargs)
|
||||||
|
|
||||||
|
def test_seated_unsupported(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
self.event.vouchers.create(item=self.ticket, max_usages=1, seat=self._create_seat())
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkeditmax_usages'],
|
||||||
|
'bulkedit-max_usages': '2',
|
||||||
|
}, expect_error="Changing the maximum number of usages in bulk is not supported")
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkeditsubevent'],
|
||||||
|
'bulkedit-subevent': '',
|
||||||
|
}, expect_error="Changing the date in bulk is not supported")
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkedititemvar'],
|
||||||
|
'bulkedit-itemvar': f'q-{self.quota_tickets.pk}',
|
||||||
|
}, expect_error="Changing the product to a quota is not supported")
|
||||||
|
|
||||||
|
def test_seat_changed_to_valid_needs_to_be_available(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
seat = self._create_seat(blocked=True)
|
||||||
|
self.event.vouchers.create(item=self.ticket, max_usages=1, valid_until=now() - datetime.timedelta(days=1), seat=seat)
|
||||||
|
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkeditvalid_until'],
|
||||||
|
'bulkedit-valid_until_0': '',
|
||||||
|
'bulkedit-valid_until_1': '',
|
||||||
|
}, expect_error="not all assigned seats of the vouchers are still available")
|
||||||
|
|
||||||
|
seat.blocked = False
|
||||||
|
seat.save()
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkeditvalid_until'],
|
||||||
|
'bulkedit-valid_until_0': '',
|
||||||
|
'bulkedit-valid_until_1': '',
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_seat_changed_to_valid_needs_to_be_available_subevents(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
self.event.has_subevents = True
|
||||||
|
self.event.save()
|
||||||
|
se1 = self.event.subevents.create(name="Foo", date_from=now())
|
||||||
|
seat = self._create_seat(subevent=se1, blocked=True)
|
||||||
|
self.event.vouchers.create(item=self.ticket, max_usages=1, valid_until=now() - datetime.timedelta(days=1), seat=seat, subevent=se1)
|
||||||
|
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkeditvalid_until'],
|
||||||
|
'bulkedit-valid_until_0': '',
|
||||||
|
'bulkedit-valid_until_1': '',
|
||||||
|
}, expect_error="not all assigned seats of the vouchers are still available")
|
||||||
|
|
||||||
|
seat.blocked = False
|
||||||
|
seat.save()
|
||||||
|
self._update_all({
|
||||||
|
'_bulk': ['bulkeditvalid_until'],
|
||||||
|
'bulkedit-valid_until_0': '',
|
||||||
|
'bulkedit-valid_until_1': '',
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user