1. Install Django
pip install django
2. Install Django RestFramework
pip install djangorestframework
3. Setup Django App [./settings.py]
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
]
1. Setup Demo App
python manage.py startapp demo
2. Setup Django models [./demo/models.py]
from datetime import datetime
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=36, null=False, unique=True)
description = models.TextField(max_length=256, blank=True)
price = models.DecimalField(default=0, decimal_places=2, max_digits=100)
published = models.DateField()
is_published = models.BooleanField(default=False)
cover = models.ImageField(upload_to='covers/', blank=True)
# it would express title on admin site
def __str__(self):
return self.title
모델을 지정하여 DB를 구성할 수 있다. 예시에서는 문자, 텍스트, 날짜 필드 등을 사용해서 다양한 옵션을 선택하였다. 모델 구성의 상세 내용은 Django Models Docs를 참고하도록 하자
3. Setup admin
python manage.py createsuperuser
4. Migration
python manage.py makemigrations
python manage.py migrate
Setup admin page [./demo/admin.py]
from django.contrib import admin
from demo.models import Book
@admin.register(Book)
1. Setup serializers
from rest_framework import serializers
from demo.models import Book
from demo.models import Comment
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'title', 'description', 'price', 'published']
모델 시리얼라이저의 기본 형태이다. 필드로 지정한 컬럼은 모델의 컬럼과 매핑되며 필드에 지정되어있는 조건과 상태를 검증한다. 필드는 자유롭게 정의하거나 제외할 수 있으며 특정 옵션을 이용하면 Readonly
등과 같은 권한 통제도 가능하다.
**1. Setup class based view [./demo/views.py]
from .models import Book
from .serializers import BookSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class ClassBasedViewBooks(APIView):
def get(self, request):
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
# return HttpResponse(serializer.data)
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 ClassBasedViewBookDetail(APIView):
def get_object(self, pk):
try:
return Book.objects.get(pk=pk)
except Book.DoesNotExist:
raise Http404
def get(self, request, pk):
book = self.get_object(pk)
serializer = BookSerializer(book)
return Response(serializer.data)
def put(self, request, pk):
book = self.get_object(pk)
serializer = BookSerializer(book, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
book = self.get_object(pk)
book.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Class Based View는 클래스 내 지정된 핸들러 메서드를 통해 request
를 처리하는 방식이다. DRF는 다양한 뷰를 제공하고 있으며 cdrf.co에서 상세한 내용을 확인할 수 있다.
2. Setup Main Routing [./urls.py]
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('demo/', include('demo.urls')),
]
3. Setup Sub Routing [./demo/urls.py]
from django.urls import path, include
from . import views
from .views import BookViewSet
urlpatterns = [
path('cviewbooks/', views.ClassBasedViewBooks.as_view()),
path('cviewbooks/<int:pk>/', views.ClassBasedViewBookDetail.as_view()),
]
4. Test view [GET]
5. Test view [POST]
6. Test view [PUT]
7. Test view [DELETE]
**1. Setup function based view [./demo/views.py]
from .models import Book,
from .serializers import BookSerializer
from rest_framework.decorators import api_view
from django.http import Http404
from rest_framework.response import Response
@api_view()
def function_based_view_books(request):
if request.method == 'GET':
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
elif request.method == 'POST':
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)
@api_view()
def function_based_view_detail_book(request, pk):
get_object = Book.objects.get(pk=pk)
if not get_object:
raise Http404
elif request.method == 'GET':
book = get_object
serializer = BookSerializer(book)
return Response(serializer.data)
elif request.method == 'PUT':
book = get_object
serializer = BookSerializer(book, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
book = get_object
book.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
2. Setup routing [./demo/urls.py]
from django.urls import path, include
from . import views
urlpatterns = [
# we can check out the registered url
path('', include(router.urls)),
# class based view example
path('cviewbooks/', views.ClassBasedViewBooks.as_view()),
path('cviewbooks/<int:pk>/', views.ClassBasedViewBookDetail.as_view()),
# function based view
path('functionviewbooks/', views.function_based_view_books),
path('functionviewbooks/<int:pk>/', views.function_based_view_detail_book),
]
1. Setup class based view with mixin [./demo/views.py]
from .models import Book,
from .serializers import BookSerializer
from rest_framework import mixins
from rest_framework import generics
class MixingViewBooks(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)
class MixingViewDetailBook(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
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)
2. Setup routing [./demo/urls.py]
from django.urls import path, include
from . import views
urlpatterns = [
# class based view example
path('cviewbooks/', views.ClassBasedViewBooks.as_view()),
path('cviewbooks/<int:pk>/', views.ClassBasedViewBookDetail.as_view()),
# class based view with mixin
path('mixinviewbooks/', views.MixingViewBooks.as_view()),
path('mixinviewbooks/<int:pk>/', views.MixingViewDetailBook.as_view()),
]
1. Setup ViewSet [./demo/views.py]
from rest_framework import viewsets
class BookViewSet(viewsets.ModelViewSet):
serializer_class = BookSerializer
queryset = Book.objects.all()
2. Setup urls.py [./demo/urls.py]
router = routers.DefaultRouter()
router.register('books', BookViewSet)
urlpatterns = [
path('', include(router.urls)),
]
ViewSet을 사용할 경우 API Root를 제공한다.
ModelViewSet
은 아래의 Mixins과 GenericViewSet
을 상속하여 Create
, Retrieve
, Update
, Destory
, List
기능을 별도 구성 없이 사용할 수 있다.
ModelViewSet
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
1. Setup models [./demo/models.py]
from django.db import models
class Album(models.Model):
album_name = models.CharField(max_length=100)
artist = models.CharField(max_length=100)
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
order = models.IntegerField()
title = models.CharField(max_length=100)
duration = models.IntegerField()
class Meta:
unique_together = ['album', 'order']
ordering = ['order']
# String field relationship
def __str__(self):
return '%d: %s' % (self.order, self.title)
Track
클래스의 album
필드를 Album
클래스의 ForeignKey
로 지정한다. related_name
은 추후 serializers.py
의 AlbumSerializer
클래스에서 Album
과 Track
의 관계를 구성하는데 사용된다.
3. Setup serializers [./demo/serializers.py]
from rest_framework import serializers
from demo.models Album, Track
class TrackSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Track
fields = ['order', 'title', 'duration', 'url']
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True, read_only=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
TrackSerializer
클래스는 HyperLinkkModelSerializer
를 상속받는다. 해당 시리얼라이저는 id
값이 존재하지 않고 url
필드가 존재하여 해당하는 Detail
필드를 url
로 제공한다.
AlbumSerializer
클래스는 이전 예시의 클래스와 동일하게 ModelSerializer
를 상속하며 related_name
인 tracks
변수에 TrackSerializer
클래스를 지정한다. 이후 필드에 tracks
를 기재하면 Album
클래스와 Track
클래스의 관계 구성되어 아래와 같은 결과를 볼 수 있다.
4. Setup views [./demo/views.py]
from rest_framework import viewsets
class AlbumViewSet(viewsets.ModelViewSet):
serializer_class = AlbumSerializer
queryset = Album.objects.all()
class TrackViewSet(viewsets.ModelViewSet):
serializer_class = TrackSerializer
queryset = Track.objects.all()
5. Setup routing [./demo/urls.py]
from rest_framework import routers
router.register('albums', AlbumViewSet)
router.register('track', TrackViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Setup Models [./demo/models.py]
from django.db import models
class BookNumber(models.Model):
isbn_10 = models.CharField(max_length=10, blank=True)
isbn_13 = models.CharField(max_length=13, blank=True)
def __str__(self):
return self.isbn_10
class Book(models.Model):
title = models.CharField(max_length=36, null=False, unique=True)
description = models.TextField(max_length=256, blank=True)
price = models.DecimalField(default=0, decimal_places=2, max_digits=100)
published = models.DateField()
is_published = models.BooleanField(default=False)
cover = models.ImageField(upload_to='covers/', blank=True)
# relationship with BookNumber
# OneToOne can be matched with only one field
number = models.OneToOneField(BookNumber, null=True, blank=True,
on_delete=models.CASCADE)
# it would express title on admin site
def __str__(self):
return self.title
class Character(models.Model):
name = models.CharField(max_length=30)
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='characters')
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=30)
surname = models.CharField(max_length=30)
books = models.ManyToManyField(Book, related_name='authors')
def __str__(self):
return self.name
Setup serializers [./demo/serializers.py]
from rest_framework import serializers
from demo.models import Book, BookNumber, Character, Author
class BookMiniSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'title']
class CharacterSerializer(serializers.ModelSerializer):
class Meta:
model = Character
fields = ['id', 'name']
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ['id', 'name', 'surname']
class BookNumberSerializer(serializers.ModelSerializer):
class Meta:
model = BookNumber
fields = ['id', 'isbn_10', 'isbn_13']
class BookSerializer(serializers.ModelSerializer):
# OneToOne relationship needs to set many=False
number = BookNumberSerializer(many=False)
# ForeignKey relationship
characters = CharacterSerializer(many=True)
# ManyToMany relationship needs to set many=True
authors = AuthorSerializer(many=True)
class Meta:
model = Book
fields = ['id', 'title', 'description',
'price', 'published', 'number', 'characters',
'authors']
# read_only_fields = ['price']
시리얼라이저를 통해 다양한 관계 구성이 가능하다.OneToOne
구성의 경우 대상 오브젝트와 1:1 매핑을 수행하고 ForeginKey
은 OneToMany
구성이 가능하다. 다수의 오브젝트와 쌍방으로 관계 구성이 필요한 경우 ManyToMany
구성을 수행하면 된다. ForeignKey
, ManyToMany
구성 시 related_name
옵션을 사용하여 원본 오브젝트에 컬럼을 명시해주기 때문에 관계 모델 구성시 잊지말고 기재해야 한다.
Setup views [./demo/views.py]
from rest_framework import viewsets
from rest_framework.response import Response
from .serializers import BookSerializer, BookMiniSerializer
class BookViewSet(viewsets.ModelViewSet):
# serializer_class = BookSerializer
# Change the Book Serializer to Book Mini Serializer
serializer_class = BookMiniSerializer
queryset = Book.objects.all()
authentication_classes = (TokenAuthentication,)
# settings.py에 먼저 정의하더라도 아래 권한이 먼저 적용됨
# permission_classes = (IsAuthenticated,)
# You can fix the functions viewset already has
# You can fix the retrieve function which can retrieve one object's info
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
# This is original code of the RetrieveModelMixin
# serializer = self.get_serializer(instance)
# Change the book mini Serializer to book serializer
# Users can check the mini serializer and go into the detail page with book serializer
serializer = BookSerializer(instance)
return Response(serializer.data)\
1. Install rest framework auth token
pip install django-rest-authtoken
2. Setup settings.py [./settings.py]
... 중략 ...
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'rest_framework.permissions',
'demo',
]
... 중략 ...
# REST Framework token settings
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
# 'rest_framework.permissions.IsAuthenticated',
'rest_framework.permissions.AllowAny',
)
}
3. Migrations
pip manage.py makemigrations
pip manage.py migrate
이후 어드민 페이지에서 토큰 값을 생성하거나 삭제할 수 있다. 뷰에서 settings.py
에 적용한 토큰 권한을 설정할 수 있다.
4. Setup views [./demo/views.py]
... 중략 ...
class BookViewSet(viewsets.ModelViewSet):
# serializer_class = BookSerializer
# Change the Book Serializer to Book Mini Serializer
serializer_class = BookMiniSerializer
queryset = Book.objects.all()
# settings.py에 적용한 token 설정값 적용
authentication_classes = (TokenAuthentication,)
# Permission 적용 (settings.py 보다 우선순위)
# permission_classes = (IsAuthenticated,)
... 중략 ...
authentication_classes
를 통해 settings.py
에 적용한 권한을 뷰에 적용할 수 있다. 또한 permission_classes
를 사용하여 권한을 부여할 수도 있는데 이는 authentication_classes
보다 우선시된다.
5. Setup routing [./demo/urls.py]
router = routers.DefaultRouter()
router.register('books', BookViewSet)
urlpatterns = [
path('', include(router.urls)),
]