oz_externship - list

김기훈·2025년 12월 11일

부트캠프 프로젝트

목록 보기
18/39

  • 질의응답 목록 조회("/api/v1/qna/questions")

    • 모든 웹사이트 이용자는 질의응답 메뉴로 접속하여 등록된 질의응답 목록을 조회 가능
      • 모든 이용자 가능 → 권한 X
    • 질의응답 목록 조회 시 상단의 탭을 이용하여
      • 답변 작성 여부에 따라 목록을 조회 가능
      • (추가) 질문 카테고리별로 필터링을 적용 가능
    • 질의응답 목록 조회 시
      • 검색 기능을 활용하여 원하는 질의응답 항목을 찾기 가능
    • 조회된 질의응답 목록은
      • 최신순으로 정렬되어야하며 페이지네이션이 적용
    • 목록에서 각 항목은 카드 형태로 노출
  • 질의응답 목록에서 조회가능한 항목 ( 각 카드에 포함되어야 하는 항목 )

    • 질의응답 카테고리 ( 대분류 > 중분류 > 소분류 형태 )
    • 작성자 정보
      • 프로필 이미지
      • 닉네임
    • 질의응답 제목
    • 질문 내용
    • 답변 수
    • 조회수
    • 질문 작성일시
    • 질문 내용에 포함된 이미지의 썸네일 이미지"

ErrorResponse

{
  "200": "OK",
  "400": "Bad Request",
  "404": "Not Found"
}

{
  "400": {
    "error_detail": "유효하지 않은 목록 조회 요청입니다."
  },
  "404": {
    "error_detail": "조회 가능한 질문이 존재하지 않습니다."
  }
}

Success Response Schema

{
  "page": int,
  "size": int,
  "total_count": int,
  "questions": [
    {
      "question_id": bigint,
      "category_path": str,
      "profile_img_url": str,
      "nickname": str,
      "title": str,
      "content_preview": str,
      "answer_count": int,
      "view_count": int,
      "created_at": str,
      "thumbnail_img_url": str
    }
  ]
}

Django REST Framework

  • serializers.py — “데이터 계약 + 입력 검증”
    • 입력 데이터 검증
      • class QuestionListQuerySerializer(serializers.Serializer):
      • 타입 / 범위 / 필수 여부 / 포맷
    • 출력 데이터 구조 정의
      • class QuestionListSerializer(serializers.ModelSerializer):
      • 어떤 필드를 내려줄지 / 프론트와의 응답 계약
    • 단순 필드 가공 (OK)
      • 문자열 자르기 / 날짜 포맷 변경 / DB 조회X
# 시리얼라이저에 있으면 안되는 것 
❌ Question.objects.filter(...)
❌ qs.exists()raise QuestionListEmptyError
❌ permission 체크

serializer는 절대 DB 상태 판단 ❌

views.py — “HTTP 조립 담당자”

  • 요청 → serializer → service → response 흐름
  • 요청 받기 / serializer 호출 / service 호출 / Response 만들기
    • HTTP status 결정
      • return Response(data, status=200)
# views.py에 있으면 안 되는 것
❌ ORM 쿼리
❌ annotate
❌ 비즈니스 규칙
❌ exists() 판단

permissions.py — “접근 가능 여부 판단기”

  • 이 요청을 누가 할 수 있는지 → permission은 if/else 판단만
- 인증 여부 / 역할 / 소유자 여부 (내 글만 수정)
class QuestionCreatePermission(BasePermission):
    def has_permission(self, request, view):
        return request.user.role == RoleChoices.ST
  • 접근 불가 시 즉시 차단
    • raise QuestionCreateNotAuthenticated()
# permissions.py에 있으면 안 되는 것
❌ 데이터 생성
❌ 조회 조건
❌ business logic

services.py — “유스케이스의 핵심”

  • 유스케이스 흐름 (Orchestration) → 여러 단계를 조합 / 순서가 중요 / 정책이 들어감
def get_question_list(...):
    qs = base_queryset()
    qs = apply_filters(qs)
    if not qs.exists():
        raise QuestionListEmptyError()
    return paginate(qs)
  • 도메인 가드 (실무 핵심 개념) → 비즈니스 정책
if not qs.exists():
    raise QuestionListEmptyError()
  • ORM 쿼리 구성 → 성능 최적화 / 조회 전략
Question.objects.select_related(...).annotate(...)
# services.py에 있으면 안 되는 것
❌ request / response
❌ serializer.is_valid()
❌ HTTP status

  • 질문 조회 시리얼라이저가 2개가 되는 이유

    • question_list_query.py → 입력 (query params 검증)
      • 요청 파라미터 검증 (page, category, search 등)
    • question_list.py → 출력 (response 직렬화)
      • 응답 데이터 구조 정의 (id, title, author, created_at 등)
    • 입력 / 출력은 관심사가 다르기 때문에 합치지 않는 게 정상
    • 하나로 묶는 경우

      • 입력과 출력이 섞임 / serializer.is_valid()의 의미가 흐려짐


코드

view

class QuestionAPIView(APIView):

    def get_authenticators(self) -> list[BaseAuthentication]:
        if self.request.method == "GET":
            return []
        return super().get_authenticators()

    def get_permissions(self) -> list[BasePermission]:
        if self.request.method == "POST":
            return [QuestionCreatePermission()]
        return []
  • GET

    • ❌ 인증 안 함 / ❌ 권한 검사 안 함 → 완전 공개 조회
    • [] → 인증 로직 자체를 스킵
  • POST

    • 인증: super().get_authenticators() → 전역 인증(JWT 등) 적용
      • super() → settings.py에 등록된 기본 인증(JWT 등) 사용
    • 권한: QuestionCreatePermission에서 인증 여부 + 권한까지 처리
  • get_authenticators / get_permissions

    • DRF는 요청이 들어오면 가장 먼저 인증(Authentication) 을 수행
      • get_authenticators()를 오버라이드하면
        • “이 HTTP 메서드에 어떤 인증 클래스를 쓸지”를 직접 정할 수 있음
    • 권한(Permission) 은 인증 이후에 실행됨
      • POST일 때만 QuestionCreatePermission을 태움
        • 여기에서 인증도 확인하지만 permission은 이미 인증된 사용자 기준으로 판단
      • GET은 [] → 권한 검사도 없음

profile
안녕하세요.

0개의 댓글