[Fixed]-Is it possible to have validators with parameters?

20đź‘Ť

âś…

You could try writing a function which returns a function.

def date_range_validator(min_date, max_date):
    def innerfn(date_to_test):
        if not (min_date <= date_to_test <= max_date):
            raise ValidationError(
                'Inappropriate date: %s is not between %s and %s' %
                (date_to_test, min_date, max_date)
                )
    return innerfn

You could then create the validator you actually want by calling this function:

class DateForm(forms.Form):
    forms.DateField(
        validators=[
            date_range_validator(
                datetime.date(2012, 06, 01), datetime.date(2013, 03, 31)
                )
            ]
        )

(thanks for the correction @user2577922)

PS I’ve not tested this, but hopefully you get the idea – write a function which takes the
two dates you want to have as the bounds of your range, and which returns a function which
checks that a date it is passed is in the range.

👤Duncan Parkes

17đź‘Ť

I would suggest to take a look at django.core.validators to see how Django uses the Regex validator, and extends it to use for different type of fields.

class MyValidator (object):
    def __init__(self, params):
        pass  #  Init the instance variables here

    def __call__(self, value):
        pass  # Actual validation logic comes here

Simply pass your parameters to the validator in

validators=[MyValidator(params)]

Haven’t tested but I don’t see any reason why it wouldn’t work.

EDIT:

Had a chance to test it and it works.

👤amertkara

4đź‘Ť

Based on amertkara’s solution, I pass user’s email address to the form, then I validate the user input agaist his own email address.

# form
class new_user_form(forms.Form):
    def __init__(self, *args, **kwargs):
        own_mail = kwargs.pop('own_mail')
        super(new_user_form, self).__init__(*args, **kwargs)
        self.fields['email'] = forms.EmailField(
            label='',
            required=True,
            validators = [ not_own_mail(own_mail) ]
        )

# validator
class not_own_mail(object):
    def __init__(self, email):
        self.email = email

    def __call__(self, value):
       if value == self.email:
            raise ValidationError('It must be a different email address.')
       else:
           return value
👤s.benve

3đź‘Ť

You can build a validator, taking as example the builtin validators, dont forget use de deconstructible decorator on the class.

from django.utils.deconstruct import deconstructible

@deconstructible
class ValidateStartsWith(object):
    def __init__(self, prefix: str):
        self.prefix = prefix

    def __call__(self, value: str):
        value = value.strip()
        if value.startswith(self.prefix):
            if value == self.prefix:
                raise ValidationError(
                    f'El nombre debe incluir el prefijo "{self.prefix}" y algo más'
                )
        else:
            raise ValidationError(
                f'La convenciĂłn del nombre exige que empieze con el prefijo "{self.prefix}"'
            )

2đź‘Ť

You can try using a lambda expression (Never tried it myself, so I don’t know if it will work or not):

forms.DateField(validators=[lambda value: validate_range_of_data(value, "01/06/2012", "31/03/2012")])

Of course, you have to ask yourself if that’s “nicer” than just validating in the field’s clean method.

👤Chris Pratt

1đź‘Ť

in the new Django 4.2, this is the recommended way to do it:

in models.py or validators.py which is imported to models:

from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _


class max_length_validator(object):
    def __init__(self, length): # can add any params here
        if length is not None:
            self.length = length
    
    def __call__(self, value) -> Any:
        if len(str(value)) > self.length:
            raise ValidationError(
                _("%(value)s length is too long."),
                params={"value": value},
            )

and in the field itself:

field = models.IntegerField(validators=[max_length_validator(length=4)])

this is exactly how the django developer team coded the regex validator for example 🙂

👤Oak__

Leave a comment