summaryrefslogtreecommitdiff
path: root/django/core
diff options
context:
space:
mode:
Diffstat (limited to 'django/core')
-rw-r--r--django/core/cache/__init__.py23
-rw-r--r--django/core/cache/backends/base.py9
-rw-r--r--django/core/cache/backends/filebased.py29
-rw-r--r--django/core/cache/backends/locmem.py2
-rw-r--r--django/core/files/temp.py58
-rw-r--r--django/core/files/uploadedfile.py3
-rw-r--r--django/core/files/uploadhandler.py3
-rw-r--r--django/core/handlers/base.py41
-rw-r--r--django/core/handlers/modpython.py25
-rw-r--r--django/core/handlers/wsgi.py19
-rw-r--r--django/core/mail.py7
-rw-r--r--django/core/management/commands/compilemessages.py3
-rw-r--r--django/core/management/commands/test.py1
-rw-r--r--django/core/management/commands/testserver.py1
-rw-r--r--django/core/management/sql.py12
-rw-r--r--django/core/management/validation.py45
-rw-r--r--django/core/paginator.py98
-rw-r--r--django/core/serializers/base.py6
-rw-r--r--django/core/serializers/json.py9
-rw-r--r--django/core/serializers/pyyaml.py8
-rw-r--r--django/core/urlresolvers.py33
21 files changed, 275 insertions, 160 deletions
diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py
index 495cc92822..c136ce4f4d 100644
--- a/django/core/cache/__init__.py
+++ b/django/core/cache/__init__.py
@@ -19,8 +19,10 @@ from cgi import parse_qsl
from django.conf import settings
from django.core.cache.backends.base import InvalidCacheBackendError
+# Name for use in settings file --> name of module in "backends" directory.
+# Any backend scheme that is not in this dictionary is treated as a Python
+# import path to a custom backend.
BACKENDS = {
- # name for use in settings file --> name of module in "backends" directory
'memcached': 'memcached',
'locmem': 'locmem',
'file': 'filebased',
@@ -28,24 +30,12 @@ BACKENDS = {
'dummy': 'dummy',
}
-DEPRECATED_BACKENDS = {
- # deprecated backend --> replacement module
- 'simple': 'locmem',
-}
-
def get_cache(backend_uri):
if backend_uri.find(':') == -1:
raise InvalidCacheBackendError, "Backend URI must start with scheme://"
scheme, rest = backend_uri.split(':', 1)
if not rest.startswith('//'):
raise InvalidCacheBackendError, "Backend URI must start with scheme://"
- if scheme in DEPRECATED_BACKENDS:
- import warnings
- warnings.warn("'%s' backend is deprecated. Use '%s' instead." %
- (scheme, DEPRECATED_BACKENDS[scheme]), DeprecationWarning)
- scheme = DEPRECATED_BACKENDS[scheme]
- if scheme not in BACKENDS:
- raise InvalidCacheBackendError, "%r is not a valid cache backend" % scheme
host = rest[2:]
qpos = rest.find('?')
@@ -57,7 +47,10 @@ def get_cache(backend_uri):
if host.endswith('/'):
host = host[:-1]
- cache_class = getattr(__import__('django.core.cache.backends.%s' % BACKENDS[scheme], {}, {}, ['']), 'CacheClass')
- return cache_class(host, params)
+ if scheme in BACKENDS:
+ module = __import__('django.core.cache.backends.%s' % BACKENDS[scheme], {}, {}, [''])
+ else:
+ module = __import__(scheme, {}, {}, [''])
+ return getattr(module, 'CacheClass')(host, params)
cache = get_cache(settings.CACHE_BACKEND)
diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
index ff4223bf86..58e166d655 100644
--- a/django/core/cache/backends/base.py
+++ b/django/core/cache/backends/base.py
@@ -63,4 +63,11 @@ class BaseCache(object):
"""
return self.get(key) is not None
- __contains__ = has_key
+ def __contains__(self, key):
+ """
+ Returns True if the key is in the cache and has not expired.
+ """
+ # This is a separate method, rather than just a copy of has_key(),
+ # so that it always has the same functionality as has_key(), even
+ # if a subclass overrides it.
+ return self.has_key(key)
diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py
index c1277bf20c..0ad586d477 100644
--- a/django/core/cache/backends/filebased.py
+++ b/django/core/cache/backends/filebased.py
@@ -1,29 +1,31 @@
"File-based cache backend"
-import md5
-import os, time
+import os
+import time
try:
import cPickle as pickle
except ImportError:
import pickle
+
from django.core.cache.backends.base import BaseCache
+from django.utils.hashcompat import md5_constructor
class CacheClass(BaseCache):
def __init__(self, dir, params):
BaseCache.__init__(self, params)
-
+
max_entries = params.get('max_entries', 300)
try:
self._max_entries = int(max_entries)
except (ValueError, TypeError):
self._max_entries = 300
-
+
cull_frequency = params.get('cull_frequency', 3)
try:
self._cull_frequency = int(cull_frequency)
except (ValueError, TypeError):
self._cull_frequency = 3
-
+
self._dir = dir
if not os.path.exists(self._dir):
self._createdir()
@@ -31,7 +33,7 @@ class CacheClass(BaseCache):
def add(self, key, value, timeout=None):
if self.has_key(key):
return None
-
+
self.set(key, value, timeout)
def get(self, key, default=None):
@@ -52,12 +54,12 @@ class CacheClass(BaseCache):
def set(self, key, value, timeout=None):
fname = self._key_to_file(key)
dirname = os.path.dirname(fname)
-
+
if timeout is None:
timeout = self.default_timeout
-
+
self._cull()
-
+
try:
if not os.path.exists(dirname):
os.makedirs(dirname)
@@ -103,12 +105,12 @@ class CacheClass(BaseCache):
def _cull(self):
if int(self._num_entries) < self._max_entries:
return
-
+
try:
filelist = os.listdir(self._dir)
except (IOError, OSError):
return
-
+
if self._cull_frequency == 0:
doomed = filelist
else:
@@ -133,11 +135,11 @@ class CacheClass(BaseCache):
Convert the filename into an md5 string. We'll turn the first couple
bits of the path into directory prefixes to be nice to filesystems
that have problems with large numbers of files in a directory.
-
+
Thus, a cache key of "foo" gets turnned into a file named
``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``.
"""
- path = md5.new(key.encode('utf-8')).hexdigest()
+ path = md5_constructor(key.encode('utf-8')).hexdigest()
path = os.path.join(path[:2], path[2:4], path[4:])
return os.path.join(self._dir, path)
@@ -147,4 +149,3 @@ class CacheClass(BaseCache):
count += len(files)
return count
_num_entries = property(_get_num_entries)
-
diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py
index e8e1e0d450..053e0735f7 100644
--- a/django/core/cache/backends/locmem.py
+++ b/django/core/cache/backends/locmem.py
@@ -107,7 +107,7 @@ class CacheClass(BaseCache):
else:
doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0]
for k in doomed:
- self.delete(k)
+ self._delete(k)
def _delete(self, key):
try:
diff --git a/django/core/files/temp.py b/django/core/files/temp.py
new file mode 100644
index 0000000000..298fcbf0a5
--- /dev/null
+++ b/django/core/files/temp.py
@@ -0,0 +1,58 @@
+"""
+The temp module provides a NamedTemporaryFile that can be re-opened on any
+platform. Most platforms use the standard Python tempfile.TemporaryFile class,
+but MS Windows users are given a custom class.
+
+This is needed because in Windows NT, the default implementation of
+NamedTemporaryFile uses the O_TEMPORARY flag, and thus cannot be reopened [1].
+
+1: http://mail.python.org/pipermail/python-list/2005-December/359474.html
+"""
+
+import os
+import tempfile
+
+__all__ = ('NamedTemporaryFile', 'gettempdir',)
+
+if os.name == 'nt':
+ class TemporaryFile(object):
+ """
+ Temporary file object constructor that works in Windows and supports
+ reopening of the temporary file in windows.
+ """
+ def __init__(self, mode='w+b', bufsize=-1, suffix='', prefix='',
+ dir=None):
+ fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
+ dir=dir)
+ self.name = name
+ self._file = os.fdopen(fd, mode, bufsize)
+
+ def __del__(self):
+ try:
+ self._file.close()
+ except (OSError, IOError):
+ pass
+ try:
+ os.unlink(self.name)
+ except (OSError):
+ pass
+
+ try:
+ super(TemporaryFile, self).__del__()
+ except AttributeError:
+ pass
+
+
+ def read(self, *args): return self._file.read(*args)
+ def seek(self, offset): return self._file.seek(offset)
+ def write(self, s): return self._file.write(s)
+ def close(self): return self._file.close()
+ def __iter__(self): return iter(self._file)
+ def readlines(self, size=None): return self._file.readlines(size)
+ def xreadlines(self): return self._file.xreadlines()
+
+ NamedTemporaryFile = TemporaryFile
+else:
+ NamedTemporaryFile = tempfile.NamedTemporaryFile
+
+gettempdir = tempfile.gettempdir
diff --git a/django/core/files/uploadedfile.py b/django/core/files/uploadedfile.py
index 7f515f94d4..c498561c18 100644
--- a/django/core/files/uploadedfile.py
+++ b/django/core/files/uploadedfile.py
@@ -3,7 +3,6 @@ Classes representing uploaded files.
"""
import os
-import tempfile
import warnings
try:
from cStringIO import StringIO
@@ -12,6 +11,8 @@ except ImportError:
from django.conf import settings
+from django.core.files import temp as tempfile
+
__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile', 'SimpleUploadedFile')
# Because we fooled around with it a bunch, UploadedFile has a bunch
diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py
index 008a05a148..fa4d2df804 100644
--- a/django/core/files/uploadhandler.py
+++ b/django/core/files/uploadhandler.py
@@ -1,8 +1,7 @@
"""
Base file upload handler classes, and the built-in concrete subclasses
"""
-import os
-import tempfile
+
try:
from cStringIO import StringIO
except ImportError:
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index 29835d7ce5..214032e318 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -3,6 +3,7 @@ import sys
from django import http
from django.core import signals
from django.dispatch import dispatcher
+from django.utils.encoding import force_unicode
class BaseHandler(object):
# Changes that are always applied to a response (in this order).
@@ -73,7 +74,8 @@ class BaseHandler(object):
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
try:
- callback, callback_args, callback_kwargs = resolver.resolve(request.path)
+ callback, callback_args, callback_kwargs = resolver.resolve(
+ request.path_info)
# Apply view middleware
for middleware_method in self._view_middleware:
@@ -107,8 +109,11 @@ class BaseHandler(object):
from django.views import debug
return debug.technical_404_response(request, e)
else:
- callback, param_dict = resolver.resolve404()
- return callback(request, **param_dict)
+ try:
+ callback, param_dict = resolver.resolve404()
+ return callback(request, **param_dict)
+ except:
+ return self.handle_uncaught_exception(request, resolver, sys.exc_info())
except exceptions.PermissionDenied:
return http.HttpResponseForbidden('<h1>Permission denied</h1>')
except SystemExit:
@@ -118,9 +123,6 @@ class BaseHandler(object):
# Get the exception info now, in case another exception is thrown later.
exc_info = sys.exc_info()
receivers = dispatcher.send(signal=signals.got_request_exception, request=request)
-
- if settings.DEBUG_PROPAGATE_EXCEPTIONS:
- raise
return self.handle_uncaught_exception(request, resolver, exc_info)
def handle_uncaught_exception(self, request, resolver, exc_info):
@@ -136,6 +138,9 @@ class BaseHandler(object):
from django.conf import settings
from django.core.mail import mail_admins
+ if settings.DEBUG_PROPAGATE_EXCEPTIONS:
+ raise
+
if settings.DEBUG:
from django.views import debug
return debug.technical_500_response(request, *exc_info)
@@ -167,3 +172,27 @@ class BaseHandler(object):
response = func(request, response)
return response
+def get_script_name(environ):
+ """
+ Returns the equivalent of the HTTP request's SCRIPT_NAME environment
+ variable. If Apache mod_rewrite has been used, returns what would have been
+ the script name prior to any rewriting (so it's the script name as seen
+ from the client's perspective), unless DJANGO_USE_POST_REWRITE is set (to
+ anything).
+ """
+ from django.conf import settings
+ if settings.FORCE_SCRIPT_NAME is not None:
+ return force_unicode(settings.FORCE_SCRIPT_NAME)
+
+ # If Apache's mod_rewrite had a whack at the URL, Apache set either
+ # SCRIPT_URL or REDIRECT_URL to the full resource URL before applying any
+ # rewrites. Unfortunately not every webserver (lighttpd!) passes this
+ # information through all the time, so FORCE_SCRIPT_NAME, above, is still
+ # needed.
+ script_url = environ.get('SCRIPT_URL', u'')
+ if not script_url:
+ script_url = environ.get('REDIRECT_URL', u'')
+ if script_url:
+ return force_unicode(script_url[:-len(environ.get('PATH_INFO', ''))])
+ return force_unicode(environ.get('SCRIPT_NAME', u''))
+
diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py
index 332df6f54c..58699816fd 100644
--- a/django/core/handlers/modpython.py
+++ b/django/core/handlers/modpython.py
@@ -4,6 +4,7 @@ from pprint import pformat
from django import http
from django.core import signals
from django.core.handlers.base import BaseHandler
+from django.core.urlresolvers import set_script_prefix
from django.dispatch import dispatcher
from django.utils import datastructures
from django.utils.encoding import force_unicode, smart_str
@@ -15,7 +16,26 @@ from django.utils.encoding import force_unicode, smart_str
class ModPythonRequest(http.HttpRequest):
def __init__(self, req):
self._req = req
+ # FIXME: This isn't ideal. The request URI may be encoded (it's
+ # non-normalized) slightly differently to the "real" SCRIPT_NAME
+ # and PATH_INFO values. This causes problems when we compute path_info,
+ # below. For now, don't use script names that will be subject to
+ # encoding/decoding.
self.path = force_unicode(req.uri)
+ root = req.get_options().get('django.root', '')
+ self.django_root = root
+ # req.path_info isn't necessarily computed correctly in all
+ # circumstances (it's out of mod_python's control a bit), so we use
+ # req.uri and some string manipulations to get the right value.
+ if root and req.uri.startswith(root):
+ self.path_info = force_unicode(req.uri[len(root):])
+ else:
+ self.path_info = self.path
+ if not self.path_info:
+ # Django prefers empty paths to be '/', rather than '', to give us
+ # a common start character for URL patterns. So this is a little
+ # naughty, but also pretty harmless.
+ self.path_info = u'/'
def __repr__(self):
# Since this is called as part of error handling, we need to be very
@@ -100,7 +120,7 @@ class ModPythonRequest(http.HttpRequest):
'CONTENT_LENGTH': self._req.clength, # This may be wrong
'CONTENT_TYPE': self._req.content_type, # This may be wrong
'GATEWAY_INTERFACE': 'CGI/1.1',
- 'PATH_INFO': self._req.path_info,
+ 'PATH_INFO': self.path_info,
'PATH_TRANSLATED': None, # Not supported
'QUERY_STRING': self._req.args,
'REMOTE_ADDR': self._req.connection.remote_ip,
@@ -108,7 +128,7 @@ class ModPythonRequest(http.HttpRequest):
'REMOTE_IDENT': self._req.connection.remote_logname,
'REMOTE_USER': self._req.user,
'REQUEST_METHOD': self._req.method,
- 'SCRIPT_NAME': None, # Not supported
+ 'SCRIPT_NAME': self.django_root,
'SERVER_NAME': self._req.server.server_hostname,
'SERVER_PORT': self._req.server.port,
'SERVER_PROTOCOL': self._req.protocol,
@@ -153,6 +173,7 @@ class ModPythonHandler(BaseHandler):
if self._request_middleware is None:
self.load_middleware()
+ set_script_prefix(req.get_options().get('django.root', ''))
dispatcher.send(signal=signals.request_started)
try:
try:
diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
index 795f139042..a1324936cd 100644
--- a/django/core/handlers/wsgi.py
+++ b/django/core/handlers/wsgi.py
@@ -7,7 +7,8 @@ except ImportError:
from django import http
from django.core import signals
-from django.core.handlers.base import BaseHandler
+from django.core.handlers import base
+from django.core.urlresolvers import set_script_prefix
from django.dispatch import dispatcher
from django.utils import datastructures
from django.utils.encoding import force_unicode
@@ -74,9 +75,20 @@ def safe_copyfileobj(fsrc, fdst, length=16*1024, size=0):
class WSGIRequest(http.HttpRequest):
def __init__(self, environ):
+ script_name = base.get_script_name(environ)
+ path_info = force_unicode(environ.get('PATH_INFO', u'/'))
+ if not path_info:
+ # Sometimes PATH_INFO exists, but is empty (e.g. accessing
+ # the SCRIPT_NAME URL without a trailing slash). We really need to
+ # operate as if they'd requested '/'. Not amazingly nice to force
+ # the path like this, but should be harmless.
+ path_info = u'/'
self.environ = environ
- self.path = force_unicode(environ['PATH_INFO'])
+ self.path_info = path_info
+ self.path = '%s%s' % (script_name, path_info)
self.META = environ
+ self.META['PATH_INFO'] = path_info
+ self.META['SCRIPT_NAME'] = script_name
self.method = environ['REQUEST_METHOD'].upper()
def __repr__(self):
@@ -178,7 +190,7 @@ class WSGIRequest(http.HttpRequest):
REQUEST = property(_get_request)
raw_post_data = property(_get_raw_post_data)
-class WSGIHandler(BaseHandler):
+class WSGIHandler(base.BaseHandler):
initLock = Lock()
request_class = WSGIRequest
@@ -194,6 +206,7 @@ class WSGIHandler(BaseHandler):
self.load_middleware()
self.initLock.release()
+ set_script_prefix(base.get_script_name(environ))
dispatcher.send(signal=signals.request_started)
try:
try:
diff --git a/django/core/mail.py b/django/core/mail.py
index b60757366f..d6c6eea190 100644
--- a/django/core/mail.py
+++ b/django/core/mail.py
@@ -71,10 +71,11 @@ class BadHeaderError(ValueError):
def forbid_multi_line_headers(name, val):
"""Forbids multi-line headers, to prevent header injection."""
+ val = force_unicode(val)
if '\n' in val or '\r' in val:
raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
try:
- val = force_unicode(val).encode('ascii')
+ val = val.encode('ascii')
except UnicodeEncodeError:
if name.lower() in ('to', 'from', 'cc'):
result = []
@@ -84,7 +85,7 @@ def forbid_multi_line_headers(name, val):
result.append(formataddr((nm, str(addr))))
val = ', '.join(result)
else:
- val = Header(force_unicode(val), settings.DEFAULT_CHARSET)
+ val = Header(val, settings.DEFAULT_CHARSET)
return name, val
class SafeMIMEText(MIMEText):
@@ -174,7 +175,7 @@ class SMTPConnection(object):
def _send(self, email_message):
"""A helper method that does the actual sending."""
- if not email_message.to:
+ if not email_message.recipients():
return False
try:
self.connection.sendmail(email_message.from_email,
diff --git a/django/core/management/commands/compilemessages.py b/django/core/management/commands/compilemessages.py
index 7a189a426a..f0975ca6c1 100644
--- a/django/core/management/commands/compilemessages.py
+++ b/django/core/management/commands/compilemessages.py
@@ -1,8 +1,7 @@
import os
import sys
from optparse import make_option
-from django.core.management.base import BaseCommand
-from django.core.management.color import no_style
+from django.core.management.base import BaseCommand, CommandError
try:
set
diff --git a/django/core/management/commands/test.py b/django/core/management/commands/test.py
index f0e13a2069..ef7b197b00 100644
--- a/django/core/management/commands/test.py
+++ b/django/core/management/commands/test.py
@@ -17,7 +17,6 @@ class Command(BaseCommand):
def handle(self, *test_labels, **options):
from django.conf import settings
- from django.db.models import get_app, get_apps
verbosity = int(options.get('verbosity', 1))
interactive = options.get('interactive', True)
diff --git a/django/core/management/commands/testserver.py b/django/core/management/commands/testserver.py
index 9b169d3d9b..b409bc91d1 100644
--- a/django/core/management/commands/testserver.py
+++ b/django/core/management/commands/testserver.py
@@ -17,7 +17,6 @@ class Command(BaseCommand):
requires_model_validation = False
def handle(self, *fixture_labels, **options):
- from django.conf import settings
from django.core.management import call_command
from django.test.utils import create_test_db
diff --git a/django/core/management/sql.py b/django/core/management/sql.py
index 0b01edcfc9..7c17807130 100644
--- a/django/core/management/sql.py
+++ b/django/core/management/sql.py
@@ -181,7 +181,7 @@ def sql_delete(app, style):
for model in app_models:
opts = model._meta
for f in opts.local_many_to_many:
- if isinstance(f.rel, generic.GenericRel):
+ if not f.creates_table:
continue
if cursor and table_name_converter(f.m2m_db_table()) in table_names:
output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
@@ -353,7 +353,7 @@ def many_to_many_sql_for_model(model, style):
qn = connection.ops.quote_name
inline_references = connection.features.inline_fk_references
for f in opts.local_many_to_many:
- if not isinstance(f.rel, generic.GenericRel):
+ if f.creates_table:
tablespace = f.db_tablespace or opts.db_tablespace
if tablespace and connection.features.supports_tablespaces:
tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace, inline=True)
@@ -435,12 +435,12 @@ def custom_sql_for_model(model, style):
output = []
# Post-creation SQL should come before any initial SQL data is loaded.
- # However, this should not be done for fields that are part of a
- # a parent model (via model inheritance).
+ # However, this should not be done for fields that are part of a a parent
+ # model (via model inheritance).
nm = opts.init_name_map()
- post_sql_fields = [f for f in opts.fields if nm[f.name][1] is None and hasattr(f, '_post_create_sql')]
+ post_sql_fields = [f for f in opts.local_fields if hasattr(f, 'post_create_sql')]
for f in post_sql_fields:
- output.extend(f._post_create_sql(style, model._meta.db_table))
+ output.extend(f.post_create_sql(style, model._meta.db_table))
# Some backends can't execute more than one SQL statement at a time,
# so split into separate statements.
diff --git a/django/core/management/validation.py b/django/core/management/validation.py
index e17409ae5d..e9d7b53027 100644
--- a/django/core/management/validation.py
+++ b/django/core/management/validation.py
@@ -102,6 +102,7 @@ def get_validation_errors(outfile, app=None):
if r.get_accessor_name() == rel_query_name:
e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
+ seen_intermediary_signatures = []
for i, f in enumerate(opts.local_many_to_many):
# Check to see if the related m2m field will clash with any
# existing fields, m2m fields, m2m related objects or related
@@ -112,7 +113,49 @@ def get_validation_errors(outfile, app=None):
# so skip the next section
if isinstance(f.rel.to, (str, unicode)):
continue
-
+ if getattr(f.rel, 'through', None) is not None:
+ if hasattr(f.rel, 'through_model'):
+ from_model, to_model = cls, f.rel.to
+ if from_model == to_model and f.rel.symmetrical:
+ e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
+ seen_from, seen_to, seen_self = False, False, 0
+ for inter_field in f.rel.through_model._meta.fields:
+ rel_to = getattr(inter_field.rel, 'to', None)
+ if from_model == to_model: # relation to self
+ if rel_to == from_model:
+ seen_self += 1
+ if seen_self > 2:
+ e.add(opts, "Intermediary model %s has more than two foreign keys to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name))
+ else:
+ if rel_to == from_model:
+ if seen_from:
+ e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, rel_from._meta.object_name))
+ else:
+ seen_from = True
+ elif rel_to == to_model:
+ if seen_to:
+ e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, rel_to._meta.object_name))
+ else:
+ seen_to = True
+ if f.rel.through_model not in models.get_models():
+ e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed." % (f.name, f.rel.through))
+ signature = (f.rel.to, cls, f.rel.through_model)
+ if signature in seen_intermediary_signatures:
+ e.add(opts, "The model %s has two manually-defined m2m relations through the model %s, which is not permitted. Please consider using an extra field on your intermediary model instead." % (cls._meta.object_name, f.rel.through_model._meta.object_name))
+ else:
+ seen_intermediary_signatures.append(signature)
+ seen_related_fk, seen_this_fk = False, False
+ for field in f.rel.through_model._meta.fields:
+ if field.rel:
+ if not seen_related_fk and field.rel.to == f.rel.to:
+ seen_related_fk = True
+ elif field.rel.to == cls:
+ seen_this_fk = True
+ if not seen_related_fk or not seen_this_fk:
+ e.add(opts, "'%s' has a manually-defined m2m relation through model %s, which does not have foreign keys to %s and %s" % (f.name, f.rel.through, f.rel.to._meta.object_name, cls._meta.object_name))
+ else:
+ e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed" % (f.name, f.rel.through))
+
rel_opts = f.rel.to._meta
rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
rel_query_name = f.related_query_name()
diff --git a/django/core/paginator.py b/django/core/paginator.py
index 439b0bc717..495cdf2d76 100644
--- a/django/core/paginator.py
+++ b/django/core/paginator.py
@@ -1,3 +1,5 @@
+from math import ceil
+
class InvalidPage(Exception):
pass
@@ -42,10 +44,12 @@ class Paginator(object):
def _get_count(self):
"Returns the total number of objects, across all pages."
if self._count is None:
- from django.db.models.query import QuerySet
- if isinstance(self.object_list, QuerySet):
+ try:
self._count = self.object_list.count()
- else:
+ except (AttributeError, TypeError):
+ # AttributeError if object_list has no count() method.
+ # TypeError if object_list.count() requires arguments
+ # (i.e. is of type list).
self._count = len(self.object_list)
return self._count
count = property(_get_count)
@@ -53,13 +57,11 @@ class Paginator(object):
def _get_num_pages(self):
"Returns the total number of pages."
if self._num_pages is None:
- hits = self.count - 1 - self.orphans
- if hits < 1:
- hits = 0
- if hits == 0 and not self.allow_empty_first_page:
+ if self.count == 0 and not self.allow_empty_first_page:
self._num_pages = 0
else:
- self._num_pages = hits // self.per_page + 1
+ hits = max(1, self.count - self.orphans)
+ self._num_pages = int(ceil(hits / float(self.per_page)))
return self._num_pages
num_pages = property(_get_num_pages)
@@ -102,6 +104,9 @@ class Page(object):
Returns the 1-based index of the first object on this page,
relative to total objects in the paginator.
"""
+ # Special case, return zero if no items.
+ if self.paginator.count == 0:
+ return 0
return (self.paginator.per_page * (self.number - 1)) + 1
def end_index(self):
@@ -109,82 +114,7 @@ class Page(object):
Returns the 1-based index of the last object on this page,
relative to total objects found (hits).
"""
+ # Special case for the last page because there can be orphans.
if self.number == self.paginator.num_pages:
return self.paginator.count
return self.number * self.paginator.per_page
-
-class ObjectPaginator(Paginator):
- """
- Legacy ObjectPaginator class, for backwards compatibility.
-
- Note that each method on this class that takes page_number expects a
- zero-based page number, whereas the new API (Paginator/Page) uses one-based
- page numbers.
- """
- def __init__(self, query_set, num_per_page, orphans=0):
- Paginator.__init__(self, query_set, num_per_page, orphans)
- import warnings
- warnings.warn("The ObjectPaginator is deprecated. Use django.core.paginator.Paginator instead.", DeprecationWarning)
-
- # Keep these attributes around for backwards compatibility.
- self.query_set = query_set
- self.num_per_page = num_per_page
- self._hits = self._pages = None
-
- def validate_page_number(self, page_number):
- try:
- page_number = int(page_number) + 1
- except ValueError:
- raise PageNotAnInteger
- return self.validate_number(page_number)
-
- def get_page(self, page_number):
- try:
- page_number = int(page_number) + 1
- except ValueError:
- raise PageNotAnInteger
- return self.page(page_number).object_list
-
- def has_next_page(self, page_number):
- return page_number < self.pages - 1
-
- def has_previous_page(self, page_number):
- return page_number > 0
-
- def first_on_page(self, page_number):
- """
- Returns the 1-based index of the first object on the given page,
- relative to total objects found (hits).
- """
- page_number = self.validate_page_number(page_number)
- return (self.num_per_page * (page_number - 1)) + 1
-
- def last_on_page(self, page_number):
- """
- Returns the 1-based index of the last object on the given page,
- relative to total objects found (hits).
- """
- page_number = self.validate_page_number(page_number)
- if page_number == self.num_pages:
- return self.count
- return page_number * self.num_per_page
-
- def _get_count(self):
- # The old API allowed for self.object_list to be either a QuerySet or a
- # list. Here, we handle both.
- if self._count is None:
- try:
- self._count = self.object_list.count()
- except (AttributeError, TypeError):
- # AttributeError if object_list has no count() method.
- # TypeError if object_list.count() requires arguments
- # (i.e. is of type list).
- self._count = len(self.object_list)
- return self._count
- count = property(_get_count)
-
- # The old API called it "hits" instead of "count".
- hits = count
-
- # The old API called it "pages" instead of "num_pages".
- pages = Paginator.num_pages
diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py
index f6943e543e..bfd785a6fe 100644
--- a/django/core/serializers/base.py
+++ b/django/core/serializers/base.py
@@ -2,10 +2,8 @@
Module for abstract serializer/unserializer base classes.
"""
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
+from StringIO import StringIO
+
from django.db import models
from django.utils.encoding import smart_str, smart_unicode
from django.utils import datetime_safe
diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py
index a84206a0fe..97e5bc9b26 100644
--- a/django/core/serializers/json.py
+++ b/django/core/serializers/json.py
@@ -3,14 +3,13 @@ Serialize data to/from JSON
"""
import datetime
-from django.utils import simplejson
+from StringIO import StringIO
+
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.python import Deserializer as PythonDeserializer
from django.utils import datetime_safe
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
+from django.utils import simplejson
+
try:
import decimal
except ImportError:
diff --git a/django/core/serializers/pyyaml.py b/django/core/serializers/pyyaml.py
index 58cf59bed9..ac77166a2f 100644
--- a/django/core/serializers/pyyaml.py
+++ b/django/core/serializers/pyyaml.py
@@ -4,14 +4,12 @@ YAML serializer.
Requires PyYaml (http://pyyaml.org/), but that's checked for in __init__.
"""
+from StringIO import StringIO
+import yaml
+
from django.db import models
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.python import Deserializer as PythonDeserializer
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-import yaml
class Serializer(PythonSerializer):
"""
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index ff0bcbcfea..4ec725cb38 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
@@ -7,11 +7,13 @@ a string) and returns a tuple in this format:
(view_function, function_args, function_kwargs)
"""
+import re
+
from django.http import Http404
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.utils.encoding import iri_to_uri, force_unicode, smart_str
from django.utils.functional import memoize
-import re
+from django.utils.thread_support import currentThread
try:
reversed
@@ -21,6 +23,11 @@ except NameError:
_resolver_cache = {} # Maps urlconf modules to RegexURLResolver instances.
_callable_cache = {} # Maps view and url pattern names to their view functions.
+# SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
+# the current thread (which is the only one we ever access), it is assumed to
+# be empty.
+_prefixes = {}
+
class Resolver404(Http404):
pass
@@ -291,13 +298,33 @@ class RegexURLResolver(object):
def resolve(path, urlconf=None):
return get_resolver(urlconf).resolve(path)
-def reverse(viewname, urlconf=None, args=None, kwargs=None):
+def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None):
args = args or []
kwargs = kwargs or {}
- return iri_to_uri(u'/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs))
+ if prefix is None:
+ prefix = get_script_prefix()
+ return iri_to_uri(u'%s%s' % (prefix, get_resolver(urlconf).reverse(viewname,
+ *args, **kwargs)))
def clear_url_caches():
global _resolver_cache
global _callable_cache
_resolver_cache.clear()
_callable_cache.clear()
+
+def set_script_prefix(prefix):
+ """
+ Sets the script prefix for the current thread.
+ """
+ if not prefix.endswith('/'):
+ prefix += '/'
+ _prefixes[currentThread()] = prefix
+
+def get_script_prefix():
+ """
+ Returns the currently active script prefix. Useful for client code that
+ wishes to construct their own URLs manually (although accessing the request
+ instance is normally going to be a lot cleaner).
+ """
+ return _prefixes.get(currentThread(), u'/')
+