django 프레임워크는 어떤 특정한 일을 수행할 때마다 알려줄 것을 설정하고, 그 때에 지정한 동작을 수행할 수 있게 하는 신호(signal)를 발생하는 기능을 가지고 있다. 자바스크립트의 이벤트를 생각해보면 아마도 이해하는 데 도움이 될 것이다. django에서 signal이 어떻게 돌아가는 지를 간단하게 예제를 통해 설명해 보겠다.

여러 종류의 signal이 있는데, 여기서는 post_save signal을 이용해서 DB model에 관련해서 save가 작동하면, 저장이 완료된 이후에 지정한 동작을 수행하는 예제를 써보겠다.

myapp/models.py

from django.db import models

class Location( models.Model ):
    country = models.CharField( max_length = 50 )
    num_pizzazip = models.IntegerField( )
    created = models.DateTimeField( 'created at', auto_now_add = True )

class Pizzazip( models.Model ):
    location = models.ForeignKey( Location )
    rating = models.IntegerField( )


myapp/signals.py 파일을 새로 만들자.

from django.db.models.signals import post_save
from django.dispatch import receiver
from myapp.models import Location, Pizzazip

@receiver( post_save, sender = Pizzazip )
def pizzazip_post_save( sender, **kwargs ):
    location = kwargs[ 'instance' ].location
    location.num_pizzazip += 1
    location.save( )


myapp/apps.py 에 다음 내용을 채워넣자( django 1.9 부터는 startapp 실행 시 자동으로 만들어진다 ).

from django.apps import AppConfig

class MyAppConfig( AppConfig ):
    name = 'MyApp'
    verbose_name = 'My App Configuration for Pizza'
    def ready( self ):
        import myapp.signals


myapp/__init__.py 텅 빈 파일을 열어 한 줄을 추가하자.

default_app_config = 'myapp.apps.MyAppConfig'

위 코드를 간단히 설명하면서 이 signal이 어떻게 돌아가는 지 얘기해보자. 

우선 models.py 에서  두 개의 모델 클래스를 만들었다. '지역'과 '피자집', 그리고 피자집은 지역을 ForeignKey로 삼는다. 예제를 위해 만들어낸 것이니 필드들의 의미는 일일이 설명하지 않겠다.

signals.py 에서는 실제 신호가 발생하는 시점과 그 시점에 해야할 행동이 정해지는 메인 부분이다. 앞 부분에서 말했듯이 post_save 신호 외 필요한 여러가지를 import했고, receiver decorator( @를 사용하는 선언 )를 통해서 post_save를 쓸 것이며 보내는 사람은 모델 클래스인 피자집이라고 정했다. 그리고 선언하는 함수를 receiver function이라고 부른다. 이 함수의 내용은 피자집 하나를 저장할 때마다 작동이 될 것이다. 아니 엄밀히 말하면 피자집이 저장이 완료된 직후에 될 것이다( 또 다른 신호인 pre_save도 있음을 생각해보자 ).

그럼 이제 pizzazip_post_save 함수의 내용 중 설명이 필요한 것이 딱 하나인데, 바로 kwargs에 관한 것이다. receiver function은 2개의 인자를 필수로 받는데, 예제 코드에 쓰인 대로 sender와 kwargs( keyword arguments; 딕셔너리 인자라고 생각하면 된다 )이다. sender는 신호를 보내고자 하는 주체의 클래스를 넣으면 되고, kwargs는... signal 종류마다 그 내용이 다르다. 지금 post_save의 경우에는 instance라는 키워드 인자를 가지는데, 이는 현재 저장된 바로 그 녀석을 얘기하는 것이다. signal 종류에 따른 kwargs 정보는 django doc에서 확인하자.

apps.py 는 앱의 전반적인 설정을 지정하는 용도로 쓰인다(아마도). 여기서 signal과 연결되는 부분은 ready method 안에 있는 import일 것이다. 여기서 signal을 import함으로써 피자집이 저장될 때마다 신호를 발생시키는 모드를 발동시키는 것이다. 그리고 이 ready method는 __init__.py에서의 한 줄 선언으로 인해 또한 발동될 것이다.

이렇게 해서 피자집 하나를 저장할 때마다 피자집의 지역 카운트를 하나씩 올리는 신호를 만들어보았다. 사실 Location에 피자집 넘버 카운트를 만들 필요도 없고, 시그널을 이용해 저장할 때마다 1씩 더할 필요도 없지만 예제를 예제답게 이해하고 넘어가주세요><



참고자료

http://www.koopman.me/2015/01/django-signals-example/

시그널에 대한 전반적인 설명
https://docs.djangoproject.com/en/1.9/topics/signals/

django가 기본으로 제공하는 built-in signal에 대한 상세 정보
https://docs.djangoproject.com/en/1.9/ref/signals/

apps.py는 어느 용도에 쓰일까요?
http://stackoverflow.com/questions/32795227/what-is-the-purpose-of-apps-py-in-django-1-9