from django.db.transaction import atomic from drf_extra_fields.fields import Base64ImageField from rest_framework import serializers from account.models.bonus import BonusProgram from account.serializers import UserSerializer from utils.exceptions import CRMException from store.models import Checklist, Category, PaymentMethod, Promocode, Image, Gift from core.models import GlobalSettings 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(UserSerializer, 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 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 GlobalSettingsSerializer(serializers.ModelSerializer): yuan_rate = PriceField(source='full_yuan_rate', read_only=True) yuan_rate_commission = PriceField() chinadelivery = PriceField(source='delivery_price_CN') commission = PriceField(source='commission_rub') pickup = serializers.CharField(source='pickup_address') class Meta: model = GlobalSettings fields = ('yuan_rate', 'yuan_rate_last_updated', 'yuan_rate_commission', 'commission', 'chinadelivery', 'pickup', 'time_to_buy') read_only_fields = ('yuan_rate_last_updated',) class AnonymousGlobalSettingsSerializer(GlobalSettingsSerializer): class Meta: model = GlobalSettingsSerializer.Meta.model fields = tuple(set(GlobalSettingsSerializer.Meta.fields) - {'yuan_rate_commission', 'yuan_rate_last_updated'}) 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')