/dojango/forms/widgets.py
Python | 565 lines | 505 code | 41 blank | 19 comment | 11 complexity | e923c89d8175c16a6470bfe6485fe3a7 MD5 | raw file
- import datetime
- from django.forms import *
- from django.utils import formats
- from django.utils.encoding import StrAndUnicode, force_unicode
- from django.utils.html import conditional_escape
- from django.utils.safestring import mark_safe
- from django.forms.util import flatatt
- from django.utils import datetime_safe
- from dojango.util import json_encode
- from dojango.util.config import Config
- from dojango.util import dojo_collector
- __all__ = (
- 'Media', 'MediaDefiningClass', # original django classes
- 'DojoWidgetMixin', 'Input', 'Widget', 'TextInput', 'PasswordInput',
- 'HiddenInput', 'MultipleHiddenInput', 'FileInput', 'Textarea',
- 'DateInput', 'DateTimeInput', 'TimeInput', 'CheckboxInput', 'Select',
- 'NullBooleanSelect', 'SelectMultiple', 'RadioInput', 'RadioFieldRenderer',
- 'RadioSelect', 'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
- 'SplitHiddenDateTimeWidget', 'SimpleTextarea', 'EditorInput', 'HorizontalSliderInput',
- 'VerticalSliderInput', 'ValidationTextInput', 'ValidationPasswordInput',
- 'EmailTextInput', 'IPAddressTextInput', 'URLTextInput', 'NumberTextInput',
- 'RangeBoundTextInput', 'NumberSpinnerInput', 'RatingInput', 'DateInputAnim',
- 'DropDownSelect', 'CheckedMultiSelect', 'FilteringSelect', 'ComboBox',
- 'ComboBoxStore', 'FilteringSelectStore', 'ListInput',
- )
- dojo_config = Config() # initialize the configuration
- class DojoWidgetMixin:
- """A helper mixin, that is used by every custom dojo widget.
- Some dojo widgets can utilize the validation information of a field and here
- we mixin those attributes into the widget. Field attributes that are listed
- in the 'valid_extra_attrs' will be mixed into the attributes of a widget.
- The 'default_field_attr_map' property contains the default mapping of field
- attributes to dojo widget attributes.
- This mixin also takes care passing the required dojo modules to the collector.
- 'dojo_type' defines the used dojo module type of this widget and adds this
- module to the collector, if no 'alt_require' property is defined. When
- 'alt_require' is set, this module will be passed to the collector. By using
- 'extra_dojo_require' it is possible to pass additional dojo modules to the
- collector.
- """
- dojo_type = None # this is the dojoType definition of the widget. also used for generating the dojo.require call
- alt_require = None # alternative dojo.require call (not using the dojo_type)
- extra_dojo_require = [] # these dojo modules also needs to be loaded for this widget
- default_field_attr_map = { # the default map for mapping field attributes to dojo attributes
- 'required':'required',
- 'help_text':'promptMessage',
- 'min_value':'constraints.min',
- 'max_value':'constraints.max',
- 'max_length':'maxLength',
- #'max_digits':'maxDigits',
- 'decimal_places':'constraints.places',
- 'js_regex':'regExp',
- 'multiple':'multiple',
- }
- field_attr_map = {} # used for overwriting the default attr-map
- valid_extra_attrs = [] # these field_attributes are valid for the current widget
- def _mixin_attr(self, attrs, key, value):
- """Mixes in the passed key/value into the passed attrs and returns that
- extended attrs dictionary.
- A 'key', that is separated by a dot, e.g. 'constraints.min', will be
- added as:
- {'constraints':{'min':value}}
- """
- dojo_field_attr = key.split(".")
- inner_dict = attrs
- len_fields = len(dojo_field_attr)
- count = 0
- for i in dojo_field_attr:
- count = count+1
- if count == len_fields and inner_dict.get(i, None) is None:
- if isinstance(value, datetime.datetime):
- if isinstance(self, TimeInput):
- value = value.strftime('T%H:%M:%S')
- if isinstance(self, DateInput):
- value = value.strftime('%Y-%m-%d')
- value = str(value).replace(' ', 'T') # see dojo.date.stamp
- if isinstance(value, datetime.date):
- value = str(value)
- if isinstance(value, datetime.time):
- value = "T" + str(value) # see dojo.date.stamp
- inner_dict[i] = value
- elif not inner_dict.has_key(i):
- inner_dict[i] = {}
- inner_dict = inner_dict[i]
- return attrs
- def build_attrs(self, extra_attrs=None, **kwargs):
- """Overwritten helper function for building an attribute dictionary.
- This helper also takes care passing the used dojo modules to the
- collector. Furthermore it mixes in the used field attributes into the
- attributes of this widget.
- """
- # gathering all widget attributes
- attrs = dict(self.attrs, **kwargs)
- field_attr = self.default_field_attr_map.copy() # use a copy of that object. otherwise changed field_attr_map would overwrite the default-map for all widgets!
- field_attr.update(self.field_attr_map) # the field-attribute-mapping can be customzied
- if extra_attrs:
- attrs.update(extra_attrs)
- # assigning dojoType to our widget
- dojo_type = getattr(self, "dojo_type", False)
- if dojo_type:
- attrs["dojoType"] = dojo_type # add the dojoType attribute
- # fill the global collector object
- if getattr(self, "alt_require", False):
- dojo_collector.add_module(self.alt_require)
- elif dojo_type:
- dojo_collector.add_module(self.dojo_type)
- extra_requires = getattr(self, "extra_dojo_require", [])
- for i in extra_requires:
- dojo_collector.add_module(i)
- # mixin those additional field attrs, that are valid for this widget
- extra_field_attrs = attrs.get("extra_field_attrs", False)
- if extra_field_attrs:
- for i in self.valid_extra_attrs:
- field_val = extra_field_attrs.get(i, None)
- new_attr_name = field_attr.get(i, None)
- if field_val is not None and new_attr_name is not None:
- attrs = self._mixin_attr(attrs, new_attr_name, field_val)
- del attrs["extra_field_attrs"]
- # now encode several attributes, e.g. False = false, True = true
- for i in attrs:
- if isinstance(attrs[i], bool):
- attrs[i] = json_encode(attrs[i])
- return attrs
- #############################################
- # ALL OVERWRITTEN DEFAULT DJANGO WIDGETS
- #############################################
- class Widget(DojoWidgetMixin, widgets.Widget):
- dojo_type = 'dijit._Widget'
- class Input(DojoWidgetMixin, widgets.Input):
- pass
- class TextInput(DojoWidgetMixin, widgets.TextInput):
- dojo_type = 'dijit.form.TextBox'
- valid_extra_attrs = [
- 'max_length',
- ]
- class PasswordInput(DojoWidgetMixin, widgets.PasswordInput):
- dojo_type = 'dijit.form.TextBox'
- valid_extra_attrs = [
- 'max_length',
- ]
- class HiddenInput(DojoWidgetMixin, widgets.HiddenInput):
- dojo_type = 'dijit.form.TextBox' # otherwise dijit.form.Form can't get its values
- class MultipleHiddenInput(DojoWidgetMixin, widgets.MultipleHiddenInput):
- dojo_type = 'dijit.form.TextBox' # otherwise dijit.form.Form can't get its values
- class FileInput(DojoWidgetMixin, widgets.FileInput):
- dojo_type = 'dojox.form.FileInput'
- class Media:
- css = {
- 'all': ('%(base_url)s/dojox/form/resources/FileInput.css' % {
- 'base_url':dojo_config.dojo_base_url
- },)
- }
- class Textarea(DojoWidgetMixin, widgets.Textarea):
- """Auto resizing textarea"""
- dojo_type = 'dijit.form.Textarea'
- valid_extra_attrs = [
- 'max_length'
- ]
- if DateInput:
- class DateInput(DojoWidgetMixin, widgets.DateInput):
- dojo_type = 'dijit.form.DateTextBox'
- valid_extra_attrs = [
- 'required',
- 'help_text',
- 'min_value',
- 'max_value',
- ]
- else: # fallback for older django versions
- class DateInput(TextInput):
- """Copy of the implementation in Django 1.1. Before this widget did not exists."""
- dojo_type = 'dijit.form.DateTextBox'
- valid_extra_attrs = [
- 'required',
- 'help_text',
- 'min_value',
- 'max_value',
- ]
- format = '%Y-%m-%d' # '2006-10-25'
- def __init__(self, attrs=None, format=None):
- super(DateInput, self).__init__(attrs)
- if format:
- self.format = format
- def render(self, name, value, attrs=None):
- if value is None:
- value = ''
- elif hasattr(value, 'strftime'):
- value = datetime_safe.new_date(value)
- value = value.strftime(self.format)
- return super(DateInput, self).render(name, value, attrs)
- if TimeInput:
- class TimeInput(DojoWidgetMixin, widgets.TimeInput):
- dojo_type = 'dijit.form.TimeTextBox'
- valid_extra_attrs = [
- 'required',
- 'help_text',
- 'min_value',
- 'max_value',
- ]
- format = "T%H:%M:%S" # special for dojo: 'T12:12:33'
-
- def __init__(self, attrs=None, format=None):
- # always passing the dojo time format
- super(TimeInput, self).__init__(attrs, format=self.format)
-
- def _has_changed(self, initial, data):
- try:
- input_format = self.format
- 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)
- else: # fallback for older django versions
- class TimeInput(TextInput):
- """Copy of the implementation in Django 1.1. Before this widget did not exists."""
- dojo_type = 'dijit.form.TimeTextBox'
- valid_extra_attrs = [
- 'required',
- 'help_text',
- 'min_value',
- 'max_value',
- ]
- format = "T%H:%M:%S" # special for dojo: 'T12:12:33'
- def __init__(self, attrs=None, format=None):
- super(TimeInput, self).__init__(attrs)
- if format:
- self.format = format
- def render(self, name, value, attrs=None):
- if value is None:
- value = ''
- elif hasattr(value, 'strftime'):
- value = value.strftime(self.format)
- return super(TimeInput, self).render(name, value, attrs)
- class CheckboxInput(DojoWidgetMixin, widgets.CheckboxInput):
- dojo_type = 'dijit.form.CheckBox'
- class Select(DojoWidgetMixin, widgets.Select):
- dojo_type = dojo_config.version < '1.4' and 'dijit.form.FilteringSelect' or 'dijit.form.Select'
- valid_extra_attrs = dojo_config.version < '1.4' and \
- ['required', 'help_text',] or \
- ['required',]
- class NullBooleanSelect(DojoWidgetMixin, widgets.NullBooleanSelect):
- dojo_type = dojo_config.version < '1.4' and 'dijit.form.FilteringSelect' or 'dijit.form.Select'
- valid_extra_attrs = dojo_config.version < '1.4' and \
- ['required', 'help_text',] or \
- ['required',]
- class SelectMultiple(DojoWidgetMixin, widgets.SelectMultiple):
- dojo_type = 'dijit.form.MultiSelect'
- RadioInput = widgets.RadioInput
- RadioFieldRenderer = widgets.RadioFieldRenderer
- class RadioSelect(DojoWidgetMixin, widgets.RadioSelect):
- dojo_type = 'dijit.form.RadioButton'
- def __init__(self, *args, **kwargs):
- if dojo_config.version < '1.3':
- self.alt_require = 'dijit.form.CheckBox'
- super(RadioSelect, self).__init__(*args, **kwargs)
- class CheckboxSelectMultiple(DojoWidgetMixin, widgets.CheckboxSelectMultiple):
- dojo_type = 'dijit.form.CheckBox'
- class MultiWidget(DojoWidgetMixin, widgets.MultiWidget):
- dojo_type = None
- class SplitDateTimeWidget(widgets.SplitDateTimeWidget):
- "DateTimeInput is using two input fields."
- date_format = DateInput.format
- time_format = TimeInput.format
- def __init__(self, attrs=None, date_format=None, time_format=None):
- if date_format:
- self.date_format = date_format
- if time_format:
- self.time_format = time_format
- split_widgets = (DateInput(attrs=attrs, format=self.date_format),
- TimeInput(attrs=attrs, format=self.time_format))
- # Note that we're calling MultiWidget, not SplitDateTimeWidget, because
- # we want to define widgets.
- widgets.MultiWidget.__init__(self, split_widgets, attrs)
- class SplitHiddenDateTimeWidget(DojoWidgetMixin, widgets.SplitHiddenDateTimeWidget):
- dojo_type = "dijit.form.TextBox"
- DateTimeInput = SplitDateTimeWidget
- #############################################
- # MORE ENHANCED DJANGO/DOJO WIDGETS
- #############################################
- class SimpleTextarea(Textarea):
- """No autoexpanding textarea"""
- dojo_type = "dijit.form.SimpleTextarea"
- class EditorInput(Textarea):
- dojo_type = 'dijit.Editor'
- def render(self, name, value, attrs=None):
- if value is None: value = ''
- final_attrs = self.build_attrs(attrs, name=name)
- # dijit.Editor must be rendered in a div (see dijit/_editor/RichText.js)
- return mark_safe(u'<div%s>%s</div>' % (flatatt(final_attrs),
- force_unicode(value))) # we don't escape the value for the editor
- class HorizontalSliderInput(TextInput):
- dojo_type = 'dijit.form.HorizontalSlider'
- valid_extra_attrs = [
- 'max_value',
- 'min_value',
- ]
- field_attr_map = {
- 'max_value': 'maximum',
- 'min_value': 'minimum',
- }
- def __init__(self, attrs=None):
- if dojo_config.version < '1.3':
- self.alt_require = 'dijit.form.Slider'
- super(HorizontalSliderInput, self).__init__(attrs)
- class VerticalSliderInput(HorizontalSliderInput):
- dojo_type = 'dijit.form.VerticalSlider'
- class ValidationTextInput(TextInput):
- dojo_type = 'dijit.form.ValidationTextBox'
- valid_extra_attrs = [
- 'required',
- 'help_text',
- 'js_regex',
- 'max_length',
- ]
- js_regex_func = None
- def render(self, name, value, attrs=None):
- if self.js_regex_func:
- attrs = self.build_attrs(attrs, regExpGen=self.js_regex_func)
- return super(ValidationTextInput, self).render(name, value, attrs)
- class ValidationPasswordInput(PasswordInput):
- dojo_type = 'dijit.form.ValidationTextBox'
- valid_extra_attrs = [
- 'required',
- 'help_text',
- 'js_regex',
- 'max_length',
- ]
- class EmailTextInput(ValidationTextInput):
- extra_dojo_require = [
- 'dojox.validate.regexp'
- ]
- js_regex_func = "dojox.validate.regexp.emailAddress"
- def __init__(self, attrs=None):
- if dojo_config.version < '1.3':
- self.js_regex_func = 'dojox.regexp.emailAddress'
- super(EmailTextInput, self).__init__(attrs)
- class IPAddressTextInput(ValidationTextInput):
- extra_dojo_require = [
- 'dojox.validate.regexp'
- ]
- js_regex_func = "dojox.validate.regexp.ipAddress"
- def __init__(self, attrs=None):
- if dojo_config.version < '1.3':
- self.js_regex_func = 'dojox.regexp.ipAddress'
- super(IPAddressTextInput, self).__init__(attrs)
- class URLTextInput(ValidationTextInput):
- extra_dojo_require = [
- 'dojox.validate.regexp'
- ]
- js_regex_func = "dojox.validate.regexp.url"
- def __init__(self, attrs=None):
- if dojo_config.version < '1.3':
- self.js_regex_func = 'dojox.regexp.url'
- super(URLTextInput, self).__init__(attrs)
- class NumberTextInput(TextInput):
- dojo_type = 'dijit.form.NumberTextBox'
- valid_extra_attrs = [
- 'min_value',
- 'max_value',
- 'required',
- 'help_text',
- 'decimal_places',
- ]
- class RangeBoundTextInput(NumberTextInput):
- dojo_type = 'dijit.form.RangeBoundTextBox'
- class NumberSpinnerInput(NumberTextInput):
- dojo_type = 'dijit.form.NumberSpinner'
- class RatingInput(TextInput):
- dojo_type = 'dojox.form.Rating'
- valid_extra_attrs = [
- 'max_value',
- ]
- field_attr_map = {
- 'max_value': 'numStars',
- }
- class Media:
- css = {
- 'all': ('%(base_url)s/dojox/form/resources/Rating.css' % {
- 'base_url':dojo_config.dojo_base_url
- },)
- }
- class DateInputAnim(DateInput):
- dojo_type = 'dojox.form.DateTextBox'
- class Media:
- css = {
- 'all': ('%(base_url)s/dojox/widget/Calendar/Calendar.css' % {
- 'base_url':dojo_config.dojo_base_url
- },)
- }
- class DropDownSelect(Select):
- dojo_type = 'dojox.form.DropDownSelect'
- valid_extra_attrs = []
- class Media:
- css = {
- 'all': ('%(base_url)s/dojox/form/resources/DropDownSelect.css' % {
- 'base_url':dojo_config.dojo_base_url
- },)
- }
- class CheckedMultiSelect(SelectMultiple):
- dojo_type = 'dojox.form.CheckedMultiSelect'
- valid_extra_attrs = []
- # TODO: fix attribute multiple=multiple
- # seems there is a dependency in dojox.form.CheckedMultiSelect for dijit.form.MultiSelect,
- # but CheckedMultiSelect is not extending that
- class Media:
- css = {
- 'all': ('%(base_url)s/dojox/form/resources/CheckedMultiSelect.css' % {
- 'base_url':dojo_config.dojo_base_url
- },)
- }
- class ComboBox(DojoWidgetMixin, widgets.Select):
- """Nearly the same as FilteringSelect, but ignoring the option value."""
- dojo_type = 'dijit.form.ComboBox'
- valid_extra_attrs = [
- 'required',
- 'help_text',
- ]
- class FilteringSelect(ComboBox):
- dojo_type = 'dijit.form.FilteringSelect'
- class ComboBoxStore(TextInput):
- """A combobox that is receiving data from a given dojo data url.
- As default dojo.data.ItemFileReadStore is used. You can overwrite
- that behaviour by passing a different store name
- (e.g. dojox.data.QueryReadStore).
- Usage:
- ComboBoxStore("/dojo-data-store-url/")
- """
- dojo_type = 'dijit.form.ComboBox'
- valid_extra_attrs = [
- 'required',
- 'help_text',
- ]
- store = 'dojo.data.ItemFileReadStore'
- store_attrs = {}
- url = None
-
- def __init__(self, url, attrs=None, store=None, store_attrs={}):
- self.url = url
- if store:
- self.store = store
- if store_attrs:
- self.store_attrs = store_attrs
- self.extra_dojo_require.append(self.store)
- super(ComboBoxStore, self).__init__(attrs)
-
- def render(self, name, value, attrs=None):
- if value is None: value = ''
- store_id = self.get_store_id(getattr(attrs, "id", None), name)
- final_attrs = self.build_attrs(attrs, type=self.input_type, name=name, store=store_id)
- if value != '':
- # Only add the 'value' attribute if a value is non-empty.
- final_attrs['value'] = force_unicode(self._format_value(value))
- self.store_attrs.update({
- 'dojoType': self.store,
- 'url': self.url,
- 'jsId':store_id
- })
- # TODO: convert store attributes to valid js-format (False => false, dict => {}, array = [])
- store_node = '<div%s></div>' % flatatt(self.store_attrs)
- return mark_safe(u'%s<input%s />' % (store_node, flatatt(final_attrs)))
- def get_store_id(self, id, name):
- return "_store_" + (id and id or name)
- class FilteringSelectStore(ComboBoxStore):
- dojo_type = 'dijit.form.FilteringSelect'
-
- class ListInput(DojoWidgetMixin, widgets.TextInput):
- dojo_type = 'dojox.form.ListInput'
- class Media:
- css = {
- 'all': ('%(base_url)s/dojox/form/resources/ListInput.css' % {
- 'base_url':dojo_config.dojo_base_url
- },)
- }
- # THE RANGE SLIDER NEEDS A DIFFERENT REPRESENTATION WITHIN HTML
- # SOMETHING LIKE:
- # <div dojoType="dojox.form.RangeSlider"><input value="5"/><input value="10"/></div>
- '''class HorizontalRangeSlider(HorizontalSliderInput):
- """This just can be used with a comma-separated-value like: 20,40"""
- dojo_type = 'dojox.form.HorizontalRangeSlider'
- alt_require = 'dojox.form.RangeSlider'
- class Media:
- css = {
- 'all': ('%(base_url)s/dojox/form/resources/RangeSlider.css' % {
- 'base_url':dojo_config.dojo_base_url
- },)
- }
- '''
- # TODO: implement
- # dojox.form.RangeSlider
- # dojox.form.MultiComboBox
- # dojox.form.FileUploader