diff options
Diffstat (limited to 'lib/python2.7/site-packages/django/forms')
-rw-r--r-- | lib/python2.7/site-packages/django/forms/__init__.py | 11 | ||||
-rw-r--r-- | lib/python2.7/site-packages/django/forms/extras/__init__.py | 3 | ||||
-rw-r--r-- | lib/python2.7/site-packages/django/forms/extras/widgets.py | 138 | ||||
-rw-r--r-- | lib/python2.7/site-packages/django/forms/fields.py | 1146 | ||||
-rw-r--r-- | lib/python2.7/site-packages/django/forms/forms.py | 584 | ||||
-rw-r--r-- | lib/python2.7/site-packages/django/forms/formsets.py | 417 | ||||
-rw-r--r-- | lib/python2.7/site-packages/django/forms/models.py | 1231 | ||||
-rw-r--r-- | lib/python2.7/site-packages/django/forms/util.py | 103 | ||||
-rw-r--r-- | lib/python2.7/site-packages/django/forms/widgets.py | 869 |
9 files changed, 4502 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/django/forms/__init__.py b/lib/python2.7/site-packages/django/forms/__init__.py new file mode 100644 index 0000000..2588098 --- /dev/null +++ b/lib/python2.7/site-packages/django/forms/__init__.py @@ -0,0 +1,11 @@ +""" +Django validation and HTML form handling. +""" + +from __future__ import absolute_import + +from django.core.exceptions import ValidationError +from django.forms.fields import * +from django.forms.forms import * +from django.forms.models import * +from django.forms.widgets import * diff --git a/lib/python2.7/site-packages/django/forms/extras/__init__.py b/lib/python2.7/site-packages/django/forms/extras/__init__.py new file mode 100644 index 0000000..d801e4f --- /dev/null +++ b/lib/python2.7/site-packages/django/forms/extras/__init__.py @@ -0,0 +1,3 @@ +from __future__ import absolute_import + +from django.forms.extras.widgets import * diff --git a/lib/python2.7/site-packages/django/forms/extras/widgets.py b/lib/python2.7/site-packages/django/forms/extras/widgets.py new file mode 100644 index 0000000..0b96dc4 --- /dev/null +++ b/lib/python2.7/site-packages/django/forms/extras/widgets.py @@ -0,0 +1,138 @@ +""" +Extra HTML Widget classes +""" +from __future__ import unicode_literals + +import datetime +import re + +from django.forms.widgets import Widget, Select +from django.utils import datetime_safe +from django.utils.dates import MONTHS +from django.utils.encoding import force_str +from django.utils.safestring import mark_safe +from django.utils.formats import get_format +from django.utils import six +from django.conf import settings + +__all__ = ('SelectDateWidget',) + +RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$') + +def _parse_date_fmt(): + fmt = get_format('DATE_FORMAT') + escaped = False + output = [] + for char in fmt: + if escaped: + escaped = False + elif char == '\\': + escaped = True + elif char in 'Yy': + output.append('year') + #if not self.first_select: self.first_select = 'year' + elif char in 'bEFMmNn': + output.append('month') + #if not self.first_select: self.first_select = 'month' + elif char in 'dj': + output.append('day') + #if not self.first_select: self.first_select = 'day' + return output + +class SelectDateWidget(Widget): + """ + A Widget that splits date input into three <select> boxes. + + This also serves as an example of a Widget that has more than one HTML + element and hence implements value_from_datadict. + """ + none_value = (0, '---') + month_field = '%s_month' + day_field = '%s_day' + year_field = '%s_year' + + def __init__(self, attrs=None, years=None, required=True): + # years is an optional list/tuple of years to use in the "year" select box. + self.attrs = attrs or {} + self.required = required + if years: + self.years = years + else: + this_year = datetime.date.today().year + self.years = range(this_year, this_year+10) + + def render(self, name, value, attrs=None): + try: + year_val, month_val, day_val = value.year, value.month, value.day + except AttributeError: + year_val = month_val = day_val = None + if isinstance(value, six.string_types): + if settings.USE_L10N: + try: + input_format = get_format('DATE_INPUT_FORMATS')[0] + v = datetime.datetime.strptime(force_str(value), input_format) + year_val, month_val, day_val = v.year, v.month, v.day + except ValueError: + pass + else: + match = RE_DATE.match(value) + if match: + year_val, month_val, day_val = [int(v) for v in match.groups()] + choices = [(i, i) for i in self.years] + year_html = self.create_select(name, self.year_field, value, year_val, choices) + choices = list(six.iteritems(MONTHS)) + month_html = self.create_select(name, self.month_field, value, month_val, choices) + choices = [(i, i) for i in range(1, 32)] + day_html = self.create_select(name, self.day_field, value, day_val, choices) + + output = [] + for field in _parse_date_fmt(): + if field == 'year': + output.append(year_html) + elif field == 'month': + output.append(month_html) + elif field == 'day': + output.append(day_html) + return mark_safe('\n'.join(output)) + + def id_for_label(self, id_): + first_select = None + field_list = _parse_date_fmt() + if field_list: + first_select = field_list[0] + if first_select is not None: + return '%s_%s' % (id_, first_select) + else: + return '%s_month' % id_ + + def value_from_datadict(self, data, files, name): + y = data.get(self.year_field % name) + m = data.get(self.month_field % name) + d = data.get(self.day_field % name) + if y == m == d == "0": + return None + if y and m and d: + if settings.USE_L10N: + input_format = get_format('DATE_INPUT_FORMATS')[0] + try: + date_value = datetime.date(int(y), int(m), int(d)) + except ValueError: + return '%s-%s-%s' % (y, m, d) + else: + date_value = datetime_safe.new_date(date_value) + return date_value.strftime(input_format) + else: + return '%s-%s-%s' % (y, m, d) + return data.get(name, None) + + def create_select(self, name, field, value, val, choices): + if 'id' in self.attrs: + id_ = self.attrs['id'] + else: + id_ = 'id_%s' % name + if not (self.required and val): + choices.insert(0, self.none_value) + local_attrs = self.build_attrs(id=field % id_) + s = Select(choices=choices) + select_html = s.render(field % name, val, local_attrs) + return select_html 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) diff --git a/lib/python2.7/site-packages/django/forms/forms.py b/lib/python2.7/site-packages/django/forms/forms.py new file mode 100644 index 0000000..573215d --- /dev/null +++ b/lib/python2.7/site-packages/django/forms/forms.py @@ -0,0 +1,584 @@ +""" +Form classes +""" + +from __future__ import absolute_import, unicode_literals + +import copy +import warnings + +from django.core.exceptions import ValidationError +from django.forms.fields import Field, FileField +from django.forms.util import flatatt, ErrorDict, ErrorList +from django.forms.widgets import Media, media_property, TextInput, Textarea +from django.utils.datastructures import SortedDict +from django.utils.html import conditional_escape, format_html +from django.utils.encoding import smart_text, force_text, python_2_unicode_compatible +from django.utils.safestring import mark_safe +from django.utils.translation import ugettext as _ +from django.utils import six + + +__all__ = ('BaseForm', 'Form') + +NON_FIELD_ERRORS = '__all__' + +def pretty_name(name): + """Converts 'first_name' to 'First name'""" + if not name: + return '' + return name.replace('_', ' ').capitalize() + +def get_declared_fields(bases, attrs, with_base_fields=True): + """ + Create a list of form field instances from the passed in 'attrs', plus any + similar fields on the base classes (in 'bases'). This is used by both the + Form and ModelForm metaclasses. + + If 'with_base_fields' is True, all fields from the bases are used. + Otherwise, only fields in the 'declared_fields' attribute on the bases are + used. The distinction is useful in ModelForm subclassing. + Also integrates any additional media definitions. + """ + fields = [(field_name, attrs.pop(field_name)) for field_name, obj in list(six.iteritems(attrs)) if isinstance(obj, Field)] + fields.sort(key=lambda x: x[1].creation_counter) + + # If this class is subclassing another Form, add that Form's fields. + # Note that we loop over the bases in *reverse*. This is necessary in + # order to preserve the correct order of fields. + if with_base_fields: + for base in bases[::-1]: + if hasattr(base, 'base_fields'): + fields = list(six.iteritems(base.base_fields)) + fields + else: + for base in bases[::-1]: + if hasattr(base, 'declared_fields'): + fields = list(six.iteritems(base.declared_fields)) + fields + + return SortedDict(fields) + +class DeclarativeFieldsMetaclass(type): + """ + Metaclass that converts Field attributes to a dictionary called + 'base_fields', taking into account parent class 'base_fields' as well. + """ + def __new__(cls, name, bases, attrs): + attrs['base_fields'] = get_declared_fields(bases, attrs) + new_class = super(DeclarativeFieldsMetaclass, + cls).__new__(cls, name, bases, attrs) + if 'media' not in attrs: + new_class.media = media_property(new_class) + return new_class + +@python_2_unicode_compatible +class BaseForm(object): + # This is the main implementation of all the Form logic. Note that this + # class is different than Form. See the comments by the Form class for more + # information. Any improvements to the form API should be made to *this* + # class, not to the Form class. + def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, + initial=None, error_class=ErrorList, label_suffix=None, + empty_permitted=False): + self.is_bound = data is not None or files is not None + self.data = data or {} + self.files = files or {} + self.auto_id = auto_id + self.prefix = prefix + self.initial = initial or {} + self.error_class = error_class + # Translators: This is the default suffix added to form field labels + self.label_suffix = label_suffix if label_suffix is not None else _(':') + self.empty_permitted = empty_permitted + self._errors = None # Stores the errors after clean() has been called. + self._changed_data = None + + # The base_fields class attribute is the *class-wide* definition of + # fields. Because a particular *instance* of the class might want to + # alter self.fields, we create self.fields here by copying base_fields. + # Instances should always modify self.fields; they should not modify + # self.base_fields. + self.fields = copy.deepcopy(self.base_fields) + + def __str__(self): + return self.as_table() + + def __iter__(self): + for name in self.fields: + yield self[name] + + def __getitem__(self, name): + "Returns a BoundField with the given name." + try: + field = self.fields[name] + except KeyError: + raise KeyError('Key %r not found in Form' % name) + return BoundField(self, field, name) + + @property + def errors(self): + "Returns an ErrorDict for the data provided for the form" + if self._errors is None: + self.full_clean() + return self._errors + + def is_valid(self): + """ + Returns True if the form has no errors. Otherwise, False. If errors are + being ignored, returns False. + """ + return self.is_bound and not bool(self.errors) + + def add_prefix(self, field_name): + """ + Returns the field name with a prefix appended, if this Form has a + prefix set. + + Subclasses may wish to override. + """ + return '%s-%s' % (self.prefix, field_name) if self.prefix else field_name + + def add_initial_prefix(self, field_name): + """ + Add a 'initial' prefix for checking dynamic initial values + """ + return 'initial-%s' % self.add_prefix(field_name) + + def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): + "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." + top_errors = self.non_field_errors() # Errors that should be displayed above all fields. + output, hidden_fields = [], [] + + for name, field in self.fields.items(): + html_class_attr = '' + bf = self[name] + # Escape and cache in local variable. + bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) + if bf.is_hidden: + if bf_errors: + top_errors.extend( + [_('(Hidden field %(name)s) %(error)s') % {'name': name, 'error': force_text(e)} + for e in bf_errors]) + hidden_fields.append(six.text_type(bf)) + else: + # Create a 'class="..."' atribute if the row should have any + # CSS classes applied. + css_classes = bf.css_classes() + if css_classes: + html_class_attr = ' class="%s"' % css_classes + + if errors_on_separate_row and bf_errors: + output.append(error_row % force_text(bf_errors)) + + if bf.label: + label = conditional_escape(force_text(bf.label)) + label = bf.label_tag(label) or '' + else: + label = '' + + if field.help_text: + help_text = help_text_html % force_text(field.help_text) + else: + help_text = '' + + output.append(normal_row % { + 'errors': force_text(bf_errors), + 'label': force_text(label), + 'field': six.text_type(bf), + 'help_text': help_text, + 'html_class_attr': html_class_attr + }) + + if top_errors: + output.insert(0, error_row % force_text(top_errors)) + + if hidden_fields: # Insert any hidden fields in the last row. + str_hidden = ''.join(hidden_fields) + if output: + last_row = output[-1] + # Chop off the trailing row_ender (e.g. '</td></tr>') and + # insert the hidden fields. + if not last_row.endswith(row_ender): + # This can happen in the as_p() case (and possibly others + # that users write): if there are only top errors, we may + # not be able to conscript the last row for our purposes, + # so insert a new, empty row. + last_row = (normal_row % {'errors': '', 'label': '', + 'field': '', 'help_text':'', + 'html_class_attr': html_class_attr}) + output.append(last_row) + output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender + else: + # If there aren't any rows in the output, just append the + # hidden fields. + output.append(str_hidden) + return mark_safe('\n'.join(output)) + + def as_table(self): + "Returns this form rendered as HTML <tr>s -- excluding the <table></table>." + return self._html_output( + normal_row = '<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', + error_row = '<tr><td colspan="2">%s</td></tr>', + row_ender = '</td></tr>', + help_text_html = '<br /><span class="helptext">%s</span>', + errors_on_separate_row = False) + + def as_ul(self): + "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>." + return self._html_output( + normal_row = '<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>', + error_row = '<li>%s</li>', + row_ender = '</li>', + help_text_html = ' <span class="helptext">%s</span>', + errors_on_separate_row = False) + + def as_p(self): + "Returns this form rendered as HTML <p>s." + return self._html_output( + normal_row = '<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>', + error_row = '%s', + row_ender = '</p>', + help_text_html = ' <span class="helptext">%s</span>', + errors_on_separate_row = True) + + def non_field_errors(self): + """ + Returns an ErrorList of errors that aren't associated with a particular + field -- i.e., from Form.clean(). Returns an empty ErrorList if there + are none. + """ + return self.errors.get(NON_FIELD_ERRORS, self.error_class()) + + def _raw_value(self, fieldname): + """ + Returns the raw_value for a particular field name. This is just a + convenient wrapper around widget.value_from_datadict. + """ + field = self.fields[fieldname] + prefix = self.add_prefix(fieldname) + return field.widget.value_from_datadict(self.data, self.files, prefix) + + def full_clean(self): + """ + Cleans all of self.data and populates self._errors and + self.cleaned_data. + """ + self._errors = ErrorDict() + if not self.is_bound: # Stop further processing. + return + self.cleaned_data = {} + # If the form is permitted to be empty, and none of the form data has + # changed from the initial data, short circuit any validation. + if self.empty_permitted and not self.has_changed(): + return + self._clean_fields() + self._clean_form() + self._post_clean() + + def _clean_fields(self): + for name, field in self.fields.items(): + # value_from_datadict() gets the data from the data dictionaries. + # Each widget type knows how to retrieve its own data, because some + # widgets split data over several HTML fields. + value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) + try: + if isinstance(field, FileField): + initial = self.initial.get(name, field.initial) + value = field.clean(value, initial) + else: + value = field.clean(value) + self.cleaned_data[name] = value + if hasattr(self, 'clean_%s' % name): + value = getattr(self, 'clean_%s' % name)() + self.cleaned_data[name] = value + except ValidationError as e: + self._errors[name] = self.error_class(e.messages) + if name in self.cleaned_data: + del self.cleaned_data[name] + + def _clean_form(self): + try: + self.cleaned_data = self.clean() + except ValidationError as e: + self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages) + + def _post_clean(self): + """ + An internal hook for performing additional cleaning after form cleaning + is complete. Used for model validation in model forms. + """ + pass + + def clean(self): + """ + Hook for doing any extra form-wide cleaning after Field.clean() been + called on every field. Any ValidationError raised by this method will + not be associated with a particular field; it will have a special-case + association with the field named '__all__'. + """ + return self.cleaned_data + + def has_changed(self): + """ + Returns True if data differs from initial. + """ + return bool(self.changed_data) + + @property + def changed_data(self): + if self._changed_data is None: + self._changed_data = [] + # XXX: For now we're asking the individual widgets whether or not the + # data has changed. It would probably be more efficient to hash the + # initial data, store it in a hidden field, and compare a hash of the + # submitted data, but we'd need a way to easily get the string value + # for a given field. Right now, that logic is embedded in the render + # method of each widget. + for name, field in self.fields.items(): + prefixed_name = self.add_prefix(name) + data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name) + if not field.show_hidden_initial: + initial_value = self.initial.get(name, field.initial) + if callable(initial_value): + initial_value = initial_value() + else: + initial_prefixed_name = self.add_initial_prefix(name) + hidden_widget = field.hidden_widget() + try: + initial_value = field.to_python(hidden_widget.value_from_datadict( + self.data, self.files, initial_prefixed_name)) + except ValidationError: + # Always assume data has changed if validation fails. + self._changed_data.append(name) + continue + if hasattr(field.widget, '_has_changed'): + warnings.warn("The _has_changed method on widgets is deprecated," + " define it at field level instead.", + PendingDeprecationWarning, stacklevel=2) + if field.widget._has_changed(initial_value, data_value): + self._changed_data.append(name) + elif field._has_changed(initial_value, data_value): + self._changed_data.append(name) + return self._changed_data + + @property + def media(self): + """ + Provide a description of all media required to render the widgets on this form + """ + media = Media() + for field in self.fields.values(): + media = media + field.widget.media + return media + + def is_multipart(self): + """ + Returns True if the form needs to be multipart-encoded, i.e. it has + FileInput. Otherwise, False. + """ + for field in self.fields.values(): + if field.widget.needs_multipart_form: + return True + return False + + def hidden_fields(self): + """ + Returns a list of all the BoundField objects that are hidden fields. + Useful for manual form layout in templates. + """ + return [field for field in self if field.is_hidden] + + def visible_fields(self): + """ + Returns a list of BoundField objects that aren't hidden fields. + The opposite of the hidden_fields() method. + """ + return [field for field in self if not field.is_hidden] + +class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)): + "A collection of Fields, plus their associated data." + # This is a separate class from BaseForm in order to abstract the way + # self.fields is specified. This class (Form) is the one that does the + # fancy metaclass stuff purely for the semantic sugar -- it allows one + # to define a form using declarative syntax. + # BaseForm itself has no way of designating self.fields. + +@python_2_unicode_compatible +class BoundField(object): + "A Field plus data" + def __init__(self, form, field, name): + self.form = form + self.field = field + self.name = name + self.html_name = form.add_prefix(name) + self.html_initial_name = form.add_initial_prefix(name) + self.html_initial_id = form.add_initial_prefix(self.auto_id) + if self.field.label is None: + self.label = pretty_name(name) + else: + self.label = self.field.label + self.help_text = field.help_text or '' + + def __str__(self): + """Renders this field as an HTML widget.""" + if self.field.show_hidden_initial: + return self.as_widget() + self.as_hidden(only_initial=True) + return self.as_widget() + + def __iter__(self): + """ + Yields rendered strings that comprise all widgets in this BoundField. + + This really is only useful for RadioSelect widgets, so that you can + iterate over individual radio buttons in a template. + """ + for subwidget in self.field.widget.subwidgets(self.html_name, self.value()): + yield subwidget + + def __len__(self): + return len(list(self.__iter__())) + + def __getitem__(self, idx): + return list(self.__iter__())[idx] + + @property + def errors(self): + """ + Returns an ErrorList for this field. Returns an empty ErrorList + if there are none. + """ + return self.form.errors.get(self.name, self.form.error_class()) + + def as_widget(self, widget=None, attrs=None, only_initial=False): + """ + Renders the field by rendering the passed widget, adding any HTML + attributes passed as attrs. If no widget is specified, then the + field's default widget will be used. + """ + if not widget: + widget = self.field.widget + + if self.field.localize: + widget.is_localized = True + + attrs = attrs or {} + auto_id = self.auto_id + if auto_id and 'id' not in attrs and 'id' not in widget.attrs: + if not only_initial: + attrs['id'] = auto_id + else: + attrs['id'] = self.html_initial_id + + if not only_initial: + name = self.html_name + else: + name = self.html_initial_name + return widget.render(name, self.value(), attrs=attrs) + + def as_text(self, attrs=None, **kwargs): + """ + Returns a string of HTML for representing this as an <input type="text">. + """ + return self.as_widget(TextInput(), attrs, **kwargs) + + def as_textarea(self, attrs=None, **kwargs): + "Returns a string of HTML for representing this as a <textarea>." + return self.as_widget(Textarea(), attrs, **kwargs) + + def as_hidden(self, attrs=None, **kwargs): + """ + Returns a string of HTML for representing this as an <input type="hidden">. + """ + return self.as_widget(self.field.hidden_widget(), attrs, **kwargs) + + @property + def data(self): + """ + Returns the data for this BoundField, or None if it wasn't given. + """ + return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name) + + def value(self): + """ + Returns the value for this BoundField, using the initial value if + the form is not bound or the data otherwise. + """ + if not self.form.is_bound: + data = self.form.initial.get(self.name, self.field.initial) + if callable(data): + data = data() + else: + data = self.field.bound_data( + self.data, self.form.initial.get(self.name, self.field.initial) + ) + return self.field.prepare_value(data) + + def label_tag(self, contents=None, attrs=None, label_suffix=None): + """ + Wraps the given contents in a <label>, if the field has an ID attribute. + contents should be 'mark_safe'd to avoid HTML escaping. If contents + aren't given, uses the field's HTML-escaped label. + + If attrs are given, they're used as HTML attributes on the <label> tag. + + label_suffix allows overriding the form's label_suffix. + """ + contents = contents or self.label + # Only add the suffix if the label does not end in punctuation. + label_suffix = label_suffix if label_suffix is not None else self.form.label_suffix + # Translators: If found as last label character, these punctuation + # characters will prevent the default label_suffix to be appended to the label + if label_suffix and contents and contents[-1] not in _(':?.!'): + contents = format_html('{0}{1}', contents, label_suffix) + widget = self.field.widget + id_ = widget.attrs.get('id') or self.auto_id + if id_: + id_for_label = widget.id_for_label(id_) + if id_for_label: + attrs = dict(attrs or {}, **{'for': id_for_label}) + attrs = flatatt(attrs) if attrs else '' + contents = format_html('<label{0}>{1}</label>', attrs, contents) + else: + contents = conditional_escape(contents) + return mark_safe(contents) + + def css_classes(self, extra_classes=None): + """ + Returns a string of space-separated CSS classes for this field. + """ + if hasattr(extra_classes, 'split'): + extra_classes = extra_classes.split() + extra_classes = set(extra_classes or []) + if self.errors and hasattr(self.form, 'error_css_class'): + extra_classes.add(self.form.error_css_class) + if self.field.required and hasattr(self.form, 'required_css_class'): + extra_classes.add(self.form.required_css_class) + return ' '.join(extra_classes) + + @property + def is_hidden(self): + "Returns True if this BoundField's widget is hidden." + return self.field.widget.is_hidden + + @property + def auto_id(self): + """ + Calculates and returns the ID attribute for this BoundField, if the + associated Form has specified auto_id. Returns an empty string otherwise. + """ + auto_id = self.form.auto_id + if auto_id and '%s' in smart_text(auto_id): + return smart_text(auto_id) % self.html_name + elif auto_id: + return self.html_name + return '' + + @property + def id_for_label(self): + """ + Wrapper around the field widget's `id_for_label` method. + Useful, for example, for focusing on this field regardless of whether + it has a single widget or a MultiWidget. + """ + widget = self.field.widget + id_ = widget.attrs.get('id') or self.auto_id + return widget.id_for_label(id_) diff --git a/lib/python2.7/site-packages/django/forms/formsets.py b/lib/python2.7/site-packages/django/forms/formsets.py new file mode 100644 index 0000000..cb3126e --- /dev/null +++ b/lib/python2.7/site-packages/django/forms/formsets.py @@ -0,0 +1,417 @@ +from __future__ import absolute_import, unicode_literals + +from django.core.exceptions import ValidationError +from django.forms import Form +from django.forms.fields import IntegerField, BooleanField +from django.forms.util import ErrorList +from django.forms.widgets import HiddenInput +from django.utils.encoding import python_2_unicode_compatible +from django.utils.functional import cached_property +from django.utils.safestring import mark_safe +from django.utils import six +from django.utils.six.moves import xrange +from django.utils.translation import ungettext, ugettext as _ + + +__all__ = ('BaseFormSet', 'all_valid') + +# special field names +TOTAL_FORM_COUNT = 'TOTAL_FORMS' +INITIAL_FORM_COUNT = 'INITIAL_FORMS' +MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS' +ORDERING_FIELD_NAME = 'ORDER' +DELETION_FIELD_NAME = 'DELETE' + +# default maximum number of forms in a formset, to prevent memory exhaustion +DEFAULT_MAX_NUM = 1000 + +class ManagementForm(Form): + """ + ``ManagementForm`` is used to keep track of how many form instances + are displayed on the page. If adding new forms via javascript, you should + increment the count field of this form as well. + """ + def __init__(self, *args, **kwargs): + self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput) + self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput) + # MAX_NUM_FORM_COUNT is output with the rest of the management form, + # but only for the convenience of client-side code. The POST + # value of MAX_NUM_FORM_COUNT returned from the client is not checked. + self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput) + super(ManagementForm, self).__init__(*args, **kwargs) + +@python_2_unicode_compatible +class BaseFormSet(object): + """ + A collection of instances of the same Form class. + """ + def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, + initial=None, error_class=ErrorList): + self.is_bound = data is not None or files is not None + self.prefix = prefix or self.get_default_prefix() + self.auto_id = auto_id + self.data = data or {} + self.files = files or {} + self.initial = initial + self.error_class = error_class + self._errors = None + self._non_form_errors = None + + def __str__(self): + return self.as_table() + + def __iter__(self): + """Yields the forms in the order they should be rendered""" + return iter(self.forms) + + def __getitem__(self, index): + """Returns the form at the given index, based on the rendering order""" + return self.forms[index] + + def __len__(self): + return len(self.forms) + + def __bool__(self): + """All formsets have a management form which is not included in the length""" + return True + + def __nonzero__(self): # Python 2 compatibility + return type(self).__bool__(self) + + @property + def management_form(self): + """Returns the ManagementForm instance for this FormSet.""" + if self.is_bound: + form = ManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix) + if not form.is_valid(): + raise ValidationError( + _('ManagementForm data is missing or has been tampered with'), + code='missing_management_form', + ) + else: + form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial={ + TOTAL_FORM_COUNT: self.total_form_count(), + INITIAL_FORM_COUNT: self.initial_form_count(), + MAX_NUM_FORM_COUNT: self.max_num + }) + return form + + def total_form_count(self): + """Returns the total number of forms in this FormSet.""" + if self.is_bound: + # return absolute_max if it is lower than the actual total form + # count in the data; this is DoS protection to prevent clients + # from forcing the server to instantiate arbitrary numbers of + # forms + return min(self.management_form.cleaned_data[TOTAL_FORM_COUNT], self.absolute_max) + else: + initial_forms = self.initial_form_count() + total_forms = initial_forms + self.extra + # Allow all existing related objects/inlines to be displayed, + # but don't allow extra beyond max_num. + if initial_forms > self.max_num >= 0: + total_forms = initial_forms + elif total_forms > self.max_num >= 0: + total_forms = self.max_num + return total_forms + + def initial_form_count(self): + """Returns the number of forms that are required in this FormSet.""" + if self.is_bound: + return self.management_form.cleaned_data[INITIAL_FORM_COUNT] + else: + # Use the length of the initial data if it's there, 0 otherwise. + initial_forms = len(self.initial) if self.initial else 0 + return initial_forms + + @cached_property + def forms(self): + """ + Instantiate forms at first property access. + """ + # DoS protection is included in total_form_count() + forms = [self._construct_form(i) for i in xrange(self.total_form_count())] + return forms + + def _construct_form(self, i, **kwargs): + """ + Instantiates and returns the i-th form instance in a formset. + """ + defaults = { + 'auto_id': self.auto_id, + 'prefix': self.add_prefix(i), + 'error_class': self.error_class, + } + if self.is_bound: + defaults['data'] = self.data + defaults['files'] = self.files + if self.initial and not 'initial' in kwargs: + try: + defaults['initial'] = self.initial[i] + except IndexError: + pass + # Allow extra forms to be empty. + if i >= self.initial_form_count(): + defaults['empty_permitted'] = True + defaults.update(kwargs) + form = self.form(**defaults) + self.add_fields(form, i) + return form + + @property + def initial_forms(self): + """Return a list of all the initial forms in this formset.""" + return self.forms[:self.initial_form_count()] + + @property + def extra_forms(self): + """Return a list of all the extra forms in this formset.""" + return self.forms[self.initial_form_count():] + + @property + def empty_form(self): + form = self.form( + auto_id=self.auto_id, + prefix=self.add_prefix('__prefix__'), + empty_permitted=True, + ) + self.add_fields(form, None) + return form + + @property + def cleaned_data(self): + """ + Returns a list of form.cleaned_data dicts for every form in self.forms. + """ + if not self.is_valid(): + raise AttributeError("'%s' object has no attribute 'cleaned_data'" % self.__class__.__name__) + return [form.cleaned_data for form in self.forms] + + @property + def deleted_forms(self): + """ + Returns a list of forms that have been marked for deletion. + """ + if not self.is_valid() or not self.can_delete: + return [] + # construct _deleted_form_indexes which is just a list of form indexes + # that have had their deletion widget set to True + if not hasattr(self, '_deleted_form_indexes'): + self._deleted_form_indexes = [] + for i in range(0, self.total_form_count()): + form = self.forms[i] + # if this is an extra form and hasn't changed, don't consider it + if i >= self.initial_form_count() and not form.has_changed(): + continue + if self._should_delete_form(form): + self._deleted_form_indexes.append(i) + return [self.forms[i] for i in self._deleted_form_indexes] + + @property + def ordered_forms(self): + """ + Returns a list of form in the order specified by the incoming data. + Raises an AttributeError if ordering is not allowed. + """ + if not self.is_valid() or not self.can_order: + raise AttributeError("'%s' object has no attribute 'ordered_forms'" % self.__class__.__name__) + # Construct _ordering, which is a list of (form_index, order_field_value) + # tuples. After constructing this list, we'll sort it by order_field_value + # so we have a way to get to the form indexes in the order specified + # by the form data. + if not hasattr(self, '_ordering'): + self._ordering = [] + for i in range(0, self.total_form_count()): + form = self.forms[i] + # if this is an extra form and hasn't changed, don't consider it + if i >= self.initial_form_count() and not form.has_changed(): + continue + # don't add data marked for deletion to self.ordered_data + if self.can_delete and self._should_delete_form(form): + continue + self._ordering.append((i, form.cleaned_data[ORDERING_FIELD_NAME])) + # After we're done populating self._ordering, sort it. + # A sort function to order things numerically ascending, but + # None should be sorted below anything else. Allowing None as + # a comparison value makes it so we can leave ordering fields + # blank. + def compare_ordering_key(k): + if k[1] is None: + return (1, 0) # +infinity, larger than any number + return (0, k[1]) + self._ordering.sort(key=compare_ordering_key) + # Return a list of form.cleaned_data dicts in the order specified by + # the form data. + return [self.forms[i[0]] for i in self._ordering] + + @classmethod + def get_default_prefix(cls): + return 'form' + + def non_form_errors(self): + """ + Returns an ErrorList of errors that aren't associated with a particular + form -- i.e., from formset.clean(). Returns an empty ErrorList if there + are none. + """ + if self._non_form_errors is None: + self.full_clean() + return self._non_form_errors + + @property + def errors(self): + """ + Returns a list of form.errors for every form in self.forms. + """ + if self._errors is None: + self.full_clean() + return self._errors + + def total_error_count(self): + """ + Returns the number of errors across all forms in the formset. + """ + return len(self.non_form_errors()) +\ + sum(len(form_errors) for form_errors in self.errors) + + def _should_delete_form(self, form): + """ + Returns whether or not the form was marked for deletion. + """ + return form.cleaned_data.get(DELETION_FIELD_NAME, False) + + def is_valid(self): + """ + Returns True if every form in self.forms is valid. + """ + if not self.is_bound: + return False + # We loop over every form.errors here rather than short circuiting on the + # first failure to make sure validation gets triggered for every form. + forms_valid = True + err = self.errors + for i in range(0, self.total_form_count()): + form = self.forms[i] + if self.can_delete: + if self._should_delete_form(form): + # This form is going to be deleted so any of its errors + # should not cause the entire formset to be invalid. + continue + forms_valid &= form.is_valid() + return forms_valid and not bool(self.non_form_errors()) + + def full_clean(self): + """ + Cleans all of self.data and populates self._errors and + self._non_form_errors. + """ + self._errors = [] + self._non_form_errors = self.error_class() + + if not self.is_bound: # Stop further processing. + return + for i in range(0, self.total_form_count()): + form = self.forms[i] + self._errors.append(form.errors) + try: + if (self.validate_max and + self.total_form_count() - len(self.deleted_forms) > self.max_num) or \ + self.management_form.cleaned_data[TOTAL_FORM_COUNT] > self.absolute_max: + raise ValidationError(ungettext( + "Please submit %d or fewer forms.", + "Please submit %d or fewer forms.", self.max_num) % self.max_num, + code='too_many_forms', + ) + # Give self.clean() a chance to do cross-form validation. + self.clean() + except ValidationError as e: + self._non_form_errors = self.error_class(e.messages) + + def clean(self): + """ + Hook for doing any extra formset-wide cleaning after Form.clean() has + been called on every form. Any ValidationError raised by this method + will not be associated with a particular form; it will be accesible + via formset.non_form_errors() + """ + pass + + def has_changed(self): + """ + Returns true if data in any form differs from initial. + """ + return any(form.has_changed() for form in self) + + def add_fields(self, form, index): + """A hook for adding extra fields on to each form instance.""" + if self.can_order: + # Only pre-fill the ordering field for initial forms. + if index is not None and index < self.initial_form_count(): + form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_('Order'), initial=index+1, required=False) + else: + form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_('Order'), required=False) + if self.can_delete: + form.fields[DELETION_FIELD_NAME] = BooleanField(label=_('Delete'), required=False) + + def add_prefix(self, index): + return '%s-%s' % (self.prefix, index) + + def is_multipart(self): + """ + Returns True if the formset needs to be multipart, i.e. it + has FileInput. Otherwise, False. + """ + if self.forms: + return self.forms[0].is_multipart() + else: + return self.empty_form.is_multipart() + + @property + def media(self): + # All the forms on a FormSet are the same, so you only need to + # interrogate the first form for media. + if self.forms: + return self.forms[0].media + else: + return self.empty_form.media + + def as_table(self): + "Returns this formset rendered as HTML <tr>s -- excluding the <table></table>." + # XXX: there is no semantic division between forms here, there + # probably should be. It might make sense to render each form as a + # table row with each field as a td. + forms = ' '.join([form.as_table() for form in self]) + return mark_safe('\n'.join([six.text_type(self.management_form), forms])) + + def as_p(self): + "Returns this formset rendered as HTML <p>s." + forms = ' '.join([form.as_p() for form in self]) + return mark_safe('\n'.join([six.text_type(self.management_form), forms])) + + def as_ul(self): + "Returns this formset rendered as HTML <li>s." + forms = ' '.join([form.as_ul() for form in self]) + return mark_safe('\n'.join([six.text_type(self.management_form), forms])) + +def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, + can_delete=False, max_num=None, validate_max=False): + """Return a FormSet for the given form class.""" + if max_num is None: + max_num = DEFAULT_MAX_NUM + # hard limit on forms instantiated, to prevent memory-exhaustion attacks + # limit is simply max_num + DEFAULT_MAX_NUM (which is 2*DEFAULT_MAX_NUM + # if max_num is None in the first place) + absolute_max = max_num + DEFAULT_MAX_NUM + attrs = {'form': form, 'extra': extra, + 'can_order': can_order, 'can_delete': can_delete, + 'max_num': max_num, 'absolute_max': absolute_max, + 'validate_max' : validate_max} + return type(form.__name__ + str('FormSet'), (formset,), attrs) + +def all_valid(formsets): + """Returns true if every formset in formsets is valid.""" + valid = True + for formset in formsets: + if not formset.is_valid(): + valid = False + return valid diff --git a/lib/python2.7/site-packages/django/forms/models.py b/lib/python2.7/site-packages/django/forms/models.py new file mode 100644 index 0000000..15c0015 --- /dev/null +++ b/lib/python2.7/site-packages/django/forms/models.py @@ -0,0 +1,1231 @@ +""" +Helper functions for creating Form classes from Django models +and database field objects. +""" + +from __future__ import absolute_import, unicode_literals + +import warnings + +from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, FieldError +from django.forms.fields import Field, ChoiceField +from django.forms.forms import BaseForm, get_declared_fields +from django.forms.formsets import BaseFormSet, formset_factory +from django.forms.util import ErrorList +from django.forms.widgets import (SelectMultiple, HiddenInput, + MultipleHiddenInput, media_property, CheckboxSelectMultiple) +from django.utils.encoding import smart_text, force_text +from django.utils.datastructures import SortedDict +from django.utils import six +from django.utils.text import get_text_list, capfirst +from django.utils.translation import ugettext_lazy as _, ugettext, string_concat + + +__all__ = ( + 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model', + 'save_instance', 'ModelChoiceField', 'ModelMultipleChoiceField', + 'ALL_FIELDS', +) + +ALL_FIELDS = '__all__' + + +def construct_instance(form, instance, fields=None, exclude=None): + """ + Constructs and returns a model instance from the bound ``form``'s + ``cleaned_data``, but does not save the returned instance to the + database. + """ + from django.db import models + opts = instance._meta + + cleaned_data = form.cleaned_data + file_field_list = [] + for f in opts.fields: + if not f.editable or isinstance(f, models.AutoField) \ + or not f.name in cleaned_data: + continue + if fields is not None and f.name not in fields: + continue + if exclude and f.name in exclude: + continue + # Defer saving file-type fields until after the other fields, so a + # callable upload_to can use the values from other fields. + if isinstance(f, models.FileField): + file_field_list.append(f) + else: + f.save_form_data(instance, cleaned_data[f.name]) + + for f in file_field_list: + f.save_form_data(instance, cleaned_data[f.name]) + + return instance + +def save_instance(form, instance, fields=None, fail_message='saved', + commit=True, exclude=None, construct=True): + """ + Saves bound Form ``form``'s cleaned_data into model instance ``instance``. + + If commit=True, then the changes to ``instance`` will be saved to the + database. Returns ``instance``. + + If construct=False, assume ``instance`` has already been constructed and + just needs to be saved. + """ + if construct: + instance = construct_instance(form, instance, fields, exclude) + opts = instance._meta + if form.errors: + raise ValueError("The %s could not be %s because the data didn't" + " validate." % (opts.object_name, fail_message)) + + # Wrap up the saving of m2m data as a function. + def save_m2m(): + cleaned_data = form.cleaned_data + # Note that for historical reasons we want to include also + # virtual_fields here. (GenericRelation was previously a fake + # m2m field). + for f in opts.many_to_many + opts.virtual_fields: + if not hasattr(f, 'save_form_data'): + continue + if fields and f.name not in fields: + continue + if exclude and f.name in exclude: + continue + if f.name in cleaned_data: + f.save_form_data(instance, cleaned_data[f.name]) + if commit: + # If we are committing, save the instance and the m2m data immediately. + instance.save() + save_m2m() + else: + # We're not committing. Add a method to the form to allow deferred + # saving of m2m data. + form.save_m2m = save_m2m + return instance + + +# ModelForms ################################################################# + +def model_to_dict(instance, fields=None, exclude=None): + """ + Returns a dict containing the data in ``instance`` suitable for passing as + a Form's ``initial`` keyword argument. + + ``fields`` is an optional list of field names. If provided, only the named + fields will be included in the returned dict. + + ``exclude`` is an optional list of field names. If provided, the named + fields will be excluded from the returned dict, even if they are listed in + the ``fields`` argument. + """ + # avoid a circular import + from django.db.models.fields.related import ManyToManyField + opts = instance._meta + data = {} + for f in opts.concrete_fields + opts.virtual_fields + opts.many_to_many: + if not getattr(f, 'editable', False): + continue + if fields and not f.name in fields: + continue + if exclude and f.name in exclude: + continue + if isinstance(f, ManyToManyField): + # If the object doesn't have a primary key yet, just use an empty + # list for its m2m fields. Calling f.value_from_object will raise + # an exception. + if instance.pk is None: + data[f.name] = [] + else: + # MultipleChoiceWidget needs a list of pks, not object instances. + data[f.name] = list(f.value_from_object(instance).values_list('pk', flat=True)) + else: + data[f.name] = f.value_from_object(instance) + return data + +def fields_for_model(model, fields=None, exclude=None, widgets=None, + formfield_callback=None, localized_fields=None, + labels=None, help_texts=None, error_messages=None): + """ + Returns a ``SortedDict`` containing form fields for the given model. + + ``fields`` is an optional list of field names. If provided, only the named + fields will be included in the returned fields. + + ``exclude`` is an optional list of field names. If provided, the named + fields will be excluded from the returned fields, even if they are listed + in the ``fields`` argument. + + ``widgets`` is a dictionary of model field names mapped to a widget. + + ``localized_fields`` is a list of names of fields which should be localized. + + ``labels`` is a dictionary of model field names mapped to a label. + + ``help_texts`` is a dictionary of model field names mapped to a help text. + + ``error_messages`` is a dictionary of model field names mapped to a + dictionary of error messages. + + ``formfield_callback`` is a callable that takes a model field and returns + a form field. + """ + field_list = [] + ignored = [] + opts = model._meta + # Avoid circular import + from django.db.models.fields import Field as ModelField + sortable_virtual_fields = [f for f in opts.virtual_fields + if isinstance(f, ModelField)] + for f in sorted(opts.concrete_fields + sortable_virtual_fields + opts.many_to_many): + if not getattr(f, 'editable', False): + continue + if fields is not None and not f.name in fields: + continue + if exclude and f.name in exclude: + continue + + kwargs = {} + if widgets and f.name in widgets: + kwargs['widget'] = widgets[f.name] + if localized_fields == ALL_FIELDS or (localized_fields and f.name in localized_fields): + kwargs['localize'] = True + if labels and f.name in labels: + kwargs['label'] = labels[f.name] + if help_texts and f.name in help_texts: + kwargs['help_text'] = help_texts[f.name] + if error_messages and f.name in error_messages: + kwargs['error_messages'] = error_messages[f.name] + + if formfield_callback is None: + formfield = f.formfield(**kwargs) + elif not callable(formfield_callback): + raise TypeError('formfield_callback must be a function or callable') + else: + formfield = formfield_callback(f, **kwargs) + + if formfield: + field_list.append((f.name, formfield)) + else: + ignored.append(f.name) + field_dict = SortedDict(field_list) + if fields: + field_dict = SortedDict( + [(f, field_dict.get(f)) for f in fields + if ((not exclude) or (exclude and f not in exclude)) and (f not in ignored)] + ) + return field_dict + +class ModelFormOptions(object): + def __init__(self, options=None): + self.model = getattr(options, 'model', None) + self.fields = getattr(options, 'fields', None) + self.exclude = getattr(options, 'exclude', None) + self.widgets = getattr(options, 'widgets', None) + self.localized_fields = getattr(options, 'localized_fields', None) + self.labels = getattr(options, 'labels', None) + self.help_texts = getattr(options, 'help_texts', None) + self.error_messages = getattr(options, 'error_messages', None) + + +class ModelFormMetaclass(type): + def __new__(cls, name, bases, attrs): + formfield_callback = attrs.pop('formfield_callback', None) + try: + parents = [b for b in bases if issubclass(b, ModelForm)] + except NameError: + # We are defining ModelForm itself. + parents = None + declared_fields = get_declared_fields(bases, attrs, False) + new_class = super(ModelFormMetaclass, cls).__new__(cls, name, bases, + attrs) + if not parents: + return new_class + + if 'media' not in attrs: + new_class.media = media_property(new_class) + opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None)) + + # We check if a string was passed to `fields` or `exclude`, + # which is likely to be a mistake where the user typed ('foo') instead + # of ('foo',) + for opt in ['fields', 'exclude', 'localized_fields']: + value = getattr(opts, opt) + if isinstance(value, six.string_types) and value != ALL_FIELDS: + msg = ("%(model)s.Meta.%(opt)s cannot be a string. " + "Did you mean to type: ('%(value)s',)?" % { + 'model': new_class.__name__, + 'opt': opt, + 'value': value, + }) + raise TypeError(msg) + + if opts.model: + # If a model is defined, extract form fields from it. + + if opts.fields is None and opts.exclude is None: + # This should be some kind of assertion error once deprecation + # cycle is complete. + warnings.warn("Creating a ModelForm without either the 'fields' attribute " + "or the 'exclude' attribute is deprecated - form %s " + "needs updating" % name, + PendingDeprecationWarning, stacklevel=2) + + if opts.fields == ALL_FIELDS: + # sentinel for fields_for_model to indicate "get the list of + # fields from the model" + opts.fields = None + + fields = fields_for_model(opts.model, opts.fields, opts.exclude, + opts.widgets, formfield_callback, + opts.localized_fields, opts.labels, + opts.help_texts, opts.error_messages) + + # make sure opts.fields doesn't specify an invalid field + none_model_fields = [k for k, v in six.iteritems(fields) if not v] + missing_fields = set(none_model_fields) - \ + set(declared_fields.keys()) + if missing_fields: + message = 'Unknown field(s) (%s) specified for %s' + message = message % (', '.join(missing_fields), + opts.model.__name__) + raise FieldError(message) + # Override default model fields with any custom declared ones + # (plus, include all the other declared fields). + fields.update(declared_fields) + else: + fields = declared_fields + new_class.declared_fields = declared_fields + new_class.base_fields = fields + return new_class + +class BaseModelForm(BaseForm): + def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, + initial=None, error_class=ErrorList, label_suffix=None, + empty_permitted=False, instance=None): + opts = self._meta + if opts.model is None: + raise ValueError('ModelForm has no model class specified.') + if instance is None: + # if we didn't get an instance, instantiate a new one + self.instance = opts.model() + object_data = {} + else: + self.instance = instance + object_data = model_to_dict(instance, opts.fields, opts.exclude) + # if initial was provided, it should override the values from instance + if initial is not None: + object_data.update(initial) + # self._validate_unique will be set to True by BaseModelForm.clean(). + # It is False by default so overriding self.clean() and failing to call + # super will stop validate_unique from being called. + self._validate_unique = False + super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data, + error_class, label_suffix, empty_permitted) + + def _update_errors(self, errors): + for field, messages in errors.error_dict.items(): + if field not in self.fields: + continue + field = self.fields[field] + for message in messages: + if isinstance(message, ValidationError): + if message.code in field.error_messages: + message.message = field.error_messages[message.code] + + message_dict = errors.message_dict + for k, v in message_dict.items(): + if k != NON_FIELD_ERRORS: + self._errors.setdefault(k, self.error_class()).extend(v) + # Remove the data from the cleaned_data dict since it was invalid + if k in self.cleaned_data: + del self.cleaned_data[k] + if NON_FIELD_ERRORS in message_dict: + messages = message_dict[NON_FIELD_ERRORS] + self._errors.setdefault(NON_FIELD_ERRORS, self.error_class()).extend(messages) + + def _get_validation_exclusions(self): + """ + For backwards-compatibility, several types of fields need to be + excluded from model validation. See the following tickets for + details: #12507, #12521, #12553 + """ + exclude = [] + # Build up a list of fields that should be excluded from model field + # validation and unique checks. + for f in self.instance._meta.fields: + field = f.name + # Exclude fields that aren't on the form. The developer may be + # adding these values to the model after form validation. + if field not in self.fields: + exclude.append(f.name) + + # Don't perform model validation on fields that were defined + # manually on the form and excluded via the ModelForm's Meta + # class. See #12901. + elif self._meta.fields and field not in self._meta.fields: + exclude.append(f.name) + elif self._meta.exclude and field in self._meta.exclude: + exclude.append(f.name) + + # Exclude fields that failed form validation. There's no need for + # the model fields to validate them as well. + elif field in self._errors.keys(): + exclude.append(f.name) + + # Exclude empty fields that are not required by the form, if the + # underlying model field is required. This keeps the model field + # from raising a required error. Note: don't exclude the field from + # validation if the model field allows blanks. If it does, the blank + # value may be included in a unique check, so cannot be excluded + # from validation. + else: + form_field = self.fields[field] + field_value = self.cleaned_data.get(field, None) + if not f.blank and not form_field.required and field_value in form_field.empty_values: + exclude.append(f.name) + return exclude + + def clean(self): + self._validate_unique = True + return self.cleaned_data + + def _post_clean(self): + opts = self._meta + # Update the model instance with self.cleaned_data. + self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude) + + exclude = self._get_validation_exclusions() + + # Foreign Keys being used to represent inline relationships + # are excluded from basic field value validation. This is for two + # reasons: firstly, the value may not be supplied (#12507; the + # case of providing new values to the admin); secondly the + # object being referred to may not yet fully exist (#12749). + # However, these fields *must* be included in uniqueness checks, + # so this can't be part of _get_validation_exclusions(). + for f_name, field in self.fields.items(): + if isinstance(field, InlineForeignKeyField): + exclude.append(f_name) + + try: + self.instance.full_clean(exclude=exclude, + validate_unique=False) + except ValidationError as e: + self._update_errors(e) + + # Validate uniqueness if needed. + if self._validate_unique: + self.validate_unique() + + def validate_unique(self): + """ + Calls the instance's validate_unique() method and updates the form's + validation errors if any were raised. + """ + exclude = self._get_validation_exclusions() + try: + self.instance.validate_unique(exclude=exclude) + except ValidationError as e: + self._update_errors(e) + + def save(self, commit=True): + """ + Saves this ``form``'s cleaned_data into model instance + ``self.instance``. + + If commit=True, then the changes to ``instance`` will be saved to the + database. Returns ``instance``. + """ + if self.instance.pk is None: + fail_message = 'created' + else: + fail_message = 'changed' + return save_instance(self, self.instance, self._meta.fields, + fail_message, commit, self._meta.exclude, + construct=False) + + save.alters_data = True + +class ModelForm(six.with_metaclass(ModelFormMetaclass, BaseModelForm)): + pass + +def modelform_factory(model, form=ModelForm, fields=None, exclude=None, + formfield_callback=None, widgets=None, localized_fields=None, + labels=None, help_texts=None, error_messages=None): + """ + Returns a ModelForm containing form fields for the given model. + + ``fields`` is an optional list of field names. If provided, only the named + fields will be included in the returned fields. If omitted or '__all__', + all fields will be used. + + ``exclude`` is an optional list of field names. If provided, the named + fields will be excluded from the returned fields, even if they are listed + in the ``fields`` argument. + + ``widgets`` is a dictionary of model field names mapped to a widget. + + ``localized_fields`` is a list of names of fields which should be localized. + + ``formfield_callback`` is a callable that takes a model field and returns + a form field. + + ``labels`` is a dictionary of model field names mapped to a label. + + ``help_texts`` is a dictionary of model field names mapped to a help text. + + ``error_messages`` is a dictionary of model field names mapped to a + dictionary of error messages. + """ + # Create the inner Meta class. FIXME: ideally, we should be able to + # construct a ModelForm without creating and passing in a temporary + # inner class. + + # Build up a list of attributes that the Meta object will have. + attrs = {'model': model} + if fields is not None: + attrs['fields'] = fields + if exclude is not None: + attrs['exclude'] = exclude + if widgets is not None: + attrs['widgets'] = widgets + if localized_fields is not None: + attrs['localized_fields'] = localized_fields + if labels is not None: + attrs['labels'] = labels + if help_texts is not None: + attrs['help_texts'] = help_texts + if error_messages is not None: + attrs['error_messages'] = error_messages + + # If parent form class already has an inner Meta, the Meta we're + # creating needs to inherit from the parent's inner meta. + parent = (object,) + if hasattr(form, 'Meta'): + parent = (form.Meta, object) + Meta = type(str('Meta'), parent, attrs) + + # Give this new form class a reasonable name. + class_name = model.__name__ + str('Form') + + # Class attributes for the new form class. + form_class_attrs = { + 'Meta': Meta, + 'formfield_callback': formfield_callback + } + + # The ModelFormMetaclass will trigger a similar warning/error, but this will + # be difficult to debug for code that needs updating, so we produce the + # warning here too. + if (getattr(Meta, 'fields', None) is None and + getattr(Meta, 'exclude', None) is None): + warnings.warn("Calling modelform_factory without defining 'fields' or " + "'exclude' explicitly is deprecated", + PendingDeprecationWarning, stacklevel=2) + + # Instatiate type(form) in order to use the same metaclass as form. + return type(form)(class_name, (form,), form_class_attrs) + + +# ModelFormSets ############################################################## + +class BaseModelFormSet(BaseFormSet): + """ + A ``FormSet`` for editing a queryset and/or adding new objects to it. + """ + model = None + + def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, + queryset=None, **kwargs): + self.queryset = queryset + self.initial_extra = kwargs.pop('initial', None) + defaults = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} + defaults.update(kwargs) + super(BaseModelFormSet, self).__init__(**defaults) + + def initial_form_count(self): + """Returns the number of forms that are required in this FormSet.""" + if not (self.data or self.files): + return len(self.get_queryset()) + return super(BaseModelFormSet, self).initial_form_count() + + def _existing_object(self, pk): + if not hasattr(self, '_object_dict'): + self._object_dict = dict([(o.pk, o) for o in self.get_queryset()]) + return self._object_dict.get(pk) + + def _construct_form(self, i, **kwargs): + if self.is_bound and i < self.initial_form_count(): + # Import goes here instead of module-level because importing + # django.db has side effects. + from django.db import connections + pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name) + pk = self.data[pk_key] + pk_field = self.model._meta.pk + pk = pk_field.get_db_prep_lookup('exact', pk, + connection=connections[self.get_queryset().db]) + if isinstance(pk, list): + pk = pk[0] + kwargs['instance'] = self._existing_object(pk) + if i < self.initial_form_count() and not kwargs.get('instance'): + kwargs['instance'] = self.get_queryset()[i] + if i >= self.initial_form_count() and self.initial_extra: + # Set initial values for extra forms + try: + kwargs['initial'] = self.initial_extra[i-self.initial_form_count()] + except IndexError: + pass + return super(BaseModelFormSet, self)._construct_form(i, **kwargs) + + def get_queryset(self): + if not hasattr(self, '_queryset'): + if self.queryset is not None: + qs = self.queryset + else: + qs = self.model._default_manager.get_queryset() + + # If the queryset isn't already ordered we need to add an + # artificial ordering here to make sure that all formsets + # constructed from this queryset have the same form order. + if not qs.ordered: + qs = qs.order_by(self.model._meta.pk.name) + + # Removed queryset limiting here. As per discussion re: #13023 + # on django-dev, max_num should not prevent existing + # related objects/inlines from being displayed. + self._queryset = qs + return self._queryset + + def save_new(self, form, commit=True): + """Saves and returns a new model instance for the given form.""" + return form.save(commit=commit) + + def save_existing(self, form, instance, commit=True): + """Saves and returns an existing model instance for the given form.""" + return form.save(commit=commit) + + def save(self, commit=True): + """Saves model instances for every form, adding and changing instances + as necessary, and returns the list of instances. + """ + if not commit: + self.saved_forms = [] + def save_m2m(): + for form in self.saved_forms: + form.save_m2m() + self.save_m2m = save_m2m + return self.save_existing_objects(commit) + self.save_new_objects(commit) + + save.alters_data = True + + def clean(self): + self.validate_unique() + + def validate_unique(self): + # Collect unique_checks and date_checks to run from all the forms. + all_unique_checks = set() + all_date_checks = set() + forms_to_delete = self.deleted_forms + valid_forms = [form for form in self.forms if form.is_valid() and form not in forms_to_delete] + for form in valid_forms: + exclude = form._get_validation_exclusions() + unique_checks, date_checks = form.instance._get_unique_checks(exclude=exclude) + all_unique_checks = all_unique_checks.union(set(unique_checks)) + all_date_checks = all_date_checks.union(set(date_checks)) + + errors = [] + # Do each of the unique checks (unique and unique_together) + for uclass, unique_check in all_unique_checks: + seen_data = set() + for form in valid_forms: + # get data for each field of each of unique_check + row_data = tuple([form.cleaned_data[field] for field in unique_check if field in form.cleaned_data]) + if row_data and not None in row_data: + # if we've already seen it then we have a uniqueness failure + if row_data in seen_data: + # poke error messages into the right places and mark + # the form as invalid + errors.append(self.get_unique_error_message(unique_check)) + form._errors[NON_FIELD_ERRORS] = self.error_class([self.get_form_error()]) + # remove the data from the cleaned_data dict since it was invalid + for field in unique_check: + if field in form.cleaned_data: + del form.cleaned_data[field] + # mark the data as seen + seen_data.add(row_data) + # iterate over each of the date checks now + for date_check in all_date_checks: + seen_data = set() + uclass, lookup, field, unique_for = date_check + for form in valid_forms: + # see if we have data for both fields + if (form.cleaned_data and form.cleaned_data[field] is not None + and form.cleaned_data[unique_for] is not None): + # if it's a date lookup we need to get the data for all the fields + if lookup == 'date': + date = form.cleaned_data[unique_for] + date_data = (date.year, date.month, date.day) + # otherwise it's just the attribute on the date/datetime + # object + else: + date_data = (getattr(form.cleaned_data[unique_for], lookup),) + data = (form.cleaned_data[field],) + date_data + # if we've already seen it then we have a uniqueness failure + if data in seen_data: + # poke error messages into the right places and mark + # the form as invalid + errors.append(self.get_date_error_message(date_check)) + form._errors[NON_FIELD_ERRORS] = self.error_class([self.get_form_error()]) + # remove the data from the cleaned_data dict since it was invalid + del form.cleaned_data[field] + # mark the data as seen + seen_data.add(data) + if errors: + raise ValidationError(errors) + + def get_unique_error_message(self, unique_check): + if len(unique_check) == 1: + return ugettext("Please correct the duplicate data for %(field)s.") % { + "field": unique_check[0], + } + else: + return ugettext("Please correct the duplicate data for %(field)s, " + "which must be unique.") % { + "field": get_text_list(unique_check, six.text_type(_("and"))), + } + + def get_date_error_message(self, date_check): + return ugettext("Please correct the duplicate data for %(field_name)s " + "which must be unique for the %(lookup)s in %(date_field)s.") % { + 'field_name': date_check[2], + 'date_field': date_check[3], + 'lookup': six.text_type(date_check[1]), + } + + def get_form_error(self): + return ugettext("Please correct the duplicate values below.") + + def save_existing_objects(self, commit=True): + self.changed_objects = [] + self.deleted_objects = [] + if not self.initial_forms: + return [] + + saved_instances = [] + forms_to_delete = self.deleted_forms + for form in self.initial_forms: + pk_name = self._pk_field.name + raw_pk_value = form._raw_value(pk_name) + + # clean() for different types of PK fields can sometimes return + # the model instance, and sometimes the PK. Handle either. + pk_value = form.fields[pk_name].clean(raw_pk_value) + pk_value = getattr(pk_value, 'pk', pk_value) + + obj = self._existing_object(pk_value) + if form in forms_to_delete: + self.deleted_objects.append(obj) + obj.delete() + continue + if form.has_changed(): + self.changed_objects.append((obj, form.changed_data)) + saved_instances.append(self.save_existing(form, obj, commit=commit)) + if not commit: + self.saved_forms.append(form) + return saved_instances + + def save_new_objects(self, commit=True): + self.new_objects = [] + for form in self.extra_forms: + if not form.has_changed(): + continue + # If someone has marked an add form for deletion, don't save the + # object. + if self.can_delete and self._should_delete_form(form): + continue + self.new_objects.append(self.save_new(form, commit=commit)) + if not commit: + self.saved_forms.append(form) + return self.new_objects + + def add_fields(self, form, index): + """Add a hidden field for the object's primary key.""" + from django.db.models import AutoField, OneToOneField, ForeignKey + self._pk_field = pk = self.model._meta.pk + # If a pk isn't editable, then it won't be on the form, so we need to + # add it here so we can tell which object is which when we get the + # data back. Generally, pk.editable should be false, but for some + # reason, auto_created pk fields and AutoField's editable attribute is + # True, so check for that as well. + def pk_is_not_editable(pk): + return ((not pk.editable) or (pk.auto_created or isinstance(pk, AutoField)) + or (pk.rel and pk.rel.parent_link and pk_is_not_editable(pk.rel.to._meta.pk))) + if pk_is_not_editable(pk) or pk.name not in form.fields: + if form.is_bound: + pk_value = form.instance.pk + else: + try: + if index is not None: + pk_value = self.get_queryset()[index].pk + else: + pk_value = None + except IndexError: + pk_value = None + if isinstance(pk, OneToOneField) or isinstance(pk, ForeignKey): + qs = pk.rel.to._default_manager.get_queryset() + else: + qs = self.model._default_manager.get_queryset() + qs = qs.using(form.instance._state.db) + if form._meta.widgets: + widget = form._meta.widgets.get(self._pk_field.name, HiddenInput) + else: + widget = HiddenInput + form.fields[self._pk_field.name] = ModelChoiceField(qs, initial=pk_value, required=False, widget=widget) + super(BaseModelFormSet, self).add_fields(form, index) + +def modelformset_factory(model, form=ModelForm, formfield_callback=None, + formset=BaseModelFormSet, extra=1, can_delete=False, + can_order=False, max_num=None, fields=None, exclude=None, + widgets=None, validate_max=False, localized_fields=None, + labels=None, help_texts=None, error_messages=None): + """ + Returns a FormSet class for the given Django model class. + """ + # modelform_factory will produce the same warning/error, but that will be + # difficult to debug for code that needs upgrading, so we produce the + # warning here too. This logic is reproducing logic inside + # modelform_factory, but it can be removed once the deprecation cycle is + # complete, since the validation exception will produce a helpful + # stacktrace. + meta = getattr(form, 'Meta', None) + if meta is None: + meta = type(str('Meta'), (object,), {}) + if (getattr(meta, 'fields', fields) is None and + getattr(meta, 'exclude', exclude) is None): + warnings.warn("Calling modelformset_factory without defining 'fields' or " + "'exclude' explicitly is deprecated", + PendingDeprecationWarning, stacklevel=2) + + form = modelform_factory(model, form=form, fields=fields, exclude=exclude, + formfield_callback=formfield_callback, + widgets=widgets, localized_fields=localized_fields, + labels=labels, help_texts=help_texts, error_messages=error_messages) + FormSet = formset_factory(form, formset, extra=extra, max_num=max_num, + can_order=can_order, can_delete=can_delete, + validate_max=validate_max) + FormSet.model = model + return FormSet + + +# InlineFormSets ############################################################# + +class BaseInlineFormSet(BaseModelFormSet): + """A formset for child objects related to a parent.""" + def __init__(self, data=None, files=None, instance=None, + save_as_new=False, prefix=None, queryset=None, **kwargs): + if instance is None: + self.instance = self.fk.rel.to() + else: + self.instance = instance + self.save_as_new = save_as_new + if queryset is None: + queryset = self.model._default_manager + if self.instance.pk is not None: + qs = queryset.filter(**{self.fk.name: self.instance}) + else: + qs = queryset.none() + super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix, + queryset=qs, **kwargs) + + def initial_form_count(self): + if self.save_as_new: + return 0 + return super(BaseInlineFormSet, self).initial_form_count() + + + def _construct_form(self, i, **kwargs): + form = super(BaseInlineFormSet, self)._construct_form(i, **kwargs) + if self.save_as_new: + # Remove the primary key from the form's data, we are only + # creating new instances + form.data[form.add_prefix(self._pk_field.name)] = None + + # Remove the foreign key from the form's data + form.data[form.add_prefix(self.fk.name)] = None + + # Set the fk value here so that the form can do its validation. + setattr(form.instance, self.fk.get_attname(), self.instance.pk) + return form + + @classmethod + def get_default_prefix(cls): + from django.db.models.fields.related import RelatedObject + return RelatedObject(cls.fk.rel.to, cls.model, cls.fk).get_accessor_name().replace('+','') + + def save_new(self, form, commit=True): + # Use commit=False so we can assign the parent key afterwards, then + # save the object. + obj = form.save(commit=False) + pk_value = getattr(self.instance, self.fk.rel.field_name) + setattr(obj, self.fk.get_attname(), getattr(pk_value, 'pk', pk_value)) + if commit: + obj.save() + # form.save_m2m() can be called via the formset later on if commit=False + if commit and hasattr(form, 'save_m2m'): + form.save_m2m() + return obj + + def add_fields(self, form, index): + super(BaseInlineFormSet, self).add_fields(form, index) + if self._pk_field == self.fk: + name = self._pk_field.name + kwargs = {'pk_field': True} + else: + # The foreign key field might not be on the form, so we poke at the + # Model field to get the label, since we need that for error messages. + name = self.fk.name + kwargs = { + 'label': getattr(form.fields.get(name), 'label', capfirst(self.fk.verbose_name)) + } + if self.fk.rel.field_name != self.fk.rel.to._meta.pk.name: + kwargs['to_field'] = self.fk.rel.field_name + + form.fields[name] = InlineForeignKeyField(self.instance, **kwargs) + + # Add the generated field to form._meta.fields if it's defined to make + # sure validation isn't skipped on that field. + if form._meta.fields: + if isinstance(form._meta.fields, tuple): + form._meta.fields = list(form._meta.fields) + form._meta.fields.append(self.fk.name) + + def get_unique_error_message(self, unique_check): + unique_check = [field for field in unique_check if field != self.fk.name] + return super(BaseInlineFormSet, self).get_unique_error_message(unique_check) + + +def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False): + """ + Finds and returns the ForeignKey from model to parent if there is one + (returns None if can_fail is True and no such field exists). If fk_name is + provided, assume it is the name of the ForeignKey field. Unles can_fail is + True, an exception is raised if there is no ForeignKey from model to + parent_model. + """ + # avoid circular import + from django.db.models import ForeignKey + opts = model._meta + if fk_name: + fks_to_parent = [f for f in opts.fields if f.name == fk_name] + if len(fks_to_parent) == 1: + fk = fks_to_parent[0] + if not isinstance(fk, ForeignKey) or \ + (fk.rel.to != parent_model and + fk.rel.to not in parent_model._meta.get_parent_list()): + raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model)) + elif len(fks_to_parent) == 0: + raise Exception("%s has no field named '%s'" % (model, fk_name)) + else: + # Try to discover what the ForeignKey from model to parent_model is + fks_to_parent = [ + f for f in opts.fields + if isinstance(f, ForeignKey) + and (f.rel.to == parent_model + or f.rel.to in parent_model._meta.get_parent_list()) + ] + if len(fks_to_parent) == 1: + fk = fks_to_parent[0] + elif len(fks_to_parent) == 0: + if can_fail: + return + raise Exception("%s has no ForeignKey to %s" % (model, parent_model)) + else: + raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model)) + return fk + + +def inlineformset_factory(parent_model, model, form=ModelForm, + formset=BaseInlineFormSet, fk_name=None, + fields=None, exclude=None, extra=3, can_order=False, + can_delete=True, max_num=None, formfield_callback=None, + widgets=None, validate_max=False, localized_fields=None, + labels=None, help_texts=None, error_messages=None): + """ + Returns an ``InlineFormSet`` for the given kwargs. + + You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey`` + to ``parent_model``. + """ + fk = _get_foreign_key(parent_model, model, fk_name=fk_name) + # enforce a max_num=1 when the foreign key to the parent model is unique. + if fk.unique: + max_num = 1 + kwargs = { + 'form': form, + 'formfield_callback': formfield_callback, + 'formset': formset, + 'extra': extra, + 'can_delete': can_delete, + 'can_order': can_order, + 'fields': fields, + 'exclude': exclude, + 'max_num': max_num, + 'widgets': widgets, + 'validate_max': validate_max, + 'localized_fields': localized_fields, + 'labels': labels, + 'help_texts': help_texts, + 'error_messages': error_messages, + } + FormSet = modelformset_factory(model, **kwargs) + FormSet.fk = fk + return FormSet + + +# Fields ##################################################################### + +class InlineForeignKeyField(Field): + """ + A basic integer field that deals with validating the given value to a + given parent instance in an inline. + """ + widget = HiddenInput + default_error_messages = { + 'invalid_choice': _('The inline foreign key did not match the parent instance primary key.'), + } + + def __init__(self, parent_instance, *args, **kwargs): + self.parent_instance = parent_instance + self.pk_field = kwargs.pop("pk_field", False) + self.to_field = kwargs.pop("to_field", None) + if self.parent_instance is not None: + if self.to_field: + kwargs["initial"] = getattr(self.parent_instance, self.to_field) + else: + kwargs["initial"] = self.parent_instance.pk + kwargs["required"] = False + super(InlineForeignKeyField, self).__init__(*args, **kwargs) + + def clean(self, value): + if value in self.empty_values: + if self.pk_field: + return None + # if there is no value act as we did before. + return self.parent_instance + # ensure the we compare the values as equal types. + if self.to_field: + orig = getattr(self.parent_instance, self.to_field) + else: + orig = self.parent_instance.pk + if force_text(value) != force_text(orig): + raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice') + return self.parent_instance + + def _has_changed(self, initial, data): + return False + +class ModelChoiceIterator(object): + def __init__(self, field): + self.field = field + self.queryset = field.queryset + + def __iter__(self): + if self.field.empty_label is not None: + yield ("", self.field.empty_label) + if self.field.cache_choices: + if self.field.choice_cache is None: + self.field.choice_cache = [ + self.choice(obj) for obj in self.queryset.all() + ] + for choice in self.field.choice_cache: + yield choice + else: + for obj in self.queryset.all(): + yield self.choice(obj) + + def __len__(self): + return len(self.queryset) +\ + (1 if self.field.empty_label is not None else 0) + + def choice(self, obj): + return (self.field.prepare_value(obj), self.field.label_from_instance(obj)) + +class ModelChoiceField(ChoiceField): + """A ChoiceField whose choices are a model QuerySet.""" + # This class is a subclass of ChoiceField for purity, but it doesn't + # actually use any of ChoiceField's implementation. + default_error_messages = { + 'invalid_choice': _('Select a valid choice. That choice is not one of' + ' the available choices.'), + } + + def __init__(self, queryset, empty_label="---------", cache_choices=False, + required=True, widget=None, label=None, initial=None, + help_text='', to_field_name=None, *args, **kwargs): + if required and (initial is not None): + self.empty_label = None + else: + self.empty_label = empty_label + self.cache_choices = cache_choices + + # Call Field instead of ChoiceField __init__() because we don't need + # ChoiceField.__init__(). + Field.__init__(self, required, widget, label, initial, help_text, + *args, **kwargs) + self.queryset = queryset + self.choice_cache = None + self.to_field_name = to_field_name + + def __deepcopy__(self, memo): + result = super(ChoiceField, self).__deepcopy__(memo) + # Need to force a new ModelChoiceIterator to be created, bug #11183 + result.queryset = result.queryset + return result + + def _get_queryset(self): + return self._queryset + + def _set_queryset(self, queryset): + self._queryset = queryset + self.widget.choices = self.choices + + queryset = property(_get_queryset, _set_queryset) + + # this method will be used to create object labels by the QuerySetIterator. + # Override it to customize the label. + def label_from_instance(self, obj): + """ + This method is used to convert objects into strings; it's used to + generate the labels for the choices presented by this object. Subclasses + can override this method to customize the display of the choices. + """ + return smart_text(obj) + + def _get_choices(self): + # If self._choices is set, then somebody must have manually set + # the property self.choices. In this case, just return self._choices. + if hasattr(self, '_choices'): + return self._choices + + # Otherwise, execute the QuerySet in self.queryset to determine the + # choices dynamically. Return a fresh ModelChoiceIterator that has not been + # consumed. Note that we're instantiating a new ModelChoiceIterator *each* + # time _get_choices() is called (and, thus, each time self.choices is + # accessed) so that we can ensure the QuerySet has not been consumed. This + # construct might look complicated but it allows for lazy evaluation of + # the queryset. + return ModelChoiceIterator(self) + + choices = property(_get_choices, ChoiceField._set_choices) + + def prepare_value(self, value): + if hasattr(value, '_meta'): + if self.to_field_name: + return value.serializable_value(self.to_field_name) + else: + return value.pk + return super(ModelChoiceField, self).prepare_value(value) + + def to_python(self, value): + if value in self.empty_values: + return None + try: + key = self.to_field_name or 'pk' + value = self.queryset.get(**{key: value}) + except (ValueError, self.queryset.model.DoesNotExist): + raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice') + return value + + def validate(self, value): + return Field.validate(self, value) + + def _has_changed(self, initial, data): + initial_value = initial if initial is not None else '' + data_value = data if data is not None else '' + return force_text(self.prepare_value(initial_value)) != force_text(data_value) + +class ModelMultipleChoiceField(ModelChoiceField): + """A MultipleChoiceField whose choices are a model QuerySet.""" + widget = SelectMultiple + hidden_widget = MultipleHiddenInput + default_error_messages = { + 'list': _('Enter a list of values.'), + 'invalid_choice': _('Select a valid choice. %(value)s is not one of the' + ' available choices.'), + 'invalid_pk_value': _('"%(pk)s" is not a valid value for a primary key.') + } + + def __init__(self, queryset, cache_choices=False, required=True, + widget=None, label=None, initial=None, + help_text='', *args, **kwargs): + super(ModelMultipleChoiceField, self).__init__(queryset, None, + cache_choices, required, widget, label, initial, help_text, + *args, **kwargs) + # Remove this in Django 1.8 + if isinstance(self.widget, SelectMultiple) and not isinstance(self.widget, CheckboxSelectMultiple): + msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.') + self.help_text = string_concat(self.help_text, ' ', msg) + + def to_python(self, value): + if not value: + return [] + to_py = super(ModelMultipleChoiceField, self).to_python + return [to_py(val) for val in value] + + def clean(self, value): + if self.required and not value: + raise ValidationError(self.error_messages['required'], code='required') + elif not self.required and not value: + return self.queryset.none() + if not isinstance(value, (list, tuple)): + raise ValidationError(self.error_messages['list'], code='list') + key = self.to_field_name or 'pk' + for pk in value: + try: + self.queryset.filter(**{key: pk}) + except ValueError: + raise ValidationError( + self.error_messages['invalid_pk_value'], + code='invalid_pk_value', + params={'pk': pk}, + ) + qs = self.queryset.filter(**{'%s__in' % key: value}) + pks = set([force_text(getattr(o, key)) for o in qs]) + for val in value: + if force_text(val) not in pks: + raise ValidationError( + self.error_messages['invalid_choice'], + code='invalid_choice', + params={'value': val}, + ) + # Since this overrides the inherited ModelChoiceField.clean + # we run custom validators here + self.run_validators(value) + return qs + + def prepare_value(self, value): + if (hasattr(value, '__iter__') and + not isinstance(value, six.text_type) and + not hasattr(value, '_meta')): + return [super(ModelMultipleChoiceField, self).prepare_value(v) for v in value] + return super(ModelMultipleChoiceField, self).prepare_value(value) + + 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 self.prepare_value(initial)]) + data_set = set([force_text(value) for value in data]) + return data_set != initial_set + + +def modelform_defines_fields(form_class): + return (form_class is not None and ( + hasattr(form_class, '_meta') and + (form_class._meta.fields is not None or + form_class._meta.exclude is not None) + )) diff --git a/lib/python2.7/site-packages/django/forms/util.py b/lib/python2.7/site-packages/django/forms/util.py new file mode 100644 index 0000000..0a73320 --- /dev/null +++ b/lib/python2.7/site-packages/django/forms/util.py @@ -0,0 +1,103 @@ +from __future__ import unicode_literals + +from django.conf import settings +from django.utils.html import format_html, format_html_join +from django.utils.encoding import force_text, python_2_unicode_compatible +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ +from django.utils import six +import sys + +# Import ValidationError so that it can be imported from this +# module to maintain backwards compatibility. +from django.core.exceptions import ValidationError + +def flatatt(attrs): + """ + Convert a dictionary of attributes to a single string. + The returned string will contain a leading space followed by key="value", + XML-style pairs. It is assumed that the keys do not need to be XML-escaped. + If the passed dictionary is empty, then return an empty string. + + The result is passed through 'mark_safe'. + """ + return format_html_join('', ' {0}="{1}"', sorted(attrs.items())) + +@python_2_unicode_compatible +class ErrorDict(dict): + """ + A collection of errors that knows how to display itself in various formats. + + The dictionary keys are the field names, and the values are the errors. + """ + def __str__(self): + return self.as_ul() + + def as_ul(self): + if not self: return '' + return format_html('<ul class="errorlist">{0}</ul>', + format_html_join('', '<li>{0}{1}</li>', + ((k, force_text(v)) + for k, v in self.items()) + )) + + def as_text(self): + return '\n'.join(['* %s\n%s' % (k, '\n'.join([' * %s' % force_text(i) for i in v])) for k, v in self.items()]) + +@python_2_unicode_compatible +class ErrorList(list): + """ + A collection of errors that knows how to display itself in various formats. + """ + def __str__(self): + return self.as_ul() + + def as_ul(self): + if not self: return '' + return format_html('<ul class="errorlist">{0}</ul>', + format_html_join('', '<li>{0}</li>', + ((force_text(e),) for e in self) + ) + ) + + def as_text(self): + if not self: return '' + return '\n'.join(['* %s' % force_text(e) for e in self]) + + def __repr__(self): + return repr([force_text(e) for e in self]) + +# Utilities for time zone support in DateTimeField et al. + +def from_current_timezone(value): + """ + When time zone support is enabled, convert naive datetimes + entered in the current time zone to aware datetimes. + """ + if settings.USE_TZ and value is not None and timezone.is_naive(value): + current_timezone = timezone.get_current_timezone() + try: + return timezone.make_aware(value, current_timezone) + except Exception: + message = _( + '%(datetime)s couldn\'t be interpreted ' + 'in time zone %(current_timezone)s; it ' + 'may be ambiguous or it may not exist.' + ) + params = {'datetime': value, 'current_timezone': current_timezone} + six.reraise(ValidationError, ValidationError( + message, + code='ambiguous_timezone', + params=params, + ), sys.exc_info()[2]) + return value + +def to_current_timezone(value): + """ + When time zone support is enabled, convert aware datetimes + to naive dateimes in the current time zone for display. + """ + if settings.USE_TZ and value is not None and timezone.is_aware(value): + current_timezone = timezone.get_current_timezone() + return timezone.make_naive(value, current_timezone) + return value diff --git a/lib/python2.7/site-packages/django/forms/widgets.py b/lib/python2.7/site-packages/django/forms/widgets.py new file mode 100644 index 0000000..cf68bac --- /dev/null +++ b/lib/python2.7/site-packages/django/forms/widgets.py @@ -0,0 +1,869 @@ +""" +HTML Widget classes +""" + +from __future__ import absolute_import, unicode_literals + +import copy +from itertools import chain +import warnings + +from django.conf import settings +from django.forms.util import flatatt, to_current_timezone +from django.utils.datastructures import MultiValueDict, MergeDict +from django.utils.html import conditional_escape, format_html +from django.utils.translation import ugettext_lazy +from django.utils.encoding import force_text, python_2_unicode_compatible +from django.utils.safestring import mark_safe +from django.utils import datetime_safe, formats, six +from django.utils.six.moves.urllib.parse import urljoin + +__all__ = ( + 'Media', 'MediaDefiningClass', 'Widget', 'TextInput', + 'EmailInput', 'URLInput', 'NumberInput', 'PasswordInput', + 'HiddenInput', 'MultipleHiddenInput', 'ClearableFileInput', + 'FileInput', 'DateInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput', + 'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', + 'CheckboxSelectMultiple', 'MultiWidget', + 'SplitDateTimeWidget', +) + +MEDIA_TYPES = ('css','js') + +@python_2_unicode_compatible +class Media(object): + def __init__(self, media=None, **kwargs): + if media: + media_attrs = media.__dict__ + else: + media_attrs = kwargs + + self._css = {} + self._js = [] + + for name in MEDIA_TYPES: + getattr(self, 'add_' + name)(media_attrs.get(name, None)) + + # Any leftover attributes must be invalid. + # if media_attrs != {}: + # raise TypeError("'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys())) + + def __str__(self): + return self.render() + + def render(self): + return mark_safe('\n'.join(chain(*[getattr(self, 'render_' + name)() for name in MEDIA_TYPES]))) + + def render_js(self): + return [format_html('<script type="text/javascript" src="{0}"></script>', self.absolute_path(path)) for path in self._js] + + def render_css(self): + # To keep rendering order consistent, we can't just iterate over items(). + # We need to sort the keys, and iterate over the sorted list. + media = sorted(self._css.keys()) + return chain(*[ + [format_html('<link href="{0}" type="text/css" media="{1}" rel="stylesheet" />', self.absolute_path(path), medium) + for path in self._css[medium]] + for medium in media]) + + def absolute_path(self, path, prefix=None): + if path.startswith(('http://', 'https://', '/')): + return path + if prefix is None: + if settings.STATIC_URL is None: + # backwards compatibility + prefix = settings.MEDIA_URL + else: + prefix = settings.STATIC_URL + return urljoin(prefix, path) + + def __getitem__(self, name): + "Returns a Media object that only contains media of the given type" + if name in MEDIA_TYPES: + return Media(**{str(name): getattr(self, '_' + name)}) + raise KeyError('Unknown media type "%s"' % name) + + def add_js(self, data): + if data: + for path in data: + if path not in self._js: + self._js.append(path) + + def add_css(self, data): + if data: + for medium, paths in data.items(): + for path in paths: + if not self._css.get(medium) or path not in self._css[medium]: + self._css.setdefault(medium, []).append(path) + + def __add__(self, other): + combined = Media() + for name in MEDIA_TYPES: + getattr(combined, 'add_' + name)(getattr(self, '_' + name, None)) + getattr(combined, 'add_' + name)(getattr(other, '_' + name, None)) + return combined + +def media_property(cls): + def _media(self): + # Get the media property of the superclass, if it exists + sup_cls = super(cls, self) + try: + base = sup_cls.media + except AttributeError: + base = Media() + + # Get the media definition for this class + definition = getattr(cls, 'Media', None) + if definition: + extend = getattr(definition, 'extend', True) + if extend: + if extend == True: + m = base + else: + m = Media() + for medium in extend: + m = m + base[medium] + return m + Media(definition) + else: + return Media(definition) + else: + return base + return property(_media) + +class MediaDefiningClass(type): + "Metaclass for classes that can have media definitions" + def __new__(cls, name, bases, attrs): + new_class = super(MediaDefiningClass, cls).__new__(cls, name, bases, + attrs) + if 'media' not in attrs: + new_class.media = media_property(new_class) + return new_class + +@python_2_unicode_compatible +class SubWidget(object): + """ + Some widgets are made of multiple HTML elements -- namely, RadioSelect. + This is a class that represents the "inner" HTML element of a widget. + """ + def __init__(self, parent_widget, name, value, attrs, choices): + self.parent_widget = parent_widget + self.name, self.value = name, value + self.attrs, self.choices = attrs, choices + + def __str__(self): + args = [self.name, self.value, self.attrs] + if self.choices: + args.append(self.choices) + return self.parent_widget.render(*args) + +class Widget(six.with_metaclass(MediaDefiningClass)): + is_hidden = False # Determines whether this corresponds to an <input type="hidden">. + needs_multipart_form = False # Determines does this widget need multipart form + is_localized = False + is_required = False + + def __init__(self, attrs=None): + if attrs is not None: + self.attrs = attrs.copy() + else: + self.attrs = {} + + def __deepcopy__(self, memo): + obj = copy.copy(self) + obj.attrs = self.attrs.copy() + memo[id(self)] = obj + return obj + + def subwidgets(self, name, value, attrs=None, choices=()): + """ + Yields all "subwidgets" of this widget. Used only by RadioSelect to + allow template access to individual <input type="radio"> buttons. + + Arguments are the same as for render(). + """ + yield SubWidget(self, name, value, attrs, choices) + + def render(self, name, value, attrs=None): + """ + Returns this Widget rendered as HTML, as a Unicode string. + + The 'value' given is not guaranteed to be valid input, so subclass + implementations should program defensively. + """ + raise NotImplementedError + + def build_attrs(self, extra_attrs=None, **kwargs): + "Helper function for building an attribute dictionary." + attrs = dict(self.attrs, **kwargs) + if extra_attrs: + attrs.update(extra_attrs) + return attrs + + def value_from_datadict(self, data, files, name): + """ + Given a dictionary of data and this widget's name, returns the value + of this widget. Returns None if it's not provided. + """ + return data.get(name, None) + + def id_for_label(self, id_): + """ + Returns the HTML ID attribute of this Widget for use by a <label>, + given the ID of the field. Returns None if no ID is available. + + This hook is necessary because some widgets have multiple HTML + elements and, thus, multiple IDs. In that case, this method should + return an ID value that corresponds to the first ID in the widget's + tags. + """ + return id_ + +class Input(Widget): + """ + Base class for all <input> widgets (except type='checkbox' and + type='radio', which are special). + """ + input_type = None # Subclasses must define this. + + def _format_value(self, value): + if self.is_localized: + return formats.localize_input(value) + return value + + def render(self, name, value, attrs=None): + if value is None: + value = '' + final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) + if value != '': + # Only add the 'value' attribute if a value is non-empty. + final_attrs['value'] = force_text(self._format_value(value)) + return format_html('<input{0} />', flatatt(final_attrs)) + + +class TextInput(Input): + input_type = 'text' + + def __init__(self, attrs=None): + if attrs is not None: + self.input_type = attrs.pop('type', self.input_type) + super(TextInput, self).__init__(attrs) + + +class NumberInput(TextInput): + input_type = 'number' + + +class EmailInput(TextInput): + input_type = 'email' + + +class URLInput(TextInput): + input_type = 'url' + + +class PasswordInput(TextInput): + input_type = 'password' + + def __init__(self, attrs=None, render_value=False): + super(PasswordInput, self).__init__(attrs) + self.render_value = render_value + + def render(self, name, value, attrs=None): + if not self.render_value: value=None + return super(PasswordInput, self).render(name, value, attrs) + +class HiddenInput(Input): + input_type = 'hidden' + is_hidden = True + +class MultipleHiddenInput(HiddenInput): + """ + A widget that handles <input type="hidden"> for fields that have a list + of values. + """ + def __init__(self, attrs=None, choices=()): + super(MultipleHiddenInput, self).__init__(attrs) + # choices can be any iterable + self.choices = choices + + def render(self, name, value, attrs=None, choices=()): + if value is None: value = [] + final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) + id_ = final_attrs.get('id', None) + inputs = [] + for i, v in enumerate(value): + input_attrs = dict(value=force_text(v), **final_attrs) + if id_: + # An ID attribute was given. Add a numeric index as a suffix + # so that the inputs don't all have the same ID attribute. + input_attrs['id'] = '%s_%s' % (id_, i) + inputs.append(format_html('<input{0} />', flatatt(input_attrs))) + return mark_safe('\n'.join(inputs)) + + def value_from_datadict(self, data, files, name): + if isinstance(data, (MultiValueDict, MergeDict)): + return data.getlist(name) + return data.get(name, None) + +class FileInput(Input): + input_type = 'file' + needs_multipart_form = True + + def render(self, name, value, attrs=None): + return super(FileInput, self).render(name, None, attrs=attrs) + + def value_from_datadict(self, data, files, name): + "File widgets take data from FILES, not POST" + return files.get(name, None) + + +FILE_INPUT_CONTRADICTION = object() + +class ClearableFileInput(FileInput): + initial_text = ugettext_lazy('Currently') + input_text = ugettext_lazy('Change') + clear_checkbox_label = ugettext_lazy('Clear') + + template_with_initial = '%(initial_text)s: %(initial)s %(clear_template)s<br />%(input_text)s: %(input)s' + + template_with_clear = '%(clear)s <label for="%(clear_checkbox_id)s">%(clear_checkbox_label)s</label>' + + url_markup_template = '<a href="{0}">{1}</a>' + + def clear_checkbox_name(self, name): + """ + Given the name of the file input, return the name of the clear checkbox + input. + """ + return name + '-clear' + + def clear_checkbox_id(self, name): + """ + Given the name of the clear checkbox input, return the HTML id for it. + """ + return name + '_id' + + def render(self, name, value, attrs=None): + substitutions = { + 'initial_text': self.initial_text, + 'input_text': self.input_text, + 'clear_template': '', + 'clear_checkbox_label': self.clear_checkbox_label, + } + template = '%(input)s' + substitutions['input'] = super(ClearableFileInput, self).render(name, value, attrs) + + if value and hasattr(value, "url"): + template = self.template_with_initial + substitutions['initial'] = format_html(self.url_markup_template, + value.url, + force_text(value)) + if not self.is_required: + checkbox_name = self.clear_checkbox_name(name) + checkbox_id = self.clear_checkbox_id(checkbox_name) + substitutions['clear_checkbox_name'] = conditional_escape(checkbox_name) + substitutions['clear_checkbox_id'] = conditional_escape(checkbox_id) + substitutions['clear'] = CheckboxInput().render(checkbox_name, False, attrs={'id': checkbox_id}) + substitutions['clear_template'] = self.template_with_clear % substitutions + + return mark_safe(template % substitutions) + + def value_from_datadict(self, data, files, name): + upload = super(ClearableFileInput, self).value_from_datadict(data, files, name) + if not self.is_required and CheckboxInput().value_from_datadict( + data, files, self.clear_checkbox_name(name)): + if upload: + # If the user contradicts themselves (uploads a new file AND + # checks the "clear" checkbox), we return a unique marker + # object that FileField will turn into a ValidationError. + return FILE_INPUT_CONTRADICTION + # False signals to clear any existing value, as opposed to just None + return False + return upload + +class Textarea(Widget): + def __init__(self, attrs=None): + # The 'rows' and 'cols' attributes are required for HTML correctness. + default_attrs = {'cols': '40', 'rows': '10'} + if attrs: + default_attrs.update(attrs) + super(Textarea, self).__init__(default_attrs) + + def render(self, name, value, attrs=None): + if value is None: value = '' + final_attrs = self.build_attrs(attrs, name=name) + return format_html('<textarea{0}>\r\n{1}</textarea>', + flatatt(final_attrs), + force_text(value)) + + +class DateInput(TextInput): + def __init__(self, attrs=None, format=None): + super(DateInput, self).__init__(attrs) + if format: + self.format = format + self.manual_format = True + else: + self.format = formats.get_format('DATE_INPUT_FORMATS')[0] + self.manual_format = False + + def _format_value(self, value): + if self.is_localized and not self.manual_format: + return formats.localize_input(value) + elif hasattr(value, 'strftime'): + value = datetime_safe.new_date(value) + return value.strftime(self.format) + return value + + +class DateTimeInput(TextInput): + def __init__(self, attrs=None, format=None): + super(DateTimeInput, self).__init__(attrs) + if format: + self.format = format + self.manual_format = True + else: + self.format = formats.get_format('DATETIME_INPUT_FORMATS')[0] + self.manual_format = False + + def _format_value(self, value): + if self.is_localized and not self.manual_format: + return formats.localize_input(value) + elif hasattr(value, 'strftime'): + value = datetime_safe.new_datetime(value) + return value.strftime(self.format) + return value + + +class TimeInput(TextInput): + def __init__(self, attrs=None, format=None): + super(TimeInput, self).__init__(attrs) + if format: + self.format = format + self.manual_format = True + else: + self.format = formats.get_format('TIME_INPUT_FORMATS')[0] + self.manual_format = False + + def _format_value(self, value): + if self.is_localized and not self.manual_format: + return formats.localize_input(value) + elif hasattr(value, 'strftime'): + return value.strftime(self.format) + return value + + +# Defined at module level so that CheckboxInput is picklable (#17976) +def boolean_check(v): + return not (v is False or v is None or v == '') + + +class CheckboxInput(Widget): + def __init__(self, attrs=None, check_test=None): + super(CheckboxInput, self).__init__(attrs) + # check_test is a callable that takes a value and returns True + # if the checkbox should be checked for that value. + self.check_test = boolean_check if check_test is None else check_test + + def render(self, name, value, attrs=None): + final_attrs = self.build_attrs(attrs, type='checkbox', name=name) + if self.check_test(value): + final_attrs['checked'] = 'checked' + if not (value is True or value is False or value is None or value == ''): + # Only add the 'value' attribute if a value is non-empty. + final_attrs['value'] = force_text(value) + return format_html('<input{0} />', flatatt(final_attrs)) + + def value_from_datadict(self, data, files, name): + if name not in data: + # A missing value means False because HTML form submission does not + # send results for unselected checkboxes. + return False + value = data.get(name) + # Translate true and false strings to boolean values. + values = {'true': True, 'false': False} + if isinstance(value, six.string_types): + value = values.get(value.lower(), value) + return bool(value) + + +class Select(Widget): + allow_multiple_selected = False + + def __init__(self, attrs=None, choices=()): + super(Select, self).__init__(attrs) + # choices can be any iterable, but we may need to render this widget + # multiple times. Thus, collapse it into a list so it can be consumed + # more than once. + self.choices = list(choices) + + def render(self, name, value, attrs=None, choices=()): + if value is None: value = '' + final_attrs = self.build_attrs(attrs, name=name) + output = [format_html('<select{0}>', flatatt(final_attrs))] + options = self.render_options(choices, [value]) + if options: + output.append(options) + output.append('</select>') + return mark_safe('\n'.join(output)) + + def render_option(self, selected_choices, option_value, option_label): + option_value = force_text(option_value) + if option_value in selected_choices: + selected_html = mark_safe(' selected="selected"') + if not self.allow_multiple_selected: + # Only allow for a single selection. + selected_choices.remove(option_value) + else: + selected_html = '' + return format_html('<option value="{0}"{1}>{2}</option>', + option_value, + selected_html, + force_text(option_label)) + + def render_options(self, choices, selected_choices): + # Normalize to strings. + selected_choices = set(force_text(v) for v in selected_choices) + output = [] + for option_value, option_label in chain(self.choices, choices): + if isinstance(option_label, (list, tuple)): + output.append(format_html('<optgroup label="{0}">', force_text(option_value))) + for option in option_label: + output.append(self.render_option(selected_choices, *option)) + output.append('</optgroup>') + else: + output.append(self.render_option(selected_choices, option_value, option_label)) + return '\n'.join(output) + +class NullBooleanSelect(Select): + """ + A Select Widget intended to be used with NullBooleanField. + """ + def __init__(self, attrs=None): + choices = (('1', ugettext_lazy('Unknown')), + ('2', ugettext_lazy('Yes')), + ('3', ugettext_lazy('No'))) + super(NullBooleanSelect, self).__init__(attrs, choices) + + def render(self, name, value, attrs=None, choices=()): + try: + value = {True: '2', False: '3', '2': '2', '3': '3'}[value] + except KeyError: + value = '1' + return super(NullBooleanSelect, self).render(name, value, attrs, choices) + + def value_from_datadict(self, data, files, name): + value = data.get(name, None) + return {'2': True, + True: True, + 'True': True, + '3': False, + 'False': False, + False: False}.get(value, None) + + +class SelectMultiple(Select): + allow_multiple_selected = True + + def render(self, name, value, attrs=None, choices=()): + if value is None: value = [] + final_attrs = self.build_attrs(attrs, name=name) + output = [format_html('<select multiple="multiple"{0}>', flatatt(final_attrs))] + options = self.render_options(choices, value) + if options: + output.append(options) + output.append('</select>') + return mark_safe('\n'.join(output)) + + def value_from_datadict(self, data, files, name): + if isinstance(data, (MultiValueDict, MergeDict)): + return data.getlist(name) + return data.get(name, None) + + +@python_2_unicode_compatible +class ChoiceInput(SubWidget): + """ + An object used by ChoiceFieldRenderer that represents a single + <input type='$input_type'>. + """ + input_type = None # Subclasses must define this + + def __init__(self, name, value, attrs, choice, index): + self.name = name + self.value = value + self.attrs = attrs + self.choice_value = force_text(choice[0]) + self.choice_label = force_text(choice[1]) + self.index = index + + def __str__(self): + return self.render() + + def render(self, name=None, value=None, attrs=None, choices=()): + name = name or self.name + value = value or self.value + attrs = attrs or self.attrs + if 'id' in self.attrs: + label_for = format_html(' for="{0}_{1}"', self.attrs['id'], self.index) + else: + label_for = '' + return format_html('<label{0}>{1} {2}</label>', label_for, self.tag(), self.choice_label) + + def is_checked(self): + return self.value == self.choice_value + + def tag(self): + if 'id' in self.attrs: + self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index) + final_attrs = dict(self.attrs, type=self.input_type, name=self.name, value=self.choice_value) + if self.is_checked(): + final_attrs['checked'] = 'checked' + return format_html('<input{0} />', flatatt(final_attrs)) + + +class RadioChoiceInput(ChoiceInput): + input_type = 'radio' + + def __init__(self, *args, **kwargs): + super(RadioChoiceInput, self).__init__(*args, **kwargs) + self.value = force_text(self.value) + + +class RadioInput(RadioChoiceInput): + def __init__(self, *args, **kwargs): + msg = "RadioInput has been deprecated. Use RadioChoiceInput instead." + warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) + super(RadioInput, self).__init__(*args, **kwargs) + + +class CheckboxChoiceInput(ChoiceInput): + input_type = 'checkbox' + + def __init__(self, *args, **kwargs): + super(CheckboxChoiceInput, self).__init__(*args, **kwargs) + self.value = set(force_text(v) for v in self.value) + + def is_checked(self): + return self.choice_value in self.value + + +@python_2_unicode_compatible +class ChoiceFieldRenderer(object): + """ + An object used by RadioSelect to enable customization of radio widgets. + """ + + choice_input_class = None + + def __init__(self, name, value, attrs, choices): + self.name = name + self.value = value + self.attrs = attrs + self.choices = choices + + def __iter__(self): + for i, choice in enumerate(self.choices): + yield self.choice_input_class(self.name, self.value, self.attrs.copy(), choice, i) + + def __getitem__(self, idx): + choice = self.choices[idx] # Let the IndexError propogate + return self.choice_input_class(self.name, self.value, self.attrs.copy(), choice, idx) + + def __str__(self): + return self.render() + + def render(self): + """ + Outputs a <ul> for this set of choice fields. + If an id was given to the field, it is applied to the <ul> (each + item in the list will get an id of `$id_$i`). + """ + id_ = self.attrs.get('id', None) + start_tag = format_html('<ul id="{0}">', id_) if id_ else '<ul>' + output = [start_tag] + for widget in self: + output.append(format_html('<li>{0}</li>', force_text(widget))) + output.append('</ul>') + return mark_safe('\n'.join(output)) + + +class RadioFieldRenderer(ChoiceFieldRenderer): + choice_input_class = RadioChoiceInput + + +class CheckboxFieldRenderer(ChoiceFieldRenderer): + choice_input_class = CheckboxChoiceInput + + +class RendererMixin(object): + renderer = None # subclasses must define this + _empty_value = None + + def __init__(self, *args, **kwargs): + # Override the default renderer if we were passed one. + renderer = kwargs.pop('renderer', None) + if renderer: + self.renderer = renderer + super(RendererMixin, self).__init__(*args, **kwargs) + + def subwidgets(self, name, value, attrs=None, choices=()): + for widget in self.get_renderer(name, value, attrs, choices): + yield widget + + def get_renderer(self, name, value, attrs=None, choices=()): + """Returns an instance of the renderer.""" + if value is None: + value = self._empty_value + final_attrs = self.build_attrs(attrs) + choices = list(chain(self.choices, choices)) + return self.renderer(name, value, final_attrs, choices) + + def render(self, name, value, attrs=None, choices=()): + return self.get_renderer(name, value, attrs, choices).render() + + def id_for_label(self, id_): + # Widgets using this RendererMixin are made of a collection of + # subwidgets, each with their own <label>, and distinct ID. + # The IDs are made distinct by y "_X" suffix, where X is the zero-based + # index of the choice field. Thus, the label for the main widget should + # reference the first subwidget, hence the "_0" suffix. + if id_: + id_ += '_0' + return id_ + + +class RadioSelect(RendererMixin, Select): + renderer = RadioFieldRenderer + _empty_value = '' + + +class CheckboxSelectMultiple(RendererMixin, SelectMultiple): + renderer = CheckboxFieldRenderer + _empty_value = [] + + +class MultiWidget(Widget): + """ + A widget that is composed of multiple widgets. + + Its render() method is different than other widgets', because it has to + figure out how to split a single value for display in multiple widgets. + The ``value`` argument can be one of two things: + + * A list. + * A normal value (e.g., a string) that has been "compressed" from + a list of values. + + In the second case -- i.e., if the value is NOT a list -- render() will + first "decompress" the value into a list before rendering it. It does so by + calling the decompress() method, which MultiWidget subclasses must + implement. This method takes a single "compressed" value and returns a + list. + + When render() does its HTML rendering, each value in the list is rendered + with the corresponding widget -- the first value is rendered in the first + widget, the second value is rendered in the second widget, etc. + + Subclasses may implement format_output(), which takes the list of rendered + widgets and returns a string of HTML that formats them any way you'd like. + + You'll probably want to use this class with MultiValueField. + """ + def __init__(self, widgets, attrs=None): + self.widgets = [w() if isinstance(w, type) else w for w in widgets] + super(MultiWidget, self).__init__(attrs) + + def render(self, name, value, attrs=None): + if self.is_localized: + for widget in self.widgets: + widget.is_localized = self.is_localized + # value is a list of values, each corresponding to a widget + # in self.widgets. + if not isinstance(value, list): + value = self.decompress(value) + output = [] + final_attrs = self.build_attrs(attrs) + id_ = final_attrs.get('id', None) + for i, widget in enumerate(self.widgets): + try: + widget_value = value[i] + except IndexError: + widget_value = None + if id_: + final_attrs = dict(final_attrs, id='%s_%s' % (id_, i)) + output.append(widget.render(name + '_%s' % i, widget_value, final_attrs)) + return mark_safe(self.format_output(output)) + + def id_for_label(self, id_): + # See the comment for RadioSelect.id_for_label() + if id_: + id_ += '_0' + return id_ + + def value_from_datadict(self, data, files, name): + return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)] + + def format_output(self, rendered_widgets): + """ + Given a list of rendered widgets (as strings), returns a Unicode string + representing the HTML for the whole lot. + + This hook allows you to format the HTML design of the widgets, if + needed. + """ + return ''.join(rendered_widgets) + + def decompress(self, value): + """ + Returns a list of decompressed values for the given compressed value. + The given value can be assumed to be valid, but not necessarily + non-empty. + """ + raise NotImplementedError('Subclasses must implement this method.') + + def _get_media(self): + "Media for a multiwidget is the combination of all media of the subwidgets" + media = Media() + for w in self.widgets: + media = media + w.media + return media + media = property(_get_media) + + def __deepcopy__(self, memo): + obj = super(MultiWidget, self).__deepcopy__(memo) + obj.widgets = copy.deepcopy(self.widgets) + return obj + + @property + def needs_multipart_form(self): + return any(w.needs_multipart_form for w in self.widgets) + + +class SplitDateTimeWidget(MultiWidget): + """ + A Widget that splits datetime input into two <input type="text"> boxes. + """ + + def __init__(self, attrs=None, date_format=None, time_format=None): + widgets = (DateInput(attrs=attrs, format=date_format), + TimeInput(attrs=attrs, format=time_format)) + super(SplitDateTimeWidget, self).__init__(widgets, attrs) + + def decompress(self, value): + if value: + value = to_current_timezone(value) + return [value.date(), value.time().replace(microsecond=0)] + return [None, None] + +class SplitHiddenDateTimeWidget(SplitDateTimeWidget): + """ + A Widget that splits datetime input into two <input type="hidden"> inputs. + """ + is_hidden = True + + def __init__(self, attrs=None, date_format=None, time_format=None): + super(SplitHiddenDateTimeWidget, self).__init__(attrs, date_format, time_format) + for widget in self.widgets: + widget.input_type = 'hidden' + widget.is_hidden = True |