본문으로 건너뛰기

Python Django REST Framework 요약 (내가 보려고 만든)

Dev Python Django
thumbnail

Python Django REST Framework를 공부하며 기록해둔 것들을 포스팅합니다.

실무에서 바로 사용할 수 있도록 기록하였습니다.


⭐ MTV 패턴
#

Django REST Framework MTV Pattern

  • Model : DB와의 상호작용을 담당하며 데이터의 구조를 정의
  • Serializer: Model의 데이터를 Frontend에서 처리할 수 있는 JSON 포맷으로 변환
  • View : 비즈니스 로직을 처리하고 Model과 Serializer 간의 연결을 관리

⭐ 설치
#

프로젝트 : weblibrary

앱(서비스) : bookinfo

1. venv 세팅
#

# .venv 외 원하는 이름 사용 가능
python3 -m venv .venv
source .venv/bin/activate

2. Django REST Framework 설치
#

pip install django~=3.2.10

pip install djangorestframework==3.13.1

3. 프로젝트 생성
#

weblibrary 프로젝트 디렉토리가 생성됩니다.

django-admin startproject weblibrary .

4. 앱 추가
#

bookinfo 디렉토리가 생성됩니다.

python manage.py startapp bookinfo

5. 앱 시작
#

python manage.py runserver

⭐ 설정
#

1. [ myblog/settings.py ]
#

앱 등록
#

rest_framework 와 생성한 앱을 INSTALLED_APPS 에 등록해주어야 정상 작동합니다.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # DRF 라이브러리 추가
    'bookinfo', # 앱 추가
]

TIME_ZONE = 'Asia/Seoul' # 시간대 한국으로 변경

디버깅 모드 설정
#

배포 시에는 False로 설정해야 합니다. (에러 내용 노출 방지)

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

접근 제한 설정
#

허용 가능한 호스트는 운영 서버 등에 배포하여, 서비스할 때 호스트로 사용 가능한 호스트 또는 도메인 목록입니다.

위 DEBUG 설정이 True 이고 ALLOWED_HOSTS 설정 값이 비어있으면 [’.localhost’, ‘127.0.0.1’, ‘[::1]’] 대상에 대해 유효성을 검증합니다.

ALLOWED_HOSTS = []

2. [ myblog/urls.py ]
#

각 앱에 경로를 할당합니다. (아래는 기본 값)

urlpatterns = [
    path('admin/', admin.site.urls),
]

3. 어드민 모델 마이그레이션
#

어드민 기능을 사용하려면 필수 사항입니다.

python manage.py migrate

4. 어드민 계정 생성
#

python manage.py createsuperuser
# ID: ruff
# pw: 1q2w3e4r!

⭐ Model
#

앱의 모델 생성
#

📄 bookinfo/models.py

from django.db import models

class Book(models.Model):
    isbn = models.CharField(max_length=13, primary_key=True)          # ISBN
    title = models.CharField(max_length=100)        # 제목
    author = models.CharField(max_length=50)        # 저자
    category = models.CharField(max_length=50)      # 카테고리    
    pages = models.IntegerField()                   # 페이지 수
    price = models.IntegerField()                   # 가격
    publisher = models.CharField(max_length=50)     # 출판사
    publication_date = models.DateField()           # 출판일
    description = models.TextField()                # 책 소개
# 정의한 모델을 이용하여 DB 생성
python manage.py makemigrations bookinfo
python manage.py migrate

앱 모델을 어드민 페이지에 적용
#

📄 bookinfo/admin.py

from django.contrib import admin
from .models import Todo

admin.site.register(Todo)

⭐ Serializer
#

📄 bookinfo/serializers.py

from rest_framework import serializers  # DRF의 serializers 모듈에서 필요한 클래스 가져오기
from .models import Book  # 현재 앱의 models.py에서 Book 모델 가져오기

# 1. Book 모델을 직렬화하는 Serializer 정의
class BookSerializer(serializers.ModelSerializer):
    # Meta 클래스에서 Serializer의 설정을 정의
    class Meta:
        model = Book  # 직렬화에 사용할 모델(Book)
        
        # 직렬화에 포함할 모델의 필드 정의
        fields = [
            'isbn', 'title', 'author', 'category', 'pages',
            'price', 'publisher', 'publication_date', 'description'
        ]
  • class Meta:
    • Meta 클래스는 BookSerializer설정을 정의하는 내부 클래스입니다.
    • 여기서는 어떤 모델을 직렬화할지(model)와 어떤 필드들을 직렬화할지(fields)를 정의합니다.
  • model = Book
    • 이 Serializer가 어떤 모델과 연결될지를 정의합니다. 여기서는 Book 모델과 연결되었습니다.
  • fields = [...]
    • 직렬화할 필드 목록을 정의합니다. 이 목록에 포함된 필드만 JSON 응답에 포함됩니다.
    • ['isbn', 'title', 'author', 'category', 'pages', 'price', 'publisher', 'publication_date', 'description']와 같은 필드가 직렬화됩니다.
    • 이 목록에 포함되지 않은 필드는 응답에 포함되지 않습니다.

⭐ View
#

📄 bookinfo/views.py

from rest_framework import viewsets
from .models import Book
from .serializers import BookSerializer
# from rest_framework import generics, mixins
# from rest_framework.response import Response
# from rest_framework.views import APIView
# from rest_framework.decorators import api_view
# from rest_framework.generics import get_object_or_404

# Viewsets 사용
class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    lookup_field = 'isbn'

'''
# mixin, generics 함께 사용
class BooksAPIGenerics(generics.ListCreateAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

class BookAPIGenerics(generics.RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    lookup_field = 'isbn'
'''

'''
# mixin만 사용
class BooksAPIMixins(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
    
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)
    
class BookAPIMixins(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    lookup_field = 'isbn'

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)
    
    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)
    
    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)
'''

'''
# 기본 view
class BooksAPI(APIView):
    def get(self, request):
        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    def post(self, request):
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
class BookAPI(APIView):
    def get(self, request, isbn):
        book = get_object_or_404(Book, isbn=isbn)
        serializer = BookSerializer(book)
        return Response(serializer.data, status=status.HTTP_200_OK)
'''

⭐ URL
#

📄 weblibrary/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path("bookinfo/", include("bookinfo.urls")),
]

📄 bookinfo/urls.py

from rest_framework import routers
from django.urls import path
from .views import BookViewSet
# from .views import BooksAPIGenerics, BookAPIGenerics
# from .views import BooksAPIMixins, BookAPIMixins
# from .views import BooksAPI, BookAPI

# router 사용
router = routers.SimpleRouter()
router.register('books', BookViewSet)

urlpatterns = router.urls

'''
# mixin, generics 함께 사용
urlpatterns = [
    path("books/", BooksAPIGenerics.as_view()),
    path("book/<str:isbn>/", BookAPIGenerics.as_view()),
]
'''

'''
# mixin 사용
urlpatterns = [
    path("books/", BooksAPIMixins.as_view()),
    path("book/<str:isbn>/", BookAPIMixins.as_view()),
]
'''

'''
# 기본
urlpatterns = [
    path("books/", BooksAPI.as_view()),
    path("book/<str:isbn>/", BookAPI.as_view()),
]
'''

⭐ 참고
#

Serializer 메소드에 인자가 2개 들어가는 경우

📄 todo/serializers.py

from rest_framework import serializers
from .models import Todo

class TodoSimpleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ('id', 'title', 'complete', 'important')

class TodoDetailSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ('id', 'title', 'description', 'created', 'complete', 'important')

class TodoCreateSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ('title', 'description', 'important')

📄 todo/views.py

from rest_framework import status
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import viewsets

from .models import Todo
from .serializers import TodoSimpleSerializer, TodoDetailSerializer, TodoCreateSerializer

class TodosAPIView(APIView):
    def get(self, request):
        todos = Todo.objects.filter(complete=False)
        serializer = TodoSimpleSerializer(todos, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
    
    def post(self, request):
        serializer = TodoCreateSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
class TodoAPIView(APIView):
    def get(self, request, pk):
        todo = get_object_or_404(Todo, id=pk)
        serializer = TodoDetailSerializer(todo)
        return Response(serializer.data, status=status.HTTP_200_OK)
    
    def put(self, request, pk):
        todo = get_object_or_404(Todo, id=pk)
        serializer = TodoCreateSerializer(todo, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class DoneTodosAPIView(APIView):
    def get(self, request):
        todos = Todo.objects.filter(complete=True)
        serializer = TodoSimpleSerializer(todos, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
    
class DoneTodoAPIView(APIView):
    def get(self, request, pk):
        done = get_object_or_404(Todo, id=pk)
        done.complete = True
        done.save()
        serializer = TodoDetailSerializer(done)
        return Response(serializer.data, status=status.HTTP_200_OK)

TodoSimpleSerializer(todos, many=True)

위 메소드의 정의는 아래와 같은데, ModelSerializer를 상속받고 있습니다.

class TodoSimpleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ('id', 'title', 'complete', 'important')

📄 .venv/lib/python3.12/site-packages/rest_framework/serializers.py

ModelSerializer의 생성자에서 instance 인자를 받고 있고, 이 인지에 todos를 넘기고 있는 것입니다.

    def __init__(self, instance=None, data=empty, **kwargs):
        self.instance = instance
        if data is not empty:
            self.initial_data = data
        self.partial = kwargs.pop('partial', False)
        self._context = kwargs.pop('context', {})
        kwargs.pop('many', None)
        super().__init__(**kwargs)