4

TLDR;

How do I provide custom, model level, field validation which is encapsulated inside the field class?

The Rest

I am playing with the two JSONField implementations, (first, second). I am using a combination of Django and Django REST framework for my application. I don't do anything on the form level, only a web API exposing the model.

Ideally I would like to write this validation in one place and for it to run on the serializer level + model level (like when I pass a validators=[x]). I can't use the usual validators=[x] because I need to allow blank=True but also validate the blank values type :|.

I have the use case that I want to validate the contents (keys, types of values) of my JSONField. Using validictory, I can do this like so:

  • force a call to self.full_clean() in my save()
  • override clean() on my model and do the validation there

However, what I really want to do is: add this validation to a subclass of the JSONField. I want to leave as much of the parent Field class to do it's thing. So far, I have:

from django.db import models
from jsonfield import JSONField

class myValidatorField(JSONField):
    def validate(self, *args, **kwargs):
        # do some validation here
        super(myValidatorField, self).validate(*args, **kwargs)

class MyModel(models.Model):
    jsonstuff = myValidatorField(default=[])

    def save(self, *args, **kwargs):
        self.full_clean()
        super(MyModel, self).save(*args, **kwargs)

However, I can't get this to work. This validate() method doesn't run for the second implementation, and for the first, it runs 4 times.

Confused.

1 Answers1

1

I ended up with this code and it seems to work the trick.

FYI, in my use case, I had to implement a custom django rest framework exception handler to catch all my model level ValidationError errors and convert them into web 400 errors.

# fields.py
import validictory
from jsonfield import JSONField

class JSONSchemaField(JSONField):
    """A field that will ensure the data entered into it is valid JSON *and*
    internally validate to a JSON schema of your choice."""
    def __init__(self, *args, **kwargs):
        self.schema = kwargs.pop('schema', {})
        super(JSONSchemaField, self).__init__(*args, **kwargs)

    def clean(self, raw_value, model_instance):
        try:
            validictory.validate(raw_value, self.schema)
        except (validictory.FieldValidationError,
                validictory.SchemaError,
                validictory.validator.RequiredFieldValidationError) as err:
            raise ValidationError(err)
        return super(JSONSchemaField, self).clean(raw_value, model_instance)

# mixins.py
class ModelValidationMixin(object):
"""Django currently doesn't force validation on the model level
for compatibility reasons. We enforce here, that on each save,
a full valdation run will be done the for model instance"""
def save(self, *args, **kwargs):
    self.full_clean()
    super(ModelValidationMixin, self).save(*args, **kwargs)

# models.py
class MyModel(ModelValidationMixin):
    json = JSONSchemaField(default='[]', schema=SCHEMA)
  • watch out: `validictory` has been deprecated in 2018 in favor or `jsonschema` (or others: https://json-schema.org/implementations.html#validators ) – nuts May 09 '21 at 11:09