Package: django-dynamic-model-validation
django-dynamic-model-validationIntroductionThis package aims to provide tools needed to define custom field validation logic which can be used independently or with
django forms, test cases, API implementation or any model operation that requires saving data to the database.This can also be extended by defining table check constraints if needed but currently validation
will only be handled at the model level.Installationdjango-dynamic-model-validation is distributed onPyPIas a universal
wheel and is available on Linux/macOS and Windows and supports
Python 2.7/3.5+ and PyPy.pipinstalldjango-dynamic-model-validationUsageThis provides model level validation which includes:Conditional field validationCross field validationRequired field validationOptional field validationRequire a single field in a collectionfromdjango.dbimportmodelsfromdynamic_validatorimportModelFieldRequiredMixinclassTestModel(ModelFieldRequiredMixin,models.Model):amount=models.DecimalField(max_digits=5,decimal_places=2,null=True,blank=True)fixed_price=models.DecimalField(max_digits=7,decimal_places=2,null=True,blank=True)percentage=models.DecimalField(max_digits=3,decimal_places=0,null=True,blank=True)REQUIRED_TOGGLE_FIELDS=[['amount','fixed_price','percentage'],# Require only one of the following fields.]pythonmanage.pyshell
...
>>>fromdecimalimportDecimal
>>>fromdemo.modelsimportTestModel
>>>TestModel.objects.create(amount=Decimal('2.50'),fixed_price=Decimal('3.00'))...
ValueError:{'fixed_price':ValidationError([u'Please provide only one of: Amount, Fixed price, Percentage'])}Require all fieldsfromdjango.dbimportmodelsfromdynamic_validatorimportModelFieldRequiredMixinclassTestModel(ModelFieldRequiredMixin,models.Model):amount=models.DecimalField(max_digits=5,decimal_places=2,null=True,blank=True)fixed_price=models.DecimalField(max_digits=7,decimal_places=2,null=True,blank=True)percentage=models.DecimalField(max_digits=3,decimal_places=0,null=True,blank=True)REQUIRED_FIELDS=['amount']# Always requires an amount to create the instance.pythonmanage.pyshell
...
>>>fromdecimalimportDecimal
>>>fromdemo.modelsimportTestModel
>>>TestModel.objects.create(fixed_price=Decimal('3.00'))...
ValueError:{'amount':ValidationError([u'Please provide a value for: "amount".'])}Optionally required fieldsfromdjango.dbimportmodelsfromdynamic_validatorimportModelFieldRequiredMixinclassTestModel(ModelFieldRequiredMixin,models.Model):amount=models.DecimalField(max_digits=5,decimal_places=2,null=True,blank=True)fixed_price=models.DecimalField(max_digits=7,decimal_places=2,null=True,blank=True)percentage=models.DecimalField(max_digits=3,decimal_places=0,null=True,blank=True)OPTIONAL_TOGGLE_FIELDS=[['fixed_price','percentage']# Optionally validates that only fixed price/percentage are provided when present.]pythonmanage.pyshell
...
>>>fromdecimalimportDecimal
>>>fromdemo.modelsimportTestModel
>>>first_obj=TestModel.objects.create(amount=Decimal('2.0'))>>>second_obj=TestModel.objects.create(amount=Decimal('2.0'),fixed_price=Decimal('3.00'))>>>third_obj=TestModel.objects.create(amount=Decimal('2.0'),fixed_price=Decimal('3.00'),percentage=Decimal('10.0'))...
ValueError:{'percentage':ValidationError([u'Please provide only one of: Fixed price, Percentage'])}Conditional required fieldsfromdjango.dbimportmodelsfromdjango.confimportsettingsfromdynamic_validatorimportModelFieldRequiredMixinclassTestModel(ModelFieldRequiredMixin,models.Model):user=models.ForeignKey(settings.AUTH_USER_MODEL)amount=models.DecimalField(max_digits=5,decimal_places=2,null=True,blank=True)fixed_price=models.DecimalField(max_digits=7,decimal_places=2,null=True,blank=True)percentage=models.DecimalField(max_digits=3,decimal_places=0,null=True,blank=True)CONDITIONAL_REQUIRED_FIELDS=[(lambdainstance:instance.user.is_active,['amount','percentage'],),]pythonmanage.pyshell
...
>>>fromdecimalimportDecimal
>>>fromdjango.contrib.authimportget_user_model
>>>fromdemo.modelsimportTestModel
>>>user=get_user_model().objects.create(username='test',is_active=True)>>>first_obj=TestModel.objects.create(user=user,amount=Decimal('2.0'))...
ValueError:{u'percentage':ValidationError([u'Please provide a value for: "percentage"'])}Conditional required optional fieldsfromdjango.dbimportmodelsfromdjango.confimportsettingsfromdynamic_validatorimportModelFieldRequiredMixinclassTestModel(ModelFieldRequiredMixin,models.Model):user=models.ForeignKey(settings.AUTH_USER_MODEL)amount=models.DecimalField(max_digits=5,decimal_places=2,null=True,blank=True)fixed_price=models.DecimalField(max_digits=7,decimal_places=2,null=True,blank=True)percentage=models.DecimalField(max_digits=3,decimal_places=0,null=True,blank=True)CONDITIONAL_REQUIRED_TOGGLE_FIELDS=[(lambdainstance:instance.user.is_active,['fixed_price','percentage','amount'],),]pythonmanage.pyshell
...
>>>fromdecimalimportDecimal
>>>fromdjango.contrib.authimportget_user_model
>>>fromdemo.modelsimportTestModel
>>>user=get_user_model().objects.create(username='test',is_active=True)>>>first_obj=TestModel.objects.create(user=user)...
ValueError:{'__all__':ValidationError([u'Please provide a valid value for any of the following fields: Fixed price, Percentage, Amount'])}...
>>>first_obj=TestModel.objects.create(user=user,amount=Decimal('2'),fixed_price=Decimal('2'))...
ValueError:{'__all__':ValidationError([u'Please provide only one of the following fields: Fixed price, Percentage, Amount'])}...Model AttributesThis is done using model attributes below.#  Using a list/iterable: [['a', 'b'], ['c', 'd']] which validates that a field from each item is provided.REQUIRED_TOGGLE_FIELDS=[]# Using a list/iterable validates that all fields are provided.REQUIRED_FIELDS=[]# Optional toggle fields list: [['a', 'b']] which runs the validation only when any of the fields are present.OPTIONAL_TOGGLE_FIELDS=[]# Conditional field validation using a list of tuples the condition which could be boolean or a callable and the list/iterable of fields that are required if the condition evaluates to `True`.# [(condition, [fields]), (condition, fields)]# Using a callable CONDITIONAL_REQUIRED_FIELDS = [(lambda instance: instance.is_admin, ['a', 'd'])]# Using a boolean CONDITIONAL_REQUIRED_TOGGLE_FIELDS = [(True, ['b', 'c']), (True, ['d', f])]# asserts that either 'b' or 'c' is provided and either 'd' or 'f'.# (Note: This can also be handled using REQUIRED_FIELDS/REQUIRED_TOGGLE_FIELDS)# Validates that all fields are present if the condition is TrueCONDITIONAL_REQUIRED_FIELDS=[]# Validated at least one not both fields are provided if the condition is True.CONDITIONAL_REQUIRED_TOGGLE_FIELDS=[]Licensedjango-dynamic-model-validation is distributed under the terms of bothMIT LicenseApache License, Version 2.0at your option.TODO'sMove to support class and function based validators that use the instance object this should enable cross field model validation.