본문 바로가기
Django

[Django] Signals 사용법 - 프로필 자동 생성, 좋아요 숫자 갱신(signal 표로 정리)

by kyung-mini 2024. 9. 5.

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데커레이터가 호출됩니다. 이때, 새로운 유저가 생성된 경우 createdTrue로 설정되어서, 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 : 추가적인 키워드 인자 / 현재 코드에서는 사용하지 않으나, 관행적으로 남겨둠
  • UserProfile.objects.create(user=instance)
    • createdTrue라면 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 : 추가적인 키워드 인자 / 현재 코드에서는 사용하지 않으나, 관행적으로 남겨둠
  • @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에 대한 이해를 높이는데 도움이 되었으면 좋겠습니다.

 

[참고 사이트]

[같이 볼만한 포스팅]