[인프런] Django REST Framework 핵심사항 학습 정리
1. django template view class를 사용해서 json 응답값 보내기
- BaseXXXView,View 등의 class를 상속받아 직접 리스트를 만들고 최종적으로 이를 딕셔너리로 만들어 JsonResponse()에 넣어 응답을 할 수 있다.
- DRF를 사용하지 않고서도 json 응답을 전송할 수 있다
- 예시
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)
2. 섹션 1 - DRF Example 따라하기
- url에는 json으로 보낼지 html으로 보낼지에 대한 format 설정을 할 수 있다.
- 예시
from rest_framework.urlpatterns import format_suffix_patterns
from blog import views
urlpatterns = [
path('', views.apt_root),
path('comments/', views.comment_list),
path('comments/<int:pk>/', views.comment_detail)
]
urlpatterns = format_suffix_patterns(urlpatterns, allowed=['json', 'html'])
- 참고
https://www.django-rest-framework.org/api-guide/format-suffixes/
- format_suffix_patterns을 사용하려면 view에 다음과 같은 'format' keyword argument 를 설정해야 한다.
@api_view(['GET', 'POST'])
def comment_list(request, format=None):
# do stuff...
class CommentList(APIView):
def get(self, request, format=None):
# do stuff...
def post(self, request, format=None):
# do stuff...
- https://www.django-rest-framework.org/ 사이트의 예제 코드를 실행하면 브라우저로 접근시 오른쪽 상단에 login 텍스트가 보인다.
- 해당 아이콘이 보이는 설정은 url에 다음과 같은 경로를 정의한다.
- 해당 경로는 login 텍스트의 보이는 유무의 설정만을 정의한다.
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
- 예제 코드에서 로그인을 하지 않으면 put, delete 등의 CUD 동작을 할 수 없다.
- 이것은 권한 설정과 연결되어 있다. (settings.py)
- 로그인 사용자만 CUD가 가능하고 로그인 하지 않으면 읽기(read)만 가능하다
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly"
]
}
3. 섹션 2 - DRF API 만들기
- app을 등록할때, {app 이름}.apps.{app config 클래스명} 으로 등록하는게 원칙이다.
- app 이름만 정의해도 나머지는 자동으로 등록이 된다.
- 자동으로 생성되는 url
- root api, format suffix
- 해당 url들은 router를 사용했을 때만 자동 생성이 된다.
- format suffix ex : users.json, users.api
- http://127.0.0.1:8000/api2/users.json, http://127.0.0.1:8000/api2/users.api
- 참고
- https://www.django-rest-framework.org/api-guide/routers/
- https://www.django-rest-framework.org/api-guide/routers/#defaultrouter
- simple 라우터와 default 라우터의 차이점은 API root view, .json style format suffixes의 자동 생성 여부이다.
- mixins 클래스를 모아서 generics 클래스를 만들고 이를 모아서 viewsets 클래스를 만들었다
- https://www.cdrf.co/ 참고 방법
- view를 클릭했을때 나오는 view의 순서들 중 제일 밑에 있는게 최상위 클래스이다. 이후 상속을 받는 순서로 나머지 view들이 상단에 있다.
- 기본 view -> apiview -> genericapiview의 상속 단계가 있다
- genericapiview에서 mix을 추가하여 create, destroy, list, update, retrieve를 만들게 된다.
- 동작과 view 그리고 헤더와 관계 정리
동작 | view | 헤더 action 정보 |
Create | CreateAPIView | POST |
Read | ListAPIView | GET |
RetrieveAPIView | GET | |
Update | UpdateAPIView | UPDATE, PATCH |
Delete | DestroyAPIView | DELETE |
- ListApiView
- 시작은 get()에서 시작함, self.list() 호출
- list를 보면, db로 부터 데이터를 가져와서 해당 데이터를 instance자리에 넣고 many를 True로 설정함
- RetrieveApiView
- 시작은 get()에서 시작함, self.retrieve 호출()
- retrieve를 보면, db로 부터 데이터를 가져와서 해당 데이터를 instance자리에 넣고 many를 False로 설정함
- UpdateAPIView
- put과 patch의 차이는 patch에는 kwargs['partial'] = True 이 설정되어 있다는 것이다.
- partial은 update에 요구되는 필수 속성이 없어도 업데이트가 가능하게 만드는 설정이다.
- 참고 : https://www.django-rest-framework.org/api-guide/serializers/#partial-updates
4. 섹션 3 - 좋아요 API 만들기
- python manage.py shell 실행하고 sirializer 클래스를 호출하면 각 필드 정보를 확인할 수 있다.
- 필수인지 아닌지를 확인하기 편하다
- 예시
- dict의 응답이 아닌, 단순 값만 보내고자 하는 경우, Reponse의 변수로 list를 정의하면 된다.
- 예 :
return Response(data["like"])
5. 섹션 4 - 직렬화 vs 역직렬화
- django shell에서 settings.py 불러오기
import os, django
os.environ.setdefault("DJANGO_SETTINGS_MODULE","{project_name}.settings")
django.setup()
- 참고 : https://www.django-rest-framework.org/api-guide/serializers/
- 직렬화/시리얼라이즈 과정
- 인스턴스 -> dict로 변환 -> json 형식의 bytes 스트림 -> 클라이언트 전송(render()가 역할을 해줌)
- 테스트 코드
- read 오퍼레이션
- GET
c0 = Comment.objects.all()[0]
sr = CommentSerializer(instance=c0)
data0 = sr.data
type(data0)
<class 'rest_framework.utils.serializer_helpers.ReturnDict'>
from rest_framework.renderers import JSONRenderer
JSONRenderer().render(data0)
- 역직렬화/디시리얼라이즈 과정
- json 형식의 bytes 스트림 -> dict로 변환 -> 인스턴스 -> DB 저장
- 테스트 코드
- write 오퍼레이션
- POST, UPDATE, DELETE, PATCH
json0 = JSONRenderer().render(data0)
type(json0)
<class 'bytes'>
from rest_framework_parsers import JSONParser
JSONParser().parser(json0) <- 데이터를 직접 넣으면 에러 발생함
from io import ByteIO
JSONParser().parser(BytesIO(json0))
{'id':1 .....}
ddata0 = JSONParser().parse(BytesIO(json0))
type(ddata0)
<class 'dict'>
dsr = CommentSerializer(data=ddata0)
dsr.is_valid() <- 필수
#확인
dsr.is_valid()
True
dsr.errors
{}
dsr.validated_data <- 유효성 검사 완료 후 데이터
instance = Comment(**dsr.validated_data)
instance.save()
6. 섹션 5 - 카테고리/태그 API 만들기
- dict의 key에 값을 리스트로 만들어 넣기
- 2가지 방법이 있다.
- 하나의 필드만 return 하는 serializer를 만들고 인자를 many=True로 정의하기
- 정확하게 리스트 형식으로 전달되지 않고 리스트에 키 값이 있는 딕셔너리로 정의되어 있다.
- 예시 : 뷰에서 넘겨주는 딕셔너리의 키 값과 정의하는 필드 값이 일치해야 한다.
class CateTagSerializer(serializers.Serializer):
cateList = CategorySerializer(many=True)
tagList = TagSerializer(many=True)
- 결과
- serializers.ListField(child=serializers.CharField())를 시리얼라이저 클래스에 정의하기
- 정확한 리스트 형식으로 전달 된다.
- 예시 : 뷰에서 넘겨주는 딕셔너리의 키 값과 정의하는 필드 값이 일치해야 한다.
class CateTagSerializer(serializers.Serializer):
cateList = serializers.ListField(child=serializers.CharField())
tagList = serializers.ListField(child=serializers.CharField())
- 결과
- 제너릭 뷰들은 하나의 테이블을 사용을 목적으로 만들어져 있기 때문에 여러 테이블을 처리해야 하는 경우에는 APIView, GenericAPIView를 사용해야 한다.
- GenericAPIView는 get_queryset이나 get_object와 같은 테이블 처리 메소드가 있기에 이를 재활용 할거면 사용할게 맞으나 재활용 하지 않을 거라면 APIView를 사용하는게 좋다.
- DRF에서 Generic이란 DB와 관련된 처리를 하는 View에 붙혀진 용어이다.
- APIView를 상속받아 리스트 시리얼라이즈를 만드는 뷰
class CateTagAPIView(APIView):
def get(self, request, *args, **kwargs):
cateList = Category.objects.all()
tagList = Tag.objects.all()
data = {
"cateList": cateList,
"tagList": tagList,
}
serializer = CateTagSerializer(instance=data)
return Response(serializer.data)
- 참고 : https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects
7. 섹션 6 - 출력 포맷 맞추기
- 단순한 목적의 class를 만들거나 여러개의 DB table을 처리해야 하는 class를 만들어야 하는 경우 APIView 혹은 GenericAPIView를 사용한다.
- get_object 혹은 get_queryset을 사용 하려면 GenericAPIView를 사용 해야 한다.
- 예시 :
class PostLikeAPIView(GenericAPIView):
queryset = Post.objects.all()
def get(self, request, *args, **kwargs):
instance = self.get_object()
instance.like += 1
instance.save()
return Response(instance.like)
- pagenation
- 참고 : https://www.django-rest-framework.org/api-guide/pagination/#pagenumberpagination
https://velog.io/@jewon119/TIL00.-DRF-Pagination-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0
https://velog.io/@chaeri93/Django-DRF-Pagination-APIView
- 전역 설정 :
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 100
}
- 뷰 별로 페이지 네이션 적용 :
class PostPageNumberPagination(PageNumberPagination):
page_size = 3
# page_size_query_param = 'page_size'
# max_page_size = 1000
def get_paginated_response(self, data):
return Response(OrderedDict([
('postList', data),
('pageCnt', self.page.paginator.num_pages),
('curPage', self.page.number),
]))
class PostListAPIView(ListAPIView):
queryset = Post.objects.all()
serializer_class = PostListSerializer
pagination_class = PostPageNumberPagination
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': None,
'format': self.format_kwarg,
'view': self
}
- 시리얼라이저에서 외래키의 pk가 아니라 특정 인자 값을 가져오려는 경우
- 필드를 재정의 하고 source 인자에 원하는 인자를 정의하면 된다.
class PostListSerializer(serializers.ModelSerializer):
category = serializers.CharField(source='category.name')
class Meta:
model = Post
# fields = '__all__'
fields = ['id', 'title', 'image', 'like', 'category']
- drf에서 imagefield의 full path 정보를 상대 정보로 출력하는 방법
- serializers.py:ModelSerializer -> fields.py:ImageField -> FileField를 보면 to_internal_value(시리얼라이저의 쓰기)와 to_representation(시리얼라이저의 읽기) 함수가 있다. to_representation 함수 내용을 보면 다음과 코드를 확인할 수 있다.
request = self.context.get('request',None)
if request is not None:
return request.build_absoute_uri(url)
return url
- request가 None이 아니면 full url을 만들고 None이면 보통(상대) url을 만든다.
- to_representation은 출력 포멧을 결정하기 때문에 오버라이딩을 자주 한다.
- context의 내용을 변경할 수 있는 방법을 사용할 수도 있다.
- ListAPIView -> GenericAPIView를 확인하면, get_serializer_context 함수를 확인할 수 있다. 해당 함수는 다음 코드를 리턴한다.
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
- 해당 함수를 오버라이딩 하여 request를 None으로 만든다.
class PostListAPIView(ListAPIView):
queryset = Post.objects.all()
serializer_class = PostListSerializer
pagination_class = PostPageNumberPagination
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': None,
'format': self.format_kwarg,
'view': self
}
- 이전 글, 다음 글 가져오기
- get_next_by_FOO, get_previous_by_FOO는 (FOO는 필드명) django에서 제공하는 기본 함수로 데이터가 없는 경우 DoesNotExist 에러를 발생시킨다.
- 사용 예시 :
def get_prev_next(instance):
try:
prev = instance.get_previous_by_update_dt()
except instance.DoesNotExist:
prev = None
try:
next_ = instance.get_next_by_update_dt()
except instance.DoesNotExist:
next_ = None
return prev, next_
class PostRetrieveAPIView(RetrieveAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializerDetail
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
prevInstance, nextInstance = get_prev_next(instance)
commentList = instance.comment_set.all()
data = {
'post': instance,
'prevPost': prevInstance,
'nextPost': nextInstance,
'commentList': commentList,
}
serializer = self.get_serializer(instance=data)
return Response(serializer.data)
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': None,
'format': self.format_kwarg,
'view': self
}
- DRF RelatedField
- 참고 : https://www.django-rest-framework.org/api-guide/relations/
- PrimaryKeyRelatedField로 정의하면 키 번호로 출력된다.
- 예시 :
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
# 결과
{
'album_name': 'Undun',
'artist': 'The Roots',
'tracks': [
89,
90,
91,
...
]
}
- StringRelatedField로 정의하면 문자열로 출력 된다.
- model의 __str__ 에 정의된 출력 방식에 따라 출력 된다.
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.StringRelatedField(many=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
# 결과
{
'album_name': 'Things We Lost In The Fire',
'artist': 'Low',
'tracks': [
'1: Sunflower',
'2: Whitetail',
'3: Dinosaur Act',
...
]
}
8. 섹션 7 - 한걸음 더
- 시리얼라이저를 사용하지 ㅇ낳고 직접 딕셔너리로 만들어 REsponse에 넣어주는 방법도 있다.
- ViewSet을 사용하지만 라우터는 사용하지 않는 경우 url 설정 방법
path('post/', views.PostViewSet.as_view(actions={
'get': 'list',
}), name='post-list'),
path('post/<int:pk>/', views.PostViewSet.as_view(actions={
'get': 'retrieve',
}), name='post-detail'),
path('post/<int:pk>/like/', views.PostViewSet.as_view(actions={
'get': 'like',
}), name='post-like'),
path('comment/', views.CommentViewSet.as_view(actions={
'post': 'create',
}), name='comment-list'),
- as_view의 인자 값으로 actions을 정의해 준다. 헤터 타입과 매칭되는 액션을 정의해 주면 된다.
- 만약 커스텀 액션과 매칭이 필요한 경우도 똑같이 정의하면 된다
- 예 : like 함수 등록 후 다음과 같이 등록
path('post/<int:pk>/like/', views.PostViewSet.as_view(actions={
'get': 'like',
}), name='post-like'),
'Study > django' 카테고리의 다른 글
[udemy] Build REST APIs with Django REST Framework and Python 학습 (0) | 2023.10.03 |
---|---|
drf tutorial - 주요 정리 (0) | 2023.09.02 |
DRF, 기본 django 공식 사이트 (0) | 2023.08.25 |
List of Useful URL Patterns, 정규 표현에 대해 정리 (0) | 2023.08.23 |
백엔드를 위한 django rest framework with 파이썬 책 후기 (0) | 2023.08.22 |
댓글