diff options
| author | Jacob Kaplan-Moss <jacob@jacobian.org> | 2008-07-19 01:22:26 +0000 |
|---|---|---|
| committer | Jacob Kaplan-Moss <jacob@jacobian.org> | 2008-07-19 01:22:26 +0000 |
| commit | 46786b4193e04d398532bbfc3dcf63c03c1793cb (patch) | |
| tree | 8dfb9330ca0e377d89c29e3f67076231bbbbbc05 /django/newforms | |
| parent | 39af2738fd64ba7f4c4af0783590e2b2b7b88460 (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__.py | 25 | ||||
| -rw-r--r-- | django/newforms/extras/__init__.py | 1 | ||||
| -rw-r--r-- | django/newforms/extras/widgets.py | 79 | ||||
| -rw-r--r-- | django/newforms/fields.py | 812 | ||||
| -rw-r--r-- | django/newforms/forms.py | 396 | ||||
| -rw-r--r-- | django/newforms/formsets.py | 292 | ||||
| -rw-r--r-- | django/newforms/models.py | 608 | ||||
| -rw-r--r-- | django/newforms/util.py | 69 | ||||
| -rw-r--r-- | django/newforms/widgets.py | 647 |
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] - |
