Showing custom model validation exceptions in the Django admin site

Viewed 28506

I have a booking model that needs to check if the item being booked out is available. I would like to have the logic behind figuring out if the item is available centralised so that no matter where I save the instance this code validates that it can be saved.

At the moment I have this code in a custom save function of my model class:

def save(self):
    if self.is_available(): # my custom check availability function
        super(MyObj, self).save()
    else:
        # this is the bit I'm stuck with..
        raise forms.ValidationError('Item already booked for those dates')

This works fine - the error is raised if the item is unavailable, and my item is not saved. I can capture the exception from my front end form code, but what about the Django admin site? How can I get my exception to be displayed like any other validation error in the admin site?

5 Answers

In django 1.2, model validation has been added.

You can now add a "clean" method to your models which raise ValidationError exceptions, and it will be called automatically when using the django admin.

The clean() method is called when using the django admin, but NOT called on save().

If you need to use the clean() method outside of the admin, you will need to explicitly call clean() yourself.

http://docs.djangoproject.com/en/dev/ref/models/instances/?from=olddocs#validating-objects

So your clean method could be something like this:

from django.core.exceptions import ValidationError

class MyModel(models.Model):

    def is_available(self):
        #do check here
        return result

    def clean(self):
        if not self.is_available():
            raise ValidationError('Item already booked for those dates')

I haven't made use of it extensively, but seems like much less code than having to create a ModelForm, and then link that form in the admin.py file for use in django admin.

I've also tried to solve this and there is my solution- in my case i needed to deny any changes in related_objects if the main_object is locked for editing.

1) custom Exception

class Error(Exception):
    """Base class for errors in this module."""
    pass

class EditNotAllowedError(Error):
    def __init__(self, msg):
        Exception.__init__(self, msg)

2) metaclass with custom save method- all my related_data models will be based on this:

class RelatedModel(models.Model):
    main_object = models.ForeignKey("Main")

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        if self.main_object.is_editable():
            super(RelatedModel, self).save(*args, **kwargs)
        else:
            raise EditNotAllowedError, "Closed for editing"

3) metaform - all my related_data admin forms will be based on this (it will ensure that admin interface will inform user without admin interface error):

from django.forms import ModelForm, ValidationError
...
class RelatedModelForm(ModelForm):
    def clean(self):
    cleaned_data = self.cleaned_data
    if not cleaned_data.get("main_object")
        raise ValidationError("Closed for editing")
    super(RelatedModelForm, self).clean() # important- let admin do its work on data!        
    return cleaned_data

To my mind it is not so much overhead and still pretty straightforward and maintainable.

The best way is put the validation one field is use the ModelForm... [ forms.py]

class FormProduct(forms.ModelForm):

class Meta:
    model = Product

def clean_photo(self):
    if self.cleaned_data["photo"] is None:
        raise forms.ValidationError(u"You need set some imagem.")

And set the FORM that you create in respective model admin [ admin.py ]

class ProductAdmin(admin.ModelAdmin):
     form = FormProduct

from django.db import models from django.core.exceptions import ValidationError

class Post(models.Model): is_cleaned = False

title = models.CharField(max_length=255)

def clean(self):
    self.is_cleaned = True
    if something():
        raise ValidationError("my error message")
    super(Post, self).clean()

def save(self, *args, **kwargs):
    if not self.is_cleaned:
        self.full_clean()
    super(Post, self).save(*args, **kwargs)
Related