summaryrefslogtreecommitdiff
path: root/django/newforms/widgets.py
diff options
context:
space:
mode:
Diffstat (limited to 'django/newforms/widgets.py')
-rw-r--r--django/newforms/widgets.py170
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.