def update(self, instance, validated_data): configurable_options = validated_data.pop('configurable_options', None) with transaction.atomic(): # removing name and description from validated data since they should not be updated del validated_data['name'], validated_data['description'] product = validated_data['product'] component = PluginUtils.get_enduser_component( plugin_label=product.module.plugin_label, component_name='OrderProduct', ) if component: plugin_data = validated_data['plugin_data'] serializer = component.create_serializer( plugin_data=plugin_data, context=self.context) if serializer and serializer.is_valid(raise_exception=True): # update data just in case it was changed during validation process validated_data['plugin_data'] = dict( serializer.validated_data) # TODO: this is a temporary solution, we should abstract this so it would not depend on domains if product.module.plugin_label == 'domains': validated_data['name'] = plugin_data['name'] order_item = super().update( instance=instance, validated_data=validated_data) # type: OrderItem order_item.configurable_options.all().delete() for config_option in configurable_options: # Filter out all configurable options no longer valid if not config_option['option'].has_cycle( cycle=order_item.cycle.cycle, cycle_multiplier=order_item.cycle.cycle_multiplier, choice_value=config_option.get('option_value'), currency=order_item.currency.code, ): continue if config_option['option'].widget == 'yesno': if config_option['option_value'] != 'yes': # Ignore unchecked checkboxes continue choice_value = None has_price = True if config_option['option'].widget == 'text_in': has_price = False quantity = config_option.get('quantity') if config_option['option'].has_choices: choice_value = config_option['option_value'] unit_price, price, setupfee = config_option[ 'option'].get_price_by_cycle_quantity_and_choice( cycle_name=order_item.cycle.cycle, cycle_multiplier=order_item.cycle.cycle_multiplier, currency=order_item.currency, quantity=config_option['quantity'], choice_value=choice_value, option_value=config_option['option_value'], ) order_item.configurable_options.create( option=config_option['option'], option_value=config_option['option_value'], quantity=quantity, has_price=has_price, taxable=validated_data['taxable'], unit_price=unit_price, price=price, setup_fee=setupfee) # Update order item taxes order_item.taxes.all().delete() self.create_taxes(order_item=order_item) return order_item
def validate(self, attrs): cart = attrs['cart'] product = attrs.get('product') cycle = attrs.get('cycle') attrs['name'] = product.name attrs[ 'description'] = product.description or product.name or 'Product' # NOTE(tomo): description is required if not cycle and product.price_model != PricingModel.free: raise serializers.ValidationError( {'cycle': _('A billing cycle is required')}) if cycle: try: product.cycles.available_for_order(currency=cart.currency).get( pk=cycle.pk) except (ProductCycle.DoesNotExist, ProductCycle.MultipleObjectsReturned): LOG.debug( 'Tried to add a product to cart with an invalid currency.' ' Cart currency {}; Cycle currency: {}'.format( cart.currency, cycle.currency)) raise serializers.ValidationError( detail=_('Product not available')) attrs['cycle_display'] = cycle.display_name # update pricing information here self.update_pricing_from_product(attrs=attrs) # validate configurable options are valid and public and all required are present configurable_options = attrs.get('configurable_options') if cycle: req_filter = dict(cycle=cycle.cycle, cycle_multiplier=cycle.cycle_multiplier, currency=cycle.currency, required=True) required_options = product.configurable_options.public( ).with_cycles(**req_filter) for req_opt in required_options: found = False for sent_opt in configurable_options: if req_opt.pk == sent_opt['option'].pk: found = True if not found: raise serializers.ValidationError( detail=_('Value is required for {}').format( req_opt.description)) for conf_opt in configurable_options: db_opt = conf_opt.get('option') if not db_opt: raise serializers.ValidationError( detail=_('Invalid option selected')) if db_opt.status != ConfigurableOptionStatus.public: raise serializers.ValidationError( detail=_('Invalid option selected')) if not product.configurable_options.filter(id=db_opt.id).exists(): raise serializers.ValidationError( detail=_('Invalid option selected')) # validate plugin data plugin_label = product.module.plugin_label component = PluginUtils.get_enduser_component( plugin_label=plugin_label, component_name='OrderProduct', ) if component: plugin_data = attrs['plugin_data'] serializer = component.create_serializer(plugin_data=plugin_data, context=self.context) if serializer: try: serializer.is_valid(raise_exception=True) except ValidationError as e: raise ValidationError(detail={'plugin_data': e.detail}) return super().validate(attrs)