
질의응답 목록 조회("/api/v1/qna/questions")
- 모든 웹사이트 이용자는 질의응답 메뉴로 접속하여 등록된 질의응답 목록을 조회 가능
- 질의응답 목록 조회 시 상단의 탭을 이용하여
- 답변 작성 여부에 따라 목록을 조회 가능
- (추가) 질문 카테고리별로 필터링을 적용 가능
- 질의응답 목록 조회 시
- 검색 기능을 활용하여 원하는 질의응답 항목을 찾기 가능
- 조회된 질의응답 목록은
- 목록에서 각 항목은 카드 형태로 노출
질의응답 목록에서 조회가능한 항목 ( 각 카드에 포함되어야 하는 항목 )
- 질의응답 카테고리 ( 대분류 > 중분류 > 소분류 형태 )
- 작성자 정보
- 질의응답 제목
- 질문 내용
- 답변 수
- 조회수
- 질문 작성일시
- 질문 내용에 포함된 이미지의 썸네일 이미지"
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)
❌ 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()
❌ 데이터 생성
❌ 조회 조건
❌ 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(...)
❌ 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은
[] → 권한 검사도 없음