from django.db.transaction import atomic from drf_extra_fields.fields import Base64ImageField from rest_framework import serializers from bonus_program.models import BonusProgram from account.serializers import UserSimpleSerializer from utils.exceptions import CRMException from store.models import Checklist, Category, PaymentMethod, Promocode, Image, Gift from store.utils import get_primary_key_related_model from poizonstore.utils import PriceField class ImageSerializer(serializers.ModelSerializer): image = Base64ImageField() class Meta: model = Image fields = ('image',) class ImageListSerializer(serializers.ListSerializer): child = ImageSerializer() def to_internal_value(self, data): if isinstance(data, list): data = [{"image": x} for x in data] return super().to_internal_value(data) def to_representation(self, data): images = super().to_representation(data) return [image['image'] for image in images] class CategoryChecklistSerializer(serializers.ModelSerializer): class Meta: model = Category fields = ('id', 'name') class CategorySerializer(serializers.ModelSerializer): chinarush = PriceField(source='delivery_price_CN_RU') class Meta: model = Category fields = ('id', 'name', 'chinarush', 'commission') class CategoryFullSerializer(CategorySerializer): children = serializers.SerializerMethodField() def get_children(self, obj): return CategoryFullSerializer(obj.get_children(), many=True).data class Meta: model = CategorySerializer.Meta.model fields = CategorySerializer.Meta.fields + ('children',) class GiftSerializer(serializers.ModelSerializer): image = Base64ImageField(required=False, allow_null=True) min_price = PriceField() class Meta: model = Gift fields = ('id', 'name', 'image', 'min_price', 'available_count') class ChecklistSerializer(serializers.ModelSerializer): id = serializers.CharField(read_only=True) manager_id = serializers.PrimaryKeyRelatedField(read_only=True, allow_null=True) link = serializers.URLField(source='product_link', required=False) category = get_primary_key_related_model(CategoryChecklistSerializer, required=False, allow_null=True) size = serializers.CharField(required=False, allow_null=True) image = ImageListSerializer(source='main_images', required=False) preview_image_url = serializers.ImageField(read_only=True) promocode = serializers.SlugRelatedField(slug_field='name', queryset=Promocode.objects.active(), required=False, allow_null=True) use_bonuses = serializers.BooleanField(write_only=True, default=False) gift = get_primary_key_related_model(GiftSerializer, required=False, allow_null=True) yuan_rate = PriceField(read_only=True) price_yuan = PriceField(required=False) price_rub = serializers.IntegerField(read_only=True) delivery_price_CN = PriceField(read_only=True) delivery_price_CN_RU = PriceField(read_only=True) full_price = PriceField(read_only=True) real_price = PriceField(required=False, allow_null=True) commission_rub = PriceField(read_only=True) customer = get_primary_key_related_model(UserSimpleSerializer, required=False, allow_null=True) receiver_name = serializers.CharField(required=False, allow_null=True) receiver_phone = serializers.CharField(required=False, allow_null=True) is_split_payment = serializers.BooleanField(required=False) payment_method = serializers.SlugRelatedField(slug_field='slug', queryset=PaymentMethod.objects.all(), required=False, allow_null=True) payment_proof = Base64ImageField(required=False, allow_null=True) split_payment_proof = Base64ImageField(required=False, allow_null=True) receipt = Base64ImageField(required=False, allow_null=True) poizon_tracking = serializers.CharField(required=False, allow_null=True) cdek_tracking = serializers.CharField(required=False, allow_null=True) delivery = serializers.ChoiceField(choices=Checklist.DeliveryType.CHOICES, required=False, allow_null=True) delivery_display = serializers.CharField(source='get_delivery_display', read_only=True) created_at = serializers.DateTimeField(read_only=True) status_updated_at = serializers.DateTimeField(read_only=True) def _collect_images_by_fields(self, validated_data): images = {} for k in ('main_images',): if k in validated_data: images[k] = validated_data.pop(k) return images def _create_main_images(self, instance, images): if images is None: return img_objs = [Image(image=img['image'], type=Image.DEFAULT) for img in images] img_objs = Image.objects.bulk_create(img_objs) instance.images.set(img_objs) instance.generate_preview(next(iter(img_objs), None)) @atomic def create(self, validated_data): images = self._collect_images_by_fields(validated_data) # Managers can create orders with arbitrary customers # Client orders are created with client's account user = self.context['request'].user if not user.is_manager or validated_data.get('customer') is None: validated_data['customer'] = user use_bonuses = validated_data.pop('use_bonuses') instance = super().create(validated_data) self._create_main_images(instance, images.get('main_images')) if use_bonuses: BonusProgram.spend_bonuses(order=instance) return instance def update(self, instance, validated_data): images = self._collect_images_by_fields(validated_data) # Perform update instance: Checklist = super().update(instance, validated_data) self._create_main_images(instance, images.get('main_images')) # Invalidate prefetched images delattr(instance, '_images') if instance.status == Checklist.Status.NEW: has_payment_info = instance.payment_proof is not None and instance.payment_method is not None if has_payment_info: instance.status = Checklist.Status.PAYMENT instance.save() return instance @staticmethod def get_image(obj: Checklist): return obj.images.all() class Meta: model = Checklist fields = ('id', 'status', 'manager_id', 'link', 'category', 'brand', 'model', 'size', 'image', 'preview_image_url', 'yuan_rate', 'price_yuan', 'price_rub', 'delivery_price_CN', 'delivery_price_CN_RU', 'commission_rub', 'promocode', 'gift', 'use_bonuses', 'bonus_used', 'comment', 'full_price', 'real_price', 'customer', 'receiver_name', 'receiver_phone', 'is_split_payment', 'payment_method', 'payment_proof', 'split_payment_proof', 'split_accepted', 'receipt', 'poizon_tracking', 'cdek_tracking', 'cdek_barcode_pdf', 'delivery', 'delivery_display', 'created_at', 'status_updated_at', 'buy_time_remaining' ) class ChecklistListSerializer(ChecklistSerializer): class Meta: model = ChecklistSerializer.Meta.model fields = ('id', 'status', 'category', 'brand', 'model', 'size', 'preview_image_url', 'price_yuan', 'price_rub', 'full_price', 'real_price', 'comment', 'poizon_tracking', 'cdek_tracking', 'delivery', 'delivery_display', 'created_at', 'status_updated_at') class ClientChecklistSerializerMixin: def validate(self, attrs): gift = attrs.get('gift') if gift is not None: if self.instance.price_yuan < gift.min_price: raise CRMException("Can't add gift: price of order < min_price of gift") if gift.available_count == 0: raise CRMException("Gift is not available") return attrs class ClientCreateChecklistSerializer(ClientChecklistSerializerMixin, ChecklistSerializer): status = serializers.SerializerMethodField() class Meta: model = ChecklistSerializer.Meta.model fields = ChecklistSerializer.Meta.fields writable_fields = { 'link', 'brand', 'model', 'size', 'category', 'price_yuan', 'use_bonuses', 'comment', } read_only_fields = tuple(set(ChecklistSerializer.Meta.fields) - writable_fields) def get_status(self, obj): return Checklist.Status.DRAFT class ClientUpdateChecklistSerializer(ClientChecklistSerializerMixin, ChecklistSerializer): class Meta: model = ChecklistSerializer.Meta.model fields = ChecklistSerializer.Meta.fields writable_fields = { 'comment', 'payment_proof', 'payment_method', 'delivery', 'receiver_name', 'receiver_phone', 'gift', 'cdek_barcode_pdf' } read_only_fields = tuple(set(ChecklistSerializer.Meta.fields) - writable_fields) class PaymentMethodSerializer(serializers.ModelSerializer): class Meta: model = PaymentMethod fields = ('slug', 'name', 'requisites', 'cardnumber') class PromocodeSerializer(serializers.ModelSerializer): class Meta: model = Promocode fields = ('id', 'name', 'discount', 'free_delivery', 'no_comission', 'is_active')