2009-07-29 21 views
23

モデルのいずれかでフィールドが変更されたときにアクションを実行するにはどうすればよいですか?この特定のケースでは、私は、このモデルがあります:Djangoのフィールドの変更によってトリガされるアクション

class Game(models.Model): 
    STATE_CHOICES = (
     ('S', 'Setup'), 
     ('A', 'Active'), 
     ('P', 'Paused'), 
     ('F', 'Finished') 
     ) 
    name = models.CharField(max_length=100) 
    owner = models.ForeignKey(User) 
    created = models.DateTimeField(auto_now_add=True) 
    started = models.DateTimeField(null=True) 
    state = models.CharField(max_length=1, choices=STATE_CHOICES, default='S') 

をし、状態がへのセットアップから行くとき、私は、作成した単位、及び(とりわけ)、現在の日時が移入「開始」フィールドを持っていると思いますアクティブ。

私は、モデルインスタンスメソッドが必要だと思うが、ドキュメントはこのように使用することについてはあまり言わないようだ。

アップデート:私は私のゲームのクラスに以下を追加しました:効果的です

def __init__(self, *args, **kwargs): 
     super(Game, self).__init__(*args, **kwargs) 
     self.old_state = self.state 

    def save(self, force_insert=False, force_update=False): 
     if self.old_state == 'S' and self.state == 'A': 
      self.started = datetime.datetime.now() 
     super(Game, self).save(force_insert, force_update) 
     self.old_state = self.state 
+0

私はあなたのコメントに沿って私の答えを更新しました。 –

+0

django-model-utilsは、開始したフィールドの大文字小文字の区別に役立つモニタフィールドを実装しています:https://django-model-utils.readthedocs.org/en/latest/fields.html#monitorfield – jdcaballerov

答えて

10

saveメソッドをオーバーライドする必要があります。stateフィールドが変更されているかどうかを確認し、必要に応じてstartedを設定して、モデルベースクラスをデータベースに保存します。

厄介な部分は、フィールドが変更されたかどうかを調べることです。これであなたを助けるためにこの質問にミックスインや他のソリューションをチェックアウト:

+1

私はどのように感じますか? DjangoのORMのメソッドをオーバーライドします。 IMOでは、これはdjango.db.models.signals.post_saveを使用する方が良いでしょう。 –

+3

@cpharmston:私はこれが受け入れられると思います:http://docs.djangoproject.com/en/dev/topics/db/models/#overriding-predefined-model-methods - モデル以外のエンティティ通知されることを望みますが、この場合エンティティはモデルそのものなので、saveをオーバーライドします(これはオブジェクト指向のメソッドではかなり一般的です)。 – ars

+0

最後に試したときに大量の管理操作でsave()をオーバーライドしても動作しませんでした(1.0と思います) –

14

Djangoはsignalsと呼ばれる気の利いた機能を持っているが、特定の時間にオフに設定されているトリガー:

  • モデルの保存メソッドが呼び出される前後
  • モデルの削除メソッドの前後は、
  • と呼ばれます。
  • HTTPリクエストの前/後に

完全な情報については、ドキュメントを読んでください。ただし、受信者関数を作成して信号として登録するだけです。これは通常models.pyで行われます。

from django.core.signals import request_finished 

def my_callback(sender, **kwargs): 
    print "Request finished!" 

request_finished.connect(my_callback) 

シンプルな、え?

5

一つの方法は、状態のためのセッターを追加することです。それはちょうど普通の方法で、何も特別なものではありません。

class Game(models.Model): 
    # ... other code 

    def set_state(self, newstate): 
     if self.state != newstate: 
      oldstate = self.state 
      self.state = newstate 
      if oldstate == 'S' and newstate == 'A': 
       self.started = datetime.now() 
       # create units, etc. 

更新:あなたは、これが変更がモデルインスタンスに行われるたび、あなたは(上記set_state代わり)のようなものであるGame__setattr__メソッドを使用することができますがトリガされるようにしたい場合この:それ(__setattr__)が文書化され、標準のPythonの機能であるとして、あなたは特に、Djangoのドキュメントでこれを見つけられないでしょう

def __setattr__(self, name, value): 
    if name != "state": 
     object.__setattr__(self, name, value) 
    else: 
     if self.state != value: 
      oldstate = self.state 
      object.__setattr__(self, name, value) # use base class setter 
      if oldstate == 'S' and value == 'A': 
       self.started = datetime.now() 
       # create units, etc. 

注意hereであり、Django固有ではありません。

注:1より古いdjangoのバージョンについてはわかりません。2、しかし__setattr__を使用してこのコードは動作しません、それはself.stateにアクセスしようとすると、2番目のifの直後に失敗します。

私はこれに似たものを試しましたが、state(最初は__init__)の初期化を強制してこの問題を解決しようとしましたが、これは予期しない動作につながります。

私はこのコードを削除していません。以前のバージョン(または将来のバージョン)のdjangoで動作する可能性がありますが、このコードを削除することはできません。 self.state私が気付いていない問題

+0

の例の下の注を参照してください。しかし、編集するたびにそのようなメソッドを使用する方法を私には明らかにしていませんでした例えば、管理者ページから。 –

4

@dcramerはこの問題のために(私の意見では)より洗練されたソリューションを思いついた。

https://gist.github.com/730765

from django.db.models.signals import post_init 

def track_data(*fields): 
    """ 
    Tracks property changes on a model instance. 

    The changed list of properties is refreshed on model initialization 
    and save. 

    >>> @track_data('name') 
    >>> class Post(models.Model): 
    >>>  name = models.CharField(...) 
    >>> 
    >>>  @classmethod 
    >>>  def post_save(cls, sender, instance, created, **kwargs): 
    >>>   if instance.has_changed('name'): 
    >>>    print "Hooray!" 
    """ 

    UNSAVED = dict() 

    def _store(self): 
     "Updates a local copy of attributes values" 
     if self.id: 
      self.__data = dict((f, getattr(self, f)) for f in fields) 
     else: 
      self.__data = UNSAVED 

    def inner(cls): 
     # contains a local copy of the previous values of attributes 
     cls.__data = {} 

     def has_changed(self, field): 
      "Returns ``True`` if ``field`` has changed since initialization." 
      if self.__data is UNSAVED: 
       return False 
      return self.__data.get(field) != getattr(self, field) 
     cls.has_changed = has_changed 

     def old_value(self, field): 
      "Returns the previous value of ``field``" 
      return self.__data.get(field) 
     cls.old_value = old_value 

     def whats_changed(self): 
      "Returns a list of changed attributes." 
      changed = {} 
      if self.__data is UNSAVED: 
       return changed 
      for k, v in self.__data.iteritems(): 
       if v != getattr(self, k): 
        changed[k] = v 
      return changed 
     cls.whats_changed = whats_changed 

     # Ensure we are updating local attributes on model init 
     def _post_init(sender, instance, **kwargs): 
      _store(instance) 
     post_init.connect(_post_init, sender=cls, weak=False) 

     # Ensure we are updating local attributes on model save 
     def save(self, *args, **kwargs): 
      save._original(self, *args, **kwargs) 
      _store(self) 
     save._original = cls.save 
     cls.save = save 
     return cls 
    return inner 
14

それは答えたが、ここでは信号、post_initとpost_saveを使用した例ですされています。

class MyModel(models.Model): 
    state = models.IntegerField() 
    previous_state = None 

    @staticmethod 
    def post_save(sender, **kwargs): 
     instance = kwargs.get('instance') 
     created = kwargs.get('created') 
     if instance.previous_state != instance.state or created: 
      do_something_with_state_change() 

    @staticmethod 
    def remember_state(sender, **kwargs): 
     instance = kwargs.get('instance') 
     instance.previous_state = instance.state 

post_save.connect(MyModel.post_save, sender=MyModel) 
post_init.connect(MyModel.remember_state, sender=MyModel) 
0

私のソリューションは、アプリケーションの__init__.pyに次のコードを置くことです:

from django.db.models import signals 
from django.dispatch import receiver 


@receiver(signals.pre_save) 
def models_pre_save(sender, instance, **_): 
    if not sender.__module__.startswith('myproj.myapp.models'): 
     # ignore models of other apps 
     return 

    if instance.pk: 
     old = sender.objects.get(pk=instance.pk) 
     fields = sender._meta.local_fields 

     for field in fields: 
      try: 
       func = getattr(sender, field.name + '_changed', None) # class function or static function 
       if func and callable(func) and getattr(old, field.name, None) != getattr(instance, field.name, None): 
        # field has changed 
        func(old, instance) 
      except: 
       pass 

と私のモデルクラスに<field_name>_changed静的メソッドを追加します。

class Product(models.Model): 
    sold = models.BooleanField(default=False, verbose_name=_('Product|sold')) 
    sold_dt = models.DateTimeField(null=True, blank=True, verbose_name=_('Product|sold datetime')) 

    @staticmethod 
    def sold_changed(old_obj, new_obj): 
     if new_obj.sold is True: 
      new_obj.sold_dt = timezone.now() 
     else: 
      new_obj.sold_dt = None 

そしてsold_dtフィールドは、いつ変更されますsoldフィールドが変更されます。

モデルで定義されたフィールドの変更は、古いオブジェクトと新しいオブジェクトをパラメータとして使用して<field_name>_changedメソッドをトリガします。

関連する問題