2025/12/23 MainProject - 14

김기훈·2025년 12월 23일

TIL

목록 보기
92/194

오늘의 목표

오늘 학습 내용 ✅

  • PageNumberPagination, LimitOffsetPagination, CursorPagination

PageNumberPagination

  • 전체 QuerySet(예: 질문 10,000개)을 page 단위로 잘라서(예: 10개씩)

    • 요청된 page의 일부만 응답하고 응답에 다음/이전 링크 + count 같은
      • “목록 탐색 메타”를 붙여주는 클래스
  • 기본으로 해주는 일 (핵심 기능)

    • page 파라미터 처리
      • /questions?page=1 → 1페이지
      • /questions?page=2 → 2페이지
    • page_size 처리
      • 기본 page_size를 정할 수 있고
      • query param으로 size를 바꾸게 할 수도 있음(옵션)
    • 잘라낸 결과만 serializer로 내려줌
      • DB에서 전체를 다 가져오는 게 아니라,
      • 실제로는 “필요한 범위만” 조회되도록 동작(페이지 단위 조회)
    • 표준 paginated response 형태
      • 보통 이런 구조(프로젝트마다 키는 다를 수 있지만 DRF 기본은 아래 형태):
{
  "count": 123,
  "next": "...?page=3",
  "previous": "...?page=1",
  "results": [...]
}

적용 예시(내 코드)

# QuestionAPIView.get()
queryset = get_question_list(**query_serializer.validated_data)

paginator = QuestionPageNumberPagination()
page = paginator.paginate_queryset(queryset, request)

serializer = QuestionListSerializer(page, many=True)

return paginator.get_paginated_response(serializer.data)
  • 흐름

      1. get_question_list()가 필터/정렬/annotate가 반영된 QuerySet을 반환
      1. paginate_queryset(queryset, request)
      • request.query_params의 page, size 등을 보고 QuerySet을 잘라서 page list 반환
      1. 잘린 page만 serializer에 넣어서 results를 만들고
      1. get_paginated_response()로 count/next/previous/results 형태로 감싸서 응답

내 코드에서 가능한 기능

  • QuestionPageNumberPagination로

    • PageNumberPagination을 그대로 쓰지 않고
    • QuestionPageNumberPagination로 커스터마이징
      • /api/v1/qna/questions?page=1&size=5 → 5개
      • FE가 카드 UI에서 “한 화면에 12개/20개” 같은 UX를 원하면 size로 조절 가능
page_size = 10
page_size_query_param = "size" # size로 페이지 크기 변경 가능
max_page_size = 50

page가 “정상 범위 초과”일 때

  • 200 + 빈 리스트 출력

  • DRF 기본

    • page가 너무 크면 404 NotFound를 던짐
    • 내코드

      • except NotFound:에서 이를 잡아서
        • page 자체가 잘못된 것(문자/0/음수) → 400(ValidationError)
        • page 숫자는 정상인데, 범위만 초과 → 200 + results 빈 배열
  • 페이지 초과 출력 예시(ex.총 페이지 2)
{
  "count": 13,
  "next": null,
  "previous": "...?page=2",
  "results": []
}

답변여부 필터

class QuestionListQuerySerializer(serializers.Serializer):
    answer_status = serializers.CharField(required=False)
  • 쿼리 파라미터 존재 여부 / 타입 검증 (문자열인가?)
# view
queryset = get_question_list(**query_serializer.validated_data)
  • Serializer 결과를 그대로 서비스에 전달
# service
def get_question_list(
    *,
    answer_status: str | None = None,
    ...
):
    answered = None
    if answer_status == "answered":
        answered = True
    elif answer_status == "unanswered":
        answered = False
# 프론트가 이렇게 적음 
GET /api/v1/qna/questions?answer_status=answered
GET /api/v1/qna/questions?answer_status=unanswered

질문 수정 api 시작

새롭게 알게된 내용 ✅

원격 변경 먼저 가져오기 (rebase)

  • git pull --rebase origin feature/qna-question-detail-v2
    • 원격 커밋(C)을 먼저 가져오고
    • 내 커밋(B)을 그 위에 다시 얹음
    • 커밋 히스토리 깔끔 유지

@transaction.atomic

  • Django DB 트랜잭션을 하나의 “원자적(atomic) 작업 단위”로 묶어주는 데코레이터
    • 즉, 이 함수 안의 DB 작업은 전부 성공하거나, 전부 실패해야 한다” 는 보장
    • 함수 안에서 에러가 나면, 그 함수에서 수행된 모든 DB 변경 사항을 전부 롤백한다.
@transaction.atomic
def update_question(...):
    question.title = ...
    question.save()

    QuestionImage.objects.filter(...).delete()
    QuestionImage.objects.create(...)
  • 트랜잭셔으로 묶인 기능
    • 질문 제목/내용/카테고리 수정
    • 기존 이미지 전부 삭제
    • 새 이미지들 생성
      • 모두 성공해야 변경 사항이 커밋 됨 / 중간에 하나라도 싪패하면 전부 롤백

Permission vs Serializer 역할 분리

  • Permission의 역할

    • "이 요청을 처리해도 되는가?"
      • 요청을 허용 / 거부
      • YES / NO 판단
      • side-effect 없음
      • Response에 데이터를 넣지 않음
  • Serializer의 역할

    • "이 객체를 이 사용자에게 어떻게 보여줄 것인가?"
      • 응답에 포함될 데이터 표현
      • UI 판단을 돕는 “상태 값” 제공
      • FE를 위한 힌트

SerializerMethodField

  • DRF가 “이런 값 여기서 계산하라고 만든 장치”

PermissionDenied(DRF에서 403 전담 예외)

  • from rest_framework.exceptions import PermissionDenied
  • DRF가 제공하는 권한 거부용 예외 클래스 발생 시 HTTP 403 Forbidden 응답을 자동으로 반환

EMS

  • EMS를 쓰려면 반드시 커스텀 Exception 클래스를 써야 한다?” = ❌
    • PermissionDenied 사용해도 괜찮음

오늘 발생한 문제(발생 했다면) ✅

profile
안녕하세요.

0개의 댓글