본문 바로가기
Django

FieldTracker와 Signals를 활용한 Django 모델 변경 로그 관리

by dhleeone 2024. 12. 29.

django에서 모델 데이터 변경이력을 관리할 때는 주로 simple history를 사용합니다.

simple history: https://django-simple-history.readthedocs.io/en/latest/

simple history를 사용하면 쉽게 데이터 이력을 관리할 수 있지만, 변경이 잦을 경우 데이터가 지나치게 많이 쌓여 DB 리소스 낭비와 조회 속도 저하 등의 문제점이 있습니다.

FieldTracker와 Signal을 사용하여 더 효율적으로 변경 이력을 관리할 수 있습니다.

FieldTracker

FieldTracker는 모델의 특정 필드의 변경 사항을 추적하는 유틸성 필드입니다.

FieldTracker는 django-model-utils 패키지에서 제공됩니다.

from django.db import models
from model_utils import FieldTracker
from simple_history.models import HistoricalRecords


class Product(models.Model):
    name = models.CharField(max_length=255, verbose_name="Product Name")
    price = models.PositiveIntegerField(default=0)
    stock = models.PositiveIntegerField(default=0)
    description = models.TextField(null=True, blank=True)
    updated_at = models.DateTimeField(auto_now=True)
    history = HistoricalRecords()

    tracker = FieldTracker(fields=["price"])  # price 필드 변경 추적

위 Product 모델은 name, price, stock, description과 같은 필드가 있고 simple history로 변경이력을 관리합니다.

이때 FieldTracker(fields=["price"])와 같이 fields에 리스트 형태로 트래킹할 필드를 입력합니다.

예제에서는 price 필드를 트래킹할 수 있습니다.

Signal

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


@receiver(pre_save, sender=Product)
def track_price_history(sender, instance, **kwargs):
    """
    제품의 가격(price)이 변경될 때만 이력을 기록
    """
    instance.skip_history_when_saving = True
    if instance.tracker.has_changed("price"):
        del instance.skip_history_when_saving

Signal에서는 모델이 저장되기 전에 tracker 필드를 통해 price 필드의 변경 여부를 체크합니다.

만약 price가 변경되었다면 FieldTracker에 의해 tracker.has_changed("price")는 True를 반환합니다.

예제의 skip_history_when_saving는 simple history가 데이터 저장 시 history 데이터를 생성할지 결정하는 필드입니다. 따라서 price가 변경될 때만 history를 생성하도록 처리하였습니다.

 

product = Product.objects.get(name="my_product")
print(product.history.count() == 1)	 # True

# history 생성되지 않음
product.stock = 100
product.save()
print(product.history.count() == 1)	 # True

# history 생성
product.price = 1000
product.save()
print(product.history.count() == 2)	 # True

최종적으로 위 예시와 같이 price를 변경 때만 history를 생성할 수 있습니다.

 

위 예제와 같이 FieldTracker와 Signal을 활용하면 django-simple-history의 기능을 유지하면서도 불필요한 데이터 저장을 줄일 수 있습니다.

 

1. 빈번한 데이터 변경이 발생하는 경우: 불필요한 데이터는 이력을 쌓지 않음으로써 DB 크기와 리소스를 절약할 수 있습니다.

2. 특정 필드 중심의 변경 관리: 중요한 필드(예: 가격, 상태 등)만 변경 이력을 저장하여 로그 조회 시 필드 변경 사항 추적에 용이합니다.

3. 이력 관리 후처리가 필요한 경우: Signal을 통해 이력 생성 후 추가 작업(예: 알림 발송, 로그 파일 기록 등)을 수행할 수 있습니다.