def get_page_number(self): """ 获得页码 :return: """ page_number = self.request_handler.get_query_argument(self.page_query_param, 1) num_pages = self.paginator.num_pages if page_number in self.first_page_strings: page_number = 1 if page_number in self.last_page_strings: page_number = num_pages try: page_number = int(page_number) except (TypeError, ValueError): raise PaginationError(detail=_("The page number must be a number or page string")) if page_number < 1: if self.allow_first_page: page_number = 1 else: raise PaginationError(detail=_("The page number must be greater than 1")) if page_number > num_pages: if self.allow_last_page: page_number = num_pages if not self.allow_empty_page: raise PaginationError(detail=_("The page number exceeds the total page number")) return page_number
class ListField(Field): """ 列表字段 """ child = _UnvalidatedField() initial = [] default_error_messages = { 'not_a_list': _('Expected a list of items but got type "%(input_type)s"'), 'empty': _('This list may not be empty'), 'min_length': _('Ensure this field has at least {min_length} elements'), 'max_length': _('Ensure this field has no more than {max_length} elements') } def __init__(self, child=None, *args, **kwargs): self.child = copy.deepcopy(self.child) if child is None else child assert not inspect.isclass( self.child), '`child` has not been instantiated.' self.allow_empty = kwargs.pop('allow_empty', True) self.max_length = kwargs.pop('max_length', None) self.min_length = kwargs.pop('min_length', None) self.child.source = None super(ListField, self).__init__(*args, **kwargs) self.child.bind(field_name='', parent=self) if self.max_length is not None: message = self.error_messages['max_length'].format( max_length=self.max_length) self.validators.append( validators.MaxLengthValidator(self.max_length, message=message)) if self.min_length is not None: message = self.error_messages['min_length'].format( min_length=self.min_length) self.validators.append( validators.MinLengthValidator(self.min_length, message=message)) def validate(self, value): pass async def clean(self, value): if value is empty: value = [] elif isinstance(value, type('')) or isinstance(value, collections.Mapping)\ or not hasattr(value, '__iter__'): raise ValidationError(self.error_messages['not_a_list'], code='not_a_list', params=dict(input_type=type(value).__name__)) if not self.allow_empty and len(value) == 0: raise ValidationError(self.error_messages['empty'], code='empty') return [await self.child.clean(item) for item in value]
class DecimalValidator(object): """ Validate that the input does not exceed the maximum number of digits expected, otherwise raise ValidationError. """ messages = { 'max_digits': _('Ensure that there are no more than %(max)s digit in total'), 'max_decimal_places': _('Ensure that there are no more than %(max)s decimal place'), 'max_whole_digits': _('Ensure that there are no more than %(max)s digit before the decimal point' ), } def __init__(self, max_digits, decimal_places): self.max_digits = max_digits self.decimal_places = decimal_places def __call__(self, value): digit_tuple, exponent = value.as_tuple()[1:] decimals = abs(exponent) # digit_tuple doesn't include any leading zeros. digits = len(digit_tuple) if decimals > digits: # We have leading zeros up to or past the decimal point. Count # everything past the decimal point as a digit. We do not count # 0 before the decimal point as a digit since that would mean # we would not allow max_digits = decimal_places. digits = decimals whole_digits = digits - decimals if self.max_digits is not None and digits > self.max_digits: raise ValidationError( self.messages['max_digits'], code='max_digits', params={'max': self.max_digits}, ) if self.decimal_places is not None and decimals > self.decimal_places: raise ValidationError( self.messages['max_decimal_places'], code='max_decimal_places', params={'max': self.decimal_places}, ) if (self.max_digits is not None and self.decimal_places is not None and whole_digits > (self.max_digits - self.decimal_places)): raise ValidationError( self.messages['max_whole_digits'], code='max_whole_digits', params={'max': (self.max_digits - self.decimal_places)}, ) def __eq__(self, other): return (isinstance(other, self.__class__) and self.max_digits == other.max_digits and self.decimal_places == other.decimal_places)
class FormModelField(Field): default_error_messages = { 'type_error': _('Expected a list or dictionary of items but got type "%(input_type)s"' ), 'empty': _('This value not be empty') } def __init__(self, form, many=True, allow_empty=False, *args, **kwargs): self.form_class = formset_factory(form) self.many = many self.allow_empty = allow_empty super(FormModelField, self).__init__(*args, **kwargs) def validate(self, value): pass async def clean(self, value): if self.required and value is empty: raise ValidationError(self.error_messages['required'], code='required') if value is empty: value = [] if self.many else {} elif not isinstance(value, (dict, list)): raise ValidationError(self.error_messages['type_error'], code='type_error', params=dict(input_type=type(value).__name__)) form_data_size = len(value) if self.allow_empty and form_data_size == 0: return [] if self.many else {} # elif form_data_size == 0: # raise ValidationError(self.error_messages['empty'], code='empty') if isinstance(value, dict): value = [value] form_cls = self.form_class(data=value) is_valid = await form_cls.is_valid() if is_valid: result = await form_cls.cleaned_data return result if self.many else result[0] errors = await form_cls.errors raise ValidationError(errors)
def pre_handle_exception(self, exc): """ 预处理各种异常返回结构 返回Result对象 :param exc: :return:Result """ if isinstance(exc, (exceptions.APIException, exceptions.ValidationError)): error_response = self.write_response(data={ settings.NON_FIELD_ERRORS: { "message": exc.detail, "code": exc.code } }, status_code=exc.status_code) elif isinstance(exc, HTTPError): status_code = exc.status_code http_code_detail = status.HTTP_CODES.get(status_code, None) error_response = self.write_response(data={ settings.NON_FIELD_ERRORS: { "message": http_code_detail.description if http_code_detail else _("Internal Server Error"), "code": "Error" } }, status_code=exc.status_code) elif isinstance(exc, IntegrityError): error_detail = ErrorDetail( _('Insert failed, the reason may be foreign key constraints'), code="ForeignError") error_response = self.write_response( data={settings.NON_FIELD_ERRORS: error_detail}, status_code=status.HTTP_400_BAD_REQUEST) else: error_response = self.write_response( data={ settings.NON_FIELD_ERRORS: { "message": _("Internal Server Error"), "code": "Error" } }, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) return error_response
class BooleanField(Field): default_error_messages = { 'invalid': _('"%s" is not a valid boolean') } initial = False TRUE_VALUES = { 't', 'T', 'y', 'Y', 'yes', 'YES', 'true', 'True', 'TRUE', 'on', 'On', 'ON', '1', 1, True } FALSE_VALUES = { 'f', 'F', 'n', 'N', 'no', 'NO', 'false', 'False', 'FALSE', 'off', 'Off', 'OFF', '0', 0, 0.0, False } def to_python(self, value): """Returns a Python boolean object.""" if value in self.TRUE_VALUES: return True elif value in self.FALSE_VALUES: return False else: raise ValidationError(self.error_messages['invalid'], code='invalid', params=value)
class DecimalField(IntegerField): default_error_messages = { 'invalid': _('Enter a number'), } def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): self.max_digits, self.decimal_places = max_digits, decimal_places super(DecimalField, self).__init__(max_value, min_value, *args, **kwargs) self.validators.append(validators.DecimalValidator(max_digits, decimal_places)) def to_python(self, value): """ Validates that the input is a decimal number. Returns a Decimal instance. Returns None for empty values. Ensures that there are no more than max_digits in the number, and no more than decimal_places digits after the decimal point. """ if value in self.empty_values: return None value = force_text(value).strip() try: value = Decimal(value) except DecimalException: raise ValidationError(self.error_messages['invalid'], code='invalid') return value def validate(self, value): super(DecimalField, self).validate(value) if value in self.empty_values: return # Check for NaN, Inf and -Inf values. We can't compare directly for NaN, # since it is never equal to itself. However, NaN is the only value that # isn't equal to itself, so we can use this to identify NaN if value != value or value == Decimal("Inf") or value == Decimal("-Inf"): raise ValidationError(self.error_messages['invalid'], code='invalid')
class CharField(Field): """ 字符串类型 """ default_error_messages = { 'invalid': _('Not a valid string'), } initial = '' def __init__(self, max_length=None, min_length=None, strip=True, empty_value='', *args, **kwargs): self.max_length = max_length self.min_length = min_length self.strip = strip self.empty_value = empty_value super(CharField, self).__init__(*args, **kwargs) if min_length is not None: self.validators.append(validators.MinLengthValidator(int(min_length))) if max_length is not None: self.validators.append(validators.MaxLengthValidator(int(max_length))) def to_python(self, value): """ Returns a Unicode object :param value: :return: """ if value in self.empty_values: return self.empty_value value = force_text(value) if self.strip: value = value.strip() return value
async def get_object(self): """ 查询单一对象,如果为空抛出404 """ try: queryset = await self.filter_queryset(self.get_queryset()) except SkipFilterError: error_msg = self.error_msg_404 if self.error_msg_404 else {} raise exceptions.APIException( status_code=404, detail=error_msg.get("message", _("Resource data does not exist")), code=error_msg.get("code", "ResourceNotExist")) queryset = queryset.naive() lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field path_kwargs = self.path_kwargs or self.request_data assert lookup_url_kwarg in path_kwargs, ( 'Expected view %s to be called with a URL keyword argument ' 'named "%s". Fix your URL conf, or set the `.lookup_field` ' 'attribute on the view correctly.' % (self.__class__.__name__, lookup_url_kwarg)) filter_kwargs = {self.lookup_field: path_kwargs[lookup_url_kwarg]} obj = await self.get_object_or_404(queryset, **filter_kwargs) return obj
class DictField(Field): """ 字典字段 """ child = _UnvalidatedField() initial = {} default_error_messages = { 'not_a_dict': _('Expected a dictionary of items but got type "%(input_type)s"') } def __init__(self, *args, **kwargs): self.child = kwargs.pop('child', copy.deepcopy(self.child)) assert not inspect.isclass( self.child), '`child` has not been instantiated.' self.child.source = None super(DictField, self).__init__(*args, **kwargs) self.child.bind(field_name='', parent=self) async def clean(self, data): if not isinstance(data, dict): raise ValidationError(self.error_messages['not_a_dict'], code='not_a_dict', params=dict(input_type=type(data).__name__)) return { str(key): await self.child.clean(value) for key, value in data.items() }
class MinValueValidator(BaseValidator): message = _( 'Ensure this value is greater than or equal to %(limit_value)s') code = 'min_value' def compare(self, a, b): return a < b
class PaginationError(APIException): """ 分页异常 """ status_code = status.HTTP_400_BAD_REQUEST default_detail = _('Invalid page') default_code = 'page_error'
class FileExtensionValidator(object): message = _("File extension '%(extension)s' is not allowed. " "Allowed extensions are: '%(allowed_extensions)s'.") code = 'invalid_extension' def __init__(self, allowed_extensions=None, message=None, code=None): self.allowed_extensions = allowed_extensions if message is not None: self.message = message if code is not None: self.code = code def __call__(self, value): extension = os.path.splitext(value.name)[1][1:].lower() if self.allowed_extensions is not None and extension not in self.allowed_extensions: raise ValidationError(self.message, code=self.code, params={ 'extension': extension, 'allowed_extensions': ', '.join(self.allowed_extensions) }) def __eq__(self, other): return (isinstance(other, self.__class__) and self.allowed_extensions == other.allowed_extensions and self.message == other.message and self.code == other.code)
class BaseValidator(object): message = _('Ensure this value is %(limit_value)s (it is %(show_value)s)') code = 'limit_value' def __init__(self, limit_value, message=None): self.limit_value = limit_value if message: self.message = message def __call__(self, value): cleaned = self.clean(value) params = { 'limit_value': self.limit_value, 'show_value': cleaned, 'value': value } if self.compare(cleaned, self.limit_value): raise ValidationError(self.message, code=self.code, params=params) def __eq__(self, other): return (isinstance(other, self.__class__) and self.limit_value == other.limit_value and self.message == other.message and self.code == other.code) def compare(self, a, b): return a is not b def clean(self, x): return x
class IntegerField(Field): default_error_messages = { 'invalid': _('Enter a whole number'), } re_decimal = re.compile(r'\.0*\s*$') def __init__(self, max_value=None, min_value=None, *args, **kwargs): self.max_value, self.min_value = max_value, min_value super(IntegerField, self).__init__(*args, **kwargs) if max_value is not None: self.validators.append(validators.MaxValueValidator(max_value)) if min_value is not None: self.validators.append(validators.MinValueValidator(min_value)) def to_python(self, value): """ Validates that int() can be called on the input. Returns the result of int(). Returns None for empty values. """ value = super(IntegerField, self).to_python(value) if value in self.empty_values: return None try: value = int(self.re_decimal.sub('', force_text(value))) except (ValueError, TypeError): raise ValidationError(self.error_messages['invalid'], code='invalid') return value
class FloatField(IntegerField): default_error_messages = { 'invalid': _('Enter a number'), } def to_python(self, value): """ Validates that float() can be called on the input. Returns the result of float(). Returns None for empty values. """ value = super(IntegerField, self).to_python(value) if value in self.empty_values: return None try: value = float(value) except (ValueError, TypeError): raise ValidationError(self.error_messages['invalid'], code='invalid') return value def validate(self, value): super(FloatField, self).validate(value) # Check for NaN (which is the only thing not equal to itself) and +/- infinity if value != value or value in (Decimal('Inf'), Decimal('-Inf')): raise ValidationError(self.error_messages['invalid'], code='invalid') return value
class ParseError(APIException): """ 解析异常 """ status_code = status.HTTP_400_BAD_REQUEST default_detail = _('Malformed request') default_code = 'parse_error'
class APIException(Exception): status_code = status.HTTP_500_INTERNAL_SERVER_ERROR default_detail = _('A server error occurred') default_code = 'error' def __init__(self, detail=None, code=None, status_code=None): if detail is None: detail = self.default_detail if code is None: self.code = self.default_code if status_code is not None: self.status_code = status_code self.detail = _get_error_details(detail, code) def __str__(self): return self.detail def get_codes(self): """ Return only the code part of the error details. Eg. {"name": ["required"]} """ return _get_codes(self.detail) def get_full_details(self): """ Return both the message & code parts of the error details. Eg. {"name": [{"message": "This field is required.", "code": "required"}]} """ return get_full_details(self.detail)
async def prepare(self): method = self.request.method.lower() content_type = self.request.headers.get("Content-Type", "").lower() self.request_data = self._parse_query_arguments() if not content_type or method == b"get": if self.path_kwargs: self.request_data.update(self.path_kwargs) self.request.data = self.request_data return parser = self.__select_parser(content_type) if not parser: error_detail = _( 'Unsupported media type `%s` in request') % content_type raise APIException( detail=error_detail, code="MediaTypeError", status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE) parse_data = await parser.parse(self.request) if parse_data: if isinstance(parse_data, dict): self.request_data.update(parse_data) else: self.request_data = parse_data self.request.data = self.request_data
class PasswordValidator(object): """ 密码是否合法 """ message = { "number": _("Enter a valid 6-digit password"), "normal": _("Enter a valid 6-18-digit alphanumeric password"), "high": _("Enter a 6-18 bit must contain any combination of " "upper and lower case letters, numbers, symbols password") } code = 'invalid' def __init__(self, level="number", message=None, code=None, regex=None): self.level = level if message is not None: self.message = message if code is not None: self.code = code self.password_regex = lazy_re_compile(regex, flags=re.IGNORECASE) \ if regex is not None else None if self.level == "number": self.password_regex = lazy_re_compile(r"^\d{6}$", flags=re.IGNORECASE) elif self.level == "normal": self.password_regex = lazy_re_compile( r"^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,18}$", flags=re.IGNORECASE) elif self.level == "high": re_str = r"^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z\u4e00-\u9fa5\s)])+$)" \ r"([^(0-9a-zA-Z\u4e00-\u9fa5\s)]|[a-z]|[A-Z]|[0-9]){6,18}$" self.password_regex = lazy_re_compile(re_str, flags=re.IGNORECASE) def __call__(self, value): if self.level == "any" or self.password_regex is None: return valid = self.password_regex.match(value) if not valid: raise ValidationError(self.message[self.level], code=self.code)
class UniqueValidator(object): """ model模型字段设置了unique=True,进行唯一索引检查 """ message = _("This field (`%(field_name)s`) value must be unique") def __init__(self, queryset, message=None, lookup='exact'): self.queryset = queryset self.serializer_field = None self.message = message or self.message self.lookup = lookup def set_context(self, serializer_field): """ This hook is called by the serializer instance, prior to the validation call being made. """ # Determine the underlying model field name. This may not be the # same as the serializer field name if `source=<>` is set. self.field_name = serializer_field.source_attrs[-1] # Determine the existing instance, if this is an update operation. self.instance = getattr(serializer_field.parent, 'instance', None) def filter_queryset(self, value, queryset): """ Filter the queryset to all instances matching the given attribute. """ filter_kwargs = {'%s__%s' % (self.field_name, self.lookup): value} return qs_filter(queryset, **filter_kwargs) def exclude_current_instance(self, queryset): """ If an instance is being updated, then do not include that instance itself as a uniqueness conflict. """ if self.instance is not None: pk_field = self.instance._meta.primary_key return queryset.exclude(**{pk_field.name: getattr(self.instance, pk_field.name)}) return queryset @asyncio.coroutine def __call__(self, value): queryset = self.queryset queryset = self.filter_queryset(value, queryset) queryset = self.exclude_current_instance(queryset) if (yield from qs_exists(queryset)): raise ValidationError( self.message, code='unique', params={"field_name": self.field_name} ) def __repr__(self): return '<%s(queryset=%s)>' % ( self.__class__.__name__, str(self.queryset), )
class MaxLengthValidator(BaseValidator): message = _( 'Ensure this value has at most %(limit_value)d character (it has %(show_value)d)' ) code = 'max_length' def compare(self, a, b): return a > b def clean(self, x): return len(x)
async def full_clean(self): """ Cleans all of self.data and populates self._errors and self._non_form_errors. """ self._errors = {} if not self.is_bound: return if not isinstance(self.data, list): raise ValidationError( detail=_("The form data format must be a list structure, not a %s structure."), code='FormDataFormatError', params=type(self.data).__name__ ) for i in range(0, self.total_form_count): form = self.forms[i] form_error = await form.part_errors if form_error: for k, v in form_error.items(): self._errors["%s-%d" % (k, i+1)] = v try: if self.max_num is not None and self.total_form_count > self.max_num: raise ValidationError( detail=_("Please submit %d or fewer forms"), code='too_many_forms', params=self.max_num ) if self.min_num is not None and self.total_form_count < self.min_num: raise ValidationError( detail=_("Please submit %d or more forms"), code='too_few_forms', params=self.min_num ) self.clean() except ValidationError as e: self._errors[settings.NON_FIELD_ERRORS] = e.detail
class MultipleChoiceField(ChoiceField): default_error_messages = { 'invalid_choice': _('"%(input)s" is not a valid choice'), 'invalid_list': _('Enter a list of values'), } def to_python(self, value): if not value: return [] elif not isinstance(value, (list, tuple)): raise ValidationError(self.error_messages['invalid_list'], code='invalid_list') return [force_text(val) for val in value] def validate(self, value): """ Validates that the input is a list or tuple. """ if self.required and not value: raise ValidationError(self.error_messages['required'], code='required') # Validate that each value in the value list is in self.choices. for val in value: if not self.valid_value(val): raise ValidationError( self.error_messages['invalid_choice'], code='invalid_choice', params={'input': val}, ) def has_changed(self, initial, data): if initial is None: initial = [] if data is None: data = [] if len(initial) != len(data): return True initial_set = set(force_text(value) for value in initial) data_set = set(force_text(value) for value in data) return data_set != initial_set
class IdentifierValidator(object): """ 手机号码或邮箱地址是否合法 """ message = { "both": _("Enter a valid phone number or email address"), "phone": _("Enter a valid phone number"), "email": _("Enter a valid email address") } code = 'invalid' def __init__(self, protocol="both", message=None, code=None): self.protocol = protocol # assert isinstance(message, (type(None), dict)), "message值必须为字典类型" if message is not None: self.message = message if code is not None: self.code = code def __call__(self, value): if not value: raise ValidationError(self.message[self.protocol], code=self.code) if self.protocol == "both": if "@" in value: validate_email(value) elif value.isdigit(): validate_phone(value) else: raise ValidationError(self.message[self.protocol], code=self.code) elif self.protocol == "email": validate_email(value) elif self.protocol == "phone": validate_phone(value)
class ImageField(FileField): default_error_messages = { 'invalid_image': _("Upload a valid image. The file you uploaded was either not an " "image or a corrupted image"), } def to_python(self, data): """ Checks that the file-upload field data contains a valid image (GIF, JPG, PNG, possibly others -- whatever the Python Imaging Library supports). """ f = super(ImageField, self).to_python(data) if f is None: return None from PIL import Image # We need to get a file object for Pillow. We might have a path or we might # have to read the data into memory. if hasattr(data, 'temporary_file_path'): file = data.temporary_file_path() else: if hasattr(data, 'read'): file = BytesIO(data.read()) else: file = BytesIO(data['content']) try: # load() could spot a truncated JPEG, but it loads the entire # image in memory, which is a DoS vector. See #3848 and #18520. image = Image.open(file) # verify() must be called immediately after the constructor. image.verify() # Annotating so subclasses can reuse it for their own validation f.image = image # Pillow doesn't detect the MIME type of all formats. In those # cases, content_type will be None. f.content_type = Image.MIME.get(image.format) except Exception: raise ValidationError(self.error_messages['invalid_image'], code='invalid_image') if hasattr(f, 'seek') and callable(f.seek): f.seek(0) return f
class RegexValidator(object): regex = '' message = _('Enter a valid value') code = 'invalid' inverse_match = False flags = 0 def __init__(self, regex=None, message=None, code=None, inverse_match=None, flags=None): if regex is not None: self.regex = regex if message is not None: self.message = message if code is not None: self.code = code if inverse_match is not None: self.inverse_match = inverse_match if flags is not None: self.flags = flags if self.flags and not isinstance(self.regex, str): raise TypeError( "If the flags are set, regex must be a regular expression string." ) self.regex = lazy_re_compile(self.regex, self.flags) def __call__(self, value): """ Validates that the input matches the regular expression if inverse_match is False, otherwise raises ValidationError. """ if not (self.inverse_match is not bool( self.regex.search(force_text(value)))): raise ValidationError(self.message, code=self.code) def __eq__(self, other): return (isinstance(other, RegexValidator) and self.regex.pattern == other.regex.pattern and self.regex.flags == other.regex.flags and (self.message == other.message) and (self.code == other.code) and (self.inverse_match == other.inverse_match))
class TimeField(BaseTemporalField): input_formats = get_format_lazy('TIME_INPUT_FORMATS') default_error_messages = {'invalid': _('Enter a valid time')} def to_python(self, value): """ Validates that the input can be converted to a time. Returns a Python datetime.time object. """ if value in self.empty_values: return None if isinstance(value, datetime.time): return value return super(TimeField, self).to_python(value) def strptime(self, value, format): return datetime.datetime.strptime(force_text(value), format).time()
class ValidationError(APIException): status_code = status.HTTP_400_BAD_REQUEST default_detail = _('Invalid input.') default_code = 'invalid' def __init__(self, detail=None, code=None, params=None, field=None): if detail is None: detail = self.default_detail self.code = self.default_code if code is None else code self.field = field if params is not None: detail %= params self.detail = _get_error_details(detail, self.code) def __str__(self): return str(self.detail)
class PasswordField(CharField): """ 密码字段类型 """ default_error_messages = { 'invalid': _('Please enter a valid password') } def __init__(self, protection='default', level="high", *args, **kwargs): """ :param protection: 密码加密方式 default 默认,取settings PASSWORD_HASHERS的第1个 pbkdf2_sha256 pbkdf2_sha1 argon2 bcrypt_sha256 bcrypt :param level: 密码加密级别 any 任何版本,不限制 number 数字版本,6位数字密码 normal 普通版本,6-18位英文数字混合密码 high 增强版本,6-18位必须包含大小写字母/数字/符号任意两者组合密码 :param args: :param kwargs: """ if protection != "default": assert protection in hashers.get_hashers_by_algorithm().keys(), "protection不正确" assert level in ('any', "number", "normal", "high"), "level不正确" self.protection = protection.lower() self.level = level.lower() super(PasswordField, self).__init__(*args, **kwargs) self.validators.append(validators.PasswordValidator(self.level)) async def clean(self, value): """ Validates the given value and returns its "cleaned" value as an appropriate Python object. Raises ValidationError for any errors. """ value = self.to_python(value) self.validate(value) await self.run_validators(value) return hashers.make_password(password=value, hasher=self.protection)