diff options
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, 0 insertions, 1146 deletions
diff --git a/lib/python2.7/site-packages/django/forms/fields.py b/lib/python2.7/site-packages/django/forms/fields.py deleted file mode 100644 index 966b303..0000000 --- a/lib/python2.7/site-packages/django/forms/fields.py +++ /dev/null @@ -1,1146 +0,0 @@ -""" -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) |