2026/03/01 Blog - 11

김기훈·2026년 3월 1일

TIL

목록 보기
151/194
post-thumbnail

2026/02/25 ~ 2026/02/28 개인사정으로 인한 기록 불가


코딩테스트(2609)


문제 해결

수정시에 태그가 수정이 안됨

  • 수정시에 태그도 수정이 가능하도록 기능을 수정해야 함
                    <div class="row">
                        <div class="col-md-6 mb-4">
                            <label class="form-label fw-bold text-secondary">공개 설정</label>
                            <select class="form-select" id="visibility">
                                <option value="PUBLIC">전체 공개</option>
                                <option value="PRIVATE">비공개 (나만 보기)</option>
                            </select>
                        </div>
                    </div>

                    <div class="d-flex justify-content-end gap-2 mt-5 border-top pt-4">

——————————————————————————————————————[비교]—————————————————————————————————————————
                    <div class="row">
                        <div class="col-md-6 mb-4">
                            <label class="form-label fw-bold text-secondary">공개 설정</label>
                            <select class="form-select" id="visibility">
                                <option value="PUBLIC">전체 공개</option>
                                <option value="PRIVATE">비공개 (나만 보기)</option>
                            </select>
                        </div>
                    </div>
                    
                    <div class="mb-4">
                        <label for="tags" class="form-label fw-bold text-secondary">태그</label>
                        <input type="text" class="form-control" id="tags" placeholder="예: Python, Django, 일상 (쉼표로 구분)">
                    </div>


좋아요 취소 오류

  • 좋아요가 다시 눌림 다시 눌렀을 경우 삭제되지 않음

원인

  • 사용자가 이 글에 좋아요를 눌렀는지 여부를 전달하지 않음

시리얼라이저 수정

class PostDetailSerializer(serializers.ModelSerializer):
    author_nickname = serializers.CharField(source="user.nickname", read_only=True)

    tags = serializers.SlugRelatedField(many=True, read_only=True, slug_field="name")  # type: ignore
    likes_count = serializers.IntegerField(read_only=True)

    class Meta:
        model = Post
        fields = [
            "id",
            "title",
            "content",
            "thumbnail",
            "author_nickname",
            "created_at",
            "visibility",
            "tags",
            "likes_count",
        ]

——————————————————————————————————————[비교]—————————————————————————————————————————
class PostDetailSerializer(serializers.ModelSerializer):
    author_nickname = serializers.CharField(source="user.nickname", read_only=True)

    tags = serializers.SlugRelatedField(many=True, read_only=True, slug_field="name")  # type: ignore
    likes_count = serializers.IntegerField(read_only=True)
    is_liked = serializers.SerializerMethodField()

    class Meta:
        model = Post
        fields = [
            "id",
            "title",
            "content",
            "thumbnail",
            "author_nickname",
            "created_at",
            "visibility",
            "tags",
            "likes_count",
            "is_liked",
        ]

    def get_is_liked(self, obj) -> bool:
        # 1. 뷰(View)에서 넘겨준 context 안에서 현재 요청(request) 객체를 가져옵니다.
        request = self.context.get('request')

        # 2. 요청 객체가 존재하고, 로그인된 사용자(is_authenticated)일 경우에만 검사합니다.
        if request and request.user.is_authenticated:
            # 3. 현재 게시글(obj)의 좋아요(likes) 목록 중에 현재 로그인한 유저가 있는지(exists) 확인하여 True/False를 반환합니다.
            return obj.likes.filter(user=request.user).exists()

        # 4. 로그인하지 않은 사용자라면 무조건 False(좋아요 안 누름)를 반환합니다.
        return False
class PostDetailAPIView(APIView):
    permission_classes = [IsAuthenticatedOrReadOnly]

    @extend_schema(tags=["포스트"], summary="게시글 상세 조회")
    def get(self, request: Request, post_id: int):
        # 1. 서비스 레이어를 호출
        post = get_post_detail(post_id)

        # 2. 게시글이 없는 경우(None), 커스텀 예외를 발생
        if not post:
            raise BaseCustomException(ErrorMessage.POST_NOT_FOUND)
		
        # 시리얼라이저를 호출할 때 context={"request": request}를 함께 넘겨줌
        # 이렇게 해야 Serializer 내부에서 현재 접속한 유저가 누구인지 알 수 있음
        return Response(PostDetailSerializer(post, context={'request': request}).data)

해결 완료


기능 개발

댓글 수정 api

view

class CommentManageAPIView(APIView):
    """게시글의 댓글 수정/삭제을 담당하는 View입니다."""

    # 로그인한 유저만 댓글을 작성할 수 있도록 설정
    permission_classes = [IsAuthenticatedOrReadOnly]

    @extend_schema(
        tags=["댓글"],
        summary="게시글 댓글 수정",
        request=CommentCreateSerializer,
    )
    def put(self, request: Request, comment_id: int):
        # 1. 입력 데이터 검증
        serializer = CommentCreateSerializer(data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)

        # 2. User 타입 지정
        user = cast(User, request.user)

        # 3. 서비스 레이어 호출
        comment = update_comment(
            comment_id=comment_id, user=user, validated_data=serializer.validated_data
        )

        # 4. 성공 응답 반환
        return Response(
            CommentCreateSerializer(comment).data,
            status=status.HTTP_200_OK,
        )

service

@transaction.atomic
def update_comment(
    *, comment_id: int, user: User, validated_data: dict[str, Any]
) -> Comment:
    # 1. 대상 댓글이 존재하는지 검증
    comment = Comment.objects.filter(id=comment_id).first()

    # 2. 권한 검증(댓글 존재여부)
    if not comment:
        raise BaseCustomException(ErrorMessage.COMMENT_NOT_FOUND)

    # 3. 권한 검증(댓글 작성자 여부)
    if comment.user != user:
        raise BaseCustomException(ErrorMessage.NOT_COMMENT_AUTHOR)

    # 4. 데이터 업데이트
    if "content" in validated_data:
        comment.content = validated_data["content"]
        comment.save(update_fields=["content"])

    return comment  # type: ignore

댓글 삭제api

view

    def delete(self, request: Request, comment_id: int):
        # 1. User 타입 지정
        user = cast(User, request.user)

        # 2. 서비스레이어 호출
        delete_comment(comment_id=comment_id, user=user)

        return Response(status=status.HTTP_204_NO_CONTENT)

service

@transaction.atomic
def delete_comment(*, comment_id: int, user: User) -> None:
    """댓글을 삭제하는 서비스 로직입니다."""

    # 1. 대상 댓글 조회
    comment = Comment.objects.filter(id=comment_id).first()

    # 2. 권한 검증(댓글 존재여부)
    if not comment:
        raise BaseCustomException(ErrorMessage.COMMENT_NOT_FOUND)

    # 3. 권한 검증(댓글 작성자 여부)
    if comment.user != user:
        raise BaseCustomException(ErrorMessage.NOT_COMMENT_AUTHOR)

    # 4. 데이터베이스에서 댓글 삭제
    comment.delete()

디자인 도입

댓글


오늘의 문제

url 충돌

  • 문제

    • 댓글 수정테스트 중에 자꾸 405 가 출력
  • 원인

    • 처음 작성한 URL 패턴이 완전하게 동일
      • 따라서 댓글 수정을 위해 PUT /1/ 로 요청을 보내면, 장고는 첫 번째 줄인 <int:post_id>/ 패턴에 매칭
      • CommentAPIView에는 et과 post 메서드만 존재하므로 PUT이나 PATCH 요청을 받으면
        • 405 Method Not Allowed
urlpatterns = [
    path("<int:post_id>/", CommentAPIView.as_view(), name="comment_create"),
    path("<int:comment_id>/", CommentManageAPIView.as_view(), name="comment_update"),
]
profile
안녕하세요.

0개의 댓글