diff -r b758351d191f -r cc9b7e14412b web/lib/django/forms/widgets.py --- a/web/lib/django/forms/widgets.py Wed May 19 17:43:59 2010 +0200 +++ b/web/lib/django/forms/widgets.py Tue May 25 02:43:45 2010 +0200 @@ -2,12 +2,7 @@ HTML Widget classes """ -try: - set -except NameError: - from sets import Set as set # Python 2.3 fallback - -import copy +import django.utils.copycompat as copy from itertools import chain from django.conf import settings from django.utils.datastructures import MultiValueDict, MergeDict @@ -15,8 +10,9 @@ 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 datetime import time +from django.utils import formats +import time +import datetime from util import flatatt from urlparse import urljoin @@ -46,7 +42,7 @@ # Any leftover attributes must be invalid. # if media_attrs != {}: - # raise TypeError, "'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys()) + # raise TypeError("'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys())) def __unicode__(self): return self.render() @@ -80,12 +76,16 @@ def add_js(self, data): if data: - self._js.extend([path for path in data if path not in self._js]) + for path in data: + if path not in self._js: + self._js.append(path) def add_css(self, data): if data: for medium, paths in data.items(): - self._css.setdefault(medium, []).extend([path for path in paths if path not in self._css[medium]]) + for path in paths: + if not self._css.get(medium) or path not in self._css[medium]: + self._css.setdefault(medium, []).append(path) def __add__(self, other): combined = Media() @@ -247,9 +247,16 @@ 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'' % - flatatt(dict(value=force_unicode(v), **final_attrs))) - for v in value])) + id_ = final_attrs.get('id', None) + inputs = [] + for i, v in enumerate(value): + input_attrs = dict(value=force_unicode(v), **final_attrs) + if id_: + # An ID attribute was given. Add a numeric index as a suffix + # so that the inputs don't all have the same ID attribute. + input_attrs['id'] = '%s_%s' % (id_, i) + inputs.append(u'' % flatatt(input_attrs)) + return mark_safe(u'\n'.join(inputs)) def value_from_datadict(self, data, files, name): if isinstance(data, (MultiValueDict, MergeDict)): @@ -288,7 +295,7 @@ class DateInput(Input): input_type = 'text' - format = '%Y-%m-%d' # '2006-10-25' + format = None def __init__(self, attrs=None, format=None): super(DateInput, self).__init__(attrs) @@ -299,8 +306,7 @@ if value is None: return '' elif hasattr(value, 'strftime'): - value = datetime_safe.new_date(value) - return value.strftime(self.format) + return formats.localize_input(value, self.format) return value def render(self, name, value, attrs=None): @@ -308,11 +314,19 @@ return super(DateInput, self).render(name, value, attrs) def _has_changed(self, initial, data): + # If our field has show_hidden_initial=True, initial will be a string + # formatted by HiddenInput using formats.localize_input, which is not + # necessarily the format used for this widget. Attempt to convert it. + try: + input_format = formats.get_format('DATE_INPUT_FORMATS')[0] + initial = datetime.date(*time.strptime(initial, input_format)[:3]) + except (TypeError, ValueError): + pass return super(DateInput, self)._has_changed(self._format_value(initial), data) class DateTimeInput(Input): input_type = 'text' - format = '%Y-%m-%d %H:%M:%S' # '2006-10-25 14:30:59' + format = None def __init__(self, attrs=None, format=None): super(DateTimeInput, self).__init__(attrs) @@ -323,8 +337,7 @@ if value is None: return '' elif hasattr(value, 'strftime'): - value = datetime_safe.new_datetime(value) - return value.strftime(self.format) + return formats.localize_input(value, self.format) return value def render(self, name, value, attrs=None): @@ -332,11 +345,19 @@ return super(DateTimeInput, self).render(name, value, attrs) def _has_changed(self, initial, data): + # If our field has show_hidden_initial=True, initial will be a string + # formatted by HiddenInput using formats.localize_input, which is not + # necessarily the format used for this widget. Attempt to convert it. + try: + input_format = formats.get_format('DATETIME_INPUT_FORMATS')[0] + initial = datetime.datetime(*time.strptime(initial, input_format)[:6]) + except (TypeError, ValueError): + pass return super(DateTimeInput, self)._has_changed(self._format_value(initial), data) class TimeInput(Input): input_type = 'text' - format = '%H:%M:%S' # '14:30:59' + format = None def __init__(self, attrs=None, format=None): super(TimeInput, self).__init__(attrs) @@ -347,7 +368,7 @@ if value is None: return '' elif hasattr(value, 'strftime'): - return value.strftime(self.format) + return formats.localize_input(value, self.format) return value def render(self, name, value, attrs=None): @@ -355,6 +376,14 @@ return super(TimeInput, self).render(name, value, attrs) def _has_changed(self, initial, data): + # If our field has show_hidden_initial=True, initial will be a string + # formatted by HiddenInput using formats.localize_input, which is not + # necessarily the format used for this widget. Attempt to convert it. + try: + input_format = formats.get_format('TIME_INPUT_FORMATS')[0] + initial = datetime.time(*time.strptime(initial, input_format)[3:6]) + except (TypeError, ValueError): + pass return super(TimeInput, self)._has_changed(self._format_value(initial), data) class CheckboxInput(Widget): @@ -382,7 +411,12 @@ # 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) + value = data.get(name) + # Translate true and false strings to boolean values. + values = {'true': True, 'false': False} + if isinstance(value, basestring): + value = values.get(value.lower(), value) + return value def _has_changed(self, initial, data): # Sometimes data or initial could be None or u'' which should be the @@ -404,7 +438,7 @@ options = self.render_options(choices, [value]) if options: output.append(options) - output.append('') + output.append(u'') return mark_safe(u'\n'.join(output)) def render_options(self, choices, selected_choices): @@ -452,9 +486,13 @@ 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) + # For a NullBooleanSelect, None (unknown) and False (No) + # are not the same + if initial is not None: + initial = bool(initial) + if data is not None: + data = bool(data) + return initial != data class SelectMultiple(Select): def render(self, name, value, attrs=None, choices=()): @@ -700,6 +738,11 @@ return media media = property(_get_media) + def __deepcopy__(self, memo): + obj = super(MultiWidget, self).__deepcopy__(memo) + obj.widgets = copy.deepcopy(self.widgets) + return obj + class SplitDateTimeWidget(MultiWidget): """ A Widget that splits datetime input into two boxes. @@ -725,6 +768,10 @@ """ A Widget that splits datetime input into two inputs. """ - def __init__(self, attrs=None): - widgets = (HiddenInput(attrs=attrs), HiddenInput(attrs=attrs)) - super(SplitDateTimeWidget, self).__init__(widgets, attrs) + is_hidden = True + + def __init__(self, attrs=None, date_format=None, time_format=None): + super(SplitHiddenDateTimeWidget, self).__init__(attrs, date_format, time_format) + for widget in self.widgets: + widget.input_type = 'hidden' + widget.is_hidden = True