views:

573

answers:

3

I have a status field which has 3 values: pending, activated and rejected. If I am changing the value of status I want to have a check that activated cannot be changed to pending. I do not want to write stored-procs for this. Can I have the previous value in Django before saving?

Means new and old value.

+2  A: 

You can do this in an overridden save method. The thing to remember is that Django model instances aren't the actual database objects, they just get their values from there on load. So you can easily go back to the database before saving your current object to get the existing values.

def save(self, *args, **kwargs):
    if self.status == 'pending':
         old_instance = MyClass.objects.get(pk=self.pk)
         if old_instance.status == 'activated':
              raise SomeError
     super(MyModel, self).save(*args, **kwargs)

There is currently no good way of returning an error message to the user other than raising an exception. There is a Google Summer of Code project currently under way to enable 'model validation', but this will not be ready for a few months.

If you want to do something similar in the admin, the best way is to define a custom ModelForm with an overridden clean() method. However, this time since this is a form you already have access to the old values without hitting the db again. Another benefit is that you can return a form validation error to the user.

class MyModelForm(forms.ModelForm):

     class Meta:
          model = MyModel

    def clean_status(self):
        status = self.cleaned_data.get('status', '')
        if status == 'pending':
             if self.instance and self.instance.status == 'activated':
                  raise forms.ValidationError(
                      'You cannot change activated to pending'
                  )
         return status

 class MyModelAdmin(forms.ModelAdmin):
     form = MyModelForm
     model = MyModel
Daniel Roseman
that's because you should probably override ModelAdmin.save_model method: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-methods
ohnoes
I've added an alternative using the admin above.
Daniel Roseman
the if condition should be if self.instance and self.instance.status == 'activated':raise forms.ValidiationError
ha22109
A: 

Instead of overriding the save method, wouldn't this be a good place to use signals? Intercept the save before commit, check the current value in the database, and either forward the save on, or reject it?

Now I'm not sure if the signal blocks the save request or if it happens asynch, so feel free to downvote this answer if a signal can not be used to prevent the save happening upon validation.

I'm against overriding inbuilt methods if there is another inbuilt tool that works just as well.

Josh Smeaton
I think, overwriting save() and delete() can be good practice. I have a the classes Photo and Thumbnail. The Thumb has a item = models.ForeignKey(Photo). photo.delete() is overwritten and will delete all thumbs (for t in self.thumbnail_set.all():t.delete()) before running super(Photo, self).delete() .If the removing of the thumb would be done in a signal, the maintaining of the code would be harder.
vikingosegundo
but __init__ I wolud never overload. I would use signal instead.
vikingosegundo
+4  A: 
def clean_status(self):
    status = self.cleaned_data.get('status')
    if status == 'pending':
        if self.instance and self.instance.status == 'activated':
            raise forms.ValidationError('You cannot change activated to pending')

    return status
ha22109
hey Dominic Rodger can u help in 'inline-django'.My question in django
ha22109