mirror of
https://git.deluge-torrent.org/deluge
synced 2025-04-21 03:54:50 +00:00
newforms->newforms_portable
This commit is contained in:
parent
0eaa0aca27
commit
b87089c7d2
28 changed files with 2460 additions and 808 deletions
|
@ -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:
|
||||
|
|
|
@ -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']):
|
||||
|
|
|
@ -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
|
|
@ -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'<ul class="errorlist">%s</ul>' % ''.join([u'<li>%s%s</li>' % (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'<ul class="errorlist">%s</ul>' % ''.join([u'<li>%s</li>' % 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)
|
|
@ -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("'", ''')
|
|
@ -1,12 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) Martijn Voncken 2008 <mvoncken@gmail.com>
|
||||
# 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()
|
||||
|
|
|
@ -14,3 +14,5 @@ from util import ValidationError
|
|||
from widgets import *
|
||||
from fields import *
|
||||
from forms import *
|
||||
from models import *
|
||||
import django
|
3
deluge/ui/webui/lib/newforms_portable/about.txt
Normal file
3
deluge/ui/webui/lib/newforms_portable/about.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
based on django rev.7350
|
||||
,/django/ contains the parts of django required to run newforms.
|
||||
|
|
@ -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
|
|
@ -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 "<MultiValueDict: %s>" % 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='<omitted>')
|
||||
return dict.__repr__(d)
|
||||
return dict.__repr__(self)
|
102
deluge/ui/webui/lib/newforms_portable/django/utils/encoding.py
Normal file
102
deluge/ui/webui/lib/newforms_portable/django/utils/encoding.py
Normal file
|
@ -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='/#%[]=:;$&()+,!?*')
|
||||
|
241
deluge/ui/webui/lib/newforms_portable/django/utils/functional.py
Normal file
241
deluge/ui/webui/lib/newforms_portable/django/utils/functional.py
Normal file
|
@ -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)
|
163
deluge/ui/webui/lib/newforms_portable/django/utils/html.py
Normal file
163
deluge/ui/webui/lib/newforms_portable/django/utils/html.py
Normal file
|
@ -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<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%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'(<a [^>]*?)target=[^\s>]+')
|
||||
html_gunk_re = re.compile(r'(?:<br clear="all">|<i><\/i>|<b><\/b>|<em><\/em>|<strong><\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE)
|
||||
hard_coded_bullets_re = re.compile(r'((?:<p>(?:%s).*?[a-zA-Z].*?</p>\s*)+)' % '|'.join([re.escape(x) for x in DOTS]), re.DOTALL)
|
||||
trailing_empty_content_re = re.compile(r'(?:<p>(?: |\s|<br \/>)*?</p>\s*)+\Z')
|
||||
del x # Temporary variable
|
||||
|
||||
def escape(html):
|
||||
"""Returns the given HTML with ampersands, quotes and carets encoded."""
|
||||
return mark_safe(force_unicode(html).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", '''))
|
||||
escape = allow_lazy(escape, unicode)
|
||||
|
||||
def conditional_escape(html):
|
||||
"""
|
||||
Similar to escape(), except that it doesn't operate on pre-escaped strings.
|
||||
"""
|
||||
if isinstance(html, SafeData):
|
||||
return html
|
||||
else:
|
||||
return escape(html)
|
||||
|
||||
def linebreaks(value, autoescape=False):
|
||||
"""Converts newlines into <p> and <br />s."""
|
||||
value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines
|
||||
paras = re.split('\n{2,}', value)
|
||||
if autoescape:
|
||||
paras = [u'<p>%s</p>' % escape(p.strip()).replace('\n', '<br />') for p in paras]
|
||||
else:
|
||||
paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
|
||||
return u'\n\n'.join(paras)
|
||||
linebreaks = allow_lazy(linebreaks, unicode)
|
||||
|
||||
def strip_tags(value):
|
||||
"""Returns the given HTML with all tags stripped."""
|
||||
return re.sub(r'<[^>]*?>', '', force_unicode(value))
|
||||
strip_tags = allow_lazy(strip_tags)
|
||||
|
||||
def strip_spaces_between_tags(value):
|
||||
"""Returns the given HTML with spaces between tags removed."""
|
||||
return re.sub(r'>\s+<', '><', force_unicode(value))
|
||||
strip_spaces_between_tags = allow_lazy(strip_spaces_between_tags, unicode)
|
||||
|
||||
def strip_entities(value):
|
||||
"""Returns the given HTML with all entities (&something;) stripped."""
|
||||
return re.sub(r'&(?:\w+|#\d+);', '', force_unicode(value))
|
||||
strip_entities = allow_lazy(strip_entities, unicode)
|
||||
|
||||
def fix_ampersands(value):
|
||||
"""Returns the given HTML with all unencoded ampersands encoded correctly."""
|
||||
return unencoded_ampersands_re.sub('&', force_unicode(value))
|
||||
fix_ampersands = allow_lazy(fix_ampersands, unicode)
|
||||
|
||||
def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
|
||||
"""
|
||||
Converts any URLs in text into clickable links.
|
||||
|
||||
Works on http://, https://, and www. links. Links can have trailing
|
||||
punctuation (periods, commas, close-parens) and leading punctuation
|
||||
(opening parens) and it'll still do the right thing.
|
||||
|
||||
If trim_url_limit is not None, the URLs in link text longer than this limit
|
||||
will truncated to trim_url_limit-3 characters and appended with an elipsis.
|
||||
|
||||
If nofollow is True, the URLs in link text will get a rel="nofollow"
|
||||
attribute.
|
||||
"""
|
||||
if autoescape:
|
||||
trim_url = lambda x, limit=trim_url_limit: conditional_escape(limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x)
|
||||
else:
|
||||
trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
|
||||
safe_input = isinstance(text, SafeData)
|
||||
words = word_split_re.split(force_unicode(text))
|
||||
nofollow_attr = nofollow and ' rel="nofollow"' or ''
|
||||
for i, word in enumerate(words):
|
||||
match = punctuation_re.match(word)
|
||||
if match:
|
||||
lead, middle, trail = match.groups()
|
||||
if safe_input:
|
||||
middle = mark_safe(middle)
|
||||
if middle.startswith('www.') or ('@' not in middle and not middle.startswith('http://') and \
|
||||
len(middle) > 0 and middle[0] in string.ascii_letters + string.digits and \
|
||||
(middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))):
|
||||
middle = 'http://%s' % middle
|
||||
if middle.startswith('http://') or middle.startswith('https://'):
|
||||
url = urlquote(middle, safe='/&=:;#?+*')
|
||||
if autoescape and not safe_input:
|
||||
url = escape(url)
|
||||
trimmed_url = trim_url(middle)
|
||||
middle = '<a href="%s"%s>%s</a>' % (url, nofollow_attr,
|
||||
trimmed_url)
|
||||
elif '@' in middle and not middle.startswith('www.') and \
|
||||
not ':' in middle and simple_email_re.match(middle):
|
||||
if autoescape:
|
||||
middle = conditional_escape(middle)
|
||||
middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
|
||||
if lead + middle + trail != word:
|
||||
if autoescape and not safe_input:
|
||||
lead, trail = escape(lead), escape(trail)
|
||||
words[i] = mark_safe('%s%s%s' % (lead, middle, trail))
|
||||
elif autoescape and not safe_input:
|
||||
words[i] = escape(word)
|
||||
elif safe_input:
|
||||
words[i] = mark_safe(word)
|
||||
elif autoescape:
|
||||
words[i] = escape(word)
|
||||
return u''.join(words)
|
||||
urlize = allow_lazy(urlize, unicode)
|
||||
|
||||
def clean_html(text):
|
||||
"""
|
||||
Clean the given HTML. Specifically, do the following:
|
||||
* Convert <b> and <i> to <strong> and <em>.
|
||||
* Encode all ampersands correctly.
|
||||
* Remove all "target" attributes from <a> tags.
|
||||
* Remove extraneous HTML, such as presentational tags that open and
|
||||
immediately close and <br clear="all">.
|
||||
* Convert hard-coded bullets into HTML unordered lists.
|
||||
* Remove stuff like "<p> </p>", but only if it's at the
|
||||
bottom of the text.
|
||||
"""
|
||||
from django.utils.text import normalize_newlines
|
||||
text = normalize_newlines(force_unicode(text))
|
||||
text = re.sub(r'<(/?)\s*b\s*>', '<\\1strong>', text)
|
||||
text = re.sub(r'<(/?)\s*i\s*>', '<\\1em>', text)
|
||||
text = fix_ampersands(text)
|
||||
# Remove all target="" attributes from <a> tags.
|
||||
text = link_target_attribute_re.sub('\\1', text)
|
||||
# Trim stupid HTML such as <br clear="all">.
|
||||
text = html_gunk_re.sub('', text)
|
||||
# Convert hard-coded bullets into HTML unordered lists.
|
||||
def replace_p_tags(match):
|
||||
s = match.group().replace('</p>', '</li>')
|
||||
for d in DOTS:
|
||||
s = s.replace('<p>%s' % d, '<li>')
|
||||
return u'<ul>\n%s\n</ul>' % s
|
||||
text = hard_coded_bullets_re.sub(replace_p_tags, text)
|
||||
# Remove stuff like "<p> </p>", 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)
|
67
deluge/ui/webui/lib/newforms_portable/django/utils/http.py
Normal file
67
deluge/ui/webui/lib/newforms_portable/django/utils/http.py
Normal file
|
@ -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]
|
119
deluge/ui/webui/lib/newforms_portable/django/utils/safestring.py
Normal file
119
deluge/ui/webui/lib/newforms_portable/django/utils/safestring.py
Normal file
|
@ -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))
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
try:
|
||||
_('translate something')
|
||||
except:
|
||||
import gettext
|
||||
gettext.install('locale')
|
||||
|
||||
ugettext = _
|
||||
ugettext_lazy = _
|
||||
|
784
deluge/ui/webui/lib/newforms_portable/fields.py
Normal file
784
deluge/ui/webui/lib/newforms_portable/fields.py
Normal file
|
@ -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)
|
|
@ -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. '</td></tr>') and insert the hidden fields.
|
||||
# Chop off the trailing row_ender (e.g. '</td></tr>') 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 <tr>s -- excluding the <table></table>."
|
||||
|
@ -149,7 +174,7 @@ class BaseForm(StrAndUnicode):
|
|||
|
||||
def as_p(self):
|
||||
"Returns this form rendered as HTML <p>s."
|
||||
return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'<p>%s</p>', '</p>', u' %s', True)
|
||||
return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', 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 = '<label for="%s"%s>%s</label>' % (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 ''
|
398
deluge/ui/webui/lib/newforms_portable/models.py
Normal file
398
deluge/ui/webui/lib/newforms_portable/models.py
Normal file
|
@ -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
|
69
deluge/ui/webui/lib/newforms_portable/util.py
Normal file
69
deluge/ui/webui/lib/newforms_portable/util.py
Normal file
|
@ -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'<ul class="errorlist">%s</ul>'
|
||||
% ''.join([u'<li>%s%s</li>' % (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'<ul class="errorlist">%s</ul>'
|
||||
% ''.join([u'<li>%s</li>' % 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)
|
|
@ -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 <input type="hidden">.
|
||||
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'<input%s />' % 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'<input%s />' % 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'<input%s />' % flatatt(dict(value=smart_unicode(v), **final_attrs))) for v in value])
|
||||
return mark_safe(u'\n'.join([(u'<input%s />' %
|
||||
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'<textarea%s>%s</textarea>' % (flatatt(final_attrs), escape(value))
|
||||
return mark_safe(u'<textarea%s>%s</textarea>' % (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'<input%s />' % flatatt(final_attrs)
|
||||
# Only add the 'value' attribute if a value is non-empty.
|
||||
final_attrs['value'] = force_unicode(value)
|
||||
return mark_safe(u'<input%s />' % 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'<select%s>' % flatatt(final_attrs)]
|
||||
str_value = smart_unicode(value) # Normalize to string.
|
||||
# Normalize to string.
|
||||
str_value = force_unicode(value)
|
||||
for option_value, option_label in chain(self.choices, choices):
|
||||
option_value = smart_unicode(option_value)
|
||||
option_value = force_unicode(option_value)
|
||||
selected_html = (option_value == str_value) and u' selected="selected"' or ''
|
||||
output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(smart_unicode(option_label))))
|
||||
output.append(u'<option value="%s"%s>%s</option>' % (
|
||||
escape(option_value), selected_html,
|
||||
conditional_escape(force_unicode(option_label))))
|
||||
output.append(u'</select>')
|
||||
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'<select multiple="multiple"%s>' % flatatt(final_attrs)]
|
||||
str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
|
||||
str_values = set([force_unicode(v) for v in value]) # Normalize to strings.
|
||||
for option_value, option_label in chain(self.choices, choices):
|
||||
option_value = smart_unicode(option_value)
|
||||
option_value = force_unicode(option_value)
|
||||
selected_html = (option_value in str_values) and ' selected="selected"' or ''
|
||||
output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(smart_unicode(option_label))))
|
||||
output.append(u'<option value="%s"%s>%s</option>' % (
|
||||
escape(option_value), selected_html,
|
||||
conditional_escape(force_unicode(option_label))))
|
||||
output.append(u'</select>')
|
||||
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 <input type='radio'>."
|
||||
"""
|
||||
An object used by RadioFieldRenderer that represents a single
|
||||
<input type='radio'>.
|
||||
"""
|
||||
|
||||
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'<label>%s %s</label>' % (self.tag(), self.choice_label)
|
||||
return mark_safe(u'<label>%s %s</label>' % (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'<input%s />' % flatatt(final_attrs)
|
||||
return mark_safe(u'<input%s />' % 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 <ul> for this set of radio fields."
|
||||
return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self])
|
||||
return self.render()
|
||||
|
||||
def render(self):
|
||||
"""Outputs a <ul> for this set of radio fields."""
|
||||
return mark_safe(u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>'
|
||||
% 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 <input type="radio"> 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'<ul>']
|
||||
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'<li><label>%s %s</label></li>' % (rendered_cb, escape(smart_unicode(option_label))))
|
||||
output.append(u'<li><label>%s %s</label></li>' % (rendered_cb,
|
||||
conditional_escape(force_unicode(option_label))))
|
||||
output.append(u'</ul>')
|
||||
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]
|
||||
|
|
@ -103,7 +103,6 @@ urls = [
|
|||
]
|
||||
#/routing
|
||||
|
||||
|
||||
#pages:
|
||||
class login:
|
||||
@deco.deluge_page_noauth
|
||||
|
|
|
@ -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 = {})
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Reference in a new issue