WeniVooks

검색

Django 베이스캠프

Django ORM

1. Django ORM QuerySet

ORM(Object-Relational Mapping)은 객체로서 데이터베이스를 다루는 방법을 말합니다. 여기서 Object는 파이썬의 객체를 의미하고, Relational은 관계형 데이터베이스의 테이블을 의미합니다. 이것이 서로 연결(mapping)되어 있어 개발자는 데이터베이스 쿼리를 직접 작성하지 않고도 파이썬 문법 만으로도 데이터베이스를 다룰 수 있습니다.

예를 들어, "모든 상품의 이름을 알려줘"라고 물으면 데이터베이스는 상품 이름 목록을 답으로 주어야 합니다. 여기서 우리는 SQL 문법을 몰라도 Product.objects.all()이라는 Django ORM 쿼리를 사용하여 데이터베이스에 질문할 수 있습니다. 이것은 실제 SQL 구문으로는 SELECT * FROM product와 같습니다.

따라서 ORM은 파이썬의 객체와 데이터베이스 쿼리 사이의 '번역기' 역할을 한다고 볼 수 있습니다. 이를 통해 개발자는 파이썬 객체를 다루듯이 데이터베이스를 조작할 수 있게 되며, 이는 개발 생산성을 크게 향상시킵니다.

Django ORM 작동 예시

ORM을 사용하면, 개발자는 SQL을 직접 작성할 필요가 없고, 데이터베이스 종류가 바뀌어도 Python 코드를 수정할 필요가 없다는 장점이 있습니다. 실습을 통해 django의 ORM 쿼리에 대해 알아봅시다.

ORM 쿼리 실습을 위해서는 이전 3-1 실습에서 만든 모델이 있어야 합니다! 3-1 실습을 완료하지 않았다면, 먼저 3-1 실습을 완료해주세요.

2. ORM 실습 준비

Django ORM을 실습하기 위해 Django 쉘을 사용합니다.

  1. 3-1 프로젝트 폴더로 들어가는 명령어와 파이썬 쉘에 진입하는 명령어입니다.

    cd 03_1_model
    python manage.py shell
    cd 03_1_model
    python manage.py shell
  2. 명령어 실행 후 터미널 창에 >>> 모양이 나타나는 것을 확인해 주세요. >>>이 나타나면, 쉘에 진입했다는 뜻입니다. ORM 실습에서는 >>>모양 옆에 명령어를 입력하면 됩니다.

  3. 쉘 사용을 중지하려면 exit()을 입력합니다.

3. 기본 Query

기본적인 쿼리에 대해서 이야기하기 전에, 객체가 무엇인지 먼저 알아봅시다. 간단하게 말해서 객체는 우리가 다루는 데이터 한 묶음을 의미합니다. 블로그를 예시로 들어보겠습니다. 우리가 블로그의 구성을 생각해보면 전체 블로그 글의 목록, 개별 블로그 글, 글의 제목, 내용, 댓글 등으로 구성이 될 것 입니다. 여기서 객체는 블로그 묶음이라고 생각하면 됩니다.

쉘에서 모델을 사용하기 위해 models.py에서 모델을 불러옵니다. 앞에는 >>>이 있는 상태입니다.

# 쉘에서 모델 불러오기
from blog.models import Post
# 모든 Post 객체 가져오기
all_posts = Post.objects.all()
 
# all_posts의 타입 확인
type(all_posts) # <class 'django.db.models.query.QuerySet'>
 
# all_posts의 사용 가능한 메서드와 속성 확인
dir(all_posts)
# 쉘에서 모델 불러오기
from blog.models import Post
# 모든 Post 객체 가져오기
all_posts = Post.objects.all()
 
# all_posts의 타입 확인
type(all_posts) # <class 'django.db.models.query.QuerySet'>
 
# all_posts의 사용 가능한 메서드와 속성 확인
dir(all_posts)

all() 메서드는 모든 객체를 반환합니다. type()dir() 함수는 Python 내장 함수로, 객체의 타입과 사용 가능한 속성 및 메서드를 확인하는 데 사용됩니다. 여기서 우리가 주의 깊게 봐야 할 것은 QuerySet이라는 자료형입니다. 이것이 앞서 얘기한 객체입니다. QuerySet은 Django에서 데이터베이스로부터 데이터를 읽어오는 객체입니다. 이 객체는 all(), filter(), get(), update() 등의 메서드를 사용하여 데이터를 읽어오거나 수정하거나, 삭제할 수 있습니다.

4. CRUD

CRUD는 데이터베이스의 기본적인 데이터 처리 기능을 나타내는 약어로, 다음과 같은 네 가지 핵심 작업을 의미합니다.

  1. Create (생성): 새로운 데이터를 만들어 데이터베이스에 추가합니다.
  2. Read (읽기): 데이터베이스에서 정보를 조회하거나 검색합니다.
  3. Update (갱신): 기존의 데이터를 수정하여 업데이트합니다.
  4. Delete (삭제): 데이터베이스에서 기존 데이터를 제거합니다.

이 네 가지 기능은 웹 애플리케이션에서 기본적으로 필요한 기능입니다. 이 작업을 쿼리셋을 통하여 작업해보도록 하겠습니다.

4.1 Read 연산

Read는 데이터베이스에서 정보를 조회하거나 검색하는 기능입니다.

# 0번째 Post 객체 가져오기
Post.objects.all()[0]
 
# 슬라이싱을 통해 Post 객체 가져오기
Post.objects.all()[:2]
 
# ID의 순서대로 정렬, pk로 입력해도 됩니다.
Post.objects.all().order_by('id')
 
# ID의 역순으로 정렬 (최신 글이 위에 오도록)
Post.objects.all().order_by('-id')
 
# 원하는 하나의 Post 객체 가져오기
q = Post.objects.get(id=1)
q.id
q.pk
q.title # 쿼리셋은 대괄호로 접근하지 않고, dot을 이용하여 접근합니다.
q.content
q.created_at
 
# 조건에 맞는 Post 객체 모두 가져오기
Post.objects.filter(title__contains='1')
Post.objects.filter(content__contains='11').order_by('id')
 
# 여러 조건을 동시에 사용하기
Post.objects.filter(title__contains='1', content__contains='11')
 
# ID가 3보다 작은 Post 객체들
# - eq: 같음 (=), equal
# - ne: 같지 않음 (<>), not equal
# - lt: 작음 (<), less than(little이라고도 함)
# - le: 작거나 같음 (<=), less than or equal
# - gt: 큼 (>), greater than
# - ge: 크거나 같음 (>=), greater than or equal  
posts = Post.objects.filter(id__lt=3)
 
# 대소문자 상관없이 정확하게 일치하는 항목 찾기
User.objects.filter(username__iexact='john')
# 0번째 Post 객체 가져오기
Post.objects.all()[0]
 
# 슬라이싱을 통해 Post 객체 가져오기
Post.objects.all()[:2]
 
# ID의 순서대로 정렬, pk로 입력해도 됩니다.
Post.objects.all().order_by('id')
 
# ID의 역순으로 정렬 (최신 글이 위에 오도록)
Post.objects.all().order_by('-id')
 
# 원하는 하나의 Post 객체 가져오기
q = Post.objects.get(id=1)
q.id
q.pk
q.title # 쿼리셋은 대괄호로 접근하지 않고, dot을 이용하여 접근합니다.
q.content
q.created_at
 
# 조건에 맞는 Post 객체 모두 가져오기
Post.objects.filter(title__contains='1')
Post.objects.filter(content__contains='11').order_by('id')
 
# 여러 조건을 동시에 사용하기
Post.objects.filter(title__contains='1', content__contains='11')
 
# ID가 3보다 작은 Post 객체들
# - eq: 같음 (=), equal
# - ne: 같지 않음 (<>), not equal
# - lt: 작음 (<), less than(little이라고도 함)
# - le: 작거나 같음 (<=), less than or equal
# - gt: 큼 (>), greater than
# - ge: 크거나 같음 (>=), greater than or equal  
posts = Post.objects.filter(id__lt=3)
 
# 대소문자 상관없이 정확하게 일치하는 항목 찾기
User.objects.filter(username__iexact='john')

쿼리셋은 인덱싱슬라이싱이 가능하지만, 음수 인덱싱은 지원되지 않습니다.

4.1.1 get과 filter의 차이

Django에서 데이터를 찾을 때 all(), get(), filter() 세 가지 방법을 주로 사용합니다. 세가지 모두 Django ORM에서 데이터를 조회할 용도로 사용하기에 비슷해 보이지만 중요한 차이가 있습니다. 아래의 그림을 보면서 함께 알아봅시다.

첫번째로 all()조건 없이, 있는 모든 데이터를 가져옵니다. DB에게 모든 학생의 정보 모두 달라고 요청하는 것입니다.

두번째로 get()딱 하나의 결과만을 찾습니다. 예를 들어, get(id=00)은 ID가 00인 난성호 학생의 정보를 가져옵니다. "학번이 00번인 학생은 반드시 있어. 그 학생 정보 줘!"라고 확신을 가지고 DB에게 요청하는 것입니다. 하지만 이 확신 때문에, get()만약 요청하는 데이터가 없거나 여러개가 있다면 오류를 발생시킵니다. 만약 있는지 없는지 확실하지 않은 경우에는 filter()를 사용하는 것이 좋습니다. Views에서는 get_object_or_404()를 사용하여 좀 더 쉽게 이러한 오류를 한 번에 제어할 수 있습니다.

마지막으로 filter()조건에 맞는 모든 것을 찾아줍니다. 위의 이미지처럼 filter(학년=3)은 3학년인 오효림과 평하진의 정보를 가져옵니다. "3학년 학생들 있으면 명단 좀 줘볼래?"라고 DB에게 부드럽게 요청하는 것과 비슷합니다. filter()는 결과가 있든 없든, 하나이든 여러 개이든 상관없이 조용히 결과를 제공합니다. 조건에 맞는 데이터가 없다면, 빈 목록을 줍니다. 빈 목록을 받게 되면 아래와 같이 .exists()를 사용하여 데이터가 있는지 확인할 수 있습니다.

students = Student.objects.filter(id='00')
 
if students.exists():
   print(students)
else:
   print('해당하는 학생이 없습니다.')
students = Student.objects.filter(id='00')
 
if students.exists():
   print(students)
else:
   print('해당하는 학생이 없습니다.')

all()은 모든 데이터를, get()은 반드시 존재해야 하는 하나의 데이터를, filter()는 조건에 맞는 데이터가 있으면 찾아주는 방식으로 데이터를 조회합니다.

4.2 Create 연산

Create는 새로운 데이터를 생성해서, 데이터베이스에 추가하는 기능입니다.

# 새 Post 객체 생성 및 저장
new_post = Post.objects.create(title="새 글", content="내용")
Post.objects.all() # DB에 저장된 상태 확인
 
# 또는
new_post = Post(title="새 글", content="내용")
Post.objects.all() # new_post는 아직 포함되지 않음
new_post.save()
Post.objects.all() # new_post가 포함된 상태로 출력
# 새 Post 객체 생성 및 저장
new_post = Post.objects.create(title="새 글", content="내용")
Post.objects.all() # DB에 저장된 상태 확인
 
# 또는
new_post = Post(title="새 글", content="내용")
Post.objects.all() # new_post는 아직 포함되지 않음
new_post.save()
Post.objects.all() # new_post가 포함된 상태로 출력
4.3 Delete 연산

Delete는 데이터베이스에서 기존 데이터를 제거하는 기능입니다.

# 특정 Post 객체 삭제
Post.objects.all() # 삭제 전 상태 확인
post_to_delete = Post.objects.get(id=3)
post_to_delete.delete()
Post.objects.all() # 삭제 후 상태 확인
 
# 조건에 맞는 모든 Post 객체 삭제
Post.objects.filter(title__contains='임시').delete()
# 특정 Post 객체 삭제
Post.objects.all() # 삭제 전 상태 확인
post_to_delete = Post.objects.get(id=3)
post_to_delete.delete()
Post.objects.all() # 삭제 후 상태 확인
 
# 조건에 맞는 모든 Post 객체 삭제
Post.objects.filter(title__contains='임시').delete()

특정 객체를 하나씩 삭제할 수도 있고, 여러 객체를 한번에 삭제할 수도 있습니다.

4.4 Update 연산

Update는 기존의 데이터를 수정하여 업데이트하는 기능입니다.

post_to_update = Post.objects.get(id=1)
post_to_update.title = "수정된 제목"
Post.objects.all() # 수정 전 상태 확인
post_to_update.save()
Post.objects.all() # 수정 후 상태 확인
post_to_update = Post.objects.get(id=1)
post_to_update.title = "수정된 제목"
Post.objects.all() # 수정 전 상태 확인
post_to_update.save()
Post.objects.all() # 수정 후 상태 확인

수정 후에는 반드시 .save()를 해주어야 DB에 반영이 됩니다.

5. 참고 사항

ORM 쿼리셋에 대해서 더 궁금한 사항이 있다면, 아래의 장고 공식 문서를 참고하세요.

Django 공식 문서- Making queries

이렇게 생성된 sqlite3는 DB Browser for SQLite와 같은 프로그램을 사용하여 데이터를 확인할 수 있습니다. DB Browser for SQLite는 데이터베이스를 시각적으로 확인할 수 있는 프로그램입니다. 또한 VSC를 이용한다면 SQLite Viewer와 같은 확장 프로그램으로 DB를 확인할 수 있습니다.

3.1 모델(Model)3.3 CRUD 구현과 미디어 파일