forked from CGM_Public/pretix_original
API: Writeable methods for vouchers (#639)
This commit is contained in:
@@ -8,3 +8,36 @@ class VoucherSerializer(I18nAwareModelSerializer):
|
||||
fields = ('id', 'code', 'max_usages', 'redeemed', 'valid_until', 'block_quota',
|
||||
'allow_ignore_quota', 'price_mode', 'value', 'item', 'variation', 'quota',
|
||||
'tag', 'comment', 'subevent')
|
||||
read_only_fields = ('id', 'redeemed')
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
|
||||
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
|
||||
full_data.update(data)
|
||||
|
||||
Voucher.clean_item_properties(
|
||||
full_data, self.context.get('event'),
|
||||
full_data.get('quota'), full_data.get('item'), full_data.get('variation')
|
||||
)
|
||||
Voucher.clean_subevent(
|
||||
full_data, self.context.get('event')
|
||||
)
|
||||
Voucher.clean_max_usages(full_data, self.instance.redeemed if self.instance else 0)
|
||||
check_quota = Voucher.clean_quota_needs_checking(
|
||||
full_data, self.instance,
|
||||
item_changed=self.instance and (
|
||||
full_data.get('item') != self.instance.item or
|
||||
full_data.get('variation') != self.instance.variation or
|
||||
full_data.get('quota') != self.instance.quota
|
||||
),
|
||||
creating=not self.instance
|
||||
)
|
||||
if check_quota:
|
||||
Voucher.clean_quota_check(
|
||||
full_data, 1, self.instance, self.context.get('event'),
|
||||
full_data.get('quota'), full_data.get('item'), full_data.get('variation')
|
||||
)
|
||||
Voucher.clean_voucher_code(full_data, self.context.get('event'), self.instance.pk if self.instance else None)
|
||||
|
||||
return data
|
||||
|
||||
@@ -4,10 +4,12 @@ from django_filters.rest_framework import (
|
||||
BooleanFilter, DjangoFilterBackend, FilterSet,
|
||||
)
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.filters import OrderingFilter
|
||||
|
||||
from pretix.api.serializers.voucher import VoucherSerializer
|
||||
from pretix.base.models import Voucher
|
||||
from pretix.base.models.organizer import TeamAPIToken
|
||||
|
||||
|
||||
class VoucherFilter(FilterSet):
|
||||
@@ -27,7 +29,7 @@ class VoucherFilter(FilterSet):
|
||||
(Q(valid_until__isnull=False) & Q(valid_until__lte=now())))
|
||||
|
||||
|
||||
class VoucherViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
class VoucherViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = VoucherSerializer
|
||||
queryset = Voucher.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
@@ -35,6 +37,49 @@ class VoucherViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
ordering_fields = ('id', 'code', 'max_usages', 'valid_until', 'value')
|
||||
filter_class = VoucherFilter
|
||||
permission = 'can_view_vouchers'
|
||||
write_permission = 'can_change_vouchers'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.vouchers.all()
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
with request.event.lock():
|
||||
return super().create(request, *args, **kwargs)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
'pretix.voucher.added',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['event'] = self.request.event
|
||||
return ctx
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
with request.event.lock():
|
||||
return super().update(request, *args, **kwargs)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
'pretix.voucher.changed',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
if not instance.allow_delete():
|
||||
raise PermissionDenied('This voucher can not be deleted as it has already been used.')
|
||||
|
||||
instance.log_action(
|
||||
'pretix.voucher.deleted',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
)
|
||||
super().perform_destroy(instance)
|
||||
|
||||
@@ -4,6 +4,7 @@ from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MinLengthValidator
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
@@ -180,24 +181,136 @@ class Voucher(LoggedModel):
|
||||
def __str__(self):
|
||||
return self.code
|
||||
|
||||
def allow_delete(self):
|
||||
return self.redeemed == 0
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if self.quota:
|
||||
if self.item:
|
||||
Voucher.clean_item_properties(
|
||||
{
|
||||
'block_quota': self.block_quota,
|
||||
},
|
||||
self.event,
|
||||
self.quota,
|
||||
self.item,
|
||||
self.variation
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def clean_item_properties(data, event, quota, item, variation):
|
||||
if quota:
|
||||
if item:
|
||||
raise ValidationError(_('You cannot select a quota and a specific product at the same time.'))
|
||||
elif self.item:
|
||||
if self.variation and (not self.item or not self.item.has_variations):
|
||||
elif item:
|
||||
if variation and (not item or not item.has_variations):
|
||||
raise ValidationError(_('You cannot select a variation without having selected a product that provides '
|
||||
'variations.'))
|
||||
if self.variation and not self.item.variations.filter(pk=self.variation.pk).exists():
|
||||
if variation and not item.variations.filter(pk=variation.pk).exists():
|
||||
raise ValidationError(_('This variation does not belong to this product.'))
|
||||
if self.item.has_variations and not self.variation and self.block_quota:
|
||||
if item.has_variations and not variation and data.get('block_quota'):
|
||||
raise ValidationError(_('You can only block quota if you specify a specific product variation. '
|
||||
'Otherwise it might be unclear which quotas to block.'))
|
||||
if item.category and item.category.is_addon:
|
||||
raise ValidationError(_('It is currently not possible to create vouchers for add-on products.'))
|
||||
else:
|
||||
raise ValidationError(_('You need to specify either a quota or a product.'))
|
||||
if self.event.has_subevents and self.block_quota and not self.subevent:
|
||||
|
||||
@staticmethod
|
||||
def clean_max_usages(data, redeemed):
|
||||
if data.get('max_usages', 1) < redeemed:
|
||||
raise ValidationError(
|
||||
_('This voucher has already been redeemed %(redeemed)s times. You cannot reduce the maximum number of '
|
||||
'usages below this number.'),
|
||||
params={
|
||||
'redeemed': redeemed
|
||||
}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def clean_subevent(data, event):
|
||||
if event.has_subevents and data.get('block_quota') and not data.get('subevent'):
|
||||
raise ValidationError(_('If you want this voucher to block quota, you need to select a specific date.'))
|
||||
elif data.get('subevent') and not event.has_subevents:
|
||||
raise ValidationError(_('You can not select a subevent if your event is not an event series.'))
|
||||
|
||||
@staticmethod
|
||||
def clean_quota_needs_checking(data, old_instance, item_changed, creating):
|
||||
# We only need to check for quota on vouchers that are now blocking quota and haven't
|
||||
# before (or have blocked a different quota before)
|
||||
if data.get('block_quota', False):
|
||||
is_valid = data.get('valid_until') is None or data.get('valid_until') >= now()
|
||||
if not is_valid:
|
||||
# If the voucher is not valid, it won't block any quota
|
||||
return False
|
||||
|
||||
if creating:
|
||||
# This is a new voucher
|
||||
return True
|
||||
|
||||
if not old_instance.block_quota:
|
||||
# Change from nonblocking to blocking
|
||||
return True
|
||||
|
||||
if old_instance.valid_until is not None and old_instance.valid_until < now():
|
||||
# This voucher has been expired and is now valid again and therefore blocks quota again
|
||||
return True
|
||||
|
||||
if item_changed:
|
||||
# The voucher has been reassigned to a different item, variation or quota
|
||||
return True
|
||||
|
||||
if data.get('subevent') != old_instance.subevent:
|
||||
# The voucher has been reassigned to a different subevent
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def clean_quota_get_ignored(old_instance):
|
||||
quotas = set()
|
||||
was_valid = old_instance and (
|
||||
old_instance.valid_until is None or old_instance.valid_until >= now()
|
||||
)
|
||||
if old_instance and old_instance.block_quota and was_valid:
|
||||
if old_instance.quota:
|
||||
quotas.add(old_instance.quota)
|
||||
elif old_instance.variation:
|
||||
quotas |= set(old_instance.variation.quotas.filter(
|
||||
subevent=old_instance.subevent))
|
||||
elif old_instance.item:
|
||||
quotas |= set(old_instance.item.quotas.filter(
|
||||
subevent=old_instance.subevent))
|
||||
return quotas
|
||||
|
||||
@staticmethod
|
||||
def clean_quota_check(data, cnt, old_instance, event, quota, item, variation):
|
||||
old_quotas = Voucher.clean_quota_get_ignored(old_instance)
|
||||
|
||||
if event.has_subevents and data.get('block_quota') and not data.get('subevent'):
|
||||
raise ValidationError(_('If you want this voucher to block quota, you need to select a specific date.'))
|
||||
|
||||
if quota:
|
||||
if quota in old_quotas:
|
||||
return
|
||||
else:
|
||||
avail = quota.availability(count_waitinglist=False)
|
||||
elif item and item.has_variations and not variation:
|
||||
raise ValidationError(_('You can only block quota if you specify a specific product variation. '
|
||||
'Otherwise it might be unclear which quotas to block.'))
|
||||
elif item and variation:
|
||||
avail = variation.check_quotas(ignored_quotas=old_quotas, subevent=data.get('subevent'))
|
||||
elif item and not item.has_variations:
|
||||
avail = item.check_quotas(ignored_quotas=old_quotas, subevent=data.get('subevent'))
|
||||
else:
|
||||
raise ValidationError(_('You need to specify either a quota or a product.'))
|
||||
|
||||
if avail[0] != Quota.AVAILABILITY_OK or (avail[1] is not None and avail[1] < cnt):
|
||||
raise ValidationError(_('You cannot create a voucher that blocks quota as the selected product or '
|
||||
'quota is currently sold out or completely reserved.'))
|
||||
|
||||
@staticmethod
|
||||
def clean_voucher_code(data, event, pk):
|
||||
if 'code' in data and Voucher.objects.filter(Q(code=data['code']) & Q(event=event) & ~Q(pk=pk)).exists():
|
||||
raise ValidationError(_('A voucher with this code already exists.'))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.code = self.code.upper()
|
||||
|
||||
@@ -2,9 +2,7 @@ import copy
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from pretix.base.forms import I18nModelForm
|
||||
from pretix.base.models import Item, ItemVariation, Quota, Voucher
|
||||
@@ -92,113 +90,41 @@ class VoucherForm(I18nModelForm):
|
||||
self.instance.variation = None
|
||||
self.instance.quota = None
|
||||
|
||||
if self.instance.item.category and self.instance.item.category.is_addon:
|
||||
raise ValidationError(_('It is currently not possible to create vouchers for add-on products.'))
|
||||
else:
|
||||
self.instance.quota = Quota.objects.get(pk=quotaid, event=self.instance.event)
|
||||
self.instance.item = None
|
||||
self.instance.variation = None
|
||||
|
||||
if data.get('max_usages', 0) < self.instance.redeemed:
|
||||
raise ValidationError(
|
||||
_('This voucher has already been redeemed %(redeemed)s times. You cannot reduce the maximum number of '
|
||||
'usages below this number.'),
|
||||
params={
|
||||
'redeemed': self.instance.redeemed
|
||||
}
|
||||
)
|
||||
|
||||
if 'codes' in data:
|
||||
data['codes'] = [a.strip() for a in data.get('codes', '').strip().split("\n") if a]
|
||||
cnt = len(data['codes']) * data['max_usages']
|
||||
else:
|
||||
cnt = data['max_usages']
|
||||
|
||||
if self.instance.event.has_subevents and data['block_quota'] and not data.get('subevent'):
|
||||
raise ValidationError(pgettext_lazy(
|
||||
'subevent',
|
||||
'If you want this voucher to block quota, you need to select a specific date.'
|
||||
))
|
||||
|
||||
if self._clean_quota_needs_checking(data):
|
||||
self._clean_quota_check(data, cnt)
|
||||
|
||||
if 'code' in data and Voucher.objects.filter(Q(code=data['code']) & Q(event=self.instance.event) & ~Q(pk=self.instance.pk)).exists():
|
||||
raise ValidationError(_('A voucher with this code already exists.'))
|
||||
Voucher.clean_item_properties(
|
||||
data, self.instance.event,
|
||||
self.instance.quota, self.instance.item, self.instance.variation
|
||||
)
|
||||
Voucher.clean_subevent(
|
||||
data, self.instance.event
|
||||
)
|
||||
Voucher.clean_max_usages(data, self.instance.redeemed)
|
||||
check_quota = Voucher.clean_quota_needs_checking(
|
||||
data, self.initial_instance_data,
|
||||
item_changed=data.get('itemvar') != self.initial.get('itemvar'),
|
||||
creating=not self.instance.pk
|
||||
)
|
||||
if check_quota:
|
||||
Voucher.clean_quota_check(
|
||||
data, cnt, self.initial_instance_data, self.instance.event,
|
||||
self.instance.quota, self.instance.item, self.instance.variation
|
||||
)
|
||||
Voucher.clean_voucher_code(data, self.instance.event, self.instance.pk)
|
||||
|
||||
voucher_form_validation.send(sender=self.instance.event, form=self, data=data)
|
||||
|
||||
return data
|
||||
|
||||
def _clean_quota_needs_checking(self, data):
|
||||
# We only need to check for quota on vouchers that are now blocking quota and haven't
|
||||
# before (or have blocked a different quota before)
|
||||
if data.get('block_quota', False):
|
||||
is_valid = data.get('valid_until') is None or data.get('valid_until') >= now()
|
||||
if not is_valid:
|
||||
# If the voucher is not valid, it won't block any quota
|
||||
return False
|
||||
|
||||
if not self.instance.pk:
|
||||
# This is a new voucher
|
||||
return True
|
||||
|
||||
if not self.initial_instance_data.block_quota:
|
||||
# Change from nonblocking to blocking
|
||||
return True
|
||||
|
||||
if not self._clean_was_valid():
|
||||
# This voucher has been expired and is now valid again and therefore blocks quota again
|
||||
return True
|
||||
|
||||
if data.get('itemvar') != self.initial.get('itemvar'):
|
||||
# The voucher has been reassigned to a different item, variation or quota
|
||||
return True
|
||||
|
||||
if data.get('subevent') != self.initial.get('subevent'):
|
||||
# The voucher has been reassigned to a different subevent
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _clean_was_valid(self):
|
||||
return self.initial_instance_data.valid_until is None or self.initial_instance_data.valid_until >= now()
|
||||
|
||||
def _clean_quota_get_ignored(self):
|
||||
quotas = set()
|
||||
if self.initial_instance_data and self.initial_instance_data.block_quota and self._clean_was_valid():
|
||||
if self.initial_instance_data.quota:
|
||||
quotas.add(self.initial_instance_data.quota)
|
||||
elif self.initial_instance_data.variation:
|
||||
quotas |= set(self.initial_instance_data.variation.quotas.filter(
|
||||
subevent=self.initial_instance_data.subevent))
|
||||
elif self.initial_instance_data.item:
|
||||
quotas |= set(self.initial_instance_data.item.quotas.filter(
|
||||
subevent=self.initial_instance_data.subevent))
|
||||
return quotas
|
||||
|
||||
def _clean_quota_check(self, data, cnt):
|
||||
old_quotas = self._clean_quota_get_ignored()
|
||||
|
||||
if self.instance.quota:
|
||||
if self.instance.quota in old_quotas:
|
||||
return
|
||||
else:
|
||||
avail = self.instance.quota.availability(count_waitinglist=False)
|
||||
elif self.instance.item and self.instance.item.has_variations and not self.instance.variation:
|
||||
raise ValidationError(_('You can only block quota if you specify a specific product variation. '
|
||||
'Otherwise it might be unclear which quotas to block.'))
|
||||
elif self.instance.item and self.instance.variation:
|
||||
avail = self.instance.variation.check_quotas(ignored_quotas=old_quotas, subevent=data.get('subevent'))
|
||||
elif self.instance.item and not self.instance.item.has_variations:
|
||||
avail = self.instance.item.check_quotas(ignored_quotas=old_quotas, subevent=data.get('subevent'))
|
||||
else:
|
||||
raise ValidationError(_('You need to specify either a quota or a product.'))
|
||||
|
||||
if avail[0] != Quota.AVAILABILITY_OK or (avail[1] is not None and avail[1] < cnt):
|
||||
raise ValidationError(_('You cannot create a voucher that blocks quota as the selected product or '
|
||||
'quota is currently sold out or completely reserved.'))
|
||||
|
||||
def save(self, commit=True):
|
||||
super().save(commit)
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ class VoucherDelete(EventPermissionRequiredMixin, DeleteView):
|
||||
raise Http404(_("The requested voucher does not exist."))
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if self.get_object().redeemed > 0:
|
||||
if not self.get_object().allow_delete():
|
||||
messages.error(request, _('A voucher can not be deleted if it already has been redeemed.'))
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
return super().get(request, *args, **kwargs)
|
||||
@@ -136,7 +136,7 @@ class VoucherDelete(EventPermissionRequiredMixin, DeleteView):
|
||||
self.object = self.get_object()
|
||||
success_url = self.get_success_url()
|
||||
|
||||
if self.object.redeemed > 0:
|
||||
if not self.object.allow_delete():
|
||||
messages.error(request, _('A voucher can not be deleted if it already has been redeemed.'))
|
||||
else:
|
||||
self.object.log_action('pretix.voucher.deleted', user=self.request.user)
|
||||
|
||||
@@ -38,7 +38,9 @@ def team(organizer):
|
||||
return Team.objects.create(
|
||||
organizer=organizer,
|
||||
can_change_items=True,
|
||||
can_change_event_settings=True
|
||||
can_change_event_settings=True,
|
||||
can_change_vouchers=True,
|
||||
can_view_vouchers=True,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ event_permission_urls = [
|
||||
('put', 'can_change_event_settings', 'taxrules/1/', 404),
|
||||
('patch', 'can_change_event_settings', 'taxrules/1/', 404),
|
||||
('delete', 'can_change_event_settings', 'taxrules/1/', 404),
|
||||
('post', 'can_change_vouchers', 'vouchers/', 400),
|
||||
('put', 'can_change_vouchers', 'vouchers/1/', 404),
|
||||
('patch', 'can_change_vouchers', 'vouchers/1/', 404),
|
||||
('delete', 'can_change_vouchers', 'vouchers/1/', 404),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
from django.utils import timezone
|
||||
from django.utils.timezone import now
|
||||
|
||||
from pretix.base.models import Voucher
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -212,3 +216,634 @@ def test_voucher_detail(token_client, organizer, event, voucher, item):
|
||||
voucher.pk))
|
||||
assert resp.status_code == 200
|
||||
assert res == resp.data
|
||||
|
||||
|
||||
def create_voucher(token_client, organizer, event, data, expected_failure=False):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/vouchers/'.format(organizer.slug, event.slug),
|
||||
data=data, format='json'
|
||||
)
|
||||
if expected_failure:
|
||||
assert resp.status_code == 400
|
||||
else:
|
||||
assert resp.status_code == 201
|
||||
return Voucher.objects.get(pk=resp.data['id'])
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_voucher_require_item(token_client, organizer, event, item):
|
||||
create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_voucher_create_minimal(token_client, organizer, event, item):
|
||||
v = create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'item': item.pk,
|
||||
},
|
||||
)
|
||||
assert v.item == item
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_voucher_create_full(token_client, organizer, event, item):
|
||||
v = create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'code': 'ABCDEFGHI',
|
||||
'max_usages': 1,
|
||||
'valid_until': None,
|
||||
'block_quota': False,
|
||||
'allow_ignore_quota': False,
|
||||
'price_mode': 'set',
|
||||
'value': '12.00',
|
||||
'item': item.pk,
|
||||
'variation': None,
|
||||
'quota': None,
|
||||
'tag': 'Foo',
|
||||
'comment': '',
|
||||
'subevent': None
|
||||
},
|
||||
)
|
||||
|
||||
assert v.code == 'ABCDEFGHI'
|
||||
assert v.max_usages == 1
|
||||
assert v.redeemed == 0
|
||||
assert v.valid_until is None
|
||||
assert v.max_usages == 1
|
||||
assert v.block_quota is False
|
||||
assert v.price_mode == 'set'
|
||||
assert v.value == Decimal('12.00')
|
||||
assert v.item == item
|
||||
assert v.variation is None
|
||||
assert v.quota is None
|
||||
assert v.tag == 'Foo'
|
||||
assert v.subevent is None
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_voucher_create_for_addon_item(token_client, organizer, event, item):
|
||||
c = event.categories.create(name="Foo", is_addon=True)
|
||||
item.category = c
|
||||
item.save()
|
||||
create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'item': item.pk,
|
||||
}, expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_non_blocking_item_voucher(token_client, organizer, event, item):
|
||||
v = create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'item': item.pk,
|
||||
}
|
||||
)
|
||||
assert v.item == item
|
||||
assert v.variation is None
|
||||
assert v.quota is None
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_non_blocking_variation_voucher(token_client, organizer, event, item):
|
||||
variation = item.variations.create(value="XL")
|
||||
v = create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'item': item.pk,
|
||||
'variation': variation.pk
|
||||
}
|
||||
)
|
||||
assert v.item == variation.item
|
||||
assert v.variation == variation
|
||||
assert v.quota is None
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_non_blocking_quota_voucher(token_client, organizer, event, quota):
|
||||
v = create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'quota': quota.pk
|
||||
}
|
||||
)
|
||||
assert not v.block_quota
|
||||
assert v.quota == quota
|
||||
assert v.item is None
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_blocking_item_voucher_quota_free(token_client, organizer, event, item, quota):
|
||||
v = create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'item': item.pk,
|
||||
'block_quota': True
|
||||
}
|
||||
)
|
||||
assert v.block_quota
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_blocking_item_voucher_quota_full(token_client, organizer, event, item, quota):
|
||||
quota.size = 0
|
||||
quota.save()
|
||||
create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'item': item.pk,
|
||||
'block_quota': True
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_blocking_item_voucher_quota_full_invalid(token_client, organizer, event, item, quota):
|
||||
quota.size = 0
|
||||
quota.save()
|
||||
v = create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'item': item.pk,
|
||||
'block_quota': True,
|
||||
'valid_until': (now() - datetime.timedelta(days=3)).isoformat()
|
||||
}
|
||||
)
|
||||
assert v.block_quota
|
||||
assert not v.is_active()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_blocking_variation_voucher_quota_free(token_client, organizer, event, item, quota):
|
||||
variation = item.variations.create(value="XL")
|
||||
quota.variations.add(variation)
|
||||
v = create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'item': item.pk,
|
||||
'variation': variation.pk,
|
||||
'block_quota': True
|
||||
}
|
||||
)
|
||||
assert v.block_quota
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_short_code(token_client, organizer, event, item):
|
||||
create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'code': 'ABC',
|
||||
'item': item.pk
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_blocking_variation_voucher_quota_full(token_client, organizer, event, item, quota):
|
||||
variation = item.variations.create(value="XL")
|
||||
quota.variations.add(variation)
|
||||
quota.size = 0
|
||||
quota.save()
|
||||
create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'item': item.pk,
|
||||
'variation': variation.pk,
|
||||
'block_quota': True
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_blocking_quota_voucher_quota_free(token_client, organizer, event, quota):
|
||||
create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'quota': quota.pk,
|
||||
'block_quota': True
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_blocking_quota_voucher_quota_full(token_client, organizer, event, quota):
|
||||
quota.size = 0
|
||||
quota.save()
|
||||
create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'quota': quota.pk,
|
||||
'block_quota': True
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_duplicate_code(token_client, organizer, event, quota):
|
||||
v = event.vouchers.create(quota=quota)
|
||||
create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'quota': quota.pk,
|
||||
'code': v.code,
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_subevent_optional(token_client, organizer, event, item, subevent):
|
||||
v = create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'item': item.pk,
|
||||
},
|
||||
)
|
||||
assert v.subevent is None
|
||||
assert v.block_quota is False
|
||||
assert v.item == item
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_subevent_required_for_blocking(token_client, organizer, event, item, subevent):
|
||||
create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'item': item.pk,
|
||||
'block_quota': True
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_subevent_blocking_quota_free(token_client, organizer, event, item, quota, subevent):
|
||||
se2 = event.subevents.create(name="Bar", date_from=now())
|
||||
quota.subevent = subevent
|
||||
quota.save()
|
||||
q2 = event.quotas.create(event=event, name='Tickets', size=0, subevent=se2)
|
||||
q2.items.add(item)
|
||||
|
||||
v = create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'item': item.pk,
|
||||
'block_quota': True,
|
||||
'subevent': subevent.pk
|
||||
},
|
||||
)
|
||||
assert v.block_quota
|
||||
assert v.subevent == subevent
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_subevent_blocking_quota_full(token_client, organizer, event, item, quota, subevent):
|
||||
se2 = event.subevents.create(name="Bar", date_from=now())
|
||||
quota.subevent = subevent
|
||||
quota.size = 0
|
||||
quota.save()
|
||||
q2 = event.quotas.create(event=event, name='Tickets', size=5, subevent=se2)
|
||||
q2.items.add(item)
|
||||
|
||||
create_voucher(
|
||||
token_client, organizer, event,
|
||||
data={
|
||||
'item': item.pk,
|
||||
'block_quota': True,
|
||||
'subevent': subevent.pk
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
def change_voucher(token_client, organizer, event, voucher, data, expected_failure=False):
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/vouchers/{}/'.format(organizer.slug, event.slug, voucher.pk),
|
||||
data=data, format='json'
|
||||
)
|
||||
if expected_failure:
|
||||
assert resp.status_code == 400
|
||||
else:
|
||||
assert resp.status_code == 200
|
||||
voucher.refresh_from_db()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_non_blocking_voucher(token_client, organizer, event, item, quota):
|
||||
v = event.vouchers.create(item=item)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'quota': quota.pk,
|
||||
'item': None
|
||||
}
|
||||
)
|
||||
assert v.item is None
|
||||
assert v.quota == quota
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_voucher_reduce_max_usages(token_client, organizer, event, item, quota):
|
||||
v = event.vouchers.create(item=item, max_usages=5, redeemed=3)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'max_usages': 2
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
assert v.max_usages == 5
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_blocking_voucher_unchanged_quota_full(token_client, organizer, event, item, quota):
|
||||
quota.size = 0
|
||||
quota.save()
|
||||
v = event.vouchers.create(item=item, block_quota=True)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'comment': 'Foo'
|
||||
}
|
||||
)
|
||||
assert v.item == item
|
||||
assert v.block_quota
|
||||
assert v.comment == 'Foo'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_voucher_to_blocking_quota_full(token_client, organizer, event, item, quota):
|
||||
quota.size = 0
|
||||
quota.save()
|
||||
v = event.vouchers.create(item=item)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'block_quota': True
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_voucher_to_blocking_quota_free(token_client, organizer, event, item, quota):
|
||||
v = event.vouchers.create(item=item)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'block_quota': True
|
||||
},
|
||||
)
|
||||
assert v.block_quota
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_voucher_validity_to_valid_quota_full(token_client, organizer, event, item, quota):
|
||||
quota.size = 0
|
||||
quota.save()
|
||||
v = event.vouchers.create(item=item, valid_until=now() - datetime.timedelta(days=3),
|
||||
block_quota=True)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'valid_until': (now() + datetime.timedelta(days=3)).isoformat()
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
assert v.valid_until < now()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_voucher_validity_to_valid_quota_free(token_client, organizer, event, item, quota):
|
||||
v = event.vouchers.create(item=item, valid_until=now() - datetime.timedelta(days=3),
|
||||
block_quota=True)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'valid_until': (now() + datetime.timedelta(days=3)).isoformat()
|
||||
},
|
||||
)
|
||||
assert v.valid_until > now()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_item_of_blocking_voucher_quota_free(token_client, organizer, event, item, quota):
|
||||
ticket2 = event.items.create(name='Late-bird ticket', default_price=23)
|
||||
quota.items.add(ticket2)
|
||||
v = event.vouchers.create(item=item, block_quota=True)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'item': ticket2.pk
|
||||
},
|
||||
)
|
||||
assert v.item == ticket2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_item_of_blocking_voucher_quota_full(token_client, organizer, event, item, quota):
|
||||
ticket2 = event.items.create(name='Late-bird ticket', default_price=23)
|
||||
quota2 = event.quotas.create(name='Late', size=0)
|
||||
quota2.items.add(ticket2)
|
||||
v = event.vouchers.create(item=item, block_quota=True)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'item': ticket2.pk
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_variation_of_blocking_voucher_quota_free(token_client, organizer, event):
|
||||
shirt = event.items.create(name='Shirt', default_price=23)
|
||||
vs = shirt.variations.create(value='S')
|
||||
vm = shirt.variations.create(value='M')
|
||||
qs = event.quotas.create(name='S', size=2)
|
||||
qs.variations.add(vs)
|
||||
qm = event.quotas.create(name='M', size=2)
|
||||
qm.variations.add(vm)
|
||||
v = event.vouchers.create(item=shirt, variation=vs, block_quota=True)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'variation': vm.pk
|
||||
},
|
||||
)
|
||||
assert v.variation == vm
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_variation_of_blocking_voucher_without_quota_change(token_client, organizer, event):
|
||||
shirt = event.items.create(name='Shirt', default_price=23)
|
||||
vs = shirt.variations.create(value='S')
|
||||
vm = shirt.variations.create(value='M')
|
||||
q = event.quotas.create(name='S', size=0)
|
||||
q.variations.add(vs)
|
||||
q.variations.add(vm)
|
||||
v = event.vouchers.create(item=shirt, variation=vs, block_quota=True)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'variation': vm.pk
|
||||
}
|
||||
)
|
||||
assert v.variation == vm
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_variation_of_blocking_voucher_quota_full(token_client, organizer, event):
|
||||
shirt = event.items.create(name='Shirt', default_price=23)
|
||||
vs = shirt.variations.create(value='S')
|
||||
vm = shirt.variations.create(value='M')
|
||||
qs = event.quotas.create(name='S', size=2)
|
||||
qs.variations.add(vs)
|
||||
qm = event.quotas.create(name='M', size=0)
|
||||
qm.variations.add(vm)
|
||||
v = event.vouchers.create(item=shirt, variation=vs, block_quota=True)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'variation': vm.pk
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_quota_of_blocking_voucher_quota_free(token_client, organizer, event):
|
||||
qs = event.quotas.create(name='S', size=2)
|
||||
qm = event.quotas.create(name='M', size=2)
|
||||
v = event.vouchers.create(quota=qs, block_quota=True)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'quota': qm.pk
|
||||
},
|
||||
)
|
||||
assert v.quota == qm
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_quota_of_blocking_voucher_quota_full(token_client, organizer, event):
|
||||
qs = event.quotas.create(name='S', size=2)
|
||||
qm = event.quotas.create(name='M', size=0)
|
||||
v = event.vouchers.create(quota=qs, block_quota=True)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'quota': qm.pk
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_item_of_blocking_voucher_without_quota_change(token_client, organizer, event, item, quota):
|
||||
quota.size = 0
|
||||
quota.save()
|
||||
ticket2 = event.items.create(name='Standard Ticket', default_price=23)
|
||||
quota.items.add(ticket2)
|
||||
v = event.vouchers.create(item=item, block_quota=True)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'item': ticket2.pk
|
||||
},
|
||||
)
|
||||
assert v.item == ticket2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_code_to_duplicate(token_client, organizer, event, item, quota):
|
||||
v1 = event.vouchers.create(quota=quota)
|
||||
v2 = event.vouchers.create(quota=quota)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v1,
|
||||
data={
|
||||
'code': v2.code
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_subevent_blocking_quota_free(token_client, organizer, event, item, quota, subevent):
|
||||
quota.subevent = subevent
|
||||
quota.save()
|
||||
se2 = event.subevents.create(name="Bar", date_from=now())
|
||||
q2 = event.quotas.create(event=event, name='Tickets', size=5, subevent=se2)
|
||||
q2.items.add(item)
|
||||
|
||||
v = event.vouchers.create(item=item, block_quota=True, subevent=subevent)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'subevent': se2.pk
|
||||
},
|
||||
)
|
||||
assert v.subevent == se2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_subevent_blocking_quota_full(token_client, organizer, event, item, quota, subevent):
|
||||
quota.subevent = subevent
|
||||
quota.save()
|
||||
se2 = event.subevents.create(name="Bar", date_from=now())
|
||||
q2 = event.quotas.create(event=event, name='Tickets', size=0, subevent=se2)
|
||||
q2.items.add(item)
|
||||
|
||||
v = event.vouchers.create(item=item, block_quota=True, subevent=subevent)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'subevent': se2.pk
|
||||
},
|
||||
expected_failure=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_voucher(token_client, organizer, event, quota):
|
||||
v = event.vouchers.create(quota=quota)
|
||||
resp = token_client.delete(
|
||||
'/api/v1/organizers/{}/events/{}/vouchers/{}/'.format(organizer.slug, event.slug, v.pk),
|
||||
)
|
||||
assert resp.status_code == 204
|
||||
assert not event.vouchers.filter(pk=v.id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_voucher_redeemed(token_client, organizer, event, quota):
|
||||
v = event.vouchers.create(quota=quota, redeemed=1)
|
||||
resp = token_client.delete(
|
||||
'/api/v1/organizers/{}/events/{}/vouchers/{}/'.format(organizer.slug, event.slug, v.pk),
|
||||
)
|
||||
assert resp.status_code == 403
|
||||
assert event.vouchers.filter(pk=v.id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_redeemed_is_not_writable(token_client, organizer, event, item):
|
||||
v = event.vouchers.create(item=item)
|
||||
change_voucher(
|
||||
token_client, organizer, event, v,
|
||||
data={
|
||||
'redeemed': 1,
|
||||
},
|
||||
)
|
||||
assert v.redeemed == 0
|
||||
|
||||
@@ -144,6 +144,14 @@ class VoucherFormTest(SoupTest):
|
||||
assert v.code in doc.select(".alert-success")[0].text
|
||||
assert count_before + 1 == self.event.vouchers.count()
|
||||
|
||||
def test_create_voucher_for_addon_item(self):
|
||||
c = self.event.categories.create(name="Foo", is_addon=True)
|
||||
self.ticket.category = c
|
||||
self.ticket.save()
|
||||
self._create_voucher({
|
||||
'itemvar': '%d' % self.ticket.pk
|
||||
}, expected_failure=True)
|
||||
|
||||
def test_create_non_blocking_item_voucher(self):
|
||||
self._create_voucher({
|
||||
'itemvar': '%d' % self.ticket.pk
|
||||
@@ -255,6 +263,12 @@ class VoucherFormTest(SoupTest):
|
||||
v.refresh_from_db()
|
||||
assert v.block_quota
|
||||
|
||||
def test_change_voucher_reduce_max_usages(self):
|
||||
v = self.event.vouchers.create(item=self.ticket, max_usages=5, redeemed=3)
|
||||
self._change_voucher(v, {
|
||||
'max_usages': '2'
|
||||
}, expected_failure=True)
|
||||
|
||||
def test_change_voucher_to_blocking_quota_full(self):
|
||||
self.quota_tickets.size = 0
|
||||
self.quota_tickets.save()
|
||||
|
||||
Reference in New Issue
Block a user