Django로 개발하다 보면 '이 이벤트가 발생하면 어떤 동작을 수행하고 싶다'라는 상황이 정말 자주 생긴다. 예를 들어, 특정 모델이 저장될 때마다 로그를 남기거나, 사용자 프로필이 업데이트되면 다른 관련 데이터를 자동으로 갱신하고 싶을 때가 있다. 이런 때 유용한 것이 바로 Django Signals다. Signals는 이벤트 기반의 구조를 만들어주기 때문에, 코드 간의 결합을 느슨하게 하면서도 필요한 작업을 할 수 있게 해준다.
이번 포스트에서는 Django Signals 중에서도 가장 많이 사용되는 pre_save
와 post_save
신호를 중심으로 어떻게 활용할 수 있는지 알아보자.

1. Django Signals 개요
Django Signals는 애플리케이션에서 특정 이벤트가 발생했을 때, 미리 정의된 함수를 자동으로 호출해주는 기능이다. 개발자가 직접 신호를 정의할 수도 있지만, Django에서는 pre_save
, post_save
, pre_delete
, post_delete
같은 신호를 기본적으로 제공한다. 이런 신호는 모델 인스턴스가 저장되거나 삭제될 때 발생하므로, 데이터베이스와 관련된 동작을 트리거할 때 특히 유용하다.
Tip: Signals를 설정할 때는 신호의 종류와 신호를 받을 대상(모델)을 명확히 지정해야 예기치 않은 오류를 피할 수 있다.
2. pre_save
와 post_save
의 차이점
pre_save
: 모델 인스턴스가 데이터베이스에 저장되기 전에 실행된다.post_save
: 모델 인스턴스가 데이터베이스에 저장된 후에 실행된다.
이 두 신호는 시점이 다르기 때문에, 어떤 작업을 할지에 따라 알맞은 신호를 선택해야 한다. 예를 들어, 저장되기 전에 값을 변경해야 한다면 pre_save
를, 저장이 끝난 후 다른 작업을 하고 싶다면 post_save
가 더 적합하다.
3. pre_save
사용 예제
이제 pre_save
신호를 통해 실제로 어떤 작업을 할 수 있는지 살펴보자. 예를 들어, 사용자 이름을 저장하기 전에 자동으로 소문자로 변환하는 작업을 하고 싶다고 가정하자.
from django.db.models.signals import pre_save
from django.dispatch import receiver
from .models import UserProfile
@receiver(pre_save, sender=UserProfile)
def lowercase_username(sender, instance, **kwargs):
instance.username = instance.username.lower()
위 코드에서 @receiver
데코레이터는 pre_save
신호와 lowercase_username
함수를 연결한다. 이제 UserProfile
모델 인스턴스가 저장되기 전에 이 함수가 자동으로 호출되어 username
필드를 소문자로 변환하게 된다.
Tip:
pre_save
신호는 데이터가 데이터베이스에 들어가기 전에 처리되므로, 저장 전 데이터 검증이나 필드 값 변환 등에 유용하다.
pre_save
에서 자주 발생하는 실수
pre_save
신호를 사용할 때, 가장 흔히 하는 실수 중 하나는 save
메서드를 다시 호출하는 것이다. 예를 들어, 저장 전에 특정 필드를 업데이트하면서 실수로 instance.save()
를 다시 호출하는 경우 무한 루프에 빠질 수 있다. 신호 처리 함수 내부에서 다시 save()
를 호출하지 않도록 주의하자.
4. post_save
사용 예제
이번에는 post_save
신호를 사용해 보자. 예를 들어, 사용자가 회원가입을 하면 환영 이메일을 보내는 기능을 구현하고 싶다고 하자. 이럴 때 post_save
신호가 매우 유용하다.
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.mail import send_mail
from .models import UserProfile
@receiver(post_save, sender=UserProfile)
def send_welcome_email(sender, instance, created, **kwargs):
if created: # 새로 생성된 경우에만 이메일 전송
send_mail(
'Welcome!',
'Thank you for signing up!',
'from@example.com',
[instance.email],
fail_silently=False,
)
여기서는 created
라는 매개변수를 사용해 객체가 새로 생성된 경우에만 이메일을 전송하도록 설정했다. post_save
신호는 데이터베이스에 저장이 완료된 후에 동작하기 때문에, 데이터를 확인하고 추가적인 후속 작업을 하기 적합하다.
post_save
에서의 활용 예제: 관련 모델 업데이트
post_save
신호는 주로 "모델이 저장된 후에 추가적으로 작업해야 할 때" 사용된다. 예를 들어, 블로그 게시물이 저장된 후 자동으로 태그와 카테고리 개수를 업데이트하거나, 상품 재고가 변경될 때 로그를 남기고 싶을 때 활용할 수 있다.
from .models import BlogPost, Category
@receiver(post_save, sender=BlogPost)
def update_category_count(sender, instance, created, **kwargs):
if created:
category = instance.category
category.post_count = BlogPost.objects.filter(category=category).count()
category.save()
위 예제는 BlogPost
인스턴스가 새로 저장될 때마다 관련 카테고리의 post_count
를 업데이트하는 방식이다. post_save
신호를 사용하면 데이터를 저장한 후 연관된 데이터를 동적으로 변경할 수 있어 매우 유용하다.
5. pre_save
와 post_save
를 사용할 때 주의할 점
- 무한 루프 방지: 신호 처리 함수 안에서
save()
를 다시 호출하지 않도록 주의하자.save()
를 호출하면 다시pre_save
와post_save
신호가 발생해 무한 루프가 발생할 수 있다. - 조건부 처리: 특정 조건에서만 신호를 처리하도록 설정하는 것이 좋다. 예를 들어,
post_save
에서created
매개변수를 사용해 객체가 새로 생성된 경우와 업데이트된 경우를 구분할 수 있다. - 신호 등록 위치: 신호를 등록하는 위치도 중요하다. 일반적으로
apps.py
파일의ready()
메서드 안에 신호를 등록하거나, 별도의signals.py
파일을 만들어 관리하는 것이 좋다. 신호를 여러 곳에서 등록하면 예기치 않은 동작이 발생할 수 있다.
마무리하며
Django의 pre_save
와 post_save
신호는 데이터 저장 전후로 다양한 작업을 수행할 수 있게 해준다. 단순히 데이터를 저장하는 것에서 끝나지 않고, 저장되기 전에 데이터를 검증하거나, 저장된 후에 다른 모델과의 관계를 업데이트하는 등 신호를 활용하면 개발 효율성과 코드의 유연성을 크게 높일 수 있다.
다음 글에서는 pre_delete
와 post_delete
신호에 대해 알아보고, 삭제 시점에서 할 수 있는 여러 가지 활용법을 다뤄볼 예정이다. Signals는 Django 개발을 더 흥미롭고 효율적으로 만들어 주는 도구이니, 상황에 맞게 잘 활용해 보자!
Add a New Comment