Actions triggered by field change in Django


Question

How do I have actions occur when a field gets changed in one of my models? In this particular case, I have this model:

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')

and I would like to have Units created, and the 'started' field populated with the current datetime (among other things), when the state goes from Setup to Active.

I suspect that a model instance method is needed, but the docs don't seem to have much to say about using them in this manner.

Update: I've added the following to my Game class:

    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
1
29
7/29/2009 9:58:35 PM

Accepted Answer

Basically, you need to override the save method, check if the state field was changed, set started if needed and then let the model base class finish persisting to the database.

The tricky part is figuring out if the field was changed. Check out the mixins and other solutions in this question to help you out with this:

13
5/23/2017 11:47:19 AM

It has been answered, but here's an example of using signals, post_init and post_save.

from django.db.models.signals import post_save, post_init

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)

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon