티스토리 뷰
복습 겸 RESTful API를 활용
django rest framework는 어떨 때 쓰는 것인가?
mysite2에 있는 urls
urlpatterns = [
path('', HomeView.as_view(), name='home'),
path("admin/", admin.site.urls),
path("blog/", include('blog.urls')),
path("api/", include('api.urls')),
api 앱이 있다.
카테고리와 좋아요 버튼, 태그의
blog의 models에 3개가 있다.
Post,Category, Tag
Post안에 Foriegnkey = Post가 m이고, category가 1인 1:M 관계
tags
tag.post_set으로 관계 설정
TemplateView를 상
app_name = 'api'
데이터를 응답하는 녀석들
app_name = 'api'
urlpatterns = [
path('post/list/', views.ApiPostLV.as_view(), name='post_list'),
path('post/<int:pk>/', views.ApiPostDV.as_view(), name='post_detail'),
path('catetag/', views.ApiCateTagView.as_view(), name='catetag_list'),
path('like/<int:pk>/', views.ApiPostLikeDV.as_view(), name='post_like'),
path('comment/create/', views.ApiCommentCV.as_view(), name='comment_create'),
]
127.0.0.1:8000/api/post/list
그러면 json 파일이 나온다.
{"postList": [{"id": 6, "title": "\ubc1c\ub9ac (Bali)", "image": "/media/blog/2022/10/06.jpg", "like": 0, "category": "\uc544\uc2dc\uc544"}, {"id": 7, "title": "\uc0b0\ud1a0\ub9ac\ub2c8(Santorini)", "image": "/media/blog/2022/10/07.jpg", "like": 0, "category": "\uc720\ub7fd"}, {"id": 8, "title": "\uba5c\ud06c \uc218\ub3c4\uc6d0 (Melk abbey)", "image": "/media/blog/2022/10/08.jpg", "like": 0, "category": "\uc720\ub7fd"}], "pageCnt": 4, "curPage": 1}
image를 context 형태로 넘겨주는 게아니라 문자열 형태로 넘겨줘야 한다.
앞에 Base가 붙여져 있는 제네릭 뷰는 조금 커스터마이징 해서 써야 한다.
from django.views.generic.detail import BaseDetailView
from django.views.generic.edit import BaseCreateView
from django.views.generic.list import BaseListView
아래 코드 같은 경우도 상속받아서 구현한 것이다.
get_queryset은 문자열을 뽑아내는 작업을 한다.
class ApiPostLV(BaseListView):
# model = Post
paginate_by = 3
def get_queryset(self):
paramCate = self.request.GET.get('category')
paramTag = self.request.GET.get('tag')
if paramCate:
qs = Post.objects.filter(category__name__iexact=paramCate)
elif paramTag:
qs = Post.objects.filter(tags__name__iexact=paramTag)
else:
qs = Post.objects.all()
return qs.select_related('category').prefetch_related('tags')
def render_to_response(self, context, **response_kwargs):
qs = context['object_list']
postList = [obj_to_post(obj, False) for obj in qs]
pageCnt = context['paginator'].num_pages
curPage = context['page_obj'].number
# print(pageCnt, curPage)
jsonData = {
'postList': postList,
'pageCnt': pageCnt,
'curPage': curPage,
}
return JsonResponse(data=jsonData, safe=True, status=200)
post/list에서 어떠한 파미터도 넘기지 않았다.
select_related('category')
prefetch_related('tag')
post와 관계되어 있는 카테고리와 태그를 가져오도록 메소드로 제공한다.
select_related = 1:N 관계
prefetch_realted = M:N 관계
tag하고 post는 다대다, post와 category는 1:n관계이므로 한꺼번에 가져와서 사용한다.
render_to_response = 응답 데이터 처리
jsonData로 바꿔서 응답해준다.
두 번째 url
api/list/post로 들어가보자.
디테일 뷰이다.
class ApiPostDV(BaseDetailView):
# model = Post
def get_queryset(self):
return Post.objects.all().select_related('category').prefetch_related('tags', 'comment_set')
def render_to_response(self, context, **response_kwargs):
obj = context['object']
post = obj_to_post(obj)
prevPost, nextPost = prev_next_post(obj)
qsComment = obj.comment_set.all()
commentList = [obj_to_comment(obj) for obj in qsComment]
jsonData = {
'post': post,
'prevPost': prevPost,
'nextPost': nextPost,
'commentList': commentList,
}
return JsonResponse(data=jsonData, safe=True, status=200)
http://127.0.0.1:8000/api/post/1/
세번째 URL
http://127.0.0.1:8000/api/catetag
class ApiCateTagView(View):
def get(self, request, *args, **kwargs):
qs1 = Category.objects.all()
qs2 = Tag.objects.all()
cateList = [cate.name for cate in qs1]
tagList = [tag.name for tag in qs2]
jsonData = {
'cateList': cateList,
'tagList': tagList,
}
return JsonResponse(data=jsonData, safe=True, status=200)
카테고리 리스트와 tag 리스트를 반환하는 뷰이다.
입력하고 submit하면 버튼
class ApiCommentCV(BaseCreateView):
model = Comment
fields = '__all__'
def form_valid(self, form):
self.object = form.save()
comment = obj_to_comment(self.object)
return JsonResponse(data=comment, safe=True, status=201)
def form_invalid(self, form):
return JsonResponse(data=form.errors, safe=True, status=400)
여기까지의 기능은 장고의 기본 기능을 활용
이런 Framework을, 따로 적용하지 않고도 효율적으로 활용할 수 있도록 하는게
Django REST Framework이다.
Django REST Framework
REST의 정의
기존의 작업은 서버에서 요청을 받아서 처리를 하고, 템플릿을 클라이언트에 응답을 내보낸다.
이제 템플릿 페이지는 없고, 데이터(JSON) 형태로 응답한다.
요청정보는 헤더하고 바디로 이루어져 있고, 헤더의 첫번째 라인 요청 방식, 그 다음에 uri가 들어간다.
요청 방식과 URI로 CRUD를 구분할 수 있게 하는 게 REST이다.
GET
POST외에도 PUT,Delete가 있다.
URI
클라이언트가 서버한테 요청하는 방식이 결정된다.
GET -> READ 방식
POST -> CREATE 방식
PUT -> UPDATE
DELETE -> DELETE 방식
요청 방식에 따라서 요청하는 서비스가 결정된다.
서버 쪽에서 요청을 받고, 모델을 가지고 DB에서 처리가 된다.
그걸 내보내는 사이클 자체를 REST API라고 하고, 응답을 내보내주는 해당 서비스 자체를 RESTful이라고 이야기 한다.
REST API 설계 규칙
1. 요청 방식과 URI 가 제일 중요하다.
2. URI는 정보의 자원을 표현해야 한다.
3. 자원에 대한 행위는 HTTP Method(GET,PUT,POST,DELETE등)으로 표현한다.
4. 슬래시 구분자(/)는 계층 관계를 나타낸다.
5. URI 마지막 문자로 슬새시를 포함하지 않는다.
6. 밑줄 은 URI URI에 사용하지 않는다
7. URI 경로에는 소문자가 적합하다
8. 파일확장자는 URIURI에 포함하지 않는다
9. 하이픈 은 URI 가독성을 높이는데 사용한다.
장고 REST framework 활용하기
https://www.django-rest-framework.org/
DRF URI는 무조건 해당 격식에 맞는
GET, /api/post - action : list
GET, /api/post/99 - retrieve
POST /api/post - create
PUT /api/post/99 - update
DELETE, /api/post/99 - action : delete
일부 요청 및 삭제 - PATCH - partial_update
REST Framework 설치 및 앱 등록
pip install djangorestframework
mysite2/settings.py
INSTALLED_APPS에 추가해준다
[ "rest_framework"
]
새로운 앱을 api2로 만든다. 그리고 mysite2/urls.py에 등록한다.
python manage.py startapp api2
mysite2/urls.py
path("api2/", include('api2.urls')),
api 가이드에서 seializers 클릭
https://www.django-rest-framework.org/api-guide/serializers/
from rest_framework import serializers
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
serializer = CommentSerializer(comment)
serializer.data
# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}
폼이나 모델 상속 받아서 만드는 것과 매우 비슷하다.
json 형태의 구조에서, 하나의 딕셔너리 인스턴스가 해당 필드가 된다.
그냥 시리얼 라이즈 말고, model seializer도 있다. 모델을 지정하면 자동으로 serializer를 만들어준다.
RESTful API 구현
model과 serializer는, client에서 요청을 받아서, DB에서 데이터를 받아서 json 데이터로서 client에 전달한다.
APIView라고 붙어 있으면 View 객체이다.
CreateAPIView - post
DestroyAPIView - delete
GenericAPIView
ListAPIView - get
RetrieveAPIView - 뽑아서 응답해주는 객체
UpdateAPIView - put,patch
전부다 APIView를 상속받고 있다.
from rest_framework.views import APIView
https://www.cdrf.co/3.14/rest_framework.views/APIView.html
해당하는 메소드들이 있다.
CreateAPIView
def create = Post에 해당하는 단어이다.
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
destroryAPIView
delete, destrory가 따로 있다.
delete는 삭제 작업, destroy는 아예 완전 삭제
ViewSETS
viewset은 여러가지 기능이 통합되어 있는 기능이다.
실제 구현
시리얼라이즈 객체를 먼저 만들어서 처리해야 한다.
api2/serializers.py
from django.contrib.auth.models import User
from rest_framework import serializers
from blog.models import *
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ['url', 'username', 'email', 'is_staff']
auth 앱에서 제공하는 user를 받는다.
해당하는 유저의 데이터와 필드를 정의한다.
그러면 해당하는 4개의 데이터를 받아서 json으로 응답하게 된다.
api2/views.py
from django.contrib.auth.models import User
from rest_framework import viewsets
from .serializers import *
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
queryset과 serializer가 필수이다.
queryset에서 전체 object를 뽑고, 해당하는 요청을 뱉어준다.
이제 해당하는 view에 어떤 요청이 들어오느냐에 따라서 알아서 내부적으로 처리가 된다.
api2/urls.py에 새로운 앱을 생성해준다.
from django.urls import path, include
from rest_framework import routers
from .views import UserViewSet
router = routers.DefaultRouter()
router.register('users', UserViewSet)
urlpatterns = [
path('', include(router.urls)),
]
일단은 UserViewSet을 활용한다.
url을 자동으로 만들어주는 객체 = routers.DefaultRouter()
DefaultRouter도 똑같이 요청 방식에 따라 action이 결정된다.
해당하는 URL이 자동으로 결정된다.
나중에는 해당하는 것도 APIView로 바꿀 것이다.
from django.urls import path
from . import views
urlpatterns = [
path('post/', views.PostListAPIView.as_view(), name='post-list'),
path('post/<int:pk>/', views.PostRetrieveAPIView.as_view(), name='post-deatil'),
path('comment/', views.CommentCreateAPIView.as_view(), name='comment-list'),
path('post/<int:pk>/like/', views.PostLikeAPIView.as_view(), name='post-like'),
path('catetag/', views.CateTagAPIView.as_view(), name='catetag'),
]
http://127.0.0.1:8000/api2/users/
에 들어가면, user의 방식을 볼 수 있다.
http://127.0.0.1:8000/api2/users.json 하면, json 파일도 볼 수 있다.
이제 http://127.0.0.1:8000/api2/users/ 아래에서 post 방식으로 요청한다.
그럼 다음과 같이 accept 되는 걸 볼 수 있다.
get 버튼을 누르면, get 방식으로 바뀐다.
http://127.0.0.1:8000/api2/users/6/
해당하는 유저 인스턴스에 직접 들어가면, put 방식으로 요청해서 바꿀 수 있다.
여기서는 Delete도 가능하다.
PostSerializer
api2/serializers.py
from blog.models import *
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['id','title','image','like','category']
from blog.models import *
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
urls.py
from django.urls import path, include
from rest_framework import routers
from .views import *
router = routers.DefaultRouter()
router.register('users', UserViewSet)
router.register('post', PostViewSet)
urlpatterns = [
path('', include(router.urls)),
]
이런 다음, http://127.0.0.1:8000/api2/post/에 가면 post list를 확인할 수 있다.
각 action마다 지정하고 싶다고 하면 다 따로 따로 지정해야 한다.
view에서는 Router 대신 URL path 직접 정의를 해야 한다.
ModelVIEW 말고, ListAPIView 를 상속받아서 해야 한다.
Comment REST API 구현
똑같이 serializers.py
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
views.py
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
urls.py에도 url 하나만 더 추가하면된다.
from django.urls import path, include
from rest_framework import routers
from .views import *
router = routers.DefaultRouter()
router.register('users', UserViewSet)
router.register('post', PostViewSet)
router.register('comment', CommentViewSet)
urlpatterns = [
path('', include(router.urls)),
]
똑같이 http://localhost:8000/api2/comment/로 들어가면 된다.
그러면 커멘트 창이 나온다.
Router 대신 URP path 직접 정의하기
router 대신 PostRetrieveSelrializer를 사용해서 직접 url을 정의한다.
serializer.py
class PostListSerializer(serializers.ModelSerializer):
category = serializers.CharField(source='category.name')
class Meta:
model = Post
fields = ['id','title','image','like','category']
class PostRetrieveSerializer(serializers.ModelSerializer):
category = serializers.StringRelatedField()
tags = serializers.StringRelatedField(many=True)
class Meta:
model = Post
exclude = ['create_dt']
views.py에도 직접 정의한다.
from rest_framework.generics import *
# class PostListAPIView(ListAPIView):
# queryset = Post.objects.all()
# serializer_class = PostListSerializer
# class PostRetrieveAPIView(RetrieveAPIView):
# queryset = Post.objects.all()
# serializer_class = PostRetrieveSerializer
class CommentCreateAPIView(CreateAPIView):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
urls.py를 고쳐준다.
from django.urls import path
from . import views
urlpatterns = [
path('post/', views.PostListAPIView.as_view(), name='post-list'),
path('post/<int:pk>/', views.PostRetrieveAPIView.as_view(), name='post-deatil'),
path('comment/', views.CommentCreateAPIView.as_view(), name='comment-list'),
]
여기는 이전 예제 하고의 차이점이 상속을 받아서 사용하기에, 기본 페이지인 api2는 오류가 난다.
http://localhost:8000/api2/post/
좋아요 기능 구현
views.py
class PostLikeAPIView(UpdateAPIView):
queryset = Post.objects.all()
serializer_class = PostLikeSerializer
urls.py에도 추가
path('post/<int:pk>/like/', views.PostLikeAPIView.as_view(), name='post-like'),
http://127.0.0.1:8000/api2/post/6/like/ - get방식 연결
raw data에서 다 지워버리고 patch를 누르면 되는데, put은 오류난다.
get으로 불러왔을 때는 405 에러가 나는 걸 볼 수 있다.
patch는 일부 필드만 대상으로 삼기 때문에 오류가 안 나고, put은 모든 필드를 대상으로 삼기 때문에 오류가 난다.
from rest_framework.response import Response
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
data = {'like': instance.like+1}
serializer = self.get_serializer(instance, data=data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
instance._prefetched_objects_cache = {}
return Response(data['like'])
postlikeAPIview에서, 다음과 같은 함수를 안에 넣어준다.
그러면 모든 필드가 다 안넣어진다.
아니면 그냥 응답받는 json 구조를 like만 넣어주면 된다.
class PostLikeSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['like']
좋아요 필드만 보내주려면,
class PostLikeAPIView(UpdateAPIView):
queryset = Post.objects.all()
serializer_class = PostLikeSerializer
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
data = {'like': instance.like+1}
serializer = self.get_serializer(instance, data=data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
instance._prefetched_objects_cache = {}
return Response(serializer.data)
마지막의 Response를 serializer.data로 바꿔주면 된다.
불러오는 serializer_class도 postlikeserializer로 바꿔준다.
http://127.0.0.1:8000/api/like/6/
좋아요 필드만 보려면 위와 같이 하면 된다.
좋아요 숫자만 보내기
class PostLikeAPIView(UpdateAPIView):
queryset = Post.objects.all()
serializer_class = PostLikeSerializer
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
data = {'like': instance.like+1}
serializer = self.get_serializer(instance, data=data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
instance._prefetched_objects_cache = {}
return Response(data['like'])
다시 데이터를 data like로 바꾸면 된다.
위에서 했던 건 json 데이터를 전체 보낸다. 그러나 이건 딱 그 숫자만 보낸다.