# blog/serializers.py import os import re from rest_framework import serializers from .models import Post, Comment, Tag, Attachment def get_unique_filename(original_name, existing_names): """ 중복 파일명이 있으면 (2), (3) 등의 번호를 추가하여 고유한 파일명 반환 """ if original_name not in existing_names: return original_name name, ext = os.path.splitext(original_name) match = re.match(r'^(.+)\((\d+)\)$', name) if match: base_name = match.group(1) start_num = int(match.group(2)) + 1 else: base_name = name start_num = 2 counter = start_num while True: new_name = f"{base_name}({counter}){ext}" if new_name not in existing_names: return new_name counter += 1 class TagSerializer(serializers.ModelSerializer): """태그 시리얼라이저""" post_count = serializers.SerializerMethodField() class Meta: model = Tag fields = ['id', 'name', 'post_count'] read_only_fields = ['id'] def get_post_count(self, obj): return obj.posts.count() class CommentSerializer(serializers.ModelSerializer): """댓글/대댓글 시리얼라이저""" replies = serializers.SerializerMethodField() reply_count = serializers.SerializerMethodField() class Meta: model = Comment fields = [ 'id', 'post', 'parent', 'content', 'author_id', 'author_name', 'created_at', 'updated_at', 'reply_count', 'replies' ] read_only_fields = ['author_id', 'author_name', 'post', 'created_at', 'updated_at'] def get_replies(self, obj): # 최상위 댓글의 대댓글만 반환 if obj.parent is None: replies = obj.replies.all() return CommentSerializer(replies, many=True, context=self.context).data return [] def get_reply_count(self, obj): if obj.parent is None: return obj.replies.count() return 0 class AttachmentSerializer(serializers.ModelSerializer): """첨부파일 시리얼라이저""" file_url = serializers.SerializerMethodField() class Meta: model = Attachment fields = ['id', 'file', 'file_url', 'original_name', 'file_size', 'uploaded_at'] read_only_fields = ['original_name', 'file_size', 'uploaded_at', 'file_url'] def get_file_url(self, obj): if obj.file: return obj.file.url return None class PostSerializer(serializers.ModelSerializer): comment_count = serializers.SerializerMethodField() tags = TagSerializer(many=True, read_only=True) tag_names = serializers.ListField( child=serializers.CharField(max_length=50), write_only=True, required=False ) attachments = AttachmentSerializer(many=True, read_only=True) attachment_ids = serializers.ListField( child=serializers.IntegerField(), write_only=True, required=False ) # 임시 업로드된 파일 ID 목록 class Meta: model = Post fields = [ 'id', 'title', 'content', 'author_id', 'author_name', 'created_at', 'updated_at', 'comment_count', 'tags', 'tag_names', 'attachments', 'attachment_ids' ] read_only_fields = ['author_id', 'author_name', 'created_at', 'updated_at'] def get_comment_count(self, obj): return obj.comments.count() def create(self, validated_data): tag_names = validated_data.pop('tag_names', []) attachment_ids = validated_data.pop('attachment_ids', []) post = Post.objects.create(**validated_data) self._set_tags(post, tag_names) self._link_attachments(post, attachment_ids) return post def update(self, instance, validated_data): tag_names = validated_data.pop('tag_names', None) attachment_ids = validated_data.pop('attachment_ids', None) for attr, value in validated_data.items(): setattr(instance, attr, value) instance.save() if tag_names is not None: self._set_tags(instance, tag_names) if attachment_ids is not None: self._link_attachments(instance, attachment_ids) return instance def _set_tags(self, post, tag_names): """태그 설정 (없으면 생성)""" tags = [] for name in tag_names: name = name.strip().lower() if name: tag, _ = Tag.objects.get_or_create(name=name) tags.append(tag) post.tags.set(tags) def _link_attachments(self, post, attachment_ids): """임시 업로드된 첨부파일을 게시글에 연결 (중복 파일명 자동 번호 추가)""" if not attachment_ids: return # 게시글의 기존 첨부파일명 목록 existing_names = set(post.attachments.values_list('original_name', flat=True)) # 임시 파일들을 가져와서 중복 체크 후 연결 temp_attachments = Attachment.objects.filter( id__in=attachment_ids, post__isnull=True ) for attachment in temp_attachments: unique_name = get_unique_filename(attachment.original_name, existing_names) attachment.original_name = unique_name attachment.post = post attachment.save() existing_names.add(unique_name) class PostListSerializer(serializers.ModelSerializer): """목록 조회용 간소화된 시리얼라이저""" comment_count = serializers.SerializerMethodField() tags = TagSerializer(many=True, read_only=True) thumbnail = serializers.SerializerMethodField() class Meta: model = Post fields = [ 'id', 'title', 'author_name', 'created_at', 'updated_at', 'comment_count', 'tags', 'thumbnail' ] def get_comment_count(self, obj): return obj.comments.count() def get_thumbnail(self, obj): """첫 번째 이미지 파일을 섬네일로 반환""" image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp'] for attachment in obj.attachments.all(): original_name = attachment.original_name.lower() if any(original_name.endswith(ext) for ext in image_extensions): return { 'id': attachment.id, 'file_url': attachment.file.url if attachment.file else None, 'original_name': attachment.original_name } return None