Python Django REST Framework를 공부하며 기록해둔 것들을 포스팅합니다.
실무에서 바로 사용할 수 있도록 기록하였습니다.
⭐ MTV 패턴#
- 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
모델과 연결되었습니다.
- 이 Serializer가 어떤 모델과 연결될지를 정의합니다. 여기서는
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)