관리 사이트를 통해 블로그 게시글을 관리하는 법을 살펴 보았으니,
프로그래밍 방식으로 DB에서 컨텐츠를 읽고 쓰는 방법을 살펴보자.
쟝고 ORM은 객체를 데이터베이스에 쉽기 생성, 검색, 갱신 및 삭제할 수 있는 강력한 데이터베이스 추상화 API이다.
ORM을 사용하면 파이썬의 객체지향 패러다임을 사용해 SQL 쿼리를 생성할 수 있다.
쟝고 ORM은 MySQL, PostgreSQL, SQLite, Oracle 및 MariaDB와 호환된다.
쟝고는 한 번에 여러 DB로 작업할 수 있고, DB 라우터를 프로그래밍해 커스텀 데이터 라우팅 스키마를 만들 수 있다.
쟝고 ORM은 QuerySet을 기반으로 한다.
QuerySet은 DB에서 객체를 검색하기 위한 일련의 DB 쿼리 모음이다.
QuerySet에 필터를 적용해 주어진 매개변수를 기반으로 쿼리 결과의 범위를 좁힐 수 있다.
쉘 프롬프트에 다음 명령을 사용해 파이썬 쉘을 실행한다.
python manage.py shell
쉘이 실행되면 다음의 코드들을 입력해보자.
In [1]: from django.contrib.auth.models import User
In [2]: from blog.models import Post
In [3]: user = User.objects.get(username='admin')
In [4]: post = Post(title='테스트포스트',
...: slug='테스트포스트',
...: body='포스트 내용',
...: author=user)
In [5]: post.save()
코드 내용을 살펴보자.
먼저 사용자 이름이 admin인 user 객체를 찾는다.
user = User.objects.get(username='admin')
get() 메서드를 사용해 DB에서 단일 객체를 검색할 수 있다.
DB에서 결과가 반환되지 않으면 이 메서드는 DoesNotExist 예외를 발생시키고 DB가 둘 이상의 결과를 반환할 경우 MultipleObjectsReturned 예외를 발생시킨다.
다음으로 title, slug, body에 값을 입력하고 앞에서 조회한 user를 게시글의 저자로 설정해 Post 인스턴스를 생성한다.
post = Post(title='테스트포스트', slug='테스트포스트', body='포스트 내용', author=user)
이 post 객체는 DB에 있지 않고 메모리에 존재하는 상태이다.
실행 중에는 사용할 수 있지만, DB에 저장되지 않은 파이썬 객체를 만든 것이다.
마지막으로 save() 메서드를 사용해 Post 객체를 DB에 저장한다.
post.save()
이 메서드는 INSERT 문을 수행한다.
위 코드에서 먼저 메모리에 객체를 만든 다음 DB에 저장했는데,
아래와 같이 create() 메서드를 사용해 한 줄로 객체를 만들고 DB에 바로 저장할 수도 있다.
Post.objects.crate(title='테스트포스트', slug='테스트포스트', body='포스트 내용', author=user)
이제 게시글의 제목을 다른 것으로 변경하고 객체를 다시 저장해보자.
In [6]: post.title = '수정된 포스트 제목'
In [7]: post.save()
save() 메서드가 이번에는 UPDATE 문을 수행한다.
위에서 Post.objects.get() 메서드를 사용해 단일 객체를 조회했었다.
쟝고 모델은 각각 최소 하나의 관리자가 있고, 기본 관리자는 objects라고 한다.
테이블에서 모든 객체를 조회하려면 아래와 같이 기본 관리자에서 all() 메서드를 사용하면 된다.
all_posts = Post.objects.all()
print(all_posts)
<QuerySet [<Post: 수정된 포스트 제목>, <Post: test1>]>
all_posts = Post.objects.all() 만 입력할 경우 QuerySet은 실행되지 않는다.
쟝고의 QuerySet은 lazy 특성을 갖고 있는데,
이는 필요할 때만 실행된다는 의미이다.
QuerySet을 변수에 할당하는 동안에는 SQL문이 실행되지 않고 QuerySet을 생성하기만 하고,
내용을 출력하도록 하면 QuerySet의 SQL문이 실행된다.
QuerySet을 필터링하기 위해서 관리자의 filter() 메서드를 사용할 수 있다.
예를 들어 아래의 QuerySet을 사용해 2025년에 게시된 모든 게시글들을 조회할 수 있다.
Post.objects.filter(publish__year=2025)
<QuerySet [<Post: 수정된 포스트 제목>, <Post: test1>]>
아래와 같이 동시에 여러 필드에 필터링을 적용할 수 있다.
Post.objects.filter(publish__year=2025, author__username='admin')
<QuerySet [<Post: 수정된 포스트 제목>, <Post: test1>]>
위 코드는 아래와 같이 여러 필터들을 연결해 동일한 QuerySet을 만드는 것과 같다.
Post.objects.filter(publish__year=2025).filter(author__username='admin')
<QuerySet [<Post: 수정된 포스트 제목>, <Post: test1>]>
관리자의 exclude() 메서드를 사용해 QuerySet에서 특정 결과를 제외할 수 있다.
Post.objects.filter(publish__year=2025).exclude(title__startswith='수정된')
<QuerySet [<Post: test1>]>
관리자의 order_by()메서드를 사용해 필드별로 결과를 정렬할 수 있다.
Post.objects.order_by('title')
<QuerySet [<Post: test1>, <Post: 수정된 포스트 제목>]>
정렬은 기본적으로 오름차순이다.
아래와 같이 - 기호를 접두사로 사용해 내림차순으로 나타낼 수 있다.
Post.objects.order_by('-title')
<QuerySet [<Post: 수정된 포스트 제목>, <Post: test1>]>
객체를 삭제하기 위해서는 객체 인스턴스에서 delete() 메서드를 사용하면 된다.
post = Post.objects.get(id=1)
post.delete()
모든 모델의 기본 관리자는 objects 관리자로, DB의 모든 객체를 조회한다.
모델에는 커스텀 관리자를 정의할 수도 있다.
PUBLISHED 상태의 모든 게시물을 조회하는 커스텀 관리자를 만들어 보자.
모델에 관리자를 추가하거나 커스터마이징 하는 방법에는 두 가지가 있다.
기존 관리자에 관리자 메서드를 추가하거나, 관리자가 반환하는 초기 QuerySet을 수정해 새로운 관리자를 만들 수 있다.
첫 번째 방법은 Post.objects.my_manager()와 같은 QuerySet 표기법을,
두 번째 방법은 Post.my_manager.all()과 같은 표기법을 제공한다.
우리는 Post,published.all() 표기법을 사용해 게시물을 조회할 수 있는 관리자를 구현하는 두 번째 방법을 선택해 보자.
blog 애플리케이션의 models.py를 편집해 아래와 같이 커스텀 관리자를 추가하자.
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
# Create your models here.
class PublishedManager(models.Manager):
def get_queryset(self) -> models.QuerySet:
return super().get_queryset().filter(status=Post.Status.PUBLISHED)
class Post(models.Model):
class Status(models.TextChoices):
DRAFT = 'DF', 'Draft'
PUBLISHED = 'PB', 'Published'
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=250)
author = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='blog_posts')
body = models.TextField()
publish = models.DateTimeField(default=timezone.now)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
status = models.CharField(
max_length=2, choices=Status.choices, default=Status.DRAFT)
objects = models.Manager()
published = PublishedManager()
class Meta:
ordering = ['-publish']
indexes = [
models.Index(fields=['-publish']),
]
def __str__(self):
return self.title
기본적으로 모델에 선언된 첫 번째 관리자가 기본 관리자가 된다.
Meta의 속성 중 default_manager_name을 사용해 다른 기본 관리자를 지정할 수 있다.
모델에 정의된 관리자가 없으면 쟝고는 자동으로 objects 기본 관리자를 생성한다.
모델에 관리자를 생성했지만, objects 관리자도 유지하고 싶다면 모델에 명시적으로 추가해야 한다.
관리자의 get_queryset() 메서드는 실행할 QuerySet을 반환한다.
이제 Post 모델에 커스텀 관리자를 정의했으니 다시 쉘 프롬프트에 진입해 테스트해보자.
In [1]: from blog.models import Post
In [2]: Post.published.all()
Out[2]: <QuerySet []>
현재 PUBLISHED인 객체가 없으로 빈 결과를 반환하고 있다.