ValueError: Не удалось найти валидатор функций при вызове makemigrations в Django 4.0

Используя решение здесь для проверки URLField, я получаю ValueError при запуске python manage.py makemigrations и не уверен почему. Что я делаю неправильно?

from django.contrib.auth.models import User
from django.db import models
from django.core.exceptions import ValidationError

from urllib.parse import urlparse


def validate_hostname(*hostnames):
    hostnames = set(hostnames)
    def validator(value):
        try:
            result = urlparse(value)
            if result.hostname not in hostnames:
                raise ValidationError(f'The hostname {result.hostname} is not allowed.')
        except ValueError:
            raise ValidationError('Invalid URL')
    return validator

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.SET_NULL, null=True)
    twitter = models.URLField(
        blank=True,
        null=True,
        validators=[validate_hostname('twitter.com', 'www.twitter.com')]
    )

Traceback

Вы делаете return validator, однако validator - это функция. Попробуйте return validator(hostnames), возможно, или любой другой аргумент, который вам нужно дать вашей функции.

Если вы посмотрите на вашу функцию validate_hostname, то у нее есть внутренняя функция validator, которую вы пытаетесь вернуть. отсюда и ошибка: ValueError: Could not find function validator in userprofiles.models.

Этот фрагмент кода должен работать:

from django.contrib.auth.models import User
from django.db import models
from django.core.exceptions import ValidationError



HOST_NAMES = ['twitter.com', 'www.twitter.com']
def validate_hostname(entered_hostname):
    if entered_hostname in HOST_NAMES:
        return entered_hostname
    else:
        raise ValidationError(f'The hostname {entered_hostname} is not allowed.')
   

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.SET_NULL, null=True)
    twitter = models.URLField(
        blank=True,
        null=True,
        validators=[validate_hostname]
    )

Вы можете изменить это, используя urlparse в validate_hostname функции

Обратите внимание, что вы также можете объявить валидаторы следующим образом:

from django.utils.deconstruct import deconstructible

@deconstructible
class HostnameValidator:
    def __init__(self, hostnames):
        self.hostnames = set(hostnames)

    def __call__(self, value):
        try:
            result = urlparse(value)
            if result.hostname not in self.hostnames:
                raise ValidationError(f'The hostname {result.hostname} is not allowed.')
        except ValueError:
            raise ValidationError('Invalid URL')

    def __eq__(self, other):
        return (
            isinstance(other, HostnameValidator) and
            self.hostnames == other.hostnames
        )

Таким образом можно "кривить" валидатор.

hostname = models.CharField(max_length=150, validators=[HostnameValidator(["example.org", "example.net"])])

Это можно исправить, добавив украшение functools.wraps:

from functools import wraps
[...]

def validate_hostname(*hostnames):
    [...]
    @wraps(validate_hostname)
    def validator(value):
        [...]
    return validator
Вернуться на верх