From b87089c7d2b48d89ca91c911b7d9881e88d35f2f Mon Sep 17 00:00:00 2001
From: Martijn Voncken
Date: Sat, 22 Mar 2008 12:55:49 +0000
Subject: [PATCH] newforms->newforms_portable
---
deluge/ui/webui/components.py | 2 +-
deluge/ui/webui/config_tabs_deluge.py | 2 +-
deluge/ui/webui/lib/newforms/fields.py | 492 -----------
deluge/ui/webui/lib/newforms/util.py | 78 --
deluge/ui/webui/lib/newforms/utils/html.py | 7 -
deluge/ui/webui/lib/newforms_plus.py | 12 +-
.../{newforms => newforms_portable}/LICENSE | 0
.../__init__.py | 2 +
.../ui/webui/lib/newforms_portable/about.txt | 3 +
.../django}/__init__.py | 0
.../newforms_portable/django/core/__init__.py | 0
.../django/core/exceptions.py | 29 +
.../django/utils/__init__.py | 0
.../django}/utils/datastructures.py | 213 +++--
.../django/utils/encoding.py | 102 +++
.../django/utils/functional.py | 241 ++++++
.../newforms_portable/django/utils/html.py | 163 ++++
.../newforms_portable/django/utils/http.py | 67 ++
.../django/utils/safestring.py | 119 +++
.../django/utils/translation.py | 9 +
.../ui/webui/lib/newforms_portable/fields.py | 784 ++++++++++++++++++
.../{newforms => newforms_portable}/forms.py | 205 +++--
.../ui/webui/lib/newforms_portable/models.py | 398 +++++++++
deluge/ui/webui/lib/newforms_portable/util.py | 69 ++
.../widgets.py | 266 ++++--
deluge/ui/webui/pages.py | 1 -
deluge/ui/webui/torrent_add.py | 2 +-
deluge/ui/webui/torrent_move.py | 2 +-
28 files changed, 2460 insertions(+), 808 deletions(-)
delete mode 100644 deluge/ui/webui/lib/newforms/fields.py
delete mode 100644 deluge/ui/webui/lib/newforms/util.py
delete mode 100644 deluge/ui/webui/lib/newforms/utils/html.py
rename deluge/ui/webui/lib/{newforms => newforms_portable}/LICENSE (100%)
rename deluge/ui/webui/lib/{newforms => newforms_portable}/__init__.py (91%)
create mode 100644 deluge/ui/webui/lib/newforms_portable/about.txt
rename deluge/ui/webui/lib/{newforms/utils => newforms_portable/django}/__init__.py (100%)
create mode 100644 deluge/ui/webui/lib/newforms_portable/django/core/__init__.py
create mode 100644 deluge/ui/webui/lib/newforms_portable/django/core/exceptions.py
create mode 100644 deluge/ui/webui/lib/newforms_portable/django/utils/__init__.py
rename deluge/ui/webui/lib/{newforms => newforms_portable/django}/utils/datastructures.py (50%)
create mode 100644 deluge/ui/webui/lib/newforms_portable/django/utils/encoding.py
create mode 100644 deluge/ui/webui/lib/newforms_portable/django/utils/functional.py
create mode 100644 deluge/ui/webui/lib/newforms_portable/django/utils/html.py
create mode 100644 deluge/ui/webui/lib/newforms_portable/django/utils/http.py
create mode 100644 deluge/ui/webui/lib/newforms_portable/django/utils/safestring.py
create mode 100644 deluge/ui/webui/lib/newforms_portable/django/utils/translation.py
create mode 100644 deluge/ui/webui/lib/newforms_portable/fields.py
rename deluge/ui/webui/lib/{newforms => newforms_portable}/forms.py (60%)
create mode 100644 deluge/ui/webui/lib/newforms_portable/models.py
create mode 100644 deluge/ui/webui/lib/newforms_portable/util.py
rename deluge/ui/webui/lib/{newforms => newforms_portable}/widgets.py (56%)
diff --git a/deluge/ui/webui/components.py b/deluge/ui/webui/components.py
index 19e12d6f2..4f877a31b 100644
--- a/deluge/ui/webui/components.py
+++ b/deluge/ui/webui/components.py
@@ -153,7 +153,7 @@ class ConfigPageManager(component.Component):
def __init__(self):
component.Component.__init__(self, "ConfigPageManager")
self.groups = []
- self.blocks = forms.utils.datastructures.SortedDict()
+ self.blocks = forms.django.utils.datastructures.SortedDict()
def register(self, group, name, form):
if not group in self.groups:
diff --git a/deluge/ui/webui/config_tabs_deluge.py b/deluge/ui/webui/config_tabs_deluge.py
index 44696c7ac..0b7562162 100644
--- a/deluge/ui/webui/config_tabs_deluge.py
+++ b/deluge/ui/webui/config_tabs_deluge.py
@@ -57,7 +57,7 @@ class NetworkPorts(config_forms.CfgForm ):
data['listen_ports'] = [data['_port_from'] , data['_port_to'] ]
del(data['_port_from'])
del(data['_port_to'])
- config_forms.config.CfgForm.save(self, data)
+ config_forms.CfgForm.save(self, data)
def validate(self, data):
if (data['_port_to'] < data['_port_from']):
diff --git a/deluge/ui/webui/lib/newforms/fields.py b/deluge/ui/webui/lib/newforms/fields.py
deleted file mode 100644
index 5076c5824..000000000
--- a/deluge/ui/webui/lib/newforms/fields.py
+++ /dev/null
@@ -1,492 +0,0 @@
-"""
-Field classes
-"""
-
-from gettext import gettext
-from util import ErrorList, ValidationError, smart_unicode
-from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple
-import datetime
-import re
-import time
-
-__all__ = (
- 'Field', 'CharField', 'IntegerField',
- 'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
- 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
- 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
- 'RegexField', 'EmailField', 'URLField', 'BooleanField',
- 'ChoiceField', 'NullBooleanField', 'MultipleChoiceField',
- 'ComboField', 'MultiValueField',
- 'SplitDateTimeField',
-)
-
-# These values, if given to to_python(), will trigger the self.required check.
-EMPTY_VALUES = (None, '')
-
-try:
- set # Only available in Python 2.4+
-except NameError:
- from sets import Set as set # Python 2.3 fallback
-
-class Field(object):
- widget = TextInput # Default widget to use when rendering this type of Field.
- hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
-
- # Tracks each time a Field instance is created. Used to retain order.
- creation_counter = 0
-
- def __init__(self, required=True, widget=None, label=None, initial=None, help_text=None):
- # required -- Boolean that specifies whether the field is required.
- # True by default.
- # widget -- A Widget class, or instance of a Widget class, that should be
- # used for this Field when displaying it. Each Field has a default
- # Widget that it'll use if you don't specify this. In most cases,
- # the default widget is TextInput.
- # label -- A verbose name for this field, for use in displaying this field in
- # a form. By default, Django will use a "pretty" version of the form
- # field name, if the Field is part of a Form.
- # initial -- A value to use in this Field's initial display. This value is
- # *not* used as a fallback if data isn't given.
- # help_text -- An optional string to use as "help text" for this Field.
- if label is not None:
- label = smart_unicode(label)
- self.required, self.label, self.initial = required, label, initial
- self.help_text = smart_unicode(help_text or '')
- widget = widget or self.widget
- if isinstance(widget, type):
- widget = widget()
-
- # Hook into self.widget_attrs() for any Field-specific HTML attributes.
- extra_attrs = self.widget_attrs(widget)
- if extra_attrs:
- widget.attrs.update(extra_attrs)
-
- self.widget = widget
-
- # Increase the creation counter, and save our local copy.
- self.creation_counter = Field.creation_counter
- Field.creation_counter += 1
-
- def clean(self, value):
- """
- Validates the given value and returns its "cleaned" value as an
- appropriate Python object.
-
- Raises ValidationError for any errors.
- """
- if self.required and value in EMPTY_VALUES:
- raise ValidationError(gettext(u'This field is required.'))
- return value
-
- def widget_attrs(self, widget):
- """
- Given a Widget instance (*not* a Widget class), returns a dictionary of
- any HTML attributes that should be added to the Widget, based on this
- Field.
- """
- return {}
-
-class CharField(Field):
- def __init__(self, max_length=None, min_length=None, *args, **kwargs):
- self.max_length, self.min_length = max_length, min_length
- super(CharField, self).__init__(*args, **kwargs)
-
- def clean(self, value):
- "Validates max_length and min_length. Returns a Unicode object."
- super(CharField, self).clean(value)
- if value in EMPTY_VALUES:
- return u''
- value = smart_unicode(value)
- if self.max_length is not None and len(value) > self.max_length:
- raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length)
- if self.min_length is not None and len(value) < self.min_length:
- raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length)
- return value
-
- def widget_attrs(self, widget):
- if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
- return {'maxlength': str(self.max_length)}
-
-class IntegerField(Field):
- def __init__(self, max_value=None, min_value=None, *args, **kwargs):
- self.max_value, self.min_value = max_value, min_value
- super(IntegerField, self).__init__(*args, **kwargs)
-
- def clean(self, value):
- """
- Validates that int() can be called on the input. Returns the result
- of int(). Returns None for empty values.
- """
- super(IntegerField, self).clean(value)
- if value in EMPTY_VALUES:
- return None
- try:
- value = int(value)
- except (ValueError, TypeError):
- raise ValidationError(gettext(u'Enter a whole number.'))
- if self.max_value is not None and value > self.max_value:
- raise ValidationError(gettext(u'Ensure this value is less than or equal to %s.') % self.max_value)
- if self.min_value is not None and value < self.min_value:
- raise ValidationError(gettext(u'Ensure this value is greater than or equal to %s.') % self.min_value)
- return value
-
-DEFAULT_DATE_INPUT_FORMATS = (
- '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
- '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
- '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
- '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
- '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
-)
-
-class DateField(Field):
- def __init__(self, input_formats=None, *args, **kwargs):
- super(DateField, self).__init__(*args, **kwargs)
- self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
-
- def clean(self, value):
- """
- Validates that the input can be converted to a date. Returns a Python
- datetime.date object.
- """
- super(DateField, self).clean(value)
- if value in EMPTY_VALUES:
- return None
- if isinstance(value, datetime.datetime):
- return value.date()
- if isinstance(value, datetime.date):
- return value
- for format in self.input_formats:
- try:
- return datetime.date(*time.strptime(value, format)[:3])
- except ValueError:
- continue
- raise ValidationError(gettext(u'Enter a valid date.'))
-
-DEFAULT_TIME_INPUT_FORMATS = (
- '%H:%M:%S', # '14:30:59'
- '%H:%M', # '14:30'
-)
-
-class TimeField(Field):
- def __init__(self, input_formats=None, *args, **kwargs):
- super(TimeField, self).__init__(*args, **kwargs)
- self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
-
- def clean(self, value):
- """
- Validates that the input can be converted to a time. Returns a Python
- datetime.time object.
- """
- super(TimeField, self).clean(value)
- if value in EMPTY_VALUES:
- return None
- if isinstance(value, datetime.time):
- return value
- for format in self.input_formats:
- try:
- return datetime.time(*time.strptime(value, format)[3:6])
- except ValueError:
- continue
- raise ValidationError(gettext(u'Enter a valid time.'))
-
-DEFAULT_DATETIME_INPUT_FORMATS = (
- '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
- '%Y-%m-%d %H:%M', # '2006-10-25 14:30'
- '%Y-%m-%d', # '2006-10-25'
- '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
- '%m/%d/%Y %H:%M', # '10/25/2006 14:30'
- '%m/%d/%Y', # '10/25/2006'
- '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
- '%m/%d/%y %H:%M', # '10/25/06 14:30'
- '%m/%d/%y', # '10/25/06'
-)
-
-class DateTimeField(Field):
- def __init__(self, input_formats=None, *args, **kwargs):
- super(DateTimeField, self).__init__(*args, **kwargs)
- self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
-
- def clean(self, value):
- """
- Validates that the input can be converted to a datetime. Returns a
- Python datetime.datetime object.
- """
- super(DateTimeField, self).clean(value)
- if value in EMPTY_VALUES:
- return None
- if isinstance(value, datetime.datetime):
- return value
- if isinstance(value, datetime.date):
- return datetime.datetime(value.year, value.month, value.day)
- for format in self.input_formats:
- try:
- return datetime.datetime(*time.strptime(value, format)[:6])
- except ValueError:
- continue
- raise ValidationError(gettext(u'Enter a valid date/time.'))
-
-class RegexField(Field):
- def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
- """
- regex can be either a string or a compiled regular expression object.
- error_message is an optional error message to use, if
- 'Enter a valid value' is too generic for you.
- """
- super(RegexField, self).__init__(*args, **kwargs)
- if isinstance(regex, basestring):
- regex = re.compile(regex)
- self.regex = regex
- self.max_length, self.min_length = max_length, min_length
- self.error_message = error_message or gettext(u'Enter a valid value.')
-
- def clean(self, value):
- """
- Validates that the input matches the regular expression. Returns a
- Unicode object.
- """
- super(RegexField, self).clean(value)
- if value in EMPTY_VALUES:
- value = u''
- value = smart_unicode(value)
- if value == u'':
- return value
- if self.max_length is not None and len(value) > self.max_length:
- raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length)
- if self.min_length is not None and len(value) < self.min_length:
- raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length)
- if not self.regex.search(value):
- raise ValidationError(self.error_message)
- return value
-
-email_re = re.compile(
- r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
- r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
- r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
-
-class EmailField(RegexField):
- def __init__(self, max_length=None, min_length=None, *args, **kwargs):
- RegexField.__init__(self, email_re, max_length, min_length,
- gettext(u'Enter a valid e-mail address.'), *args, **kwargs)
-
-url_re = re.compile(
- r'^https?://' # http:// or https://
- r'(?:[A-Z0-9-]+\.)+[A-Z]{2,6}' # domain
- r'(?::\d+)?' # optional port
- r'(?:/?|/\S+)$', re.IGNORECASE)
-
-try:
- from django.conf import settings
- URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT
-except ImportError:
- # It's OK if Django settings aren't configured.
- URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
-
-class URLField(RegexField):
- def __init__(self, max_length=None, min_length=None, verify_exists=False,
- validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs):
- super(URLField, self).__init__(url_re, max_length, min_length, gettext(u'Enter a valid URL.'), *args, **kwargs)
- self.verify_exists = verify_exists
- self.user_agent = validator_user_agent
-
- def clean(self, value):
- value = super(URLField, self).clean(value)
- if value == u'':
- return value
- if self.verify_exists:
- import urllib2
- from django.conf import settings
- headers = {
- "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
- "Accept-Language": "en-us,en;q=0.5",
- "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
- "Connection": "close",
- "User-Agent": self.user_agent,
- }
- try:
- req = urllib2.Request(value, None, headers)
- u = urllib2.urlopen(req)
- except ValueError:
- raise ValidationError(gettext(u'Enter a valid URL.'))
- except: # urllib2.URLError, httplib.InvalidURL, etc.
- raise ValidationError(gettext(u'This URL appears to be a broken link.'))
- return value
-
-class BooleanField(Field):
- widget = CheckboxInput
-
- def clean(self, value):
- "Returns a Python boolean object."
- super(BooleanField, self).clean(value)
- return bool(value)
-
-class NullBooleanField(BooleanField):
- """
- A field whose valid values are None, True and False. Invalid values are
- cleaned to None.
- """
- widget = NullBooleanSelect
-
- def clean(self, value):
- return {True: True, False: False}.get(value, None)
-
-class ChoiceField(Field):
- def __init__(self, choices=(), required=True, widget=Select, label=None, initial=None, help_text=None):
- super(ChoiceField, self).__init__(required, widget, label, initial, help_text)
- self.choices = choices
-
- def _get_choices(self):
- return self._choices
-
- def _set_choices(self, value):
- # Setting choices also sets the choices on the widget.
- # choices can be any iterable, but we call list() on it because
- # it will be consumed more than once.
- self._choices = self.widget.choices = list(value)
-
- choices = property(_get_choices, _set_choices)
-
- def clean(self, value):
- """
- Validates that the input is in self.choices.
- """
- value = super(ChoiceField, self).clean(value)
- if value in EMPTY_VALUES:
- value = u''
- value = smart_unicode(value)
- if value == u'':
- return value
- valid_values = set([str(k) for k, v in self.choices])
- if value not in valid_values:
- raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.'))
- return value
-
-class MultipleChoiceField(ChoiceField):
- hidden_widget = MultipleHiddenInput
-
- def __init__(self, choices=(), required=True, widget=SelectMultiple, label=None, initial=None, help_text=None):
- super(MultipleChoiceField, self).__init__(choices, required, widget, label, initial, help_text)
-
- def clean(self, value):
- """
- Validates that the input is a list or tuple.
- """
- if self.required and not value:
- raise ValidationError(gettext(u'This field is required.'))
- elif not self.required and not value:
- return []
- if not isinstance(value, (list, tuple)):
- raise ValidationError(gettext(u'Enter a list of values.'))
- new_value = []
- for val in value:
- val = smart_unicode(val)
- new_value.append(val)
- # Validate that each value in the value list is in self.choices.
- valid_values = set([smart_unicode(k) for k, v in self.choices])
- for val in new_value:
- if val not in valid_values:
- raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val)
- return new_value
-
-class ComboField(Field):
- """
- A Field whose clean() method calls multiple Field clean() methods.
- """
- def __init__(self, fields=(), *args, **kwargs):
- super(ComboField, self).__init__(*args, **kwargs)
- # Set 'required' to False on the individual fields, because the
- # required validation will be handled by ComboField, not by those
- # individual fields.
- for f in fields:
- f.required = False
- self.fields = fields
-
- def clean(self, value):
- """
- Validates the given value against all of self.fields, which is a
- list of Field instances.
- """
- super(ComboField, self).clean(value)
- for field in self.fields:
- value = field.clean(value)
- return value
-
-class MultiValueField(Field):
- """
- A Field that is composed of multiple Fields.
-
- Its clean() method takes a "decompressed" list of values. Each value in
- this list is cleaned by the corresponding field -- the first value is
- cleaned by the first field, the second value is cleaned by the second
- field, etc. Once all fields are cleaned, the list of clean values is
- "compressed" into a single value.
-
- Subclasses should implement compress(), which specifies how a list of
- valid values should be converted to a single value. Subclasses should not
- have to implement clean().
-
- You'll probably want to use this with MultiWidget.
- """
- def __init__(self, fields=(), *args, **kwargs):
- super(MultiValueField, self).__init__(*args, **kwargs)
- # Set 'required' to False on the individual fields, because the
- # required validation will be handled by MultiValueField, not by those
- # individual fields.
- for f in fields:
- f.required = False
- self.fields = fields
-
- def clean(self, value):
- """
- Validates every value in the given list. A value is validated against
- the corresponding Field in self.fields.
-
- For example, if this MultiValueField was instantiated with
- fields=(DateField(), TimeField()), clean() would call
- DateField.clean(value[0]) and TimeField.clean(value[1]).
- """
- clean_data = []
- errors = ErrorList()
- if self.required and not value:
- raise ValidationError(gettext(u'This field is required.'))
- elif not self.required and not value:
- return self.compress([])
- if not isinstance(value, (list, tuple)):
- raise ValidationError(gettext(u'Enter a list of values.'))
- for i, field in enumerate(self.fields):
- try:
- field_value = value[i]
- except KeyError:
- field_value = None
- if self.required and field_value in EMPTY_VALUES:
- raise ValidationError(gettext(u'This field is required.'))
- try:
- clean_data.append(field.clean(field_value))
- except ValidationError, e:
- # Collect all validation errors in a single list, which we'll
- # raise at the end of clean(), rather than raising a single
- # exception for the first error we encounter.
- errors.extend(e.messages)
- if errors:
- raise ValidationError(errors)
- return self.compress(clean_data)
-
- def compress(self, data_list):
- """
- Returns a single value for the given list of values. The values can be
- assumed to be valid.
-
- For example, if this MultiValueField was instantiated with
- fields=(DateField(), TimeField()), this might return a datetime
- object created by combining the date and time in data_list.
- """
- raise NotImplementedError('Subclasses must implement this method.')
-
-class SplitDateTimeField(MultiValueField):
- def __init__(self, *args, **kwargs):
- fields = (DateField(), TimeField())
- super(SplitDateTimeField, self).__init__(fields, *args, **kwargs)
-
- def compress(self, data_list):
- if data_list:
- return datetime.datetime.combine(*data_list)
- return None
diff --git a/deluge/ui/webui/lib/newforms/util.py b/deluge/ui/webui/lib/newforms/util.py
deleted file mode 100644
index 8b234df04..000000000
--- a/deluge/ui/webui/lib/newforms/util.py
+++ /dev/null
@@ -1,78 +0,0 @@
-from utils.html import escape
-
-class settings(object):
- DEFAULT_CHARSET = 'utf-8'
-
-
-# Converts a dictionary to a single string with key="value", XML-style with
-# a leading space. Assumes keys do not need to be XML-escaped.
-flatatt = lambda attrs: u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
-
-def smart_unicode(s):
- if not isinstance(s, basestring):
- if hasattr(s, '__unicode__'):
- s = unicode(s)
- else:
- s = unicode(str(s), settings.DEFAULT_CHARSET)
- elif not isinstance(s, unicode):
- s = unicode(s, settings.DEFAULT_CHARSET)
- return s
-
-class StrAndUnicode(object):
- """
- A class whose __str__ returns its __unicode__ as a bytestring
- according to settings.DEFAULT_CHARSET.
-
- Useful as a mix-in.
- """
- def __str__(self):
- return self.__unicode__().encode(settings.DEFAULT_CHARSET)
-
-class ErrorDict(dict):
- """
- A collection of errors that knows how to display itself in various formats.
-
- The dictionary keys are the field names, and the values are the errors.
- """
- def __str__(self):
- return self.as_ul()
-
- def as_ul(self):
- if not self: return u''
- return u'
%s
' % ''.join([u'
%s%s
' % (k, v) for k, v in self.items()])
-
- def as_text(self):
- return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u' * %s' % i for i in v])) for k, v in self.items()])
-
-class ErrorList(list):
- """
- A collection of errors that knows how to display itself in various formats.
- """
- def __str__(self):
- return self.as_ul()
-
- def as_ul(self):
- if not self: return u''
- return u'
%s
' % ''.join([u'
%s
' % e for e in self])
-
- def as_text(self):
- if not self: return u''
- return u'\n'.join([u'* %s' % e for e in self])
-
-class ValidationError(Exception):
- def __init__(self, message):
- "ValidationError can be passed a string or a list."
- self.message = message
- if isinstance(message, list):
- self.messages = ErrorList([smart_unicode(msg) for msg in message])
- else:
- assert isinstance(message, basestring), ("%s should be a basestring" % repr(message))
- message = smart_unicode(message)
- self.messages = ErrorList([message])
-
- def __str__(self):
- # This is needed because, without a __str__(), printing an exception
- # instance would result in this:
- # AttributeError: ValidationError instance has no attribute 'args'
- # See http://www.python.org/doc/current/tut/node10.html#handling
- return repr(self.messages)
diff --git a/deluge/ui/webui/lib/newforms/utils/html.py b/deluge/ui/webui/lib/newforms/utils/html.py
deleted file mode 100644
index e1f67cd43..000000000
--- a/deluge/ui/webui/lib/newforms/utils/html.py
+++ /dev/null
@@ -1,7 +0,0 @@
-"HTML utilities suitable for global use."
-
-def escape(html):
- "Returns the given HTML with ampersands, quotes and carets encoded"
- if not isinstance(html, basestring):
- html = str(html)
- return html.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')
diff --git a/deluge/ui/webui/lib/newforms_plus.py b/deluge/ui/webui/lib/newforms_plus.py
index 7e7877670..e1e8ffa4d 100644
--- a/deluge/ui/webui/lib/newforms_plus.py
+++ b/deluge/ui/webui/lib/newforms_plus.py
@@ -1,12 +1,14 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Martijn Voncken 2008
-# Django Lisence, see ./newforms/LICENCE
+# Django Licence, see ./newforms_portable/LICENCE
#
-from newforms import *
-import newforms
-from newforms.forms import BoundField
+from newforms_portable import *
+import newforms_portable as newforms
+from newforms_portable.forms import BoundField
+from newforms_portable.util import ErrorList, escape
+
import sys, os
@@ -83,7 +85,7 @@ class Form(FilteredForm):
def start_save(self):
"called by config_page"
- data = web.Storage(self.clean_data)
+ data = web.Storage(self.cleaned_data)
self.validate(data)
self.save(data)
self.post_save()
diff --git a/deluge/ui/webui/lib/newforms/LICENSE b/deluge/ui/webui/lib/newforms_portable/LICENSE
similarity index 100%
rename from deluge/ui/webui/lib/newforms/LICENSE
rename to deluge/ui/webui/lib/newforms_portable/LICENSE
diff --git a/deluge/ui/webui/lib/newforms/__init__.py b/deluge/ui/webui/lib/newforms_portable/__init__.py
similarity index 91%
rename from deluge/ui/webui/lib/newforms/__init__.py
rename to deluge/ui/webui/lib/newforms_portable/__init__.py
index 62125e218..a34f46c8e 100644
--- a/deluge/ui/webui/lib/newforms/__init__.py
+++ b/deluge/ui/webui/lib/newforms_portable/__init__.py
@@ -14,3 +14,5 @@ from util import ValidationError
from widgets import *
from fields import *
from forms import *
+from models import *
+import django
diff --git a/deluge/ui/webui/lib/newforms_portable/about.txt b/deluge/ui/webui/lib/newforms_portable/about.txt
new file mode 100644
index 000000000..333869ab8
--- /dev/null
+++ b/deluge/ui/webui/lib/newforms_portable/about.txt
@@ -0,0 +1,3 @@
+based on django rev.7350
+,/django/ contains the parts of django required to run newforms.
+
diff --git a/deluge/ui/webui/lib/newforms/utils/__init__.py b/deluge/ui/webui/lib/newforms_portable/django/__init__.py
similarity index 100%
rename from deluge/ui/webui/lib/newforms/utils/__init__.py
rename to deluge/ui/webui/lib/newforms_portable/django/__init__.py
diff --git a/deluge/ui/webui/lib/newforms_portable/django/core/__init__.py b/deluge/ui/webui/lib/newforms_portable/django/core/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/deluge/ui/webui/lib/newforms_portable/django/core/exceptions.py b/deluge/ui/webui/lib/newforms_portable/django/core/exceptions.py
new file mode 100644
index 000000000..d9fc326cf
--- /dev/null
+++ b/deluge/ui/webui/lib/newforms_portable/django/core/exceptions.py
@@ -0,0 +1,29 @@
+"Global Django exceptions"
+
+class ObjectDoesNotExist(Exception):
+ "The requested object does not exist"
+ silent_variable_failure = True
+
+class MultipleObjectsReturned(Exception):
+ "The query returned multiple objects when only one was expected."
+ pass
+
+class SuspiciousOperation(Exception):
+ "The user did something suspicious"
+ pass
+
+class PermissionDenied(Exception):
+ "The user did not have permission to do that"
+ pass
+
+class ViewDoesNotExist(Exception):
+ "The requested view does not exist"
+ pass
+
+class MiddlewareNotUsed(Exception):
+ "This middleware is not used in this server configuration"
+ pass
+
+class ImproperlyConfigured(Exception):
+ "Django is somehow improperly configured"
+ pass
diff --git a/deluge/ui/webui/lib/newforms_portable/django/utils/__init__.py b/deluge/ui/webui/lib/newforms_portable/django/utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/deluge/ui/webui/lib/newforms/utils/datastructures.py b/deluge/ui/webui/lib/newforms_portable/django/utils/datastructures.py
similarity index 50%
rename from deluge/ui/webui/lib/newforms/utils/datastructures.py
rename to deluge/ui/webui/lib/newforms_portable/django/utils/datastructures.py
index 7b7fa2b0f..4c278c0d8 100644
--- a/deluge/ui/webui/lib/newforms/utils/datastructures.py
+++ b/deluge/ui/webui/lib/newforms_portable/django/utils/datastructures.py
@@ -1,24 +1,24 @@
class MergeDict(object):
"""
- A simple class for creating new "virtual" dictionaries that actualy look
+ A simple class for creating new "virtual" dictionaries that actually look
up values in more than one dictionary, passed in the constructor.
+
+ If a key appears in more than one of the given dictionaries, only the
+ first occurrence will be used.
"""
def __init__(self, *dicts):
self.dicts = dicts
def __getitem__(self, key):
- for dict in self.dicts:
+ for dict_ in self.dicts:
try:
- return dict[key]
+ return dict_[key]
except KeyError:
pass
raise KeyError
- def __contains__(self, key):
- return self.has_key(key)
-
- def __copy__(self):
- return self.__class__(*self.dicts)
+ def __copy__(self):
+ return self.__class__(*self.dicts)
def get(self, key, default=None):
try:
@@ -27,84 +27,145 @@ class MergeDict(object):
return default
def getlist(self, key):
- for dict in self.dicts:
- try:
- return dict.getlist(key)
- except KeyError:
- pass
- raise KeyError
+ for dict_ in self.dicts:
+ if key in dict_.keys():
+ return dict_.getlist(key)
+ return []
def items(self):
item_list = []
- for dict in self.dicts:
- item_list.extend(dict.items())
+ for dict_ in self.dicts:
+ item_list.extend(dict_.items())
return item_list
def has_key(self, key):
- for dict in self.dicts:
- if dict.has_key(key):
+ for dict_ in self.dicts:
+ if key in dict_:
return True
return False
-
- def copy(self):
- """ returns a copy of this object"""
+
+ __contains__ = has_key
+
+ def copy(self):
+ """Returns a copy of this object."""
return self.__copy__()
class SortedDict(dict):
- "A dictionary that keeps its keys in the order in which they're inserted."
+ """
+ A dictionary that keeps its keys in the order in which they're inserted.
+ """
def __init__(self, data=None):
- if data is None: data = {}
- dict.__init__(self, data)
- self.keyOrder = data.keys()
+ if data is None:
+ data = {}
+ super(SortedDict, self).__init__(data)
+ if isinstance(data, dict):
+ self.keyOrder = data.keys()
+ else:
+ self.keyOrder = []
+ for key, value in data:
+ if key not in self.keyOrder:
+ self.keyOrder.append(key)
+
+ def __deepcopy__(self, memo):
+ from copy import deepcopy
+ return self.__class__([(key, deepcopy(value, memo))
+ for key, value in self.iteritems()])
def __setitem__(self, key, value):
- dict.__setitem__(self, key, value)
+ super(SortedDict, self).__setitem__(key, value)
if key not in self.keyOrder:
self.keyOrder.append(key)
def __delitem__(self, key):
- dict.__delitem__(self, key)
+ super(SortedDict, self).__delitem__(key)
self.keyOrder.remove(key)
def __iter__(self):
for k in self.keyOrder:
yield k
+ def pop(self, k, *args):
+ result = super(SortedDict, self).pop(k, *args)
+ try:
+ self.keyOrder.remove(k)
+ except ValueError:
+ # Key wasn't in the dictionary in the first place. No problem.
+ pass
+ return result
+
+ def popitem(self):
+ result = super(SortedDict, self).popitem()
+ self.keyOrder.remove(result[0])
+ return result
+
def items(self):
return zip(self.keyOrder, self.values())
+ def iteritems(self):
+ for key in self.keyOrder:
+ yield key, super(SortedDict, self).__getitem__(key)
+
def keys(self):
return self.keyOrder[:]
- def values(self):
- return [dict.__getitem__(self, k) for k in self.keyOrder]
+ def iterkeys(self):
+ return iter(self.keyOrder)
- def update(self, dict):
- for k, v in dict.items():
+ def values(self):
+ return [super(SortedDict, self).__getitem__(k) for k in self.keyOrder]
+
+ def itervalues(self):
+ for key in self.keyOrder:
+ yield super(SortedDict, self).__getitem__(key)
+
+ def update(self, dict_):
+ for k, v in dict_.items():
self.__setitem__(k, v)
def setdefault(self, key, default):
if key not in self.keyOrder:
self.keyOrder.append(key)
- return dict.setdefault(self, key, default)
+ return super(SortedDict, self).setdefault(key, default)
def value_for_index(self, index):
- "Returns the value of the item at the given zero-based index."
+ """Returns the value of the item at the given zero-based index."""
return self[self.keyOrder[index]]
+ def insert(self, index, key, value):
+ """Inserts the key, value pair before the item with the given index."""
+ if key in self.keyOrder:
+ n = self.keyOrder.index(key)
+ del self.keyOrder[n]
+ if n < index:
+ index -= 1
+ self.keyOrder.insert(index, key)
+ super(SortedDict, self).__setitem__(key, value)
+
def copy(self):
- "Returns a copy of this object."
+ """Returns a copy of this object."""
# This way of initializing the copy means it works for subclasses, too.
obj = self.__class__(self)
- obj.keyOrder = self.keyOrder
+ obj.keyOrder = self.keyOrder[:]
return obj
+ def __repr__(self):
+ """
+ Replaces the normal dict.__repr__ with a version that returns the keys
+ in their sorted order.
+ """
+ return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()])
+
+ def clear(self):
+ super(SortedDict, self).clear()
+ self.keyOrder = []
+
class MultiValueDictKeyError(KeyError):
pass
class MultiValueDict(dict):
"""
- A subclass of dictionary customized to handle multiple values for the same key.
+ A subclass of dictionary customized to handle multiple values for the
+ same key.
>>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
>>> d['name']
@@ -120,10 +181,11 @@ class MultiValueDict(dict):
single name-value pairs.
"""
def __init__(self, key_to_list_mapping=()):
- dict.__init__(self, key_to_list_mapping)
+ super(MultiValueDict, self).__init__(key_to_list_mapping)
def __repr__(self):
- return "" % dict.__repr__(self)
+ return "<%s: %s>" % (self.__class__.__name__,
+ super(MultiValueDict, self).__repr__())
def __getitem__(self, key):
"""
@@ -131,7 +193,7 @@ class MultiValueDict(dict):
raises KeyError if not found.
"""
try:
- list_ = dict.__getitem__(self, key)
+ list_ = super(MultiValueDict, self).__getitem__(key)
except KeyError:
raise MultiValueDictKeyError, "Key %r not found in %r" % (key, self)
try:
@@ -140,22 +202,27 @@ class MultiValueDict(dict):
return []
def __setitem__(self, key, value):
- dict.__setitem__(self, key, [value])
+ super(MultiValueDict, self).__setitem__(key, [value])
def __copy__(self):
- return self.__class__(dict.items(self))
+ return self.__class__(super(MultiValueDict, self).items())
def __deepcopy__(self, memo=None):
import copy
- if memo is None: memo = {}
+ if memo is None:
+ memo = {}
result = self.__class__()
memo[id(self)] = result
for key, value in dict.items(self):
- dict.__setitem__(result, copy.deepcopy(key, memo), copy.deepcopy(value, memo))
+ dict.__setitem__(result, copy.deepcopy(key, memo),
+ copy.deepcopy(value, memo))
return result
def get(self, key, default=None):
- "Returns the default value if the requested data doesn't exist"
+ """
+ Returns the last data value for the passed key. If key doesn't exist
+ or value is an empty list, then default is returned.
+ """
try:
val = self[key]
except KeyError:
@@ -165,14 +232,17 @@ class MultiValueDict(dict):
return val
def getlist(self, key):
- "Returns an empty list if the requested data doesn't exist"
+ """
+ Returns the list of values for the passed key. If key doesn't exist,
+ then an empty list is returned.
+ """
try:
- return dict.__getitem__(self, key)
+ return super(MultiValueDict, self).__getitem__(key)
except KeyError:
return []
def setlist(self, key, list_):
- dict.__setitem__(self, key, list_)
+ super(MultiValueDict, self).__setitem__(key, list_)
def setdefault(self, key, default=None):
if key not in self:
@@ -185,9 +255,9 @@ class MultiValueDict(dict):
return self.getlist(key)
def appendlist(self, key, value):
- "Appends an item to the internal list associated with key"
+ """Appends an item to the internal list associated with key."""
self.setlistdefault(key, [])
- dict.__setitem__(self, key, self.getlist(key) + [value])
+ super(MultiValueDict, self).__setitem__(key, self.getlist(key) + [value])
def items(self):
"""
@@ -197,21 +267,24 @@ class MultiValueDict(dict):
return [(key, self[key]) for key in self.keys()]
def lists(self):
- "Returns a list of (key, list) pairs."
- return dict.items(self)
+ """Returns a list of (key, list) pairs."""
+ return super(MultiValueDict, self).items()
def values(self):
- "Returns a list of the last value on every key list."
+ """Returns a list of the last value on every key list."""
return [self[key] for key in self.keys()]
def copy(self):
- "Returns a copy of this object."
+ """Returns a copy of this object."""
return self.__deepcopy__()
def update(self, *args, **kwargs):
- "update() extends rather than replaces existing key lists. Also accepts keyword args."
+ """
+ update() extends rather than replaces existing key lists.
+ Also accepts keyword args.
+ """
if len(args) > 1:
- raise TypeError, "update expected at most 1 arguments, got %d", len(args)
+ raise TypeError, "update expected at most 1 arguments, got %d" % len(args)
if args:
other_dict = args[0]
if isinstance(other_dict, MultiValueDict):
@@ -232,22 +305,20 @@ class DotExpandedDict(dict):
may contain dots to specify inner dictionaries. It's confusing, but this
example should make sense.
- >>> d = DotExpandedDict({'person.1.firstname': ['Simon'],
- 'person.1.lastname': ['Willison'],
- 'person.2.firstname': ['Adrian'],
+ >>> d = DotExpandedDict({'person.1.firstname': ['Simon'], \
+ 'person.1.lastname': ['Willison'], \
+ 'person.2.firstname': ['Adrian'], \
'person.2.lastname': ['Holovaty']})
>>> d
- {'person': {'1': {'lastname': ['Willison'], 'firstname': ['Simon']},
- '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}}
+ {'person': {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}}
>>> d['person']
- {'1': {'firstname': ['Simon'], 'lastname': ['Willison'],
- '2': {'firstname': ['Adrian'], 'lastname': ['Holovaty']}
+ {'1': {'lastname': ['Willison'], 'firstname': ['Simon']}, '2': {'lastname': ['Holovaty'], 'firstname': ['Adrian']}}
>>> d['person']['1']
- {'firstname': ['Simon'], 'lastname': ['Willison']}
+ {'lastname': ['Willison'], 'firstname': ['Simon']}
# Gotcha: Results are unpredictable if the dots are "uneven":
>>> DotExpandedDict({'c.1': 2, 'c.2': 3, 'c': 1})
- >>> {'c': 1}
+ {'c': 1}
"""
def __init__(self, key_to_list_mapping):
for k, v in key_to_list_mapping.items():
@@ -259,4 +330,16 @@ class DotExpandedDict(dict):
try:
current[bits[-1]] = v
except TypeError: # Special-case if current isn't a dict.
- current = {bits[-1] : v}
+ current = {bits[-1]: v}
+
+class FileDict(dict):
+ """
+ A dictionary used to hold uploaded file contents. The only special feature
+ here is that repr() of this object won't dump the entire contents of the
+ file to the output. A handy safeguard for a large file upload.
+ """
+ def __repr__(self):
+ if 'content' in self:
+ d = dict(self, content='')
+ return dict.__repr__(d)
+ return dict.__repr__(self)
diff --git a/deluge/ui/webui/lib/newforms_portable/django/utils/encoding.py b/deluge/ui/webui/lib/newforms_portable/django/utils/encoding.py
new file mode 100644
index 000000000..2b5219cc6
--- /dev/null
+++ b/deluge/ui/webui/lib/newforms_portable/django/utils/encoding.py
@@ -0,0 +1,102 @@
+import types
+import urllib
+import datetime
+
+from functional import Promise
+from safestring import SafeData, mark_safe
+
+class DjangoUnicodeDecodeError(UnicodeDecodeError):
+ def __init__(self, obj, *args):
+ self.obj = obj
+ UnicodeDecodeError.__init__(self, *args)
+
+ def __str__(self):
+ original = UnicodeDecodeError.__str__(self)
+ return '%s. You passed in %r (%s)' % (original, self.obj,
+ type(self.obj))
+
+class StrAndUnicode(object):
+ """
+ A class whose __str__ returns its __unicode__ as a UTF-8 bytestring.
+
+ Useful as a mix-in.
+ """
+ def __str__(self):
+ return self.__unicode__().encode('utf-8')
+
+def smart_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
+ """
+ Returns a unicode object representing 's'. Treats bytestrings using the
+ 'encoding' codec.
+
+ If strings_only is True, don't convert (some) non-string-like objects.
+ """
+ if isinstance(s, Promise):
+ # The input is the result of a gettext_lazy() call.
+ return s
+ return force_unicode(s, encoding, strings_only, errors)
+
+def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
+ """
+ Similar to smart_unicode, except that lazy instances are resolved to
+ strings, rather than kept as lazy objects.
+
+ If strings_only is True, don't convert (some) non-string-like objects.
+ """
+ if strings_only and isinstance(s, (types.NoneType, int, long, datetime.datetime, datetime.date, datetime.time, float)):
+ return s
+ try:
+ if not isinstance(s, basestring,):
+ if hasattr(s, '__unicode__'):
+ s = unicode(s)
+ else:
+ s = unicode(str(s), encoding, errors)
+ elif not isinstance(s, unicode):
+ # Note: We use .decode() here, instead of unicode(s, encoding,
+ # errors), so that if s is a SafeString, it ends up being a
+ # SafeUnicode at the end.
+ s = s.decode(encoding, errors)
+ except UnicodeDecodeError, e:
+ raise DjangoUnicodeDecodeError(s, *e.args)
+ return s
+
+def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
+ """
+ Returns a bytestring version of 's', encoded as specified in 'encoding'.
+
+ If strings_only is True, don't convert (some) non-string-like objects.
+ """
+ if strings_only and isinstance(s, (types.NoneType, int)):
+ return s
+ if isinstance(s, Promise):
+ return unicode(s).encode(encoding, errors)
+ elif not isinstance(s, basestring):
+ try:
+ return str(s)
+ except UnicodeEncodeError:
+ return unicode(s).encode(encoding, errors)
+ elif isinstance(s, unicode):
+ return s.encode(encoding, errors)
+ elif s and encoding != 'utf-8':
+ return s.decode('utf-8', errors).encode(encoding, errors)
+ else:
+ return s
+
+def iri_to_uri(iri):
+ """
+ Convert an Internationalized Resource Identifier (IRI) portion to a URI
+ portion that is suitable for inclusion in a URL.
+
+ This is the algorithm from section 3.1 of RFC 3987. However, since we are
+ assuming input is either UTF-8 or unicode already, we can simplify things a
+ little from the full method.
+
+ Returns an ASCII string containing the encoded result.
+ """
+ # The list of safe characters here is constructed from the printable ASCII
+ # characters that are not explicitly excluded by the list at the end of
+ # section 3.1 of RFC 3987.
+ if iri is None:
+ return iri
+ return urllib.quote(smart_str(iri), safe='/#%[]=:;$&()+,!?*')
+
diff --git a/deluge/ui/webui/lib/newforms_portable/django/utils/functional.py b/deluge/ui/webui/lib/newforms_portable/django/utils/functional.py
new file mode 100644
index 000000000..3de693e18
--- /dev/null
+++ b/deluge/ui/webui/lib/newforms_portable/django/utils/functional.py
@@ -0,0 +1,241 @@
+# License for code in this file that was taken from Python 2.5.
+
+# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+# --------------------------------------------
+#
+# 1. This LICENSE AGREEMENT is between the Python Software Foundation
+# ("PSF"), and the Individual or Organization ("Licensee") accessing and
+# otherwise using this software ("Python") in source or binary form and
+# its associated documentation.
+#
+# 2. Subject to the terms and conditions of this License Agreement, PSF
+# hereby grants Licensee a nonexclusive, royalty-free, world-wide
+# license to reproduce, analyze, test, perform and/or display publicly,
+# prepare derivative works, distribute, and otherwise use Python
+# alone or in any derivative version, provided, however, that PSF's
+# License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
+# 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation;
+# All Rights Reserved" are retained in Python alone or in any derivative
+# version prepared by Licensee.
+#
+# 3. In the event Licensee prepares a derivative work that is based on
+# or incorporates Python or any part thereof, and wants to make
+# the derivative work available to others as provided herein, then
+# Licensee hereby agrees to include in any such work a brief summary of
+# the changes made to Python.
+#
+# 4. PSF is making Python available to Licensee on an "AS IS"
+# basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+# IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+# DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+# FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+# INFRINGE ANY THIRD PARTY RIGHTS.
+#
+# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+# FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+# A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+# OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+#
+# 6. This License Agreement will automatically terminate upon a material
+# breach of its terms and conditions.
+#
+# 7. Nothing in this License Agreement shall be deemed to create any
+# relationship of agency, partnership, or joint venture between PSF and
+# Licensee. This License Agreement does not grant permission to use PSF
+# trademarks or trade name in a trademark sense to endorse or promote
+# products or services of Licensee, or any third party.
+#
+# 8. By copying, installing or otherwise using Python, Licensee
+# agrees to be bound by the terms and conditions of this License
+# Agreement.
+
+
+def curry(_curried_func, *args, **kwargs):
+ def _curried(*moreargs, **morekwargs):
+ return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs))
+ return _curried
+
+### Begin from Python 2.5 functools.py ########################################
+
+# Summary of changes made to the Python 2.5 code below:
+# * swapped ``partial`` for ``curry`` to maintain backwards-compatibility
+# in Django.
+# * Wrapped the ``setattr`` call in ``update_wrapper`` with a try-except
+# block to make it compatible with Python 2.3, which doesn't allow
+# assigning to ``__name__``.
+
+# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation.
+# All Rights Reserved.
+
+###############################################################################
+
+# update_wrapper() and wraps() are tools to help write
+# wrapper functions that can handle naive introspection
+
+WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
+WRAPPER_UPDATES = ('__dict__',)
+def update_wrapper(wrapper,
+ wrapped,
+ assigned = WRAPPER_ASSIGNMENTS,
+ updated = WRAPPER_UPDATES):
+ """Update a wrapper function to look like the wrapped function
+
+ wrapper is the function to be updated
+ wrapped is the original function
+ assigned is a tuple naming the attributes assigned directly
+ from the wrapped function to the wrapper function (defaults to
+ functools.WRAPPER_ASSIGNMENTS)
+ updated is a tuple naming the attributes off the wrapper that
+ are updated with the corresponding attribute from the wrapped
+ function (defaults to functools.WRAPPER_UPDATES)
+ """
+ for attr in assigned:
+ try:
+ setattr(wrapper, attr, getattr(wrapped, attr))
+ except TypeError: # Python 2.3 doesn't allow assigning to __name__.
+ pass
+ for attr in updated:
+ getattr(wrapper, attr).update(getattr(wrapped, attr))
+ # Return the wrapper so this can be used as a decorator via curry()
+ return wrapper
+
+def wraps(wrapped,
+ assigned = WRAPPER_ASSIGNMENTS,
+ updated = WRAPPER_UPDATES):
+ """Decorator factory to apply update_wrapper() to a wrapper function
+
+ Returns a decorator that invokes update_wrapper() with the decorated
+ function as the wrapper argument and the arguments to wraps() as the
+ remaining arguments. Default arguments are as for update_wrapper().
+ This is a convenience function to simplify applying curry() to
+ update_wrapper().
+ """
+ return curry(update_wrapper, wrapped=wrapped,
+ assigned=assigned, updated=updated)
+
+### End from Python 2.5 functools.py ##########################################
+
+def memoize(func, cache, num_args):
+ """
+ Wrap a function so that results for any argument tuple are stored in
+ 'cache'. Note that the args to the function must be usable as dictionary
+ keys.
+
+ Only the first num_args are considered when creating the key.
+ """
+ def wrapper(*args):
+ mem_args = args[:num_args]
+ if mem_args in cache:
+ return cache[mem_args]
+ result = func(*args)
+ cache[mem_args] = result
+ return result
+ return wraps(func)(wrapper)
+
+class Promise(object):
+ """
+ This is just a base class for the proxy class created in
+ the closure of the lazy function. It can be used to recognize
+ promises in code.
+ """
+ pass
+
+def lazy(func, *resultclasses):
+ """
+ Turns any callable into a lazy evaluated callable. You need to give result
+ classes or types -- at least one is needed so that the automatic forcing of
+ the lazy evaluation code is triggered. Results are not memoized; the
+ function is evaluated on every access.
+ """
+ class __proxy__(Promise):
+ # This inner class encapsulates the code that should be evaluated
+ # lazily. On calling of one of the magic methods it will force
+ # the evaluation and store the result. Afterwards, the result
+ # is delivered directly. So the result is memoized.
+ def __init__(self, args, kw):
+ self.__func = func
+ self.__args = args
+ self.__kw = kw
+ self.__dispatch = {}
+ for resultclass in resultclasses:
+ self.__dispatch[resultclass] = {}
+ for (k, v) in resultclass.__dict__.items():
+ setattr(self, k, self.__promise__(resultclass, k, v))
+ self._delegate_str = str in resultclasses
+ self._delegate_unicode = unicode in resultclasses
+ assert not (self._delegate_str and self._delegate_unicode), "Cannot call lazy() with both str and unicode return types."
+ if self._delegate_unicode:
+ # Each call to lazy() makes a new __proxy__ object, so this
+ # doesn't interfere with any other lazy() results.
+ __proxy__.__unicode__ = __proxy__.__unicode_cast
+ elif self._delegate_str:
+ __proxy__.__str__ = __proxy__.__str_cast
+
+ def __promise__(self, klass, funcname, func):
+ # Builds a wrapper around some magic method and registers that magic
+ # method for the given type and method name.
+ def __wrapper__(*args, **kw):
+ # Automatically triggers the evaluation of a lazy value and
+ # applies the given magic method of the result type.
+ res = self.__func(*self.__args, **self.__kw)
+ return self.__dispatch[type(res)][funcname](res, *args, **kw)
+
+ if klass not in self.__dispatch:
+ self.__dispatch[klass] = {}
+ self.__dispatch[klass][funcname] = func
+ return __wrapper__
+
+ def __unicode_cast(self):
+ return self.__func(*self.__args, **self.__kw)
+
+ def __str_cast(self):
+ return str(self.__func(*self.__args, **self.__kw))
+
+ def __cmp__(self, rhs):
+ if self._delegate_str:
+ s = str(self.__func(*self.__args, **self.__kw))
+ elif self._delegate_unicode:
+ s = unicode(self.__func(*self.__args, **self.__kw))
+ else:
+ s = self.__func(*self.__args, **self.__kw)
+ if isinstance(rhs, Promise):
+ return -cmp(rhs, s)
+ else:
+ return cmp(s, rhs)
+
+ def __mod__(self, rhs):
+ if self._delegate_str:
+ return str(self) % rhs
+ elif self._delegate_unicode:
+ return unicode(self) % rhs
+ else:
+ raise AssertionError('__mod__ not supported for non-string types')
+
+ def __deepcopy__(self, memo):
+ # Instances of this class are effectively immutable. It's just a
+ # collection of functions. So we don't need to do anything
+ # complicated for copying.
+ memo[id(self)] = self
+ return self
+
+ def __wrapper__(*args, **kw):
+ # Creates the proxy object, instead of the actual value.
+ return __proxy__(args, kw)
+
+ return wraps(func)(__wrapper__)
+
+def allow_lazy(func, *resultclasses):
+ """
+ A decorator that allows a function to be called with one or more lazy
+ arguments. If none of the args are lazy, the function is evaluated
+ immediately, otherwise a __proxy__ is returned that will evaluate the
+ function when needed.
+ """
+ def wrapper(*args, **kwargs):
+ for arg in list(args) + kwargs.values():
+ if isinstance(arg, Promise):
+ break
+ else:
+ return func(*args, **kwargs)
+ return lazy(func, *resultclasses)(*args, **kwargs)
+ return wraps(func)(wrapper)
diff --git a/deluge/ui/webui/lib/newforms_portable/django/utils/html.py b/deluge/ui/webui/lib/newforms_portable/django/utils/html.py
new file mode 100644
index 000000000..3ac39864d
--- /dev/null
+++ b/deluge/ui/webui/lib/newforms_portable/django/utils/html.py
@@ -0,0 +1,163 @@
+"""HTML utilities suitable for global use."""
+
+import re
+import string
+
+from safestring import SafeData, mark_safe
+from encoding import force_unicode
+from functional import allow_lazy
+from http import urlquote
+
+# Configuration for urlize() function.
+LEADING_PUNCTUATION = ['(', '<', '<']
+TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '>']
+
+# List of possible strings used for bullets in bulleted lists.
+DOTS = ['·', '*', '\xe2\x80\xa2', '', '•', '•']
+
+unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)')
+word_split_re = re.compile(r'(\s+)')
+punctuation_re = re.compile('^(?P(?:%s)*)(?P.*?)(?P(?:%s)*)$' % \
+ ('|'.join([re.escape(x) for x in LEADING_PUNCTUATION]),
+ '|'.join([re.escape(x) for x in TRAILING_PUNCTUATION])))
+simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
+link_target_attribute_re = re.compile(r'(]*?)target=[^\s>]+')
+html_gunk_re = re.compile(r'(?: |<\/i>|<\/b>|<\/em>|<\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE)
+hard_coded_bullets_re = re.compile(r'((?:
' % s
+ text = hard_coded_bullets_re.sub(replace_p_tags, text)
+ # Remove stuff like "
", but only if it's at the bottom
+ # of the text.
+ text = trailing_empty_content_re.sub('', text)
+ return text
+clean_html = allow_lazy(clean_html, unicode)
diff --git a/deluge/ui/webui/lib/newforms_portable/django/utils/http.py b/deluge/ui/webui/lib/newforms_portable/django/utils/http.py
new file mode 100644
index 000000000..db8bb9644
--- /dev/null
+++ b/deluge/ui/webui/lib/newforms_portable/django/utils/http.py
@@ -0,0 +1,67 @@
+import urllib
+from email.Utils import formatdate
+
+from encoding import smart_str, force_unicode
+from functional import allow_lazy
+
+def urlquote(url, safe='/'):
+ """
+ A version of Python's urllib.quote() function that can operate on unicode
+ strings. The url is first UTF-8 encoded before quoting. The returned string
+ can safely be used as part of an argument to a subsequent iri_to_uri() call
+ without double-quoting occurring.
+ """
+ return force_unicode(urllib.quote(smart_str(url), safe))
+
+urlquote = allow_lazy(urlquote, unicode)
+
+def urlquote_plus(url, safe=''):
+ """
+ A version of Python's urllib.quote_plus() function that can operate on
+ unicode strings. The url is first UTF-8 encoded before quoting. The
+ returned string can safely be used as part of an argument to a subsequent
+ iri_to_uri() call without double-quoting occurring.
+ """
+ return force_unicode(urllib.quote_plus(smart_str(url), safe))
+urlquote_plus = allow_lazy(urlquote_plus, unicode)
+
+def urlencode(query, doseq=0):
+ """
+ A version of Python's urllib.urlencode() function that can operate on
+ unicode strings. The parameters are first case to UTF-8 encoded strings and
+ then encoded as per normal.
+ """
+ if hasattr(query, 'items'):
+ query = query.items()
+ return urllib.urlencode(
+ [(smart_str(k),
+ isinstance(v, (list,tuple)) and [smart_str(i) for i in v] or smart_str(v))
+ for k, v in query],
+ doseq)
+
+def cookie_date(epoch_seconds=None):
+ """
+ Formats the time to ensure compatibility with Netscape's cookie standard.
+
+ Accepts a floating point number expressed in seconds since the epoch, in
+ UTC - such as that outputted by time.time(). If set to None, defaults to
+ the current time.
+
+ Outputs a string in the format 'Wdy, DD-Mon-YYYY HH:MM:SS GMT'.
+ """
+ rfcdate = formatdate(epoch_seconds)
+ return '%s-%s-%s GMT' % (rfcdate[:7], rfcdate[8:11], rfcdate[12:25])
+
+def http_date(epoch_seconds=None):
+ """
+ Formats the time to match the RFC1123 date format as specified by HTTP
+ RFC2616 section 3.3.1.
+
+ Accepts a floating point number expressed in seconds since the epoch, in
+ UTC - such as that outputted by time.time(). If set to None, defaults to
+ the current time.
+
+ Outputs a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'.
+ """
+ rfcdate = formatdate(epoch_seconds)
+ return '%s GMT' % rfcdate[:25]
diff --git a/deluge/ui/webui/lib/newforms_portable/django/utils/safestring.py b/deluge/ui/webui/lib/newforms_portable/django/utils/safestring.py
new file mode 100644
index 000000000..99658fb8b
--- /dev/null
+++ b/deluge/ui/webui/lib/newforms_portable/django/utils/safestring.py
@@ -0,0 +1,119 @@
+"""
+Functions for working with "safe strings": strings that can be displayed safely
+without further escaping in HTML. Marking something as a "safe string" means
+that the producer of the string has already turned characters that should not
+be interpreted by the HTML engine (e.g. '<') into the appropriate entities.
+"""
+from functional import curry, Promise
+
+class EscapeData(object):
+ pass
+
+class EscapeString(str, EscapeData):
+ """
+ A string that should be HTML-escaped when output.
+ """
+ pass
+
+class EscapeUnicode(unicode, EscapeData):
+ """
+ A unicode object that should be HTML-escaped when output.
+ """
+ pass
+
+class SafeData(object):
+ pass
+
+class SafeString(str, SafeData):
+ """
+ A string subclass that has been specifically marked as "safe" (requires no
+ further escaping) for HTML output purposes.
+ """
+ def __add__(self, rhs):
+ """
+ Concatenating a safe string with another safe string or safe unicode
+ object is safe. Otherwise, the result is no longer safe.
+ """
+ t = super(SafeString, self).__add__(rhs)
+ if isinstance(rhs, SafeUnicode):
+ return SafeUnicode(t)
+ elif isinstance(rhs, SafeString):
+ return SafeString(t)
+ return t
+
+ def _proxy_method(self, *args, **kwargs):
+ """
+ Wrap a call to a normal unicode method up so that we return safe
+ results. The method that is being wrapped is passed in the 'method'
+ argument.
+ """
+ method = kwargs.pop('method')
+ data = method(self, *args, **kwargs)
+ if isinstance(data, str):
+ return SafeString(data)
+ else:
+ return SafeUnicode(data)
+
+ decode = curry(_proxy_method, method = str.decode)
+
+class SafeUnicode(unicode, SafeData):
+ """
+ A unicode subclass that has been specifically marked as "safe" for HTML
+ output purposes.
+ """
+ def __add__(self, rhs):
+ """
+ Concatenating a safe unicode object with another safe string or safe
+ unicode object is safe. Otherwise, the result is no longer safe.
+ """
+ t = super(SafeUnicode, self).__add__(rhs)
+ if isinstance(rhs, SafeData):
+ return SafeUnicode(t)
+ return t
+
+ def _proxy_method(self, *args, **kwargs):
+ """
+ Wrap a call to a normal unicode method up so that we return safe
+ results. The method that is being wrapped is passed in the 'method'
+ argument.
+ """
+ method = kwargs.pop('method')
+ data = method(self, *args, **kwargs)
+ if isinstance(data, str):
+ return SafeString(data)
+ else:
+ return SafeUnicode(data)
+
+ encode = curry(_proxy_method, method = unicode.encode)
+
+def mark_safe(s):
+ """
+ Explicitly mark a string as safe for (HTML) output purposes. The returned
+ object can be used everywhere a string or unicode object is appropriate.
+
+ Can be called multiple times on a single string.
+ """
+ if isinstance(s, SafeData):
+ return s
+ if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str):
+ return SafeString(s)
+ if isinstance(s, (unicode, Promise)):
+ return SafeUnicode(s)
+ return SafeString(str(s))
+
+def mark_for_escaping(s):
+ """
+ Explicitly mark a string as requiring HTML escaping upon output. Has no
+ effect on SafeData subclasses.
+
+ Can be called multiple times on a single string (the resulting escaping is
+ only applied once).
+ """
+ if isinstance(s, (SafeData, EscapeData)):
+ return s
+ if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str):
+ return EscapeString(s)
+ if isinstance(s, (unicode, Promise)):
+ return EscapeUnicode(s)
+ return EscapeString(str(s))
+
diff --git a/deluge/ui/webui/lib/newforms_portable/django/utils/translation.py b/deluge/ui/webui/lib/newforms_portable/django/utils/translation.py
new file mode 100644
index 000000000..ad65bd959
--- /dev/null
+++ b/deluge/ui/webui/lib/newforms_portable/django/utils/translation.py
@@ -0,0 +1,9 @@
+try:
+ _('translate something')
+except:
+ import gettext
+ gettext.install('locale')
+
+ugettext = _
+ugettext_lazy = _
+
diff --git a/deluge/ui/webui/lib/newforms_portable/fields.py b/deluge/ui/webui/lib/newforms_portable/fields.py
new file mode 100644
index 000000000..c20899ada
--- /dev/null
+++ b/deluge/ui/webui/lib/newforms_portable/fields.py
@@ -0,0 +1,784 @@
+"""
+Field classes.
+"""
+
+import copy
+import datetime
+import os
+import re
+import time
+# Python 2.3 fallbacks
+try:
+ from decimal import Decimal, DecimalException
+except ImportError:
+ from django.utils._decimal import Decimal, DecimalException
+try:
+ set
+except NameError:
+ from sets import Set as set
+
+from django.utils.translation import ugettext_lazy as _
+from django.utils.encoding import StrAndUnicode, smart_unicode, smart_str
+
+from util import ErrorList, ValidationError
+from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
+
+
+__all__ = (
+ 'Field', 'CharField', 'IntegerField',
+ 'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
+ 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
+ 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
+ 'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
+ 'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
+ 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
+ 'SplitDateTimeField', 'IPAddressField', 'FilePathField',
+)
+
+# These values, if given to to_python(), will trigger the self.required check.
+EMPTY_VALUES = (None, '')
+
+
+class Field(object):
+ widget = TextInput # Default widget to use when rendering this type of Field.
+ hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
+ default_error_messages = {
+ 'required': _(u'This field is required.'),
+ 'invalid': _(u'Enter a valid value.'),
+ }
+
+ # Tracks each time a Field instance is created. Used to retain order.
+ creation_counter = 0
+
+ def __init__(self, required=True, widget=None, label=None, initial=None,
+ help_text=None, error_messages=None):
+ # required -- Boolean that specifies whether the field is required.
+ # True by default.
+ # widget -- A Widget class, or instance of a Widget class, that should
+ # be used for this Field when displaying it. Each Field has a
+ # default Widget that it'll use if you don't specify this. In
+ # most cases, the default widget is TextInput.
+ # label -- A verbose name for this field, for use in displaying this
+ # field in a form. By default, Django will use a "pretty"
+ # version of the form field name, if the Field is part of a
+ # Form.
+ # initial -- A value to use in this Field's initial display. This value
+ # is *not* used as a fallback if data isn't given.
+ # help_text -- An optional string to use as "help text" for this Field.
+ if label is not None:
+ label = smart_unicode(label)
+ self.required, self.label, self.initial = required, label, initial
+ self.help_text = smart_unicode(help_text or '')
+ widget = widget or self.widget
+ if isinstance(widget, type):
+ widget = widget()
+
+ # Hook into self.widget_attrs() for any Field-specific HTML attributes.
+ extra_attrs = self.widget_attrs(widget)
+ if extra_attrs:
+ widget.attrs.update(extra_attrs)
+
+ self.widget = widget
+
+ # Increase the creation counter, and save our local copy.
+ self.creation_counter = Field.creation_counter
+ Field.creation_counter += 1
+
+ def set_class_error_messages(messages, klass):
+ for base_class in klass.__bases__:
+ set_class_error_messages(messages, base_class)
+ messages.update(getattr(klass, 'default_error_messages', {}))
+
+ messages = {}
+ set_class_error_messages(messages, self.__class__)
+ messages.update(error_messages or {})
+ self.error_messages = messages
+
+ def clean(self, value):
+ """
+ Validates the given value and returns its "cleaned" value as an
+ appropriate Python object.
+
+ Raises ValidationError for any errors.
+ """
+ if self.required and value in EMPTY_VALUES:
+ raise ValidationError(self.error_messages['required'])
+ return value
+
+ def widget_attrs(self, widget):
+ """
+ Given a Widget instance (*not* a Widget class), returns a dictionary of
+ any HTML attributes that should be added to the Widget, based on this
+ Field.
+ """
+ return {}
+
+ def __deepcopy__(self, memo):
+ result = copy.copy(self)
+ memo[id(self)] = result
+ result.widget = copy.deepcopy(self.widget, memo)
+ return result
+
+class CharField(Field):
+ default_error_messages = {
+ 'max_length': _(u'Ensure this value has at most %(max)d characters (it has %(length)d).'),
+ 'min_length': _(u'Ensure this value has at least %(min)d characters (it has %(length)d).'),
+ }
+
+ def __init__(self, max_length=None, min_length=None, *args, **kwargs):
+ self.max_length, self.min_length = max_length, min_length
+ super(CharField, self).__init__(*args, **kwargs)
+
+ def clean(self, value):
+ "Validates max_length and min_length. Returns a Unicode object."
+ super(CharField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ value = smart_unicode(value)
+ value_length = len(value)
+ if self.max_length is not None and value_length > self.max_length:
+ raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length})
+ if self.min_length is not None and value_length < self.min_length:
+ raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length})
+ return value
+
+ def widget_attrs(self, widget):
+ if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
+ # The HTML attribute is maxlength, not max_length.
+ return {'maxlength': str(self.max_length)}
+
+class IntegerField(Field):
+ default_error_messages = {
+ 'invalid': _(u'Enter a whole number.'),
+ 'max_value': _(u'Ensure this value is less than or equal to %s.'),
+ 'min_value': _(u'Ensure this value is greater than or equal to %s.'),
+ }
+
+ def __init__(self, max_value=None, min_value=None, *args, **kwargs):
+ self.max_value, self.min_value = max_value, min_value
+ super(IntegerField, self).__init__(*args, **kwargs)
+
+ def clean(self, value):
+ """
+ Validates that int() can be called on the input. Returns the result
+ of int(). Returns None for empty values.
+ """
+ super(IntegerField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return None
+ try:
+ value = int(str(value))
+ except (ValueError, TypeError):
+ raise ValidationError(self.error_messages['invalid'])
+ if self.max_value is not None and value > self.max_value:
+ raise ValidationError(self.error_messages['max_value'] % self.max_value)
+ if self.min_value is not None and value < self.min_value:
+ raise ValidationError(self.error_messages['min_value'] % self.min_value)
+ return value
+
+class FloatField(Field):
+ default_error_messages = {
+ 'invalid': _(u'Enter a number.'),
+ 'max_value': _(u'Ensure this value is less than or equal to %s.'),
+ 'min_value': _(u'Ensure this value is greater than or equal to %s.'),
+ }
+
+ def __init__(self, max_value=None, min_value=None, *args, **kwargs):
+ self.max_value, self.min_value = max_value, min_value
+ Field.__init__(self, *args, **kwargs)
+
+ def clean(self, value):
+ """
+ Validates that float() can be called on the input. Returns a float.
+ Returns None for empty values.
+ """
+ super(FloatField, self).clean(value)
+ if not self.required and value in EMPTY_VALUES:
+ return None
+ try:
+ value = float(value)
+ except (ValueError, TypeError):
+ raise ValidationError(self.error_messages['invalid'])
+ if self.max_value is not None and value > self.max_value:
+ raise ValidationError(self.error_messages['max_value'] % self.max_value)
+ if self.min_value is not None and value < self.min_value:
+ raise ValidationError(self.error_messages['min_value'] % self.min_value)
+ return value
+
+class DecimalField(Field):
+ default_error_messages = {
+ 'invalid': _(u'Enter a number.'),
+ 'max_value': _(u'Ensure this value is less than or equal to %s.'),
+ 'min_value': _(u'Ensure this value is greater than or equal to %s.'),
+ 'max_digits': _('Ensure that there are no more than %s digits in total.'),
+ 'max_decimal_places': _('Ensure that there are no more than %s decimal places.'),
+ 'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.')
+ }
+
+ def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs):
+ self.max_value, self.min_value = max_value, min_value
+ self.max_digits, self.decimal_places = max_digits, decimal_places
+ Field.__init__(self, *args, **kwargs)
+
+ def clean(self, value):
+ """
+ Validates that the input is a decimal number. Returns a Decimal
+ instance. Returns None for empty values. Ensures that there are no more
+ than max_digits in the number, and no more than decimal_places digits
+ after the decimal point.
+ """
+ super(DecimalField, self).clean(value)
+ if not self.required and value in EMPTY_VALUES:
+ return None
+ value = smart_str(value).strip()
+ try:
+ value = Decimal(value)
+ except DecimalException:
+ raise ValidationError(self.error_messages['invalid'])
+ pieces = str(value).lstrip("-").split('.')
+ decimals = (len(pieces) == 2) and len(pieces[1]) or 0
+ digits = len(pieces[0])
+ if self.max_value is not None and value > self.max_value:
+ raise ValidationError(self.error_messages['max_value'] % self.max_value)
+ if self.min_value is not None and value < self.min_value:
+ raise ValidationError(self.error_messages['min_value'] % self.min_value)
+ if self.max_digits is not None and (digits + decimals) > self.max_digits:
+ raise ValidationError(self.error_messages['max_digits'] % self.max_digits)
+ if self.decimal_places is not None and decimals > self.decimal_places:
+ raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places)
+ if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places):
+ raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
+ return value
+
+DEFAULT_DATE_INPUT_FORMATS = (
+ '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
+ '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
+ '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
+ '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
+ '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
+)
+
+class DateField(Field):
+ default_error_messages = {
+ 'invalid': _(u'Enter a valid date.'),
+ }
+
+ def __init__(self, input_formats=None, *args, **kwargs):
+ super(DateField, self).__init__(*args, **kwargs)
+ self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
+
+ def clean(self, value):
+ """
+ Validates that the input can be converted to a date. Returns a Python
+ datetime.date object.
+ """
+ super(DateField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return None
+ if isinstance(value, datetime.datetime):
+ return value.date()
+ if isinstance(value, datetime.date):
+ return value
+ for format in self.input_formats:
+ try:
+ return datetime.date(*time.strptime(value, format)[:3])
+ except ValueError:
+ continue
+ raise ValidationError(self.error_messages['invalid'])
+
+DEFAULT_TIME_INPUT_FORMATS = (
+ '%H:%M:%S', # '14:30:59'
+ '%H:%M', # '14:30'
+)
+
+class TimeField(Field):
+ default_error_messages = {
+ 'invalid': _(u'Enter a valid time.')
+ }
+
+ def __init__(self, input_formats=None, *args, **kwargs):
+ super(TimeField, self).__init__(*args, **kwargs)
+ self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS
+
+ def clean(self, value):
+ """
+ Validates that the input can be converted to a time. Returns a Python
+ datetime.time object.
+ """
+ super(TimeField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return None
+ if isinstance(value, datetime.time):
+ return value
+ for format in self.input_formats:
+ try:
+ return datetime.time(*time.strptime(value, format)[3:6])
+ except ValueError:
+ continue
+ raise ValidationError(self.error_messages['invalid'])
+
+DEFAULT_DATETIME_INPUT_FORMATS = (
+ '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
+ '%Y-%m-%d %H:%M', # '2006-10-25 14:30'
+ '%Y-%m-%d', # '2006-10-25'
+ '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
+ '%m/%d/%Y %H:%M', # '10/25/2006 14:30'
+ '%m/%d/%Y', # '10/25/2006'
+ '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
+ '%m/%d/%y %H:%M', # '10/25/06 14:30'
+ '%m/%d/%y', # '10/25/06'
+)
+
+class DateTimeField(Field):
+ widget = DateTimeInput
+ default_error_messages = {
+ 'invalid': _(u'Enter a valid date/time.'),
+ }
+
+ def __init__(self, input_formats=None, *args, **kwargs):
+ super(DateTimeField, self).__init__(*args, **kwargs)
+ self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
+
+ def clean(self, value):
+ """
+ Validates that the input can be converted to a datetime. Returns a
+ Python datetime.datetime object.
+ """
+ super(DateTimeField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return None
+ if isinstance(value, datetime.datetime):
+ return value
+ if isinstance(value, datetime.date):
+ return datetime.datetime(value.year, value.month, value.day)
+ if isinstance(value, list):
+ # Input comes from a SplitDateTimeWidget, for example. So, it's two
+ # components: date and time.
+ if len(value) != 2:
+ raise ValidationError(self.error_messages['invalid'])
+ value = '%s %s' % tuple(value)
+ for format in self.input_formats:
+ try:
+ return datetime.datetime(*time.strptime(value, format)[:6])
+ except ValueError:
+ continue
+ raise ValidationError(self.error_messages['invalid'])
+
+class RegexField(CharField):
+ def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
+ """
+ regex can be either a string or a compiled regular expression object.
+ error_message is an optional error message to use, if
+ 'Enter a valid value' is too generic for you.
+ """
+ # error_message is just kept for backwards compatibility:
+ if error_message:
+ error_messages = kwargs.get('error_messages') or {}
+ error_messages['invalid'] = error_message
+ kwargs['error_messages'] = error_messages
+ super(RegexField, self).__init__(max_length, min_length, *args, **kwargs)
+ if isinstance(regex, basestring):
+ regex = re.compile(regex)
+ self.regex = regex
+
+ def clean(self, value):
+ """
+ Validates that the input matches the regular expression. Returns a
+ Unicode object.
+ """
+ value = super(RegexField, self).clean(value)
+ if value == u'':
+ return value
+ if not self.regex.search(value):
+ raise ValidationError(self.error_messages['invalid'])
+ return value
+
+email_re = re.compile(
+ r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
+ r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
+ r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
+
+class EmailField(RegexField):
+ default_error_messages = {
+ 'invalid': _(u'Enter a valid e-mail address.'),
+ }
+
+ def __init__(self, max_length=None, min_length=None, *args, **kwargs):
+ RegexField.__init__(self, email_re, max_length, min_length, *args,
+ **kwargs)
+
+try:
+ from django.conf import settings
+ URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT
+except ImportError:
+ # It's OK if Django settings aren't configured.
+ URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
+
+class UploadedFile(StrAndUnicode):
+ "A wrapper for files uploaded in a FileField"
+ def __init__(self, filename, content):
+ self.filename = filename
+ self.content = content
+
+ def __unicode__(self):
+ """
+ The unicode representation is the filename, so that the pre-database-insertion
+ logic can use UploadedFile objects
+ """
+ return self.filename
+
+class FileField(Field):
+ widget = FileInput
+ default_error_messages = {
+ 'invalid': _(u"No file was submitted. Check the encoding type on the form."),
+ 'missing': _(u"No file was submitted."),
+ 'empty': _(u"The submitted file is empty."),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(FileField, self).__init__(*args, **kwargs)
+
+ def clean(self, data, initial=None):
+ super(FileField, self).clean(initial or data)
+ if not self.required and data in EMPTY_VALUES:
+ return None
+ elif not data and initial:
+ return initial
+ try:
+ f = UploadedFile(data['filename'], data['content'])
+ except TypeError:
+ raise ValidationError(self.error_messages['invalid'])
+ except KeyError:
+ raise ValidationError(self.error_messages['missing'])
+ if not f.content:
+ raise ValidationError(self.error_messages['empty'])
+ return f
+
+class ImageField(FileField):
+ default_error_messages = {
+ 'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
+ }
+
+ def clean(self, data, initial=None):
+ """
+ Checks that the file-upload field data contains a valid image (GIF, JPG,
+ PNG, possibly others -- whatever the Python Imaging Library supports).
+ """
+ f = super(ImageField, self).clean(data, initial)
+ if f is None:
+ return None
+ elif not data and initial:
+ return initial
+ from PIL import Image
+ from cStringIO import StringIO
+ try:
+ # load() is the only method that can spot a truncated JPEG,
+ # but it cannot be called sanely after verify()
+ trial_image = Image.open(StringIO(f.content))
+ trial_image.load()
+ # verify() is the only method that can spot a corrupt PNG,
+ # but it must be called immediately after the constructor
+ trial_image = Image.open(StringIO(f.content))
+ trial_image.verify()
+ except Exception: # Python Imaging Library doesn't recognize it as an image
+ raise ValidationError(self.error_messages['invalid_image'])
+ return f
+
+url_re = re.compile(
+ r'^https?://' # http:// or https://
+ r'(?:(?:[A-Z0-9-]+\.)+[A-Z]{2,6}|' #domain...
+ r'localhost|' #localhost...
+ r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
+ r'(?::\d+)?' # optional port
+ r'(?:/?|/\S+)$', re.IGNORECASE)
+
+class URLField(RegexField):
+ default_error_messages = {
+ 'invalid': _(u'Enter a valid URL.'),
+ 'invalid_link': _(u'This URL appears to be a broken link.'),
+ }
+
+ def __init__(self, max_length=None, min_length=None, verify_exists=False,
+ validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs):
+ super(URLField, self).__init__(url_re, max_length, min_length, *args,
+ **kwargs)
+ self.verify_exists = verify_exists
+ self.user_agent = validator_user_agent
+
+ def clean(self, value):
+ # If no URL scheme given, assume http://
+ if value and '://' not in value:
+ value = u'http://%s' % value
+ value = super(URLField, self).clean(value)
+ if value == u'':
+ return value
+ if self.verify_exists:
+ import urllib2
+ from django.conf import settings
+ headers = {
+ "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
+ "Accept-Language": "en-us,en;q=0.5",
+ "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
+ "Connection": "close",
+ "User-Agent": self.user_agent,
+ }
+ try:
+ req = urllib2.Request(value, None, headers)
+ u = urllib2.urlopen(req)
+ except ValueError:
+ raise ValidationError(self.error_messages['invalid'])
+ except: # urllib2.URLError, httplib.InvalidURL, etc.
+ raise ValidationError(self.error_messages['invalid_link'])
+ return value
+
+class BooleanField(Field):
+ widget = CheckboxInput
+
+ def clean(self, value):
+ """Returns a Python boolean object."""
+ super(BooleanField, self).clean(value)
+ # Explicitly check for the string 'False', which is what a hidden field
+ # will submit for False. Because bool("True") == True, we don't need to
+ # handle that explicitly.
+ if value == 'False':
+ return False
+ return bool(value)
+
+class NullBooleanField(BooleanField):
+ """
+ A field whose valid values are None, True and False. Invalid values are
+ cleaned to None.
+ """
+ widget = NullBooleanSelect
+
+ def clean(self, value):
+ return {True: True, False: False}.get(value, None)
+
+class ChoiceField(Field):
+ widget = Select
+ default_error_messages = {
+ 'invalid_choice': _(u'Select a valid choice. That choice is not one of the available choices.'),
+ }
+
+ def __init__(self, choices=(), required=True, widget=None, label=None,
+ initial=None, help_text=None, *args, **kwargs):
+ super(ChoiceField, self).__init__(required, widget, label, initial,
+ help_text, *args, **kwargs)
+ self.choices = choices
+
+ def _get_choices(self):
+ return self._choices
+
+ def _set_choices(self, value):
+ # Setting choices also sets the choices on the widget.
+ # choices can be any iterable, but we call list() on it because
+ # it will be consumed more than once.
+ self._choices = self.widget.choices = list(value)
+
+ choices = property(_get_choices, _set_choices)
+
+ def clean(self, value):
+ """
+ Validates that the input is in self.choices.
+ """
+ value = super(ChoiceField, self).clean(value)
+ if value in EMPTY_VALUES:
+ value = u''
+ value = smart_unicode(value)
+ if value == u'':
+ return value
+ valid_values = set([smart_unicode(k) for k, v in self.choices])
+ if value not in valid_values:
+ raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
+ return value
+
+class MultipleChoiceField(ChoiceField):
+ hidden_widget = MultipleHiddenInput
+ widget = SelectMultiple
+ default_error_messages = {
+ 'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'),
+ 'invalid_list': _(u'Enter a list of values.'),
+ }
+
+ def clean(self, value):
+ """
+ Validates that the input is a list or tuple.
+ """
+ if self.required and not value:
+ raise ValidationError(self.error_messages['required'])
+ elif not self.required and not value:
+ return []
+ if not isinstance(value, (list, tuple)):
+ raise ValidationError(self.error_messages['invalid_list'])
+ new_value = [smart_unicode(val) for val in value]
+ # Validate that each value in the value list is in self.choices.
+ valid_values = set([smart_unicode(k) for k, v in self.choices])
+ for val in new_value:
+ if val not in valid_values:
+ raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
+ return new_value
+
+class ComboField(Field):
+ """
+ A Field whose clean() method calls multiple Field clean() methods.
+ """
+ def __init__(self, fields=(), *args, **kwargs):
+ super(ComboField, self).__init__(*args, **kwargs)
+ # Set 'required' to False on the individual fields, because the
+ # required validation will be handled by ComboField, not by those
+ # individual fields.
+ for f in fields:
+ f.required = False
+ self.fields = fields
+
+ def clean(self, value):
+ """
+ Validates the given value against all of self.fields, which is a
+ list of Field instances.
+ """
+ super(ComboField, self).clean(value)
+ for field in self.fields:
+ value = field.clean(value)
+ return value
+
+class MultiValueField(Field):
+ """
+ A Field that aggregates the logic of multiple Fields.
+
+ Its clean() method takes a "decompressed" list of values, which are then
+ cleaned into a single value according to self.fields. Each value in
+ this list is cleaned by the corresponding field -- the first value is
+ cleaned by the first field, the second value is cleaned by the second
+ field, etc. Once all fields are cleaned, the list of clean values is
+ "compressed" into a single value.
+
+ Subclasses should not have to implement clean(). Instead, they must
+ implement compress(), which takes a list of valid values and returns a
+ "compressed" version of those values -- a single value.
+
+ You'll probably want to use this with MultiWidget.
+ """
+ default_error_messages = {
+ 'invalid': _(u'Enter a list of values.'),
+ }
+
+ def __init__(self, fields=(), *args, **kwargs):
+ super(MultiValueField, self).__init__(*args, **kwargs)
+ # Set 'required' to False on the individual fields, because the
+ # required validation will be handled by MultiValueField, not by those
+ # individual fields.
+ for f in fields:
+ f.required = False
+ self.fields = fields
+
+ def clean(self, value):
+ """
+ Validates every value in the given list. A value is validated against
+ the corresponding Field in self.fields.
+
+ For example, if this MultiValueField was instantiated with
+ fields=(DateField(), TimeField()), clean() would call
+ DateField.clean(value[0]) and TimeField.clean(value[1]).
+ """
+ cleaned_data = []
+ errors = ErrorList()
+ if not value or isinstance(value, (list, tuple)):
+ if not value or not [v for v in value if v not in EMPTY_VALUES]:
+ if self.required:
+ raise ValidationError(self.error_messages['required'])
+ else:
+ return self.compress([])
+ else:
+ raise ValidationError(self.error_messages['invalid'])
+ for i, field in enumerate(self.fields):
+ try:
+ field_value = value[i]
+ except IndexError:
+ field_value = None
+ if self.required and field_value in EMPTY_VALUES:
+ raise ValidationError(self.error_messages['required'])
+ try:
+ cleaned_data.append(field.clean(field_value))
+ except ValidationError, e:
+ # Collect all validation errors in a single list, which we'll
+ # raise at the end of clean(), rather than raising a single
+ # exception for the first error we encounter.
+ errors.extend(e.messages)
+ if errors:
+ raise ValidationError(errors)
+ return self.compress(cleaned_data)
+
+ def compress(self, data_list):
+ """
+ Returns a single value for the given list of values. The values can be
+ assumed to be valid.
+
+ For example, if this MultiValueField was instantiated with
+ fields=(DateField(), TimeField()), this might return a datetime
+ object created by combining the date and time in data_list.
+ """
+ raise NotImplementedError('Subclasses must implement this method.')
+
+class FilePathField(ChoiceField):
+ def __init__(self, path, match=None, recursive=False, required=True,
+ widget=Select, label=None, initial=None, help_text=None,
+ *args, **kwargs):
+ self.path, self.match, self.recursive = path, match, recursive
+ super(FilePathField, self).__init__(choices=(), required=required,
+ widget=widget, label=label, initial=initial, help_text=help_text,
+ *args, **kwargs)
+ self.choices = []
+ if self.match is not None:
+ self.match_re = re.compile(self.match)
+ if recursive:
+ for root, dirs, files in os.walk(self.path):
+ for f in files:
+ if self.match is None or self.match_re.search(f):
+ f = os.path.join(root, f)
+ self.choices.append((f, f.replace(path, "", 1)))
+ else:
+ try:
+ for f in os.listdir(self.path):
+ full_file = os.path.join(self.path, f)
+ if os.path.isfile(full_file) and (self.match is None or self.match_re.search(f)):
+ self.choices.append((full_file, f))
+ except OSError:
+ pass
+ self.widget.choices = self.choices
+
+class SplitDateTimeField(MultiValueField):
+ default_error_messages = {
+ 'invalid_date': _(u'Enter a valid date.'),
+ 'invalid_time': _(u'Enter a valid time.'),
+ }
+
+ def __init__(self, *args, **kwargs):
+ errors = self.default_error_messages.copy()
+ if 'error_messages' in kwargs:
+ errors.update(kwargs['error_messages'])
+ fields = (
+ DateField(error_messages={'invalid': errors['invalid_date']}),
+ TimeField(error_messages={'invalid': errors['invalid_time']}),
+ )
+ super(SplitDateTimeField, self).__init__(fields, *args, **kwargs)
+
+ def compress(self, data_list):
+ if data_list:
+ # Raise a validation error if time or date is empty
+ # (possible if SplitDateTimeField has required=False).
+ if data_list[0] in EMPTY_VALUES:
+ raise ValidationError(self.error_messages['invalid_date'])
+ if data_list[1] in EMPTY_VALUES:
+ raise ValidationError(self.error_messages['invalid_time'])
+ return datetime.datetime.combine(*data_list)
+ return None
+
+ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
+
+class IPAddressField(RegexField):
+ default_error_messages = {
+ 'invalid': _(u'Enter a valid IPv4 address.'),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs)
diff --git a/deluge/ui/webui/lib/newforms/forms.py b/deluge/ui/webui/lib/newforms_portable/forms.py
similarity index 60%
rename from deluge/ui/webui/lib/newforms/forms.py
rename to deluge/ui/webui/lib/newforms_portable/forms.py
index df4af3b54..2c481e47a 100644
--- a/deluge/ui/webui/lib/newforms/forms.py
+++ b/deluge/ui/webui/lib/newforms_portable/forms.py
@@ -2,14 +2,18 @@
Form classes
"""
-from utils.datastructures import SortedDict, MultiValueDict
-from utils.html import escape
-from fields import Field
-from widgets import TextInput, Textarea, HiddenInput, MultipleHiddenInput
-from util import flatatt, StrAndUnicode, ErrorDict, ErrorList, ValidationError
-import copy
+from copy import deepcopy
-#__all__ = ('BaseForm', 'Form')
+from django.utils.datastructures import SortedDict
+from django.utils.html import escape
+from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
+from django.utils.safestring import mark_safe
+
+from fields import Field, FileField
+from widgets import TextInput, Textarea
+from util import flatatt, ErrorDict, ErrorList, ValidationError
+
+__all__ = ('BaseForm', 'Form')
NON_FIELD_ERRORS = '__all__'
@@ -18,17 +22,32 @@ def pretty_name(name):
name = name[0].upper() + name[1:]
return name.replace('_', ' ')
-class SortedDictFromList(SortedDict):
- "A dictionary that keeps its keys in the order in which they're inserted."
- # This is different than django.utils.datastructures.SortedDict, because
- # this takes a list/tuple as the argument to __init__().
- def __init__(self, data=None):
- if data is None: data = []
- self.keyOrder = [d[0] for d in data]
- dict.__init__(self, dict(data))
+def get_declared_fields(bases, attrs, with_base_fields=True):
+ """
+ Create a list of form field instances from the passed in 'attrs', plus any
+ similar fields on the base classes (in 'bases'). This is used by both the
+ Form and ModelForm metclasses.
- def copy(self):
- return SortedDictFromList([(k, copy.copy(v)) for k, v in self.items()])
+ If 'with_base_fields' is True, all fields from the bases are used.
+ Otherwise, only fields in the 'declared_fields' attribute on the bases are
+ used. The distinction is useful in ModelForm subclassing.
+ """
+ fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
+ fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
+
+ # If this class is subclassing another Form, add that Form's fields.
+ # Note that we loop over the bases in *reverse*. This is necessary in
+ # order to preserve the correct order of fields.
+ if with_base_fields:
+ for base in bases[::-1]:
+ if hasattr(base, 'base_fields'):
+ fields = base.base_fields.items() + fields
+ else:
+ for base in bases[::-1]:
+ if hasattr(base, 'declared_fields'):
+ fields = base.declared_fields.items() + fields
+
+ return SortedDict(fields)
class DeclarativeFieldsMetaclass(type):
"""
@@ -36,17 +55,7 @@ class DeclarativeFieldsMetaclass(type):
'base_fields', taking into account parent class 'base_fields' as well.
"""
def __new__(cls, name, bases, attrs):
- fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
- fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
-
- # If this class is subclassing another Form, add that Form's fields.
- # Note that we loop over the bases in *reverse*. This is necessary in
- # order to preserve the correct order of fields.
- for base in bases[::-1]:
- if hasattr(base, 'base_fields'):
- fields = base.base_fields.items() + fields
-
- attrs['base_fields'] = SortedDictFromList(fields)
+ attrs['base_fields'] = get_declared_fields(bases, attrs)
return type.__new__(cls, name, bases, attrs)
class BaseForm(StrAndUnicode):
@@ -54,20 +63,24 @@ class BaseForm(StrAndUnicode):
# class is different than Form. See the comments by the Form class for more
# information. Any improvements to the form API should be made to *this*
# class, not to the Form class.
- def __init__(self, data=None, auto_id='id_%s', prefix=None, initial=None):
- self.is_bound = data is not None
+ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
+ initial=None, error_class=ErrorList, label_suffix=':'):
+ self.is_bound = data is not None or files is not None
self.data = data or {}
+ self.files = files or {}
self.auto_id = auto_id
self.prefix = prefix
self.initial = initial or {}
- self.__errors = None # Stores the errors after clean() has been called.
+ self.error_class = error_class
+ self.label_suffix = label_suffix
+ self._errors = None # Stores the errors after clean() has been called.
# The base_fields class attribute is the *class-wide* definition of
# fields. Because a particular *instance* of the class might want to
# alter self.fields, we create self.fields here by copying base_fields.
# Instances should always modify self.fields; they should not modify
# self.base_fields.
- self.fields = self.base_fields.copy()
+ self.fields = deepcopy(self.base_fields)
def __unicode__(self):
return self.as_table()
@@ -84,12 +97,12 @@ class BaseForm(StrAndUnicode):
raise KeyError('Key %r not found in Form' % name)
return BoundField(self, field, name)
- def _errors(self):
- "Returns an ErrorDict for self.data"
- if self.__errors is None:
+ def _get_errors(self):
+ "Returns an ErrorDict for the data provided for the form"
+ if self._errors is None:
self.full_clean()
- return self.__errors
- errors = property(_errors)
+ return self._errors
+ errors = property(_get_errors)
def is_valid(self):
"""
@@ -113,31 +126,43 @@ class BaseForm(StrAndUnicode):
output, hidden_fields = [], []
for name, field in self.fields.items():
bf = BoundField(self, field, name)
- bf_errors = ErrorList([escape(error) for error in bf.errors]) # Escape and cache in local variable.
+ bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable.
if bf.is_hidden:
if bf_errors:
- top_errors.extend(['(Hidden field %s) %s' % (name, e) for e in bf_errors])
+ top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
hidden_fields.append(unicode(bf))
else:
if errors_on_separate_row and bf_errors:
- output.append(error_row % bf_errors)
- label = bf.label and bf.label_tag(escape(bf.label + ':')) or ''
+ output.append(error_row % force_unicode(bf_errors))
+ if bf.label:
+ label = escape(force_unicode(bf.label))
+ # Only add the suffix if the label does not end in
+ # punctuation.
+ if self.label_suffix:
+ if label[-1] not in ':?.!':
+ label += self.label_suffix
+ label = bf.label_tag(label) or ''
+ else:
+ label = ''
if field.help_text:
- help_text = help_text_html % field.help_text
+ help_text = help_text_html % force_unicode(field.help_text)
else:
help_text = u''
- output.append(normal_row % {'errors': bf_errors, 'label': label, 'field': unicode(bf), 'help_text': help_text})
+ output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
if top_errors:
- output.insert(0, error_row % top_errors)
+ output.insert(0, error_row % force_unicode(top_errors))
if hidden_fields: # Insert any hidden fields in the last row.
str_hidden = u''.join(hidden_fields)
if output:
last_row = output[-1]
- # Chop off the trailing row_ender (e.g. '') and insert the hidden fields.
+ # Chop off the trailing row_ender (e.g. '') and
+ # insert the hidden fields.
output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
- else: # If there aren't any rows in the output, just append the hidden fields.
+ else:
+ # If there aren't any rows in the output, just append the
+ # hidden fields.
output.append(str_hidden)
- return u'\n'.join(output)
+ return mark_safe(u'\n'.join(output))
def as_table(self):
"Returns this form rendered as HTML
s -- excluding the
."
@@ -149,7 +174,7 @@ class BaseForm(StrAndUnicode):
def as_p(self):
"Returns this form rendered as HTML
', u'%s', '', u' %s', True)
def non_field_errors(self):
"""
@@ -157,37 +182,42 @@ class BaseForm(StrAndUnicode):
field -- i.e., from Form.clean(). Returns an empty ErrorList if there
are none.
"""
- return self.errors.get(NON_FIELD_ERRORS, ErrorList())
+ return self.errors.get(NON_FIELD_ERRORS, self.error_class())
def full_clean(self):
"""
- Cleans all of self.data and populates self.__errors and self.clean_data.
+ Cleans all of self.data and populates self._errors and
+ self.cleaned_data.
"""
- errors = ErrorDict()
+ self._errors = ErrorDict()
if not self.is_bound: # Stop further processing.
- self.__errors = errors
return
- self.clean_data = {}
+ self.cleaned_data = {}
for name, field in self.fields.items():
- # value_from_datadict() gets the data from the dictionary.
+ # value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
- value = field.widget.value_from_datadict(self.data, self.add_prefix(name))
+ value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
- value = field.clean(value)
- self.clean_data[name] = value
+ if isinstance(field, FileField):
+ initial = self.initial.get(name, field.initial)
+ value = field.clean(value, initial)
+ else:
+ value = field.clean(value)
+ self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
- self.clean_data[name] = value
+ self.cleaned_data[name] = value
except ValidationError, e:
- errors[name] = e.messages
+ self._errors[name] = e.messages
+ if name in self.cleaned_data:
+ del self.cleaned_data[name]
try:
- self.clean_data = self.clean()
+ self.cleaned_data = self.clean()
except ValidationError, e:
- errors[NON_FIELD_ERRORS] = e.messages
- if errors:
- delattr(self, 'clean_data')
- self.__errors = errors
+ self._errors[NON_FIELD_ERRORS] = e.messages
+ if self._errors:
+ delattr(self, 'cleaned_data')
def clean(self):
"""
@@ -196,7 +226,17 @@ class BaseForm(StrAndUnicode):
not be associated with a particular field; it will have a special-case
association with the field named '__all__'.
"""
- return self.clean_data
+ return self.cleaned_data
+
+ def is_multipart(self):
+ """
+ Returns True if the form needs to be multipart-encrypted, i.e. it has
+ FileInput. Otherwise, False.
+ """
+ for field in self.fields.values():
+ if field.widget.needs_multipart_form:
+ return True
+ return False
class Form(BaseForm):
"A collection of Fields, plus their associated data."
@@ -221,32 +261,33 @@ class BoundField(StrAndUnicode):
self.help_text = field.help_text or ''
def __unicode__(self):
- "Renders this field as an HTML widget."
- # Use the 'widget' attribute on the field to determine which type
- # of HTML widget to use.
- value = self.as_widget(self.field.widget)
- if not isinstance(value, basestring):
- # Some Widget render() methods -- notably RadioSelect -- return a
- # "special" object rather than a string. Call the __str__() on that
- # object to get its rendered value.
- value = value.__str__()
- return value
+ """Renders this field as an HTML widget."""
+ return self.as_widget()
def _errors(self):
"""
Returns an ErrorList for this field. Returns an empty ErrorList
if there are none.
"""
- return self.form.errors.get(self.name, ErrorList())
+ return self.form.errors.get(self.name, self.form.error_class())
errors = property(_errors)
- def as_widget(self, widget, attrs=None):
+ def as_widget(self, widget=None, attrs=None):
+ """
+ Renders the field by rendering the passed widget, adding any HTML
+ attributes passed as attrs. If no widget is specified, then the
+ field's default widget will be used.
+ """
+ if not widget:
+ widget = self.field.widget
attrs = attrs or {}
auto_id = self.auto_id
- if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'):
+ if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
attrs['id'] = auto_id
if not self.form.is_bound:
data = self.form.initial.get(self.name, self.field.initial)
+ if callable(data):
+ data = data()
else:
data = self.data
return widget.render(self.html_name, data, attrs=attrs)
@@ -271,7 +312,7 @@ class BoundField(StrAndUnicode):
"""
Returns the data for this BoundField, or None if it wasn't given.
"""
- return self.field.widget.value_from_datadict(self.form.data, self.html_name)
+ return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
data = property(_data)
def label_tag(self, contents=None, attrs=None):
@@ -288,7 +329,7 @@ class BoundField(StrAndUnicode):
if id_:
attrs = attrs and flatatt(attrs) or ''
contents = '' % (widget.id_for_label(id_), attrs, contents)
- return contents
+ return mark_safe(contents)
def _is_hidden(self):
"Returns True if this BoundField's widget is hidden."
@@ -301,8 +342,8 @@ class BoundField(StrAndUnicode):
associated Form has specified auto_id. Returns an empty string otherwise.
"""
auto_id = self.form.auto_id
- if auto_id and '%s' in str(auto_id):
- return str(auto_id) % self.html_name
+ if auto_id and '%s' in smart_unicode(auto_id):
+ return smart_unicode(auto_id) % self.html_name
elif auto_id:
return self.html_name
return ''
diff --git a/deluge/ui/webui/lib/newforms_portable/models.py b/deluge/ui/webui/lib/newforms_portable/models.py
new file mode 100644
index 000000000..0590839b2
--- /dev/null
+++ b/deluge/ui/webui/lib/newforms_portable/models.py
@@ -0,0 +1,398 @@
+"""
+Helper functions for creating Form classes from Django models
+and database field objects.
+"""
+
+from warnings import warn
+
+from django.utils.translation import ugettext_lazy as _
+from django.utils.encoding import smart_unicode
+from django.utils.datastructures import SortedDict
+from django.core.exceptions import ImproperlyConfigured
+
+from util import ValidationError, ErrorList
+from forms import BaseForm, get_declared_fields
+from fields import Field, ChoiceField, EMPTY_VALUES
+from widgets import Select, SelectMultiple, MultipleHiddenInput
+
+__all__ = (
+ 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
+ 'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
+ 'ModelChoiceField', 'ModelMultipleChoiceField'
+)
+
+def save_instance(form, instance, fields=None, fail_message='saved',
+ commit=True):
+ """
+ Saves bound Form ``form``'s cleaned_data into model instance ``instance``.
+
+ If commit=True, then the changes to ``instance`` will be saved to the
+ database. Returns ``instance``.
+ """
+ from django.db import models
+ opts = instance.__class__._meta
+ if form.errors:
+ raise ValueError("The %s could not be %s because the data didn't"
+ " validate." % (opts.object_name, fail_message))
+ cleaned_data = form.cleaned_data
+ for f in opts.fields:
+ if not f.editable or isinstance(f, models.AutoField) \
+ or not f.name in cleaned_data:
+ continue
+ if fields and f.name not in fields:
+ continue
+ f.save_form_data(instance, cleaned_data[f.name])
+ # Wrap up the saving of m2m data as a function.
+ def save_m2m():
+ opts = instance.__class__._meta
+ cleaned_data = form.cleaned_data
+ for f in opts.many_to_many:
+ if fields and f.name not in fields:
+ continue
+ if f.name in cleaned_data:
+ f.save_form_data(instance, cleaned_data[f.name])
+ if commit:
+ # If we are committing, save the instance and the m2m data immediately.
+ instance.save()
+ save_m2m()
+ else:
+ # We're not committing. Add a method to the form to allow deferred
+ # saving of m2m data.
+ form.save_m2m = save_m2m
+ return instance
+
+def make_model_save(model, fields, fail_message):
+ """Returns the save() method for a Form."""
+ def save(self, commit=True):
+ return save_instance(self, model(), fields, fail_message, commit)
+ return save
+
+def make_instance_save(instance, fields, fail_message):
+ """Returns the save() method for a Form."""
+ def save(self, commit=True):
+ return save_instance(self, instance, fields, fail_message, commit)
+ return save
+
+def form_for_model(model, form=BaseForm, fields=None,
+ formfield_callback=lambda f: f.formfield()):
+ """
+ Returns a Form class for the given Django model class.
+
+ Provide ``form`` if you want to use a custom BaseForm subclass.
+
+ Provide ``formfield_callback`` if you want to define different logic for
+ determining the formfield for a given database field. It's a callable that
+ takes a database Field instance and returns a form Field instance.
+ """
+ warn("form_for_model is deprecated. Use ModelForm instead.",
+ PendingDeprecationWarning, stacklevel=3)
+ opts = model._meta
+ field_list = []
+ for f in opts.fields + opts.many_to_many:
+ if not f.editable:
+ continue
+ if fields and not f.name in fields:
+ continue
+ formfield = formfield_callback(f)
+ if formfield:
+ field_list.append((f.name, formfield))
+ base_fields = SortedDict(field_list)
+ return type(opts.object_name + 'Form', (form,),
+ {'base_fields': base_fields, '_model': model,
+ 'save': make_model_save(model, fields, 'created')})
+
+def form_for_instance(instance, form=BaseForm, fields=None,
+ formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
+ """
+ Returns a Form class for the given Django model instance.
+
+ Provide ``form`` if you want to use a custom BaseForm subclass.
+
+ Provide ``formfield_callback`` if you want to define different logic for
+ determining the formfield for a given database field. It's a callable that
+ takes a database Field instance, plus **kwargs, and returns a form Field
+ instance with the given kwargs (i.e. 'initial').
+ """
+ warn("form_for_instance is deprecated. Use ModelForm instead.",
+ PendingDeprecationWarning, stacklevel=3)
+ model = instance.__class__
+ opts = model._meta
+ field_list = []
+ for f in opts.fields + opts.many_to_many:
+ if not f.editable:
+ continue
+ if fields and not f.name in fields:
+ continue
+ current_value = f.value_from_object(instance)
+ formfield = formfield_callback(f, initial=current_value)
+ if formfield:
+ field_list.append((f.name, formfield))
+ base_fields = SortedDict(field_list)
+ return type(opts.object_name + 'InstanceForm', (form,),
+ {'base_fields': base_fields, '_model': model,
+ 'save': make_instance_save(instance, fields, 'changed')})
+
+def form_for_fields(field_list):
+ """
+ Returns a Form class for the given list of Django database field instances.
+ """
+ fields = SortedDict([(f.name, f.formfield())
+ for f in field_list if f.editable])
+ return type('FormForFields', (BaseForm,), {'base_fields': fields})
+
+
+# ModelForms #################################################################
+
+def model_to_dict(instance, fields=None, exclude=None):
+ """
+ Returns a dict containing the data in ``instance`` suitable for passing as
+ a Form's ``initial`` keyword argument.
+
+ ``fields`` is an optional list of field names. If provided, only the named
+ fields will be included in the returned dict.
+
+ ``exclude`` is an optional list of field names. If provided, the named
+ fields will be excluded from the returned dict, even if they are listed in
+ the ``fields`` argument.
+ """
+ # avoid a circular import
+ from django.db.models.fields.related import ManyToManyField
+ opts = instance._meta
+ data = {}
+ for f in opts.fields + opts.many_to_many:
+ if not f.editable:
+ continue
+ if fields and not f.name in fields:
+ continue
+ if exclude and f.name in exclude:
+ continue
+ if isinstance(f, ManyToManyField):
+ # If the object doesn't have a primry key yet, just use an empty
+ # list for its m2m fields. Calling f.value_from_object will raise
+ # an exception.
+ if instance.pk is None:
+ data[f.name] = []
+ else:
+ # MultipleChoiceWidget needs a list of pks, not object instances.
+ data[f.name] = [obj.pk for obj in f.value_from_object(instance)]
+ else:
+ data[f.name] = f.value_from_object(instance)
+ return data
+
+def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda f: f.formfield()):
+ """
+ Returns a ``SortedDict`` containing form fields for the given model.
+
+ ``fields`` is an optional list of field names. If provided, only the named
+ fields will be included in the returned fields.
+
+ ``exclude`` is an optional list of field names. If provided, the named
+ fields will be excluded from the returned fields, even if they are listed
+ in the ``fields`` argument.
+ """
+ # TODO: if fields is provided, it would be nice to return fields in that order
+ field_list = []
+ opts = model._meta
+ for f in opts.fields + opts.many_to_many:
+ if not f.editable:
+ continue
+ if fields and not f.name in fields:
+ continue
+ if exclude and f.name in exclude:
+ continue
+ formfield = formfield_callback(f)
+ if formfield:
+ field_list.append((f.name, formfield))
+ return SortedDict(field_list)
+
+class ModelFormOptions(object):
+ def __init__(self, options=None):
+ self.model = getattr(options, 'model', None)
+ self.fields = getattr(options, 'fields', None)
+ self.exclude = getattr(options, 'exclude', None)
+
+
+class ModelFormMetaclass(type):
+ def __new__(cls, name, bases, attrs,
+ formfield_callback=lambda f: f.formfield()):
+ try:
+ parents = [b for b in bases if issubclass(b, ModelForm)]
+ except NameError:
+ # We are defining ModelForm itself.
+ parents = None
+ if not parents:
+ return super(ModelFormMetaclass, cls).__new__(cls, name, bases,
+ attrs)
+
+ new_class = type.__new__(cls, name, bases, attrs)
+ declared_fields = get_declared_fields(bases, attrs, False)
+ opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
+ if opts.model:
+ # If a model is defined, extract form fields from it.
+ fields = fields_for_model(opts.model, opts.fields,
+ opts.exclude, formfield_callback)
+ # Override default model fields with any custom declared ones
+ # (plus, include all the other declared fields).
+ fields.update(declared_fields)
+ else:
+ fields = declared_fields
+ new_class.declared_fields = declared_fields
+ new_class.base_fields = fields
+ return new_class
+
+class BaseModelForm(BaseForm):
+ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
+ initial=None, error_class=ErrorList, label_suffix=':',
+ instance=None):
+ opts = self._meta
+ if instance is None:
+ # if we didn't get an instance, instantiate a new one
+ self.instance = opts.model()
+ object_data = {}
+ else:
+ self.instance = instance
+ object_data = model_to_dict(instance, opts.fields, opts.exclude)
+ # if initial was provided, it should override the values from instance
+ if initial is not None:
+ object_data.update(initial)
+ BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix)
+
+ def save(self, commit=True):
+ """
+ Saves this ``form``'s cleaned_data into model instance
+ ``self.instance``.
+
+ If commit=True, then the changes to ``instance`` will be saved to the
+ database. Returns ``instance``.
+ """
+ if self.instance.pk is None:
+ fail_message = 'created'
+ else:
+ fail_message = 'changed'
+ return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
+
+class ModelForm(BaseModelForm):
+ __metaclass__ = ModelFormMetaclass
+
+
+# Fields #####################################################################
+
+class ModelChoiceIterator(object):
+ def __init__(self, field):
+ self.field = field
+ self.queryset = field.queryset
+
+ def __iter__(self):
+ if self.field.empty_label is not None:
+ yield (u"", self.field.empty_label)
+ for obj in self.queryset:
+ yield (obj.pk, self.field.label_from_instance(obj))
+ # Clear the QuerySet cache if required.
+ if not self.field.cache_choices:
+ self.queryset._result_cache = None
+
+class ModelChoiceField(ChoiceField):
+ """A ChoiceField whose choices are a model QuerySet."""
+ # This class is a subclass of ChoiceField for purity, but it doesn't
+ # actually use any of ChoiceField's implementation.
+ default_error_messages = {
+ 'invalid_choice': _(u'Select a valid choice. That choice is not one of'
+ u' the available choices.'),
+ }
+
+ def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
+ required=True, widget=Select, label=None, initial=None,
+ help_text=None, *args, **kwargs):
+ self.empty_label = empty_label
+ self.cache_choices = cache_choices
+
+ # Call Field instead of ChoiceField __init__() because we don't need
+ # ChoiceField.__init__().
+ Field.__init__(self, required, widget, label, initial, help_text,
+ *args, **kwargs)
+ self.queryset = queryset
+
+ def _get_queryset(self):
+ return self._queryset
+
+ def _set_queryset(self, queryset):
+ self._queryset = queryset
+ self.widget.choices = self.choices
+
+ queryset = property(_get_queryset, _set_queryset)
+
+ # this method will be used to create object labels by the QuerySetIterator.
+ # Override it to customize the label.
+ def label_from_instance(self, obj):
+ """
+ This method is used to convert objects into strings; it's used to
+ generate the labels for the choices presented by this object. Subclasses
+ can override this method to customize the display of the choices.
+ """
+ return smart_unicode(obj)
+
+ def _get_choices(self):
+ # If self._choices is set, then somebody must have manually set
+ # the property self.choices. In this case, just return self._choices.
+ if hasattr(self, '_choices'):
+ return self._choices
+
+ # Otherwise, execute the QuerySet in self.queryset to determine the
+ # choices dynamically. Return a fresh QuerySetIterator that has not been
+ # consumed. Note that we're instantiating a new QuerySetIterator *each*
+ # time _get_choices() is called (and, thus, each time self.choices is
+ # accessed) so that we can ensure the QuerySet has not been consumed. This
+ # construct might look complicated but it allows for lazy evaluation of
+ # the queryset.
+ return ModelChoiceIterator(self)
+
+ def _set_choices(self, value):
+ # This method is copied from ChoiceField._set_choices(). It's necessary
+ # because property() doesn't allow a subclass to overwrite only
+ # _get_choices without implementing _set_choices.
+ self._choices = self.widget.choices = list(value)
+
+ choices = property(_get_choices, _set_choices)
+
+ def clean(self, value):
+ Field.clean(self, value)
+ if value in EMPTY_VALUES:
+ return None
+ try:
+ value = self.queryset.get(pk=value)
+ except self.queryset.model.DoesNotExist:
+ raise ValidationError(self.error_messages['invalid_choice'])
+ return value
+
+class ModelMultipleChoiceField(ModelChoiceField):
+ """A MultipleChoiceField whose choices are a model QuerySet."""
+ hidden_widget = MultipleHiddenInput
+ default_error_messages = {
+ 'list': _(u'Enter a list of values.'),
+ 'invalid_choice': _(u'Select a valid choice. %s is not one of the'
+ u' available choices.'),
+ }
+
+ def __init__(self, queryset, cache_choices=False, required=True,
+ widget=SelectMultiple, label=None, initial=None,
+ help_text=None, *args, **kwargs):
+ super(ModelMultipleChoiceField, self).__init__(queryset, None,
+ cache_choices, required, widget, label, initial, help_text,
+ *args, **kwargs)
+
+ def clean(self, value):
+ if self.required and not value:
+ raise ValidationError(self.error_messages['required'])
+ elif not self.required and not value:
+ return []
+ if not isinstance(value, (list, tuple)):
+ raise ValidationError(self.error_messages['list'])
+ final_values = []
+ for val in value:
+ try:
+ obj = self.queryset.get(pk=val)
+ except self.queryset.model.DoesNotExist:
+ raise ValidationError(self.error_messages['invalid_choice'] % val)
+ else:
+ final_values.append(obj)
+ return final_values
diff --git a/deluge/ui/webui/lib/newforms_portable/util.py b/deluge/ui/webui/lib/newforms_portable/util.py
new file mode 100644
index 000000000..b3edf41ad
--- /dev/null
+++ b/deluge/ui/webui/lib/newforms_portable/util.py
@@ -0,0 +1,69 @@
+from django.utils.html import escape
+from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
+from django.utils.functional import Promise
+from django.utils.safestring import mark_safe
+
+def flatatt(attrs):
+ """
+ Convert a dictionary of attributes to a single string.
+ The returned string will contain a leading space followed by key="value",
+ XML-style pairs. It is assumed that the keys do not need to be XML-escaped.
+ If the passed dictionary is empty, then return an empty string.
+ """
+ return u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
+
+class ErrorDict(dict, StrAndUnicode):
+ """
+ A collection of errors that knows how to display itself in various formats.
+
+ The dictionary keys are the field names, and the values are the errors.
+ """
+ def __unicode__(self):
+ return self.as_ul()
+
+ def as_ul(self):
+ if not self: return u''
+ return mark_safe(u'
%s
'
+ % ''.join([u'
%s%s
' % (k, force_unicode(v))
+ for k, v in self.items()]))
+
+ def as_text(self):
+ return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u' * %s' % force_unicode(i) for i in v])) for k, v in self.items()])
+
+class ErrorList(list, StrAndUnicode):
+ """
+ A collection of errors that knows how to display itself in various formats.
+ """
+ def __unicode__(self):
+ return self.as_ul()
+
+ def as_ul(self):
+ if not self: return u''
+ return mark_safe(u'
%s
'
+ % ''.join([u'
%s
' % force_unicode(e) for e in self]))
+
+ def as_text(self):
+ if not self: return u''
+ return u'\n'.join([u'* %s' % force_unicode(e) for e in self])
+
+ def __repr__(self):
+ return repr([force_unicode(e) for e in self])
+
+class ValidationError(Exception):
+ def __init__(self, message):
+ """
+ ValidationError can be passed any object that can be printed (usually
+ a string) or a list of objects.
+ """
+ if isinstance(message, list):
+ self.messages = ErrorList([smart_unicode(msg) for msg in message])
+ else:
+ message = smart_unicode(message)
+ self.messages = ErrorList([message])
+
+ def __str__(self):
+ # This is needed because, without a __str__(), printing an exception
+ # instance would result in this:
+ # AttributeError: ValidationError instance has no attribute 'args'
+ # See http://www.python.org/doc/current/tut/node10.html#handling
+ return repr(self.messages)
diff --git a/deluge/ui/webui/lib/newforms/widgets.py b/deluge/ui/webui/lib/newforms_portable/widgets.py
similarity index 56%
rename from deluge/ui/webui/lib/newforms/widgets.py
rename to deluge/ui/webui/lib/newforms_portable/widgets.py
index f58c137d0..20a7cab46 100644
--- a/deluge/ui/webui/lib/newforms/widgets.py
+++ b/deluge/ui/webui/lib/newforms_portable/widgets.py
@@ -2,29 +2,44 @@
HTML Widget classes
"""
-__all__ = (
- 'Widget', 'TextInput', 'PasswordInput', 'HiddenInput', 'MultipleHiddenInput',
- 'FileInput', 'Textarea', 'CheckboxInput',
- 'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', 'CheckboxSelectMultiple',
- 'MultiWidget', 'SplitDateTimeWidget',
-)
+try:
+ set
+except NameError:
+ from sets import Set as set # Python 2.3 fallback
-from util import flatatt, StrAndUnicode, smart_unicode
-from utils.datastructures import MultiValueDict
-from utils.html import escape
-from gettext import gettext
+import copy
from itertools import chain
-try:
- set # Only available in Python 2.4+
-except NameError:
- from sets import Set as set # Python 2.3 fallback
+from django.utils.datastructures import MultiValueDict
+from django.utils.html import escape, conditional_escape
+from django.utils.translation import ugettext
+from django.utils.encoding import StrAndUnicode, force_unicode
+from django.utils.safestring import mark_safe
+from util import flatatt
+
+__all__ = (
+ 'Widget', 'TextInput', 'PasswordInput',
+ 'HiddenInput', 'MultipleHiddenInput',
+ 'FileInput', 'DateTimeInput', 'Textarea', 'CheckboxInput',
+ 'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
+ 'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
+)
class Widget(object):
is_hidden = False # Determines whether this corresponds to an .
+ needs_multipart_form = False # Determines does this widget need multipart-encrypted form
def __init__(self, attrs=None):
- self.attrs = attrs or {}
+ if attrs is not None:
+ self.attrs = attrs.copy()
+ else:
+ self.attrs = {}
+
+ def __deepcopy__(self, memo):
+ obj = copy.copy(self)
+ obj.attrs = self.attrs.copy()
+ memo[id(self)] = obj
+ return obj
def render(self, name, value, attrs=None):
"""
@@ -42,7 +57,7 @@ class Widget(object):
attrs.update(extra_attrs)
return attrs
- def value_from_datadict(self, data, name):
+ def value_from_datadict(self, data, files, name):
"""
Given a dictionary of data and this widget's name, returns the value
of this widget. Returns None if it's not provided.
@@ -72,8 +87,10 @@ class Input(Widget):
def render(self, name, value, attrs=None):
if value is None: value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
- if value != '': final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty.
- return u'' % flatatt(final_attrs)
+ if value != '':
+ # Only add the 'value' attribute if a value is non-empty.
+ final_attrs['value'] = force_unicode(value)
+ return mark_safe(u'' % flatatt(final_attrs))
class TextInput(Input):
input_type = 'text'
@@ -82,7 +99,7 @@ class PasswordInput(Input):
input_type = 'password'
def __init__(self, attrs=None, render_value=True):
- self.attrs = attrs or {}
+ super(PasswordInput, self).__init__(attrs)
self.render_value = render_value
def render(self, name, value, attrs=None):
@@ -99,35 +116,68 @@ class MultipleHiddenInput(HiddenInput):
of values.
"""
def __init__(self, attrs=None, choices=()):
+ super(MultipleHiddenInput, self).__init__(attrs)
# choices can be any iterable
- self.attrs = attrs or {}
self.choices = choices
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 u'\n'.join([(u'' % flatatt(dict(value=smart_unicode(v), **final_attrs))) for v in value])
+ return mark_safe(u'\n'.join([(u'' %
+ flatatt(dict(value=force_unicode(v), **final_attrs)))
+ for v in value]))
- def value_from_datadict(self, data, name):
+ def value_from_datadict(self, data, files, name):
if isinstance(data, MultiValueDict):
return data.getlist(name)
return data.get(name, None)
class FileInput(Input):
input_type = 'file'
+ needs_multipart_form = True
+
+ def render(self, name, value, attrs=None):
+ return super(FileInput, self).render(name, None, attrs=attrs)
+
+ def value_from_datadict(self, data, files, name):
+ "File widgets take data from FILES, not POST"
+ return files.get(name, None)
class Textarea(Widget):
+ def __init__(self, attrs=None):
+ # The 'rows' and 'cols' attributes are required for HTML correctness.
+ self.attrs = {'cols': '40', 'rows': '10'}
+ if attrs:
+ self.attrs.update(attrs)
+
def render(self, name, value, attrs=None):
if value is None: value = ''
- value = smart_unicode(value)
+ value = force_unicode(value)
final_attrs = self.build_attrs(attrs, name=name)
- return u'' % (flatatt(final_attrs), escape(value))
+ return mark_safe(u'' % (flatatt(final_attrs),
+ conditional_escape(force_unicode(value))))
+
+class DateTimeInput(Input):
+ input_type = 'text'
+ format = '%Y-%m-%d %H:%M:%S' # '2006-10-25 14:30:59'
+
+ def __init__(self, attrs=None, format=None):
+ super(DateTimeInput, 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(DateTimeInput, self).render(name, value, attrs)
class CheckboxInput(Widget):
def __init__(self, attrs=None, check_test=bool):
+ super(CheckboxInput, self).__init__(attrs)
# check_test is a callable that takes a value and returns True
# if the checkbox should be checked for that value.
- self.attrs = attrs or {}
self.check_test = check_test
def render(self, name, value, attrs=None):
@@ -139,12 +189,20 @@ class CheckboxInput(Widget):
if result:
final_attrs['checked'] = 'checked'
if value not in ('', True, False, None):
- final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty.
- return u'' % flatatt(final_attrs)
+ # Only add the 'value' attribute if a value is non-empty.
+ final_attrs['value'] = force_unicode(value)
+ return mark_safe(u'' % flatatt(final_attrs))
+
+ def value_from_datadict(self, data, files, name):
+ if name not in data:
+ # 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)
class Select(Widget):
def __init__(self, attrs=None, choices=()):
- self.attrs = attrs or {}
+ super(Select, self).__init__(attrs)
# choices can be any iterable, but we may need to render this widget
# multiple times. Thus, collapse it into a list so it can be consumed
# more than once.
@@ -154,20 +212,23 @@ class Select(Widget):
if value is None: value = ''
final_attrs = self.build_attrs(attrs, name=name)
output = [u'')
- return u'\n'.join(output)
+ return mark_safe(u'\n'.join(output))
class NullBooleanSelect(Select):
"""
A Select Widget intended to be used with NullBooleanField.
"""
def __init__(self, attrs=None):
- choices = ((u'1', gettext('Unknown')), (u'2', gettext('Yes')), (u'3', gettext('No')))
+ choices = ((u'1', ugettext('Unknown')), (u'2', ugettext('Yes')), (u'3', ugettext('No')))
super(NullBooleanSelect, self).__init__(attrs, choices)
def render(self, name, value, attrs=None, choices=()):
@@ -177,58 +238,68 @@ class NullBooleanSelect(Select):
value = u'1'
return super(NullBooleanSelect, self).render(name, value, attrs, choices)
- def value_from_datadict(self, data, name):
+ def value_from_datadict(self, data, files, name):
value = data.get(name, None)
return {u'2': True, u'3': False, True: True, False: False}.get(value, None)
class SelectMultiple(Widget):
def __init__(self, attrs=None, choices=()):
+ super(SelectMultiple, self).__init__(attrs)
# choices can be any iterable
- self.attrs = attrs or {}
self.choices = choices
def render(self, name, value, attrs=None, choices=()):
if value is None: value = []
final_attrs = self.build_attrs(attrs, name=name)
output = [u'')
- return u'\n'.join(output)
+ return mark_safe(u'\n'.join(output))
- def value_from_datadict(self, data, name):
+ def value_from_datadict(self, data, files, name):
if isinstance(data, MultiValueDict):
return data.getlist(name)
return data.get(name, None)
class RadioInput(StrAndUnicode):
- "An object used by RadioFieldRenderer that represents a single ."
+ """
+ An object used by RadioFieldRenderer that represents a single
+ .
+ """
+
def __init__(self, name, value, attrs, choice, index):
self.name, self.value = name, value
self.attrs = attrs
- self.choice_value = smart_unicode(choice[0])
- self.choice_label = smart_unicode(choice[1])
+ self.choice_value = force_unicode(choice[0])
+ self.choice_label = force_unicode(choice[1])
self.index = index
def __unicode__(self):
- return u'' % (self.tag(), self.choice_label)
+ return mark_safe(u'' % (self.tag(),
+ conditional_escape(force_unicode(self.choice_label))))
def is_checked(self):
return self.value == self.choice_value
def tag(self):
- if self.attrs.has_key('id'):
+ if 'id' in self.attrs:
self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
if self.is_checked():
final_attrs['checked'] = 'checked'
- return u'' % flatatt(final_attrs)
+ return mark_safe(u'' % flatatt(final_attrs))
class RadioFieldRenderer(StrAndUnicode):
- "An object used by RadioSelect to enable customization of radio widgets."
+ """
+ An object used by RadioSelect to enable customization of radio widgets.
+ """
+
def __init__(self, name, value, attrs, choices):
self.name, self.value, self.attrs = name, value, attrs
self.choices = choices
@@ -242,16 +313,33 @@ class RadioFieldRenderer(StrAndUnicode):
return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
def __unicode__(self):
- "Outputs a
for this set of radio fields."
- return u'
\n%s\n
' % u'\n'.join([u'
%s
' % w for w in self])
+ return self.render()
+
+ def render(self):
+ """Outputs a
for this set of radio fields."""
+ return mark_safe(u'
\n%s\n
' % u'\n'.join([u'
%s
'
+ % force_unicode(w) for w in self]))
class RadioSelect(Select):
- def render(self, name, value, attrs=None, choices=()):
- "Returns a RadioFieldRenderer instance rather than a Unicode string."
+ renderer = RadioFieldRenderer
+
+ def __init__(self, *args, **kwargs):
+ # Override the default renderer if we were passed one.
+ renderer = kwargs.pop('renderer', None)
+ if renderer:
+ self.renderer = renderer
+ super(RadioSelect, self).__init__(*args, **kwargs)
+
+ def get_renderer(self, name, value, attrs=None, choices=()):
+ """Returns an instance of the renderer."""
if value is None: value = ''
- str_value = smart_unicode(value) # Normalize to string.
- attrs = attrs or {}
- return RadioFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices)))
+ str_value = force_unicode(value) # Normalize to string.
+ final_attrs = self.build_attrs(attrs)
+ choices = list(chain(self.choices, choices))
+ return self.renderer(name, str_value, final_attrs, choices)
+
+ def render(self, name, value, attrs=None, choices=()):
+ return self.get_renderer(name, value, attrs, choices).render()
def id_for_label(self, id_):
# RadioSelect is represented by multiple fields,
@@ -266,21 +354,23 @@ class RadioSelect(Select):
class CheckboxSelectMultiple(SelectMultiple):
def render(self, name, value, attrs=None, choices=()):
if value is None: value = []
- has_id = attrs and attrs.has_key('id')
+ has_id = attrs and 'id' in attrs
final_attrs = self.build_attrs(attrs, name=name)
output = [u'
']
- str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
+ # Normalize to strings
+ str_values = set([force_unicode(v) for v in value])
for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
# If an ID attribute was given, add a numeric index as a suffix,
# so that the checkboxes don't all have the same ID attribute.
if has_id:
final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
- option_value = smart_unicode(option_value)
+ option_value = force_unicode(option_value)
rendered_cb = cb.render(name, option_value)
- output.append(u'' % (rendered_cb, escape(smart_unicode(option_label))))
+ output.append(u'' % (rendered_cb,
+ conditional_escape(force_unicode(option_label))))
output.append(u'
')
- return u'\n'.join(output)
+ return mark_safe(u'\n'.join(output))
def id_for_label(self, id_):
# See the comment for RadioSelect.id_for_label()
@@ -293,19 +383,28 @@ class MultiWidget(Widget):
"""
A widget that is composed of multiple widgets.
- Its render() method takes a "decompressed" list of values, not a single
- value. Each value in this list is rendered in the corresponding widget --
- the first value is rendered in the first widget, the second value is
- rendered in the second widget, etc.
+ Its render() method is different than other widgets', because it has to
+ figure out how to split a single value for display in multiple widgets.
+ The ``value`` argument can be one of two things:
- Subclasses should implement decompress(), which specifies how a single
- value should be converted to a list of values. Subclasses should not
- have to implement clean().
+ * A list.
+ * A normal value (e.g., a string) that has been "compressed" from
+ a list of values.
+
+ In the second case -- i.e., if the value is NOT a list -- render() will
+ first "decompress" the value into a list before rendering it. It does so by
+ calling the decompress() method, which MultiWidget subclasses must
+ implement. This method takes a single "compressed" value and returns a
+ list.
+
+ When render() does its HTML rendering, each value in the list is rendered
+ with the corresponding widget -- the first value is rendered in the first
+ widget, the second value is rendered in the second widget, etc.
Subclasses may implement format_output(), which takes the list of rendered
- widgets and returns HTML that formats them any way you'd like.
+ widgets and returns a string of HTML that formats them any way you'd like.
- You'll probably want to use this with MultiValueField.
+ You'll probably want to use this class with MultiValueField.
"""
def __init__(self, widgets, attrs=None):
self.widgets = [isinstance(w, type) and w() or w for w in widgets]
@@ -317,18 +416,36 @@ class MultiWidget(Widget):
if not isinstance(value, list):
value = self.decompress(value)
output = []
+ final_attrs = self.build_attrs(attrs)
+ id_ = final_attrs.get('id', None)
for i, widget in enumerate(self.widgets):
try:
widget_value = value[i]
- except KeyError:
+ except IndexError:
widget_value = None
- output.append(widget.render(name + '_%s' % i, widget_value, attrs))
- return self.format_output(output)
+ if id_:
+ final_attrs = dict(final_attrs, id='%s_%s' % (id_, i))
+ output.append(widget.render(name + '_%s' % i, widget_value, final_attrs))
+ return mark_safe(self.format_output(output))
- def value_from_datadict(self, data, name):
- return [data.get(name + '_%s' % i) for i in range(len(self.widgets))]
+ def id_for_label(self, id_):
+ # See the comment for RadioSelect.id_for_label()
+ if id_:
+ id_ += '_0'
+ return id_
+ id_for_label = classmethod(id_for_label)
+
+ 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 format_output(self, rendered_widgets):
+ """
+ Given a list of rendered widgets (as strings), returns a Unicode string
+ representing the HTML for the whole lot.
+
+ This hook allows you to format the HTML design of the widgets, if
+ needed.
+ """
return u''.join(rendered_widgets)
def decompress(self, value):
@@ -349,5 +466,6 @@ class SplitDateTimeWidget(MultiWidget):
def decompress(self, value):
if value:
- return [value.date(), value.time()]
+ return [value.date(), value.time().replace(microsecond=0)]
return [None, None]
+
diff --git a/deluge/ui/webui/pages.py b/deluge/ui/webui/pages.py
index 2e67656bb..53bddfc08 100644
--- a/deluge/ui/webui/pages.py
+++ b/deluge/ui/webui/pages.py
@@ -103,7 +103,6 @@ urls = [
]
#/routing
-
#pages:
class login:
@deco.deluge_page_noauth
diff --git a/deluge/ui/webui/torrent_add.py b/deluge/ui/webui/torrent_add.py
index 35f1b19dc..bc81c063e 100644
--- a/deluge/ui/webui/torrent_add.py
+++ b/deluge/ui/webui/torrent_add.py
@@ -102,7 +102,7 @@ class torrent_add:
if not options_form.is_valid():
print self.add_page(error = _("Error in torrent options."))
return
- options = options_form.clean_data
+ options = options_form.cleaned_data
vars = web.input(url = None, torrent = {})
diff --git a/deluge/ui/webui/torrent_move.py b/deluge/ui/webui/torrent_move.py
index 0afb04209..4fb16696a 100644
--- a/deluge/ui/webui/torrent_move.py
+++ b/deluge/ui/webui/torrent_move.py
@@ -70,6 +70,6 @@ class torrent_move:
if not form.is_valid():
print self.move_page(name, error = _("Error in Path."))
return
- save_path = form.clean_data["save_path"]
+ save_path = form.cleaned_data["save_path"]
proxy.move_torrent(torrent_ids, save_path)
utils.do_redirect()