하루만에 완성하는 Django+DRF 서비스(2)

June
None
Published in
11 min readMar 2, 2023
출처: https://www.django-rest-framework.org/

안녕하세요. 휴먼스케이프 june입니다.

이번 시간엔 testcode를 작성, api 생성에 대해 알아보도록 하겠습니다.

프로젝트를 진행한 코드는 깃허브에 올라가 있으니 참고하시기 바랍니다.

테스트코드 작성

테스트코드는 간단히 e2e테스트코드만 작성하도록 하겠습니다.

개인적으로는 e2e테스트를 작성하고, 추가적으로 배포환경에서 이슈가 발생했을때 해당 케이스를 테스트코드로 작성 후 해결해나가며 테스트코드를 늘리는 쪽으로 작업합니다.

(물론 dev, staging phase에서 테스트를 진행하며 나오는 이슈도 테스트코드로 작성합니다.)

테스트코드를 작성하기 전, python답게 테스트코드를 효율적으로 작성할 수 있도록 도와주는 훌륭한 패키지를 추가해줍니다.

poetry add django-test-plus

이제 카테고리 crud(create/read/update/delete)에 대한 테스트코드를 추가해줍시다.

from test_plus.test import APITestCase

from .models import Category

class CategoryApiTestCase(APITestCase):
def setUp(self) -> None:
self.category = Category.objects.create(name='test category')
self.category2 = Category.objects.create(name='test category 2')

def test_get_categories(self):
response = self.get('/reviews/categories/')
self.assert_http_200_ok(response)
self.assertEqual(len(response.data), 2)
self.assertEqual(response.data[0]['name'], self.category.name)
self.assertEqual(response.data[1]['name'], self.category2.name)

def test_get_category(self):
response = self.get(f'/reviews/categories/{self.category.id}/')
self.assert_http_200_ok(response)
self.assertEqual(response.data['name'], self.category.name)

def test_create_category(self):
data = {'name': 'test category 3'}
response = self.post('/reviews/categories/', data=data)
self.assert_http_201_created(response)
self.assertEqual(response.data['name'], data['name'])
self.assertEqual(Category.objects.count(), 3)
self.assertEqual(Category.objects.last().name, data['name'])

def test_update_category(self):
data = {'name': 'test category 3'}
response = self.put(f'/reviews/categories/{self.category.id}/', data=data)
self.assert_http_200_ok(response)
self.assertEqual(response.data['name'], data['name'])
self.assertTrue(Category.objects.filter(name=data['name']).exists())
self.assertFalse(Category.objects.filter(name=self.category.name).exists())

def test_delete_category(self):
response = self.delete(f'/reviews/categories/{self.category.id}/')
self.assert_http_204_no_content(response)
self.assertFalse(Category.objects.filter(name=self.category.name).exists())
self.assertEqual(Category.objects.count(), 1)

아주 간단한 테스트코드를 작성했습니다.

테스트를 실행 python3 manage.py test 하게되면 당연히 실패합니다.

이제 api를 작성해 해당 테스트코드가 동작하도록 만들어주면 됩니다.

API 작성

drf의 구성 요소는 크게 model, serializer, view, url이 있으며, 필요하다면 filters, permissions등의 추가 요소가 사용됩니다.

model은 이미 작성했으니 serializer, view, url을 작성해 api를 작성해봅시다.

serializer는 django orm에서 쿼리의 리턴으로 사용하는 queryset , model instance등을 json등의 형식으로 변경해주고, 반대로 json형식을 model instance 등으로 변환해줍니다.

ModelSerializer를 상속한 serializer 클래스를 만들고, 사용할 모델을 model 에 넣어주면 해당 모델의 필드를 자동으로 인식해 serializer를 만들어줍니다.

from rest_framework import serializers
from .models import Review, Category

class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = "__all__"

이제 해당 serializer를 사용해 view를 만들어봅시다.

drf 문서를 보면 여러가지 view 종류가 있는데, 마트에서 쇼핑하듯 입맛에 맞는 클래스를 사용하면 됩니다.

저는 특별히 커스텀할 요소가 없으면 model에 맞게 기본적인 crud api를 만들어주는 ModelViewSet을 사용하도록 하겠습니다.

ModelView을 상속받고, queryset, serializer_class를 지정해주면 crud api가 생성됩니다.

from rest_framework import viewsets
from .models import Category
from .serializers import CategorySerializer

class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer

이제 만든 view를 특정 path에서 사용할 수 있도록 path와 view를 연결해줍니다.

django에서는 보통 urls.py에서 해당 로직을 처리하게 됩니다.

방금 만든 viewset을 처리하는 router를 만들고, 이를 urlpatterns에 넣으면 django에서 인식하게 됩니다.

from .views import CategoryViewSet
from rest_framework import routers
from django.urls import path, include

router = routers.DefaultRouter()
router.register(r'categories', CategoryViewSet)

urlpatterns = [
path('', include(router.urls)),
]

만든 urls.py를 기본 앱(settings.py가 위치한 바로 그 앱)의 urls에서도 연결시켜줍니다.

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

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

이제 django -> main urls.py -> reviews urls.py로 연결되어 categories api가 생성됩니다.

테스트코드를 다시 실행시켜보면 모두 성공하는것을 확인할 수 있습니다.

또, python manage.py runserver를 실행한 뒤 http://127.0.0.1:8000/reviews/categories/에 접속하면 실제로 api를 테스트해볼 수 있습니다.

Django admin

제게 Django의 최대 장점을 두가지 꼽으라면 높은 생산성과 자동 생성되는 어드민 페이지를 대답할 것 같습니다.

그만큼 Django admin은 매우 간편하게 사용할 수 있고, 사용하기에도 매우 용이합니다.

얼마나 간편하냐면, 단 1줄의 코드로 category의 어드민을 만들 수 있을 정도입니다.

이제 그 방법에 대해 알아보도록 하겠습니다.

python manage.py createsuperuser 명령어를 입력해 admin페이지에서 사용할 superuser를 생성합니다.

http://127.0.0.1:8000/admin/에 접속해 아까 생성한 계정정보를 입력하면

admin page를 확인할 수 있습니다.

지금은 아무 코드를 작성하지 않아 user, group만 관리할 수 있는 admin page입니다.

다시 코드로 돌아와, reviews/admin.py에 category, item, review model을 등록해줍시다.

from django.contrib import admin

from .models import Category, Item, Review

admin.site.register(Category)
admin.site.register(Item)
admin.site.register(Review)

(총 7줄이지만, category 부분만 보면 1줄… 1줄입니다.)

다시 어드민 페이지에 접속하면 다음과 같이 각 모델에 대한 탭이 생성된걸 확인할 수 있습니다.

category의 crud를 작성했으니, 이를 조금 응용해서 Item, review의 crud를 작성해주시면됩니다.(귀찮아서 스킵..)

다음 시간엔 category, Item, review의 모델 연결관계를 api에서 표현하는 방법, permission을 적용하는 방법(일반유저가 items를 삭제/수정하지 못하도록 관리)에 대해 알아보도록 하겠습니다.

--

--