Validators and django.contrib.postgres.fields JSONField

115 views
Skip to first unread message

Ryan Causey

unread,
Aug 22, 2016, 12:32:50 AM8/22/16
to Django users
Hello,

I've stumbled upon an issue when using JSONField from django.contrib.postgres.fields where I can't get it to run my additional validators when calling full_clean(). To show a simplified example:

validators.py:

@deconstructible
class TestValidator(object):
   
def __call__(self, foo):
       
raise ValidationError(message = _("Expected Error"))


models.py:

class DummyModel(models.Model):
    dummyJson
= JSONField(validators = [TestValidator])


shell output:

>>> from Dummy.models import DummyModel
>>> import json
>>> dummyJsonInput = json.loads("""{"foo" : "bar"}""")
>>> dummyModelInstance = DummyModel(dummyJson = dummyJsonInput)
>>> dummyModelInstance.full_clean()
>>>


Am I doing something wrong? I've read some documentation about needing to define __eq__ for migration purposes, but I tried defining it and it didn't seem to make a difference.

Any help is appreciated.

Thanks,
-Ryan Causey

Ryan Causey

unread,
Aug 28, 2016, 2:54:47 AM8/28/16
to Django users
So I've dug into this a little more, and I've come up with the debugger trace below.

-> dummyModelInstance.full_clean()
  c
:\program files\python35\lib\site-packages\django\db\models\base.py(1210)full_clean()
-> self.clean_fields(exclude=exclude)
  c
:\program files\python35\lib\site-packages\django\db\models\base.py(1252)clean_fields()
-> setattr(self, f.attname, f.clean(raw_value, self))
  c
:\program files\python35\lib\site-packages\django\db\models\fields\__init__.py(592)clean()
-> self.run_validators(value)
  c
:\program files\python35\lib\site-packages\django\db\models\fields\__init__.py(544)run_validators()
-> v(value)
> c:\program files\python35\lib\site-packages\django\utils\deconstruct.py(16)__new__()
-> def __new__(cls, *args, **kwargs):
(Pdb) ll
 
16  ->         def __new__(cls, *args, **kwargs):
 
17                 # We capture the arguments to make returning them trivial
 
18                 obj = super(klass, cls).__new__(cls)
 
19                 obj._constructor_args = (args, kwargs)
 
20                 return obj

For some reason, when run_validators() calls the custom TestValidator, it seems that a TestValidator object is constructed rather than entering the __call__() method of the class. I can't see a reason for this to happen, so what am I doing wrong here?

Thanks,
-Ryan Causey

Daniel Roseman

unread,
Aug 28, 2016, 6:46:31 AM8/28/16
to Django users
On Sunday, 28 August 2016 07:54:47 UTC+1, Ryan Causey wrote:
So I've dug into this a little more, and I've come up with the debugger trace below.

-> dummyModelInstance.full_clean()
  c
:\program files\python35\lib\site-packages\django\db\models\base.py(1210)full_clean()
-> self.clean_fields(exclude=exclude)
  c
:\program files\python35\lib\site-packages\django\db\models\base.py(1252)clean_fields()
-> setattr(self, f.attname, f.clean(raw_value, self))
  c
:\program files\python35\lib\site-packages\django\db\models\fields\__init__.py(592)clean()
-> self.run_validators(value)
  c
:\program files\python35\lib\site-packages\django\db\models\fields\__init__.py(544)run_validators()
-> v(value)
> c:\program files\python35\lib\site-packages\django\utils\deconstruct.py(16)__new__()
-> def __new__(cls, *args, **kwargs):
(Pdb) ll
 
16  ->         def __new__(cls, *args, **kwargs):
 
17                 # We capture the arguments to make returning them trivial
 
18                 obj = super(klass, cls).__new__(cls)
 
19                 obj._constructor_args = (args, kwargs)
 
20                 return obj

For some reason, when run_validators() calls the custom TestValidator, it seems that a TestValidator object is constructed rather than entering the __call__() method of the class. I can't see a reason for this to happen, so what am I doing wrong here?

Thanks,
-Ryan Causey


This isn't anything to do with JSONFields, but your misunderstanding about what callables are and what `__call__` does. That is not a class method, but an instance method; calling a class merely instantiates it (as you should expect). Whatever is passed as a validator is called; if it's a function, it will be executed, if it's an instance, its `__call__` method will be called, but if it's a class it will be instantiated.

I'm not sure why you are passing a class at all here. Normally a validator is a method; if you need a class for some reason then you should pass an *instance* of that class, not the class itself.
-- 
DR.

Ryan Causey

unread,
Aug 28, 2016, 1:28:48 PM8/28/16
to Django users
Ah! Fantastic! I love it when I make a dumb mistake because it's an easy solution!

Instead of:
class DummyModel(models.Model):
    dummyJson
= JSONField(validators = [TestValidator])#Using class, wrong!

I should be doing:

class DummyModel(models.Model):
    dummyJson
= JSONField(validators = [TestValidator()])#Using instance of class, right!

As for using a class as a validator, I am performing some complex validation on the data inside the JSON to ensure it adheres to a specific format above and beyond the normal JSON validation, so a class based validator seemed like the right way to go. I based this off things like the Regex validator in django.core.validators found here: https://docs.djangoproject.com/en/1.10/_modules/django/core/validators/#RegexValidator. Perhaps this wasn't the right approach?

Thanks for your help!
-Ryan Causey
Reply all
Reply to author
Forward
0 new messages