lty/qy_lty/card/serializers.py
pmc c0fe1f502b
All checks were successful
Build and Deploy LTY / build-and-deploy (push) Successful in 1h5m35s
feat: update card models, admin pages, and add migrations
- Update card models, serializers, views and URLs
- Update dances, songs, users admin pages and API modules
- Add card migrations (merge furniture into decoration)
- Update middleware and settings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 16:38:48 +08:00

467 lines
20 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 (
CardTemplate, Card, CardBatch, CardUsageLog,
ClothingAttributes, PropAttributes, SongAttributes,
DanceAttributes, DecorationAttributes
)
class ClothingAttributesSerializer(serializers.ModelSerializer):
class Meta:
model = ClothingAttributes
fields = [
'style', 'size', 'color', 'season',
'material', 'fit_type', 'care_instructions'
]
class PropAttributesSerializer(serializers.ModelSerializer):
class Meta:
model = PropAttributes
fields = [
'prop_type', 'material', 'size', 'weight',
'durability', 'usage_instructions'
]
class SongAttributesSerializer(serializers.ModelSerializer):
class Meta:
model = SongAttributes
fields = [
'genre', 'duration', 'bpm', 'composer',
'lyricist', 'arrangement', 'audio_file', 'lyrics'
]
class DanceAttributesSerializer(serializers.ModelSerializer):
class Meta:
model = DanceAttributes
fields = [
'style', 'difficulty', 'duration', 'choreographer',
'required_space', 'calories_burn', 'tutorial_video'
]
class DecorationAttributesSerializer(serializers.ModelSerializer):
class Meta:
model = DecorationAttributes
fields = [
'decoration_type', 'style', 'material', 'size',
'placement', 'indoor_outdoor', 'installation_required', 'care_instructions'
]
class CardTemplateSerializer(serializers.ModelSerializer):
category_display = serializers.SerializerMethodField()
rarity_display = serializers.SerializerMethodField()
card_type_display = serializers.SerializerMethodField()
status_display = serializers.SerializerMethodField()
image_url = serializers.URLField(source='image', required=False, allow_blank=True)
# 新增所有类别专有属性的write_only字段
clothing_attributes = ClothingAttributesSerializer(write_only=True, required=False)
prop_attributes = PropAttributesSerializer(write_only=True, required=False)
song_attributes = SongAttributesSerializer(write_only=True, required=False)
dance_attributes = DanceAttributesSerializer(write_only=True, required=False)
decoration_attributes = DecorationAttributesSerializer(write_only=True, required=False)
class Meta:
model = CardTemplate
fields = [
'id', 'name', 'category', 'category_display', 'description',
'card_type', 'card_type_display', 'rarity', 'rarity_display',
'image', 'image_url', 'model_url', 'model_version',
'status', 'status_display', 'published_at', 'price',
'created_at', 'updated_at',
# 新增
'clothing_attributes', 'prop_attributes', 'song_attributes', 'dance_attributes', 'decoration_attributes',
]
read_only_fields = ['created_at', 'updated_at', 'published_at']
def create(self, validated_data):
# 把所有 *_attributes 字段pop掉只留主表字段
# 属性的创建由 View 层的 create 方法负责
validated_data.pop('clothing_attributes', None)
validated_data.pop('prop_attributes', None)
validated_data.pop('song_attributes', None)
validated_data.pop('dance_attributes', None)
validated_data.pop('decoration_attributes', None)
return super().create(validated_data)
def get_category_display(self, obj):
return dict(CardTemplate.CATEGORY_CHOICES).get(obj.category)
def get_rarity_display(self, obj):
return obj.get_rarity_display()
def get_card_type_display(self, obj):
return obj.get_card_type_display()
def get_status_display(self, obj):
return obj.get_status_display()
def to_representation(self, instance):
representation = super().to_representation(instance)
# 添加类别专有属性
try:
if instance.category == 'clothing' and hasattr(instance, 'clothing_attrs'):
representation['attributes'] = ClothingAttributesSerializer(instance.clothing_attrs).data
elif instance.category == 'prop' and hasattr(instance, 'prop_attrs'):
representation['attributes'] = PropAttributesSerializer(instance.prop_attrs).data
elif instance.category == 'song' and hasattr(instance, 'song_attrs'):
representation['attributes'] = SongAttributesSerializer(instance.song_attrs).data
elif instance.category == 'dance' and hasattr(instance, 'dance_attrs'):
representation['attributes'] = DanceAttributesSerializer(instance.dance_attrs).data
elif instance.category == 'decoration' and hasattr(instance, 'decoration_attrs'):
representation['attributes'] = DecorationAttributesSerializer(instance.decoration_attrs).data
except Exception:
representation['attributes'] = None
return representation
def update(self, instance, validated_data):
# 取出专属属性数据
clothing_data = validated_data.pop('clothing_attributes', None)
prop_data = validated_data.pop('prop_attributes', None)
song_data = validated_data.pop('song_attributes', None)
dance_data = validated_data.pop('dance_attributes', None)
decoration_data = validated_data.pop('decoration_attributes', None)
# 更新主表
instance = super().update(instance, validated_data)
# 更新专属属性
if clothing_data is not None and hasattr(instance, 'clothing_attrs'):
for k, v in clothing_data.items():
setattr(instance.clothing_attrs, k, v)
instance.clothing_attrs.save()
if prop_data is not None and hasattr(instance, 'prop_attrs'):
for k, v in prop_data.items():
setattr(instance.prop_attrs, k, v)
instance.prop_attrs.save()
if song_data is not None and hasattr(instance, 'song_attrs'):
for k, v in song_data.items():
setattr(instance.song_attrs, k, v)
instance.song_attrs.save()
if dance_data is not None and hasattr(instance, 'dance_attrs'):
for k, v in dance_data.items():
setattr(instance.dance_attrs, k, v)
instance.dance_attrs.save()
if decoration_data is not None and hasattr(instance, 'decoration_attrs'):
for k, v in decoration_data.items():
setattr(instance.decoration_attrs, k, v)
instance.decoration_attrs.save()
return instance
class CardTemplateDetailSerializer(CardTemplateSerializer):
"""Detailed template serializer with related batches and cards count"""
batches_count = serializers.SerializerMethodField()
active_cards_count = serializers.SerializerMethodField()
class Meta(CardTemplateSerializer.Meta):
fields = CardTemplateSerializer.Meta.fields + ['batches_count', 'active_cards_count']
def get_batches_count(self, obj):
return obj.batches.count()
def get_active_cards_count(self, obj):
# 只计算激活状态的卡片数量
return obj.cards.filter(status='active').count()
def to_representation(self, instance):
representation = super().to_representation(instance)
# 添加类别专有属性
try:
if instance.category == 'clothing' and hasattr(instance, 'clothing_attrs'):
representation['attributes'] = ClothingAttributesSerializer(instance.clothing_attrs).data
elif instance.category == 'prop' and hasattr(instance, 'prop_attrs'):
representation['attributes'] = PropAttributesSerializer(instance.prop_attrs).data
elif instance.category == 'song' and hasattr(instance, 'song_attrs'):
representation['attributes'] = SongAttributesSerializer(instance.song_attrs).data
elif instance.category == 'dance' and hasattr(instance, 'dance_attrs'):
representation['attributes'] = DanceAttributesSerializer(instance.dance_attrs).data
elif instance.category == 'decoration' and hasattr(instance, 'decoration_attrs'):
representation['attributes'] = DecorationAttributesSerializer(instance.decoration_attrs).data
except Exception as e:
# 处理相关属性不存在的情况
representation['attributes'] = None
return representation
class CardSerializer(serializers.ModelSerializer):
category_display = serializers.SerializerMethodField()
template_name = serializers.SerializerMethodField()
status_display = serializers.SerializerMethodField()
image_url = serializers.SerializerMethodField()
class Meta:
model = Card
fields = [
'id', 'unique_id', 'template', 'template_name', 'name',
'category', 'category_display', 'description', 'batch',
'image', 'image_url', 'price', 'status', 'status_display',
'user', 'used_at', 'manufactured', 'manufactured_at',
'created_at', 'updated_at'
]
read_only_fields = [
'created_at', 'updated_at', 'used_at',
'manufactured_at', 'image_url'
]
def get_category_display(self, obj):
return dict(CardTemplate.CATEGORY_CHOICES).get(obj.category)
def get_template_name(self, obj):
return obj.template.name if obj.template else None
def get_status_display(self, obj):
return obj.get_status_display()
def get_image_url(self, obj):
if obj.image:
return obj.image.url
# 如果卡片没有图片使用模板的图片模板image现在是URLField字符串
elif obj.template and obj.template.image:
return obj.template.image
return None
class CardDetailSerializer(CardSerializer):
"""Detailed card serializer with usage logs"""
usage_logs = serializers.SerializerMethodField()
template_details = serializers.SerializerMethodField()
class Meta(CardSerializer.Meta):
fields = CardSerializer.Meta.fields + ['usage_logs', 'template_details']
def get_usage_logs(self, obj):
# Only return the 10 most recent logs
logs = obj.usage_logs.all().order_by('-created_at')[:10]
return CardUsageLogSerializer(logs, many=True).data
def get_template_details(self, obj):
if obj.template:
return {
'rarity': obj.template.rarity,
'rarity_display': obj.template.get_rarity_display(),
'card_type': obj.template.card_type,
'card_type_display': obj.template.get_card_type_display(),
'model_url': obj.template.model_url,
'model_version': obj.template.model_version,
}
return None
class CardBatchSerializer(serializers.ModelSerializer):
category_display = serializers.SerializerMethodField()
template_name = serializers.SerializerMethodField()
status_display = serializers.SerializerMethodField()
excel_file_url = serializers.SerializerMethodField()
class Meta:
model = CardBatch
fields = [
'id', 'batch_number', 'template', 'template_name',
'category', 'category_display', 'quantity', 'description',
'status', 'status_display', 'exported', 'exported_at',
'excel_file', 'excel_file_url', 'sent_to_production',
'production_date', 'published', 'published_at',
'created_at', 'updated_at',
'start_id', 'end_id'
]
read_only_fields = [
'created_at', 'updated_at', 'exported_at', 'excel_file',
'excel_file_url', 'production_date', 'published_at', 'quantity',
'start_id', 'end_id'
]
def get_category_display(self, obj):
return dict(CardTemplate.CATEGORY_CHOICES).get(obj.category)
def get_template_name(self, obj):
return obj.template.name if obj.template else None
def get_status_display(self, obj):
return obj.get_status_display()
def get_excel_file_url(self, obj):
if obj.excel_file:
return obj.excel_file.url
return None
class CardUsageLogSerializer(serializers.ModelSerializer):
action_display = serializers.SerializerMethodField()
user_display = serializers.SerializerMethodField()
class Meta:
model = CardUsageLog
fields = [
'id', 'card', 'user', 'user_display', 'action', 'action_display',
'details', 'old_status', 'new_status', 'created_at', 'ip_address'
]
read_only_fields = fields
def get_action_display(self, obj):
return obj.get_action_display() if hasattr(obj, 'get_action_display') else obj.action
def get_user_display(self, obj):
if obj.user:
return obj.user.username
return None
class CardScanSerializer(serializers.Serializer):
"""Serializer for scanning a card with NFC"""
unique_id = serializers.CharField(max_length=100)
def validate(self, data):
"""
Check that at least one identifier is provided.
"""
if not any([data.get('unique_id')]):
raise serializers.ValidationError("unique_id must be provided.")
return data
class CardUseSerializer(serializers.Serializer):
"""Serializer for using a card"""
unique_id = serializers.CharField(max_length=100)
class CardBatchGenerateSerializer(serializers.Serializer):
"""Serializer for generating card batches"""
template = serializers.PrimaryKeyRelatedField(queryset=CardTemplate.objects.all())
quantity = serializers.IntegerField(min_value=1, max_value=10000)
description = serializers.CharField(required=False, allow_blank=True)
class CardTemplatePublishSerializer(serializers.Serializer):
"""Serializer for publishing a card template"""
template_id = serializers.IntegerField()
class CardBatchPublishSerializer(serializers.Serializer):
"""Serializer for publishing a card batch"""
batch_id = serializers.IntegerField()
class CardBatchManufactureSerializer(serializers.Serializer):
"""Serializer for marking a card batch as manufactured"""
batch_id = serializers.IntegerField()
# 类别特定的属性序列化器
class CategoryTemplateSerializer(CardTemplateDetailSerializer):
"""Base class for category-specific template serializers"""
def to_representation(self, instance):
representation = super().to_representation(instance)
# Add category-specific attributes if they exist
try:
if instance.category == 'clothing' and hasattr(instance, 'clothing_attrs'):
representation['attributes'] = ClothingAttributesSerializer(instance.clothing_attrs).data
elif instance.category == 'prop' and hasattr(instance, 'prop_attrs'):
representation['attributes'] = PropAttributesSerializer(instance.prop_attrs).data
elif instance.category == 'song' and hasattr(instance, 'song_attrs'):
representation['attributes'] = SongAttributesSerializer(instance.song_attrs).data
elif instance.category == 'dance' and hasattr(instance, 'dance_attrs'):
representation['attributes'] = DanceAttributesSerializer(instance.dance_attrs).data
elif instance.category == 'decoration' and hasattr(instance, 'decoration_attrs'):
representation['attributes'] = DecorationAttributesSerializer(instance.decoration_attrs).data
except Exception as e:
# Handle the case where related attributes don't exist
representation['attributes'] = None
return representation
class MobileProductSerializer(serializers.ModelSerializer):
"""手机端通用产品序列化器 - 适用于所有分类"""
category_display = serializers.SerializerMethodField()
card_type_display = serializers.SerializerMethodField()
rarity_display = serializers.SerializerMethodField()
status_display = serializers.SerializerMethodField()
image_url = serializers.URLField(source='image', read_only=True)
active_cards_count = serializers.SerializerMethodField()
attributes = serializers.SerializerMethodField()
class Meta:
model = CardTemplate
fields = [
'id', 'name', 'description',
'category', 'category_display',
'card_type', 'card_type_display',
'rarity', 'rarity_display',
'image_url',
'status', 'status_display',
'published_at',
'active_cards_count',
'attributes',
]
def get_category_display(self, obj):
return obj.get_category_display()
def get_card_type_display(self, obj):
return obj.get_card_type_display()
def get_rarity_display(self, obj):
return obj.get_rarity_display()
def get_status_display(self, obj):
return obj.get_status_display()
def get_active_cards_count(self, obj):
return obj.cards.filter(status='active').count()
def get_attributes(self, obj):
try:
attr_map = {
'clothing': ('clothing_attrs', ClothingAttributesSerializer),
'prop': ('prop_attrs', PropAttributesSerializer),
'song': ('song_attrs', SongAttributesSerializer),
'dance': ('dance_attrs', DanceAttributesSerializer),
'decoration': ('decoration_attrs', DecorationAttributesSerializer),
}
if obj.category in attr_map:
attr_name, serializer_cls = attr_map[obj.category]
if hasattr(obj, attr_name):
return serializer_cls(getattr(obj, attr_name)).data
except Exception:
pass
return None
# 带有专有属性的卡牌详情序列化器
class CategoryCardDetailSerializer(CardDetailSerializer):
"""Card detail serializer with category-specific attributes"""
def to_representation(self, instance):
representation = super().to_representation(instance)
# If card has a template, get its specific attributes
if instance.template:
template = instance.template
try:
# Add category-specific attributes if they exist
if template.category == 'clothing' and hasattr(template, 'clothing_attrs'):
representation['attributes'] = ClothingAttributesSerializer(template.clothing_attrs).data
elif template.category == 'prop' and hasattr(template, 'prop_attrs'):
representation['attributes'] = PropAttributesSerializer(template.prop_attrs).data
elif template.category == 'song' and hasattr(template, 'song_attrs'):
representation['attributes'] = SongAttributesSerializer(template.song_attrs).data
elif template.category == 'dance' and hasattr(template, 'dance_attrs'):
representation['attributes'] = DanceAttributesSerializer(template.dance_attrs).data
elif template.category == 'decoration' and hasattr(template, 'decoration_attrs'):
representation['attributes'] = DecorationAttributesSerializer(template.decoration_attrs).data
except Exception as e:
# Handle the case where related attributes don't exist
representation['attributes'] = None
return representation