웹프로그래밍/Django

[Django] ORM Cookbook, 항목을 생성·갱신·삭제하는 방법

ssung.k 2020. 8. 27. 19:39

해당 포스팅은 Django ORM CookBook 을 공부하며 새로 알게된 사실이나 더 나아가 추가적으로 같이 알면 좋을 내용을 정리하고 있습니다.

 

1. 여러 개의 행을 한 번에 생성하기

여러 개의 데이터를 저장하기 위해서 create() 함수를 여러 번 쓰는 것은 비효율적입니다.

DB와 연결 후 저장을 하고 연결을 끊게 되는데 쿼리가 여러 개면 이 과정을 무의미하게 반복됩니다.

따라서 저장할 데이터가 많을 경우 한 번에 저장하는게 좋습니다.

Django에서는 bulk_create 를 제공해줍니다.

이 함수는 아직 저장되지 않은 객체를 담은 리스트를 인자로 받습니다.

categories = [Category(name='a'), Category(name='b')]
category = Category.objects.bulk_create(categories)
INSERT INTO `post_category` (`name`) VALUES ('a'), ('b');

 

2. 저장된 행 복사하여 새로 저장

Django ORM에는 모델 인스턴스를 복사하는 내장 메서드가 없습니다.

따라서 모델 인스턴스를 복사할 때 pk 값을 None 으로 지정하고 저장하면 새로운 행으로 저장됩니다.

Category.objects.all()
# <QuerySet [<Category: Category object (1)>, <Category: Category object (2)>]>

category = Category.objects.first()
category.pk = None
category.save()

Category.objects.all()
# <QuerySet [<Category: Category object (1)>, <Category: Category object (2)>, <Category: Category object (3)>]>

 

 

3. singleton 패턴

특정 모델의 항목이 단 하나만 생성되도록 해봅시다.

모델의 save 메소드를 재정의합니다.

class Category(models.Model):
    name = models.CharField(max_length=50)

    def save(self, *args, **kwargs):
        if self.__class__.objects.count():
            self.pk = self.__class__.objects.first().pk
        super().save(*args, **kwargs)

 

이미 객체가 있을 경우 해당 객체의 pk로 저장하게 하여 에러가 발생하게 합니다.

 

4. 반정규화된 필드를 함께 갱신하는 방법

반정규화된 필드를 함께 갱신하는 방법을 알아봅시다.

아래와 같이 Post 모델과 Like모델이 있을 경우 Post 모델에는 좋아요 수를 반정규화하여 저장중입니다.

class Post(models.Model):
    like_cnt = models.PositiveIntergerField()
    
    
class Like(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE)

 

이 때 Like의 save 메소드를 재정의합니다.

class Like(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    
    def save(self, *args, **kwargs):
        if not self.pk:
            Post.objects.filter(pk=self.post_id).update(like_cnt=F('like_cnt')+1)
        super().save(*args, **kwargs)

 

 

5. TRUNCATE 문

SQL에서는 TRUNCATE문을 사용하여 테이블의 모든 항목을 제거할 수 있습니다.

하지만 Django 에서는 TRUNCATE문을 실행하는 명령을 제공하지 않습니다.

delete() 메소드를 이용해 전부 삭제할 수 있습니다.

Category.objects.all().delete()
DELETE FROM post_category

 

하지만 삭제하는 항목의 수가 많을 경우 처리 속도가 비교적 느립니다.

 

따라서 모델의 classmethod로 truncate() 를 정의해줍시다.

class Category(models.Model):
    name = models.CharField(max_length=50)

    @classmethod
    def truncate(cls):
        with connection.cursor() as cursor:
            cursor.execute(
                f'TRUNCATE TABLE {cls._meta.db_table}'
            )

 

위에서 정의한 함수는 다음과 같이 사용가능합니다.

Category.truncate()
TRUNCATE TABLE post_category;

 

 

6. 모델 인스턴스 생성, 갱신 시 시그널

시그널을 사용하여 모델 인스턴스의 생명주기에 따라 특정 코드가 실행되도록 예약할 수 있습니다.

시그널의 종류는 다음과 같습니다.

  • pre_init
  • post_init
  • pre_save
  • post_save
  • pre_delete
  • post_delete

 

이 중에 자주 쓰이는 pre_save() 의 사용예시를 살펴봅시다.

from django.db.models.signals import pre_save
from django.dispatch import receiver

@receiver(pre_save, sender=Category, dispatch_uid="update_category_name")
def change_category_name(sender, **kwargs):
    category = kwargs['instance']
    category.name = category.name + '_change'

 

그 후 Category 객체를 생성하면 저장되기 전에 해당 함수가 먼저 수행되어 이름을 수정하고 저장하게 됩니다.

Category.objects.create(name="test")
INSERT INTO `post_category` (`name`) VALUES ('test_change');

 

save 메서드를 재정의하는 방법과 시그널을 이용하는 방법 모두 사용할 수 있습니다

  • 반정규화 필드에 영향을 끼치는 모델을 여러분이 통제할 수 있다면 save 메서드를 재정의합니다.
  • 반정규화 필드에 영향을 끼치는 모델을 여러분이 통제할 수 없다면(그 영향이 라이브러리 등에서 이루어진다면) 시그널을 이용합니다.

 

 

7. 시간 정보 저장

Django에서 시간과 관련된 텍스트를 다른 양식의 텍스트로 변환하여 저장하는 방법이 여러가지가 있습니다.

두 가지 방법에 대해 살펴봅시다.

# 방법 1

from django.utils.dateparse import parse_date
user = User.objects.get(id=1)
date_str = "2018-03-11"

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)
# 방법 2

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)