text: from decimal import Decimal
  
  from django.core.exceptions import ValidationError
  from django.db import transaction
  from django.utils.functional import cached_property
  from django.utils.translation import gettext_lazy as _
  from rest_framework import serializers
  
  from pretix.api.serializers.event import MetaDataField
  from pretix.api.serializers.i18n import I18nAwareModelSerializer
  from pretix.base.models import (
      Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue, ItemVariation,
      Question, QuestionOption, Quota,
  )
  
  
  class InlineItemVariationSerializer(I18nAwareModelSerializer):
      price = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=10,
                                       coerce_to_string=True)
  
      class Meta:
          model = ItemVariation
          fields = ('id', 'value', 'active', 'description',
                    'position', 'default_price', 'price', 'original_price')
  
  
  class ItemVariationSerializer(I18nAwareModelSerializer):
      price = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=10,
                                       coerce_to_string=True)
  
      class Meta:
          model = ItemVariation
          fields = ('id', 'value', 'active', 'description',
                    'position', 'default_price', 'price', 'original_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
          fields = ('addon_category', 'min_count', 'max_count',
                    'position', 'price_included', 'multi_allowed')
  
  
  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
          fields = ('id', 'addon_category', 'min_count', 'max_count',
                    'position', 'price_included', 'multi_allowed')
  
      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):
      def to_representation(self, i):
          if i.tax_rule:
              return str(Decimal(i.tax_rule.rate))
          else:
              return str(Decimal('0.00'))
  
  
  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)
      meta_data = MetaDataField(required=False, source='*')
  
      class Meta:
          model = Item
          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', 'require_bundling',
                    'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations',
                    'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
                    'show_quota_left', 'hidden_if_available', 'allow_waitinglist', 'issue_giftcard', 'meta_data')
          read_only_fields = ('has_variations', 'picture')
  
      def validate(self, data):
          data = super().validate(data)
          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'))
          Item.clean_available(data.get('available_from'), data.get('available_until'))
  
          if data.get('issue_giftcard'):
              if data.get('tax_rule') and data.get('tax_rule').rate > 0:
                  raise ValidationError(
                      _("Gift card products should not be associated with non-zero tax rates since sales tax will be "
                        "applied when the gift card is redeemed.")
                  )
              if data.get('admission'):
                  raise ValidationError(_(
                      "Gift card products should not be admission products at the same time."
                  ))
  
          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_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:
                  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
  
      @cached_property
      def item_meta_properties(self):
          return {
              p.name: p for p in self.context['request'].event.item_meta_properties.all()
          }
  
      def validate_meta_data(self, value):
          for key in value['meta_data'].keys():
              if key not in self.item_meta_properties:
                  raise ValidationError(_('Item meta data property \'{name}\' does not exist.').format(name=key))
          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 {}
          bundles_data = validated_data.pop('bundles') if 'bundles' in validated_data else {}
          meta_data = validated_data.pop('meta_data', None)
          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)
  
          # Meta data
          if meta_data is not None:
              for key, value in meta_data.items():
                  ItemMetaValue.objects.create(
                      property=self.item_meta_properties.get(key),
                      value=value,
                      item=item
                  )
          return item
  
      def update(self, instance, validated_data):
          meta_data = validated_data.pop('meta_data', None)
          item = super().update(instance, validated_data)
  
          # Meta data
          if meta_data is not None:
              current = {mv.property: mv for mv in item.meta_values.select_related('property')}
              for key, value in meta_data.items():
                  prop = self.item_meta_properties.get(key)
                  if prop in current:
                      current[prop].value = value
                      current[prop].save()
                  else:
                      item.meta_values.create(
                          property=self.item_meta_properties.get(key),
                          value=value
                      )
  
              for prop, current_object in current.items():
                  if prop.name not in meta_data:
                      current_object.delete()
  
          return item
  
  
  class ItemCategorySerializer(I18nAwareModelSerializer):
  
      class Meta:
          model = ItemCategory
          fields = ('id', 'name', 'internal_name', 'description', 'position', 'is_addon')
  
  
  class QuestionOptionSerializer(I18nAwareModelSerializer):
      identifier = serializers.CharField(allow_null=True)
  
      class Meta:
          model = QuestionOption
          fields = ('id', 'identifier', 'answer', 'position')
  
      def validate_identifier(self, value):
          QuestionOption.clean_identifier(self.context['event'], value, self.instance)
          return value
  
  
  class InlineQuestionOptionSerializer(I18nAwareModelSerializer):
      identifier = serializers.CharField(allow_null=True)
  
      class Meta:
          model = QuestionOption
          fields = ('id', 'identifier', 'answer', 'position')
  
  
  class LegacyDependencyValueField(serializers.CharField):
  
      def to_representation(self, obj):
          return obj[0] if obj else None
  
      def to_internal_value(self, data):
          return [data] if data else []
  
  
  class QuestionSerializer(I18nAwareModelSerializer):
      options = InlineQuestionOptionSerializer(many=True, required=False)
      identifier = serializers.CharField(allow_null=True)
      dependency_value = LegacyDependencyValueField(source='dependency_values', required=False, allow_null=True)
  
      class Meta:
          model = Question
          fields = ('id', 'question', 'type', 'required', 'items', 'options', 'position',
                    'ask_during_checkin', 'identifier', 'dependency_question', 'dependency_values',
                    'hidden', 'dependency_value', 'print_on_invoice', 'help_text', 'valid_number_min',
                    'valid_number_max', 'valid_date_min', 'valid_date_max', 'valid_datetime_min', 'valid_datetime_max'
                    )
  
      def validate_identifier(self, value):
          Question._clean_identifier(self.context['event'], value, self.instance)
          return value
  
      def validate_dependency_question(self, value):
          if value:
              if value.type not in (Question.TYPE_CHOICE, Question.TYPE_BOOLEAN, Question.TYPE_CHOICE_MULTIPLE):
                  raise ValidationError('Question dependencies can only be set to boolean or choice questions.')
              if value == self.instance:
                  raise ValidationError('A question cannot depend on itself.')
          return value
  
      def validate(self, data):
          data = super().validate(data)
          if self.instance and 'options' in data:
              raise ValidationError(_('Updating options via PATCH/PUT is not supported. Please use the dedicated'
                                      ' nested endpoint.'))
  
          event = self.context['event']
  
          full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
          full_data.update(data)
  
          if full_data.get('ask_during_checkin') and full_data.get('dependency_question'):
              raise ValidationError('Dependencies are not supported during check-in.')
  
          dep = full_data.get('dependency_question')
          if dep:
              if dep.ask_during_checkin:
                  raise ValidationError(_('Question cannot depend on a question asked during check-in.'))
  
              seen_ids = {self.instance.pk} if self.instance else set()
              while dep:
                  if dep.pk in seen_ids:
                      raise ValidationError(_('Circular dependency between questions detected.'))
                  seen_ids.add(dep.pk)
                  dep = dep.dependency_question
  
          if full_data.get('ask_during_checkin') and full_data.get('type') in Question.ASK_DURING_CHECKIN_UNSUPPORTED:
              raise ValidationError(_('This type of question cannot be asked during check-in.'))
  
          Question.clean_items(event, full_data.get('items'))
          return data
  
      def validate_options(self, value):
          if not self.instance:
              known = []
              for opt_data in value:
                  if opt_data.get('identifier'):
                      QuestionOption.clean_identifier(self.context['event'], opt_data.get('identifier'), self.instance,
                                                      known)
                      known.append(opt_data.get('identifier'))
          return value
  
      @transaction.atomic
      def create(self, validated_data):
          options_data = validated_data.pop('options') if 'options' in validated_data else []
          items = validated_data.pop('items')
  
          question = Question.objects.create(**validated_data)
          question.items.set(items)
          for opt_data in options_data:
              QuestionOption.objects.create(question=question, **opt_data)
          return question
  
  
  class QuotaSerializer(I18nAwareModelSerializer):
  
      class Meta:
          model = Quota
          fields = ('id', 'name', 'size', 'items', 'variations', 'subevent', 'closed', 'close_when_sold_out', 'release_after_exit')
  
      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)
  
          Quota.clean_variations(full_data.get('items'), full_data.get('variations'))
          Quota.clean_items(event, full_data.get('items'), full_data.get('variations'))
          Quota.clean_subevent(event, full_data.get('subevent'))
  
          return data
  