diff options
Diffstat (limited to 'django/newforms/widgets.py')
| -rw-r--r-- | django/newforms/widgets.py | 170 |
1 files changed, 168 insertions, 2 deletions
diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py index dc36530b93..2c9f3c2eba 100644 --- a/django/newforms/widgets.py +++ b/django/newforms/widgets.py @@ -9,7 +9,7 @@ except NameError: 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 @@ -17,16 +17,118 @@ 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__ = ( - 'Widget', 'TextInput', 'PasswordInput', + '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 @@ -65,6 +167,25 @@ class Widget(object): """ 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>, @@ -143,6 +264,11 @@ class FileInput(Input): 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): @@ -202,6 +328,11 @@ class CheckboxInput(Widget): 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) @@ -244,6 +375,11 @@ class NullBooleanSelect(Select): 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) @@ -268,6 +404,18 @@ class SelectMultiple(Widget): 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): """ @@ -447,6 +595,16 @@ class MultiWidget(Widget): 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): """ @@ -466,6 +624,14 @@ class MultiWidget(Widget): """ 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. |
