DRF(Django Rest Framework)란 Django 안에서 RESTful API 서버를 쉽게 구축할 수 있도록 도와주는 오픈소스 라이브러리다.
HTTP URI(Uniform Resource Identifier)를 통해 자원(Resource)을 명시하고, HTTP Method(POST, GET, PUT, DELETE)를 통해 해당 자원에 대한 CRUD Operation을 적용하는 것을 의미한다.
REST는 “Representational State Transfer”의 약자이며, 이는 자원을 이름(자원의 표현)으로 구분하여 해당 자원의 상태(정보)를 주고 받는 모든 것을 의미한다.
👉 즉, 자원(resource)의 표현(representation) 에 의한 상태 전달을 말한다.
그동안은 장고의 MVT 아키텍쳐를 이용하는 것만 해봤는데, 최근의 개발 트렌드는 프론트엔드와 백엔드를 분리하여 RESTful API를 이용해 통신하는 것이다. 최근에는 flask를 이용해 RESTful API 구축해 리액트로 만든 프론트엔드와 통신해 보았다.
하지만 플라스크는 마이크로 프레임워크이며 개발자가 모든 환경을 세팅해야 한다는 번거로움이 있다. 이 때문에 지난 프로젝트를 하면서도 굳이 플라스크를 사용해야 할까 하는 고민이 많았다. 반면 장고는 플라스크 보다 10배는 무거운 프레임 워크이며 이미 필요한 것들이 REST framework처럼 거의 다 개발되어 있어 편의성이 높다.
들어가기 전에....
프로젝트 구성
크게 'boards'와 'users' 앱으로 구성하였으며 각 앱의 model을 아래와 같이 users : boards의 일대다 관계로 구성하였다.
/boards/models.py
from django.db import models
from users.models import User
class Board(models.Model):
# 글의 제목, 내용, 작성일, 마지막 수정일
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="boards")
title = models.CharField("제목", max_length=50, null=False)
content = models.TextField("내용", null=False)
dt_created = models.DateTimeField("작성일", auto_now_add=True, null=False)
dt_modified = models.DateTimeField("수정일", auto_now=True, null=False)
def __str__(self):
return self.title
related_name은 장고 ORM모델을 위한 것이며,
ORM모델은 쿼리문 없이 장고에서 데이터베이스와 소통하기 위한 것이다.
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name= 'comment' )
👉 related_name은 Comment가 참조하고 있는 User 모델에 생기는 것이기 때문에 참조해준 객체 입장에서 related_name을 설정해야 한다.(만약 related_name='user'라 한다면 "comment = user.user.all()"이라는 이상한 관계가 되어 버린다.)
Serialization(직렬화)은 컴퓨터 과학의 데이터 스토리지 문맥에서 데이터 구조나 오브젝트 상태를 동일하거나 다른 컴퓨터 환경에 저장하고 나중에 재구성할 수 있는 포맷으로 변환하는 과정이다. 오브젝트를 직렬화하는 과정은 오브젝트를 마샬링한다고도 한다.
간단히 말하면 명시된 fields의 데이터를 json 형식으로 전달하는 역할을 한다고 볼 수 있다.
/boards/serializer.py
from .models import Board
from users.serializers import UserSerializer
from rest_framework import serializers
class BoardSerializer(serializers.ModelSerializer):
class Meta:
model = Board
fields = [
"id",
"author_email",
"title",
"content",
"dt_created",
"dt_modified",
]
author_email = serializers.SerializerMethodField("get_authors_email")
def get_authors_email(self, obj):
return obj.author.email
class BoardDetailSerializer(serializers.ModelSerializer):
class Meta:
model = Board
fields = [
"id",
"author_email",
"title",
"content",
"dt_created",
"dt_modified",
]
author_email = serializers.SerializerMethodField("get_authors_email")
def get_authors_email(self, obj):
return obj.author.email
View를 작성하기 전, 다양한 class형 view가 있다는 것을 알게 되었다. 대표적으로는 'APIView', 'generic', 'Viewset'이 있는데 왼쪽에서 오른쪽 순으로 편의성이 높아진다. 검색해 보니 generic으로 만들어진 것이 많아, 일단 다수가 사용하는 것을 선택하기로 했다.
/boards/views.py
from .models import Board
from .serializers import BoardSerializer, BoardDetailSerializer
from rest_framework import generics
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from knox.auth import TokenAuthentication
from .permissions import IsOwnerOrReadOnly
class BoardAPI(generics.ListCreateAPIView):
queryset = Board.objects.all()
serializer_class = BoardSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class BoardDetailAPI(generics.RetrieveUpdateDestroyAPIView):
queryset = Board.objects.all()
serializer_class = BoardDetailSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsOwnerOrReadOnly]
GET, POST Method 두 가지의 요청이 가능하다.
GET, PUT, DELETE Method 세 가지의 요청이 가능하다.
boards/permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# 읽기 권한 요청이 들어오면 허용
if request.method in permissions.SAFE_METHODS:
return True
# 요청자(request.user)가 객체(Board)의 author와 동일한지 확인
if obj.author == request.user:
return True
myweb/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("boards.urls")),
path("auth/", include("users.urls")),
]
boards/urls.py
from django.urls import path, include
from . import views
app_name = "boards"
urlpatterns = [
path("board/", views.BoardAPI.as_view(), name="board-list"),
path("board/<int:pk>/", views.BoardDetailAPI.as_view(), name="board-detail"),
]
💡 이미 seed_data를 생성했기 때문에 아래와 같은 데이터가 나오는 것입니다. seed data를 생성하는 데 사용되는 django_seed는 Django REST framework로 게시판 CRUD (3) 글에 작성할 예정입니다. 또한, '게시물 작성하기'에서 토큰 authroization과 관련한 부분은 Django REST framework로 게시판 CRUD (2)에 작성할 예정입니다!
게시판 전체 불러오기
게시물 작성하기
특정 게시물 보기
특정 게시물 수정하기
PUT 혹은 PATCH 메소드로 요청할 수 있습니다.
토큰을 제공하지 않으면 아래와 같이 토큰이 제공되지 않았다는 메시지가 뜹니다.
토큰을 header에 함께 수정할 내용을 body에 담아 요청하면 아래와 같이 수정된 결과를 볼 수 있습니다.
특정 게시물 삭제하기