summaryrefslogtreecommitdiff
path: root/django/newforms
diff options
context:
space:
mode:
authorJacob Kaplan-Moss <jacob@jacobian.org>2008-07-19 01:22:26 +0000
committerJacob Kaplan-Moss <jacob@jacobian.org>2008-07-19 01:22:26 +0000
commit46786b4193e04d398532bbfc3dcf63c03c1793cb (patch)
tree8dfb9330ca0e377d89c29e3f67076231bbbbbc05 /django/newforms
parent39af2738fd64ba7f4c4af0783590e2b2b7b88460 (diff)
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
Diffstat (limited to 'django/newforms')
-rw-r--r--django/newforms/__init__.py25
-rw-r--r--django/newforms/extras/__init__.py1
-rw-r--r--django/newforms/extras/widgets.py79
-rw-r--r--django/newforms/fields.py812
-rw-r--r--django/newforms/forms.py396
-rw-r--r--django/newforms/formsets.py292
-rw-r--r--django/newforms/models.py608
-rw-r--r--django/newforms/util.py69
-rw-r--r--django/newforms/widgets.py647
9 files changed, 7 insertions, 2922 deletions
diff --git a/django/newforms/__init__.py b/django/newforms/__init__.py
index 99631e4e8f..5f319f8c1b 100644
--- a/django/newforms/__init__.py
+++ b/django/newforms/__init__.py
@@ -1,18 +1,7 @@
-"""
-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 *
-from formsets import * \ No newline at end of file
+import warnings
+warnings.warn(
+ category = DeprecationWarning,
+ message = "django.newforms is no longer new. Import django.forms instead.",
+ stacklevel = 2
+)
+from django.forms import * \ No newline at end of file
diff --git a/django/newforms/extras/__init__.py b/django/newforms/extras/__init__.py
deleted file mode 100644
index a7f6a9b3f6..0000000000
--- a/django/newforms/extras/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from widgets import *
diff --git a/django/newforms/extras/widgets.py b/django/newforms/extras/widgets.py
deleted file mode 100644
index e3ef1d7f69..0000000000
--- a/django/newforms/extras/widgets.py
+++ /dev/null
@@ -1,79 +0,0 @@
-"""
-Extra HTML Widget classes
-"""
-
-import datetime
-import re
-
-from django.newforms.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)
diff --git a/django/newforms/fields.py b/django/newforms/fields.py
deleted file mode 100644
index 77bd8cbe94..0000000000
--- a/django/newforms/fields.py
+++ /dev/null
@@ -1,812 +0,0 @@
-"""
-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)
diff --git a/django/newforms/forms.py b/django/newforms/forms.py
deleted file mode 100644
index 753ee254bc..0000000000
--- a/django/newforms/forms.py
+++ /dev/null
@@ -1,396 +0,0 @@
-"""
-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)
diff --git a/django/newforms/formsets.py b/django/newforms/formsets.py
deleted file mode 100644
index 1ae27bf58c..0000000000
--- a/django/newforms/formsets.py
+++ /dev/null
@@ -1,292 +0,0 @@
-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
diff --git a/django/newforms/models.py b/django/newforms/models.py
deleted file mode 100644
index 43e2978ba8..0000000000
--- a/django/newforms/models.py
+++ /dev/null
@@ -1,608 +0,0 @@
-"""
-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
diff --git a/django/newforms/util.py b/django/newforms/util.py
deleted file mode 100644
index b3edf41adf..0000000000
--- a/django/newforms/util.py
+++ /dev/null
@@ -1,69 +0,0 @@
-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)
diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py
deleted file mode 100644
index 2c9f3c2eba..0000000000
--- a/django/newforms/widgets.py
+++ /dev/null
@@ -1,647 +0,0 @@
-"""
-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]
-