Some checks failed
Build And Test / build-and-push (push) Has been cancelled
- Post 모델에 is_private 필드 추가 - 비공개 게시글은 작성자만 목록/상세에서 조회 가능 - 공개/비공개 토글 기능 지원 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
201 lines
6.6 KiB
Python
201 lines
6.6 KiB
Python
# 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',
|
|
'is_private'
|
|
]
|
|
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',
|
|
'is_private'
|
|
]
|
|
|
|
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
|