django 리팩토링 :: 모델 save 오버라이딩 -> signal

phyyou·2020년 11월 10일
1
post-thumbnail

django 모델의 save() 오버라이딩


django의 db.models를 들어가 보면 위의 사진과 같이 save 메소드가 있고, 늘 그렇듯이 이걸 오버라이딩해서 사용할 수 있다. 나는 이를 이용해 foreign key로 연결한 모델의 한 컬럼을 바꿔주는 것으로 오버라이딩해서 사용했다.

def save(self, *args, **kwargs):        
        relate_student = Student.objects.get(slug=self.student.slug)
        try:
            Rating_Avg = round((list(relate_student.rating.aggregate(Avg('stars')).values())[0] + self.stars) / 2, 2)
            relate_student.rating_stars = Rating_Avg
            relate_student.save() 
        except TypeError:
            Rating_Avg = self.stars
            relate_student.rating_stars = Rating_Avg
            relate_student.save() 
        else:
            pass  
        return super(Rating, self).save(*args, **kwargs)

하지만 이 과정은 정말 순탄치 않았다. 계속 django 쿼리 메소드를 사용하면서 여간 불편한 점이 없었다. 또한 무조건 저장할 때 실행되고, 저장되기 전에 실행되기 때문 에 Rating_Avg = round((list(relate_student.rating.aggregate(Avg('stars')).values())[0] + self.stars) / 2, 2) 에 타입에러가 나는것을 억지로 except를 쓰면서 바꿔준 것이다.

signal

그러던 도중 django에서 singnal을 찾게 되었고 내가 생각하던 것이랑 똑같아 사용해 보기로 하였다.

사실 이런 용도 외에도 사용 가능성이 무궁무진하지만, 지금 사용하는 것은 post_save signal이다.

여러 종류의 signal이 있는데, 여기서는 post_save signal을 이용해서 DB model에 관련해서 save가 작동하면, 저장이 완료된 이후에 위의 행동을 처리하도록 리팩토링해보자.

이제 그럼 signal.py 파일을 작성해보자

from django.db.models.signals import post_save
from django.dispatch import receiver
from student.models import Student, Rating
from django.db.models import F, Sum, Count, Case, When, Avg

@receiver(post_save, sender = Rating)
def rating_post_save(sender, **kwargs ):
    student = kwargs['instance'].student
    Rating_Avg = round((list(student.rating.aggregate(Avg('stars')).values())[0]), 2)
    student.rating_stars = Rating_Avg
    student.save()

또한 이를 읽기 위해 apps.py 파일에 앱 클래스에 ready() 메소드를 올려준다.

    ...
    def ready(self):
        import student.signals

이 외에도 reciever를 지정해서 하는 방법도 많이 있으니 공식 문서를 참고해서 다른 방법도 참고해 보자

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

default_app_config = 'student.apps.StudentConfig'

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

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

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

이렇게 해서 Rating 하나를 저장할 때마다 student의 rating을 계산하여 지정하는 signal을 만들어 리팩토링해 보았다.


잘 동작한다


참고 자료
[django signal의 이해를 위한 간단한 예제]
(https://dgkim5360.tistory.com/entry/django-signal-example)

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

[시그널에 대한 전반적인 설명]
(https://docs.djangoproject.com/en/3.1/topics/signals/)

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

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

profile
박효영

0개의 댓글