forked from CGM_Public/pretix_original
* MKBDIGI-184: Basic create added for API items endpoint * MKBDIGI-184: Starting endpoint for GET /api/v1/organizers/(organizer)/events/(event)/items/(id)/variations/ * MKBDIGI-184: endpoint for GET /api/v1/organizers/(organizer)/events/(event)/items/(id)/variations/ * MKBDIGI-184: Completed endpoint for variations * MKBDIGI-184: Added endpoint for addons * MKBDIGI-184: Added Item validation * MKBDIGI-184: Added check for order/cart positions on item variation destroy. * MKBDIGI-184: Fixed check for order/cart positions on item variation destroy. * MKBDIGI-184: Updated tests, validation for addons * MKBDIGI-184: Documentation feedback corrections * MKBDIGI-184: Added documentation for item add-ons * MKBDIGI-184: Code formatting fixes * MKBDIGI-184: Feedback fixes * MKBDIGI-184: Updated tests for delete item * MKBDIGI-184: Cleaned up tests * MKBDIGI-184: Added additional test URLs * MKBDIGI-184: Documentation fixes * MKBDIGI-184: Fixed read-only fields/Documentation * MKBDIGI-184: Documentation fixes * MKBDIGI-184: Added helper for dict merge for 3.4 compatibility * MKBDIGI-184: Validation updates * MKBDIGI-184: Fixed permissions test error. Changed to HTTP 404 for POST to addons endpoint * MKBDIGI-184: Implemented nested variations and add-ons for POST on the item endpoint.
This commit is contained in:
committed by
Raphael Michel
parent
f5dba45fa0
commit
8eaada992f
@@ -1,5 +1,8 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||
@@ -16,11 +19,44 @@ class InlineItemVariationSerializer(I18nAwareModelSerializer):
|
||||
'position', 'default_price', 'price')
|
||||
|
||||
|
||||
class ItemVariationSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = ItemVariation
|
||||
fields = ('id', 'value', 'active', 'description',
|
||||
'position', 'default_price', 'price')
|
||||
|
||||
|
||||
class InlineItemAddOnSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ItemAddOn
|
||||
fields = ('addon_category', 'min_count', 'max_count',
|
||||
'position')
|
||||
'position', 'price_included')
|
||||
|
||||
|
||||
class ItemAddOnSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ItemAddOn
|
||||
fields = ('id', 'addon_category', 'min_count', 'max_count',
|
||||
'position', 'price_included')
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
|
||||
ItemAddOn.clean_max_min_count(data.get('max_count'), data.get('min_count'))
|
||||
|
||||
return data
|
||||
|
||||
def validate_min_count(self, value):
|
||||
ItemAddOn.clean_min_count(value)
|
||||
return value
|
||||
|
||||
def validate_max_count(self, value):
|
||||
ItemAddOn.clean_max_count(value)
|
||||
return value
|
||||
|
||||
def validate_addon_category(self, value):
|
||||
ItemAddOn.clean_categories(self.context['event'], self.context['item'], self.instance, value)
|
||||
return value
|
||||
|
||||
|
||||
class ItemTaxRateField(serializers.Field):
|
||||
@@ -32,8 +68,8 @@ class ItemTaxRateField(serializers.Field):
|
||||
|
||||
|
||||
class ItemSerializer(I18nAwareModelSerializer):
|
||||
addons = InlineItemAddOnSerializer(many=True)
|
||||
variations = InlineItemVariationSerializer(many=True)
|
||||
addons = InlineItemAddOnSerializer(many=True, required=False)
|
||||
variations = InlineItemVariationSerializer(many=True, required=False)
|
||||
tax_rate = ItemTaxRateField(source='*', read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -44,6 +80,55 @@ class ItemSerializer(I18nAwareModelSerializer):
|
||||
'require_voucher', 'hide_without_voucher', 'allow_cancel',
|
||||
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations',
|
||||
'variations', 'addons')
|
||||
read_only_fields = ('has_variations', 'picture')
|
||||
|
||||
def get_serializer_context(self):
|
||||
return {"has_variations": self.kwargs['has_variations']}
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
|
||||
Item.clean_per_order(data.get('min_per_order'), data.get('max_per_order'))
|
||||
Item.clean_available(data.get('available_from'), data.get('available_until'))
|
||||
|
||||
return data
|
||||
|
||||
def validate_category(self, value):
|
||||
Item.clean_category(value, self.context['event'])
|
||||
return value
|
||||
|
||||
def validate_tax_rule(self, value):
|
||||
Item.clean_tax_rule(value, self.context['event'])
|
||||
return value
|
||||
|
||||
def validate_variations(self, value):
|
||||
if self.instance is not None:
|
||||
raise ValidationError(_('Updating variations via PATCH/PUT is not supported. Please use the dedicated'
|
||||
' nested endpoint.'))
|
||||
return value
|
||||
|
||||
def validate_addons(self, value):
|
||||
if self.instance is not None:
|
||||
raise ValidationError(_('Updating add-ons via PATCH/PUT is not supported. Please use the dedicated'
|
||||
' nested endpoint.'))
|
||||
else:
|
||||
for addon_data in value:
|
||||
ItemAddOn.clean_categories(self.context['event'], None, self.instance, addon_data['addon_category'])
|
||||
ItemAddOn.clean_min_count(addon_data['min_count'])
|
||||
ItemAddOn.clean_max_count(addon_data['max_count'])
|
||||
ItemAddOn.clean_max_min_count(addon_data['max_count'], addon_data['min_count'])
|
||||
return value
|
||||
|
||||
@transaction.atomic
|
||||
def create(self, validated_data):
|
||||
variations_data = validated_data.pop('variations') if 'variations' in validated_data else {}
|
||||
addons_data = validated_data.pop('addons') if 'addons' in validated_data else {}
|
||||
item = Item.objects.create(**validated_data)
|
||||
for variation_data in variations_data:
|
||||
ItemVariation.objects.create(item=item, **variation_data)
|
||||
for addon_data in addons_data:
|
||||
ItemAddOn.objects.create(base_item=item, **addon_data)
|
||||
return item
|
||||
|
||||
|
||||
class ItemCategorySerializer(I18nAwareModelSerializer):
|
||||
|
||||
@@ -29,6 +29,10 @@ event_router.register(r'checkinlists', checkin.CheckinListViewSet)
|
||||
checkinlist_router = routers.DefaultRouter()
|
||||
checkinlist_router.register(r'positions', checkin.CheckinListPositionViewSet)
|
||||
|
||||
item_router = routers.DefaultRouter()
|
||||
item_router.register(r'variations', item.ItemVariationViewSet)
|
||||
item_router.register(r'addons', item.ItemAddOnViewSet)
|
||||
|
||||
# Force import of all plugins to give them a chance to register URLs with the router
|
||||
for app in apps.get_app_configs():
|
||||
if hasattr(app, 'PretixPluginMeta'):
|
||||
@@ -39,6 +43,7 @@ urlpatterns = [
|
||||
url(r'^', include(router.urls)),
|
||||
url(r'^organizers/(?P<organizer>[^/]+)/', include(orga_router.urls)),
|
||||
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/', include(event_router.urls)),
|
||||
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/items/(?P<item>[^/]+)/', include(item_router.urls)),
|
||||
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/checkinlists/(?P<list>[^/]+)/',
|
||||
include(checkinlist_router.urls)),
|
||||
]
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import django_filters
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import detail_route
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from rest_framework.response import Response
|
||||
|
||||
from pretix.api.serializers.item import (
|
||||
ItemCategorySerializer, ItemSerializer, QuestionSerializer,
|
||||
QuotaSerializer,
|
||||
ItemAddOnSerializer, ItemCategorySerializer, ItemSerializer,
|
||||
ItemVariationSerializer, QuestionSerializer, QuotaSerializer,
|
||||
)
|
||||
from pretix.base.models import (
|
||||
Item, ItemAddOn, ItemCategory, ItemVariation, Question, Quota,
|
||||
)
|
||||
from pretix.base.models import Item, ItemCategory, Question, Quota
|
||||
from pretix.base.models.organizer import TeamAPIToken
|
||||
from pretix.helpers.dicts import merge_dicts
|
||||
|
||||
|
||||
class ItemFilter(FilterSet):
|
||||
@@ -28,7 +33,7 @@ class ItemFilter(FilterSet):
|
||||
fields = ['active', 'category', 'admission', 'tax_rate', 'free_price']
|
||||
|
||||
|
||||
class ItemViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
class ItemViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = ItemSerializer
|
||||
queryset = Item.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
@@ -36,10 +41,159 @@ class ItemViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
ordering = ('position', 'id')
|
||||
filter_class = ItemFilter
|
||||
permission = 'can_change_items'
|
||||
write_permission = 'can_change_items'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.items.select_related('tax_rule').prefetch_related('variations', 'addons').all()
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
'pretix.event.item.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
|
||||
ctx['has_variations'] = self.request.data.get('has_variations')
|
||||
return ctx
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
'pretix.event.item.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 item cannot be deleted because it has already been ordered '
|
||||
'by a user or currently is in a users\'s cart. Please set the item as '
|
||||
'"inactive" instead.')
|
||||
|
||||
instance.log_action(
|
||||
'pretix.event.item.deleted',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
)
|
||||
super().perform_destroy(instance)
|
||||
|
||||
|
||||
class ItemVariationViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = ItemVariationSerializer
|
||||
queryset = ItemVariation.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter,)
|
||||
ordering_fields = ('id', 'position')
|
||||
ordering = ('id',)
|
||||
permission = 'can_change_items'
|
||||
write_permission = 'can_change_items'
|
||||
|
||||
def get_queryset(self):
|
||||
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
|
||||
return item.variations.all()
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['item'] = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
|
||||
return ctx
|
||||
|
||||
def perform_create(self, serializer):
|
||||
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
|
||||
if not item.has_variations:
|
||||
raise PermissionDenied('This variation cannot be created because the item does not have variations. '
|
||||
'Changing a product without variations to a product with variations is not allowed.')
|
||||
serializer.save(item=item)
|
||||
item.log_action(
|
||||
'pretix.event.item.variation.added',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk},
|
||||
{'value': serializer.instance.value})
|
||||
)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.item.log_action(
|
||||
'pretix.event.item.variation.changed',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk},
|
||||
{'value': serializer.instance.value})
|
||||
)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
if not instance.allow_delete():
|
||||
raise PermissionDenied('This variation cannot be deleted because it has already been ordered '
|
||||
'by a user or currently is in a users\'s cart. Please set the variation as '
|
||||
'\'inactive\' instead.')
|
||||
if instance.is_only_variation():
|
||||
raise PermissionDenied('This variation cannot be deleted because it is the only variation. Changing a '
|
||||
'product with variations to a product without variations is not allowed.')
|
||||
super().perform_destroy(instance)
|
||||
instance.item.log_action(
|
||||
'pretix.event.item.variation.deleted',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
data={
|
||||
'value': instance.value,
|
||||
'id': self.kwargs['pk']
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ItemAddOnViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = ItemAddOnSerializer
|
||||
queryset = ItemAddOn.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter,)
|
||||
ordering_fields = ('id', 'position')
|
||||
ordering = ('id',)
|
||||
permission = 'can_change_items'
|
||||
write_permission = 'can_change_items'
|
||||
|
||||
def get_queryset(self):
|
||||
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
|
||||
return item.addons.all()
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['event'] = self.request.event
|
||||
ctx['item'] = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
|
||||
return ctx
|
||||
|
||||
def perform_create(self, serializer):
|
||||
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
|
||||
category = get_object_or_404(ItemCategory, pk=self.request.data['addon_category'])
|
||||
serializer.save(base_item=item, addon_category=category)
|
||||
item.log_action(
|
||||
'pretix.event.item.addons.added',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk})
|
||||
)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.base_item.log_action(
|
||||
'pretix.event.item.addons.changed',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk})
|
||||
)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
super().perform_destroy(instance)
|
||||
instance.base_item.log_action(
|
||||
'pretix.event.item.addons.removed',
|
||||
user=self.request.user,
|
||||
api_token=(self.request.auth if isinstance(self.request.auth, TeamAPIToken) else None),
|
||||
data={'category': instance.addon_category.pk}
|
||||
)
|
||||
|
||||
|
||||
class ItemCategoryFilter(FilterSet):
|
||||
class Meta:
|
||||
|
||||
@@ -372,10 +372,41 @@ class Item(LoggedModel):
|
||||
return min([q.availability(count_waitinglist=count_waitinglist, _cache=_cache) for q in check_quotas],
|
||||
key=lambda s: (s[0], s[1] if s[1] is not None else sys.maxsize))
|
||||
|
||||
def allow_delete(self):
|
||||
from pretix.base.models.orders import CartPosition, OrderPosition
|
||||
|
||||
return (
|
||||
not OrderPosition.objects.filter(item=self).exists()
|
||||
and not CartPosition.objects.filter(item=self).exists()
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def has_variations(self):
|
||||
return self.variations.exists()
|
||||
|
||||
@staticmethod
|
||||
def clean_per_order(min_per_order, max_per_order):
|
||||
if min_per_order is not None and max_per_order is not None:
|
||||
if min_per_order > max_per_order:
|
||||
raise ValidationError(_('The maximum number per order can not be lower than the minimum number per '
|
||||
'order.'))
|
||||
|
||||
@staticmethod
|
||||
def clean_category(category, event):
|
||||
if category is not None and category.event is not None and category.event != event:
|
||||
raise ValidationError(_('The item\'s category must belong to the same event as the item.'))
|
||||
|
||||
@staticmethod
|
||||
def clean_tax_rule(tax_rule, event):
|
||||
if tax_rule is not None and tax_rule.event is not None and tax_rule.event != event:
|
||||
raise ValidationError(_('The item\'s tax rule must belong to the same event as the item.'))
|
||||
|
||||
@staticmethod
|
||||
def clean_available(from_date, until_date):
|
||||
if from_date is not None and until_date is not None:
|
||||
if from_date > until_date:
|
||||
raise ValidationError(_('The item\'s availability cannot end before it starts.'))
|
||||
|
||||
|
||||
class ItemVariation(models.Model):
|
||||
"""
|
||||
@@ -479,6 +510,17 @@ class ItemVariation(models.Model):
|
||||
return self.id < other.id
|
||||
return self.position < other.position
|
||||
|
||||
def allow_delete(self):
|
||||
from pretix.base.models.orders import CartPosition, OrderPosition
|
||||
|
||||
return (
|
||||
not OrderPosition.objects.filter(variation=self).exists()
|
||||
and not CartPosition.objects.filter(variation=self).exists()
|
||||
)
|
||||
|
||||
def is_only_variation(self):
|
||||
return ItemVariation.objects.filter(item=self.item).count() == 1
|
||||
|
||||
|
||||
class ItemAddOn(models.Model):
|
||||
"""
|
||||
@@ -530,8 +572,34 @@ class ItemAddOn(models.Model):
|
||||
ordering = ('position', 'pk')
|
||||
|
||||
def clean(self):
|
||||
if self.max_count < self.min_count:
|
||||
raise ValidationError(_('The minimum number needs to be lower than the maximum number.'))
|
||||
self.clean_min_count(self.min_count)
|
||||
self.clean_max_count(self.max_count)
|
||||
self.clean_max_min_count(self.max_count, self.min_count)
|
||||
|
||||
@staticmethod
|
||||
def clean_categories(event, item, addon, new_category):
|
||||
if event != new_category.event:
|
||||
raise ValidationError(_('The add-on\'s category must belong to the same event as the item.'))
|
||||
if item is not None:
|
||||
if addon is None or addon.addon_category != new_category:
|
||||
for addon in item.addons.all():
|
||||
if addon.addon_category == new_category:
|
||||
raise ValidationError(_('The item already has an add-on of this category.'))
|
||||
|
||||
@staticmethod
|
||||
def clean_min_count(min_count):
|
||||
if min_count < 0:
|
||||
raise ValidationError(_('The minimum count needs to be equal to or greater than zero.'))
|
||||
|
||||
@staticmethod
|
||||
def clean_max_count(max_count):
|
||||
if max_count < 0:
|
||||
raise ValidationError(_('The maximum count needs to be equal to or greater than zero.'))
|
||||
|
||||
@staticmethod
|
||||
def clean_max_min_count(max_count, min_count):
|
||||
if max_count < min_count:
|
||||
raise ValidationError(_('The maximum count needs to be greater than the minimum count.'))
|
||||
|
||||
|
||||
class Question(LoggedModel):
|
||||
|
||||
@@ -1031,7 +1031,8 @@ class ItemDelete(EventPermissionRequiredMixin, DeleteView):
|
||||
@transaction.atomic
|
||||
def delete(self, request, *args, **kwargs):
|
||||
success_url = self.get_success_url()
|
||||
if self.is_allowed():
|
||||
o = self.get_object()
|
||||
if o.allow_delete():
|
||||
self.get_object().cartposition_set.all().delete()
|
||||
self.get_object().log_action('pretix.event.item.deleted', user=self.request.user)
|
||||
self.get_object().delete()
|
||||
|
||||
9
src/pretix/helpers/dicts.py
Normal file
9
src/pretix/helpers/dicts.py
Normal file
@@ -0,0 +1,9 @@
|
||||
def merge_dicts(*dict_args):
|
||||
"""
|
||||
Given any number of dicts, shallow copy and merge into a new dict,
|
||||
precedence goes to key value pairs in latter dicts.
|
||||
"""
|
||||
result = {}
|
||||
for dictionary in dict_args:
|
||||
result.update(dictionary)
|
||||
return result
|
||||
@@ -117,3 +117,8 @@ def subevent2(event2, meta_prop):
|
||||
@pytest.fixture
|
||||
def taxrule(event):
|
||||
return event.tax_rules.create(name="VAT", rate=19)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def taxrule2(event2):
|
||||
return event2.tax_rules.create(name="VAT", rate=25)
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from django_countries.fields import Country
|
||||
from pytz import UTC
|
||||
|
||||
from pretix.base.models import Quota
|
||||
from pretix.base.models import (
|
||||
CartPosition, InvoiceAddress, Item, ItemAddOn, ItemVariation, Order,
|
||||
OrderPosition, Quota,
|
||||
)
|
||||
from pretix.base.models.orders import OrderFee
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -10,6 +18,64 @@ def category(event):
|
||||
return event.categories.create(name="Tickets")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def category2(event2):
|
||||
return event2.categories.create(name="Tickets2")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def order(event, item, taxrule):
|
||||
testtime = datetime(2017, 12, 1, 10, 0, 0, tzinfo=UTC)
|
||||
|
||||
with mock.patch('django.utils.timezone.now') as mock_now:
|
||||
mock_now.return_value = testtime
|
||||
o = Order.objects.create(
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, secret="k24fiuwvu8kxz3y1",
|
||||
datetime=datetime(2017, 12, 1, 10, 0, 0, tzinfo=UTC),
|
||||
expires=datetime(2017, 12, 10, 10, 0, 0, tzinfo=UTC),
|
||||
total=23, payment_provider='banktransfer', locale='en'
|
||||
)
|
||||
o.fees.create(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=Decimal('0.25'), tax_rate=Decimal('19.00'),
|
||||
tax_value=Decimal('0.05'), tax_rule=taxrule)
|
||||
InvoiceAddress.objects.create(order=o, company="Sample company", country=Country('NZ'))
|
||||
return o
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def order_position(item, order, taxrule, variations):
|
||||
op = OrderPosition.objects.create(
|
||||
order=order,
|
||||
item=item,
|
||||
variation=variations[0],
|
||||
tax_rule=taxrule,
|
||||
tax_rate=taxrule.rate,
|
||||
tax_value=Decimal("3"),
|
||||
price=Decimal("23"),
|
||||
attendee_name="Peter",
|
||||
secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w"
|
||||
)
|
||||
return op
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cart_position(event, item, variations):
|
||||
testtime = datetime(2017, 12, 1, 10, 0, 0, tzinfo=UTC)
|
||||
|
||||
with mock.patch('django.utils.timezone.now') as mock_now:
|
||||
mock_now.return_value = testtime
|
||||
c = CartPosition.objects.create(
|
||||
event=event,
|
||||
item=item,
|
||||
datetime=datetime.now(),
|
||||
expires=datetime.now() + timedelta(days=1),
|
||||
variation=variations[0],
|
||||
price=Decimal("23"),
|
||||
cart_id="z3fsn8jyufm5kpk768q69gkbyr5f4h6w"
|
||||
)
|
||||
return c
|
||||
|
||||
|
||||
TEST_CATEGORY_RES = {
|
||||
"name": {"en": "Tickets"},
|
||||
"description": {"en": ""},
|
||||
@@ -187,7 +253,8 @@ def test_item_detail_addons(token_client, organizer, event, team, item, category
|
||||
"addon_category": category.pk,
|
||||
"min_count": 0,
|
||||
"max_count": 1,
|
||||
"position": 0
|
||||
"position": 0,
|
||||
"price_included": False
|
||||
}]
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug,
|
||||
item.pk))
|
||||
@@ -195,20 +262,390 @@ def test_item_detail_addons(token_client, organizer, event, team, item, category
|
||||
assert res == resp.data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def quota(event, item):
|
||||
q = event.quotas.create(name="Budget Quota", size=200)
|
||||
q.items.add(item)
|
||||
return q
|
||||
@pytest.mark.django_db
|
||||
def test_item_create(token_client, organizer, event, item, category, taxrule):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"category": category.pk,
|
||||
"name": {
|
||||
"en": "Ticket"
|
||||
},
|
||||
"active": True,
|
||||
"description": None,
|
||||
"default_price": "23.00",
|
||||
"free_price": False,
|
||||
"tax_rate": "19.00",
|
||||
"tax_rule": taxrule.pk,
|
||||
"admission": True,
|
||||
"position": 0,
|
||||
"picture": None,
|
||||
"available_from": None,
|
||||
"available_until": None,
|
||||
"require_voucher": False,
|
||||
"hide_without_voucher": False,
|
||||
"allow_cancel": True,
|
||||
"min_per_order": None,
|
||||
"max_per_order": None,
|
||||
"checkin_attention": False,
|
||||
"has_variations": True
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
|
||||
|
||||
TEST_QUOTA_RES = {
|
||||
"name": "Budget Quota",
|
||||
"size": 200,
|
||||
"items": [],
|
||||
"variations": [],
|
||||
"subevent": None
|
||||
}
|
||||
@pytest.mark.django_db
|
||||
def test_item_create_with_variation(token_client, organizer, event, item, category, taxrule):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"category": category.pk,
|
||||
"name": {
|
||||
"en": "Ticket"
|
||||
},
|
||||
"active": True,
|
||||
"description": None,
|
||||
"default_price": "23.00",
|
||||
"free_price": False,
|
||||
"tax_rate": "19.00",
|
||||
"tax_rule": taxrule.pk,
|
||||
"admission": True,
|
||||
"position": 0,
|
||||
"picture": None,
|
||||
"available_from": None,
|
||||
"available_until": None,
|
||||
"require_voucher": False,
|
||||
"hide_without_voucher": False,
|
||||
"allow_cancel": True,
|
||||
"min_per_order": None,
|
||||
"max_per_order": None,
|
||||
"checkin_attention": False,
|
||||
"has_variations": True,
|
||||
"variations": [
|
||||
{
|
||||
"value": {
|
||||
"de": "Kommentar",
|
||||
"en": "Comment"
|
||||
},
|
||||
"active": True,
|
||||
"description": None,
|
||||
"position": 0,
|
||||
"default_price": None,
|
||||
"price": 23.0
|
||||
}
|
||||
]
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
new_item = Item.objects.get(pk=resp.data['id'])
|
||||
assert new_item.variations.first().value.localize('de') == "Kommentar"
|
||||
assert new_item.variations.first().value.localize('en') == "Comment"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_item_create_with_addon(token_client, organizer, event, item, category, category2, taxrule):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"category": category.pk,
|
||||
"name": {
|
||||
"en": "Ticket"
|
||||
},
|
||||
"active": True,
|
||||
"description": None,
|
||||
"default_price": "23.00",
|
||||
"free_price": False,
|
||||
"tax_rate": "19.00",
|
||||
"tax_rule": taxrule.pk,
|
||||
"admission": True,
|
||||
"position": 0,
|
||||
"picture": None,
|
||||
"available_from": None,
|
||||
"available_until": None,
|
||||
"require_voucher": False,
|
||||
"hide_without_voucher": False,
|
||||
"allow_cancel": True,
|
||||
"min_per_order": None,
|
||||
"max_per_order": None,
|
||||
"checkin_attention": False,
|
||||
"has_variations": True,
|
||||
"addons": [
|
||||
{
|
||||
"addon_category": category.pk,
|
||||
"min_count": 0,
|
||||
"max_count": 10,
|
||||
"position": 0,
|
||||
"price_included": True
|
||||
}
|
||||
]
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
item = Item.objects.get(pk=resp.data['id'])
|
||||
assert item.addons.first().addon_category == category
|
||||
assert item.addons.first().max_count == 10
|
||||
assert 2 == Item.objects.all().count()
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"category": category.pk,
|
||||
"name": {
|
||||
"en": "Ticket"
|
||||
},
|
||||
"active": True,
|
||||
"description": None,
|
||||
"default_price": "23.00",
|
||||
"free_price": False,
|
||||
"tax_rate": "19.00",
|
||||
"tax_rule": taxrule.pk,
|
||||
"admission": True,
|
||||
"position": 0,
|
||||
"picture": None,
|
||||
"available_from": None,
|
||||
"available_until": None,
|
||||
"require_voucher": False,
|
||||
"hide_without_voucher": False,
|
||||
"allow_cancel": True,
|
||||
"min_per_order": None,
|
||||
"max_per_order": None,
|
||||
"checkin_attention": False,
|
||||
"has_variations": True,
|
||||
"addons": [
|
||||
{
|
||||
"addon_category": category2.pk,
|
||||
"min_count": 0,
|
||||
"max_count": 10,
|
||||
"position": 0,
|
||||
"price_included": True
|
||||
}
|
||||
]
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"addons":["The add-on\'s category must belong to the same event as the item."]}'
|
||||
assert 2 == Item.objects.all().count()
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"category": category.pk,
|
||||
"name": {
|
||||
"en": "Ticket"
|
||||
},
|
||||
"active": True,
|
||||
"description": None,
|
||||
"default_price": "23.00",
|
||||
"free_price": False,
|
||||
"tax_rate": "19.00",
|
||||
"tax_rule": taxrule.pk,
|
||||
"admission": True,
|
||||
"position": 0,
|
||||
"picture": None,
|
||||
"available_from": None,
|
||||
"available_until": None,
|
||||
"require_voucher": False,
|
||||
"hide_without_voucher": False,
|
||||
"allow_cancel": True,
|
||||
"min_per_order": None,
|
||||
"max_per_order": None,
|
||||
"checkin_attention": False,
|
||||
"has_variations": True,
|
||||
"addons": [
|
||||
{
|
||||
"addon_category": category.pk,
|
||||
"min_count": 110,
|
||||
"max_count": 10,
|
||||
"position": 0,
|
||||
"price_included": True
|
||||
}
|
||||
]
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"addons":["The maximum count needs to be greater than the minimum count."]}'
|
||||
assert 2 == Item.objects.all().count()
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
"category": category.pk,
|
||||
"name": {
|
||||
"en": "Ticket"
|
||||
},
|
||||
"active": True,
|
||||
"description": None,
|
||||
"default_price": "23.00",
|
||||
"free_price": False,
|
||||
"tax_rate": "19.00",
|
||||
"tax_rule": taxrule.pk,
|
||||
"admission": True,
|
||||
"position": 0,
|
||||
"picture": None,
|
||||
"available_from": None,
|
||||
"available_until": None,
|
||||
"require_voucher": False,
|
||||
"hide_without_voucher": False,
|
||||
"allow_cancel": True,
|
||||
"min_per_order": None,
|
||||
"max_per_order": None,
|
||||
"checkin_attention": False,
|
||||
"has_variations": True,
|
||||
"addons": [
|
||||
{
|
||||
"addon_category": category.pk,
|
||||
"min_count": -1,
|
||||
"max_count": 10,
|
||||
"position": 0,
|
||||
"price_included": True
|
||||
}
|
||||
]
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"addons":["The minimum count needs to be equal to or greater than zero."]}'
|
||||
assert 2 == Item.objects.all().count()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_item_update(token_client, organizer, event, item, category2, taxrule2):
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"min_per_order": 10,
|
||||
"max_per_order": 2
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["The maximum number per order can not be lower than the ' \
|
||||
'minimum number per order."]}'
|
||||
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"available_from": "2017-12-30T12:00",
|
||||
"available_until": "2017-12-29T12:00"
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["The item\'s availability cannot end before it starts."]}'
|
||||
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"category": category2.pk
|
||||
},
|
||||
format='json'
|
||||
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"category":["The item\'s category must belong to the same event as the item."]}'
|
||||
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"tax_rule": taxrule2.pk
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"tax_rule":["The item\'s tax rule must belong to the same event as the item."]}'
|
||||
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"addons": [
|
||||
{
|
||||
"addon_category": 1,
|
||||
"min_count": 0,
|
||||
"max_count": 10,
|
||||
"position": 0,
|
||||
"price_included": True
|
||||
}
|
||||
]
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"addons":["Updating add-ons via PATCH/PUT is not supported. Please use ' \
|
||||
'the dedicated nested endpoint."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_item_update_with_variation(token_client, organizer, event, item):
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"variations": [
|
||||
{
|
||||
"value": {
|
||||
"de": "Kommentar",
|
||||
"en": "Comment"
|
||||
},
|
||||
"active": True,
|
||||
"description": None,
|
||||
"position": 0,
|
||||
"default_price": None,
|
||||
"price": 23.0
|
||||
}
|
||||
]
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"variations":["Updating variations via PATCH/PUT is not supported. Please use ' \
|
||||
'the dedicated nested endpoint."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_item_update_with_addon(token_client, organizer, event, item, category):
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"addons": [
|
||||
{
|
||||
"addon_category": category.pk,
|
||||
"min_count": 0,
|
||||
"max_count": 10,
|
||||
"position": 0,
|
||||
"price_included": True
|
||||
}
|
||||
]
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"addons":["Updating add-ons via PATCH/PUT is not supported. Please use ' \
|
||||
'the dedicated nested endpoint."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_items_delete(token_client, organizer, event, item):
|
||||
resp = token_client.delete('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk))
|
||||
assert resp.status_code == 204
|
||||
assert not event.items.filter(pk=item.id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_items_with_order_position_not_delete(token_client, organizer, event, item, order_position):
|
||||
resp = token_client.delete('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk))
|
||||
assert resp.status_code == 403
|
||||
assert event.items.filter(pk=item.id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_items_with_cart_position_not_delete(token_client, organizer, event, item, cart_position):
|
||||
resp = token_client.delete('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk))
|
||||
assert resp.status_code == 403
|
||||
assert event.items.filter(pk=item.id).exists()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -227,6 +664,304 @@ def variations2(item2):
|
||||
return v
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def variation(item):
|
||||
return item.variations.create(value="ChildC1")
|
||||
|
||||
|
||||
TEST_VARIATIONS_RES = {
|
||||
"value": {
|
||||
"en": "ChildC1"
|
||||
},
|
||||
"active": True,
|
||||
"description": None,
|
||||
"position": 0,
|
||||
"default_price": None,
|
||||
"price": 23.0
|
||||
}
|
||||
|
||||
TEST_VARIATIONS_UPDATE = {
|
||||
"value": {
|
||||
"en": "ChildC2"
|
||||
},
|
||||
"active": True,
|
||||
"description": None,
|
||||
"position": 1,
|
||||
"default_price": None,
|
||||
"price": 23.0
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_variations_list(token_client, organizer, event, item, variation):
|
||||
res = dict(TEST_VARIATIONS_RES)
|
||||
res["id"] = variation.pk
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/variations/'.format(organizer.slug, event.slug, item.pk))
|
||||
assert resp.status_code == 200
|
||||
assert res['value'] == resp.data['results'][0]['value']
|
||||
assert res['position'] == resp.data['results'][0]['position']
|
||||
assert res['price'] == resp.data['results'][0]['price']
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_variations_detail(token_client, organizer, event, item, variation):
|
||||
res = dict(TEST_VARIATIONS_RES)
|
||||
res["id"] = variation.pk
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(organizer.slug, event.slug, item.pk, variation.pk))
|
||||
assert resp.status_code == 200
|
||||
assert res == resp.data
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_variations_create(token_client, organizer, event, item, variation):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/variations/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"value": {
|
||||
"en": "ChildC2"
|
||||
},
|
||||
"active": True,
|
||||
"description": None,
|
||||
"position": 1,
|
||||
"default_price": None,
|
||||
"price": 23.0
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
var = ItemVariation.objects.get(pk=resp.data['id'])
|
||||
assert var.position == 1
|
||||
assert var.price == 23.0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_variations_create_not_allowed(token_client, organizer, event, item):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/variations/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"value": {
|
||||
"en": "ChildC2"
|
||||
},
|
||||
"active": True,
|
||||
"description": None,
|
||||
"position": 1,
|
||||
"default_price": None,
|
||||
"price": 23.0
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 403
|
||||
assert resp.content.decode() == '{"detail":"This variation cannot be created because the item does ' \
|
||||
'not have variations. Changing a product without variations to a product with ' \
|
||||
'variations is not allowed."}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_variations_update(token_client, organizer, event, item, item3, variation):
|
||||
res = dict(TEST_VARIATIONS_UPDATE)
|
||||
res["id"] = variation.pk
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(organizer.slug, event.slug, item.pk, variation.pk),
|
||||
{
|
||||
"value": {
|
||||
"en": "ChildC2"
|
||||
},
|
||||
"position": 1
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert res == resp.data
|
||||
|
||||
# Variation exists but do not belong to item
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(organizer.slug, event.slug, item3.pk, variation.pk),
|
||||
{
|
||||
"position": 1
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_variations_delete(token_client, organizer, event, item, variations, order):
|
||||
resp = token_client.delete('/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(organizer.slug, event.slug, item.pk, variations[0].pk))
|
||||
assert resp.status_code == 204
|
||||
assert not item.variations.filter(pk=variations[0].pk).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_variations_with_order_position_not_delete(token_client, organizer, event, item, order, variations, order_position):
|
||||
assert item.variations.filter(pk=variations[0].id).exists()
|
||||
resp = token_client.delete('/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(organizer.slug, event.slug, item.pk, variations[0].pk))
|
||||
assert resp.status_code == 403
|
||||
assert resp.content.decode() == '{"detail":"This variation cannot be deleted because it has already been ordered ' \
|
||||
'by a user or currently is in a users\'s cart. Please set the variation as ' \
|
||||
'\'inactive\' instead."}'
|
||||
assert item.variations.filter(pk=variations[0].id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_variations_with_cart_position_not_delete(token_client, organizer, event, item, variations, cart_position):
|
||||
assert item.variations.filter(pk=variations[0].id).exists()
|
||||
resp = token_client.delete('/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(organizer.slug, event.slug, item.pk, variations[0].pk))
|
||||
assert resp.status_code == 403
|
||||
assert resp.content.decode() == '{"detail":"This variation cannot be deleted because it has already been ordered ' \
|
||||
'by a user or currently is in a users\'s cart. Please set the variation as ' \
|
||||
'\'inactive\' instead."}'
|
||||
assert item.variations.filter(pk=variations[0].id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_only_variation_not_delete(token_client, organizer, event, item, variation):
|
||||
assert item.variations.filter(pk=variation.id).exists()
|
||||
resp = token_client.delete('/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(organizer.slug, event.slug, item.pk, variation.pk))
|
||||
assert resp.status_code == 403
|
||||
assert resp.content.decode() == '{"detail":"This variation cannot be deleted because it is the only variation. ' \
|
||||
'Changing a product with variations to a product without variations is not ' \
|
||||
'allowed."}'
|
||||
assert item.variations.filter(pk=variation.id).exists()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def addon(item, category):
|
||||
return item.addons.create(addon_category=category, min_count=0, max_count=10, position=1)
|
||||
|
||||
|
||||
TEST_ADDONS_RES = {
|
||||
"min_count": 0,
|
||||
"max_count": 10,
|
||||
"position": 1,
|
||||
"price_included": False
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_addons_list(token_client, organizer, event, item, addon, category):
|
||||
res = dict(TEST_ADDONS_RES)
|
||||
res["id"] = addon.pk
|
||||
res["addon_category"] = category.pk
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/addons/'.format(organizer.slug, event.slug,
|
||||
item.pk))
|
||||
assert resp.status_code == 200
|
||||
assert res['addon_category'] == resp.data['results'][0]['addon_category']
|
||||
assert res['min_count'] == resp.data['results'][0]['min_count']
|
||||
assert res['max_count'] == resp.data['results'][0]['max_count']
|
||||
assert res['position'] == resp.data['results'][0]['position']
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_addons_detail(token_client, organizer, event, item, addon, category):
|
||||
res = dict(TEST_ADDONS_RES)
|
||||
res["id"] = addon.pk
|
||||
res["addon_category"] = category.pk
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/addons/{}/'.format(organizer.slug, event.slug,
|
||||
item.pk, addon.pk))
|
||||
assert resp.status_code == 200
|
||||
assert res == resp.data
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_addons_create(token_client, organizer, event, item, category, category2):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/addons/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"addon_category": category.pk,
|
||||
"min_count": 0,
|
||||
"max_count": 10,
|
||||
"position": 1,
|
||||
"price_included": False
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
addon = ItemAddOn.objects.get(pk=resp.data['id'])
|
||||
assert addon.position == 1
|
||||
assert addon.addon_category == category
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/addons/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"addon_category": category.pk,
|
||||
"min_count": 10,
|
||||
"max_count": 20,
|
||||
"position": 2,
|
||||
"price_included": False
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"addon_category":["The item already has an add-on of this category."]}'
|
||||
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/addons/'.format(organizer.slug, event.slug, item.pk),
|
||||
{
|
||||
"addon_category": category2.pk,
|
||||
"min_count": 10,
|
||||
"max_count": 20,
|
||||
"position": 2,
|
||||
"price_included": False
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"addon_category":["The add-on\'s category must belong to the same event as ' \
|
||||
'the item."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_addons_update(token_client, organizer, event, item, addon):
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/addons/{}/'.format(organizer.slug, event.slug, item.pk, addon.pk),
|
||||
{
|
||||
"min_count": 100,
|
||||
"max_count": 101
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
a = ItemAddOn.objects.get(pk=addon.pk)
|
||||
assert a.min_count == 100
|
||||
assert a.max_count == 101
|
||||
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/addons/{}/'.format(organizer.slug, event.slug, item.pk, a.pk),
|
||||
{
|
||||
"min_count": 10,
|
||||
"max_count": 1
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.content.decode() == '{"non_field_errors":["The maximum count needs to be greater than the minimum ' \
|
||||
'count."]}'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_addons_delete(token_client, organizer, event, item, addon):
|
||||
resp = token_client.delete('/api/v1/organizers/{}/events/{}/items/{}/addons/{}/'.format(organizer.slug, event.slug,
|
||||
item.pk, addon.pk))
|
||||
assert resp.status_code == 204
|
||||
assert not item.addons.filter(pk=addon.id).exists()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def quota(event, item):
|
||||
q = event.quotas.create(name="Budget Quota", size=200)
|
||||
q.items.add(item)
|
||||
return q
|
||||
|
||||
|
||||
TEST_QUOTA_RES = {
|
||||
"name": "Budget Quota",
|
||||
"size": 200,
|
||||
"items": [],
|
||||
"variations": [],
|
||||
"subevent": None
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_quota_list(token_client, organizer, event, quota, item, subevent):
|
||||
res = dict(TEST_QUOTA_RES)
|
||||
@@ -425,6 +1160,13 @@ def test_quota_update(token_client, organizer, event, quota, item):
|
||||
assert quota.size == 111
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_quota_delete(token_client, organizer, event, quota):
|
||||
resp = token_client.delete('/api/v1/organizers/{}/events/{}/quotas/{}/'.format(organizer.slug, event.slug, quota.pk))
|
||||
assert resp.status_code == 204
|
||||
assert not event.quotas.filter(pk=quota.id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_quota_availability(token_client, organizer, event, quota, item):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/quotas/{}/availability/'.format(
|
||||
|
||||
@@ -30,6 +30,18 @@ event_permission_urls = [
|
||||
('get', 'can_change_items', 'items/', 200),
|
||||
('get', 'can_change_items', 'questions/', 200),
|
||||
('get', 'can_change_items', 'quotas/', 200),
|
||||
('post', 'can_change_items', 'items/', 400),
|
||||
('put', 'can_change_items', 'items/1/', 404),
|
||||
('patch', 'can_change_items', 'items/1/', 404),
|
||||
('delete', 'can_change_items', 'items/1/', 404),
|
||||
('post', 'can_change_items', 'items/1/variations/', 404),
|
||||
('put', 'can_change_items', 'items/1/variations/1/', 404),
|
||||
('patch', 'can_change_items', 'items/1/variations/1/', 404),
|
||||
('delete', 'can_change_items', 'items/1/variations/1/', 404),
|
||||
('post', 'can_change_items', 'items/1/addons/', 404),
|
||||
('put', 'can_change_items', 'items/1/addons/1/', 404),
|
||||
('patch', 'can_change_items', 'items/1/addons/1/', 404),
|
||||
('delete', 'can_change_items', 'items/1/addons/1/', 404),
|
||||
('post', 'can_change_event_settings', 'taxrules/', 400),
|
||||
('put', 'can_change_event_settings', 'taxrules/1/', 404),
|
||||
('patch', 'can_change_event_settings', 'taxrules/1/', 404),
|
||||
|
||||
Reference in New Issue
Block a user