web/lib/django/forms/widgets.py
changeset 29 cc9b7e14412b
parent 0 0d40e90630ef
--- 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'<input%s />' %
-            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'<input%s />' % 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('</select>')
+        output.append(u'</select>')
         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 <input type="text"> boxes.
@@ -725,6 +768,10 @@
     """
     A Widget that splits datetime input into two <input type="hidden"> 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