import logging import re from ipaddress import ip_address import phonenumbers from django.core import validators from django.core.exceptions import ValidationError from django.utils.deconstruct import deconstructible from django.utils.translation import gettext_lazy as _ from ..utils.python import import_class logger = logging.getLogger(__name__) def all_valid(*args): """ helper function to merge multiple validators at once """ if len(args) == 1: # Dict errors = {} kwargs = args[0] for field, validator in kwargs.items(): try: validator[0](*validator[1:]) except ValidationError as error: errors[field] = error else: # List errors = [] value, validators = args for validator in validators: try: validator(value) except ValidationError as error: errors.append(error) if errors: raise ValidationError(errors) @deconstructible class OrValidator(object): """ Run validators with an OR logic """ def __init__(self, *validators): self.validators = validators def __call__(self, value): msg = [] for validator in self.validators: try: validator(value) except ValidationError as err: msg.append(str(err)) else: return raise ValidationError(' OR '.join(msg)) def validate_ipv4_address(value): msg = _("Not a valid IPv4 address") try: ip = ip_address(value) except ValueError: raise ValidationError(msg) if ip.version != 4: raise ValidationError(msg) def validate_ipv6_address(value): msg = _("Not a valid IPv6 address") try: ip = ip_address(value) except ValueError: raise ValidationError(msg) if ip.version != 6: raise ValidationError(msg) def validate_ip_address(value): msg = _("Not a valid IP address") try: ip_address(value) except ValueError: raise ValidationError(msg) def validate_name(value): """ A single non-empty line of free-form text with no whitespace. """ validators.RegexValidator('^[\.\_\-0-9a-z]+$', _("Enter a valid name (spaceless lowercase text including _.-)."), 'invalid')(value) def validate_ascii(value): try: value.encode('ascii') except UnicodeEncodeError: raise ValidationError('This is not an ASCII string.') def validate_hostname(hostname): """ Ensures that each segment * contains at least one character and a maximum of 63 characters * consists only of allowed characters * doesn't begin or end with a hyphen. http://stackoverflow.com/a/2532344 """ if len(hostname) > 255: raise ValidationError(_("Too long for a hostname.")) hostname = hostname.rstrip('.') allowed = re.compile('(?!-)[A-Z\d-]{1,63}(? 64: raise ValidationError(_("Too long for a username.")) if len(value) < 3: raise ValidationError(_("Too short for a username.")) validators.RegexValidator(r'^[a-zA-Z\d.-]+$', _(f"Enter a valid username ({value})."))(value) def validate_username(value): validators.RegexValidator(r'^[\w.-]+$', _("Enter a valid username."))(value) def validate_password(value): try: import crack except: try: import cracklib as crack except: logger.error("Can not validate password. Cracklib bindings are not installed.") return try: crack.VeryFascistCheck(value) except ValueError as message: raise ValidationError("Password %s." % str(message)[3:]) def validate_url_path(value): if not re.match(r'^\/[/.a-zA-Z0-9-_]*$', value): raise ValidationError(_('"%s" is not a valid URL-path.') % value) def validate_vat(vat, country): field = 'localflavor.{lower}.forms.{upper}IdentityCardNumberField'.format( lower=country.lower(), upper=country.upper() ) try: field = import_class(field) except (ImportError, AttributeError, ValueError): pass else: field().clean(vat) def validate_zipcode(zipcode, country): field = 'localflavor.{lower}.forms.{upper}PostalCodeField'.format( lower=country.lower(), upper=country.upper() ) try: field = import_class(field) except (ImportError, AttributeError, ValueError): pass else: field().clean(zipcode) def validate_phone(value, country): """ local phone number or international """ msg = _("Not a valid %s nor international phone number.") % country try: number = phonenumbers.parse(value, country) except phonenumbers.phonenumberutil.NumberParseException: raise ValidationError(msg) if not phonenumbers.is_valid_number(number): raise ValidationError(msg) def validate_string_dir(value): """ A single non-empty line of free-form text with no whitespace. """ validators.RegexValidator('^[\_\-0-9a-z]+$', _("Enter a valid name dir (spaceless lowercase text, number and _- )"), 'invalid')(value)