8

I have a model that saves an Excursion. The user can change this excursion, but I need to know what the excursion was before he change it, because I keep track of how many "bookings" are made per excursion, and if you change your excursion, I need to remove one booking from the previous excursion.

Im not entirely sure how this should be done.

Im guessing you use a signal for this?

Should I use pre_save, pre_init or what would be the best for this?

pre_save is not the correct one it seems, as it prints the new values, not the "old value" as I expected

@receiver(pre_save, sender=Delegate)
def my_callback(sender, instance, *args, **kwargs):
    print instance.excursion
Harry
  • 13,091
  • 29
  • 107
  • 167
  • 1
    If this was my app: [django model utils field tracker](https://django-model-utils.readthedocs.org/en/latest/utilities.html#accessing-a-field-tracker) + [post_save signal](https://docs.djangoproject.com/en/dev/ref/signals/#post-save) – dani herrera Sep 03 '14 at 15:12
  • thats pretty cool! thanks, let me try that – Harry Sep 03 '14 at 15:13
  • check it. Then let me know if it is a solution for you in order to post it as answer. Good luck. – dani herrera Sep 03 '14 at 15:15
  • In what way django model utils change your db? – dani herrera Sep 03 '14 at 15:21
  • Well from what I read I need to use their db structure for my models. But here is a simple solution.. in the model save method: print Delegate.objects.get(pk=self.pk).excursion > this would be the old one, before save – Harry Sep 03 '14 at 15:22
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/60548/discussion-between-danihp-and-harry). – dani herrera Sep 03 '14 at 15:23

4 Answers4

11

You have several options.

First one is to overwrite save method:

#Delegate
def save(self, *args, **kwargs):
    if self.pk:
        previous_excursion = Delegate.objects.get(self.pk).excursion
    super().save(*args, **kwargs)
    if self.pk and (self.excursion != previous_excursion):
        #change booking

Second one is binding function to post save signal + django model utils field tracker:

@receiver(post_save, sender=Delegate)
def create_change_booking(sender,instance, signal, created, **kwargs):
    if created:
        previous_excursion = get it from django model utils field tracker
        #change booking

And another solution is in pre_save as you are running:

@receiver(pre_save, sender=Delegate)
def my_callback(sender, instance, *args, **kwargs):
    previous_excursion = Delegate.objects.get(self.pk).excursion
    if instance.pk and instance.excursion != previous_excursion:
        #change booking  
nik_m
  • 11,825
  • 4
  • 43
  • 57
dani herrera
  • 48,760
  • 8
  • 117
  • 177
  • The 2nd method `binding function to post save signal + django model utils field tracker` doesn't work, because FieldTracker looks at whether value changed after last save. – eugene Mar 03 '15 at 03:54
  • 1
    Runs for me. 'Post save' means 'after save'. – dani herrera Mar 03 '15 at 07:11
  • FieldTracker gives value that's changed after last save. It doesn't give you value that was changed in the last save. – eugene Mar 03 '15 at 07:16
  • @eugene, take a look to FiekdTraker docs examples, see `previous` example method. It resturns the field value before save. It isn't? – dani herrera Mar 03 '15 at 07:26
  • it returns the field value during the last save, not before save, which means on `post_save` when you call `previous`, you get `self.value` – eugene Mar 03 '15 at 08:04
3

You can use django model utils to track django model fields. check this example.

pip install django-model-utils

Then you can define your model and use fieldtracker in your model .

from django.db import models
from model_utils import FieldTracker

class Post(models.Model):
    title = models.CharField(max_length=100)
    body = models.TextField()
    tracker = FieldTracker()
    status = models.CharField(choices=STATUS, default=STATUS.draft, max_length=20)

after that in post save you can use like this :

@receiver(post_save, sender=Post) 
def my_callback(sender, instance,*args, **kwargs):
    print (instance.title)
    print (instance.tracker.previous('title'))
    print (instance.status)
    print (instance.tracker.previous('status'))

This will help you a lot to do activity on status change. as because overwrite save method is not good idea.

Yogesh dwivedi Geitpl
  • 4,252
  • 2
  • 20
  • 34
1

As an alternative and if you are using Django forms:

The to-be version of your instance is stored in form.instance of the Django form of your model. On save, validations are run and this new version is applied to the model and then the model is saved.

Meaning that you can check differences between the new and the old version by comparing form.instance to the current model.

This is what happens when the Django Admin's save_model method is called. (See contrib/admin/options.py)

If you can make use of Django forms, this is the most Djangothic way to go, I'd say.

This is the essence on using the Django form for handling data changes:

form = ModelForm(request.POST, request.FILES, instance=obj)
new_object = form.instance  # not saved yet
# changes are stored in form.changed_data
new_saved_object = form.save()

form.changed_data will contain the changed fields which means that it is empty if there are no changes.

Risadinha
  • 16,058
  • 2
  • 88
  • 91
0

There's yet another option:

Django's documentation has an example showing exactly how you could do this by overriding model methods.

In short:

  1. override Model.from_db() to add a dynamic attribute containing the original values
  2. override the Model.save() method to compare the new values against the originals

This has the advantage that it does not require an additional database query.

djvg
  • 11,722
  • 5
  • 72
  • 103