from rest_framework import serializers from .models import ( CardTemplate, Card, CardBatch, CardUsageLog, ClothingAttributes, PropAttributes, SongAttributes, DanceAttributes, FurnitureAttributes, 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 FurnitureAttributesSerializer(serializers.ModelSerializer): class Meta: model = FurnitureAttributes fields = [ 'furniture_type', 'style', 'material', 'dimensions', 'weight', 'assembly_required', 'max_weight_capacity', 'care_instructions' ] 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) furniture_attributes = FurnitureAttributesSerializer(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', 'furniture_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('furniture_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 == 'furniture' and hasattr(instance, 'furniture_attrs'): representation['attributes'] = FurnitureAttributesSerializer(instance.furniture_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) furniture_data = validated_data.pop('furniture_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 furniture_data is not None and hasattr(instance, 'furniture_attrs'): for k, v in furniture_data.items(): setattr(instance.furniture_attrs, k, v) instance.furniture_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 == 'furniture' and hasattr(instance, 'furniture_attrs'): representation['attributes'] = FurnitureAttributesSerializer(instance.furniture_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 == 'furniture' and hasattr(instance, 'furniture_attrs'): representation['attributes'] = FurnitureAttributesSerializer(instance.furniture_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 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 == 'furniture' and hasattr(template, 'furniture_attrs'): representation['attributes'] = FurnitureAttributesSerializer(template.furniture_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