web/lib/django/forms/widgets.py
changeset 29 cc9b7e14412b
parent 0 0d40e90630ef
equal deleted inserted replaced
28:b758351d191f 29:cc9b7e14412b
     1 """
     1 """
     2 HTML Widget classes
     2 HTML Widget classes
     3 """
     3 """
     4 
     4 
     5 try:
     5 import django.utils.copycompat as copy
     6     set
       
     7 except NameError:
       
     8     from sets import Set as set   # Python 2.3 fallback
       
     9 
       
    10 import copy
       
    11 from itertools import chain
     6 from itertools import chain
    12 from django.conf import settings
     7 from django.conf import settings
    13 from django.utils.datastructures import MultiValueDict, MergeDict
     8 from django.utils.datastructures import MultiValueDict, MergeDict
    14 from django.utils.html import escape, conditional_escape
     9 from django.utils.html import escape, conditional_escape
    15 from django.utils.translation import ugettext
    10 from django.utils.translation import ugettext
    16 from django.utils.encoding import StrAndUnicode, force_unicode
    11 from django.utils.encoding import StrAndUnicode, force_unicode
    17 from django.utils.safestring import mark_safe
    12 from django.utils.safestring import mark_safe
    18 from django.utils import datetime_safe
    13 from django.utils import formats
    19 from datetime import time
    14 import time
       
    15 import datetime
    20 from util import flatatt
    16 from util import flatatt
    21 from urlparse import urljoin
    17 from urlparse import urljoin
    22 
    18 
    23 __all__ = (
    19 __all__ = (
    24     'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput',
    20     'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput',
    44         for name in MEDIA_TYPES:
    40         for name in MEDIA_TYPES:
    45             getattr(self, 'add_' + name)(media_attrs.get(name, None))
    41             getattr(self, 'add_' + name)(media_attrs.get(name, None))
    46 
    42 
    47         # Any leftover attributes must be invalid.
    43         # Any leftover attributes must be invalid.
    48         # if media_attrs != {}:
    44         # if media_attrs != {}:
    49         #     raise TypeError, "'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys())
    45         #     raise TypeError("'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys()))
    50 
    46 
    51     def __unicode__(self):
    47     def __unicode__(self):
    52         return self.render()
    48         return self.render()
    53 
    49 
    54     def render(self):
    50     def render(self):
    78             return Media(**{str(name): getattr(self, '_' + name)})
    74             return Media(**{str(name): getattr(self, '_' + name)})
    79         raise KeyError('Unknown media type "%s"' % name)
    75         raise KeyError('Unknown media type "%s"' % name)
    80 
    76 
    81     def add_js(self, data):
    77     def add_js(self, data):
    82         if data:
    78         if data:
    83             self._js.extend([path for path in data if path not in self._js])
    79             for path in data:
       
    80                 if path not in self._js:
       
    81                     self._js.append(path)
    84 
    82 
    85     def add_css(self, data):
    83     def add_css(self, data):
    86         if data:
    84         if data:
    87             for medium, paths in data.items():
    85             for medium, paths in data.items():
    88                 self._css.setdefault(medium, []).extend([path for path in paths if path not in self._css[medium]])
    86                 for path in paths:
       
    87                     if not self._css.get(medium) or path not in self._css[medium]:
       
    88                         self._css.setdefault(medium, []).append(path)
    89 
    89 
    90     def __add__(self, other):
    90     def __add__(self, other):
    91         combined = Media()
    91         combined = Media()
    92         for name in MEDIA_TYPES:
    92         for name in MEDIA_TYPES:
    93             getattr(combined, 'add_' + name)(getattr(self, '_' + name, None))
    93             getattr(combined, 'add_' + name)(getattr(self, '_' + name, None))
   245         self.choices = choices
   245         self.choices = choices
   246 
   246 
   247     def render(self, name, value, attrs=None, choices=()):
   247     def render(self, name, value, attrs=None, choices=()):
   248         if value is None: value = []
   248         if value is None: value = []
   249         final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
   249         final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
   250         return mark_safe(u'\n'.join([(u'<input%s />' %
   250         id_ = final_attrs.get('id', None)
   251             flatatt(dict(value=force_unicode(v), **final_attrs)))
   251         inputs = []
   252             for v in value]))
   252         for i, v in enumerate(value):
       
   253             input_attrs = dict(value=force_unicode(v), **final_attrs)
       
   254             if id_:
       
   255                 # An ID attribute was given. Add a numeric index as a suffix
       
   256                 # so that the inputs don't all have the same ID attribute.
       
   257                 input_attrs['id'] = '%s_%s' % (id_, i)
       
   258             inputs.append(u'<input%s />' % flatatt(input_attrs))
       
   259         return mark_safe(u'\n'.join(inputs))
   253 
   260 
   254     def value_from_datadict(self, data, files, name):
   261     def value_from_datadict(self, data, files, name):
   255         if isinstance(data, (MultiValueDict, MergeDict)):
   262         if isinstance(data, (MultiValueDict, MergeDict)):
   256             return data.getlist(name)
   263             return data.getlist(name)
   257         return data.get(name, None)
   264         return data.get(name, None)
   286         return mark_safe(u'<textarea%s>%s</textarea>' % (flatatt(final_attrs),
   293         return mark_safe(u'<textarea%s>%s</textarea>' % (flatatt(final_attrs),
   287                 conditional_escape(force_unicode(value))))
   294                 conditional_escape(force_unicode(value))))
   288 
   295 
   289 class DateInput(Input):
   296 class DateInput(Input):
   290     input_type = 'text'
   297     input_type = 'text'
   291     format = '%Y-%m-%d'     # '2006-10-25'
   298     format = None
   292 
   299 
   293     def __init__(self, attrs=None, format=None):
   300     def __init__(self, attrs=None, format=None):
   294         super(DateInput, self).__init__(attrs)
   301         super(DateInput, self).__init__(attrs)
   295         if format:
   302         if format:
   296             self.format = format
   303             self.format = format
   297 
   304 
   298     def _format_value(self, value):
   305     def _format_value(self, value):
   299         if value is None:
   306         if value is None:
   300             return ''
   307             return ''
   301         elif hasattr(value, 'strftime'):
   308         elif hasattr(value, 'strftime'):
   302             value = datetime_safe.new_date(value)
   309             return formats.localize_input(value, self.format)
   303             return value.strftime(self.format)
       
   304         return value
   310         return value
   305 
   311 
   306     def render(self, name, value, attrs=None):
   312     def render(self, name, value, attrs=None):
   307         value = self._format_value(value)
   313         value = self._format_value(value)
   308         return super(DateInput, self).render(name, value, attrs)
   314         return super(DateInput, self).render(name, value, attrs)
   309 
   315 
   310     def _has_changed(self, initial, data):
   316     def _has_changed(self, initial, data):
       
   317         # If our field has show_hidden_initial=True, initial will be a string
       
   318         # formatted by HiddenInput using formats.localize_input, which is not
       
   319         # necessarily the format used for this widget. Attempt to convert it.
       
   320         try:
       
   321             input_format = formats.get_format('DATE_INPUT_FORMATS')[0]
       
   322             initial = datetime.date(*time.strptime(initial, input_format)[:3])
       
   323         except (TypeError, ValueError):
       
   324             pass
   311         return super(DateInput, self)._has_changed(self._format_value(initial), data)
   325         return super(DateInput, self)._has_changed(self._format_value(initial), data)
   312 
   326 
   313 class DateTimeInput(Input):
   327 class DateTimeInput(Input):
   314     input_type = 'text'
   328     input_type = 'text'
   315     format = '%Y-%m-%d %H:%M:%S'     # '2006-10-25 14:30:59'
   329     format = None
   316 
   330 
   317     def __init__(self, attrs=None, format=None):
   331     def __init__(self, attrs=None, format=None):
   318         super(DateTimeInput, self).__init__(attrs)
   332         super(DateTimeInput, self).__init__(attrs)
   319         if format:
   333         if format:
   320             self.format = format
   334             self.format = format
   321 
   335 
   322     def _format_value(self, value):
   336     def _format_value(self, value):
   323         if value is None:
   337         if value is None:
   324             return ''
   338             return ''
   325         elif hasattr(value, 'strftime'):
   339         elif hasattr(value, 'strftime'):
   326             value = datetime_safe.new_datetime(value)
   340             return formats.localize_input(value, self.format)
   327             return value.strftime(self.format)
       
   328         return value
   341         return value
   329 
   342 
   330     def render(self, name, value, attrs=None):
   343     def render(self, name, value, attrs=None):
   331         value = self._format_value(value)
   344         value = self._format_value(value)
   332         return super(DateTimeInput, self).render(name, value, attrs)
   345         return super(DateTimeInput, self).render(name, value, attrs)
   333 
   346 
   334     def _has_changed(self, initial, data):
   347     def _has_changed(self, initial, data):
       
   348         # If our field has show_hidden_initial=True, initial will be a string
       
   349         # formatted by HiddenInput using formats.localize_input, which is not
       
   350         # necessarily the format used for this widget. Attempt to convert it.
       
   351         try:
       
   352             input_format = formats.get_format('DATETIME_INPUT_FORMATS')[0]
       
   353             initial = datetime.datetime(*time.strptime(initial, input_format)[:6])
       
   354         except (TypeError, ValueError):
       
   355             pass
   335         return super(DateTimeInput, self)._has_changed(self._format_value(initial), data)
   356         return super(DateTimeInput, self)._has_changed(self._format_value(initial), data)
   336 
   357 
   337 class TimeInput(Input):
   358 class TimeInput(Input):
   338     input_type = 'text'
   359     input_type = 'text'
   339     format = '%H:%M:%S'     # '14:30:59'
   360     format = None
   340 
   361 
   341     def __init__(self, attrs=None, format=None):
   362     def __init__(self, attrs=None, format=None):
   342         super(TimeInput, self).__init__(attrs)
   363         super(TimeInput, self).__init__(attrs)
   343         if format:
   364         if format:
   344             self.format = format
   365             self.format = format
   345 
   366 
   346     def _format_value(self, value):
   367     def _format_value(self, value):
   347         if value is None:
   368         if value is None:
   348             return ''
   369             return ''
   349         elif hasattr(value, 'strftime'):
   370         elif hasattr(value, 'strftime'):
   350             return value.strftime(self.format)
   371             return formats.localize_input(value, self.format)
   351         return value
   372         return value
   352 
   373 
   353     def render(self, name, value, attrs=None):
   374     def render(self, name, value, attrs=None):
   354         value = self._format_value(value)
   375         value = self._format_value(value)
   355         return super(TimeInput, self).render(name, value, attrs)
   376         return super(TimeInput, self).render(name, value, attrs)
   356 
   377 
   357     def _has_changed(self, initial, data):
   378     def _has_changed(self, initial, data):
       
   379         # If our field has show_hidden_initial=True, initial will be a string
       
   380         # formatted by HiddenInput using formats.localize_input, which is not
       
   381         # necessarily the format used for this  widget. Attempt to convert it.
       
   382         try:
       
   383             input_format = formats.get_format('TIME_INPUT_FORMATS')[0]
       
   384             initial = datetime.time(*time.strptime(initial, input_format)[3:6])
       
   385         except (TypeError, ValueError):
       
   386             pass
   358         return super(TimeInput, self)._has_changed(self._format_value(initial), data)
   387         return super(TimeInput, self)._has_changed(self._format_value(initial), data)
   359 
   388 
   360 class CheckboxInput(Widget):
   389 class CheckboxInput(Widget):
   361     def __init__(self, attrs=None, check_test=bool):
   390     def __init__(self, attrs=None, check_test=bool):
   362         super(CheckboxInput, self).__init__(attrs)
   391         super(CheckboxInput, self).__init__(attrs)
   380     def value_from_datadict(self, data, files, name):
   409     def value_from_datadict(self, data, files, name):
   381         if name not in data:
   410         if name not in data:
   382             # A missing value means False because HTML form submission does not
   411             # A missing value means False because HTML form submission does not
   383             # send results for unselected checkboxes.
   412             # send results for unselected checkboxes.
   384             return False
   413             return False
   385         return super(CheckboxInput, self).value_from_datadict(data, files, name)
   414         value = data.get(name)
       
   415         # Translate true and false strings to boolean values.
       
   416         values =  {'true': True, 'false': False}
       
   417         if isinstance(value, basestring):
       
   418             value = values.get(value.lower(), value)
       
   419         return value
   386 
   420 
   387     def _has_changed(self, initial, data):
   421     def _has_changed(self, initial, data):
   388         # Sometimes data or initial could be None or u'' which should be the
   422         # Sometimes data or initial could be None or u'' which should be the
   389         # same thing as False.
   423         # same thing as False.
   390         return bool(initial) != bool(data)
   424         return bool(initial) != bool(data)
   402         final_attrs = self.build_attrs(attrs, name=name)
   436         final_attrs = self.build_attrs(attrs, name=name)
   403         output = [u'<select%s>' % flatatt(final_attrs)]
   437         output = [u'<select%s>' % flatatt(final_attrs)]
   404         options = self.render_options(choices, [value])
   438         options = self.render_options(choices, [value])
   405         if options:
   439         if options:
   406             output.append(options)
   440             output.append(options)
   407         output.append('</select>')
   441         output.append(u'</select>')
   408         return mark_safe(u'\n'.join(output))
   442         return mark_safe(u'\n'.join(output))
   409 
   443 
   410     def render_options(self, choices, selected_choices):
   444     def render_options(self, choices, selected_choices):
   411         def render_option(option_value, option_label):
   445         def render_option(option_value, option_label):
   412             option_value = force_unicode(option_value)
   446             option_value = force_unicode(option_value)
   450                 u'3': False,
   484                 u'3': False,
   451                 'False': False,
   485                 'False': False,
   452                 False: False}.get(value, None)
   486                 False: False}.get(value, None)
   453 
   487 
   454     def _has_changed(self, initial, data):
   488     def _has_changed(self, initial, data):
   455         # Sometimes data or initial could be None or u'' which should be the
   489         # For a NullBooleanSelect, None (unknown) and False (No)
   456         # same thing as False.
   490         # are not the same
   457         return bool(initial) != bool(data)
   491         if initial is not None:
       
   492             initial = bool(initial)
       
   493         if data is not None:
       
   494             data = bool(data)
       
   495         return initial != data
   458 
   496 
   459 class SelectMultiple(Select):
   497 class SelectMultiple(Select):
   460     def render(self, name, value, attrs=None, choices=()):
   498     def render(self, name, value, attrs=None, choices=()):
   461         if value is None: value = []
   499         if value is None: value = []
   462         final_attrs = self.build_attrs(attrs, name=name)
   500         final_attrs = self.build_attrs(attrs, name=name)
   698         for w in self.widgets:
   736         for w in self.widgets:
   699             media = media + w.media
   737             media = media + w.media
   700         return media
   738         return media
   701     media = property(_get_media)
   739     media = property(_get_media)
   702 
   740 
       
   741     def __deepcopy__(self, memo):
       
   742         obj = super(MultiWidget, self).__deepcopy__(memo)
       
   743         obj.widgets = copy.deepcopy(self.widgets)
       
   744         return obj
       
   745 
   703 class SplitDateTimeWidget(MultiWidget):
   746 class SplitDateTimeWidget(MultiWidget):
   704     """
   747     """
   705     A Widget that splits datetime input into two <input type="text"> boxes.
   748     A Widget that splits datetime input into two <input type="text"> boxes.
   706     """
   749     """
   707     date_format = DateInput.format
   750     date_format = DateInput.format
   723 
   766 
   724 class SplitHiddenDateTimeWidget(SplitDateTimeWidget):
   767 class SplitHiddenDateTimeWidget(SplitDateTimeWidget):
   725     """
   768     """
   726     A Widget that splits datetime input into two <input type="hidden"> inputs.
   769     A Widget that splits datetime input into two <input type="hidden"> inputs.
   727     """
   770     """
   728     def __init__(self, attrs=None):
   771     is_hidden = True
   729         widgets = (HiddenInput(attrs=attrs), HiddenInput(attrs=attrs))
   772 
   730         super(SplitDateTimeWidget, self).__init__(widgets, attrs)
   773     def __init__(self, attrs=None, date_format=None, time_format=None):
       
   774         super(SplitHiddenDateTimeWidget, self).__init__(attrs, date_format, time_format)
       
   775         for widget in self.widgets:
       
   776             widget.input_type = 'hidden'
       
   777             widget.is_hidden = True