mirror of
				https://github.com/django/django.git
				synced 2025-10-28 08:06:09 +00:00 
			
		
		
		
	Fixed #7741: django.newforms is now django.forms. This is obviously a backwards-incompatible change. There's a warning upon import of django.newforms itself, but deeper imports will raise errors.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7971 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
		| @@ -1 +1,17 @@ | ||||
| from django.oldforms import * | ||||
| """ | ||||
| Django validation and HTML form handling. | ||||
|  | ||||
| TODO: | ||||
|     Default value for field | ||||
|     Field labels | ||||
|     Nestable Forms | ||||
|     FatalValidationError -- short-circuits all other validators on a form | ||||
|     ValidationWarning | ||||
|     "This form field requires foo.js" and form.js_includes() | ||||
| """ | ||||
|  | ||||
| from util import ValidationError | ||||
| from widgets import * | ||||
| from fields import * | ||||
| from forms import * | ||||
| from models import * | ||||
|   | ||||
							
								
								
									
										1
									
								
								django/forms/extras/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								django/forms/extras/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| from widgets import * | ||||
							
								
								
									
										79
									
								
								django/forms/extras/widgets.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								django/forms/extras/widgets.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| """ | ||||
| Extra HTML Widget classes | ||||
| """ | ||||
|  | ||||
| import datetime | ||||
| import re | ||||
|  | ||||
| from django.forms.widgets import Widget, Select | ||||
| from django.utils.dates import MONTHS | ||||
| from django.utils.safestring import mark_safe | ||||
|  | ||||
| __all__ = ('SelectDateWidget',) | ||||
|  | ||||
| RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$') | ||||
|  | ||||
| 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. | ||||
|     """ | ||||
|     month_field = '%s_month' | ||||
|     day_field = '%s_day' | ||||
|     year_field = '%s_year' | ||||
|  | ||||
|     def __init__(self, attrs=None, years=None): | ||||
|         # years is an optional list/tuple of years to use in the "year" select box. | ||||
|         self.attrs = attrs or {} | ||||
|         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, basestring): | ||||
|                 match = RE_DATE.match(value) | ||||
|                 if match: | ||||
|                     year_val, month_val, day_val = [int(v) for v in match.groups()] | ||||
|  | ||||
|         output = [] | ||||
|  | ||||
|         if 'id' in self.attrs: | ||||
|             id_ = self.attrs['id'] | ||||
|         else: | ||||
|             id_ = 'id_%s' % name | ||||
|  | ||||
|         month_choices = MONTHS.items() | ||||
|         month_choices.sort() | ||||
|         local_attrs = self.build_attrs(id=self.month_field % id_) | ||||
|         select_html = Select(choices=month_choices).render(self.month_field % name, month_val, local_attrs) | ||||
|         output.append(select_html) | ||||
|  | ||||
|         day_choices = [(i, i) for i in range(1, 32)] | ||||
|         local_attrs['id'] = self.day_field % id_ | ||||
|         select_html = Select(choices=day_choices).render(self.day_field % name, day_val, local_attrs) | ||||
|         output.append(select_html) | ||||
|  | ||||
|         year_choices = [(i, i) for i in self.years] | ||||
|         local_attrs['id'] = self.year_field % id_ | ||||
|         select_html = Select(choices=year_choices).render(self.year_field % name, year_val, local_attrs) | ||||
|         output.append(select_html) | ||||
|  | ||||
|         return mark_safe(u'\n'.join(output)) | ||||
|  | ||||
|     def id_for_label(self, id_): | ||||
|         return '%s_month' % id_ | ||||
|     id_for_label = classmethod(id_for_label) | ||||
|  | ||||
|     def value_from_datadict(self, data, files, name): | ||||
|         y, m, d = data.get(self.year_field % name), data.get(self.month_field % name), data.get(self.day_field % name) | ||||
|         if y and m and d: | ||||
|             return '%s-%s-%s' % (y, m, d) | ||||
|         return data.get(name, None) | ||||
							
								
								
									
										812
									
								
								django/forms/fields.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										812
									
								
								django/forms/fields.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,812 @@ | ||||
| """ | ||||
| Field classes. | ||||
| """ | ||||
|  | ||||
| import copy | ||||
| import datetime | ||||
| import os | ||||
| import re | ||||
| import time | ||||
| try: | ||||
|     from cStringIO import StringIO | ||||
| except ImportError: | ||||
|     from StringIO import StringIO | ||||
|  | ||||
| # Python 2.3 fallbacks | ||||
| try: | ||||
|     from decimal import Decimal, DecimalException | ||||
| except ImportError: | ||||
|     from django.utils._decimal import Decimal, DecimalException | ||||
| try: | ||||
|     set | ||||
| except NameError: | ||||
|     from sets import Set as set | ||||
|  | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| from django.utils.encoding import StrAndUnicode, smart_unicode, smart_str | ||||
|  | ||||
| from util import ErrorList, ValidationError | ||||
| from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput | ||||
| from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile | ||||
|  | ||||
| __all__ = ( | ||||
|     'Field', 'CharField', 'IntegerField', | ||||
|     'DEFAULT_DATE_INPUT_FORMATS', 'DateField', | ||||
|     'DEFAULT_TIME_INPUT_FORMATS', 'TimeField', | ||||
|     'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', | ||||
|     'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', | ||||
|     'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField', | ||||
|     'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', | ||||
|     'SplitDateTimeField', 'IPAddressField', 'FilePathField', | ||||
| ) | ||||
|  | ||||
| # These values, if given to to_python(), will trigger the self.required check. | ||||
| EMPTY_VALUES = (None, '') | ||||
|  | ||||
|  | ||||
| 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_error_messages = { | ||||
|         'required': _(u'This field is required.'), | ||||
|         'invalid': _(u'Enter a valid value.'), | ||||
|     } | ||||
|  | ||||
|     # 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=None, error_messages=None): | ||||
|         # 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. | ||||
|         if label is not None: | ||||
|             label = smart_unicode(label) | ||||
|         self.required, self.label, self.initial = required, label, initial | ||||
|         self.help_text = smart_unicode(help_text or '') | ||||
|         widget = widget or self.widget | ||||
|         if isinstance(widget, type): | ||||
|             widget = widget() | ||||
|  | ||||
|         # 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 | ||||
|  | ||||
|         def set_class_error_messages(messages, klass): | ||||
|             for base_class in klass.__bases__: | ||||
|                 set_class_error_messages(messages, base_class) | ||||
|             messages.update(getattr(klass, 'default_error_messages', {})) | ||||
|  | ||||
|         messages = {} | ||||
|         set_class_error_messages(messages, self.__class__) | ||||
|         messages.update(error_messages or {}) | ||||
|         self.error_messages = messages | ||||
|  | ||||
|     def clean(self, value): | ||||
|         """ | ||||
|         Validates the given value and returns its "cleaned" value as an | ||||
|         appropriate Python object. | ||||
|  | ||||
|         Raises ValidationError for any errors. | ||||
|         """ | ||||
|         if self.required and value in EMPTY_VALUES: | ||||
|             raise ValidationError(self.error_messages['required']) | ||||
|         return value | ||||
|  | ||||
|     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 __deepcopy__(self, memo): | ||||
|         result = copy.copy(self) | ||||
|         memo[id(self)] = result | ||||
|         result.widget = copy.deepcopy(self.widget, memo) | ||||
|         return result | ||||
|  | ||||
| class CharField(Field): | ||||
|     default_error_messages = { | ||||
|         'max_length': _(u'Ensure this value has at most %(max)d characters (it has %(length)d).'), | ||||
|         'min_length': _(u'Ensure this value has at least %(min)d characters (it has %(length)d).'), | ||||
|     } | ||||
|  | ||||
|     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) | ||||
|  | ||||
|     def clean(self, value): | ||||
|         "Validates max_length and min_length. Returns a Unicode object." | ||||
|         super(CharField, self).clean(value) | ||||
|         if value in EMPTY_VALUES: | ||||
|             return u'' | ||||
|         value = smart_unicode(value) | ||||
|         value_length = len(value) | ||||
|         if self.max_length is not None and value_length > self.max_length: | ||||
|             raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length}) | ||||
|         if self.min_length is not None and value_length < self.min_length: | ||||
|             raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length}) | ||||
|         return value | ||||
|  | ||||
|     def widget_attrs(self, widget): | ||||
|         if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)): | ||||
|             # The HTML attribute is maxlength, not max_length. | ||||
|             return {'maxlength': str(self.max_length)} | ||||
|  | ||||
| class IntegerField(Field): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a whole number.'), | ||||
|         'max_value': _(u'Ensure this value is less than or equal to %s.'), | ||||
|         'min_value': _(u'Ensure this value is greater than or equal to %s.'), | ||||
|     } | ||||
|  | ||||
|     def __init__(self, max_value=None, min_value=None, *args, **kwargs): | ||||
|         self.max_value, self.min_value = max_value, min_value | ||||
|         super(IntegerField, self).__init__(*args, **kwargs) | ||||
|  | ||||
|     def clean(self, value): | ||||
|         """ | ||||
|         Validates that int() can be called on the input. Returns the result | ||||
|         of int(). Returns None for empty values. | ||||
|         """ | ||||
|         super(IntegerField, self).clean(value) | ||||
|         if value in EMPTY_VALUES: | ||||
|             return None | ||||
|         try: | ||||
|             value = int(str(value)) | ||||
|         except (ValueError, TypeError): | ||||
|             raise ValidationError(self.error_messages['invalid']) | ||||
|         if self.max_value is not None and value > self.max_value: | ||||
|             raise ValidationError(self.error_messages['max_value'] % self.max_value) | ||||
|         if self.min_value is not None and value < self.min_value: | ||||
|             raise ValidationError(self.error_messages['min_value'] % self.min_value) | ||||
|         return value | ||||
|  | ||||
| class FloatField(Field): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a number.'), | ||||
|         'max_value': _(u'Ensure this value is less than or equal to %s.'), | ||||
|         'min_value': _(u'Ensure this value is greater than or equal to %s.'), | ||||
|     } | ||||
|  | ||||
|     def __init__(self, max_value=None, min_value=None, *args, **kwargs): | ||||
|         self.max_value, self.min_value = max_value, min_value | ||||
|         Field.__init__(self, *args, **kwargs) | ||||
|  | ||||
|     def clean(self, value): | ||||
|         """ | ||||
|         Validates that float() can be called on the input. Returns a float. | ||||
|         Returns None for empty values. | ||||
|         """ | ||||
|         super(FloatField, self).clean(value) | ||||
|         if not self.required and value in EMPTY_VALUES: | ||||
|             return None | ||||
|         try: | ||||
|             value = float(value) | ||||
|         except (ValueError, TypeError): | ||||
|             raise ValidationError(self.error_messages['invalid']) | ||||
|         if self.max_value is not None and value > self.max_value: | ||||
|             raise ValidationError(self.error_messages['max_value'] % self.max_value) | ||||
|         if self.min_value is not None and value < self.min_value: | ||||
|             raise ValidationError(self.error_messages['min_value'] % self.min_value) | ||||
|         return value | ||||
|  | ||||
| class DecimalField(Field): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a number.'), | ||||
|         'max_value': _(u'Ensure this value is less than or equal to %s.'), | ||||
|         'min_value': _(u'Ensure this value is greater than or equal to %s.'), | ||||
|         'max_digits': _('Ensure that there are no more than %s digits in total.'), | ||||
|         'max_decimal_places': _('Ensure that there are no more than %s decimal places.'), | ||||
|         'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.') | ||||
|     } | ||||
|  | ||||
|     def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): | ||||
|         self.max_value, self.min_value = max_value, min_value | ||||
|         self.max_digits, self.decimal_places = max_digits, decimal_places | ||||
|         Field.__init__(self, *args, **kwargs) | ||||
|  | ||||
|     def clean(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. | ||||
|         """ | ||||
|         super(DecimalField, self).clean(value) | ||||
|         if not self.required and value in EMPTY_VALUES: | ||||
|             return None | ||||
|         value = smart_str(value).strip() | ||||
|         try: | ||||
|             value = Decimal(value) | ||||
|         except DecimalException: | ||||
|             raise ValidationError(self.error_messages['invalid']) | ||||
|         pieces = str(value).lstrip("-").split('.') | ||||
|         decimals = (len(pieces) == 2) and len(pieces[1]) or 0 | ||||
|         digits = len(pieces[0]) | ||||
|         if self.max_value is not None and value > self.max_value: | ||||
|             raise ValidationError(self.error_messages['max_value'] % self.max_value) | ||||
|         if self.min_value is not None and value < self.min_value: | ||||
|             raise ValidationError(self.error_messages['min_value'] % self.min_value) | ||||
|         if self.max_digits is not None and (digits + decimals) > self.max_digits: | ||||
|             raise ValidationError(self.error_messages['max_digits'] % self.max_digits) | ||||
|         if self.decimal_places is not None and decimals > self.decimal_places: | ||||
|             raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places) | ||||
|         if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places): | ||||
|             raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) | ||||
|         return value | ||||
|  | ||||
| DEFAULT_DATE_INPUT_FORMATS = ( | ||||
|     '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' | ||||
|     '%b %d %Y', '%b %d, %Y',            # 'Oct 25 2006', 'Oct 25, 2006' | ||||
|     '%d %b %Y', '%d %b, %Y',            # '25 Oct 2006', '25 Oct, 2006' | ||||
|     '%B %d %Y', '%B %d, %Y',            # 'October 25 2006', 'October 25, 2006' | ||||
|     '%d %B %Y', '%d %B, %Y',            # '25 October 2006', '25 October, 2006' | ||||
| ) | ||||
|  | ||||
| class DateField(Field): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a valid date.'), | ||||
|     } | ||||
|  | ||||
|     def __init__(self, input_formats=None, *args, **kwargs): | ||||
|         super(DateField, self).__init__(*args, **kwargs) | ||||
|         self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS | ||||
|  | ||||
|     def clean(self, value): | ||||
|         """ | ||||
|         Validates that the input can be converted to a date. Returns a Python | ||||
|         datetime.date object. | ||||
|         """ | ||||
|         super(DateField, self).clean(value) | ||||
|         if value in EMPTY_VALUES: | ||||
|             return None | ||||
|         if isinstance(value, datetime.datetime): | ||||
|             return value.date() | ||||
|         if isinstance(value, datetime.date): | ||||
|             return value | ||||
|         for format in self.input_formats: | ||||
|             try: | ||||
|                 return datetime.date(*time.strptime(value, format)[:3]) | ||||
|             except ValueError: | ||||
|                 continue | ||||
|         raise ValidationError(self.error_messages['invalid']) | ||||
|  | ||||
| DEFAULT_TIME_INPUT_FORMATS = ( | ||||
|     '%H:%M:%S',     # '14:30:59' | ||||
|     '%H:%M',        # '14:30' | ||||
| ) | ||||
|  | ||||
| class TimeField(Field): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a valid time.') | ||||
|     } | ||||
|  | ||||
|     def __init__(self, input_formats=None, *args, **kwargs): | ||||
|         super(TimeField, self).__init__(*args, **kwargs) | ||||
|         self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS | ||||
|  | ||||
|     def clean(self, value): | ||||
|         """ | ||||
|         Validates that the input can be converted to a time. Returns a Python | ||||
|         datetime.time object. | ||||
|         """ | ||||
|         super(TimeField, self).clean(value) | ||||
|         if value in EMPTY_VALUES: | ||||
|             return None | ||||
|         if isinstance(value, datetime.time): | ||||
|             return value | ||||
|         for format in self.input_formats: | ||||
|             try: | ||||
|                 return datetime.time(*time.strptime(value, format)[3:6]) | ||||
|             except ValueError: | ||||
|                 continue | ||||
|         raise ValidationError(self.error_messages['invalid']) | ||||
|  | ||||
| DEFAULT_DATETIME_INPUT_FORMATS = ( | ||||
|     '%Y-%m-%d %H:%M:%S',     # '2006-10-25 14:30:59' | ||||
|     '%Y-%m-%d %H:%M',        # '2006-10-25 14:30' | ||||
|     '%Y-%m-%d',              # '2006-10-25' | ||||
|     '%m/%d/%Y %H:%M:%S',     # '10/25/2006 14:30:59' | ||||
|     '%m/%d/%Y %H:%M',        # '10/25/2006 14:30' | ||||
|     '%m/%d/%Y',              # '10/25/2006' | ||||
|     '%m/%d/%y %H:%M:%S',     # '10/25/06 14:30:59' | ||||
|     '%m/%d/%y %H:%M',        # '10/25/06 14:30' | ||||
|     '%m/%d/%y',              # '10/25/06' | ||||
| ) | ||||
|  | ||||
| class DateTimeField(Field): | ||||
|     widget = DateTimeInput | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a valid date/time.'), | ||||
|     } | ||||
|  | ||||
|     def __init__(self, input_formats=None, *args, **kwargs): | ||||
|         super(DateTimeField, self).__init__(*args, **kwargs) | ||||
|         self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS | ||||
|  | ||||
|     def clean(self, value): | ||||
|         """ | ||||
|         Validates that the input can be converted to a datetime. Returns a | ||||
|         Python datetime.datetime object. | ||||
|         """ | ||||
|         super(DateTimeField, self).clean(value) | ||||
|         if value in EMPTY_VALUES: | ||||
|             return None | ||||
|         if isinstance(value, datetime.datetime): | ||||
|             return value | ||||
|         if isinstance(value, datetime.date): | ||||
|             return datetime.datetime(value.year, value.month, value.day) | ||||
|         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']) | ||||
|             value = '%s %s' % tuple(value) | ||||
|         for format in self.input_formats: | ||||
|             try: | ||||
|                 return datetime.datetime(*time.strptime(value, format)[:6]) | ||||
|             except ValueError: | ||||
|                 continue | ||||
|         raise ValidationError(self.error_messages['invalid']) | ||||
|  | ||||
| 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) | ||||
|         if isinstance(regex, basestring): | ||||
|             regex = re.compile(regex) | ||||
|         self.regex = regex | ||||
|  | ||||
|     def clean(self, value): | ||||
|         """ | ||||
|         Validates that the input matches the regular expression. Returns a | ||||
|         Unicode object. | ||||
|         """ | ||||
|         value = super(RegexField, self).clean(value) | ||||
|         if value == u'': | ||||
|             return value | ||||
|         if not self.regex.search(value): | ||||
|             raise ValidationError(self.error_messages['invalid']) | ||||
|         return value | ||||
|  | ||||
| email_re = re.compile( | ||||
|     r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom | ||||
|     r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string | ||||
|     r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain | ||||
|  | ||||
| class EmailField(RegexField): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a valid e-mail address.'), | ||||
|     } | ||||
|  | ||||
|     def __init__(self, max_length=None, min_length=None, *args, **kwargs): | ||||
|         RegexField.__init__(self, email_re, max_length, min_length, *args, | ||||
|                             **kwargs) | ||||
|  | ||||
| try: | ||||
|     from django.conf import settings | ||||
|     URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT | ||||
| except ImportError: | ||||
|     # It's OK if Django settings aren't configured. | ||||
|     URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' | ||||
|  | ||||
|  | ||||
| class FileField(Field): | ||||
|     widget = FileInput | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u"No file was submitted. Check the encoding type on the form."), | ||||
|         'missing': _(u"No file was submitted."), | ||||
|         'empty': _(u"The submitted file is empty."), | ||||
|     } | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(FileField, self).__init__(*args, **kwargs) | ||||
|  | ||||
|     def clean(self, data, initial=None): | ||||
|         super(FileField, self).clean(initial or data) | ||||
|         if not self.required and data in EMPTY_VALUES: | ||||
|             return None | ||||
|         elif not data and initial: | ||||
|             return initial | ||||
|  | ||||
|         if isinstance(data, dict): | ||||
|             # We warn once, then support both ways below. | ||||
|             import warnings | ||||
|             warnings.warn( | ||||
|                 message = "Representing uploaded files as dictionaries is deprecated. Use django.core.files.uploadedfile.SimpleUploadedFile instead.", | ||||
|                 category = DeprecationWarning, | ||||
|                 stacklevel = 2 | ||||
|             ) | ||||
|             data = UploadedFile(data['filename'], data['content']) | ||||
|  | ||||
|         try: | ||||
|             file_name = data.name | ||||
|             file_size = data.size | ||||
|         except AttributeError: | ||||
|             raise ValidationError(self.error_messages['invalid']) | ||||
|  | ||||
|         if not file_name: | ||||
|             raise ValidationError(self.error_messages['invalid']) | ||||
|         if not file_size: | ||||
|             raise ValidationError(self.error_messages['empty']) | ||||
|  | ||||
|         return data | ||||
|  | ||||
| class ImageField(FileField): | ||||
|     default_error_messages = { | ||||
|         'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."), | ||||
|     } | ||||
|  | ||||
|     def clean(self, data, initial=None): | ||||
|         """ | ||||
|         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).clean(data, initial) | ||||
|         if f is None: | ||||
|             return None | ||||
|         elif not data and initial: | ||||
|             return initial | ||||
|         from PIL import Image | ||||
|  | ||||
|         # We need to get a file object for PIL. 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 = StringIO(data.read()) | ||||
|             else: | ||||
|                 file = StringIO(data['content']) | ||||
|  | ||||
|         try: | ||||
|             # load() is the only method that can spot a truncated JPEG, | ||||
|             #  but it cannot be called sanely after verify() | ||||
|             trial_image = Image.open(file) | ||||
|             trial_image.load() | ||||
|  | ||||
|             # Since we're about to use the file again we have to reset the | ||||
|             # file object if possible. | ||||
|             if hasattr(file, 'reset'): | ||||
|                 file.reset() | ||||
|  | ||||
|             # verify() is the only method that can spot a corrupt PNG, | ||||
|             #  but it must be called immediately after the constructor | ||||
|             trial_image = Image.open(file) | ||||
|             trial_image.verify() | ||||
|         except Exception: # Python Imaging Library doesn't recognize it as an image | ||||
|             raise ValidationError(self.error_messages['invalid_image']) | ||||
|         if hasattr(f, 'seek') and callable(f.seek): | ||||
|             f.seek(0) | ||||
|         return f | ||||
|  | ||||
| url_re = re.compile( | ||||
|     r'^https?://' # http:// or https:// | ||||
|     r'(?:(?:[A-Z0-9-]+\.)+[A-Z]{2,6}|' #domain... | ||||
|     r'localhost|' #localhost... | ||||
|     r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip | ||||
|     r'(?::\d+)?' # optional port | ||||
|     r'(?:/?|/\S+)$', re.IGNORECASE) | ||||
|  | ||||
| class URLField(RegexField): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a valid URL.'), | ||||
|         'invalid_link': _(u'This URL appears to be a broken link.'), | ||||
|     } | ||||
|  | ||||
|     def __init__(self, max_length=None, min_length=None, verify_exists=False, | ||||
|             validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs): | ||||
|         super(URLField, self).__init__(url_re, max_length, min_length, *args, | ||||
|                                        **kwargs) | ||||
|         self.verify_exists = verify_exists | ||||
|         self.user_agent = validator_user_agent | ||||
|  | ||||
|     def clean(self, value): | ||||
|         # If no URL scheme given, assume http:// | ||||
|         if value and '://' not in value: | ||||
|             value = u'http://%s' % value | ||||
|         value = super(URLField, self).clean(value) | ||||
|         if value == u'': | ||||
|             return value | ||||
|         if self.verify_exists: | ||||
|             import urllib2 | ||||
|             headers = { | ||||
|                 "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", | ||||
|                 "Accept-Language": "en-us,en;q=0.5", | ||||
|                 "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", | ||||
|                 "Connection": "close", | ||||
|                 "User-Agent": self.user_agent, | ||||
|             } | ||||
|             try: | ||||
|                 req = urllib2.Request(value, None, headers) | ||||
|                 u = urllib2.urlopen(req) | ||||
|             except ValueError: | ||||
|                 raise ValidationError(self.error_messages['invalid']) | ||||
|             except: # urllib2.URLError, httplib.InvalidURL, etc. | ||||
|                 raise ValidationError(self.error_messages['invalid_link']) | ||||
|         return value | ||||
|  | ||||
| class BooleanField(Field): | ||||
|     widget = CheckboxInput | ||||
|  | ||||
|     def clean(self, value): | ||||
|         """Returns a Python boolean object.""" | ||||
|         # Explicitly check for the string 'False', which is what a hidden field | ||||
|         # will submit for False. Because bool("True") == True, we don't need to | ||||
|         # handle that explicitly. | ||||
|         if value == 'False': | ||||
|             value = False | ||||
|         else: | ||||
|             value = bool(value) | ||||
|         super(BooleanField, self).clean(value) | ||||
|         if not value and self.required: | ||||
|             raise ValidationError(self.error_messages['required']) | ||||
|         return value | ||||
|  | ||||
| class NullBooleanField(BooleanField): | ||||
|     """ | ||||
|     A field whose valid values are None, True and False. Invalid values are | ||||
|     cleaned to None. | ||||
|     """ | ||||
|     widget = NullBooleanSelect | ||||
|  | ||||
|     def clean(self, value): | ||||
|         return {True: True, False: False}.get(value, None) | ||||
|  | ||||
| class ChoiceField(Field): | ||||
|     widget = Select | ||||
|     default_error_messages = { | ||||
|         'invalid_choice': _(u'Select a valid choice. That choice is not one of the available choices.'), | ||||
|     } | ||||
|  | ||||
|     def __init__(self, choices=(), required=True, widget=None, label=None, | ||||
|                  initial=None, help_text=None, *args, **kwargs): | ||||
|         super(ChoiceField, self).__init__(required, widget, label, initial, | ||||
|                                           help_text, *args, **kwargs) | ||||
|         self.choices = choices | ||||
|  | ||||
|     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 clean(self, value): | ||||
|         """ | ||||
|         Validates that the input is in self.choices. | ||||
|         """ | ||||
|         value = super(ChoiceField, self).clean(value) | ||||
|         if value in EMPTY_VALUES: | ||||
|             value = u'' | ||||
|         value = smart_unicode(value) | ||||
|         if value == u'': | ||||
|             return value | ||||
|         valid_values = set([smart_unicode(k) for k, v in self.choices]) | ||||
|         if value not in valid_values: | ||||
|             raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) | ||||
|         return value | ||||
|  | ||||
| class MultipleChoiceField(ChoiceField): | ||||
|     hidden_widget = MultipleHiddenInput | ||||
|     widget = SelectMultiple | ||||
|     default_error_messages = { | ||||
|         'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'), | ||||
|         'invalid_list': _(u'Enter a list of values.'), | ||||
|     } | ||||
|  | ||||
|     def clean(self, value): | ||||
|         """ | ||||
|         Validates that the input is a list or tuple. | ||||
|         """ | ||||
|         if self.required and not value: | ||||
|             raise ValidationError(self.error_messages['required']) | ||||
|         elif not self.required and not value: | ||||
|             return [] | ||||
|         if not isinstance(value, (list, tuple)): | ||||
|             raise ValidationError(self.error_messages['invalid_list']) | ||||
|         new_value = [smart_unicode(val) for val in value] | ||||
|         # Validate that each value in the value list is in self.choices. | ||||
|         valid_values = set([smart_unicode(k) for k, v in self.choices]) | ||||
|         for val in new_value: | ||||
|             if val not in valid_values: | ||||
|                 raise ValidationError(self.error_messages['invalid_choice'] % {'value': val}) | ||||
|         return new_value | ||||
|  | ||||
| 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': _(u'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 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 EMPTY_VALUES]: | ||||
|                 if self.required: | ||||
|                     raise ValidationError(self.error_messages['required']) | ||||
|                 else: | ||||
|                     return self.compress([]) | ||||
|         else: | ||||
|             raise ValidationError(self.error_messages['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 EMPTY_VALUES: | ||||
|                 raise ValidationError(self.error_messages['required']) | ||||
|             try: | ||||
|                 clean_data.append(field.clean(field_value)) | ||||
|             except ValidationError, 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.messages) | ||||
|         if errors: | ||||
|             raise ValidationError(errors) | ||||
|         return self.compress(clean_data) | ||||
|  | ||||
|     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.') | ||||
|  | ||||
| class FilePathField(ChoiceField): | ||||
|     def __init__(self, path, match=None, recursive=False, required=True, | ||||
|                  widget=Select, label=None, initial=None, help_text=None, | ||||
|                  *args, **kwargs): | ||||
|         self.path, self.match, self.recursive = path, match, recursive | ||||
|         super(FilePathField, self).__init__(choices=(), required=required, | ||||
|             widget=widget, label=label, initial=initial, help_text=help_text, | ||||
|             *args, **kwargs) | ||||
|         self.choices = [] | ||||
|         if self.match is not None: | ||||
|             self.match_re = re.compile(self.match) | ||||
|         if recursive: | ||||
|             for root, dirs, files in os.walk(self.path): | ||||
|                 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))) | ||||
|         else: | ||||
|             try: | ||||
|                 for f in os.listdir(self.path): | ||||
|                     full_file = os.path.join(self.path, f) | ||||
|                     if os.path.isfile(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): | ||||
|     default_error_messages = { | ||||
|         'invalid_date': _(u'Enter a valid date.'), | ||||
|         'invalid_time': _(u'Enter a valid time.'), | ||||
|     } | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         errors = self.default_error_messages.copy() | ||||
|         if 'error_messages' in kwargs: | ||||
|             errors.update(kwargs['error_messages']) | ||||
|         fields = ( | ||||
|             DateField(error_messages={'invalid': errors['invalid_date']}), | ||||
|             TimeField(error_messages={'invalid': errors['invalid_time']}), | ||||
|         ) | ||||
|         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 EMPTY_VALUES: | ||||
|                 raise ValidationError(self.error_messages['invalid_date']) | ||||
|             if data_list[1] in EMPTY_VALUES: | ||||
|                 raise ValidationError(self.error_messages['invalid_time']) | ||||
|             return datetime.datetime.combine(*data_list) | ||||
|         return None | ||||
|  | ||||
| ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') | ||||
|  | ||||
| class IPAddressField(RegexField): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a valid IPv4 address.'), | ||||
|     } | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs) | ||||
							
								
								
									
										396
									
								
								django/forms/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										396
									
								
								django/forms/forms.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,396 @@ | ||||
| """ | ||||
| Form classes | ||||
| """ | ||||
|  | ||||
| from copy import deepcopy | ||||
|  | ||||
| from django.utils.datastructures import SortedDict | ||||
| from django.utils.html import escape | ||||
| from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode | ||||
| from django.utils.safestring import mark_safe | ||||
|  | ||||
| from fields import Field, FileField | ||||
| from widgets import Media, media_property, TextInput, Textarea | ||||
| from util import flatatt, ErrorDict, ErrorList, ValidationError | ||||
|  | ||||
| __all__ = ('BaseForm', 'Form') | ||||
|  | ||||
| NON_FIELD_ERRORS = '__all__' | ||||
|  | ||||
| def pretty_name(name): | ||||
|     "Converts 'first_name' to 'First name'" | ||||
|     name = name[0].upper() + name[1:] | ||||
|     return name.replace('_', ' ') | ||||
|  | ||||
| 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 metclasses. | ||||
|  | ||||
|     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 attrs.items() if isinstance(obj, Field)] | ||||
|     fields.sort(lambda x, y: cmp(x[1].creation_counter, y[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 = base.base_fields.items() + fields | ||||
|     else: | ||||
|         for base in bases[::-1]: | ||||
|             if hasattr(base, 'declared_fields'): | ||||
|                 fields = base.declared_fields.items() + 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 | ||||
|  | ||||
| class BaseForm(StrAndUnicode): | ||||
|     # 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=':', | ||||
|                  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 | ||||
|         self.label_suffix = label_suffix | ||||
|         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 = deepcopy(self.base_fields) | ||||
|  | ||||
|     def __unicode__(self): | ||||
|         return self.as_table() | ||||
|  | ||||
|     def __iter__(self): | ||||
|         for name, field in self.fields.items(): | ||||
|             yield BoundField(self, field, 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) | ||||
|  | ||||
|     def _get_errors(self): | ||||
|         "Returns an ErrorDict for the data provided for the form" | ||||
|         if self._errors is None: | ||||
|             self.full_clean() | ||||
|         return self._errors | ||||
|     errors = property(_get_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 self.prefix and ('%s-%s' % (self.prefix, field_name)) or 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(): | ||||
|             bf = BoundField(self, field, name) | ||||
|             bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable. | ||||
|             if bf.is_hidden: | ||||
|                 if bf_errors: | ||||
|                     top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) | ||||
|                 hidden_fields.append(unicode(bf)) | ||||
|             else: | ||||
|                 if errors_on_separate_row and bf_errors: | ||||
|                     output.append(error_row % force_unicode(bf_errors)) | ||||
|                 if bf.label: | ||||
|                     label = escape(force_unicode(bf.label)) | ||||
|                     # Only add the suffix if the label does not end in | ||||
|                     # punctuation. | ||||
|                     if self.label_suffix: | ||||
|                         if label[-1] not in ':?.!': | ||||
|                             label += self.label_suffix | ||||
|                     label = bf.label_tag(label) or '' | ||||
|                 else: | ||||
|                     label = '' | ||||
|                 if field.help_text: | ||||
|                     help_text = help_text_html % force_unicode(field.help_text) | ||||
|                 else: | ||||
|                     help_text = u'' | ||||
|                 output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text}) | ||||
|         if top_errors: | ||||
|             output.insert(0, error_row % force_unicode(top_errors)) | ||||
|         if hidden_fields: # Insert any hidden fields in the last row. | ||||
|             str_hidden = u''.join(hidden_fields) | ||||
|             if output: | ||||
|                 last_row = output[-1] | ||||
|                 # Chop off the trailing row_ender (e.g. '</td></tr>') and | ||||
|                 # insert the hidden fields. | ||||
|                 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(u'\n'.join(output)) | ||||
|  | ||||
|     def as_table(self): | ||||
|         "Returns this form rendered as HTML <tr>s -- excluding the <table></table>." | ||||
|         return self._html_output(u'<tr><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', u'<br />%s', False) | ||||
|  | ||||
|     def as_ul(self): | ||||
|         "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>." | ||||
|         return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False) | ||||
|  | ||||
|     def as_p(self): | ||||
|         "Returns this form rendered as HTML <p>s." | ||||
|         return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', 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 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 | ||||
|         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, e: | ||||
|                 self._errors[name] = e.messages | ||||
|                 if name in self.cleaned_data: | ||||
|                     del self.cleaned_data[name] | ||||
|         try: | ||||
|             self.cleaned_data = self.clean() | ||||
|         except ValidationError, e: | ||||
|             self._errors[NON_FIELD_ERRORS] = e.messages | ||||
|         if self._errors: | ||||
|             delattr(self, 'cleaned_data') | ||||
|  | ||||
|     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) | ||||
|      | ||||
|     def _get_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) | ||||
|                 initial_value = self.initial.get(name, field.initial) | ||||
|                 if field.widget._has_changed(initial_value, data_value): | ||||
|                     self._changed_data.append(name) | ||||
|         return self._changed_data | ||||
|     changed_data = property(_get_changed_data) | ||||
|  | ||||
|     def _get_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 | ||||
|     media = property(_get_media) | ||||
|  | ||||
|     def is_multipart(self): | ||||
|         """ | ||||
|         Returns True if the form needs to be multipart-encrypted, i.e. it has | ||||
|         FileInput. Otherwise, False. | ||||
|         """ | ||||
|         for field in self.fields.values(): | ||||
|             if field.widget.needs_multipart_form: | ||||
|                 return True | ||||
|         return False | ||||
|  | ||||
| class Form(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. | ||||
|     __metaclass__ = DeclarativeFieldsMetaclass | ||||
|  | ||||
| class BoundField(StrAndUnicode): | ||||
|     "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) | ||||
|         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 __unicode__(self): | ||||
|         """Renders this field as an HTML widget.""" | ||||
|         return self.as_widget() | ||||
|  | ||||
|     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()) | ||||
|     errors = property(_errors) | ||||
|  | ||||
|     def as_widget(self, widget=None, attrs=None): | ||||
|         """ | ||||
|         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 | ||||
|         attrs = attrs or {} | ||||
|         auto_id = self.auto_id | ||||
|         if auto_id and 'id' not in attrs and 'id' not in widget.attrs: | ||||
|             attrs['id'] = auto_id | ||||
|         if not self.form.is_bound: | ||||
|             data = self.form.initial.get(self.name, self.field.initial) | ||||
|             if callable(data): | ||||
|                 data = data() | ||||
|         else: | ||||
|             data = self.data | ||||
|         return widget.render(self.html_name, data, attrs=attrs) | ||||
|  | ||||
|     def as_text(self, attrs=None): | ||||
|         """ | ||||
|         Returns a string of HTML for representing this as an <input type="text">. | ||||
|         """ | ||||
|         return self.as_widget(TextInput(), attrs) | ||||
|  | ||||
|     def as_textarea(self, attrs=None): | ||||
|         "Returns a string of HTML for representing this as a <textarea>." | ||||
|         return self.as_widget(Textarea(), attrs) | ||||
|  | ||||
|     def as_hidden(self, attrs=None): | ||||
|         """ | ||||
|         Returns a string of HTML for representing this as an <input type="hidden">. | ||||
|         """ | ||||
|         return self.as_widget(self.field.hidden_widget(), attrs) | ||||
|  | ||||
|     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) | ||||
|     data = property(_data) | ||||
|  | ||||
|     def label_tag(self, contents=None, attrs=None): | ||||
|         """ | ||||
|         Wraps the given contents in a <label>, if the field has an ID attribute. | ||||
|         Does not HTML-escape the contents. 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. | ||||
|         """ | ||||
|         contents = contents or escape(self.label) | ||||
|         widget = self.field.widget | ||||
|         id_ = widget.attrs.get('id') or self.auto_id | ||||
|         if id_: | ||||
|             attrs = attrs and flatatt(attrs) or '' | ||||
|             contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents) | ||||
|         return mark_safe(contents) | ||||
|  | ||||
|     def _is_hidden(self): | ||||
|         "Returns True if this BoundField's widget is hidden." | ||||
|         return self.field.widget.is_hidden | ||||
|     is_hidden = property(_is_hidden) | ||||
|  | ||||
|     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_unicode(auto_id): | ||||
|             return smart_unicode(auto_id) % self.html_name | ||||
|         elif auto_id: | ||||
|             return self.html_name | ||||
|         return '' | ||||
|     auto_id = property(_auto_id) | ||||
							
								
								
									
										292
									
								
								django/forms/formsets.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								django/forms/formsets.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,292 @@ | ||||
| from forms import Form | ||||
| from django.utils.encoding import StrAndUnicode | ||||
| from django.utils.safestring import mark_safe | ||||
| from fields import IntegerField, BooleanField | ||||
| from widgets import Media, HiddenInput, TextInput | ||||
| from util import ErrorList, ValidationError | ||||
|  | ||||
| __all__ = ('BaseFormSet', 'all_valid') | ||||
|  | ||||
| # special field names | ||||
| TOTAL_FORM_COUNT = 'TOTAL_FORMS' | ||||
| INITIAL_FORM_COUNT = 'INITIAL_FORMS' | ||||
| MAX_FORM_COUNT = 'MAX_FORMS' | ||||
| ORDERING_FIELD_NAME = 'ORDER' | ||||
| DELETION_FIELD_NAME = 'DELETE' | ||||
|  | ||||
| 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) | ||||
|         self.base_fields[MAX_FORM_COUNT] = IntegerField(widget=HiddenInput) | ||||
|         super(ManagementForm, self).__init__(*args, **kwargs) | ||||
|  | ||||
| class BaseFormSet(StrAndUnicode): | ||||
|     """ | ||||
|     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 'form' | ||||
|         self.auto_id = auto_id | ||||
|         self.data = data | ||||
|         self.files = files | ||||
|         self.initial = initial | ||||
|         self.error_class = error_class | ||||
|         self._errors = None | ||||
|         self._non_form_errors = None | ||||
|         # initialization is different depending on whether we recieved data, initial, or nothing | ||||
|         if data or files: | ||||
|             self.management_form = ManagementForm(data, auto_id=self.auto_id, prefix=self.prefix) | ||||
|             if self.management_form.is_valid(): | ||||
|                 self._total_form_count = self.management_form.cleaned_data[TOTAL_FORM_COUNT] | ||||
|                 self._initial_form_count = self.management_form.cleaned_data[INITIAL_FORM_COUNT] | ||||
|                 self._max_form_count = self.management_form.cleaned_data[MAX_FORM_COUNT] | ||||
|             else: | ||||
|                 raise ValidationError('ManagementForm data is missing or has been tampered with') | ||||
|         else: | ||||
|             if initial: | ||||
|                 self._initial_form_count = len(initial) | ||||
|                 if self._initial_form_count > self._max_form_count and self._max_form_count > 0: | ||||
|                     self._initial_form_count = self._max_form_count | ||||
|                 self._total_form_count = self._initial_form_count + self.extra | ||||
|             else: | ||||
|                 self._initial_form_count = 0 | ||||
|                 self._total_form_count = self.extra | ||||
|             if self._total_form_count > self._max_form_count and self._max_form_count > 0: | ||||
|                 self._total_form_count = self._max_form_count | ||||
|             initial = {TOTAL_FORM_COUNT: self._total_form_count, | ||||
|                        INITIAL_FORM_COUNT: self._initial_form_count, | ||||
|                        MAX_FORM_COUNT: self._max_form_count} | ||||
|             self.management_form = ManagementForm(initial=initial, auto_id=self.auto_id, prefix=self.prefix) | ||||
|          | ||||
|         # construct the forms in the formset | ||||
|         self._construct_forms() | ||||
|  | ||||
|     def __unicode__(self): | ||||
|         return self.as_table() | ||||
|  | ||||
|     def _construct_forms(self): | ||||
|         # instantiate all the forms and put them in self.forms | ||||
|         self.forms = [] | ||||
|         for i in xrange(self._total_form_count): | ||||
|             self.forms.append(self._construct_form(i)) | ||||
|      | ||||
|     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)} | ||||
|         if self.data or self.files: | ||||
|             defaults['data'] = self.data | ||||
|             defaults['files'] = self.files | ||||
|         if self.initial: | ||||
|             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 | ||||
|  | ||||
|     def _get_initial_forms(self): | ||||
|         """Return a list of all the intial forms in this formset.""" | ||||
|         return self.forms[:self._initial_form_count] | ||||
|     initial_forms = property(_get_initial_forms) | ||||
|  | ||||
|     def _get_extra_forms(self): | ||||
|         """Return a list of all the extra forms in this formset.""" | ||||
|         return self.forms[self._initial_form_count:] | ||||
|     extra_forms = property(_get_extra_forms) | ||||
|  | ||||
|     # Maybe this should just go away? | ||||
|     def _get_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] | ||||
|     cleaned_data = property(_get_cleaned_data) | ||||
|  | ||||
|     def _get_deleted_forms(self): | ||||
|         """ | ||||
|         Returns a list of forms that have been marked for deletion. Raises an  | ||||
|         AttributeError is deletion is not allowed. | ||||
|         """ | ||||
|         if not self.is_valid() or not self.can_delete: | ||||
|             raise AttributeError("'%s' object has no attribute 'deleted_forms'" % self.__class__.__name__) | ||||
|         # 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 form.cleaned_data[DELETION_FIELD_NAME]: | ||||
|                     self._deleted_form_indexes.append(i) | ||||
|         return [self.forms[i] for i in self._deleted_form_indexes] | ||||
|     deleted_forms = property(_get_deleted_forms) | ||||
|  | ||||
|     def _get_ordered_forms(self): | ||||
|         """ | ||||
|         Returns a list of form in the order specified by the incoming data. | ||||
|         Raises an AttributeError is deletion 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 form.cleaned_data[DELETION_FIELD_NAME]: | ||||
|                     continue | ||||
|                 # 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 | ||||
|                 # blamk. | ||||
|                 def compare_ordering_values(x, y): | ||||
|                     if x[1] is None: | ||||
|                         return 1 | ||||
|                     if y[1] is None: | ||||
|                         return -1 | ||||
|                     return x[1] - y[1] | ||||
|                 self._ordering.append((i, form.cleaned_data[ORDERING_FIELD_NAME])) | ||||
|             # After we're done populating self._ordering, sort it. | ||||
|             self._ordering.sort(compare_ordering_values) | ||||
|         # Return a list of form.cleaned_data dicts in the order spcified by | ||||
|         # the form data. | ||||
|         return [self.forms[i[0]] for i in self._ordering] | ||||
|     ordered_forms = property(_get_ordered_forms) | ||||
|  | ||||
|     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 not None: | ||||
|             return self._non_form_errors | ||||
|         return self.error_class() | ||||
|  | ||||
|     def _get_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 | ||||
|     errors = property(_get_errors) | ||||
|  | ||||
|     def is_valid(self): | ||||
|         """ | ||||
|         Returns True if form.errors is empty for every form in self.forms. | ||||
|         """ | ||||
|         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 | ||||
|         for errors in self.errors: | ||||
|             if bool(errors): | ||||
|                 forms_valid = False | ||||
|         return forms_valid and not bool(self.non_form_errors()) | ||||
|  | ||||
|     def full_clean(self): | ||||
|         """ | ||||
|         Cleans all of self.data and populates self._errors. | ||||
|         """ | ||||
|         self._errors = [] | ||||
|         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) | ||||
|         # Give self.clean() a chance to do cross-form validation. | ||||
|         try: | ||||
|             self.clean() | ||||
|         except ValidationError, e: | ||||
|             self._non_form_errors = 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 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 < 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-encrypted, i.e. it | ||||
|         has FileInput. Otherwise, False. | ||||
|         """ | ||||
|         return self.forms[0].is_multipart() | ||||
|  | ||||
|     def _get_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 Media() | ||||
|     media = property(_get_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 = u' '.join([form.as_table() for form in self.forms]) | ||||
|         return mark_safe(u'\n'.join([unicode(self.management_form), forms])) | ||||
|  | ||||
| def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, | ||||
|                     can_delete=False, max_num=0): | ||||
|     """Return a FormSet for the given form class.""" | ||||
|     attrs = {'form': form, 'extra': extra, | ||||
|              'can_order': can_order, 'can_delete': can_delete, | ||||
|              '_max_form_count': max_num} | ||||
|     return type(form.__name__ + '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 | ||||
							
								
								
									
										608
									
								
								django/forms/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										608
									
								
								django/forms/models.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,608 @@ | ||||
| """ | ||||
| Helper functions for creating Form classes from Django models | ||||
| and database field objects. | ||||
| """ | ||||
|  | ||||
| from warnings import warn | ||||
|  | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| from django.utils.encoding import smart_unicode | ||||
| from django.utils.datastructures import SortedDict | ||||
| from django.core.exceptions import ImproperlyConfigured | ||||
|  | ||||
| from util import ValidationError, ErrorList | ||||
| from forms import BaseForm, get_declared_fields | ||||
| from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES | ||||
| from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput | ||||
| from widgets import media_property | ||||
| from formsets import BaseFormSet, formset_factory, DELETION_FIELD_NAME | ||||
|  | ||||
| __all__ = ( | ||||
|     'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model', | ||||
|     'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', | ||||
|     'ModelChoiceField', 'ModelMultipleChoiceField', | ||||
| ) | ||||
|  | ||||
| def save_instance(form, instance, fields=None, fail_message='saved', | ||||
|                   commit=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``. | ||||
|     """ | ||||
|     from django.db import models | ||||
|     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)) | ||||
|     cleaned_data = form.cleaned_data | ||||
|     for f in opts.fields: | ||||
|         if not f.editable or isinstance(f, models.AutoField) \ | ||||
|                 or not f.name in cleaned_data: | ||||
|             continue | ||||
|         if fields and f.name not in fields: | ||||
|             continue | ||||
|         f.save_form_data(instance, cleaned_data[f.name]) | ||||
|     # Wrap up the saving of m2m data as a function. | ||||
|     def save_m2m(): | ||||
|         opts = instance._meta | ||||
|         cleaned_data = form.cleaned_data | ||||
|         for f in opts.many_to_many: | ||||
|             if fields and f.name not in fields: | ||||
|                 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 | ||||
|  | ||||
| def make_model_save(model, fields, fail_message): | ||||
|     """Returns the save() method for a Form.""" | ||||
|     def save(self, commit=True): | ||||
|         return save_instance(self, model(), fields, fail_message, commit) | ||||
|     return save | ||||
|  | ||||
| def make_instance_save(instance, fields, fail_message): | ||||
|     """Returns the save() method for a Form.""" | ||||
|     def save(self, commit=True): | ||||
|         return save_instance(self, instance, fields, fail_message, commit) | ||||
|     return save | ||||
|  | ||||
| def form_for_model(model, form=BaseForm, fields=None, | ||||
|                    formfield_callback=lambda f: f.formfield()): | ||||
|     """ | ||||
|     Returns a Form class for the given Django model class. | ||||
|  | ||||
|     Provide ``form`` if you want to use a custom BaseForm subclass. | ||||
|  | ||||
|     Provide ``formfield_callback`` if you want to define different logic for | ||||
|     determining the formfield for a given database field. It's a callable that | ||||
|     takes a database Field instance and returns a form Field instance. | ||||
|     """ | ||||
|     warn("form_for_model is deprecated. Use ModelForm instead.", | ||||
|         PendingDeprecationWarning, stacklevel=3) | ||||
|     opts = model._meta | ||||
|     field_list = [] | ||||
|     for f in opts.fields + opts.many_to_many: | ||||
|         if not f.editable: | ||||
|             continue | ||||
|         if fields and not f.name in fields: | ||||
|             continue | ||||
|         formfield = formfield_callback(f) | ||||
|         if formfield: | ||||
|             field_list.append((f.name, formfield)) | ||||
|     base_fields = SortedDict(field_list) | ||||
|     return type(opts.object_name + 'Form', (form,), | ||||
|         {'base_fields': base_fields, '_model': model, | ||||
|          'save': make_model_save(model, fields, 'created')}) | ||||
|  | ||||
| def form_for_instance(instance, form=BaseForm, fields=None, | ||||
|                       formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)): | ||||
|     """ | ||||
|     Returns a Form class for the given Django model instance. | ||||
|  | ||||
|     Provide ``form`` if you want to use a custom BaseForm subclass. | ||||
|  | ||||
|     Provide ``formfield_callback`` if you want to define different logic for | ||||
|     determining the formfield for a given database field. It's a callable that | ||||
|     takes a database Field instance, plus **kwargs, and returns a form Field | ||||
|     instance with the given kwargs (i.e. 'initial'). | ||||
|     """ | ||||
|     warn("form_for_instance is deprecated. Use ModelForm instead.", | ||||
|         PendingDeprecationWarning, stacklevel=3) | ||||
|     model = instance.__class__ | ||||
|     opts = model._meta | ||||
|     field_list = [] | ||||
|     for f in opts.fields + opts.many_to_many: | ||||
|         if not f.editable: | ||||
|             continue | ||||
|         if fields and not f.name in fields: | ||||
|             continue | ||||
|         current_value = f.value_from_object(instance) | ||||
|         formfield = formfield_callback(f, initial=current_value) | ||||
|         if formfield: | ||||
|             field_list.append((f.name, formfield)) | ||||
|     base_fields = SortedDict(field_list) | ||||
|     return type(opts.object_name + 'InstanceForm', (form,), | ||||
|         {'base_fields': base_fields, '_model': model, | ||||
|          'save': make_instance_save(instance, fields, 'changed')}) | ||||
|  | ||||
| def form_for_fields(field_list): | ||||
|     """ | ||||
|     Returns a Form class for the given list of Django database field instances. | ||||
|     """ | ||||
|     fields = SortedDict([(f.name, f.formfield()) | ||||
|                          for f in field_list if f.editable]) | ||||
|     return type('FormForFields', (BaseForm,), {'base_fields': fields}) | ||||
|  | ||||
|  | ||||
| # 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.fields + opts.many_to_many: | ||||
|         if not f.editable: | ||||
|             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 primry 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] = [obj.pk for obj in f.value_from_object(instance)] | ||||
|         else: | ||||
|             data[f.name] = f.value_from_object(instance) | ||||
|     return data | ||||
|  | ||||
| def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda f: f.formfield()): | ||||
|     """ | ||||
|     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. | ||||
|     """ | ||||
|     # TODO: if fields is provided, it would be nice to return fields in that order | ||||
|     field_list = [] | ||||
|     opts = model._meta | ||||
|     for f in opts.fields + opts.many_to_many: | ||||
|         if not f.editable: | ||||
|             continue | ||||
|         if fields and not f.name in fields: | ||||
|             continue | ||||
|         if exclude and f.name in exclude: | ||||
|             continue | ||||
|         formfield = formfield_callback(f) | ||||
|         if formfield: | ||||
|             field_list.append((f.name, formfield)) | ||||
|     return SortedDict(field_list) | ||||
|  | ||||
| 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) | ||||
|  | ||||
|  | ||||
| class ModelFormMetaclass(type): | ||||
|     def __new__(cls, name, bases, attrs): | ||||
|         formfield_callback = attrs.pop('formfield_callback', | ||||
|                 lambda f: f.formfield()) | ||||
|         try: | ||||
|             parents = [b for b in bases if issubclass(b, ModelForm)] | ||||
|         except NameError: | ||||
|             # We are defining ModelForm itself. | ||||
|             parents = None | ||||
|         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) | ||||
|         declared_fields = get_declared_fields(bases, attrs, False) | ||||
|         opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None)) | ||||
|         if opts.model: | ||||
|             # If a model is defined, extract form fields from it. | ||||
|             fields = fields_for_model(opts.model, opts.fields, | ||||
|                                       opts.exclude, formfield_callback) | ||||
|             # 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=':', | ||||
|                  empty_permitted=False, instance=None): | ||||
|         opts = self._meta | ||||
|         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) | ||||
|         BaseForm.__init__(self, data, files, auto_id, prefix, object_data, | ||||
|                           error_class, label_suffix, empty_permitted) | ||||
|  | ||||
|     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) | ||||
|  | ||||
| class ModelForm(BaseModelForm): | ||||
|     __metaclass__ = ModelFormMetaclass | ||||
|  | ||||
| def modelform_factory(model, form=ModelForm, fields=None, exclude=None, | ||||
|                        formfield_callback=lambda f: f.formfield()): | ||||
|     # HACK: we should be able to construct a ModelForm without creating | ||||
|     # and passing in a temporary inner class | ||||
|     class Meta: | ||||
|         pass | ||||
|     setattr(Meta, 'model', model) | ||||
|     setattr(Meta, 'fields', fields) | ||||
|     setattr(Meta, 'exclude', exclude) | ||||
|     class_name = model.__name__ + 'Form' | ||||
|     return ModelFormMetaclass(class_name, (form,), {'Meta': Meta,  | ||||
|                               'formfield_callback': formfield_callback}) | ||||
|  | ||||
|  | ||||
| # 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 | ||||
|         defaults = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} | ||||
|         if self._max_form_count > 0: | ||||
|             qs = self.get_queryset()[:self._max_form_count] | ||||
|         else: | ||||
|             qs = self.get_queryset() | ||||
|         defaults['initial'] = [model_to_dict(obj) for obj in qs] | ||||
|         defaults.update(kwargs) | ||||
|         super(BaseModelFormSet, self).__init__(**defaults) | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         if self.queryset is not None: | ||||
|             return self.queryset | ||||
|         return self.model._default_manager.get_query_set() | ||||
|  | ||||
|     def save_new(self, form, commit=True): | ||||
|         """Saves and returns a new model instance for the given form.""" | ||||
|         return save_instance(form, self.model(), commit=commit) | ||||
|  | ||||
|     def save_existing(self, form, instance, commit=True): | ||||
|         """Saves and returns an existing model instance for the given form.""" | ||||
|         return save_instance(form, instance, 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) | ||||
|  | ||||
|     def save_existing_objects(self, commit=True): | ||||
|         self.changed_objects = [] | ||||
|         self.deleted_objects = [] | ||||
|         if not self.get_queryset(): | ||||
|             return [] | ||||
|  | ||||
|         # Put the objects from self.get_queryset into a dict so they are easy to lookup by pk | ||||
|         existing_objects = {} | ||||
|         for obj in self.get_queryset(): | ||||
|             existing_objects[obj.pk] = obj | ||||
|         saved_instances = [] | ||||
|         for form in self.initial_forms: | ||||
|             obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]] | ||||
|             if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]: | ||||
|                 self.deleted_objects.append(obj) | ||||
|                 obj.delete() | ||||
|             else: | ||||
|                 if form.changed_data: | ||||
|                     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 form.cleaned_data[DELETION_FIELD_NAME]: | ||||
|                 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.""" | ||||
|         self._pk_field_name = self.model._meta.pk.attname | ||||
|         form.fields[self._pk_field_name] = IntegerField(required=False, widget=HiddenInput) | ||||
|         super(BaseModelFormSet, self).add_fields(form, index) | ||||
|  | ||||
| def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(), | ||||
|                          formset=BaseModelFormSet, | ||||
|                          extra=1, can_delete=False, can_order=False, | ||||
|                          max_num=0, fields=None, exclude=None): | ||||
|     """ | ||||
|     Returns a FormSet class for the given Django model class. | ||||
|     """ | ||||
|     form = modelform_factory(model, form=form, fields=fields, exclude=exclude, | ||||
|                              formfield_callback=formfield_callback) | ||||
|     FormSet = formset_factory(form, formset, extra=extra, max_num=max_num, | ||||
|                               can_order=can_order, can_delete=can_delete) | ||||
|     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): | ||||
|         from django.db.models.fields.related import RelatedObject | ||||
|         self.instance = instance | ||||
|         self.save_as_new = save_as_new | ||||
|         # is there a better way to get the object descriptor? | ||||
|         self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name() | ||||
|         super(BaseInlineFormset, self).__init__(data, files, prefix=self.rel_name) | ||||
|      | ||||
|     def _construct_forms(self): | ||||
|         if self.save_as_new: | ||||
|             self._total_form_count = self._initial_form_count | ||||
|             self._initial_form_count = 0 | ||||
|         super(BaseInlineFormset, self)._construct_forms() | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         """ | ||||
|         Returns this FormSet's queryset, but restricted to children of  | ||||
|         self.instance | ||||
|         """ | ||||
|         kwargs = {self.fk.name: self.instance} | ||||
|         return self.model._default_manager.filter(**kwargs) | ||||
|  | ||||
|     def save_new(self, form, commit=True): | ||||
|         kwargs = {self.fk.get_attname(): self.instance.pk} | ||||
|         new_obj = self.model(**kwargs) | ||||
|         return save_instance(form, new_obj, commit=commit) | ||||
|  | ||||
| def _get_foreign_key(parent_model, model, fk_name=None): | ||||
|     """ | ||||
|     Finds and returns the ForeignKey from model to parent if there is one. | ||||
|     If fk_name is provided, assume it is the name of the ForeignKey field. | ||||
|     """ | ||||
|     # 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: | ||||
|                 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] | ||||
|         if len(fks_to_parent) == 1: | ||||
|             fk = fks_to_parent[0] | ||||
|         elif len(fks_to_parent) == 0: | ||||
|             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=0, | ||||
|                           formfield_callback=lambda f: f.formfield()): | ||||
|     """ | ||||
|     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) | ||||
|     # let the formset handle object deletion by default | ||||
|      | ||||
|     if exclude is not None: | ||||
|         exclude.append(fk.name) | ||||
|     else: | ||||
|         exclude = [fk.name] | ||||
|     FormSet = modelformset_factory(model, 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) | ||||
|     FormSet.fk = fk | ||||
|     return FormSet | ||||
|  | ||||
|  | ||||
| # Fields ##################################################################### | ||||
|  | ||||
| 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 (u"", self.field.empty_label) | ||||
|         if self.field.cache_choices: | ||||
|             if self.field.choice_cache is None: | ||||
|                 self.field.choice_cache = [ | ||||
|                     (obj.pk, self.field.label_from_instance(obj)) | ||||
|                     for obj in self.queryset.all() | ||||
|                 ] | ||||
|             for choice in self.field.choice_cache: | ||||
|                 yield choice | ||||
|         else: | ||||
|             for obj in self.queryset.all(): | ||||
|                 yield (obj.pk, 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': _(u'Select a valid choice. That choice is not one of' | ||||
|                             u' the available choices.'), | ||||
|     } | ||||
|  | ||||
|     def __init__(self, queryset, empty_label=u"---------", cache_choices=False, | ||||
|                  required=True, widget=Select, label=None, initial=None, | ||||
|                  help_text=None, *args, **kwargs): | ||||
|         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 | ||||
|  | ||||
|     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_unicode(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 QuerySetIterator that has not been | ||||
|         # consumed. Note that we're instantiating a new QuerySetIterator *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 clean(self, value): | ||||
|         Field.clean(self, value) | ||||
|         if value in EMPTY_VALUES: | ||||
|             return None | ||||
|         try: | ||||
|             value = self.queryset.get(pk=value) | ||||
|         except self.queryset.model.DoesNotExist: | ||||
|             raise ValidationError(self.error_messages['invalid_choice']) | ||||
|         return value | ||||
|  | ||||
| class ModelMultipleChoiceField(ModelChoiceField): | ||||
|     """A MultipleChoiceField whose choices are a model QuerySet.""" | ||||
|     hidden_widget = MultipleHiddenInput | ||||
|     default_error_messages = { | ||||
|         'list': _(u'Enter a list of values.'), | ||||
|         'invalid_choice': _(u'Select a valid choice. %s is not one of the' | ||||
|                             u' available choices.'), | ||||
|     } | ||||
|  | ||||
|     def __init__(self, queryset, cache_choices=False, required=True, | ||||
|                  widget=SelectMultiple, label=None, initial=None, | ||||
|                  help_text=None, *args, **kwargs): | ||||
|         super(ModelMultipleChoiceField, self).__init__(queryset, None, | ||||
|             cache_choices, required, widget, label, initial, help_text, | ||||
|             *args, **kwargs) | ||||
|  | ||||
|     def clean(self, value): | ||||
|         if self.required and not value: | ||||
|             raise ValidationError(self.error_messages['required']) | ||||
|         elif not self.required and not value: | ||||
|             return [] | ||||
|         if not isinstance(value, (list, tuple)): | ||||
|             raise ValidationError(self.error_messages['list']) | ||||
|         final_values = [] | ||||
|         for val in value: | ||||
|             try: | ||||
|                 obj = self.queryset.get(pk=val) | ||||
|             except self.queryset.model.DoesNotExist: | ||||
|                 raise ValidationError(self.error_messages['invalid_choice'] % val) | ||||
|             else: | ||||
|                 final_values.append(obj) | ||||
|         return final_values | ||||
							
								
								
									
										69
									
								
								django/forms/util.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								django/forms/util.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| from django.utils.html import escape | ||||
| from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode | ||||
| from django.utils.functional import Promise | ||||
| from django.utils.safestring import mark_safe | ||||
|  | ||||
| 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. | ||||
|     """ | ||||
|     return u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()]) | ||||
|  | ||||
| class ErrorDict(dict, StrAndUnicode): | ||||
|     """ | ||||
|     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 __unicode__(self): | ||||
|         return self.as_ul() | ||||
|  | ||||
|     def as_ul(self): | ||||
|         if not self: return u'' | ||||
|         return mark_safe(u'<ul class="errorlist">%s</ul>' | ||||
|                 % ''.join([u'<li>%s%s</li>' % (k, force_unicode(v)) | ||||
|                     for k, v in self.items()])) | ||||
|  | ||||
|     def as_text(self): | ||||
|         return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u'  * %s' % force_unicode(i) for i in v])) for k, v in self.items()]) | ||||
|  | ||||
| class ErrorList(list, StrAndUnicode): | ||||
|     """ | ||||
|     A collection of errors that knows how to display itself in various formats. | ||||
|     """ | ||||
|     def __unicode__(self): | ||||
|         return self.as_ul() | ||||
|  | ||||
|     def as_ul(self): | ||||
|         if not self: return u'' | ||||
|         return mark_safe(u'<ul class="errorlist">%s</ul>' | ||||
|                 % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self])) | ||||
|  | ||||
|     def as_text(self): | ||||
|         if not self: return u'' | ||||
|         return u'\n'.join([u'* %s' % force_unicode(e) for e in self]) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return repr([force_unicode(e) for e in self]) | ||||
|  | ||||
| class ValidationError(Exception): | ||||
|     def __init__(self, message): | ||||
|         """ | ||||
|         ValidationError can be passed any object that can be printed (usually | ||||
|         a string) or a list of objects. | ||||
|         """ | ||||
|         if isinstance(message, list): | ||||
|             self.messages = ErrorList([smart_unicode(msg) for msg in message]) | ||||
|         else: | ||||
|             message = smart_unicode(message) | ||||
|             self.messages = ErrorList([message]) | ||||
|  | ||||
|     def __str__(self): | ||||
|         # This is needed because, without a __str__(), printing an exception | ||||
|         # instance would result in this: | ||||
|         # AttributeError: ValidationError instance has no attribute 'args' | ||||
|         # See http://www.python.org/doc/current/tut/node10.html#handling | ||||
|         return repr(self.messages) | ||||
							
								
								
									
										647
									
								
								django/forms/widgets.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										647
									
								
								django/forms/widgets.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,647 @@ | ||||
| """ | ||||
| HTML Widget classes | ||||
| """ | ||||
|  | ||||
| try: | ||||
|     set | ||||
| except NameError: | ||||
|     from sets import Set as set   # Python 2.3 fallback | ||||
|  | ||||
| import copy | ||||
| from itertools import chain | ||||
| from django.conf import settings | ||||
| from django.utils.datastructures import MultiValueDict | ||||
| from django.utils.html import escape, conditional_escape | ||||
| from django.utils.translation import ugettext | ||||
| from django.utils.encoding import StrAndUnicode, force_unicode | ||||
| from django.utils.safestring import mark_safe | ||||
| from django.utils import datetime_safe | ||||
| from util import flatatt | ||||
| from urlparse import urljoin | ||||
|  | ||||
| __all__ = ( | ||||
|     'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput', | ||||
|     'HiddenInput', 'MultipleHiddenInput', | ||||
|     'FileInput', 'DateTimeInput', 'Textarea', 'CheckboxInput', | ||||
|     'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', | ||||
|     'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget', | ||||
| ) | ||||
|  | ||||
| MEDIA_TYPES = ('css','js') | ||||
|  | ||||
| class Media(StrAndUnicode): | ||||
|     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 __unicode__(self): | ||||
|         return self.render() | ||||
|          | ||||
|     def render(self): | ||||
|         return u'\n'.join(chain(*[getattr(self, 'render_' + name)() for name in MEDIA_TYPES])) | ||||
|          | ||||
|     def render_js(self): | ||||
|         return [u'<script type="text/javascript" src="%s"></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 = self._css.keys() | ||||
|         media.sort() | ||||
|         return chain(*[ | ||||
|             [u'<link href="%s" type="text/css" media="%s" rel="stylesheet" />' % (self.absolute_path(path), medium)  | ||||
|                     for path in self._css[medium]]  | ||||
|                 for medium in media]) | ||||
|          | ||||
|     def absolute_path(self, path): | ||||
|         if path.startswith(u'http://') or path.startswith(u'https://') or path.startswith(u'/'): | ||||
|             return path | ||||
|         return urljoin(settings.MEDIA_URL,path) | ||||
|  | ||||
|     def __getitem__(self, name): | ||||
|         "Returns a Media object that only contains media of the given type" | ||||
|         if name in MEDIA_TYPES: | ||||
|             return Media(**{name: getattr(self, '_' + name)}) | ||||
|         raise KeyError('Unknown media type "%s"' % name) | ||||
|  | ||||
|     def add_js(self, data): | ||||
|         if data:     | ||||
|             self._js.extend([path for path in data if path not in self._js]) | ||||
|              | ||||
|     def add_css(self, data): | ||||
|         if data: | ||||
|             for medium, paths in data.items(): | ||||
|                 self._css.setdefault(medium, []).extend([path for path in paths if path not in self._css[medium]]) | ||||
|  | ||||
|     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 | ||||
|         if hasattr(super(cls, self), 'media'): | ||||
|             base = super(cls, self).media | ||||
|         else: | ||||
|             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 | ||||
|          | ||||
| class Widget(object): | ||||
|     __metaclass__ = MediaDefiningClass | ||||
|     is_hidden = False          # Determines whether this corresponds to an <input type="hidden">. | ||||
|     needs_multipart_form = False # Determines does this widget need multipart-encrypted form | ||||
|  | ||||
|     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 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 _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/ u''. | ||||
|         if data is None: | ||||
|             data_value = u'' | ||||
|         else: | ||||
|             data_value = data | ||||
|         if initial is None: | ||||
|             initial_value = u'' | ||||
|         else: | ||||
|             initial_value = initial | ||||
|         if force_unicode(initial_value) != force_unicode(data_value): | ||||
|             return True | ||||
|         return False | ||||
|  | ||||
|     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_ | ||||
|     id_for_label = classmethod(id_for_label) | ||||
|  | ||||
| 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 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_unicode(value) | ||||
|         return mark_safe(u'<input%s />' % flatatt(final_attrs)) | ||||
|  | ||||
| class TextInput(Input): | ||||
|     input_type = 'text' | ||||
|  | ||||
| class PasswordInput(Input): | ||||
|     input_type = 'password' | ||||
|  | ||||
|     def __init__(self, attrs=None, render_value=True): | ||||
|         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) | ||||
|         return mark_safe(u'\n'.join([(u'<input%s />' % | ||||
|             flatatt(dict(value=force_unicode(v), **final_attrs))) | ||||
|             for v in value])) | ||||
|  | ||||
|     def value_from_datadict(self, data, files, name): | ||||
|         if isinstance(data, MultiValueDict): | ||||
|             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) | ||||
|      | ||||
|     def _has_changed(self, initial, data): | ||||
|         if data is None: | ||||
|             return False | ||||
|         return True | ||||
|  | ||||
| class Textarea(Widget): | ||||
|     def __init__(self, attrs=None): | ||||
|         # The 'rows' and 'cols' attributes are required for HTML correctness. | ||||
|         self.attrs = {'cols': '40', 'rows': '10'} | ||||
|         if attrs: | ||||
|             self.attrs.update(attrs) | ||||
|  | ||||
|     def render(self, name, value, attrs=None): | ||||
|         if value is None: value = '' | ||||
|         value = force_unicode(value) | ||||
|         final_attrs = self.build_attrs(attrs, name=name) | ||||
|         return mark_safe(u'<textarea%s>%s</textarea>' % (flatatt(final_attrs), | ||||
|                 conditional_escape(force_unicode(value)))) | ||||
|  | ||||
| class DateTimeInput(Input): | ||||
|     input_type = 'text' | ||||
|     format = '%Y-%m-%d %H:%M:%S'     # '2006-10-25 14:30:59' | ||||
|  | ||||
|     def __init__(self, attrs=None, format=None): | ||||
|         super(DateTimeInput, self).__init__(attrs) | ||||
|         if format: | ||||
|             self.format = format | ||||
|  | ||||
|     def render(self, name, value, attrs=None): | ||||
|         if value is None: | ||||
|             value = '' | ||||
|         elif hasattr(value, 'strftime'): | ||||
|             value = datetime_safe.new_datetime(value) | ||||
|             value = value.strftime(self.format) | ||||
|         return super(DateTimeInput, self).render(name, value, attrs) | ||||
|  | ||||
| class CheckboxInput(Widget): | ||||
|     def __init__(self, attrs=None, check_test=bool): | ||||
|         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 = check_test | ||||
|  | ||||
|     def render(self, name, value, attrs=None): | ||||
|         final_attrs = self.build_attrs(attrs, type='checkbox', name=name) | ||||
|         try: | ||||
|             result = self.check_test(value) | ||||
|         except: # Silently catch exceptions | ||||
|             result = False | ||||
|         if result: | ||||
|             final_attrs['checked'] = 'checked' | ||||
|         if value not in ('', True, False, None): | ||||
|             # Only add the 'value' attribute if a value is non-empty. | ||||
|             final_attrs['value'] = force_unicode(value) | ||||
|         return mark_safe(u'<input%s />' % 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 | ||||
|         return super(CheckboxInput, self).value_from_datadict(data, files, name) | ||||
|  | ||||
|     def _has_changed(self, initial, data): | ||||
|         # Sometimes data or initial could be None or u'' which should be the | ||||
|         # same thing as False. | ||||
|         return bool(initial) != bool(data) | ||||
|  | ||||
| class Select(Widget): | ||||
|     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 = [u'<select%s>' % flatatt(final_attrs)] | ||||
|         # Normalize to string. | ||||
|         str_value = force_unicode(value) | ||||
|         for option_value, option_label in chain(self.choices, choices): | ||||
|             option_value = force_unicode(option_value) | ||||
|             selected_html = (option_value == str_value) and u' selected="selected"' or '' | ||||
|             output.append(u'<option value="%s"%s>%s</option>' % ( | ||||
|                     escape(option_value), selected_html, | ||||
|                     conditional_escape(force_unicode(option_label)))) | ||||
|         output.append(u'</select>') | ||||
|         return mark_safe(u'\n'.join(output)) | ||||
|  | ||||
| class NullBooleanSelect(Select): | ||||
|     """ | ||||
|     A Select Widget intended to be used with NullBooleanField. | ||||
|     """ | ||||
|     def __init__(self, attrs=None): | ||||
|         choices = ((u'1', ugettext('Unknown')), (u'2', ugettext('Yes')), (u'3', ugettext('No'))) | ||||
|         super(NullBooleanSelect, self).__init__(attrs, choices) | ||||
|  | ||||
|     def render(self, name, value, attrs=None, choices=()): | ||||
|         try: | ||||
|             value = {True: u'2', False: u'3', u'2': u'2', u'3': u'3'}[value] | ||||
|         except KeyError: | ||||
|             value = u'1' | ||||
|         return super(NullBooleanSelect, self).render(name, value, attrs, choices) | ||||
|  | ||||
|     def value_from_datadict(self, data, files, name): | ||||
|         value = data.get(name, None) | ||||
|         return {u'2': True, u'3': False, True: True, False: False}.get(value, None) | ||||
|  | ||||
|     def _has_changed(self, initial, data): | ||||
|         # Sometimes data or initial could be None or u'' which should be the | ||||
|         # same thing as False. | ||||
|         return bool(initial) != bool(data) | ||||
|  | ||||
| class SelectMultiple(Widget): | ||||
|     def __init__(self, attrs=None, choices=()): | ||||
|         super(SelectMultiple, 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, name=name) | ||||
|         output = [u'<select multiple="multiple"%s>' % flatatt(final_attrs)] | ||||
|         str_values = set([force_unicode(v) for v in value]) # Normalize to strings. | ||||
|         for option_value, option_label in chain(self.choices, choices): | ||||
|             option_value = force_unicode(option_value) | ||||
|             selected_html = (option_value in str_values) and ' selected="selected"' or '' | ||||
|             output.append(u'<option value="%s"%s>%s</option>' % ( | ||||
|                     escape(option_value), selected_html, | ||||
|                     conditional_escape(force_unicode(option_label)))) | ||||
|         output.append(u'</select>') | ||||
|         return mark_safe(u'\n'.join(output)) | ||||
|  | ||||
|     def value_from_datadict(self, data, files, name): | ||||
|         if isinstance(data, MultiValueDict): | ||||
|             return data.getlist(name) | ||||
|         return data.get(name, None) | ||||
|      | ||||
|     def _has_changed(self, initial, data): | ||||
|         if initial is None: | ||||
|             initial = [] | ||||
|         if data is None: | ||||
|             data = [] | ||||
|         if len(initial) != len(data): | ||||
|             return True | ||||
|         for value1, value2 in zip(initial, data): | ||||
|             if force_unicode(value1) != force_unicode(value2): | ||||
|                 return True | ||||
|         return False | ||||
|  | ||||
| class RadioInput(StrAndUnicode): | ||||
|     """ | ||||
|     An object used by RadioFieldRenderer that represents a single | ||||
|     <input type='radio'>. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, name, value, attrs, choice, index): | ||||
|         self.name, self.value = name, value | ||||
|         self.attrs = attrs | ||||
|         self.choice_value = force_unicode(choice[0]) | ||||
|         self.choice_label = force_unicode(choice[1]) | ||||
|         self.index = index | ||||
|  | ||||
|     def __unicode__(self): | ||||
|         if 'id' in self.attrs: | ||||
|             label_for = ' for="%s_%s"' % (self.attrs['id'], self.index) | ||||
|         else: | ||||
|             label_for = '' | ||||
|         choice_label = conditional_escape(force_unicode(self.choice_label)) | ||||
|         return mark_safe(u'<label%s>%s %s</label>' % (label_for, self.tag(), 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='radio', name=self.name, value=self.choice_value) | ||||
|         if self.is_checked(): | ||||
|             final_attrs['checked'] = 'checked' | ||||
|         return mark_safe(u'<input%s />' % flatatt(final_attrs)) | ||||
|  | ||||
| class RadioFieldRenderer(StrAndUnicode): | ||||
|     """ | ||||
|     An object used by RadioSelect to enable customization of radio widgets. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, name, value, attrs, choices): | ||||
|         self.name, self.value, self.attrs = name, value, attrs | ||||
|         self.choices = choices | ||||
|  | ||||
|     def __iter__(self): | ||||
|         for i, choice in enumerate(self.choices): | ||||
|             yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i) | ||||
|  | ||||
|     def __getitem__(self, idx): | ||||
|         choice = self.choices[idx] # Let the IndexError propogate | ||||
|         return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx) | ||||
|  | ||||
|     def __unicode__(self): | ||||
|         return self.render() | ||||
|  | ||||
|     def render(self): | ||||
|         """Outputs a <ul> for this set of radio fields.""" | ||||
|         return mark_safe(u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' | ||||
|                 % force_unicode(w) for w in self])) | ||||
|  | ||||
| class RadioSelect(Select): | ||||
|     renderer = RadioFieldRenderer | ||||
|  | ||||
|     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(RadioSelect, self).__init__(*args, **kwargs) | ||||
|  | ||||
|     def get_renderer(self, name, value, attrs=None, choices=()): | ||||
|         """Returns an instance of the renderer.""" | ||||
|         if value is None: value = '' | ||||
|         str_value = force_unicode(value) # Normalize to string. | ||||
|         final_attrs = self.build_attrs(attrs) | ||||
|         choices = list(chain(self.choices, choices)) | ||||
|         return self.renderer(name, str_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_): | ||||
|         # RadioSelect is represented by multiple <input type="radio"> fields, | ||||
|         # each of which has a distinct ID. The IDs are made distinct by a "_X" | ||||
|         # suffix, where X is the zero-based index of the radio field. Thus, | ||||
|         # the label for a RadioSelect should reference the first one ('_0'). | ||||
|         if id_: | ||||
|             id_ += '_0' | ||||
|         return id_ | ||||
|     id_for_label = classmethod(id_for_label) | ||||
|  | ||||
| class CheckboxSelectMultiple(SelectMultiple): | ||||
|     def render(self, name, value, attrs=None, choices=()): | ||||
|         if value is None: value = [] | ||||
|         has_id = attrs and 'id' in attrs | ||||
|         final_attrs = self.build_attrs(attrs, name=name) | ||||
|         output = [u'<ul>'] | ||||
|         # Normalize to strings | ||||
|         str_values = set([force_unicode(v) for v in value]) | ||||
|         for i, (option_value, option_label) in enumerate(chain(self.choices, choices)): | ||||
|             # If an ID attribute was given, add a numeric index as a suffix, | ||||
|             # so that the checkboxes don't all have the same ID attribute. | ||||
|             if has_id: | ||||
|                 final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i)) | ||||
|                 label_for = u' for="%s"' % final_attrs['id'] | ||||
|             else: | ||||
|                 label_for = '' | ||||
|                  | ||||
|             cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values) | ||||
|             option_value = force_unicode(option_value) | ||||
|             rendered_cb = cb.render(name, option_value) | ||||
|             option_label = conditional_escape(force_unicode(option_label)) | ||||
|             output.append(u'<li><label%s>%s %s</label></li>' % (label_for, rendered_cb, option_label)) | ||||
|         output.append(u'</ul>') | ||||
|         return mark_safe(u'\n'.join(output)) | ||||
|  | ||||
|     def id_for_label(self, id_): | ||||
|         # See the comment for RadioSelect.id_for_label() | ||||
|         if id_: | ||||
|             id_ += '_0' | ||||
|         return id_ | ||||
|     id_for_label = classmethod(id_for_label) | ||||
|  | ||||
| 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 = [isinstance(w, type) and w() or w for w in widgets] | ||||
|         super(MultiWidget, self).__init__(attrs) | ||||
|  | ||||
|     def render(self, name, value, attrs=None): | ||||
|         # 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_ | ||||
|     id_for_label = classmethod(id_for_label) | ||||
|  | ||||
|     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 _has_changed(self, initial, data): | ||||
|         if initial is None: | ||||
|             initial = [u'' for x in range(0, len(data))] | ||||
|         else: | ||||
|             initial = self.decompress(initial) | ||||
|         for widget, initial, data in zip(self.widgets, initial, data): | ||||
|             if widget._has_changed(initial, data): | ||||
|                 return True | ||||
|         return False | ||||
|  | ||||
|     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 u''.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) | ||||
|      | ||||
| class SplitDateTimeWidget(MultiWidget): | ||||
|     """ | ||||
|     A Widget that splits datetime input into two <input type="text"> boxes. | ||||
|     """ | ||||
|     def __init__(self, attrs=None): | ||||
|         widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs)) | ||||
|         super(SplitDateTimeWidget, self).__init__(widgets, attrs) | ||||
|  | ||||
|     def decompress(self, value): | ||||
|         if value: | ||||
|             return [value.date(), value.time().replace(microsecond=0)] | ||||
|         return [None, None] | ||||
|  | ||||
		Reference in New Issue
	
	Block a user