summaryrefslogtreecommitdiff
path: root/django/core
diff options
context:
space:
mode:
authorRobin Munn <robin.munn@gmail.com>2006-11-08 04:50:01 +0000
committerRobin Munn <robin.munn@gmail.com>2006-11-08 04:50:01 +0000
commitdadfca08c0db567ce33284aaa8eb388cf667a836 (patch)
treeab7255eeee1bbe03d95652cc74a3843fa052d8ac /django/core
parent0b059aa4eadc1d95ceca3a32821b65a9fb2a53e8 (diff)
sqlalchemy: Merged revisions 3918 to 4053 from trunk.
git-svn-id: http://code.djangoproject.com/svn/django/branches/sqlalchemy@4054 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/core')
-rw-r--r--django/core/cache/__init__.py2
-rw-r--r--django/core/handlers/base.py2
-rw-r--r--django/core/handlers/modpython.py3
-rw-r--r--django/core/mail.py4
-rw-r--r--django/core/management.py24
-rw-r--r--django/core/paginator.py75
-rw-r--r--django/core/serializers/__init__.py2
-rw-r--r--django/core/serializers/base.py10
-rw-r--r--django/core/serializers/python.py2
-rw-r--r--django/core/serializers/xml_serializer.py6
-rw-r--r--django/core/servers/fastcgi.py16
-rw-r--r--django/core/urlresolvers.py15
-rw-r--r--django/core/validators.py55
13 files changed, 145 insertions, 71 deletions
diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py
index 17008c4637..6da8e883b9 100644
--- a/django/core/cache/__init__.py
+++ b/django/core/cache/__init__.py
@@ -48,7 +48,7 @@ def get_cache(backend_uri):
if host.endswith('/'):
host = host[:-1]
- cache_class = getattr(__import__('django.core.cache.backends.%s' % BACKENDS[scheme], '', '', ['']), 'CacheClass')
+ cache_class = getattr(__import__('django.core.cache.backends.%s' % BACKENDS[scheme], {}, {}, ['']), 'CacheClass')
return cache_class(host, params)
cache = get_cache(settings.CACHE_BACKEND)
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index 213c528952..c1403ea4fa 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -26,7 +26,7 @@ class BaseHandler(object):
raise exceptions.ImproperlyConfigured, '%s isn\'t a middleware module' % middleware_path
mw_module, mw_classname = middleware_path[:dot], middleware_path[dot+1:]
try:
- mod = __import__(mw_module, '', '', [''])
+ mod = __import__(mw_module, {}, {}, [''])
except ImportError, e:
raise exceptions.ImproperlyConfigured, 'Error importing middleware %s: "%s"' % (mw_module, e)
try:
diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py
index bf759db068..5fc41a048b 100644
--- a/django/core/handlers/modpython.py
+++ b/django/core/handlers/modpython.py
@@ -41,6 +41,7 @@ class ModPythonRequest(http.HttpRequest):
return '%s%s' % (self.path, self._req.args and ('?' + self._req.args) or '')
def is_secure(self):
+ # Note: modpython 3.2.10+ has req.is_https(), but we need to support previous versions
return self._req.subprocess_env.has_key('HTTPS') and self._req.subprocess_env['HTTPS'] == 'on'
def _load_post_and_files(self):
@@ -102,7 +103,7 @@ class ModPythonRequest(http.HttpRequest):
'REQUEST_METHOD': self._req.method,
'SCRIPT_NAME': None, # Not supported
'SERVER_NAME': self._req.server.server_hostname,
- 'SERVER_PORT': str(self._req.connection.local_addr[1]),
+ 'SERVER_PORT': self._req.server.port,
'SERVER_PROTOCOL': self._req.protocol,
'SERVER_SOFTWARE': 'mod_python'
}
diff --git a/django/core/mail.py b/django/core/mail.py
index da4cacbe29..d1e5d0aa55 100644
--- a/django/core/mail.py
+++ b/django/core/mail.py
@@ -4,6 +4,9 @@ from django.conf import settings
from email.MIMEText import MIMEText
from email.Header import Header
import smtplib, rfc822
+import socket
+import time
+import random
class BadHeaderError(ValueError):
pass
@@ -50,6 +53,7 @@ def send_mass_mail(datatuple, fail_silently=False, auth_user=settings.EMAIL_HOST
msg['From'] = from_email
msg['To'] = ', '.join(recipient_list)
msg['Date'] = rfc822.formatdate()
+ msg['Message-ID'] = "<%d.%d@%s>" % (time.time(), random.getrandbits(64), socket.getfqdn())
try:
server.sendmail(from_email, recipient_list, msg.as_string())
num_sent += 1
diff --git a/django/core/management.py b/django/core/management.py
index 5e709db6ac..d1a97c4a53 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -32,6 +32,7 @@ class dummy: pass
style = dummy()
style.ERROR = termcolors.make_style(fg='red', opts=('bold',))
style.ERROR_OUTPUT = termcolors.make_style(fg='red', opts=('bold',))
+style.NOTICE = termcolors.make_style(fg='red')
style.SQL_FIELD = termcolors.make_style(fg='green', opts=('bold',))
style.SQL_COLTYPE = termcolors.make_style(fg='green')
style.SQL_KEYWORD = termcolors.make_style(fg='yellow')
@@ -348,6 +349,8 @@ def get_sql_initial_data_for_model(model):
if os.path.exists(sql_file):
fp = open(sql_file, 'U')
for statement in statements.split(fp.read()):
+ # Remove any comments from the file
+ statement = re.sub(r"--.*[\n\Z]", "", statement)
if statement.strip():
output.append(statement + ";")
fp.close()
@@ -445,7 +448,7 @@ def syncdb(verbosity=1, interactive=True):
# dispatcher events.
for app_name in settings.INSTALLED_APPS:
try:
- __import__(app_name + '.management', '', '', [''])
+ __import__(app_name + '.management', {}, {}, [''])
except ImportError:
pass
@@ -540,7 +543,7 @@ def syncdb(verbosity=1, interactive=True):
transaction.rollback_unless_managed()
else:
transaction.commit_unless_managed()
-
+
syncdb.args = ''
def get_admin_index(app):
@@ -627,6 +630,7 @@ install.args = APP_ARGS
def reset(app, interactive=True):
"Executes the equivalent of 'get_sql_reset' in the current database."
from django.db import connection, transaction
+ from django.conf import settings
app_name = app.__name__.split('.')[-2]
disable_termcolors()
@@ -638,13 +642,14 @@ def reset(app, interactive=True):
if interactive:
confirm = raw_input("""
You have requested a database reset.
-This will IRREVERSIBLY DESTROY any data in your database.
+This will IRREVERSIBLY DESTROY any data for
+the "%s" application in the database "%s".
Are you sure you want to do this?
-Type 'yes' to continue, or 'no' to cancel: """)
+Type 'yes' to continue, or 'no' to cancel: """ % (app_name, settings.DATABASE_NAME))
else:
confirm = 'yes'
-
+
if confirm == 'yes':
try:
cursor = connection.cursor()
@@ -694,7 +699,10 @@ def _start_helper(app_or_project, name, directory, other_name=''):
fp_new.write(fp_old.read().replace('{{ %s_name }}' % app_or_project, name).replace('{{ %s_name }}' % other, other_name))
fp_old.close()
fp_new.close()
- shutil.copymode(path_old, path_new)
+ try:
+ shutil.copymode(path_old, path_new)
+ except OSError:
+ sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new))
def startproject(project_name, directory):
"Creates a Django project for the given project_name in the given directory."
@@ -1224,7 +1232,7 @@ def test(app_labels, verbosity=1):
test_module_name = '.'.join(test_path[:-1])
else:
test_module_name = '.'
- test_module = __import__(test_module_name, [],[],test_path[-1])
+ test_module = __import__(test_module_name, {}, {}, test_path[-1])
test_runner = getattr(test_module, test_path[-1])
test_runner(app_list, verbosity)
@@ -1413,7 +1421,7 @@ def setup_environ(settings_mod):
project_directory = os.path.dirname(settings_mod.__file__)
project_name = os.path.basename(project_directory)
sys.path.append(os.path.join(project_directory, '..'))
- project_module = __import__(project_name, '', '', [''])
+ project_module = __import__(project_name, {}, {}, [''])
sys.path.pop()
# Set DJANGO_SETTINGS_MODULE appropriately.
diff --git a/django/core/paginator.py b/django/core/paginator.py
index 026fe0a675..380808a3dd 100644
--- a/django/core/paginator.py
+++ b/django/core/paginator.py
@@ -1,54 +1,46 @@
-from math import ceil
-
class InvalidPage(Exception):
pass
class ObjectPaginator(object):
"""
- This class makes pagination easy. Feed it a QuerySet, plus the number of
- objects you want on each page. Then read the hits and pages properties to
+ This class makes pagination easy. Feed it a QuerySet or list, plus the number
+ of objects you want on each page. Then read the hits and pages properties to
see how many pages it involves. Call get_page with a page number (starting
at 0) to get back a list of objects for that page.
Finally, check if a page number has a next/prev page using
has_next_page(page_number) and has_previous_page(page_number).
+
+ Use orphans to avoid small final pages. For example:
+ 13 records, num_per_page=10, orphans=2 --> pages==2, len(self.get_page(0))==10
+ 12 records, num_per_page=10, orphans=2 --> pages==1, len(self.get_page(0))==12
"""
- def __init__(self, query_set, num_per_page):
+ def __init__(self, query_set, num_per_page, orphans=0):
self.query_set = query_set
self.num_per_page = num_per_page
- self._hits, self._pages = None, None
- self._has_next = {} # Caches page_number -> has_next_boolean
+ self.orphans = orphans
+ self._hits = self._pages = None
- def get_page(self, page_number):
+ def validate_page_number(self, page_number):
try:
page_number = int(page_number)
except ValueError:
raise InvalidPage
- if page_number < 0:
+ if page_number < 0 or page_number > self.pages - 1:
raise InvalidPage
+ return page_number
- # Retrieve one extra record, and check for the existence of that extra
- # record to determine whether there's a next page.
- limit = self.num_per_page + 1
- offset = page_number * self.num_per_page
-
- object_list = list(self.query_set[offset:offset+limit])
-
- if not object_list:
- raise InvalidPage
-
- self._has_next[page_number] = (len(object_list) > self.num_per_page)
- return object_list[:self.num_per_page]
+ def get_page(self, page_number):
+ page_number = self.validate_page_number(page_number)
+ bottom = page_number * self.num_per_page
+ top = bottom + self.num_per_page
+ if top + self.orphans >= self.hits:
+ top = self.hits
+ return self.query_set[bottom:top]
def has_next_page(self, page_number):
"Does page $page_number have a 'next' page?"
- if not self._has_next.has_key(page_number):
- if self._pages is None:
- offset = (page_number + 1) * self.num_per_page
- self._has_next[page_number] = len(self.query_set[offset:offset+1]) > 0
- else:
- self._has_next[page_number] = page_number < (self.pages - 1)
- return self._has_next[page_number]
+ return page_number < self.pages - 1
def has_previous_page(self, page_number):
return page_number > 0
@@ -58,8 +50,7 @@ class ObjectPaginator(object):
Returns the 1-based index of the first object on the given page,
relative to total objects found (hits).
"""
- if page_number == 0:
- return 1
+ page_number = self.validate_page_number(page_number)
return (self.num_per_page * page_number) + 1
def last_on_page(self, page_number):
@@ -67,20 +58,30 @@ class ObjectPaginator(object):
Returns the 1-based index of the last object on the given page,
relative to total objects found (hits).
"""
- if page_number == 0 and self.num_per_page >= self._hits:
- return self._hits
- elif page_number == (self._pages - 1) and (page_number + 1) * self.num_per_page > self._hits:
- return self._hits
- return (page_number + 1) * self.num_per_page
+ page_number = self.validate_page_number(page_number)
+ page_number += 1 # 1-base
+ if page_number == self.pages:
+ return self.hits
+ return page_number * self.num_per_page
def _get_hits(self):
if self._hits is None:
- self._hits = self.query_set.count()
+ # Try .count() or fall back to len().
+ try:
+ self._hits = int(self.query_set.count())
+ except (AttributeError, TypeError, ValueError):
+ # AttributeError if query_set has no object count.
+ # TypeError if query_set.count() required arguments.
+ # ValueError if int() fails.
+ self._hits = len(self.query_set)
return self._hits
def _get_pages(self):
if self._pages is None:
- self._pages = int(ceil(self.hits / float(self.num_per_page)))
+ hits = (self.hits - 1 - self.orphans)
+ if hits < 1:
+ hits = 0
+ self._pages = hits // self.num_per_page + 1
return self._pages
hits = property(_get_hits)
diff --git a/django/core/serializers/__init__.py b/django/core/serializers/__init__.py
index 75e087ee1b..a1268321f2 100644
--- a/django/core/serializers/__init__.py
+++ b/django/core/serializers/__init__.py
@@ -29,7 +29,7 @@ _serializers = {}
def register_serializer(format, serializer_module):
"""Register a new serializer by passing in a module name."""
- module = __import__(serializer_module, '', '', [''])
+ module = __import__(serializer_module, {}, {}, [''])
_serializers[format] = module
def unregister_serializer(format):
diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py
index fb293c7c13..5b0acdc480 100644
--- a/django/core/serializers/base.py
+++ b/django/core/serializers/base.py
@@ -28,6 +28,7 @@ class Serializer(object):
self.options = options
self.stream = options.get("stream", StringIO())
+ self.selected_fields = options.get("fields")
self.start_serialization()
for obj in queryset:
@@ -36,11 +37,14 @@ class Serializer(object):
if field is obj._meta.pk:
continue
elif field.rel is None:
- self.handle_field(obj, field)
+ if self.selected_fields is None or field.attname in self.selected_fields:
+ self.handle_field(obj, field)
else:
- self.handle_fk_field(obj, field)
+ if self.selected_fields is None or field.attname[:-3] in self.selected_fields:
+ self.handle_fk_field(obj, field)
for field in obj._meta.many_to_many:
- self.handle_m2m_field(obj, field)
+ if self.selected_fields is None or field.attname in self.selected_fields:
+ self.handle_m2m_field(obj, field)
self.end_object(obj)
self.end_serialization()
return self.getvalue()
diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py
index 4181bc7f2b..859816c226 100644
--- a/django/core/serializers/python.py
+++ b/django/core/serializers/python.py
@@ -76,7 +76,7 @@ def Deserializer(object_list, **options):
m2m_data[field.name] = field.rel.to._default_manager.in_bulk(field_value).values()
# Handle FK fields
- elif field.rel and isinstance(field.rel, models.ManyToOneRel):
+ elif field.rel and isinstance(field.rel, models.ManyToOneRel) and field_value is not None:
try:
data[field.name] = field.rel.to._default_manager.get(pk=field_value)
except field.rel.to.DoesNotExist:
diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py
index 09fff408cf..512b8c6176 100644
--- a/django/core/serializers/xml_serializer.py
+++ b/django/core/serializers/xml_serializer.py
@@ -166,7 +166,11 @@ class Deserializer(base.Deserializer):
# If it doesn't exist, set the field to None (which might trigger
# validation error, but that's expected).
RelatedModel = self._get_model_from_node(node, "to")
- return RelatedModel.objects.get(pk=getInnerText(node).strip().encode(self.encoding))
+ # Check if there is a child node named 'None', returning None if so.
+ if len(node.childNodes) == 1 and node.childNodes[0].nodeName == 'None':
+ return None
+ else:
+ return RelatedModel.objects.get(pk=getInnerText(node).strip().encode(self.encoding))
def _handle_m2m_field_node(self, node):
"""
diff --git a/django/core/servers/fastcgi.py b/django/core/servers/fastcgi.py
index c6507fe173..fccb7bf087 100644
--- a/django/core/servers/fastcgi.py
+++ b/django/core/servers/fastcgi.py
@@ -31,9 +31,11 @@ Optional Fcgi settings: (setting=value)
port=PORTNUM port to listen on.
socket=FILE UNIX socket to listen on.
method=IMPL prefork or threaded (default prefork)
- maxspare=NUMBER max number of spare processes to keep running.
- minspare=NUMBER min number of spare processes to prefork.
- maxchildren=NUMBER hard limit number of processes in prefork mode.
+ maxrequests=NUMBER number of requests a child handles before it is
+ killed and a new child is forked (0 = no limit).
+ maxspare=NUMBER max number of spare processes / threads
+ minspare=NUMBER min number of spare processes / threads.
+ maxchildren=NUMBER hard limit number of processes / threads
daemonize=BOOL whether to detach from terminal.
pidfile=FILE write the spawned process-id to this file.
workdir=DIRECTORY change to this directory when daemonizing
@@ -66,6 +68,7 @@ FASTCGI_OPTIONS = {
'maxspare': 5,
'minspare': 2,
'maxchildren': 50,
+ 'maxrequests': 0,
}
def fastcgi_help(message=None):
@@ -103,10 +106,15 @@ def runfastcgi(argset=[], **kwargs):
'maxSpare': int(options["maxspare"]),
'minSpare': int(options["minspare"]),
'maxChildren': int(options["maxchildren"]),
+ 'maxRequests': int(options["maxrequests"]),
}
elif options['method'] in ('thread', 'threaded'):
from flup.server.fcgi import WSGIServer
- wsgi_opts = {}
+ wsgi_opts = {
+ 'maxSpare': int(options["maxspare"]),
+ 'minSpare': int(options["minspare"]),
+ 'maxThreads': int(options["maxchildren"]),
+ }
else:
return fastcgi_help("ERROR: Implementation must be one of prefork or thread.")
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index 45705cb223..93c9c30cca 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
@@ -21,7 +21,10 @@ class NoReverseMatch(Exception):
def get_mod_func(callback):
# Converts 'django.views.news.stories.story_detail' to
# ['django.views.news.stories', 'story_detail']
- dot = callback.rindex('.')
+ try:
+ dot = callback.rindex('.')
+ except ValueError:
+ return callback, ''
return callback[:dot], callback[dot+1:]
def reverse_helper(regex, *args, **kwargs):
@@ -119,7 +122,7 @@ class RegexURLPattern(object):
return self._callback
mod_name, func_name = get_mod_func(self._callback_str)
try:
- self._callback = getattr(__import__(mod_name, '', '', ['']), func_name)
+ self._callback = getattr(__import__(mod_name, {}, {}, ['']), func_name)
except ImportError, e:
raise ViewDoesNotExist, "Could not import %s. Error was: %s" % (mod_name, str(e))
except AttributeError, e:
@@ -130,7 +133,7 @@ class RegexURLPattern(object):
def reverse(self, viewname, *args, **kwargs):
mod_name, func_name = get_mod_func(viewname)
try:
- lookup_view = getattr(__import__(mod_name, '', '', ['']), func_name)
+ lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
except (ImportError, AttributeError):
raise NoReverseMatch
if lookup_view != self.callback:
@@ -171,7 +174,7 @@ class RegexURLResolver(object):
return self._urlconf_module
except AttributeError:
try:
- self._urlconf_module = __import__(self.urlconf_name, '', '', [''])
+ self._urlconf_module = __import__(self.urlconf_name, {}, {}, [''])
except ValueError, e:
# Invalid urlconf_name, such as "foo.bar." (note trailing period)
raise ImproperlyConfigured, "Error while importing URLconf %r: %s" % (self.urlconf_name, e)
@@ -186,7 +189,7 @@ class RegexURLResolver(object):
callback = getattr(self.urlconf_module, 'handler%s' % view_type)
mod_name, func_name = get_mod_func(callback)
try:
- return getattr(__import__(mod_name, '', '', ['']), func_name), {}
+ return getattr(__import__(mod_name, {}, {}, ['']), func_name), {}
except (ImportError, AttributeError), e:
raise ViewDoesNotExist, "Tried %s. Error was: %s" % (callback, str(e))
@@ -200,7 +203,7 @@ class RegexURLResolver(object):
if not callable(lookup_view):
mod_name, func_name = get_mod_func(lookup_view)
try:
- lookup_view = getattr(__import__(mod_name, '', '', ['']), func_name)
+ lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
except (ImportError, AttributeError):
raise NoReverseMatch
for pattern in self.urlconf_module.urlpatterns:
diff --git a/django/core/validators.py b/django/core/validators.py
index 4c3f59143e..a2f3dc3bc3 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -8,6 +8,7 @@ validator will *always* be run, regardless of whether its associated
form field is required.
"""
+import urllib2
from django.conf import settings
from django.utils.translation import gettext, gettext_lazy, ngettext
from django.utils.functional import Promise, lazy
@@ -223,18 +224,26 @@ def isWellFormedXmlFragment(field_data, all_data):
isWellFormedXml('<root>%s</root>' % field_data, all_data)
def isExistingURL(field_data, all_data):
- import urllib2
try:
- u = urllib2.urlopen(field_data)
+ 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": settings.URL_VALIDATOR_USER_AGENT
+ }
+ req = urllib2.Request(field_data,None, headers)
+ u = urllib2.urlopen(req)
except ValueError:
- raise ValidationError, gettext("Invalid URL: %s") % field_data
+ raise ValidationError, _("Invalid URL: %s") % field_data
except urllib2.HTTPError, e:
# 401s are valid; they just mean authorization is required.
- if e.code not in ('401',):
- raise ValidationError, gettext("The URL %s is a broken link.") % field_data
+ # 301 and 302 are redirects; they just mean look somewhere else.
+ if str(e.code) not in ('401','301','302'):
+ raise ValidationError, _("The URL %s is a broken link.") % field_data
except: # urllib2.URLError, httplib.InvalidURL, etc.
- raise ValidationError, gettext("The URL %s is a broken link.") % field_data
-
+ raise ValidationError, _("The URL %s is a broken link.") % field_data
+
def isValidUSState(field_data, all_data):
"Checks that the given string is a valid two-letter U.S. state abbreviation"
states = ['AA', 'AE', 'AK', 'AL', 'AP', 'AR', 'AS', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'FM', 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MH', 'MI', 'MN', 'MO', 'MP', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'PR', 'PW', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI', 'VT', 'WA', 'WI', 'WV', 'WY']
@@ -344,6 +353,38 @@ class UniqueAmongstFieldsWithPrefix(object):
if field_name != self.field_name and value == field_data:
raise ValidationError, self.error_message
+class NumberIsInRange(object):
+ """
+ Validator that tests if a value is in a range (inclusive).
+ """
+ def __init__(self, lower=None, upper=None, error_message=''):
+ self.lower, self.upper = lower, upper
+ if not error_message:
+ if lower and upper:
+ self.error_message = gettext("This value must be between %s and %s.") % (lower, upper)
+ elif lower:
+ self.error_message = gettext("This value must be at least %s.") % lower
+ elif upper:
+ self.error_message = gettext("This value must be no more than %s.") % upper
+ else:
+ self.error_message = error_message
+
+ def __call__(self, field_data, all_data):
+ # Try to make the value numeric. If this fails, we assume another
+ # validator will catch the problem.
+ try:
+ val = float(field_data)
+ except ValueError:
+ return
+
+ # Now validate
+ if self.lower and self.upper and (val < self.lower or val > self.upper):
+ raise ValidationError(self.error_message)
+ elif self.lower and val < self.lower:
+ raise ValidationError(self.error_message)
+ elif self.upper and val > self.upper:
+ raise ValidationError(self.error_message)
+
class IsAPowerOf(object):
"""
>>> v = IsAPowerOf(2)