Files
market_page/backend/shop/serializers.py
jeremygan2021 84e30d26af
Some checks failed
Deploy to Server / deploy (push) Has been cancelled
代码支持
2026-03-01 17:39:36 +08:00

369 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from rest_framework import serializers
from .models import ESP32Config, Order, Salesperson, Service, VCCourse, ProductFeature, ServiceOrder, WeChatUser, Distributor, Withdrawal, CommissionLog, CourseEnrollment
from .utils import get_current_wechat_user
class CommissionLogSerializer(serializers.ModelSerializer):
"""
佣金记录序列化器
"""
order_info = serializers.SerializerMethodField()
class Meta:
model = CommissionLog
fields = ['id', 'amount', 'level', 'status', 'created_at', 'order_info']
read_only_fields = ['id', 'amount', 'level', 'status', 'created_at', 'order_info']
def get_order_info(self, obj):
return {
'order_id': obj.order.id,
'total_price': obj.order.total_price,
'customer_name': obj.order.customer_name
}
class WeChatUserSerializer(serializers.ModelSerializer):
is_admin = serializers.SerializerMethodField()
has_web_account = serializers.SerializerMethodField()
class Meta:
model = WeChatUser
fields = ['id', 'openid', 'nickname', 'avatar_url', 'gender', 'country', 'province', 'city', 'phone_number', 'is_star', 'title', 'skills', 'is_admin', 'has_web_account', 'has_web_badge']
read_only_fields = ['id', 'openid', 'phone_number', 'is_star', 'title', 'skills', 'is_admin', 'has_web_account', 'has_web_badge']
def get_is_admin(self, obj):
# 检查是否关联了系统用户且具有管理员权限
return bool(obj.user and obj.user.is_staff)
def get_has_web_account(self, obj):
# 检查是否关联了系统用户(即网页账号)
return obj.user is not None
class DistributorSerializer(serializers.ModelSerializer):
user_info = WeChatUserSerializer(source='user', read_only=True)
class Meta:
model = Distributor
fields = ['id', 'user_info', 'level', 'commission_rate', 'total_earnings', 'withdrawable_balance', 'status', 'invite_code', 'qr_code_url']
read_only_fields = ['level', 'commission_rate', 'total_earnings', 'withdrawable_balance', 'status', 'invite_code', 'qr_code_url']
class WithdrawalSerializer(serializers.ModelSerializer):
class Meta:
model = Withdrawal
fields = ['id', 'amount', 'status', 'remark', 'created_at']
read_only_fields = ['status', 'created_at', 'remark']
class ProductFeatureSerializer(serializers.ModelSerializer):
"""
产品特性序列化器
"""
display_icon = serializers.SerializerMethodField()
class Meta:
model = ProductFeature
fields = ['title', 'description', 'icon_name', 'display_icon', 'order']
def get_display_icon(self, obj):
if obj.icon_url:
return obj.icon_url
if obj.icon_image:
return obj.icon_image.url
return None
class ServiceSerializer(serializers.ModelSerializer):
"""
AI服务序列化器
"""
features_list = serializers.SerializerMethodField()
display_icon = serializers.SerializerMethodField()
display_detail_image = serializers.SerializerMethodField()
class Meta:
model = Service
fields = '__all__'
def get_features_list(self, obj):
if obj.features:
return [line.strip() for line in obj.features.split('\n') if line.strip()]
return []
def get_display_icon(self, obj):
if obj.icon_url:
return obj.icon_url
if obj.icon:
return obj.icon.url
return None
def get_display_detail_image(self, obj):
if obj.detail_image_url:
return obj.detail_image_url
if obj.detail_image:
return obj.detail_image.url
return None
class CourseEnrollmentSerializer(serializers.ModelSerializer):
"""
课程报名序列化器
"""
course_title = serializers.CharField(source='course.title', read_only=True)
ref_code = serializers.CharField(write_only=True, required=False, allow_blank=True)
class Meta:
model = CourseEnrollment
fields = ['id', 'course', 'course_title', 'customer_name', 'phone_number', 'email', 'wechat_id', 'message', 'status', 'created_at', 'ref_code']
read_only_fields = ['status', 'created_at']
def create(self, validated_data):
ref_code = validated_data.pop('ref_code', None)
# 尝试关联销售员或分销员
if ref_code:
try:
salesperson = Salesperson.objects.get(code=ref_code)
validated_data['salesperson'] = salesperson
except Salesperson.DoesNotExist:
pass
try:
distributor = Distributor.objects.get(invite_code=ref_code)
validated_data['distributor'] = distributor
except Distributor.DoesNotExist:
pass
return super().create(validated_data)
class ServiceOrderSerializer(serializers.ModelSerializer):
"""
AI服务订单序列化器
"""
service_name = serializers.CharField(source='service.title', read_only=True)
# 接收前端传来的 ref_code
ref_code = serializers.CharField(write_only=True, required=False, allow_blank=True)
class Meta:
model = ServiceOrder
fields = ['id', 'service', 'service_name', 'customer_name', 'company_name',
'phone_number', 'email', 'requirements', 'total_price', 'status', 'created_at', 'ref_code']
read_only_fields = ['total_price', 'status', 'created_at']
def create(self, validated_data):
ref_code = validated_data.pop('ref_code', None)
service = validated_data.get('service')
# 默认设置预估总价为服务起步价
if service:
validated_data['total_price'] = service.price
# 尝试关联销售员
if ref_code:
try:
salesperson = Salesperson.objects.get(code=ref_code)
validated_data['salesperson'] = salesperson
except Salesperson.DoesNotExist:
pass
try:
distributor = Distributor.objects.get(invite_code=ref_code)
validated_data['distributor'] = distributor
except Distributor.DoesNotExist:
pass
return super().create(validated_data)
class VCCourseSerializer(serializers.ModelSerializer):
"""
VC课程序列化器
"""
display_cover_image = serializers.SerializerMethodField()
display_detail_image = serializers.SerializerMethodField()
course_type_display = serializers.CharField(source='get_course_type_display', read_only=True)
video_url = serializers.SerializerMethodField()
video_embed_code = serializers.SerializerMethodField()
is_purchased = serializers.SerializerMethodField()
class Meta:
model = VCCourse
fields = '__all__'
def get_display_cover_image(self, obj):
if obj.cover_image_url:
return obj.cover_image_url
if obj.cover_image:
return obj.cover_image.url
return None
def get_display_detail_image(self, obj):
if obj.detail_image_url:
return obj.detail_image_url
if obj.detail_image:
return obj.detail_image.url
return None
def _check_purchased(self, obj):
request = self.context.get('request')
if not request:
return False
# 尝试获取当前用户
user = get_current_wechat_user(request)
if not user:
return False
# 如果是管理员,视为已购买
if user.user and user.user.is_staff:
return True
# 检查是否已购买/报名 (通过已支付的订单)
has_order = Order.objects.filter(
wechat_user=user,
course=obj,
status__in=['paid', 'shipped', 'completed']
).exists()
return has_order
def get_is_purchased(self, obj):
return self._check_purchased(obj)
def get_video_url(self, obj):
"""
仅当用户已付费/报名时返回视频URL
"""
if not obj.is_video_course:
return None
if self._check_purchased(obj):
return obj.video_url
return None
def get_video_embed_code(self, obj):
"""
仅当用户已付费/报名时返回视频嵌入代码
"""
if not obj.is_video_course:
return None
if self._check_purchased(obj):
return obj.video_embed_code
return None
class ESP32ConfigSerializer(serializers.ModelSerializer):
"""
ESP32配置序列化器
"""
display_detail_image = serializers.SerializerMethodField()
features = ProductFeatureSerializer(many=True, read_only=True)
class Meta:
model = ESP32Config
fields = '__all__'
def get_display_detail_image(self, obj):
if obj.detail_image_url:
return obj.detail_image_url
if obj.detail_image:
return obj.detail_image.url
return None
class OrderSerializer(serializers.ModelSerializer):
"""
订单序列化器
"""
config_name = serializers.CharField(source='config.name', read_only=True)
course_title = serializers.CharField(source='course.title', read_only=True)
activity_title = serializers.CharField(source='activity.title', read_only=True)
config_image = serializers.SerializerMethodField()
salesperson_name = serializers.CharField(source='salesperson.name', read_only=True)
salesperson_code = serializers.CharField(source='salesperson.code', read_only=True)
# 接收前端传来的 ref_code用于查找 Salesperson
ref_code = serializers.CharField(write_only=True, required=False, allow_blank=True)
class Meta:
model = Order
fields = ['id', 'config', 'config_name', 'config_image', 'course', 'course_title', 'activity', 'activity_title', 'quantity', 'total_price', 'status', 'created_at', 'updated_at', 'wechat_trade_no',
'customer_name', 'phone_number', 'shipping_address', 'ref_code', 'salesperson_name', 'salesperson_code', 'courier_name', 'tracking_number']
read_only_fields = ['total_price', 'status', 'wechat_trade_no', 'created_at', 'updated_at']
extra_kwargs = {
'customer_name': {'required': True},
'phone_number': {'required': True},
}
def validate(self, data):
# 如果是部分更新 (PATCH),可能不需要校验所有字段,但这里主要用于创建
if self.instance:
return data
config = data.get('config')
course = data.get('course')
activity = data.get('activity')
if not config and not course and not activity:
raise serializers.ValidationError("必须选择一种商品(硬件配置、课程或活动)")
# Count how many types are selected
selected_types = sum([bool(config), bool(course), bool(activity)])
if selected_types > 1:
raise serializers.ValidationError("一次只能购买一种类型的商品")
if config and not data.get('shipping_address'):
raise serializers.ValidationError({"shipping_address": "购买硬件产品需要填写收货地址"})
return data
def get_config_image(self, obj):
if obj.config:
if obj.config.static_image_url:
return obj.config.static_image_url
if obj.config.detail_image_url:
return obj.config.detail_image_url
if obj.config.detail_image:
return obj.config.detail_image.url
elif obj.course:
if obj.course.cover_image_url:
return obj.course.cover_image_url
if obj.course.cover_image:
return obj.course.cover_image.url
elif obj.activity:
# Use activity.display_banner_url logic
if obj.activity.banner:
return obj.activity.banner.url
if obj.activity.banner_url:
return obj.activity.banner_url
return None
def create(self, validated_data):
"""
重写创建方法,自动计算总价并关联销售员/分销员
"""
config = validated_data.get('config')
course = validated_data.get('course')
activity = validated_data.get('activity')
quantity = validated_data.get('quantity', 1)
ref_code = validated_data.pop('ref_code', None)
if config:
validated_data['total_price'] = config.price * quantity
elif course:
validated_data['total_price'] = course.price * quantity
elif activity:
validated_data['total_price'] = activity.price * quantity
# 尝试关联销售员或分销员
if ref_code:
# 1. 尝试查找旧版销售员
try:
salesperson = Salesperson.objects.get(code=ref_code)
validated_data['salesperson'] = salesperson
except Salesperson.DoesNotExist:
pass
# 2. 尝试查找新版分销员
try:
distributor = Distributor.objects.get(invite_code=ref_code)
validated_data['distributor'] = distributor
except Distributor.DoesNotExist:
pass
return super().create(validated_data)