tklodd
tklodd

Reputation: 1080

How to OR Django model field validators?

Does anyone know how to OR together Django model field validators?

Something like this:

example_field = models.CharField(max_length=255, validators=[validator1|validator2])

I am guessing that there is a way and it involves the Q operator, but I can't find what it is exactly.

Upvotes: 1

Views: 127

Answers (2)

robin
robin

Reputation: 546

I needed an OR-validator as well, so I made a little reusable validator that can accept any number of sub-validators that are OR-ed together. Inspired by Django's own validators, so should work everywhere (only tested in Django Rest Framework serializer)

from django.core.exceptions import ValidationError
from django.core.validators import EmailValidator, RegexValidator
from django.utils.deconstruct import deconstructible
from rest_framework import serializers

@deconstructible  # allows usage in migrations
class OrValidator:
    message = 'Enter a valid value.'  # customize this based on the sub-validators
    code = 'invalid'

    def __init__(self, validators, message=None, code=None):
        self.validators = validators
        self._errors = []
        if code is not None:
            self.code = code
        if message is not None:
            self.message = message

    def __call__(self, value):
        for validator in self.validators:
            try:
                return validator(value)
            except ValidationError as e:
                # save error for debugging
                self._errors.append(e)
        # non matched, raise error
        raise ValidationError(self.message, code=self.code)

    def __eq__(self, other):
        return (
            self.validators == other.validators and
            isinstance(other, self.__class__) and
            self.message == other.message and
            self.code == other.code
        )

class UsernameValidator(RegexValidator):
    regex = re.compile(r'^[-\w]+\Z', re.UNICODE)

# example usage in Django Rest Framework (should work in forms and models as well)

class ResetPasswordSerializer(serializers.Serializer):
    email_or_username = serializers.CharField(
        required=True,
        validators=[
            OrValidator(
                [EmailValidator(), UsernameValidator()],
                message='Enter a valid email or username',
            ),
        ],
    )

Upvotes: 0

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476528

You can do the validation in a function itself:

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

def combined_validator(value):
    try:
        return validator1(value)
    except ValidationError:
        return validator2(value)

class MyModel(models.Model):
    example_field = models.CharField(
        max_length=255,
        validators=[combined_validator]
    )

If validator1 does not detect any trouble, then the control flow is returned, and thus we are safe. If it raises a ValidationError, then we fallback on validator2. If that does not raises an error, then we are again safe. Otherwise, the error will raise out of combined_validator.

Upvotes: 3

Related Questions