feat: Attachment 모델 추가 및 관련 기능 구현
All checks were successful
Build And Test / build-and-push (push) Successful in 2m5s

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-20 23:39:05 +09:00
parent 362412f0c9
commit dfecaa7654
9 changed files with 447 additions and 7 deletions

View File

@ -1,7 +1,33 @@
# blog/serializers.py
import os
import re
from rest_framework import serializers
from .models import Post, Comment, Tag
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):
@ -45,6 +71,21 @@ class CommentSerializer(serializers.ModelSerializer):
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)
@ -53,6 +94,12 @@ class PostSerializer(serializers.ModelSerializer):
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
@ -60,7 +107,8 @@ class PostSerializer(serializers.ModelSerializer):
'id', 'title', 'content',
'author_id', 'author_name',
'created_at', 'updated_at',
'comment_count', 'tags', 'tag_names'
'comment_count', 'tags', 'tag_names',
'attachments', 'attachment_ids'
]
read_only_fields = ['author_id', 'author_name', 'created_at', 'updated_at']
@ -69,17 +117,22 @@ class PostSerializer(serializers.ModelSerializer):
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):
@ -92,19 +145,54 @@ class PostSerializer(serializers.ModelSerializer):
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'
'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