동적으로 템플릿을 생성하기 위해서는 먼저 ORM
에 대해 알아야한다.
객체 관계 맵핑 (Object Relational Mapping, ORM)
객체 관계 매핑(Object-relational mapping; ORM)은 데이터베이스와 객체 지향 프로그래밍 언어 간의 호환되지 않는 데이터를 변환하는 프로그래밍 기법이다. 객체 관계 매핑 라고도 부른다. 객체 지향 언어에서 사용할 수 있는 “가상” 객체 데이터베이스를 구축하는 방법이다. 객체 관계 매핑을 가능하게 하는 상용 또는 무료 소프트웨어 패키지들이 있고, 경우에 따라서는 독자적으로 개발하기도한다.
출처: 위키백과
이전에도 알아보았듯이 데이터베이스는 SQL
을 통해 관리된다. 물론 데이터베이스를 관리하는데 쓰이는 언어가 SQL
만 있는 것은 아니지만, SQL
이 가장 대중적으로 쓰이고 있다. 그렇다면 데이터베이스를 다룰 때 SQL
을 반드시 알아야 할까? 우리는 SQL
을 배우지 않았지만 이미 데이터베이스에 모델을 통해 테이블을 추가하였고, 다섯 개의 Post
도 만들었었다. 이는 ORM
덕분에 가능한 것이다. ORM
은 데이터를 하나의 객체로 보고 그것을 객체 지향 프로그래밍 언어로 제어하면 그에 맞는 SQL
문을 내부적으로 보내어 실제 데이터베이스를 제어하게 해준다.
데이터베이스의 테이블은 ORM
을 통해 모델로 표현되고, 하나의 데이터 즉, 레코드 (Record) 는 하나의 객체로 취급된다.
말로 설명하는 것보다 한 번 직접 활용해보는 것이 이해에 큰 도움이 되므로 직접 한 번 해보자.
shell_plus 설치
ORM
을 위한 개발 환경을 먼저 구축해보자.
콘솔을 열고 프로젝트 폴더로 간 뒤, 아래와 같이 입력해서 django_extensions
와 ipython
을 설치하자.
pip install django_extensions ipython
설치가 완료되면 settings.py
에 django_extensions
를 추가해준다.
INSTALLED_APPS = [
# 기본 내장 앱
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 써드파티 앱
'django_extensions',
# 사용자 앱
'blog',
]
이렇게 하고나면 이제 Django
전용 Shell
을 사용할 수 있게 된다.
manage.py
가 있는 폴더로 가서 아래의 명령을 실행한다.
./manage.py shell_plus
# Shell Plus Model Imports
from blog.models import Post
from django.contrib.admin.models import LogEntry
from django.contrib.auth.models import Group, Permission, User
from django.contrib.contenttypes.models import ContentType
from django.contrib.sessions.models import Session
# Shell Plus Django Imports
from django.core.cache import cache
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import transaction
from django.db.models import Avg, Case, Count, F, Max, Min, Prefetch, Q, Sum, When, Exists, OuterRef, Subquery
from django.utils import timezone
from django.urls import reverse
/home/che1/.pyenv/versions/django_assignment/lib/python3.6/site-packages/IPython/core/interactiveshell.py:728: UserWarning: Attempting to work in a virtualenv. If you encounter problems, please install IPythoninside the virtualenv.
warn("Attempting to work in a virtualenv. If you encounter problems, please "
Python 3.6.2 (default, Aug 17 2017, 14:03:57)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]:
해당 프로젝트에서 사용하는 모든 Django
패키지와 모듈을 자동으로 import
해서 콘솔 상에서 테스트 해볼 수 있는 Python 셸이 실행된다. 원래는 django_extensions
가 없이도 ./manage.py shell
을 입력하면 Django
전용 셸이 실행되지만, 패키지들을 직접 import
해주어야한다는 점이 번거롭기 때문에 shell_plus
를 사용하는 것이 간편하다.
ipython
이 설치되어 있다면, 자동적으로 ipython
으로 셸이 열리게 된다. ipyhton
은 그냥 좀 더 편의적으로 개선된 Python 셸이다.
Python 코드를 짜다가 간단히 테스트해볼 일이 생길 때 콘솔에서 셸을 열어 이것 저것 테스트 해보듯이, Django
에서 코드를 짜다가 간단히 테스트해볼 필요가 있을 때 shell_plus
를 열어 테스트 해보면 된다.
쿼리 셋 다루기
이제 본격적으로 ORM
을 경험해보자. 우선 shell_plus
를 실행시킨다.
./manage.py shell_plus
모든 객체 조회하기
ORM
은 데이터를 객체로 다룬다. 셸에서 아래와 같이 입력해서 데이터베이스에 생성되어 있는 모든 Post
객체를 확인해보자.
Post.objects.all()
<QuerySet [<Post: [Django Tutorial] Blog 만들기 - 1. 환경설정>, <Post: [Django Tutorial] Blog 만들기 - 2. 프로젝트 시작>, <Post: [Django Tutorial] Blog 만들기 - 3. 앱>, <Post: [Django Tutorial] Blog 만들기 - 4. 모델>, <Post: [Django Tutorial] Blog 만들기 - 5. 데이터베이스>]>
우리가 관리자 페이지를 통해 만들었던 Post
객체들이 QuerySet
으로 리턴되었다.
쿼리셋 (QuerySet)
은 전달받은 모델의 객체 목록이다. 이 쿼리셋으로부터 필요한 데이터를 조회하거나 필터를 적용해서 정렬할 수도 있고, 쿼리셋에 데이터를 추가하거나 삭제할 수도 있다. 지금같은 경우에는 Post
모델의 모든 객체 목록을 쿼리셋으로 가져온 것이다.
또 조회 해볼만한게 뭐가 있을까? 관리자 페이지에 로그인하기 위해 등록했던 유저명을 기억하는가? 그 유저명은 User
라는 모델의 객체로 등록되어 있다. 이것도 한 번 확인해보자.
아래 명령을 입력하면 User
모델의 모든 객체를 조회한다.
User.objects.all()
<QuerySet [<User: nachwon>]>
예전에 등록했던 유저이름이 나타난 것을 확인할 수 있다.
이렇게 객체 지향 프로그래밍 언어로 데이터를 객체처럼 다룰 때 내부적으로 어떤 명령이 오고가는지 한 번 확인해보자.
all_posts = Post.objects.all()
print(all_posts.query)
SELECT "blog_post"."id", "blog_post"."author_id", "blog_post"."title", "blog_post"."content", "blog_post"."created_date", "blog_post"."published_date" FROM "blog_post"
쿼리셋 객체에 .query
를 입력한 뒤 print
문으로 호출해보면 해당 쿼리셋을 가져오기 위해 어떤 SQL
문이 내부적으로 데이터베이스에 전달되는지 확인할 수 있다. 이렇게 ORM
덕분에 데이터를 객체처럼 다룰 수 있으며, 직접 SQL
을 작성하지 않고도 데이터베이스를 제어할 수 있게된다.
객체 생성하기
이번에는 새로운 객체를 생성해보도록 하자. 예전에는 Post
객체를 생성하기 위해 관리자 페이지에 접속해야했다.
ORM
을 활용하면 데이터베이스에 직접 접근하지 않고도 간단히 객체를 생성할 수 있다.
create()
를 사용해서 Post
객체를 새로 만들어보자. 우리가 만든 Post
객체는 작성자 명, 글 제목, 글 내용을 가져야 한다.
Post
객체를 생성하기 위해 아래와 같이 입력해보자.
Post.objects.create(author='nachwon', title='ORM Test', content='This is a test Post')
ValueError: Cannot assign "'nachwon'": "Post.author" must be a "User" instance.
ValueError
가 났다… 어떻게 된 일인걸까..?
에러 메세지를 보니 nachwon
을 할당할 수 없고 Post
객체의 author
속성은 User
객체이어야 한다고 한다.
Post
객체에서 작성자인 author
는 필수 필드이기 때문에 반드시 넣어주어야 하는데 어떻게 User
객체를 넣어줄까?
위에서 User
모델의 객체들을 조회했던 것을 기억하는가? User
모델의 쿼리셋에서 한 객체를 가져와서 me
라는 변수에 대입한 다음 그 변수를 author
에 지정해보자.
쿼리셋에서 하나의 값을 가져올 때는 get()
을 사용한다.
me = User.objects.get(username='nachwon')
me
<User: nachwon>
me
라는 변수에 username
이 nachwon
인 User
객체가 할당되었다. 이것을 다시 Post
객체의 author
필드에 넣어서 객체 생성을 해보자.
Post.objects.create(author=me, title='ORM Test', content='This is a test Post')
<Post: ORM Test>
이번에는 에러가 나지않고 ORM Test
라는 제목의 Post
객체가 생성된 것을 볼 수 있다. 실제로 생성된 것을 확인해보자.
Post.objects.all()
<QuerySet [<Post: [Django Tutorial] Blog 만들기 - 1. 환경설정>, <Post: [Django Tutorial] Blog 만들기 - 2. 프로젝트 시작>, <Post: [Django Tutorial] Blog 만들기 - 3. 앱>, <Post: [Django Tutorial] Blog 만들기 - 4. 모델>, <Post: [Django Tutorial] Blog 만들기 - 5. 데이터베이스>, <Post: ORM Test>]>
목록의 제일 마지막에 <Post: ORM Test>
가 추가된 것을 확인할 수 있다.
SQLite Browser
에서도 확인해보자. SQLite Browser
를 켠 다음, 프로젝트 폴더 내의 db.sqlite3
파일을 열고, 데이터 보기
항목의 테이블(T)
드롭다운 메뉴에서 blog_post
를 지정해주면 Post
모델의 데이터들을 조회할 수 있다.
제일 아래에 ORM Test
라는 제목의 Post
객체가 들어가있는 것을 볼 수 있다.
객체 필터링하기
filter()
를 사용하면 쿼리셋의 객체들을 필터링할 수 있다.
제목에 ORM
이라는 문자열이 포함된 객체들만 조회해보자.
Post.objects.filter(title__contains='ORM')
<QuerySet [<Post: ORM Test>]>
글 내용에에 blog
가 포함된 객체들만 조회해보자.
Post.objects.filter(content__contains='blog')
<QuerySet [<Post: [Django Tutorial] Blog 만들기 - 2. 프로젝트 시작>, <Post: [Django Tutorial] Blog 만들기 - 3. 앱>, <Post: [Django Tutorial] Blog 만들기 - 4. 모델>, <Post: [Django Tutorial] Blog 만들기 - 5. 데이터베이스>]>
title
, content
는 필드명이고 contains
는 필터이며 이 둘을 __
로 구분한다.
작성자로 필터링하려면 아래와 같이 할 수 있다.
Post.objects.filter(author=me)
<QuerySet [<Post: [Django Tutorial] Blog 만들기 - 1. 환경설정>, <Post: [Django Tutorial] Blog 만들기 - 2. 프로젝트 시작>, <Post: [Django Tutorial] Blog 만들기 - 3. 앱>, <Post: [Django Tutorial] Blog 만들기 - 4. 모델>, <Post: [Django Tutorial] Blog 만들기 - 5. 데이터베이스>, <Post: ORM Test>]>
객체 정렬하기
order_by()
를 사용하면 기준을 지정하여 객체들을 정렬할 수 있다. 아래와 같이 입력하면 글이 생성된 날짜 순서로 쿼리셋이 정렬된다.
Post.objects.order_by('created_date')
<QuerySet [<Post: [Django Tutorial] Blog 만들기 - 1. 환경설정>, <Post: [Django Tutorial] Blog 만들기 - 2. 프로젝트 시작>, <Post: [Django Tutorial] Blog 만들기 - 3. 앱>, <Post: [Django Tutorial] Blog 만들기 - 4. 모델>, <Post: [Django Tutorial] Blog 만들기 - 5. 데이터베이스>, <Post: ORM Test>]>
정렬하려는 필드명 앞에 -
를 붙이면 역순으로 정렬한다.
Post.objects.order_by('-created_date')
<QuerySet [<Post: ORM Test>, <Post: [Django Tutorial] Blog 만들기 - 5. 데이터베이스>, <Post: [Django Tutorial] Blog 만들기 - 4. 모델>, <Post: [Django Tutorial] Blog 만들기 - 3. 앱>, <Post: [Django Tutorial] Blog 만들기 - 2. 프로젝트 시작>, <Post: [Django Tutorial] Blog 만들기 - 1. 환경설정>]>
쿼리셋 필터 중복 적용
여러 개의 필터를 중복으로 적용할 수 있다.
Post.objects.filter(title__contains='django').order_by('published_date')
<QuerySet [<Post: [Django Tutorial] Blog 만들기 - 4. 모델>, <Post: [Django Tutorial] Blog 만들기- 5. 데이터베이스>, <Post: [Django Tutorial] Blog 만들기 - 1. 환경설정>, <Post: [Django Tutorial] Blog 만들기 - 2. 프로젝트 시작>, <Post: [Django Tutorial] Blog 만들기 - 3. 앱>]>
제목에 django
가 있는 글들을 게시 날짜 순으로 정렬하기
객체 삭제하기
delete()
를 사용하면 객체를 삭제할 수 있다. 방금 추가한 ORM Test
라는 제목의 객체를 삭제해보자.
post = Post.objects.get(title='ORM Test')
post.delete()
(1, {'blog.Post': 1})
delete()
는 객체를 삭제하면서, (삭제한 총 객체 수, {삭제된 객체의 타입: 삭제된 해당 타입의 객체 수})
를 리턴한다. 위의 결과를 보면, 총 1 개의 객체가 삭제되었고, blog.Post
의 객체 1 개가 삭제되었다는 것을 알 수 있다.
이 외의 쿼리셋 명령어들은 Django 공식문서 를 참고하면 된다.
이번 포스트에서는 ORM
과 쿼리셋을 통해 데이터베이스를 제어하는 방법에 대해 알아보았다. 다음 포스트에서는 이 방법을 템플릿에 적용하여 동적으로 템플릿을 생성하는 방법을 알아볼 것이다.
Reference
이한영 강사님 강의자료
Djangogirls: https://tutorial.djangogirls.org/ko/
Django 공식문서: https://docs.djangoproject.com/en/1.11/ref/models/querysets/