본문 바로가기

개발 관련/python

[django] REST API 구축하기

http://www.yes24.com/Product/Goods/69758579

 

배프의 오지랖 파이썬 웹프로그래밍 - YES24

기초부터 실전 웹 서비스까지 다 담았다!현직 개발자이자 IT강사인 배프의 강의와 실무를 통해 쌓은 노하우를 한 단계식 알려드립니다. 현 웹 프로그래밍 시장은 다양한 기술을 활용하여 얼마나

www.yes24.com

위 책을 참고하여 쓴 내용이다.

 

1. django 설치 및 프로젝트 생성하기

pip install django	# conda install django

django-admin startproject config .

python manage.py migrate

python manage.py createsuperuser

 

2. 앱 만들고 INSTALLED_APPS에 등록

python manage.py startapp booking

config/settings.py

3. 모델 만들고 데이터베이스에 적용

# booking/models.py

from django.db import models
from django.conf import settings


class Booking(models.Model):
    subscriber = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='bookings')

    date_from = models.DateField()
    date_to = models.DateField(null=True, blank=True)
    room = models.CharField(max_length=100)
    note = models.TextField()

    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.subscriber.username + " " + self.room

    class Meta:
        ordering = ['-date_from']
python manage.py makemigrations booking

python manage.py migrate booking

 

4. 관리자 페이지 등록

만든 모델의 관리를 위해 관리자 페이지에 등록해준다.

# config/admin.py
from django.contrib import admin

from .models import Booking


class BookingAdmin(admin.ModelAdmin):
    list_display = ['id', 'subscriber', 'room', 'date_from', 'date_to', 'created', 'updated']
    list_editable = ['room', 'date_from', 'date_to']
    raw_id_fields = ['subscriber']


admin.site.register(Booking, BookingAdmin)

python manage.py runserver 후에 http://127.0.0.1:8000/admin/booking/ 에 접속하여 예약 몇개를 추가한다.

 

5. API 환경 만들기

Django REST FrameWork 설치해 사용하기

pip install djangorestframework

# conda install -c conda-forge djangorestframework

 config/settings.py에 추가

 

6. Serializer 클래스 구현하기

Serializer는 요청한 모델을 API로 보여줄 때 사용하는 클래스로, 보통 GET 방식으로 모델에 대한 데이터를 요청했을 때 Serializer를 활용해 데이터를 제공한다.

booking 폴더 아래에 serializers.py 파일을 추가하고, 아래와 같이 작성한다.

from .models import Booking
from rest_framework import serializers


class BookingSerializer(serializers.ModelSerializer):
    class Meta:
        model = Booking
        fields = '__all__'

 

7. 뷰 만들기

booking/views.py에 다음과 같이 입력한다.

from rest_framework import generics

from .models import Booking
from .serializers import BookingSerializer


class BookingList(generics.ListCreateAPIView):
    queryset = Booking.objects.all()
    serializer_class = BookingSerializer


class BookingDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Booking.objects.all()
    serializer_class = BookingSerializer

 

8. booking/urls.py 생성하고 연결하기

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from .views import *

urlpatterns = [
    path('booking/', BookingList.as_view()),
    path('booking/<int:pk>/', BookingDetail.as_view()),
]

 

config/urls.py에도 include 시켜주기

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('booking.urls')),
]

 

python manage.py runserver

위 명령어를 입력해 API가 잘 동작하는지 확인한다.

 

http://127.0.0.1:8000/booking/번호 로 접근하면 예약의 상세 정보를 볼 수 있다.

수정도 해보고 제대로 반영이 되었는지 확인한다.

제대로 동작한다.

 

9. API 문서 만들기

API 문서를 자동으로 생성해주는 앱을 설치한다.

pip install django-rest-swagger

config/setttings.py에 등록한다.

config/urls.py도 수정한다.

from django.contrib import admin
from django.urls import path, include
from rest_framework_swagger.views import get_swagger_view


urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/doc/', get_swagger_view(title='Booking API Manual')),
    path('', include('booking.urls')),
]

get_swagger_view 메서드를 추가하면 각종 API 뷰들을 찾아서 자동으로 문서를 만들어준다.

오류가 났다.

찾아보니 django-rest-swagger 패키지는 더이상 관리를 안한다고 한다. 책이 2019년에 나온걸로 아는데...역시

그래서 삭제하고, drf-yasg를 깔았다. drf-yasg는 django rest framework - yet another swagger generator의 약자다.

pip uninstall django-rest-swagger

pip install drf-yasg

config/settings.py를 수정한다.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'booking',
    'rest_framework',
    'drf_yasg',
]

 

다음은 config/urls.py를 수정한다. drf-yasg 공식 문서를 참고했다.

https://drf-yasg.readthedocs.io/en/stable/readme.html

 

drf-yasg - Yet another Swagger generator — drf-yasg 1.20.1 documentation

Since the schema does not usually change during the lifetime of the django process, there is out of the box support for caching the schema view in-memory, with some sane defaults: caching is enabled by the cache_page decorator, using the default Django cac

drf-yasg.readthedocs.io

# config/urls.py

from django.contrib import admin
from django.urls import path, include, re_path
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi


schema_url_patterns = [
    path('', include('booking.urls')),
    ]

schema_view = get_schema_view(
    openapi.Info(
        title="Django API",
        default_version='v1',
        description="장고 예약 API",
        terms_of_service="https://www.google.com/policies/terms/",
    ),
    public=True,
    permission_classes=[permissions.AllowAny],
    patterns=schema_url_patterns,
)


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('booking.urls')),
    re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
    re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
    re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]

django.conf.urls.urls()는 장고 3.0에서는 deprecated 되었고, 장고 4.0+에서는 삭제되었다고 한다.

현재 내 장고 버전이 4점대이기 때문에 django.urls.re_path()로 대신하여 사용했다.

실행하고 설정한 url(swagger, reddoc)을 접속하니 잘 나온다.

 

10. 인증 추가하기

API는 보통 모든 사람이 사용할 수 있게 하지 않고, 문제를 일으킬 수 있으므로 인증 수단을 추가하는 것이 옳다.

다음을 참고하자. https://drf-yasg.readthedocs.io/en/stable/security.html

 

Describing authentication schemes — drf-yasg 1.20.1 documentation

When using the swagger-ui frontend, it is possible to interact with the API described by your Swagger document. This interaction might require authentication, which you will have to describe in order to make swagger-ui work with it. swagger-ui as OAuth2 cl

drf-yasg.readthedocs.io

토큰 인증 방식은 django rest framework에서 제공하는 앱이기 때문에 settings.py에 rest_framework.authtoken 추가만 해주자

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'booking',
    'rest_framework',
    'rest_framework.authtoken',
    'drf_yasg',
]

token 앱도 model을 지원하고 있기 때문에 migrate를 해주자.

python manage.py migrate

 

booking/views.py도 바꿔보자.

from rest_framework import generics

from .models import Booking
from .serializers import BookingSerializer

from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated


class BookingList(generics.ListCreateAPIView):
    queryset = Booking.objects.all()
    serializer_class = BookingSerializer


class BookingDetail(generics.RetrieveUpdateDestroyAPIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = (IsAuthenticated,)
    
    queryset = Booking.objects.all()
    serializer_class = BookingSerializer

authentication_classes는 어떤 인증 방식을 사용할 것인지 설정하고,

permission_classes는 인증을 해야만 볼 수 있다는 옵션을 지정할 수 있다.

 

views.py에서 목록은 인증 기능 처리를 안하고 수정이나 삭제가 가능한 detail에만 인증 기능을 처리했다.

때문에 목록은 인증 없이도 잘 보인다.

하지만 상세보기를 하면 인증이 필요하다는 오류가 나는 것을 볼 수 있다.

token을 발급받아서 접속해보자.

httpie를 설치했다.

brew install httpie

 

앞서 생성한 obtain_auth_token에서 token을 발급받는다.

http POST http://localhost:8000/api-token-auth/ username="{username}" password="{pw}"

 

발급 받은 토큰을 카피하고, api에 접근한다. 다음과 같이 입력한다.

http POST http://localhost:8000/booking/ "Authorization: Token {토큰}"

제대로 나오는 것을 볼 수 있다.

 

 

11. API 문서에서 Token 기능 사용해보기

다음은 token 획득을 API의 endpoint로 노출시켜보자.

admin에 접속한다.

[Tokens] - [Add]에 사용자를 추가하고 Token을 발급받자.

 

config/settings.py에 다음을 추가하고, 앱을 실행 후 swagger에 접속한다.

SWAGGER_SETTINGS = {
      'SECURITY_DEFINITIONS': {
         'DRF Token': {
               'type': 'apiKey',
               'name': 'Authorization',
               'in': 'header'
         }
      },
    "LOGIN_URL": "/admin/login/",
    "LOGOUT_URL": "/admin/logout/"
   }

SECURITY_DEFINITIONS 옵션을 이용하면 사용자가 Authorize 버튼을 이용해 Token 등의 API 키를 입력할 수 있다.

또 Login_url, logout_url도 추가해서 문서상 로그인/로그아웃 기능도 동작하도록 했다.

 

오른쪽의 [Authorize]를 클릭하고, 토큰을 입력하자.

추가 후에는 booking API의 테스트를 해볼 수 있다.

curl 명령 등을 이용해 테스트해보면 좋지만 문서 상에서 API 키를 입력할 수 있으면 더 편리하다. 이와 같이 설정하면 API 문서 상에서 API의 동작을 테스트해볼 수 있다.

원하는 테스트 결과를 받을 수 있다.

 

12. 추가 권한 설정하기

예약에 관한 사항은 관리자는 다 볼 수 있어야 한다. 이런 기능은 권한을 별도로 설정해주면 된다.

앱 폴더에 permissions.py 파일을 만들고 원하는 권한을 입력해주자.

# booking/permissions.py

from rest_framework import permissions


class IsOwnerOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        return obj.subscriber == request.user or request.user.is_superuser
    

class IsOwnerOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        
        return obj.subscriber == request.user or request.user.is_superuser

IsOwnerOnly는 소유자나 관리자인 경우에만 사용 가능하도록 하는 권한이고,

IsOwnerOrReadOnly는 소유자나 관리자가 아닌 경우 GET 이나 HEAD 같은 조회 기능만 가능하도록 하는 권한이다.

이를 뷰에 적용해보자.

from .permissions import IsOwnerOrReadOnly
class BookingDetail(generics.RetrieveUpdateDestroyAPIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = (IsAuthenticated,IsOwnerOrReadOnly)

    queryset = Booking.objects.all()
    serializer_class = BookingSerializer

기존에는 토큰이 있는 사용자만 조회할 수 있었다면, 이제 IsOwnerOrReadOnly를 통해 소유자가 아닌 경우 수정은 불가능하도록 만들었다.

 

 

'개발 관련 > python' 카테고리의 다른 글

[crawling] HTTP error 403  (0) 2022.01.12
[flask] 블루프린트(blueprint)  (0) 2022.01.05
[flask] 플러거블 뷰(pluggable view)  (0) 2022.01.05
[flask] 리다이렉션과 에러  (0) 2022.01.05
[difflib] 두 문자열 비교하기  (0) 2021.12.20