Django REST API · 선택 확장
기존 명단에 관리자 권한·Serializer·페이지네이션·CRUD와 실패 테스트를 추가하기
선택 확장: 기존 명단을 JSON API로 제공하기
10주 Django 프로젝트 이후 React 등 별도 클라이언트와 연결할 때 진행합니다. Member·Assignment·Submission 모델과 Form CRUD를 완료해야 합니다. 이번 API는 관리자 전용 명단 관리로, 조회를 포함해 staff 계정만 허용합니다. 개인별 데이터 권한은 다음 문서에서 별도로 연습합니다.
Serializer는 JSON 직렬화와 입력 검증, View는 요청과 권한, Model·DB 제약은 데이터 구조와 무결성을 담당합니다. ViewSet은 반복 CRUD를 줄이지만 어떤 메서드가 공개되는지 이해한 뒤 사용합니다.
설치와 Serializer
가상환경에서 python -m pip install djangorestframework를 실행하고 settings의 INSTALLEDAPPS에 `"restframework"`를 추가합니다. 기존 Django 앱과 lions는 유지합니다. requirements.txt에도 설치 버전을 반영합니다.
새 파일 lions/serializers.py:
from rest_framework import serializersfrom .models import Member
class MemberSerializer(serializers.ModelSerializer): class Meta: model = Member fields = ["id", "name", "track"] read_only_fields = ["id"]모델의 최대 길이·필수 값·선택지를 바탕으로 검증합니다. 노출 필드를 명시해 비밀번호나 내부 필드를 실수로 직렬화하지 않습니다. 여러 필드의 조합 규칙은 serializer의 validate, 동시 요청에도 필요한 제약은 DB에 둡니다.
권한과 페이지네이션이 있는 ViewSet
새 파일 lions/api.py:
from django.db.models.deletion import ProtectedErrorfrom rest_framework.authentication import SessionAuthenticationfrom rest_framework.pagination import PageNumberPaginationfrom rest_framework.permissions import IsAdminUserfrom rest_framework.response import Responsefrom rest_framework.viewsets import ModelViewSetfrom .models import Memberfrom .serializers import MemberSerializer
class MemberPagination(PageNumberPagination): page_size = 20 page_size_query_param = "page_size" max_page_size = 100
class MemberViewSet(ModelViewSet): queryset = Member.objects.order_by("id") serializer_class = MemberSerializer authentication_classes = [SessionAuthentication] permission_classes = [IsAdminUser] pagination_class = MemberPagination
def destroy(self, request, *args, **kwargs): member = self.get_object() try: member.delete() except ProtectedError: return Response({"code": "member_has_submissions", "detail": "제출 기록이 있어 삭제할 수 없습니다."}, status=409) return Response(status=204)IsAdminUser는 is_staff를 확인합니다. ModelViewSet에 권한을 생략하면 프로젝트 기본 설정에 따라 의도치 않게 쓰기가 열릴 수 있으므로 여기서는 명시합니다. SessionAuthentication의 인증된 쓰기 요청에는 CSRF가 필요합니다.
URL 연결
새 파일 lions/api_urls.py:
from rest_framework.routers import DefaultRouterfrom .api import MemberViewSet
router = DefaultRouter()router.register("members", MemberViewSet)urlpatterns = router.urlsconfig/urls.py의 urlpatterns에서 기존 빈 경로 include보다 앞에 추가합니다. 4주의 /api/members/와 겹치지 않도록 v2 경로를 사용합니다.
path("api/v2/", include("lions.api_urls")),path("api-auth/", include("rest_framework.urls")),/api-auth/login/으로 staff 계정에 로그인한 후 /api/v2/members/를 엽니다. browsable API에서 POST·수정·삭제를 검사할 수 있습니다. 목록은 배열 자체가 아니라 count·next·previous·results가 있는 페이지 응답입니다. 클라이언트가 results를 읽도록 계약을 맞춥니다.
상태 코드와 실제 오류 계약
| 요청 | 성공 | 주요 실패 |
|---|---|---|
| GET 목록·상세 | 200 | 권한 거절, 상세 없음 404 |
| POST 생성 | 201 | 입력 오류 400, CSRF·권한 거절 |
| PUT·PATCH 수정 | 200 | 400, 404, 권한 거절 |
| DELETE | 204, 본문 없음 | 404, 제출 기록 충돌 409 |
이 예제는 SessionAuthentication만 사용하므로 미인증 거절이 403일 수 있습니다. 모든 인증 방식에서 미인증이 항상 401이라고 검사하지 않습니다. 필드 검증은 DRF 기본 필드 오류 객체, 삭제 충돌은 위의 code·detail 형식입니다. 통합 오류 envelope를 이미 구현했다고 가정하지 않습니다.
권한과 입력 테스트
새 파일 lions/test_api.py를 만들고 실행합니다.
from django.contrib.auth import get_user_modelfrom rest_framework.test import APITestCasefrom .models import Member
class MemberApiPermissionTests(APITestCase): def test_anonymous_is_denied(self): self.assertEqual(self.client.get("/api/v2/members/").status_code, 403)
def test_regular_user_is_denied(self): user = get_user_model().objects.create_user(username="student") self.client.force_authenticate(user) self.assertEqual(self.client.get("/api/v2/members/").status_code, 403)
def test_staff_can_create_but_invalid_input_is_rejected(self): user = get_user_model().objects.create_user(username="mentor", is_staff=True) self.client.force_authenticate(user) response = self.client.post("/api/v2/members/", {"name": "가람", "track": "frontend"}, format="json") self.assertEqual(response.status_code, 201) invalid = self.client.post("/api/v2/members/", {"name": " ", "track": "frontend"}, format="json") self.assertEqual(invalid.status_code, 400) self.assertEqual(Member.objects.count(), 1)python manage.py test로 검사합니다. force_authenticate는 권한 테스트를 위한 지름길이며 실제 로그인·쿠키·CSRF를 검사하지 않습니다. 브라우저에서 로그인·로그아웃·CSRF 없는 쓰기도 별도로 확인합니다.
확장 실습과 완료 기준
권장 100분: 계약·설치 20분, API 35분, 권한·오류 검사 30분, 리뷰 15분입니다. 회원 21명을 테스트 데이터로 만들고 두 페이지가 중복·누락 없이 조회되는지 검사합니다. 검색을 추가한다면 허용 필드와 정렬을 명시합니다.
완료 기준은 관리자만 CRUD할 수 있고 빈 목록·페이지 경계·400·403·404·409를 설명하는 것입니다. 기존 HTML CRUD는 아직 별도 보호가 필요합니다. 다음 인증 문서에서 모든 진입점을 함께 점검합니다.