インスタンスに変化があった時だけpre_save,post_saveを実行する
Djangoにおいて、何かしらのmodelインスタンスのsaveメソッドを呼び出した時に処理をホックする方法としてpre_save、post_saveシグナルを利用する方法があります。
例えば、次のようなTwitterInfoモデルがあったとします
# myapp/models.py from django.db import models from django.contrib.auth.models import User class TwitterInfo(models.Manager): user = models.OneToOneField(User) name = models.CharField(max_length=15, blank=True, null=True) img = models.URLField(verify_exists=False, blank=True, null=True)
Userが入力したtwitterアカウントから自動的にtwitter画像のURLを取得して保存する場合、pre_saveを用いると簡単に実装できます*1
from django.db.models.signals import pre_save from myapp.utils import get_twitter_pict def update_twitter_info(sender, instance, **kwargs): instance.img = get_twitter_pict(instance.name) pre_save.connect(update_twitter_info, sender=TwitterInfo)
これでnameが入力されていれば、そのアカウントの画像URLを自動的に保存することができます。
現状では、saveが呼ばれる度にget_twitter_pictが呼ばれますが、この処理はTwitterAPIを内部的に利用していると考えられるので、毎回毎回実行するのではなく、nameが変更されている時だけ呼び出すようにしたいです。
このような場合、僕は次のようにして解決しています
# myapp/models.py from copy import copy from django.db import models from django.contrib.auth.models import User from django.db.models.signals import pre_save from myapp.utils import get_twitter_pict class TwitterInfo(models.Manager): user = models.OneToOneField(User) name = models.CharField(max_length=15, blank=True, null=True) img = models.URLField(verify_exists=False, blank=True, null=True) def __init__(self, *args, **kwargs): super(TwitterInfo, self).__init__(*args, **kwargs) self._old = copy(self) # 古い値を保持 def update_twitter_info(sender, instance, **kwargs): if instance._old.name != instance.name: # 古い値と比較 instance.img = get_twitter_pict(instance.name) pre_save.connect(update_twitter_info, sender=TwitterInfo)
TwitterInfoをコンストラクトする時に古い値をcopyして持たせ、その値と比較することでimgの更新が必要かどうかを判断します。
*1:post_saveだとTwitterInfoのsaveを2回呼ぶ必要が生じるのでこの場合はpre_saveを利用