• Home
  • About
    • Che1's Blog photo

      Che1's Blog

      Che1's Dev Blog

    • Learn More
    • Facebook
    • Instagram
    • Github
    • Steam
    • Youtube
  • Posts
    • All Posts
    • Django
    • Python
    • Front-end
    • Algorithm
    • etc
    • All Tags
  • Projects

[Django Tutorial] Blog 만들기 - 9. ORM

04 Oct 2017

Reading time ~8 minutes

동적으로 템플릿을 생성하기 위해서는 먼저 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 과 쿼리셋을 통해 데이터베이스를 제어하는 방법에 대해 알아보았다. 다음 포스트에서는 이 방법을 템플릿에 적용하여 동적으로 템플릿을 생성하는 방법을 알아볼 것이다.


Tutorial 목차

  • [Django Tutorial] Blog 만들기 - 15. 마무리
  • [Django Tutorial] Blog 만들기 - 14. 기능 추가하기
  • [Django Tutorial] Blog 만들기 - 13. 템플릿 상속
  • [Django Tutorial] Blog 만들기 - 12. 자세히 보기 페이지
  • [Django Tutorial] Blog 만들기 - 11. 스테틱 파일
  • [Django Tutorial] Blog 만들기 - 10. 템플릿 언어
  • [Django Tutorial] Blog 만들기 - 9. ORM
  • [Django Tutorial] Blog 만들기 - 8. 템플릿
  • [Django Tutorial] Blog 만들기 - 7. 뷰
  • [Django Tutorial] Blog 만들기 - 6. 관리자 페이지
  • [Django Tutorial] Blog 만들기 - 5. 데이터베이스
  • [Django Tutorial] Blog 만들기 - 4. 모델 생성
  • [Django Tutorial] Blog 만들기 - 3. 앱
  • [Django Tutorial] Blog 만들기 - 2. 프로젝트 시작
  • [Django Tutorial] Blog 만들기 - 1. 환경설정

Reference

이한영 강사님 강의자료
Djangogirls: https://tutorial.djangogirls.org/ko/
Django 공식문서: https://docs.djangoproject.com/en/1.11/ref/models/querysets/



DjangoTutorialORM Share Tweet +1