앞서나온 내용을 바탕으로 model을 하나 만들고, 얼마나 심플하게 CRUD를 만들 수 있는지 한 번 직접해보자. 그리고 testing까지 해보자. ORM과 시리얼라이저를 통해 아주 깔끔하고 짧게 CRUD를 만들고 test까지 마무리 할 수 있다.
app을 create하는 부분은 생략하고, 로직 자체에 집중하겠다. python manage.py startapp
만든 app의 디렉토리 -> models.py에서 from django.db import models
에서 models의 Model을 활용하자.
class SearchDomain(models.Model):
domain_column = models.CharField(blank=False, null=False, unique=True, max_length=20)
value = models.CharField(blank=False, null=False, max_length=1024)
created_at = models.DateField(auto_now_add=True)
def __str__(self):
return str({
"domain_column": self.domain_column,
"vlaue": self.value
})
(가상환경은 당연하다) python manage.py makemigrations app_name
으로 우리가 만든 app의 수정된 Model 값을 마이그레이션 시켜줄 py 파일을 만들어 준다.
앱 디렉토리 하위에 migrations 폴더에 아래와 같이 파일이 생성되는 것을 볼 수 있다.
django.db의 migrations.Migration을 활용해서 우리가 만든 model -> migration python file -> DB schema create 로 흘러간다.
최소한의 자세한 사항은 공식 문서에서 migrations를 꼭 읽어보자, 미리 알고 있으면 좋은 django cli 명령어는 python manage.py showmigrations
과 python manage.py sqlmigrate
이다.
python manage.py showmigrations
는 DB 변경사항 목록과 상태를 출력한다.python manage.py sqlmigrate
는 실행할 SQL 명령문을 출력한다. 어떤 명령문을 실행할지 확인할 떄 사용하고, 튜닝이 안된 쿼리나 슬로우 쿼리 여부를 확인할 수 있다.python manage.py dumpdata
현재 DB의 내용을 백업할 때 사용한다.python manage.py loaddata
백업 파일에서 DB로 내용을 복구 할 때 사용한다.python manage.py migrate [app_name: 생략가능]
으로 우리가 만든 makemigrations file 기반으로 migrate를 진행 해준다.
규모가 조금 있을 것 같다면, python manage.py sqlmigrate
으로 sql 쿼리 체크 하고 슬로우 쿼리나 튜닝 대상을 꼭 체크하는 것을 추천한다.
DB가 바뀌어도, 해당 DB에 migration log가 없으면, 전체를 (즉 migration 파일이 만들어진 순서대로) 순차적으로 실행한다. table에 대한 create - drop이 잦다면, 버전 관리도 분명 고려 대상이다.
1. 마이그레이션 file 지움
2. select * from django_migrations; 를 통해 어떤 앱의 마이그래션 진행되었는지 다 지움
3. 캐시 지움
4. 디비에서 Drop table로 타겟 테이블 다 지움;
5. 이제 마치 처음 하는 것 마냥 makemigration → migrate 성공
함수형 말고 클래스형으로, 제네릭 상속 받아서 view API를 만들어 보자. DRF의 생산성을 최고점으로 끌어올려주는게 generics 다. 추상화 되어 있는 부분이 많기때문에 초반 러닝 커브는 있지만, 어느정도만 익숙해져도 사용하기 편하다.
우리가 만든 app -> view.py에 아래와 같이 코드를 짜보자.
from rest_framework import generics
# search domain model Create(post), Read(get)
class SearchDomainListCreateAPIView(generics.ListCreateAPIView):
queryset = SearchDomain.objects.all().order_by('-id')
serializer_class = SearchDomainSerializer
# SearchDomainSerializer 은 우리가 시리얼 라이저 따로 만들어서 임포트 해줘야 한다.
이렇게만 코드를 짜두고 시리얼라이저만 만들어주면 create(post), read(get)은 끝이다. generics.ListCreateAPIView 이 다 해주기 때문이다. 코드가 참 깔끔해져 버리고, 가시성도 확 와닿는다. 커맨드(컨트롤) + 좌클릭 으로 상속 받는 class를 따라가 대표적인 오버라이딩 전용 메소드(함수)들은 꼭 한 번 확인 하고 사용하길 바란다.
무엇을 오버라이딩 해서 사용해야 하나?
사실 update-one, many경우와 부분 update를 위해 partial=True를 활용하는 등 세부적으로 꼭 살펴봐야할 부분은 있다. 물론 create many도 말이다.
보통 ListCreateAPIView과 url에서 <int:pk> 또는 querystring 값을 request.GET.get("query") 활용해서 RetrieveUpdateDestroyAPIView 두 제네릭 클래스를 하나의 모델에 대해 만들어 둔다. 이렇게만 해두면 하나의 모델에 대해 CRUD는 끝이다. 생산성 하나는 기가막힌다.
필자는 spring에서 직접 만들어서 사용하는 http reqeust - response class와 비슷하다고 많이 느꼈다.
view에서 사용하는 SearchDomainSerializer를 만들자. django는 serializer를 기본적으로 사용하지 않기 때문에 serializers.py를 만들자.
from rest_framework import serializers
from search.models import SearchDomain
class SearchDomainSerializer(serializers.ModelSerializer):
class Meta:
model = SearchDomain
fields = "__all__"
기본적으로 serializers.ModelSerializer를 사용한다. 아주 기본적인 시리얼 라이저 형태이고, 커맨드(컨트롤) + 좌클릭 으로 상속 받는 class를 따라가보면 model의 filed 형태에 따라 매핑이 되어있는 것을 확인할 수 있다.
이제 request <-> response 모두 json 형태로 가능하다. json형태의 data를 우리는 시리얼라이저를 통해 python dict(더 정확하게는 model object) 처럼 사용이 가능해졌다. restAPI 가 위 간단한 model / view / serializer 코드로 끝이 났다.
url에 path('admin/searchdomain/', SearchDomainListCreateAPIView.as_view(), name='search-domain-list-createAPI')
로 해당 view를 매핑해주면 url 세팅도 끝이다.
GRANT ALL ON test_원래DB이름.* TO '유저'@'%';
flush privileges;
commit;
from django.test import TestCase
from django.urls import reverse, include, path
# 또는 직접 view를 import
from search.views import SearchDomainListCreateAPIView
from user.models import User
from rest_framework.test import APITestCase, APIClient, URLPatternsTestCase
from rest_framework.views import status
class SearchDomainListCreateAPIViewTestCase(APITestCase, URLPatternsTestCase):
urlpatterns = [
path('api/search/', include('search.urls')),
]
def test_get_search_domain_data(self):
# user = User.objects.get(username='여기에서 auth있는 user')
# client = APIClient()
# client.force_authenticate(user=user)
url = reverse(SearchDomainListCreateAPIView)
response = self.client.get(url, format='json')
print(dir(response))
self.assertEqual(response.status_code, status.HTTP_200_OK)
python manage.py test app_name
으로 테스트 진행을 하자!System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 1 tests in 0.377s
OK
테스트 DB없이 Unit Test 하기 in Django 글을 참조하며 Got an error creating the test database: (1044, "Access denied for user ...
에 대해 고려 없이 테스트 코드를 작성해보자.
먼저, django에서 제공하는 from django.test import TestCase
를 바로 사용하려면 django는 DB에 test_dbname... 우리가 설정한 db이름 앞에 test 접두사를 붙여 DB를 만들고 테스트를 진행하려고 한다. AWS의 RDS를 사용하면 사실 이런 부분이 '귀찮다'
그리고 우리가 test 전용 db가 있다면, (가령 개발계 DB로 테스트를 할 것이다) 해당 부분을 우리가 오버라이딩과 직접 설정값을 활용해서 테스트할 수 있다.
물론 더 깊은 얘기들이 있고, 실제 활용할때 알아야 할 부분은 더 많다. 하지만 기본적으로 위와 같은 뼈대에서 출발해 이것 저것 수정하며 알아보면 된다.
코드 자체에 대해서 더 깊은 사용은 ORM에 대한 이해도 필요하고, view에서 제네릭과 관련된 queryset, pagenation과 permission_classes = [IsAdminUser] 등으로 사용하는 rest-auth 가 더 기본적으로 알아야 할 부분이다. 물론 '리펙토링'을 해야 할 부분에 대해서도 말이다.
더 깊은 내용은 시리즈를 더 진행하며 단계적으로 정리할 예정이다.
해당 내용을 https://kimdoky.github.io/django/2018/08/21/drf-tda-1/ 와 같이 비교하며 읽으면 좋을 듯하다.