Djangoのmodels.Field.validatorsに引数を渡す

django.db.models.Field.validatorsの関数に引数を与えられるようにします。
例えば以下のようなバリデータにおいて、ハードコードされた数値 100 をモデルフィールド単位に指定できるようにします。

def equal_to_100(value):
    if value != 100:
        raise ValidationError(f'{value} is not equal to 100.')
    
class MyModel(models.Model):
    foo = models.IntegerField(validators=[equal_to_100, ])

実行環境

カスタムバリデータクラスの実装

RegexValidatorのようなクラスベースのカスタムバリデータを作成します。
__init__ で任意のバリデータとキーワード引数を受け付け、 __call__ でバリデーションします。
migrationフレームワークシリアライズできるように、 @deconstructible の付与と __eq__ の実装が必要です(詳細はこちら)。

from django.utils.deconstruct import deconstructible

@deconstructible
class BaseValidator:
    def __init__(self, validator, **kwargs):
        self.validator = validator
        self.kwargs = kwargs

    def __call__(self, value):
        self.validator(value, **self.kwargs)

    def __eq__(self, other):
        return (
            isinstance(other, self.__class__) and
            self.validator == other.validator and
            self.kwargs == other.kwargs
        )

バリデータに引数を追加

これでバリデータの指定を validators=[BaseValidator(your_validator, param1=..., param2=...)] といった具合に変更することで、既存の関数に任意のキーワード引数を与えられるようになります。

from django.core.exceptions import ValidationError

def equal_to(value, expected=None):
    if value != expected:
        raise ValidationError(f'{value} is not equal to {expected}.')

class MyModel(models.Model):
    foo = models.IntegerField(validators=[BaseValidator(equal_to, expected=100)])