일러두기
이 글은 Django와, 그에 속해 있는 Django REST Framework(이하 DRF)에 관한 전반적인 것을 작성한 글입니다. 또한 Django보다 DRF에 중점을 두어 작성됨을 알려드립니다.
목차
0. Django의 아주 간단한 소개
우리는 이제 아주 넓고 얕게 Django를 알아볼 것입니다. 하지만 그 전에 Django가 무엇인지에 관해 아는 것이 중요할 것입니다. 이 글은 아주 넓고 얕게 Django를 훑는 것을 목적으로 하기 때문에 간단하게 소개하겠습니다.
Django는 2005년부터 시작된 Python 오픈 소스 풀스택 웹 프레임워크입니다.
현재까지 3.2이 나왔으며 꾸준히 업데이트가 진행되고 있습니다. 또한 Django Software Foundation
에서는 새로운 버전 배포와 동시에 로드맵을 갱신하고 있습니다.[1]
Django는 백엔드 프레임워크로만 생각할 수도 있습니다. 하지만 Django는 풀스택 프레임워크로, Django 하나만으로 프론트와 백 모두 할 수 있습니다. (하지만 이 글에서는 Django를 백엔드 용도로만 제한할 것입니다. 프론트는 멋진 프론트 개발자들이 대신해줄 것이기 때문입니다!)
1. Django의 디자인패턴
본격적으로 Django에 대해 알아볼 것입니다. Django는 프레임워크인만큼 디자인 패턴을 이용해 만들어졌습니다.
수 많은 디자인 패턴 중, Django는 MVT 패턴을 통해 만들어졌습니다. MVT는 MVC 패턴에서 대응되는 패턴이지만 여기서는 MVC 패턴이 무엇인가에 대해 얘기하지 않습니다.
MVT는 각각 Model(모델), View(뷰), Template(템플릿)로 구성되어 있습니다. 마찬가지로 여기서는 각 구성요소가 어떻게 상호작용하며, 어떻게 유기적으로 연결되어있고 이를 위해 코드는 어떻게 짜여졌고, 이러쿵 저러쿵에 관한 것은 다루지 않습니다. 단지 추상화를 하여 간단하게 짚고 넘어가겠습니다.
우리가 만드는 웹 서비스를 하나의 자판기라고 가정해보죠. 자판기는 사용자에게 어떤 음료수가 있는지에 관해 보여줄 것이며, 사용자가 값을 입력하면 그에 맞는 음료수를 사용자에게 줄 것입니다. 또한 자판기는 음료수를 저장하고 있습니다.
여기서 자판기가 저장하고 있는 음료수를 Model로 얘기할 수 있으며, 사용자가 입력한 값에 맞는 음료수를 사용자에게 주는 일을 하는 것을 View라고 할 수 있으며, 또한 어떤 음료수가 있는 지 보여주며 값을 입력하는 곳은 어딘지 보여주는 것을 Template라고 할 수 있습니다.
1.1. 통상적인 Django의 MTV 패턴
1.1.1 Model(모델)
Model은 DB에 저장되는 모든 데이터를 뜻합니다. Django는 ORM[2]을 제공하기 때문에 모든 데이터들은 객체(class)로 정의 되어 있습니다.
자판기에서는 저장하고 있는 음료수로 치환할 수 있습니다.
1.1.2. View(뷰)
View는 사용자의 요청에 맞는 로직을 실행하고 그 결과값을 Template로 전달합니다.
자판기에서는 사용자가 값을 입력하면, 그 값에 맞는 음료수를 주는 과정으로 치환할 수 있습니다.
1.1.3. Template(템플릿)
Template는 사용자에게 보여지는 화면 자체를 뜻합니다. Template는 context라는 데이터와 html을 랜더링하여 사용자에게 보여줍니다. 또한 Django에서는 Template 문법을 제공하여 이 문법을 통해 html 파일에서 곧바로 context를 활용할 수 있습니다.
자판기에서는 어떤 음료수가 있는 지 보여주는 화면으로 치환할 수 있습니다.
1.2. DRF에서의 MTV
우리는 통상적인 Django에서의 MVT 패턴에 관해 훑어보았습니다. 하지만 위에서 언급했듯이, 이 글에서 Django는 백엔드의 용도로만 제한하고 있습니다.
즉, MVT 중 Template을 사용하지 않는다는 뜻입니다! Django 개발자가 직접 Template를 작성하지도 개발하지도 않으며 이를 프론트 개발자에게 맡긴다는 것이죠.
하지만 위에서 우리는 View가 결과값을 Template에 전달한다고 배웠습니다! 또한 html 파일에서 곧바로 context를 전달할 수 있는 것도 아닙니다.
그렇다면 우리는 어떻게 프론트 개발자가 개발한 프론트에게 결과값을 전달할까요?
Django REST Framework(DRF)를 통해 소통할 것입니다.
DRF는 Django에서 REST API를 사용하기 위해 만들어진 라이브러리입니다. 이 라이브러리를 통해 우리는 프론트(Template)와 소통할 수 있게 되었습니다! DRF는 프론트와의 소통을 위해 JSON 형태로 데이터를 주고 받습니다.
하지만 View는 JSON 형태를 알지 못하고 우리 또한 알지 못합니다.(읽고 있는 당신은 알 수도 있습니다.) 때문에 우리는 이를 해결 해 줄 Serializer(시리얼라이저)에 관해 훑어봐야합니다.
1.2.1. Serializer(시리얼라이저)
serializer는 View로 들어온 JSON 데이터를 Python에서 활용하기 쉽도록 직렬화[3] 및 역직렬화 해주는 역할을 합니다.
아래 그림은 serializer가 어떤 역할을 하는 지 간단하게 추상화한 그림입니다.
dict 형태의 데이터를 JSON으로 바꿀 수도 있으며, JSON 형태의 데이터를 dict 형태로 바꿀 수도 있습니다. 혹은 객체(object)를 JSON 데이터로 바꿀 수도 있습니다.
이럴 수가! 우리는 아까 Django에서 ORM을 사용한다는 것을 읽었습니다. 때문에 serializer가 Model
을 JSON 형태로 바꿀 수도 있다는 것을 알 수 있습니다!
2. Django는 어떻게 실행되는가
우리는 Django가 무엇이며, 어떻게 구성되어 있는 지에 관해 훑어 보았습니다.
이제 우리는 Django가 실질적으로 어떻게 동작하는지, 즉 흐름에 관해 알아볼 것입니다. 반복해서 언급했듯이 이 글은 통상적인 Django를 얘기하지 않습니다. 백엔드의 용도로만 제한한, Django 그리고 DRF에 관해 얘기할 것입니다.
2.1. 실행
Django 프로젝트를 하나 만들었습니다. 우리는 DRF를 이용할 것이며 이제 우리는 Django를 실행할 것입니다. 그렇다면 먼저 console에 명령어를 입력해야합니다.
python manage.py runserver
이렇게 명령어를 입력했습니다. Enter와 동시에 명령어가 실행될 것입니다.
manage.py의 다른 명령어에 관해서 알아보지는 않을 것입니다. 그저 우리는 python manage.py runserver
를 console에 입력하면 실행이 된다는 것만 알면 됩니다.
한 번 manage.py 파일을 확인해볼까요?
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.dev')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
아주 복잡해보입니다. 하지만 우리가 주목할 부분만 집중해서 살펴보겠습니다.
def main():
"""Run administrative tasks."""
"""
생략
"""
if __name__ == '__main__':
main()
python을 공부해봤다면 이 코드가 무엇을 뜻하는 지 알 수 있을 것입니다. 바로 main이라는 함수를 실행하는 코드입니다. 즉, main를 실행하여 Django가 실행된다는 것을 우리는 알 수 있습니다.
2.2. 사용자의 요청
이제 Django를 실행했습니다. Django 서버가 열심히 실행되고 있습니다. 그렇다면 사용자의 요청이 들어올 것입니다.
우리가 실행한 Django 서버에는 Post라는 Model을 정의했다고 가정할 것입니다.
class Post(models.Model):
text = models.TextField()
Post라는 Model은 위와 같이 정의되어있습니다.
사용자는 URL을 통해 Post의 데이터들을 요청할 것입니다. Django는 URL을 통해 사용자의 요청을 판단하며, URL에 따라 호출되는 View 또한 다릅니다. 이를 선언한 파일이 바로 urls.py입니다.
GET post/
사용자가 post/로 요청을 보냈습니다. 우리는 post/로 요청이 들어왔을 때 어떻게 처리할 것인지에 관해 urls.py에 작성해두었습니다.
한 번 확인해볼까요?
from . import PostListView
urlpatterns = [
path('post/', PostListView.as_view()),
]
위의 코드는 post/로 요청이 들어왔을 때, PostListView를 실행하겠다는 뜻입니다. 즉, PostListView에서 작성한 로직이 실행된다는 것을 알 수 있습니다.
그렇다면 PostListView는 어떻게 작성되어 있을까요? View들은 views.py에 선언되어있습니다.
class PostListView(ListAPI):
serializer_class = PostSerializer
queryset = Post.objects.all()
우리는 이 코드에 관해 깊게 탐구할 수도 있습니다. 하지만 얕게 훑어보는 것으로 만족하고 넘어갈 것입니다.
class PostListView(ListAPIView):
첫 줄에서 우리는 PostListView가 ListAPI라는 객체를 상속받은 것을 알 수 있습니다.
ListAPI는 DRF에서 제공하는 객체로 Django 개발자가 쉽게 View를 개발할 수 있도록 해주는 객체입니다. 저 객체를 상속받는 것으로 개발자가 일일히 코드를 작성하는 것을 없애줍니다.
다음 줄로 넘어가볼까요?
serializer_class = PostSerializer
queryset = Post.objects.all()
먼저 queryset 변수에 관해 알아볼 것입니다.
queryset이라는 변수는 어떤 데이터(Model)을 보낼 것인지에 관한 변수입니다. Post.objects.all()
이라는 코드는 Post 데이터를 모두 가져온다는 뜻입니다. 아주 간단한 코드이지만 실제로는 ORM을 통해 SQL문이 실행됩니다.
또한 DRF의 View에는 serializer_class라는 변수가 있습니다. 이 변수는 어떤 serializer를 쓸 것인지에 관한 변수입니다. 이 serializer_class를 통해 지정한 serializer를 이용해 queryset을 JSON 형태로 바꿀 것입니다.
즉, PostListView는 모든 Post 데이터를 가져와 PostSerializer을 이용해 JSON 데이터로 바꿔 보내주는 View임을 알 수 있습니다!
2.2.1 ListAPIView
간단한 코드만으로 View가 만들어질 수 있는 것은, Django에서 제공하는 ListAPIView를 상속받았기 때문입니다.
DRF에서는 프로젝트에서 공통적으로 사용되는 기능들을 미리 작성하여 상속받는 것만으로도 쉽게 해당 기능을 구현할 수 있도록 되어있습니다.
DRF의 흐름을 이해하기 위해서 우리는 ListAPIView를 훑어볼 것입니다. 정의를 보는 것이 무서울 수 있습니다. 하지만 겁을 낼 필요 없습니다. 아주 천천히 부분적으로만 훑어볼 것이기 때문입니다.
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
ListAPIView의 정의는 위와 같습니다.
이 코드를 간단히 요약하면, 사용자가 GET 요청을 보낼 경우, list()라는 함수를 실행시킨다는 내용입니다.
그렇다면 list()의 정의를 확인해볼까요?
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
코드가 아주 복잡해 보입니다. 하지만 2.1.에서 그랬던 것처럼 필요한 부분만 살펴볼 것입니다.
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
"""
생략
"""
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
코드가 한결 보기 편해졌습니다! 이제 한 번 살펴보겠습니다.
queryset = self.filter_queryset(self.get_queryset())
위에서 PostListView에서 봤던 것처럼 queryset 변수가 있습니다. 여기서는 우리가 보지 못한 함수가 호출되어 queryset 변수에 들어가고 있습니다.
filter_queryset()와 get_queryset()은 함수명에서 볼 수 있듯이 queryset을 가져온다는 것을 알 수 있습니다. 그렇다면 이 queryset은 어디서 가져오는 것일까요?
바로 우리가 PostListView에서 선언한 값입니다.
class PostListView(ListAPI):
serializer_class = PostSerializer
queryset = Post.objects.all()
Post.objects.all()
이 list()에서 filter_queryset()과 get_queryset()을 통해 불려지는 것입니다.
그렇다면 이 불려진 데이터를 우리는 직렬화 과정을 거쳐 Object에서 JSON 형태로 바꿔줘야합니다. 이를 해주는 것이 바로 serializer이죠?
바로 다음 줄에 작성 되어 있습니다.
serializer = self.get_serializer(queryset, many=True)
serializer 변수에 또 다시 알지 못하는 함수의 반환값이 변수에 넘겨지고 있는 것을 볼 수 있습니다.
get_serializer()는 이름에서 알 수 있듯이 serializer를 가져오는 함수를 뜻합니다. 위에서 우리가 filter_queryset()과 get_queryset()에서 알 수 있었던 것처럼 앞서 살핀 PostListView에서 선언한 serializer_class의 값을 가져오는 것을 알 수 있습니다.
하지만 다른 점이 있습니다. 바로 매개 변수를 넣어준다는 점입니다. queryset과 many=True
라는 값을 넣어주고 있습니다. queryset은 Model 데이터들이라면, many=True
는 어떤 값을 뜻하는 걸까요?
바로 매개 변수명에서 알 수 있듯, 여러 개의 데이터들을 직렬화한다는 것을 뜻합니다. iterable한 객체(list, dict, set…)들을 직렬화한다는 것입니다.
return Response(serializer.data)
마지막 줄입니다. Response라는 함수에 serializer의 data를 넘겨주고 있습니다.
요청(Request)와 응답(Response) 중, 서버는 Request를 받았으므로 Response를 사용자에게 넘겨주어야합니다.
사용자는 Post들의 목록을 Reponse로 받기를 원하기 때문에, serializer을 통해 JSON 형태로 바뀐 데이터를 넘겨주는 것입니다.
이제까지 알아본 것을 정리할 수 있습니다.
- 사용자가 URL에 GET으로 요청(Request)을 보냈습니다.
- URL에 맞춰 실행되기로 한 View가 실행됩니다.
- View에 선언한 queyset이 list()에 넘겨지게 됩니다.
- 넘겨진 queryset은 마찬가지로 View에서 선언한 serializer를 통해 JSON 형태로 바뀝니다.
- JSON으로 바뀐 데이터를 사용자에게 응답(Response)합니다.
이렇게 List API를 통해 아주 얕게 DRF의 흐름을 알아보았습니다.
3. 추가적 내용
이 섹션은 넘겨도 무관한 내용입니다. 이제까지 알아본 내용들의 응용 내용입니다.
PostListView에서 ListAPIView 상속했듯이, Django는 각 APIView에서 사용하는 함수들을 override 할 수 있습니다.
3.1 get_queryset()
2.2.1에서 보았던 get_queryset()을 이용해 여러 조건을 추가하거나 변형할 수 있습니다.
가령 상속받은 View에서 선언한 queryset을 사용하지 않고 직접 값을 넘길 수도 있습니다.
class PostListView(ListAPI):
serializer_class = PostSerializer
def get_queryset(self):
return Post.objects.all()
혹은 전체를 가지고 오는 데이터에 필터링을 걸 수도 있습니다.
class PostListView(ListAPI):
serializer_class = PostSerializer
def get_queryset(self):
return Post.objects.filter(text__icontains='test')
위의 코드는 Post들 중, text에 ‘test’라는 문자열이 포함되어있는 Post들을 가져와 넘겨주는 코드입니다.
4. 맺음말
이렇게 아주 얕고 넓게 Django의 흐름을 알아보았습니다. 저는 DRF의 전체적인 흐름을 알기까지 꽤 많은 시간이 걸렸어요. 때문에 이 글을 읽는 것으로 전체적인 흐름에 감이 잡히실 수 있다면 무척 기쁠 거 같네요. 또 번역투로 한 번 작성해보았는데 작성하면서도 어색하네요. 😅
정말 얕지만 전체적인 흐름에는 도움이 됐기를 바랍니다.
5. 주석
1: https://static.djangoproject.com/img/release-roadmap.3c7ece4f31b3.png
2: Object Relational Mapping. DB의 데이터를 객체(object)로 변형 및 연결하는 것.
3: 데이터 구조나 오브젝트 상태를 동일하거나 다른 컴퓨터 환경에 저장하고 나중에 재구성할 수 있는 포맷으로 변환하는 과정.