[Django] Django ORM Cookbook : 4

GreenBean·2021년 9월 30일
0

Django ORM Cookbook

목록 보기
4/8
post-thumbnail

Django ORM

Django ORM Cookbook


여러 개의 행 한번에 생성

  • 여러 개의 신규 객체를 한꺼번에 저장하고 싶은 경우
    • 예를 들어, 여러 개의 분류 항목을 단번에 생성하되 데이터베이스에 질의를 여러 번 수행하지 않아야 함
    • bulk_create 메서드를 이용하면 여러 개의 신규 객체를 한 번에 저장 가능
>>> Category.objects.all().count()

2

>>> Category.objects.bulk_create([Category(name="God"),Category(name="Demi God"),Category(name="Mortal")])

[<Category: God>, <Category: Demi God>, <Category: Mortal>]

>>> Category.objects.all().count()

5

기존에 저장된 행 새로 저장

  • 장고 ORM에는 모델 인스턴스를 복사하는 내장 메서드가 없지만 모든 필드의 값을 복사하여 새 인스턴스를 만들고 새로 저장하는 것은 어렵지 않음
    • 모델 인스턴스를 저장할 때, pk 필드 값이 None 으로 지정되어 있으면 데이터베이스에 새 행으로 저장
    • pk 외의 모든 필드 값은 그대로 복제됨
>>> Hero.objects.all().count()

4

>>> hero = Hero.objects.first()
>>> hero.pk = None
>>> hero.save()
>>> Hero.objects.all().count()

5

특정 모델 항목이 하나만 생성되도록 강제

  • 특정 모델의 항목이 단 하나만 생성되도록 강제하고 싶을 때
    • 프로그램의 환경 설정 기록, 공유 자원에 대한 잠금 제어 등을 예로 들 수 있음
  • Origin이라는 모델을 싱글턴(단일개체)으로 만드는 기법
class Origin(models.Model):
    name = models.CharField(max_length=100)

    def save(self, *args, **kwargs):
        if self.__class__.objects.count():
            self.pk = self.__class__.objects.first().pk
        super().save(*args, **kwargs)
  • save 메서드를 재정의하여 pk 필드를 이미 존재하는 값으로 지정하도록 강제
  • 이로써 객체가 이미 존재할 때 create 메서드를 호출하는 경우 IntegrityError 예외가 발생

다른 모델에 반정규화된 필드를 함께 갱신

  • Hero 모델과 Villain 모델의 항목을 새로 저장할 때, Category 모델의 hero_count 필드와 villain_count 필드를 갱신해야 할 때
    • Hero 모델과 Villain 모델의 save 메서드를 재정의하면 됨
class Hero(models.Model):
    # ...

    def save(self, *args, **kwargs):
        if not self.pk:
            Category.objects.filter(pk=self.category_id).update(hero_count=F('hero_count')+1)
        super().save(*args, **kwargs)


class Villain(models.Model):
    # ...

    def save(self, *args, **kwargs):
        if not self.pk:
            Category.objects.filter(pk=self.category_id).update(villain_count=F('villain_count')+1)
        super().save(*args, **kwargs)
  • self.category.hero_count += 1과 같이 인스턴스의 값을 수정하는 것이 아니라, update 메서드로 데이터베이스의 갱신을 수행

Tip! 추가 내용

  • Hero 모델과 Villain 모델의 항목을 새로 저장할 때, Category 모델의 hero_count 필드와 villain_count 필드를 갱신해야 할 때
    • ‘시그널(신호)’이라는 기능을 이용하는 방법
from django.db.models.signals import pre_save
from django.dispatch import receiver

@receiver(pre_save, sender=Hero, dispatch_uid="update_hero_count")
def update_hero_count(sender, **kwargs):
    hero = kwargs['instance']
    if hero.pk:
        Category.objects.filter(pk=hero.category_id).update(hero_count=F('hero_count')+1)

@receiver(pre_save, sender=Villain, dispatch_uid="update_villain_count")
def update_villain_count(sender, **kwargs):
    villain = kwargs['instance']
    if villain.pk:
        Category.objects.filter(pk=villain.category_id).update(villain_count=F('villain_count')+1)

save 메서드 재정의 방법과 시그널의 비교

  • save 메서드를 재정의하는 방법과 시그널을 이용하는 방법 모두 사용 가능
    • 반정규화 필드에 영향을 끼치는 모델을 통제할 수 있다면 save 메서드를 재정의
    • 반정규화 필드에 영향을 끼치는 모델을 통제할 수 없다면(그 영향이 라이브러리 등에서 이루어진다면) 시그널을 이용

TRUNCATE문 수행

  • SQL의 TRUNCATE 문은 표에 저장된 모든 항목을 제거하는 명령
  • 장고는 TRUNCATE 문을 실행하는 명령을 제공하지 않지만 delete 메서드를 이용해 비슷한 결과를 얻을 수 있음
>>> Category.objects.all().count()

7

>>> Category.objects.all().delete()

(7, {'entity.Category': 7})

>>> Category.objects.all().count()

0
  • 위 코드는 잘 작동 하지만 TRUNCATE 문이 아니라 DELETE FROM ... 과 같은 SQL 질의를 수행
    • 삭제해야 하는 항목의 수가 매우 많은 경우 처리 속도가 느릴 수 있음
    • truncate 명령이 필요하다면 다음과 같이 Category 모델에 classmethod로 추가하면 됨
class Category(models.Model):
    # ...

    @classmethod
    def truncate(cls):
        with connection.cursor() as cursor:
            cursor.execute('TRUNCATE TABLE "{0}" CASCADE'.format(cls._meta.db_table))
  • Category.truncate()를 실행하여 정말로 데이터베이스 시스템에 TRUNCATE 문을 질의할 수 있음

모델 인스턴스가 생성·갱신될 때 발생하는 시그널

  • 장고의 시그널을 이용하면 모델 인스턴스의 생명주기에 따라 특정 코드가 실행되도록 예약해 둘 수 있음
  • 장고가 제공하는 시그널의 종류
    • pre_init
    • post_init
    • pre_save
    • post_save
    • pre_delete
    • post_delete
  • 이 가운데 pre_save 와 post_save 가 가장 많이 사용

시그널과 save 메서드 재정의 비교

  • 시그널을 이용하면 save 메서드를 재정의하는 것과 비슷한 효과를 누릴 수 있음
    • 다른 사람(외부 라이브러리 등)이 앱의 save 메서드를 재정의·커스터마이즈하도록 허용하려면 직접 시그널을 발생시켜야 함
    • 통제할 수 없는 앱의 save 메서드가 호출될 때 원하는 코드가 실행되도록 하려면 post_save 시그널 또는 pre_save 시그널을 이용해야 함
    • 통제할 수 있는 앱의 저장 방식을 손 볼 때는 save 메서드를 재정의해야 함

시간 정보를 다른 양식으로 변환

  • 장고에서 시간을 나타내는 텍스트를 다른 양식의 텍스트로 변환하여 데이터베이스에 저장하는 방법은 여러 가지
    • “2018-03-11”이라는 시간 텍스트가 있는데, 이 양식으로는 데이터베이스에 저장할 수 없다고 가정
    • 장고의 dateparser 모듈이나 파이썬 표준 라이브러리를 이용하여 날짜 양식을 변환할 수 있음
>>> user = User.objects.get(id=1)
>>> date_str = "2018-03-11"

>>> from django.utils.dateparse import parse_date
>>> temp_date = parse_date(date_str)
>>> a1 = Article(headline="String converted to date", pub_date=temp_date, reporter=user)
>>> a1.save()
>>> a1.pub_date

datetime.date(2018, 3, 11)


>>> from datetime import datetime 
>>> temp_date = datetime.strptime(date_str, "%Y-%m-%d").date()
>>> a2 = Article(headline="String converted to date way 2", pub_date=temp_date, reporter=user)
>>> a2.save()
>>> a2.pub_date

datetime.date(2018, 3, 11)
profile
🌱 Backend-Dev | hwaya2828@gmail.com

0개의 댓글