개발 기록장

03. Python Django 프레임웍을 사용해서 API 서버 만들기(3) 본문

데브코스(DE)/장고 활용한 API 서버 제작

03. Python Django 프레임웍을 사용해서 API 서버 만들기(3)

jxwxnk 2024. 4. 10. 17:17
반응형
학습 주제:  Django Rest Framework(DRF), Serializer, ModelSerializer, HTTP Methods, Class 기반의 뷰(views), Mixin, Generic API View

Django Rest Framework(DRF)

: Django기반의 RESTful API 서버 구축을 위한 라이브러리

  • Serialize/Deserialize

         - Serialize: 모델 인스턴스나 Queryset과 같은 데이터를 JSON 형식으로 변환하는 과정

         - Deserialize: JSON 형식 데이터를 정의된 포맷에 맞추어 다시 모델 인스턴스로 변환하는 과정

Serialize/Deserialize 흐름

  • polls_api/serializers.py

         - serializers.Serializer

from rest_framework import serializers
from polls.models import Question

#Serializer 상속
class QuestionSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    question_text = serializers.CharField(max_length=200)
    pub_date = serializers.DateTimeField(read_only=True)

    def create(self, validated_data):
        return Question.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.question_text = validated_data.get('question_text', instance.question_text)
        instance.save()
        return instance

 

  • polls_api/serializers.py

         - serializers.ModelSerializer

         - serializers.Serializer 보다 코드 간결

from rest_framework import serializers
from polls.models import Question

#ModelSerializer
class QuestionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Question
        fields = ['id','question_text', 'pub_data']

HTTP Methods

: HTTP의 동사(CRUD 수행)

: 주어진 리소스에 수행하길 원하는 동작

  • GET

        - 데이터 조회(Read)

 

  • polls_api/views.py

         - 질문 리스트 나열

         - @api_view(): 인자가 아무것도 없으면 GET

from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework.response import Response
from rest_framework.decorators import api_view

@api_view()
def question_list(request):
    questions = Question.objects.all()
    serializer = QuestionSerializer(questions, many = True)
    return Response(serializer.data)

 

  • polls_api/urls.py

         - ex) http://127.0.0.1:8000/rest/question/

from django.urls import path
from .views import *

urlpatterns = [
    path('question/', question_list, name='question-list')
]

 

  • mysite/urls.py

         -  http://127.0.0.1:8000/rest/ 까지 include

from django.urls import include, path
from django.contrib import admin

urlpatterns = [
    path('admin/', admin.site.urls),
    path('polls/', include('polls.urls')),
    path('rest/', include('polls_api.urls')), #추가
]

 

  • POST

         - 데이터 생성(Create)

 

  • polls_api/views.py

         - 질문 생성

         - @api_view(['GET','POST']): 하나의 함수로 GET, POST 모두 수행

         - 조건문으로 GET, POST 구분

from rest_framework.decorators import api_view
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework.response import Response
from rest_framework import status

@api_view(['GET','POST']) #POST 추가
def question_list(request):

	#GET 조건절: Read
    if request.method == 'GET':
        questions = Question.objects.all()
        serializer = QuestionSerializer(questions, many = True)
        return Response(serializer.data)
    
    #POST 조건절 추가: Create
    if request.method == 'POST':
        serializer = QuestionSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED) #생성 성공 201
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) #실패 400

 

  • PUT/ DELETE

         - PUT: 데이터 업데이트(Update)

         - DELETE: 데이터 삭제(Delete)

 

  • polls_api/views.py

         - 상세(detail) 페이지에서 질문 수정 및 삭제

         - @api_view(['GET','PUT','DELETE]): 하나의 함수로 GET, PUT, DELETE 모두 수행

         - 조건문으로 GET, PUT, DELETE 구분

from django.shortcuts import get_object_or_404

@api_view(['GET', 'PUT', 'DELETE'])
def question_detail(request, id):
    question = get_object_or_404(Question, pk=id)
    
    #GET 조건절: Read
    if request.method == 'GET':
        serializer = QuestionSerializer(question)
        return Response(serializer.data)

	#PUT 조건절: Update
    if request.method == 'PUT':
        serializer = QuestionSerializer(question, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:    
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
	
    #DELETE 조건절L Delete
    if request.method == 'DELETE':
        question.delete()
        return Response(status=status.HTTP_204_NO_CONTENT) #삭제 성공 204

 

  • polls_api/urls.py

         - ex) http://127.0.0.1:8000/rest/question/1/

from django.urls import path
from .views import *

urlpatterns = [
    path('question/', question_list, name='question-list'),
    path('question/<int:id>/', question_detail, name='question-detail') #추가
]

코드 변형

: 코드를 다양한 방식으로 변형시켜 단순화하고, 기능은 유지한다.

  • Class 기반의 뷰(views)

         - Class안에 각 기능을 함수로 구현

  • polls_api/views.py

         - 각 페이지를 이루는 class안에 함수로 기능 정의

         - APIView 사용

from rest_framework.views import APIView

#질문 리스트
class QuestionList(APIView):

	#GET: Read
    def get(self, request):
        questions = Question.objects.all()
        serializer = QuestionSerializer(questions, many=True)
        return Response(serializer.data)

	#POST: Create
    def post(self, request):
        serializer = QuestionSerializer(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 QuestionDetail(APIView):

	#GET: Read
    def get(self, request, id):
        question = get_object_or_404(Question, pk=id)
        serializer = QuestionSerializer(question)
        return Response(serializer.data)
        
	#PUT: Update
    def put(self, request, id):
        question = get_object_or_404(Question, pk=id)
        serializer = QuestionSerializer(question, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:    
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        
    #DELETE: Delete
    def delete(self, request, id):
        question = get_object_or_404(Question, pk=id)
        question.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

 

  • polls_api/urls.py

         - .as_view(): 각 클래스 기존의 함수처럼 URL을 통해 실행

         - 질문 리스트: ex) http://127.0.0.1:8000/rest/question/

         - 상세 페이지: ex) http://127.0.0.1:8000/rest/question/1/

from django.urls import path
from .views import *

urlpatterns = [
    path('question/', QuestionList.as_view(), name='question-list'),
    path('question/<int:id>/', QuestionDetail.as_view(), name='question-detail'),
]

 

  • Mixin
  • polls_api/views.py

         - 각 페이지를 이루는 class안에 함수로 기능 정의

         - 위의 Class에서 함수를 모두 구현하는 방식보다 코드 간소화

         - 기능들이 구현되어있는 mixins 상속 

from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework import mixins
from rest_framework import generics

class QuestionList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class QuestionDetail(mixins.RetrieveModelMixin,
                    mixins.UpdateModelMixin,
                    mixins.DestroyModelMixin,
                    generics.GenericAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer

    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)

 

 

  • polls_api/urls.py

         - .as_view(): 각 클래스 기존의 함수처럼 URL을 통해 실행

         - <int: pk>로 꼭 작성해야함 (<int: id> 불가)

         - 질문 리스트: ex) http://127.0.0.1:8000/rest/question/

         - 상세 페이지: ex) http://127.0.0.1:8000/rest/question/1/

from django.urls import path
from .views import *

urlpatterns = [
    path('question/', QuestionList.as_view(), name='question-list'),
    path('question/<int:pk>/', QuestionDetail.as_view(), name='question-detail'),
]

 

 

  • Generic API View

         - 공식 문서: https://www.django-rest-framework.org/api-guide/generic-views/

 

Generic views - Django REST framework

 

www.django-rest-framework.org

 

  • polls_api/views.py

         - ListCreateAPIView: GET, POST method handlers

         - RetrieveUpdateDestroyAPIView: GET, PUT, DELETE,(...PATCH) method handlers

         - 기능들이 구현되어있는 generics 이용

from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework import generics

class QuestionList(generics.ListCreateAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer

class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer

Rest Framework 오류

: django.template.exceptions.TemplateDoesNotExist: rest_framework/api.html

  • Rest Framework 오류 메시지

         - Rest Framework 템플릿이 존재하지 않는다는 오류

브라우저에서 오류 확인

 

터미널 창에서 오류 확인

 

 

  • 해결책

         - Rest Framework 설치 확인: 템플릿이 존재하지 않는다는 오류

  • Rest Framework 설치 확인

         - pip freeze 명령어로 설치 확인: djangorestframework==3.15.1 이런식으로 나와 있음

pip freeze

 

 

         - 설치가 되어 있지 않은 경우:

pip install djangoresframework

 

  • Rest Framework 설치되어 있는데도 오류가 나는 경우

         - settings.py에 rest_framework 추가

 

# Application definition

INSTALLED_APPS = [
    'polls.apps.PollsConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', #추가
]

 

  • 정상 작동 화면

         - ex) http://127.0.0.1:8000/rest/question/ 

오류 해결


공부하며 느낀점

 

Mixin과 Generic API View를 사용해서 코드를 간소화하면서, 개발자가 꾸준히 더 좋은 코드를 위해 공부해야 하는 이유를 깨달았다. 

반응형