1. 개요
지난주에 Django를 활용한 웹 블로그 만들기 프로젝트를 진행하였습니다. 계정 생성 시 자동으로 프로필이 생성되고, 좋아요 숫자가 자동으로 갱신되는 기능을 구현하기 위해 Django의 signals라는 기능을 사용했습니다. 이 기능을 활용하기는 했지만, signals에 대해 충분히 이해하지 못하였기에, 정리할 겸 포스팅을 남기기로 하였습니다.
본 포스팅에서는 signals가 무엇인지 알아보고, 프로젝트에서 사용된 실제 코드를 보며 사용 예시를 살펴보겠습니다. 또한 장단점을 파악해보며 포스팅을 마치겠습니다.
2. Signals란?
영어 단어 뜻 그대로 신호입니다. Django에는 "signal dispatcher"라는 기능이 포함되어있습니다. 이 기능은 특정 발신자(sender)에서 지정한 동작이 발생하였을 때, 그 발생 사실을 수신자들(receivers)에게 알립니다. 간단히 말하자면, 어떤 동작이 발생하였을 때 미리 설정된 함수들이 자동으로 실행되도록 하는 기능입니다.
Signals는 여러 코드가 동일한 동작에 연동되어 있어야 하는 경우 매우 유용합니다. 제가 사용한 방식인 계정이 생성되었을 때 프로필을 자동으로 생성시키거나, 데이터베이스의 특정 데이터가 삭제되었을 때 정리 작업을 수행하는 등의 상황에서 signal를 사용하면 작업을 자동화 시킬 수 있습니다.
Django에서는 다양한 signal를 제공합니다. 그 중 많이 사용하는 것은 post_save
, post_delete
등이 있고, 아래에서 다룰 예정입니다. 추가적인 signal은 포스트의 마지막에 표로 정리해둘테니 참고바랍니다.
3. 프로필 자동 생성, 좋아요 수 자동 갱신
Django 프로젝트에서 signals를 활용해 프로필 자동 생성과 좋아요 수 자동 갱신 기능을 구현한 코드에 대해서 설명하겠습니다.
3.1 프로필 자동 생성 코드
이번 프로젝트에서 계정 생성 시 자동으로 프로필을 생성하기 위해 사용된 signals 코드 입니다.
3.1.1 signals.py
작성
이 코드는 CustomUser
모델 인스턴스가 저장된 직후에 UserProfile
을 자동으로 생성하기 위한 코드입니다.
# accounts/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import CustomUser, UserProfile
@receiver(post_save, sender=CustomUser)
def create_or_update_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
else:
instance.userprofile.save()
CustomUser
모델의 인스턴스가 저장되면, @recieiver
데커레이터가 호출됩니다. 이때, 새로운 유저가 생성된 경우 created
가 True
로 설정되어서, UserPorfile
을 생성합니다. 이미 존재하는 유저라면 기존 프로필을 수정합니다. 아래는 구체적인 설명입니다.
@receiver(post_save, sender=CustomUser)
@receiver
: 해당 함수가 수신자라는 것을 의미post_save
: 모델 인스턴스가 저장된 직후에 호출됨을 의미sender=CustomUser
: 발신자가CustomUser
라는 것을 나타냄
def create_or_update_user_profile(sender, instance, created, **kwargs):
- Args:
sender
:signal
을 보낸 모델 클래스instance
: 저장된CustomUser
인스턴스created
: 새로운 인스턴스가 생성되었다면True
, 기존에 있던 인스턴스라면False
**kwargs
: 추가적인 키워드 인자 / 현재 코드에서는 사용하지 않으나, 관행적으로 남겨둠
- Args:
UserProfile.objects.create(user=instance)
created
가True
라면UserPofile
을 생성
instance.userprofile.save()
- 기존에 있던 프로필을 업데이트
3.1.2 앱에 signals.py
등록
Signal이 정상적으로 작동하기 위해서는 apps.py
에 등록을 해야합니다. Signal은 앱이 시작될 때 등록되어야 정상적으로 작동합니다. 하지만, Django에서는 자동으로 등록해주지 않기 때문에 직접 apps.py
에 설정하여야 합니다.
from django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "accounts"
def ready(self):
import accounts.signals
다른 부분은 그대로 쓰면 되고 import accounts.signals
만 추가하면 됩니다.
def ready(self):
: Django가 앱을 준비하는 단계에서 호출import accounts.signals
: 이 코드를 통해 해당 signal이 감지하고 코드를 실행
3.2 좋아요 수 자동 갱신 기능
아래 코드는 좋아요 수를 자동으로 갱신하기 위한 signal을 활용한 코드입니다. signal을 공부하고 싶었기 때문에 이렇게 구현을 해놓긴 하였지만, 성능 저하가 발생할 수 있습니다. 따라서, signal을 통해 이런 것도 할 수 있다는 정도로만 참고하시면 될 것 같습니다.
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from .models import Like
@receiver(post_save, sender=Like)
def update_like_count_on_save(sender, instance, **kwargs):
post = instance.post
post.like_count = post.post_likes.count()
post.save()
@receiver(post_delete, sender=Like)
def update_like_count_on_delete(sender, instance, **kwargs):
post = instance.post
post.like_count = post.post_likes.count()
post.save()
@receiver(post_save, sender=Like)
@receiver
: 해당 함수가 수신자라는 것을 의미post_save
: 모델 인스턴스가 저장된 직후에 호출된다는 것을 의미sender=Like
: 발신자가Like
라는 것을 의미
def update_like_count_on_save(sender, instance, **kwargs):
- Args:
sender
:signal
을 보낸 모델 클래스instance
: 저장된Like
인스턴스**kwargs
: 추가적인 키워드 인자 / 현재 코드에서는 사용하지 않으나, 관행적으로 남겨둠
- Args:
@receiver(post_save, sender=Like)
post_save
: 모델 인스턴스가 삭제된 직후에 호출된다는 것을 의미
def update_like_count_on_save(sender, instance, **kwargs):
- 위와 동일
3.2.2 앱에 signals.py
등록
위에서 설명한 프로필 생성 signal과 동일하게 apps.py
에 등록해야합니다.
from django.apps import AppConfig
class BlogsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "blogs"
def ready(self):
import blogs.signals
4.signal의 장단점
Signal은 매우 유용하게 사용할 수 있는 기능입니다. 하지만 모든 기능이 그렇듯이 signal에도 장단점이 있습니다. 이번에는 signal의 장점과 단점에 대해서 알아보겠습니다.
- 장점
- 코드 분리 : 모델의 비즈니스 로직과 관련된 코드 분리 가능
- 자동 처리 : 특정 동작에 대해 자동으로 움직이는 기능 추가 가능
- 단점
- 복잡한 유지보수 : signal을 많이 사용하면, 코드의 흐름을 파악하기 어려워 유지보수가 어려운 경우 존재
- 저하되는 퍼포먼스 : signal이 자주 호출되면 시스템 성능 저하시킬 가능성 존재
위와 같은 장단점이 존재하기 때문에 Signal은 필요한 경우에만 적절히 사용해야 합니다.
5.사용할 수 있는 signals
Signal 이름 | Signal 경로 | 설명 |
pre_init | django.db.models.signals.pre_init | 모델의 __init__ 메서드가 시작되기 전에 발생하는 signal. |
post_init | django.db.models.signals.post_init | 모델의 __init__ 메서드가 끝난 후 발생하는 signal. |
pre_save | django.db.models.signals.pre_save | 모델의 save() 메서드가 시작될 때 발생하는 signal. |
post_save | django.db.models.signals.post_save | 모델의 save() 메서드가 완료된 후 발생하는 signal. |
pre_delete | django.db.models.signals.pre_delete | 모델의 delete() 메서드가 시작될 때 발생하는 signal. |
post_delete | django.db.models.signals.post_delete | 모델의 delete() 메서드가 완료된 후 발생하는 signal. |
m2m_changed | django.db.models.signals.m2m_changed | 모델 인스턴스의 ManyToManyField가 변경될 때 발생하는 signal. |
class_prepared | django.db.models.signals.class_prepared | 모델 클래스가 정의되고 Django의 모델 시스템에 등록된 후 발생하는 signal. |
pre_migrate | django.db.models.signals.pre_migrate | migrate 명령이 실행되기 전에 발생하는 signal. |
post_migrate | django.db.models.signals.post_migrate | migrate 명령이 완료된 후 발생하는 signal. |
request_started | django.core.signals.request_started | Django가 HTTP 요청 처리를 시작할 때 발생하는 signal. |
request_finished | django.core.signals.request_finished | Django가 HTTP 응답을 완료한 후 발생하는 signal. |
got_request_exception | django.core.signals.got_request_exception | Django가 HTTP 요청을 처리하는 도중 예외를 만났을 때 발생하는 signal. |
connection_created | django.db.backends.signals.connection_created | 데이터베이스와의 연결이 처음 생성되었을 때 발생하는 signal. |
6.마치며
이번 포스팅에서는 Django의 Signal을 사용하는 방법에 대해서 알아보았습니다.
Signal은 코드를 분리하여 주고, 작업을 자동화 할 수 있는 매우 유용한 기능입니다. 하지만, 유지보수의 어려움과 성능 저하를 초래할 수 있기 때문에 적절히 사용해야 합니다.
프로젝트를 진행하며 Signal이라는 기능을 처음 접하게 되었는데, 다양한 곳에서 사용할 수 있을 것 같은 흥미로운 기능이었습니다. 기회가 된다면 Signal을 활용한 다른 기능들도 만들어 보고 싶습니다.
이번 포스팅이 Signal에 대한 이해를 높이는데 도움이 되었으면 좋겠습니다.
[참고 사이트]
- https://docs.djangoproject.com/en/5.1/topics/signals/
- https://docs.djangoproject.com/en/5.1/ref/signals/
[같이 볼만한 포스팅]
'Django' 카테고리의 다른 글
[Django] select_related(), prefetch_related()란?(N+1문제 해결법) (1) | 2024.09.19 |
---|---|
[Django] annotate()란? - annotate 사용법(with. only(),values() 쿼리 최적화) (0) | 2024.09.13 |
[Django] F객체, Q객체란? - ORM에서 F,Q 사용하기(짤막팁 2편) (0) | 2024.09.08 |