Fix #1001 -- Add product bundles (#1041)

* Data model + Editor

* Cart and order management

* Rebase migrations

* Fix typos, add tests on cart handling

* Add tests for checkout and quotas

* Add API endpoints

* Validation of settings

* Front page tax display

* Voucher handling

* Widget foo

* Show correct net pricing

* Front page tests

* reverse charge foo

* Allow to require bundling

* Fix test failure on postgres
This commit is contained in:
Raphael Michel
2019-03-22 14:48:48 +00:00
committed by GitHub
parent c4b18a4c81
commit 90f881c48e
34 changed files with 2747 additions and 153 deletions

View File

@@ -7,8 +7,8 @@ from rest_framework import serializers
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.models import (
Item, ItemAddOn, ItemCategory, ItemVariation, Question, QuestionOption,
Quota,
Item, ItemAddOn, ItemBundle, ItemCategory, ItemVariation, Question,
QuestionOption, Quota,
)
@@ -26,6 +26,13 @@ class ItemVariationSerializer(I18nAwareModelSerializer):
'position', 'default_price', 'price')
class InlineItemBundleSerializer(serializers.ModelSerializer):
class Meta:
model = ItemBundle
fields = ('bundled_item', 'bundled_variation', 'count',
'designated_price')
class InlineItemAddOnSerializer(serializers.ModelSerializer):
class Meta:
model = ItemAddOn
@@ -33,6 +40,31 @@ class InlineItemAddOnSerializer(serializers.ModelSerializer):
'position', 'price_included')
class ItemBundleSerializer(serializers.ModelSerializer):
class Meta:
model = ItemBundle
fields = ('id', 'bundled_item', 'bundled_variation', 'count',
'designated_price')
def validate(self, data):
data = super().validate(data)
event = self.context['event']
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
full_data.update(data)
ItemBundle.clean_itemvar(event, full_data.get('bundled_item'), full_data.get('bundled_variation'))
item = self.context['item']
if item == full_data.get('bundled_item'):
raise ValidationError(_("The bundled item must not be the same item as the bundling one."))
if full_data.get('bundled_item'):
if full_data['bundled_item'].bundles.exists():
raise ValidationError(_("The bundled item must not have bundles on its own."))
return data
class ItemAddOnSerializer(serializers.ModelSerializer):
class Meta:
model = ItemAddOn
@@ -69,6 +101,7 @@ class ItemTaxRateField(serializers.Field):
class ItemSerializer(I18nAwareModelSerializer):
addons = InlineItemAddOnSerializer(many=True, required=False)
bundles = InlineItemBundleSerializer(many=True, required=False)
variations = InlineItemVariationSerializer(many=True, required=False)
tax_rate = ItemTaxRateField(source='*', read_only=True)
@@ -77,9 +110,9 @@ class ItemSerializer(I18nAwareModelSerializer):
fields = ('id', 'category', 'name', 'internal_name', 'active', 'sales_channels', 'description',
'default_price', 'free_price', 'tax_rate', 'tax_rule', 'admission',
'position', 'picture', 'available_from', 'available_until',
'require_voucher', 'hide_without_voucher', 'allow_cancel',
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations',
'variations', 'addons', 'original_price', 'require_approval', 'generate_tickets')
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations',
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets')
read_only_fields = ('has_variations', 'picture')
def get_serializer_context(self):
@@ -87,8 +120,8 @@ class ItemSerializer(I18nAwareModelSerializer):
def validate(self, data):
data = super().validate(data)
if self.instance and ('addons' in data or 'variations' in data):
raise ValidationError(_('Updating add-ons or variations via PATCH/PUT is not supported. Please use the '
if self.instance and ('addons' in data or 'variations' in data or 'bundles' in data):
raise ValidationError(_('Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use the '
'dedicated nested endpoint.'))
Item.clean_per_order(data.get('min_per_order'), data.get('max_per_order'))
@@ -104,6 +137,12 @@ class ItemSerializer(I18nAwareModelSerializer):
Item.clean_tax_rule(value, self.context['event'])
return value
def validate_bundles(self, value):
if not self.instance:
for b_data in value:
ItemBundle.clean_itemvar(self.context['event'], b_data['bundled_item'], b_data['bundled_variation'])
return value
def validate_addons(self, value):
if not self.instance:
for addon_data in value:
@@ -117,11 +156,14 @@ class ItemSerializer(I18nAwareModelSerializer):
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 {}
bundles_data = validated_data.pop('bundles') if 'bundles' 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)
for bundle_data in bundles_data:
ItemBundle.objects.create(base_item=item, **bundle_data)
return item