class AddToCartSerializer(serializers.Serializer): """ Serialize fields used in the "Add to Cart" dialog box. """ quantity = serializers.IntegerField(default=1, min_value=1) unit_price = MoneyField(read_only=True) subtotal = MoneyField(read_only=True) product = serializers.IntegerField(read_only=True, help_text="The product's primary key") extra = serializers.DictField(read_only=True) def __init__(self, instance=None, data=empty, **kwargs): context = kwargs.get('context', {}) if 'product' in context: instance = self.get_instance(context, data, kwargs) if data == empty: quantity = self.fields['quantity'].default else: quantity = self.fields['quantity'].to_internal_value(data['quantity']) instance.setdefault('quantity', quantity) instance.setdefault('subtotal', instance['quantity'] * instance['unit_price']) super(AddToCartSerializer, self).__init__(instance, data, context=context) else: super(AddToCartSerializer, self).__init__(instance, data, **kwargs) def get_instance(self, context, data, extra_args): product = context['product'] return { 'product': product.id, 'unit_price': product.get_price(context['request']), }
class BaseItemSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(lookup_field='pk', view_name='shop:cart-detail') unit_price = MoneyField() line_total = MoneyField() summary = serializers.SerializerMethodField( help_text="Sub-serializer for fields to be shown in the product's summary.") extra_rows = ExtraCartRowList(read_only=True) class Meta: model = CartItemModel def create(self, validated_data): assert 'cart' in validated_data cart_item, _ = CartItemModel.objects.get_or_create(**validated_data) cart_item.save() return cart_item def to_representation(self, cart_item): cart_item.update(self.context['request']) representation = super(BaseItemSerializer, self).to_representation(cart_item) return representation def validate_product(self, product): if not product.active: msg = "Product `{}` is inactive, and can not be added to the cart." raise serializers.ValidationError(msg.format(product)) return product def get_summary(self, cart_item): serializer_class = app_settings.PRODUCT_SUMMARY_SERIALIZER serializer = serializer_class(cart_item.product, context=self.context, read_only=True, label=self.root.label) return serializer.data
class OrderListSerializer(serializers.ModelSerializer): number = serializers.CharField( source='get_number', read_only=True, ) url = serializers.URLField( source='get_absolute_url', read_only=True, ) status = serializers.CharField( source='status_name', read_only=True, ) subtotal = MoneyField() total = MoneyField() class Meta: model = OrderModel fields = [ 'number', 'url', 'created_at', 'updated_at', 'subtotal', 'total', 'status', 'shipping_address_text', 'billing_address_text' ]
class BaseCartSerializer(serializers.ModelSerializer): subtotal = MoneyField() total = MoneyField() extra_rows = ExtraCartRowList(read_only=True) class Meta: model = CartModel
class BaseOrderItemSerializer(serializers.ModelSerializer): line_total = MoneyField() unit_price = MoneyField() product_code = serializers.CharField() class Meta: model = OrderItemModel
class AddToCartSerializer(serializers.Serializer): """ By default, this serializer is used by the view class :class:`shop.views.catalog.AddToCartView`, which handles the communication from the "Add to Cart" dialog box. If a product has variations, which influence the fields in the "Add to Cart" dialog box, then this serializer shall be overridden by a customized implementation. Such a customized "*Add to Cart*" serializer has to be connected to the ``AddToCartView``. This usually is achieved in the projects ``urls.py`` by changing the catalog's routing to: ``` urlpatterns = [ ... url(r'^(?P<slug>[\w-]+)/add-to-cart', AddToCartView.as_view( serializer_class=CustomAddToCartSerializer, )), ... ] ``` """ quantity = serializers.IntegerField(default=1, min_value=1) unit_price = MoneyField(read_only=True) subtotal = MoneyField(read_only=True) product = serializers.IntegerField(read_only=True, help_text="The product's primary key") product_code = serializers.CharField(read_only=True, help_text="Exact product code of the cart item") extra = serializers.DictField(read_only=True, default={}) is_in_cart = serializers.BooleanField(read_only=True, default=False) def __init__(self, instance=None, data=empty, **kwargs): context = kwargs.get('context', {}) if 'product' in context: instance = self.get_instance(context, data, kwargs) if data == empty: quantity = self.fields['quantity'].default else: quantity = self.fields['quantity'].to_internal_value(data['quantity']) instance.setdefault('quantity', quantity) instance.setdefault('subtotal', instance['quantity'] * instance['unit_price']) super(AddToCartSerializer, self).__init__(instance, data, context=context) else: super(AddToCartSerializer, self).__init__(instance, data, **kwargs) def get_instance(self, context, data, extra_args): """ Method to store the ordered products in the cart item instance. Remember to override this method, if the ``product_code`` is part of the variation rather than being part of the product itself. """ product = context['product'] try: cart = CartModel.objects.get_from_request(context['request']) except CartModel.DoesNotExist: cart = None extra = data.get('extra', {}) if data is not empty else {} return { 'product': product.id, 'product_code': product.product_code, 'unit_price': product.get_price(context['request']), 'is_in_cart': bool(product.is_in_cart(cart)), 'extra': extra, }
class OrderDetailSerializer(OrderListSerializer): items = OrderItemSerializer(many=True, read_only=True) amount_paid = MoneyField(read_only=True) outstanding_amount = MoneyField(read_only=True) is_partially_paid = serializers.SerializerMethodField( method_name='get_partially_paid', help_text="Returns true, if order has been partially paid") def get_partially_paid(self, order): return order.amount_paid > 0
class OrderListSerializer(serializers.ModelSerializer): number = serializers.CharField(source='get_number', read_only=True) customer = CustomerSerializer(read_only=True) url = serializers.URLField(source='get_absolute_url', read_only=True) status = serializers.CharField(source='status_name', read_only=True) subtotal = MoneyField() total = MoneyField() extra = serializers.DictField(read_only=True) class Meta: model = OrderModel exclude = ('id', 'stored_request', '_subtotal', '_total',)
class OrderDetailSerializer(OrderListSerializer): items = app_settings.ORDER_ITEM_SERIALIZER( many=True, read_only=True, ) extra = serializers.DictField(read_only=True) amount_paid = MoneyField(read_only=True) outstanding_amount = MoneyField(read_only=True) cancelable = serializers.BooleanField(read_only=True) is_partially_paid = serializers.SerializerMethodField( method_name='get_partially_paid', help_text="Returns true, if order has been partially paid", ) annotation = serializers.CharField( write_only=True, required=False, ) reorder = serializers.BooleanField( write_only=True, default=False, ) cancel = serializers.BooleanField( write_only=True, default=False, ) class Meta: model = OrderModel exclude = ['id', 'customer', 'stored_request', '_subtotal', '_total'] read_only_fields = ['shipping_address_text', 'billing_address_text'] # TODO: not part of OrderBase def get_partially_paid(self, order): return order.amount_paid > 0 def update(self, order, validated_data): order.extra.setdefault('addenum', []) if validated_data.get('annotation'): timestamp = timezone.now().isoformat() order.extra['addenum'].append((timestamp, validated_data['annotation'])) order.save() if validated_data['reorder'] is True: cart = CartModel.objects.get_from_request(self.context['request']) order.readd_to_cart(cart) if validated_data['cancel'] is True and order.cancelable(): order.cancel_order() order.save() return order
class BaseCartSerializer(serializers.ModelSerializer): subtotal = MoneyField() total = MoneyField() extra_rows = ExtraCartRowList(read_only=True) class Meta: model = CartModel fields = ('subtotal', 'total', 'extra_rows') def to_representation(self, cart): cart.update(self.context['request']) representation = super(BaseCartSerializer, self).to_representation(cart) return representation
class AddToCartSerializer(serializers.Serializer): """ By default, this serializer is used by the view class :class:`shop.views.catalog.AddToCartView`, which handles the communication from the "Add to Cart" dialog box. This serializer shall be replaced by an alternative implementation, if product variations are used on the same catalog's detail view. """ quantity = serializers.IntegerField(default=1, min_value=1) unit_price = MoneyField(read_only=True) subtotal = MoneyField(read_only=True) product = serializers.IntegerField(read_only=True, help_text="The product's primary key") product_code = serializers.CharField( read_only=True, help_text="Exact product code of the cart item") extra = serializers.DictField(read_only=True, default={}) def __init__(self, instance=None, data=empty, **kwargs): context = kwargs.get('context', {}) if 'product' in context: instance = self.get_instance(context, data, kwargs) if data == empty: quantity = self.fields['quantity'].default else: quantity = self.fields['quantity'].to_internal_value( data['quantity']) instance.setdefault('quantity', quantity) instance.setdefault('subtotal', instance['quantity'] * instance['unit_price']) super(AddToCartSerializer, self).__init__(instance, data, context=context) else: super(AddToCartSerializer, self).__init__(instance, data, **kwargs) def get_instance(self, context, data, extra_args): """ Method to store the ordered products in the cart item instance. Remember to override this method, if the ``product_code`` is part of the variation rather than being part of the product itself. """ product = context['product'] extra = data.get('extra', {}) if data is not empty else {} return { 'product': product.id, 'product_code': product.product_code, 'unit_price': product.get_price(context['request']), 'extra': extra, }
class OrderItemSerializer(serializers.ModelSerializer): line_total = MoneyField() unit_price = MoneyField() summary = serializers.SerializerMethodField( help_text="Sub-serializer for fields to be shown in the product's summary.") class Meta: model = OrderItemModel exclude = ('id',) def get_summary(self, order_item): label = self.context.get('render_label', 'order') serializer = product_summary_serializer_class(order_item.product, context=self.context, read_only=True, label=label) return serializer.data
class BaseItemSerializer(ItemModelSerializer): url = serializers.HyperlinkedIdentityField(lookup_field='pk', view_name='shop:cart-detail') unit_price = MoneyField() line_total = MoneyField() summary = serializers.SerializerMethodField( help_text="Sub-serializer for fields to be shown in the product's summary.") extra_rows = ExtraCartRowList(read_only=True) def validate_product(self, product): if not product.active: msg = "Product `{}` is inactive, and can not be added to the cart." raise serializers.ValidationError(msg.format(product)) return product def get_summary(self, cart_item): serializer = product_summary_serializer_class(cart_item.product, context=self.context, read_only=True, label=self.root.label) return serializer.data
class ExtraCartRow(serializers.Serializer): """ This data structure holds extra information for each item, or for the whole cart, while processing the cart using their modifiers. """ label = serializers.CharField(read_only=True, help_text="A short description of this row in a natural language.") amount = MoneyField(read_only=True, help_text="The price difference, if applied.")
def to_representation(self, instance): data = super(AddToCartSerializer, self).to_representation(instance) try: data['quantity'] = self._validated_data['quantity'] except AttributeError: data['quantity'] = self.validate_quantity(data['quantity']) data['subtotal'] = MoneyField().to_representation( data['quantity'] * instance['unit_price']) return data
class BaseCartSerializer(serializers.ModelSerializer): subtotal = MoneyField() total = MoneyField() extra_rows = ExtraCartRowList(read_only=True) class Meta: model = CartModel fields = ['subtotal', 'total', 'extra_rows'] def to_representation(self, cart): cart.update(self.context['request']) representation = super(BaseCartSerializer, self).to_representation(cart) if self.with_items: items = self.represent_items(cart) representation.update(items=items) return representation def represent_items(self, cart): raise NotImplementedError("{} must implement method `represent_items()`.".format(self.__class__))
class CartIconCaptionSerializer(serializers.ModelSerializer): """ The default serializer used to render the information nearby the cart icon symbol, normally located on the top right of e-commerce sites. """ num_items = serializers.IntegerField(read_only=True, default=0) total = MoneyField(default=Money()) class Meta: model = CartModel fields = ['num_items', 'total']
class OrderDetailSerializer(OrderListSerializer): items = OrderItemSerializer(many=True, read_only=True) amount_paid = MoneyField(read_only=True) outstanding_amount = MoneyField(read_only=True) is_partially_paid = serializers.SerializerMethodField(method_name='get_partially_paid', help_text="Returns true, if order has been partially paid") annotation = serializers.CharField(write_only=True, required=False) reorder = serializers.BooleanField(write_only=True, default=False) def get_partially_paid(self, order): return order.amount_paid > 0 def update(self, order, validated_data): order.extra.setdefault('addenum', []) if validated_data.get('annotation'): timestamp = timezone.now().isoformat() order.extra['addenum'].append((timestamp, validated_data['annotation'])) if validated_data.get('reorder'): cart = CartModel.objects.get_from_request(self.context['request']) order.readd_to_cart(cart) order.save() return order
class MoneyTestSerializer(serializers.Serializer): amount = MoneyField(read_only=True)
class ProductSerializer(BaseProductSerializer): """ Base product serializer. """ FIELDS = app_settings.PRODUCT_SERIALIZER_FIELDS url = serializers.SerializerMethodField(read_only=True) add_to_cart_url = serializers.SerializerMethodField(read_only=True) unit_price = MoneyField() tax = TaxSerializer() is_available = serializers.SerializerMethodField(read_only=True) category = CategorySerializer() brand = BrandSerializer() manufacturer = ManufacturerSerializer() modifiers = ModifierSerializer(source='get_modifiers', many=True) flags = FlagSerializer(source='get_flags', many=True) width = MeasureField(measure=Distance) height = MeasureField(measure=Distance) depth = MeasureField(measure=Distance) weight = MeasureField(measure=Mass) attributes = serializers.DictField(source='get_attributes', read_only=True) attribute_choices = serializers.DictField(source='get_attribute_choices', read_only=True) discount_amount = MoneyField(read_only=True) tax_amount = MoneyField(read_only=True) variants = serializers.SerializerMethodField() variations = serializers.ListField(source='get_variations', read_only=True) attachments = serializers.SerializerMethodField() relations = RelationSerializer(source='get_relations', many=True) reviews = serializers.SerializerMethodField() class Meta: model = Product fields = [ 'id', 'name', 'slug', 'caption', 'code', 'kind', 'url', 'add_to_cart_url', 'price', 'is_available', 'description', 'unit_price', 'discount', 'tax', 'availability', 'category', 'brand', 'manufacturer', 'discountable', 'modifiers', 'flags', 'width', 'height', 'depth', 'weight', 'available_attributes', 'group', 'attributes', 'attribute_choices', 'published', 'quantity', 'order', 'active', 'created_at', 'updated_at', 'is_single', 'is_group', 'is_variant', 'is_discounted', 'is_taxed', 'discount_percent', 'tax_percent', 'discount_amount', 'tax_amount', 'variants', 'variations', 'attachments', 'relations', 'reviews', ] def get_fields(self): fields = super(ProductSerializer, self).get_fields() overriden = self.context['request'].GET.get('fields', None) names = [x for x in overriden.split(',') if x in self.Meta.fields ] if overriden is not None else self.FIELDS names = list(set(names + self.get_included_fields())) for excluded in [x for x in fields if x not in names]: del fields[excluded] return fields def get_included_fields(self): return [ x for x in self.context['request'].GET.get('include', '').split(',') if x in self.Meta.fields ] def get_url(self, obj): url = obj.get_absolute_url() return self.context['request'].build_absolute_uri(url) if url else None def get_add_to_cart_url(self, obj): try: url = reverse( 'shopit-add-to-cart', args=[obj.safe_translation_getter('slug', any_language=True)]) return self.context['request'].build_absolute_uri(url) except NoReverseMatch: return None def get_is_available(self, obj): return obj.is_available(request=self.context['request']) def get_variants(self, obj): variants = obj.get_variants() if variants: return ProductDetailSerializer(variants, context=self.context, many=True).data def get_attachments(self, obj): request = self.context['request'] attachments = obj.get_attachments() for kind, items in [x for x in attachments.items() if x[1]]: for item in items: for key, value in item.items(): if key.startswith('url'): item[key] = request.build_absolute_uri(value) return attachments def get_reviews(self, obj): reviews = obj.get_reviews( language=self.context['request'].LANGUAGE_CODE) if reviews: return ReviewSerializer(reviews, context=self.context, many=True).data
class ModifierSerializer(serializers.ModelSerializer): amount = MoneyField() class Meta: model = Modifier fields = ['id', 'name', 'code', 'amount', 'percent', 'kind', 'order']