diff options
| author | Robin Munn <robin.munn@gmail.com> | 2006-11-08 04:50:01 +0000 |
|---|---|---|
| committer | Robin Munn <robin.munn@gmail.com> | 2006-11-08 04:50:01 +0000 |
| commit | dadfca08c0db567ce33284aaa8eb388cf667a836 (patch) | |
| tree | ab7255eeee1bbe03d95652cc74a3843fa052d8ac /django/core | |
| parent | 0b059aa4eadc1d95ceca3a32821b65a9fb2a53e8 (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__.py | 2 | ||||
| -rw-r--r-- | django/core/handlers/base.py | 2 | ||||
| -rw-r--r-- | django/core/handlers/modpython.py | 3 | ||||
| -rw-r--r-- | django/core/mail.py | 4 | ||||
| -rw-r--r-- | django/core/management.py | 24 | ||||
| -rw-r--r-- | django/core/paginator.py | 75 | ||||
| -rw-r--r-- | django/core/serializers/__init__.py | 2 | ||||
| -rw-r--r-- | django/core/serializers/base.py | 10 | ||||
| -rw-r--r-- | django/core/serializers/python.py | 2 | ||||
| -rw-r--r-- | django/core/serializers/xml_serializer.py | 6 | ||||
| -rw-r--r-- | django/core/servers/fastcgi.py | 16 | ||||
| -rw-r--r-- | django/core/urlresolvers.py | 15 | ||||
| -rw-r--r-- | django/core/validators.py | 55 |
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) |
