diff options
author | ttt | 2017-05-13 00:29:47 +0530 |
---|---|---|
committer | ttt | 2017-05-13 00:29:47 +0530 |
commit | abf599be33b383a6a5baf9493093b2126a622ac8 (patch) | |
tree | 4c5ab6e0d935d5e65fabcf0258e4a00dd20a5afa /lib/python2.7/site-packages/django/forms/fields.py | |
download | SBHS-2018-Rpi-abf599be33b383a6a5baf9493093b2126a622ac8.tar.gz SBHS-2018-Rpi-abf599be33b383a6a5baf9493093b2126a622ac8.tar.bz2 SBHS-2018-Rpi-abf599be33b383a6a5baf9493093b2126a622ac8.zip |
added all server files
Diffstat (limited to 'lib/python2.7/site-packages/django/forms/fields.py')
-rw-r--r-- | lib/python2.7/site-packages/django/forms/fields.py | 1146 |
1 files changed, 1146 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/django/forms/fields.py b/lib/python2.7/site-packages/django/forms/fields.py new file mode 100644 index 0000000..966b303 --- /dev/null +++ b/lib/python2.7/site-packages/django/forms/fields.py @@ -0,0 +1,1146 @@ +""" +Field classes. +""" + +from __future__ import absolute_import, unicode_literals + +import copy +import datetime +import os +import re +import sys +from decimal import Decimal, DecimalException +from io import BytesIO + +from django.core import validators +from django.core.exceptions import ValidationError +from django.forms.util import ErrorList, from_current_timezone, to_current_timezone +from django.forms.widgets import ( + TextInput, NumberInput, EmailInput, URLInput, HiddenInput, + MultipleHiddenInput, ClearableFileInput, CheckboxInput, Select, + NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput, + SplitDateTimeWidget, SplitHiddenDateTimeWidget, FILE_INPUT_CONTRADICTION +) +from django.utils import formats +from django.utils.encoding import smart_text, force_str, force_text +from django.utils.ipv6 import clean_ipv6_address +from django.utils import six +from django.utils.six.moves.urllib.parse import urlsplit, urlunsplit +from django.utils.translation import ugettext_lazy as _, ungettext_lazy + +# Provide this import for backwards compatibility. +from django.core.validators import EMPTY_VALUES + + +__all__ = ( + 'Field', 'CharField', 'IntegerField', + 'DateField', 'TimeField', 'DateTimeField', 'TimeField', + 'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', + 'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField', + 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', + 'SplitDateTimeField', 'IPAddressField', 'GenericIPAddressField', 'FilePathField', + 'SlugField', 'TypedChoiceField', 'TypedMultipleChoiceField' +) + + +class Field(object): + widget = TextInput # Default widget to use when rendering this type of Field. + hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". + default_validators = [] # Default set of validators + # Add an 'invalid' entry to default_error_message if you want a specific + # field error message not raised by the field validators. + default_error_messages = { + 'required': _('This field is required.'), + } + empty_values = list(validators.EMPTY_VALUES) + + # Tracks each time a Field instance is created. Used to retain order. + creation_counter = 0 + + def __init__(self, required=True, widget=None, label=None, initial=None, + help_text='', error_messages=None, show_hidden_initial=False, + validators=[], localize=False): + # required -- Boolean that specifies whether the field is required. + # True by default. + # widget -- A Widget class, or instance of a Widget class, that should + # be used for this Field when displaying it. Each Field has a + # default Widget that it'll use if you don't specify this. In + # most cases, the default widget is TextInput. + # label -- A verbose name for this field, for use in displaying this + # field in a form. By default, Django will use a "pretty" + # version of the form field name, if the Field is part of a + # Form. + # initial -- A value to use in this Field's initial display. This value + # is *not* used as a fallback if data isn't given. + # help_text -- An optional string to use as "help text" for this Field. + # error_messages -- An optional dictionary to override the default + # messages that the field will raise. + # show_hidden_initial -- Boolean that specifies if it is needed to render a + # hidden widget with initial value after widget. + # validators -- List of addtional validators to use + # localize -- Boolean that specifies if the field should be localized. + self.required, self.label, self.initial = required, label, initial + self.show_hidden_initial = show_hidden_initial + self.help_text = help_text + widget = widget or self.widget + if isinstance(widget, type): + widget = widget() + + # Trigger the localization machinery if needed. + self.localize = localize + if self.localize: + widget.is_localized = True + + # Let the widget know whether it should display as required. + widget.is_required = self.required + + # Hook into self.widget_attrs() for any Field-specific HTML attributes. + extra_attrs = self.widget_attrs(widget) + if extra_attrs: + widget.attrs.update(extra_attrs) + + self.widget = widget + + # Increase the creation counter, and save our local copy. + self.creation_counter = Field.creation_counter + Field.creation_counter += 1 + + messages = {} + for c in reversed(self.__class__.__mro__): + messages.update(getattr(c, 'default_error_messages', {})) + messages.update(error_messages or {}) + self.error_messages = messages + + self.validators = self.default_validators + validators + super(Field, self).__init__() + + def prepare_value(self, value): + return value + + def to_python(self, value): + return value + + def validate(self, value): + if value in self.empty_values and self.required: + raise ValidationError(self.error_messages['required'], code='required') + + def run_validators(self, value): + if value in self.empty_values: + return + errors = [] + for v in self.validators: + try: + v(value) + except ValidationError as e: + if hasattr(e, 'code') and e.code in self.error_messages: + e.message = self.error_messages[e.code] + errors.extend(e.error_list) + if errors: + raise ValidationError(errors) + + 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) + self.run_validators(value) + return value + + def bound_data(self, data, initial): + """ + Return the value that should be shown for this field on render of a + bound form, given the submitted POST data for the field and the initial + data, if any. + + For most fields, this will simply be data; FileFields need to handle it + a bit differently. + """ + return data + + def widget_attrs(self, widget): + """ + Given a Widget instance (*not* a Widget class), returns a dictionary of + any HTML attributes that should be added to the Widget, based on this + Field. + """ + return {} + + def _has_changed(self, initial, data): + """ + Return True if data differs from initial. + """ + # For purposes of seeing whether something has changed, None is + # the same as an empty string, if the data or inital value we get + # is None, replace it w/ ''. + initial_value = initial if initial is not None else '' + try: + data = self.to_python(data) + except ValidationError: + return True + data_value = data if data is not None else '' + return initial_value != data_value + + def __deepcopy__(self, memo): + result = copy.copy(self) + memo[id(self)] = result + result.widget = copy.deepcopy(self.widget, memo) + result.validators = self.validators[:] + return result + + +class CharField(Field): + def __init__(self, max_length=None, min_length=None, *args, **kwargs): + self.max_length, self.min_length = max_length, min_length + 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." + if value in self.empty_values: + return '' + return smart_text(value) + + def widget_attrs(self, widget): + attrs = super(CharField, self).widget_attrs(widget) + if self.max_length is not None and isinstance(widget, TextInput): + # The HTML attribute is maxlength, not max_length. + attrs.update({'maxlength': str(self.max_length)}) + return attrs + + +class IntegerField(Field): + widget = NumberInput + default_error_messages = { + 'invalid': _('Enter a whole number.'), + } + + def __init__(self, max_value=None, min_value=None, *args, **kwargs): + self.max_value, self.min_value = max_value, min_value + if kwargs.get('localize') and self.widget == NumberInput: + # Localized number input is not well supported on most browsers + kwargs.setdefault('widget', super(IntegerField, self).widget) + 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 + if self.localize: + value = formats.sanitize_separators(value) + try: + value = int(str(value)) + except (ValueError, TypeError): + raise ValidationError(self.error_messages['invalid'], code='invalid') + return value + + def widget_attrs(self, widget): + attrs = super(IntegerField, self).widget_attrs(widget) + if isinstance(widget, NumberInput): + if self.min_value is not None: + attrs['min'] = self.min_value + if self.max_value is not None: + attrs['max'] = self.max_value + return attrs + + +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 + if self.localize: + value = formats.sanitize_separators(value) + try: + value = float(value) + except (ValueError, TypeError): + raise ValidationError(self.error_messages['invalid'], code='invalid') + return value + + def widget_attrs(self, widget): + attrs = super(FloatField, self).widget_attrs(widget) + if isinstance(widget, NumberInput): + attrs.setdefault('step', 'any') + return attrs + + +class DecimalField(IntegerField): + default_error_messages = { + 'invalid': _('Enter a number.'), + 'max_digits': ungettext_lazy( + 'Ensure that there are no more than %(max)s digit in total.', + 'Ensure that there are no more than %(max)s digits in total.', + 'max'), + 'max_decimal_places': ungettext_lazy( + 'Ensure that there are no more than %(max)s decimal place.', + 'Ensure that there are no more than %(max)s decimal places.', + 'max'), + 'max_whole_digits': ungettext_lazy( + 'Ensure that there are no more than %(max)s digit before the decimal point.', + 'Ensure that there are no more than %(max)s digits before the decimal point.', + 'max'), + } + + 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) + + 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 + if self.localize: + value = formats.sanitize_separators(value) + value = smart_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') + sign, digittuple, exponent = value.as_tuple() + decimals = abs(exponent) + # digittuple doesn't include any leading zeros. + digits = len(digittuple) + 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.error_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.error_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.error_messages['max_whole_digits'], + code='max_whole_digits', + params={'max': (self.max_digits - self.decimal_places)}, + ) + return value + + def widget_attrs(self, widget): + attrs = super(DecimalField, self).widget_attrs(widget) + if isinstance(widget, NumberInput): + if self.decimal_places is not None: + # Use exponential notation for small values since they might + # be parsed as 0 otherwise. ref #20765 + step = str(Decimal('1') / 10 ** self.decimal_places).lower() + else: + step = 'any' + attrs.setdefault('step', step) + return attrs + + +class BaseTemporalField(Field): + + def __init__(self, input_formats=None, *args, **kwargs): + super(BaseTemporalField, self).__init__(*args, **kwargs) + if input_formats is not None: + self.input_formats = input_formats + + def to_python(self, value): + # Try to coerce the value to unicode. + unicode_value = force_text(value, strings_only=True) + if isinstance(unicode_value, six.text_type): + value = unicode_value.strip() + # If unicode, try to strptime against each input format. + if isinstance(value, six.text_type): + for format in self.input_formats: + try: + return self.strptime(value, format) + except (ValueError, TypeError): + continue + raise ValidationError(self.error_messages['invalid'], code='invalid') + + def strptime(self, value, format): + raise NotImplementedError('Subclasses must define this method.') + + +class DateField(BaseTemporalField): + widget = DateInput + input_formats = formats.get_format_lazy('DATE_INPUT_FORMATS') + default_error_messages = { + 'invalid': _('Enter a valid date.'), + } + + def to_python(self, value): + """ + Validates that the input can be converted to a date. Returns a Python + datetime.date object. + """ + if value in self.empty_values: + return None + if isinstance(value, datetime.datetime): + return value.date() + if isinstance(value, datetime.date): + return value + return super(DateField, self).to_python(value) + + def strptime(self, value, format): + return datetime.datetime.strptime(force_str(value), format).date() + + +class TimeField(BaseTemporalField): + widget = TimeInput + input_formats = 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_str(value), format).time() + + +class DateTimeField(BaseTemporalField): + widget = DateTimeInput + input_formats = formats.get_format_lazy('DATETIME_INPUT_FORMATS') + default_error_messages = { + 'invalid': _('Enter a valid date/time.'), + } + + def prepare_value(self, value): + if isinstance(value, datetime.datetime): + value = to_current_timezone(value) + return value + + def to_python(self, value): + """ + Validates that the input can be converted to a datetime. Returns a + Python datetime.datetime object. + """ + if value in self.empty_values: + return None + if isinstance(value, datetime.datetime): + return from_current_timezone(value) + if isinstance(value, datetime.date): + result = datetime.datetime(value.year, value.month, value.day) + return from_current_timezone(result) + if isinstance(value, list): + # Input comes from a SplitDateTimeWidget, for example. So, it's two + # components: date and time. + if len(value) != 2: + raise ValidationError(self.error_messages['invalid'], code='invalid') + if value[0] in self.empty_values and value[1] in self.empty_values: + return None + value = '%s %s' % tuple(value) + result = super(DateTimeField, self).to_python(value) + return from_current_timezone(result) + + def strptime(self, value, format): + return datetime.datetime.strptime(force_str(value), format) + + +class RegexField(CharField): + def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs): + """ + regex can be either a string or a compiled regular expression object. + error_message is an optional error message to use, if + 'Enter a valid value' is too generic for you. + """ + # error_message is just kept for backwards compatibility: + if error_message: + error_messages = kwargs.get('error_messages') or {} + error_messages['invalid'] = error_message + kwargs['error_messages'] = error_messages + super(RegexField, self).__init__(max_length, min_length, *args, **kwargs) + self._set_regex(regex) + + def _get_regex(self): + return self._regex + + def _set_regex(self, regex): + if isinstance(regex, six.string_types): + regex = re.compile(regex, re.UNICODE) + self._regex = regex + if hasattr(self, '_regex_validator') and self._regex_validator in self.validators: + self.validators.remove(self._regex_validator) + self._regex_validator = validators.RegexValidator(regex=regex) + self.validators.append(self._regex_validator) + + regex = property(_get_regex, _set_regex) + + +class EmailField(CharField): + widget = EmailInput + default_validators = [validators.validate_email] + + def clean(self, value): + value = self.to_python(value).strip() + return super(EmailField, self).clean(value) + + +class FileField(Field): + widget = ClearableFileInput + default_error_messages = { + 'invalid': _("No file was submitted. Check the encoding type on the form."), + 'missing': _("No file was submitted."), + 'empty': _("The submitted file is empty."), + 'max_length': ungettext_lazy( + 'Ensure this filename has at most %(max)d character (it has %(length)d).', + 'Ensure this filename has at most %(max)d characters (it has %(length)d).', + 'max'), + 'contradiction': _('Please either submit a file or check the clear checkbox, not both.') + } + + def __init__(self, *args, **kwargs): + self.max_length = kwargs.pop('max_length', None) + self.allow_empty_file = kwargs.pop('allow_empty_file', False) + super(FileField, self).__init__(*args, **kwargs) + + def to_python(self, data): + if data in self.empty_values: + return None + + # UploadedFile objects should have name and size attributes. + try: + file_name = data.name + file_size = data.size + except AttributeError: + raise ValidationError(self.error_messages['invalid'], code='invalid') + + if self.max_length is not None and len(file_name) > self.max_length: + params = {'max': self.max_length, 'length': len(file_name)} + raise ValidationError(self.error_messages['max_length'], code='max_length', params=params) + if not file_name: + raise ValidationError(self.error_messages['invalid'], code='invalid') + if not self.allow_empty_file and not file_size: + raise ValidationError(self.error_messages['empty'], code='empty') + + return data + + def clean(self, data, initial=None): + # If the widget got contradictory inputs, we raise a validation error + if data is FILE_INPUT_CONTRADICTION: + raise ValidationError(self.error_messages['contradiction'], code='contradiction') + # False means the field value should be cleared; further validation is + # not needed. + if data is False: + if not self.required: + return False + # If the field is required, clearing is not possible (the widget + # shouldn't return False data in that case anyway). False is not + # in self.empty_value; if a False value makes it this far + # it should be validated from here on out as None (so it will be + # caught by the required check). + data = None + if not data and initial: + return initial + return super(FileField, self).clean(data) + + def bound_data(self, data, initial): + if data in (None, FILE_INPUT_CONTRADICTION): + return initial + return data + + def _has_changed(self, initial, data): + if data is None: + return False + return True + + +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 django.utils.image 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. + # verify() must be called immediately after the constructor. + Image.open(file).verify() + except Exception: + # Pillow (or PIL) doesn't recognize it as an image. + six.reraise(ValidationError, ValidationError( + self.error_messages['invalid_image'], + code='invalid_image', + ), sys.exc_info()[2]) + if hasattr(f, 'seek') and callable(f.seek): + f.seek(0) + return f + + +class URLField(CharField): + widget = URLInput + default_error_messages = { + 'invalid': _('Enter a valid URL.'), + } + default_validators = [validators.URLValidator()] + + def to_python(self, value): + + def split_url(url): + """ + Returns a list of url parts via ``urlparse.urlsplit`` (or raises a + ``ValidationError`` exception for certain). + """ + try: + return list(urlsplit(url)) + except ValueError: + # urlparse.urlsplit can raise a ValueError with some + # misformatted URLs. + raise ValidationError(self.error_messages['invalid'], code='invalid') + + value = super(URLField, self).to_python(value) + if value: + url_fields = split_url(value) + if not url_fields[0]: + # If no URL scheme given, assume http:// + url_fields[0] = 'http' + if not url_fields[1]: + # Assume that if no domain is provided, that the path segment + # contains the domain. + url_fields[1] = url_fields[2] + url_fields[2] = '' + # Rebuild the url_fields list, since the domain segment may now + # contain the path too. + url_fields = split_url(urlunsplit(url_fields)) + if not url_fields[2]: + # the path portion may need to be added before query params + url_fields[2] = '/' + value = urlunsplit(url_fields) + return value + + def clean(self, value): + value = self.to_python(value).strip() + return super(URLField, self).clean(value) + + +class BooleanField(Field): + widget = CheckboxInput + + def to_python(self, value): + """Returns a Python boolean object.""" + # Explicitly check for the string 'False', which is what a hidden field + # will submit for False. Also check for '0', since this is what + # RadioSelect will provide. Because bool("True") == bool('1') == True, + # we don't need to handle that explicitly. + if isinstance(value, six.string_types) and value.lower() in ('false', '0'): + value = False + else: + value = bool(value) + return super(BooleanField, self).to_python(value) + + def validate(self, value): + if not value and self.required: + raise ValidationError(self.error_messages['required'], code='required') + + def _has_changed(self, initial, data): + # Sometimes data or initial could be None or '' which should be the + # same thing as False. + if initial == 'False': + # show_hidden_initial may have transformed False to 'False' + initial = False + return bool(initial) != bool(data) + + +class NullBooleanField(BooleanField): + """ + A field whose valid values are None, True and False. Invalid values are + cleaned to None. + """ + widget = NullBooleanSelect + + def to_python(self, value): + """ + Explicitly checks for the string 'True' and 'False', which is what a + hidden field will submit for True and False, and for '1' and '0', which + is what a RadioField will submit. Unlike the Booleanfield we need to + explicitly check for True, because we are not using the bool() function + """ + if value in (True, 'True', '1'): + return True + elif value in (False, 'False', '0'): + return False + else: + return None + + def validate(self, value): + pass + + def _has_changed(self, initial, data): + # None (unknown) and False (No) are not the same + if initial is not None: + initial = bool(initial) + if data is not None: + data = bool(data) + return initial != data + + +class ChoiceField(Field): + widget = Select + default_error_messages = { + 'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'), + } + + def __init__(self, choices=(), required=True, widget=None, label=None, + initial=None, help_text='', *args, **kwargs): + super(ChoiceField, self).__init__(required=required, widget=widget, label=label, + initial=initial, help_text=help_text, *args, **kwargs) + self.choices = choices + + def __deepcopy__(self, memo): + result = super(ChoiceField, self).__deepcopy__(memo) + result._choices = copy.deepcopy(self._choices, memo) + return result + + def _get_choices(self): + return self._choices + + def _set_choices(self, value): + # Setting choices also sets the choices on the widget. + # choices can be any iterable, but we call list() on it because + # it will be consumed more than once. + self._choices = self.widget.choices = list(value) + + choices = property(_get_choices, _set_choices) + + def to_python(self, value): + "Returns a Unicode object." + if value in self.empty_values: + return '' + return smart_text(value) + + def validate(self, value): + """ + Validates that the input is in self.choices. + """ + super(ChoiceField, self).validate(value) + if value and not self.valid_value(value): + raise ValidationError( + self.error_messages['invalid_choice'], + code='invalid_choice', + params={'value': value}, + ) + + def valid_value(self, value): + "Check to see if the provided value is a valid choice" + text_value = force_text(value) + for k, v in self.choices: + if isinstance(v, (list, tuple)): + # This is an optgroup, so look inside the group for options + for k2, v2 in v: + if value == k2 or text_value == force_text(k2): + return True + else: + if value == k or text_value == force_text(k): + return True + return False + + +class TypedChoiceField(ChoiceField): + def __init__(self, *args, **kwargs): + self.coerce = kwargs.pop('coerce', lambda val: val) + self.empty_value = kwargs.pop('empty_value', '') + super(TypedChoiceField, self).__init__(*args, **kwargs) + + def to_python(self, value): + """ + Validates that the value is in self.choices and can be coerced to the + right type. + """ + value = super(TypedChoiceField, self).to_python(value) + if value == self.empty_value or value in self.empty_values: + return self.empty_value + try: + value = self.coerce(value) + except (ValueError, TypeError, ValidationError): + raise ValidationError( + self.error_messages['invalid_choice'], + code='invalid_choice', + params={'value': value}, + ) + return value + + +class MultipleChoiceField(ChoiceField): + hidden_widget = MultipleHiddenInput + widget = SelectMultiple + default_error_messages = { + 'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'), + '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 [smart_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={'value': 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 TypedMultipleChoiceField(MultipleChoiceField): + def __init__(self, *args, **kwargs): + self.coerce = kwargs.pop('coerce', lambda val: val) + self.empty_value = kwargs.pop('empty_value', []) + super(TypedMultipleChoiceField, self).__init__(*args, **kwargs) + + def to_python(self, value): + """ + Validates that the values are in self.choices and can be coerced to the + right type. + """ + value = super(TypedMultipleChoiceField, self).to_python(value) + if value == self.empty_value or value in self.empty_values: + return self.empty_value + new_value = [] + for choice in value: + try: + new_value.append(self.coerce(choice)) + except (ValueError, TypeError, ValidationError): + raise ValidationError( + self.error_messages['invalid_choice'], + code='invalid_choice', + params={'value': choice}, + ) + return new_value + + def validate(self, value): + if value != self.empty_value: + super(TypedMultipleChoiceField, self).validate(value) + elif self.required: + raise ValidationError(self.error_messages['required'], code='required') + + +class ComboField(Field): + """ + A Field whose clean() method calls multiple Field clean() methods. + """ + def __init__(self, fields=(), *args, **kwargs): + super(ComboField, self).__init__(*args, **kwargs) + # Set 'required' to False on the individual fields, because the + # required validation will be handled by ComboField, not by those + # individual fields. + for f in fields: + f.required = False + self.fields = fields + + def clean(self, value): + """ + Validates the given value against all of self.fields, which is a + list of Field instances. + """ + super(ComboField, self).clean(value) + for field in self.fields: + value = field.clean(value) + return value + + +class MultiValueField(Field): + """ + A Field that aggregates the logic of multiple Fields. + + Its clean() method takes a "decompressed" list of values, which are then + cleaned into a single value according to self.fields. Each value in + this list is cleaned by the corresponding field -- the first value is + cleaned by the first field, the second value is cleaned by the second + field, etc. Once all fields are cleaned, the list of clean values is + "compressed" into a single value. + + Subclasses should not have to implement clean(). Instead, they must + implement compress(), which takes a list of valid values and returns a + "compressed" version of those values -- a single value. + + You'll probably want to use this with MultiWidget. + """ + default_error_messages = { + 'invalid': _('Enter a list of values.'), + } + + def __init__(self, fields=(), *args, **kwargs): + super(MultiValueField, self).__init__(*args, **kwargs) + # Set 'required' to False on the individual fields, because the + # required validation will be handled by MultiValueField, not by those + # individual fields. + for f in fields: + f.required = False + self.fields = fields + + def validate(self, value): + pass + + def clean(self, value): + """ + Validates every value in the given list. A value is validated against + the corresponding Field in self.fields. + + For example, if this MultiValueField was instantiated with + fields=(DateField(), TimeField()), clean() would call + DateField.clean(value[0]) and TimeField.clean(value[1]). + """ + clean_data = [] + errors = ErrorList() + if not value or isinstance(value, (list, tuple)): + if not value or not [v for v in value if v not in self.empty_values]: + if self.required: + raise ValidationError(self.error_messages['required'], code='required') + else: + return self.compress([]) + else: + raise ValidationError(self.error_messages['invalid'], code='invalid') + for i, field in enumerate(self.fields): + try: + field_value = value[i] + except IndexError: + field_value = None + if self.required and field_value in self.empty_values: + raise ValidationError(self.error_messages['required'], code='required') + try: + clean_data.append(field.clean(field_value)) + except ValidationError as e: + # Collect all validation errors in a single list, which we'll + # raise at the end of clean(), rather than raising a single + # exception for the first error we encounter. + errors.extend(e.error_list) + if errors: + raise ValidationError(errors) + + out = self.compress(clean_data) + self.validate(out) + self.run_validators(out) + return out + + def compress(self, data_list): + """ + Returns a single value for the given list of values. The values can be + assumed to be valid. + + For example, if this MultiValueField was instantiated with + fields=(DateField(), TimeField()), this might return a datetime + object created by combining the date and time in data_list. + """ + raise NotImplementedError('Subclasses must implement this method.') + + def _has_changed(self, initial, data): + if initial is None: + initial = ['' for x in range(0, len(data))] + else: + if not isinstance(initial, list): + initial = self.widget.decompress(initial) + for field, initial, data in zip(self.fields, initial, data): + if field._has_changed(field.to_python(initial), data): + return True + return False + + +class FilePathField(ChoiceField): + def __init__(self, path, match=None, recursive=False, allow_files=True, + allow_folders=False, required=True, widget=None, label=None, + initial=None, help_text='', *args, **kwargs): + self.path, self.match, self.recursive = path, match, recursive + self.allow_files, self.allow_folders = allow_files, allow_folders + super(FilePathField, self).__init__(choices=(), required=required, + widget=widget, label=label, initial=initial, help_text=help_text, + *args, **kwargs) + + if self.required: + self.choices = [] + else: + self.choices = [("", "---------")] + + if self.match is not None: + self.match_re = re.compile(self.match) + + if recursive: + for root, dirs, files in sorted(os.walk(self.path)): + if self.allow_files: + for f in files: + if self.match is None or self.match_re.search(f): + f = os.path.join(root, f) + self.choices.append((f, f.replace(path, "", 1))) + if self.allow_folders: + for f in dirs: + if f == '__pycache__': + continue + if self.match is None or self.match_re.search(f): + f = os.path.join(root, f) + self.choices.append((f, f.replace(path, "", 1))) + else: + try: + for f in sorted(os.listdir(self.path)): + if f == '__pycache__': + continue + full_file = os.path.join(self.path, f) + if (((self.allow_files and os.path.isfile(full_file)) or + (self.allow_folders and os.path.isdir(full_file))) and + (self.match is None or self.match_re.search(f))): + self.choices.append((full_file, f)) + except OSError: + pass + + self.widget.choices = self.choices + + +class SplitDateTimeField(MultiValueField): + widget = SplitDateTimeWidget + hidden_widget = SplitHiddenDateTimeWidget + default_error_messages = { + 'invalid_date': _('Enter a valid date.'), + 'invalid_time': _('Enter a valid time.'), + } + + def __init__(self, input_date_formats=None, input_time_formats=None, *args, **kwargs): + errors = self.default_error_messages.copy() + if 'error_messages' in kwargs: + errors.update(kwargs['error_messages']) + localize = kwargs.get('localize', False) + fields = ( + DateField(input_formats=input_date_formats, + error_messages={'invalid': errors['invalid_date']}, + localize=localize), + TimeField(input_formats=input_time_formats, + error_messages={'invalid': errors['invalid_time']}, + localize=localize), + ) + super(SplitDateTimeField, self).__init__(fields, *args, **kwargs) + + def compress(self, data_list): + if data_list: + # Raise a validation error if time or date is empty + # (possible if SplitDateTimeField has required=False). + if data_list[0] in self.empty_values: + raise ValidationError(self.error_messages['invalid_date'], code='invalid_date') + if data_list[1] in self.empty_values: + raise ValidationError(self.error_messages['invalid_time'], code='invalid_time') + result = datetime.datetime.combine(*data_list) + return from_current_timezone(result) + return None + + +class IPAddressField(CharField): + default_validators = [validators.validate_ipv4_address] + + def to_python(self, value): + if value in self.empty_values: + return '' + return value.strip() + + +class GenericIPAddressField(CharField): + def __init__(self, protocol='both', unpack_ipv4=False, *args, **kwargs): + self.unpack_ipv4 = unpack_ipv4 + self.default_validators = validators.ip_address_validators(protocol, unpack_ipv4)[0] + super(GenericIPAddressField, self).__init__(*args, **kwargs) + + def to_python(self, value): + if value in self.empty_values: + return '' + value = value.strip() + if value and ':' in value: + return clean_ipv6_address(value, self.unpack_ipv4) + return value + + +class SlugField(CharField): + default_validators = [validators.validate_slug] + + def clean(self, value): + value = self.to_python(value).strip() + return super(SlugField, self).clean(value) |