views:

1473

answers:

3

Where should the validation of model fields go in django?

I could name at least two possible choices: in the overloaded .save() method of the model or in the .to_python() method of the models.Field subclass (obviously for that to work you must write custom fields).

Possible use cases:

  • when it is absolutely neccessary to ensure, that an empty string doesn't get written into the database (blank=False keyword argument doesn't work here, it is for form validation only)
  • when it is neccessary to ensure, that "choices" keyword argument gets respected on a db-level and not only in admin interface (kind of emulating a enum datatype)

There is also a class-level attribute empty_strings_allowed in the models.Field base class definition and derived classes happily override it, however it doesn't seem to produce any effect on the database level, meaning I can still construct a model with empty-string fields and save it to the database. Which I want to avoid (yes, it is neccessary).

Possible implementations are

on the field level:

class CustomField(models.CharField):
    __metaclass__ = models.SubfieldBase
    def to_python(self, value):
        if not value:
            raise IntegrityError(_('Empty string not allowed'))
        return models.CharField.to_python(self, value)

on the model level:

class MyModel(models.Model)
    FIELD1_CHOICES = ['foo', 'bar', 'baz']
    field1 = models.CharField(max_length=255, 
               choices=[(item,item) for item in FIELD1_CHOICES])

    def save(self, force_insert=False, force_update=False):
        if self.field1 not in MyModel.FIELD1_CHOICES:
            raise IntegrityError(_('Invalid value of field1'))
        # this can, of course, be made more generic
        models.Model.save(self, force_insert, force_update)

Perhaps, I am missing something and this can be done easier (and cleaner)?

+1  A: 

If I understand you "clearly" - you must override function get_db_prep_save instead of to_python

Oduvan
That's actually a good idea. However, I've specifically mentioned .to_python(), because it gets fired once the field is initialized (if you specify __metaclass__ = models.SubfieldBase) so the validation happens early, meaning you can't even initialize a model if you are passing bad values for the fields. Perhaps, your way is the right way, though. At least it makes some sense to me.
shylent
so override both of them
Oduvan
+1  A: 

The root issue for this, is that the validation should happen on models. This has been discussed for quite some time in django (search form model aware validation on the dev mailing list). It leads to either duplication or things escaping validation before hitting the db.

While that doesn't hit trunk, Malcolm's "poor man's model validation solution" is probably the cleanest solution to avoid repeating yourself.

Arthur Debert
That link is broken for me...
TM
I've read the contents of it in the google cache. It does make sense, yes. It is not very helpful if I am not going to to use forms (don't need forms to input data, right?), however it is, certainly, a way to avoid repeating myself.
shylent
+6  A: 

Looking forward a few months, there is already a model-validation Django branch that provides a full model-validation framework similar to form validation. It's planned for merge in time for Django 1.2. If you want to check it out and test it, that would help the process.

UPDATE: Model validation has been merged into Django trunk, and there is now documentation.

Carl Meyer
Great, great, this is just great! I've skimmed through the source (by the way, is there any way to access the documentation as it can be done with trunk?) and it seems like just the thing I need. I mean, I am all for rolling my own, but django is _excellent_ at providing a uniform way of doing things (well, IMO, anyway).
shylent
Checkout that branch, make sure you have docutils and Sphinx installed, then go into the docs/ directory and run "make html". That should build the docs in HTML form just like they are on the Django website, and you can access them locally.
Carl Meyer
Ok, I am back from reading the source (in particular `models/fields/__init__.py`, models/base.py and core/validators.py), since the documentation as of now says nothing about model validation. One should note, however, that it works almost the same as forms validation (at least, the general logic is more or less the same). Anyway, this is something I was looking for. I just hope my apps won't break horribly if I just switch from the trunk to this branch.
shylent
Certainly shouldn't break your apps! If it does, report it as a bug and you'll be doing all of us a favor. Didn't realize the docs weren't there yet, I'm sure that's on the todo list before it gets merged; maybe you want to write them? ;-)
Carl Meyer
I'd certainly love to help out django. And, because of the lack of docs, I had to resort to code-diving, which, in the case of django, is not an unpleasant experience at all and it gave me a solid understanding of how things work (even though it took some time). By the way, nothing really broke, and the validation part certainly does work.
shylent