views:

700

answers:

1

I am writing a Django application that will track changes to the models, in a similar way to the admin interface. For example, I will be able to display a list of changes to a model, that look something like Changed Status from Open to Closed.

I am using the pre_save signal to do this, comparing the relevant fields between the existing item in the database, and the "instance" which is being saved. To get the existing item, I have to do sender._default_manager.get(pk=sender.pk) which seems a bit messy, but that part works.

The problem is, the view for changing this model calls the save() method on the form twice (first with commit=False) - this means that 2 changes get logged in the database, as the pre_save signal is emitted twice.

Is there any way I can accomplish this? Maybe in a different way altogether, though I remember reading that the Django admin app uses signals to track changes that users make.

+1  A: 

Looking through the Django source, it seems that pre_save signals are sent on every call to save, even if commit is false. I would suggest inserting on the first pre_save, but add a flag column to the changes table, e.g.

class FooChanges(models.Model):
    foo = models.ForeignKey(Foo)
    dt = models.DateTimeField(default=datetime.now)
    field = models.CharField(max_length=50)
    value = models.CharField(max_length=50) # Or whatever is appropriate here
    finished = models.BooleanField(default=False)

Then, your presave can be:

def pre_save_handler(sender, instance):
    foo_changes, created = FooChanges.objects.get_or_create(foo=instance, finished=False, field='Status', value=instance.status)
    if not created:
        foo_changes.finished = True
        foo_changes.save()

So on the first pre_save, you actually insert the change. On the second pass, you retrieve it from the database, and set the flag to false to make sure you don't pick it up the next time Foo's status changes.

tghw
Genius. Thanks :)
Rob Golding