summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorMalcolm Tredinnick <malcolm.tredinnick@gmail.com>2008-07-21 07:57:10 +0000
committerMalcolm Tredinnick <malcolm.tredinnick@gmail.com>2008-07-21 07:57:10 +0000
commitbfcecbffd37a68b5fffe81954c17aedeacb1ba22 (patch)
treef838d3d4ad950943936f2e93b353f2dd9542c6fd /django
parentca7ee4be17b9b3d80559de00c7bcbcafb188d4f1 (diff)
Changed/fixed the way Django handles SCRIPT_NAME and PATH_INFO (or
equivalents). Basically, URL resolving will only use the PATH_INFO and the SCRIPT_NAME will be prepended by reverse() automatically. Allows for more portable development and installation. Also exposes SCRIPT_NAME in the HttpRequest instance. There are a number of cases where things don't work completely transparently, so mod_python and fastcgi users should read the relevant docs. Fixed #285, #1516, #3414. git-svn-id: http://code.djangoproject.com/svn/django/trunk@8015 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
-rw-r--r--django/conf/global_settings.py3
-rw-r--r--django/core/handlers/base.py28
-rw-r--r--django/core/handlers/modpython.py20
-rw-r--r--django/core/handlers/wsgi.py13
-rw-r--r--django/core/urlresolvers.py33
-rw-r--r--django/http/__init__.py2
-rw-r--r--django/test/client.py2
-rw-r--r--django/utils/thread_support.py12
-rw-r--r--django/utils/translation/trans_real.py13
9 files changed, 104 insertions, 22 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 2c9720da10..797841d297 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -188,6 +188,9 @@ APPEND_SLASH = True
# Whether to prepend the "www." subdomain to URLs that don't have it.
PREPEND_WWW = False
+# Override the server-derived value of SCRIPT_NAME
+FORCE_SCRIPT_NAME = None
+
# List of compiled regular expression objects representing User-Agent strings
# that are not allowed to visit any page, systemwide. Use this for bad
# robots/crawlers. Here are a few examples:
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index f63b28dc66..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:
@@ -170,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..d407476acf 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,21 @@ 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
def __repr__(self):
# Since this is called as part of error handling, we need to be very
@@ -100,7 +115,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 +123,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 +168,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..ca8dd4565f 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,14 @@ 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', '/'))
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 +184,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 +200,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/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'/')
+
diff --git a/django/http/__init__.py b/django/http/__init__.py
index ef15479983..fe0b93edcf 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -31,6 +31,7 @@ class HttpRequest(object):
def __init__(self):
self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
self.path = ''
+ self.path_info = ''
self.method = None
def __repr__(self):
@@ -442,3 +443,4 @@ def str_to_unicode(s, encoding):
return unicode(s, encoding, 'replace')
else:
return s
+
diff --git a/django/test/client.py b/django/test/client.py
index 47c12a4ca1..7d621449ea 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -190,7 +190,7 @@ class Client:
'PATH_INFO': '/',
'QUERY_STRING': '',
'REQUEST_METHOD': 'GET',
- 'SCRIPT_NAME': None,
+ 'SCRIPT_NAME': '',
'SERVER_NAME': 'testserver',
'SERVER_PORT': 80,
'SERVER_PROTOCOL': 'HTTP/1.1',
diff --git a/django/utils/thread_support.py b/django/utils/thread_support.py
new file mode 100644
index 0000000000..2b97d165ca
--- /dev/null
+++ b/django/utils/thread_support.py
@@ -0,0 +1,12 @@
+"""
+Code used in a couple of places to work with the current thread's environment.
+Current users include i18n and request prefix handling.
+"""
+
+try:
+ import threading
+ currentThread = threading.currentThread
+except ImportError:
+ def currentThread():
+ return "no threading"
+
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
index 451420c7d8..93bb3c9bf9 100644
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -8,18 +8,7 @@ import gettext as gettext_module
from cStringIO import StringIO
from django.utils.safestring import mark_safe, SafeData
-
-try:
- import threading
- hasThreads = True
-except ImportError:
- hasThreads = False
-
-if hasThreads:
- currentThread = threading.currentThread
-else:
- def currentThread():
- return 'no threading'
+from django.utils.thread_support import currentThread
# Translations are cached in a dictionary for every language+app tuple.
# The active translations are stored by threadid to make them thread local.