DRF

h232ch·2023년 5월 13일
0
post-thumbnail

Initialize

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',
]

Models

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)

Serializers


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 등과 같은 권한 통제도 가능하다.

View


Class based view

**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]

Function based View

**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),
    
]

Generic View

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()),

]

ViewSet


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):

Serializer Relations

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.pyAlbumSerializer 클래스에서 AlbumTrack의 관계를 구성하는데 사용된다.

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_nametracks변수에 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)),
]

Relationship (OneToOne, ForeginKey, ManyToMany)

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 매핑을 수행하고 ForeginKeyOneToMany 구성이 가능하다. 다수의 오브젝트와 쌍방으로 관계 구성이 필요한 경우 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)\
        

Token

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)),
]

Github

https://github.com/h232ch/djangoTest/tree/main

0개의 댓글