views:

43

answers:

3

Hi,

say I've got:

class LogModel(models.Model):
    message = models.CharField(max_length=512)

class Assignment(models.Model):
    someperson = models.ForeignKey(SomeOtherModel)
    def save(self, *args, **kwargs):
        super(Assignment, self).save()
        old_person = #?????
        LogModel(message="%s is no longer assigned to %s"%(old_person, self).save()
        LogModel(message="%s is now assigned to %s"%(self.someperson, self).save()

My goal is to save to LogModel some messages about who Assignment was assigned to. Notice that I need to know the old, presave value of this field.

I have seen code that suggests, before super().save(), retrieve the instance from the database via primary key and grab the old value from there. This could work, but is a bit messy.

In addition, I plan to eventually split this code out of the .save() method via signals - namely pre_save() and post_save(). Trying to use the above logic (Retrieve from the db in pre_save, make the log entry in post_save) seemingly fails here, as pre_save and post_save are two seperate methods. Perhaps in pre_save I can retrieve the old value and stick it on the model as an attribute?

I was wondering if there was a common idiom for this. Thanks.

A: 

It can be easily done with signals. There are, respectively a pre-save and post-save signal for every Django Model.

Ludwik Trammer
I know that, but you didn't answer the question about keeping the old value around between the two. Also, if doing another db hit is really the right way to keep the old value.
MDBGuy
A: 

So I came up with this:

class LogModel(models.Model):
    message = models.CharField(max_length=512)

class Assignment(models.Model):
    someperson = models.ForeignKey(SomeOtherModel)

import weakref
_save_magic = weakref.WeakKeyDictionary()

@connect(pre_save, Assignment)
def Assignment_presave(sender, instance, **kwargs):
    if instance.pk:
        _save_magic[instance] = Assignment.objects.get(pk=instance.pk).someperson

@connect(post_save, Assignment)
def Assignment_postsave(sender, instance, **kwargs):
    old = None
    if instance in _save_magic:
        old = _save_magic[instance]
        del _save_magic[instance]
        LogModel(message="%s is no longer assigned to %s"%(old, self).save()
    LogModel(message="%s is now assigned to %s"%(instance.someperson, self).save()

What does StackOverflow think? Anything better? Any tips?

MDBGuy
PS: @connect is a custom decorator that just does signal.connect(f, sender=self.sender)
MDBGuy
A: 

A couple of months ago I found somewhere online a good way to do this...

class YourModel(models.Model):

    def __init__(self, *args, **kwargs):
        super(YourModel, self).__init__(*args, **kwargs)
        self.original = {}
        id = getattr(self, 'id', None)

        for field in self._meta.fields:
            if id:
                self.original[field.name] = getattr(self, field.name, None)
            else:
                self.original[field.name] = None

Basically a copy of the model fields will get saved to self.original. You can then access it elsewhere in the model...

def save(self, *args, **kwargs):
    if self.original['my_property'] != self.my_property:
        # ...
T. Stone
Hm, good idea. Thanks a lot!
MDBGuy