ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Django] DRF를 이용한 API 서버 만들기 (2)
    개발/Python 2021. 12. 25. 14:57

     

    Django DRF를 이용한 API 서버 만들기 (1)

    새로운 프로젝트를 시작하면서 장고를 사용했다. 웹 서비스를 만드는 프로젝트고 흔히들 사용하는 기본적인 게시판 기능이 포함된 서비스를 만들었다. 이전에 소개딩이랑 이것저것 준비하면서

    www.floodnut.com

     

    이전 포스팅을 통해 장고의 기본 서버를 열어보았다.

    이번 포스팅에서는 세부적인 내용으로 들어가보겠다.

     

     

     

    1. urls.py

    ## config/urls.py
    
    from django.contrib import admin
    from django.urls import path, include
    
    urlpatterns = [
        path('admin/', admin.site.urls),
    
        
        path('api/',include('app1.urls')), # app1의 url 관리
    ]
    
    ########################################################
    
    ## app1/urls.py
    
    from django.urls import path
    
    urlpatterns = [
        path('<int:pk>/', 호출대상),
    ]

    우선 URL을 지정하는 방식에 대해 알아보자.

    장고는 프로젝트 디렉토리에 기본적으로 생성되는 urls.py에서 웹 서버에 접근할 수 있는 uri들을 지정해줄 수 있다.

    여기서 장고 앱은 기본적으로 urls.py를 생성하지 않는다.

    장고 앱인 app1 디렉토리 하위에 urls.py를 생성하고 config의 urls.py에서 위와 같이 앱 별로 uri를 지정해보자.

     

    만약 서버 주소 app1의 '호출대상' 기능을 호출하고 싶다면 api/<int:pk>/로 접근하면 된다.

    이때, include 메서드를 통해서 api/ 경로 하위의 모든 uri를 app1에서 관리하게 된다.

     

     

    2. views.py

    여기서 추가적으로 <int:pk>에 대해 알아보자.

    장고에서는 클래스형 뷰와 함수형 뷰가 있다. 나의 코드가 스파게티 코드가 된 이유들이다.

     

    API 서버에서 특정 URI를 호출하면 그에 맞는 로직이 동작해야한다. 이때, 그 코드를 작성하는 곳이 views.py이다.

    API 서버가 아닌 단일 웹 서비스를 만든다면 다음에 알아볼 serializers.py가 아닌 forms.py 를 이용하게 된다.

     

    우선 내가 정의할 기능은 기본적인 블로그 CRUD 기능이다.

    사실, 클래스형 뷰를 통해 ModelViewSet을 이용한다면 HTTP 메소드만 변경해서 CRUD를 바로 수행할 수 있다.

    또, 뷰셋의 내부에 존재하는 메서드를 오버라이딩해서 구현할 수도 있을 것이다.

     

    그렇지만 솔직히 말해서 클래스형 뷰가 처음에 조금 이해하기 어려운 점도 있어서 함수형 뷰로 작성하다보니

    뒤죽박죽 섞이게 된 경향이 있다.

     

    from rest_framework import viewsets, serializers, status
    from .serializers import *
    from .models import Activity, Chapter, Chaptercomment, Chapterfile
    
    ###
    
    class ActivityViewSet(viewsets.ModelViewSet):
        queryset = Activity.objects.all()
        serializer_class = ActivitySerializer
        
    class ChapterViewSet(viewsets.ModelViewSet):
        queryset = Chapter.objects.all()
        serializer_class = ChapterSerializer
    
    
    class ChaptercommentViewSet(viewsets.ModelViewSet):
        queryset = Chaptercomment.objects.all()
        serializer_class = ChaptercommentSerializer
    
    
    class ChapterfileViewSet(viewsets.ModelViewSet):
        queryset = Chapterfile.objects.all()
        serializer_class = ChapterfileSerializer

    기본적으로 CRUD는 데이터베이스를 이용하는 것이다.

    그렇다면 우리는 데이터베이스에서 조회한 쿼리셋과 데이터베이스에 접근할 일종의 인터페이스인 시리얼라이저를 구현해야한다.

    위와 같이 클래스형 뷰로 기능을 정의한다면 ModelViewSet 내부의 메서드 들을 그대로 사용하게 된다.

     

    @api_view(['GET', 'POST'])
    def activity_list(request):
        context = {'request': request}
        if request.method == "GET":
            activities = Activity.objects.all()
            serializer = ActivityListSerializer(activities, many=True, context=context)
    
            return Response(serializer.data)
    
        elif request.method == "POST":
            try:
                authori = request.META['HTTP_AUTHORIZATION']
                token = JWTValidation(request.META['HTTP_AUTHORIZATION'].split()[1])
                authed = token.decode_jwt()
                user_instance = User.objects.get(id=authed['user_id'])
            except:
                return Response('auth_error', status=status.HTTP_400_BAD_REQUEST)
            serializer = ActivityListSerializer(data=request.data, context=context)
            if serializer.is_valid(raise_exception=True):
                serializer.save(author=user_instance)
                serializer = ActivityListSerializer(activity_instance, context=context)
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    그렇다면 함수형 뷰로 구현한 기능을 살펴보자.

    위 코드는 GET, POST 메서드에 대해 각각 Read, Create 기능을 적용한 것이다.

    내부에 토큰 인증은 추후에 다시 알아보도록 하겠다.

     

    우선 URL을 통해 받은 요청을 인자로 받는다.

    해당 인자에서 HTTP 메서드를 판단하여 GET일 경우 Activity라는 데이터베이스 모델에 해당하는 테이블 전체를 조회한다. 이는 쿼리셋으로 반환된다.

     

    받아온 정보를 ActivityListSerializer를 통해 사용자가 보는 브라우저 화면으로 반환해주는 형태이다.

     

    POST 일 때를 보자.

    우선 토큰 인증을 거친다. 

    이 후 GET일 때와는 다르게 곧바로 serializer를 호출하는데 기본적으로 데이터베이스 입력에 해당하는 동작이고

    시리얼라이저는 데이터베이스와 장고 서버간의 인터페이스 역할을 하니 이 시리얼라이저를 통해 POST요청으로 넘어온 정보를 데이터베이스에 넣기 위해 정렬하는 것이다.

     

    시리얼라이저를 사용한다면 이 객체 내부에 유효한 값이 있는지 is_valid를 통해 확인해야한다.

    유효한 정보라면 정보를 저장할 수 있는데 이때, 인자를 통해 특정 컬럼의 데이터를 작성해줄 수 있다.

     

    3. serializers.py

    from rest_framework import serializers
    from activity.models import *
    from managing.models import *
    from managing.serializers import *
    
    ###
    
    class ActivityListSerializer(serializers.HyperlinkedModelSerializer):
        tags = Tag_IdSerializer(many=True, read_only=True)
        participants = User_IdSerializer(many=True, read_only=True)
    
        class Meta:
            model = Activity
            fields = ('url', 'id', 'title', 'type', 'author', 'createDate', 'description',
                      'startDate', 'endDate', 'currentState', 'viewerNum', 'tags', 'participants')
                      
    class ActivitySerializer(serializers.HyperlinkedModelSerializer):
        tags = Tag_IdSerializer(many=True, read_only=True)
        participants = User_IdSerializer(many=True, read_only=True)
        chapterid = ChapterListSerializer(many=True, read_only=True)
    
        class Meta:
            model = Activity
            fields = ('url', 'id', 'title', 'type', 'author', 'createDate', 'description',
                      'startDate', 'endDate', 'currentState', 'viewerNum', 'tags', 'participants', 'chapterid')

    그렇다면 이 시리얼라이저를 잠깐 살펴보자

    앞서서 일부 코드를 생략하여 설명하였으니 이 부분은 혼동하지 말기를

     

    우선 시리얼라이저는 장고에서 정의한 데이터베이스 모델과 사용할 필드(컬럼)에 접근하기 위한 수단이다.

    이 시리얼라이저를 json 형태로 반환하기 위해 ModelSerializer를 사용한다.

    나는 여기서 호출한 URL 또한 같이 넘겨주기 위해 HyperlinkedModelSerializer를 사용했다.

    이것들은 사용환경에 따라 선택적으로 사용하면 된다.

     

    그렇다면 내가 정의한 시리얼라이저 내부에서 호출한 다른 시리얼라이저들에 대해 간단하게 설명해보자.

    우리는 데이터베이스를 설계할 때, 여러 테이블 간의 참조 관계를 활용해야하는 경우가 생긴다.

    특정 컬럼은 외래키로 다른 테이블을 참조하고 있고 이 정보들 또한 같이 반환하고자 할 때, 다른 곳에서 정의한 시리얼라이저를 호출한다면 같이 정보를 반환해줄 수 있다. 

    4. models.py

    class Activity(models.Model):
        id = models.AutoField(primary_key=True)
        title = models.CharField(max_length=50)
        type_CHOICES = (
            ('CTF', 'CTF'),
            ('Study', 'Study'),
            ('Project', 'Project'),
        )
        type = models.CharField(max_length=50, choices=type_CHOICES)
        author = models.CharField(max_length=50)
        createDate = models.DateField(db_column='createDate')
        description = models.CharField(max_length=65)
        startDate = models.DateField(db_column='startDate')
        endDate = models.DateField(db_column='endDate')
        currentState_CHOICES = (
            (0, '0 : 예정'),
            (1, '1 : 진행'),
            (2, '2 : 종료'),
        )
        currentState = models.PositiveIntegerField(db_column='currentState', default=0,
                                                   choices=currentState_CHOICES)
        viewerNum = models.PositiveIntegerField(db_column='viewerNum', default=0)
    
    
        class Meta:
            managed = True
            db_table = 'activity'
    
        def __str__(self):
            return self.title

     

    마지막으로 기본 요소 중 데이터베이스의 스킴을 확인하는 models.py를 알아보자.

    이 자체가 데이터베이스에 직통으로 연결되는 값은 아니다.

     

    장고가 '나는 이러한 데이터베이스 테이블과 컬럼을 사용할거야' 라고 정의해놓는 값이고 마이그레이션을 통해 이 정보를 가지고 시리얼라이저가 데이터베이스에 접근한다.

    물론 models.py의 객체를 통해 직접 데이터베이스에 접근할 수도 있다.

     

    장고가 데이터베이스를 구성할 때는 두 가지 방법이 있다.

     

    먼저 데이터베이스를 구성하고 장고에 정의하는 방식이다.

    python3 manage.py inspectdb

    우리가 장고를 실행할 때 위와 같은 명령을 입력한다면?

    우리가 생성한 테이블과 컬럼 정보를 장고의 models.py로 바로 적용할 수 있도록 변환 값을 출력해준다.

     

    두번째 방식은 models.py를 먼저 정의하는 방식이다.

    위의 예시처럼 데이터베이스 모델을 작성하고 managed 옵션을 True로 준다.

    그 후 아래와 같은 명령 입력을 통해 마이그레이션 시점에서 데이터베이스 테이블을 새롭게 생성할 수 있다.

    python3 manage.py makemigrations
    python3 manage.py migrate --run-syncdb

     

     

    이처럼 우리는 기본적인 CRUD 작성하고 데이터베이스에 접근할 수 있게 된다.

    사실 코드 작성에 급급한 나머지 설명도 빈약하고 나 조차도 무엇을 알고 무엇을 모르는지 확신을 내릴 수 없다...

    그래서 이 포스트를 작성하면서 다시금 정리하려고 한다.

     

    다음은 이번 글의 연장선 격으로 작성해보겠다.

    댓글

Designed by Tistory.