Study/django

[인프런] Django REST Framework 핵심사항 학습 정리

bluebamus 2023. 8. 29.

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 suffixes - Django REST framework

 

www.django-rest-framework.org

 - 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/

 

Routers - Django REST framework

 

www.django-rest-framework.org

      - https://www.django-rest-framework.org/api-guide/routers/#defaultrouter

 

Routers - Django REST framework

 

www.django-rest-framework.org

   - 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

 

Serializers - Django REST framework

 

www.django-rest-framework.org

 

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/

 

Serializers - Django REST framework

 

www.django-rest-framework.org

 - 직렬화/시리얼라이즈 과정

   - 인스턴스 -> 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

 

Serializers - Django REST framework

 

www.django-rest-framework.org

 

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

 

Pagination - Django REST framework

pagination.py Django provides a few classes that help you manage paginated data – that is, data that’s split across several pages, with “Previous/Next” links. — Django documentation REST framework includes support for customizable pagination styl

www.django-rest-framework.org

https://velog.io/@jewon119/TIL00.-DRF-Pagination-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0 

 

TIL126. DRF : Pagination 적용하기

📌 이 포스팅에서는 Django Rest Framework에서 제공하는 PageNumberPagination, LimitOffsetPagination, CursorPagination를 적용시키는 방법에 대해 정리하였습니다. 🌈 Pagination 적용하기 > ##

velog.io

https://velog.io/@chaeri93/Django-DRF-Pagination-APIView

 

[Django] DRF-Pagination APIView

지난 포스트에서는 viewset에서의 pagination을 소개했었다. 그러나 프로젝트를 진행하다 보면서 viewset을 사용하지 않고 커스텀하기 쉬운 APIview를 통해서 뷰를 짰다. 따라서 APIview에서는 다르게 pagina

velog.io

 - 전역 설정 :

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
        }

 - 이전 글, 다음 글 가져오기

   - 참고 :   https://docs.djangoproject.com/en/4.2/ref/models/instances/#django.db.models.Model.get_previous_by_FOO

 

Django

The web framework for perfectionists with deadlines.

docs.djangoproject.com

   - 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/

 

Serializer relations - Django REST framework

relations.py Data structures, not algorithms, are central to programming. — Rob Pike Relational fields are used to represent model relationships. They can be applied to ForeignKey, ManyToManyField and OneToOneField relationships, as well as to reverse re

www.django-rest-framework.org

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

 

댓글