Django

Code

Changeset 8015

Show
Ignore:
Timestamp:
07/21/08 02:57:10 (3 months ago)
Author:
mtredinnick
Message:

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.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/conf/global_settings.py

    r7814 r8015  
    189189PREPEND_WWW = False 
    190190 
     191# Override the server-derived value of SCRIPT_NAME 
     192FORCE_SCRIPT_NAME = None 
     193 
    191194# List of compiled regular expression objects representing User-Agent strings 
    192195# that are not allowed to visit any page, systemwide. Use this for bad 
  • django/trunk/django/core/handlers/base.py

    r7995 r8015  
    44from django.core import signals 
    55from django.dispatch import dispatcher 
     6from django.utils.encoding import force_unicode 
    67 
    78class BaseHandler(object): 
     
    7475        resolver = urlresolvers.RegexURLResolver(r'^/', urlconf) 
    7576        try: 
    76             callback, callback_args, callback_kwargs = resolver.resolve(request.path) 
     77            callback, callback_args, callback_kwargs = resolver.resolve( 
     78                    request.path_info) 
    7779 
    7880            # Apply view middleware 
     
    171173        return response 
    172174 
     175def get_script_name(environ): 
     176    """ 
     177    Returns the equivalent of the HTTP request's SCRIPT_NAME environment 
     178    variable. If Apache mod_rewrite has been used, returns what would have been 
     179    the script name prior to any rewriting (so it's the script name as seen 
     180    from the client's perspective), unless DJANGO_USE_POST_REWRITE is set (to 
     181    anything). 
     182    """ 
     183    from django.conf import settings 
     184    if settings.FORCE_SCRIPT_NAME is not None: 
     185        return force_unicode(settings.FORCE_SCRIPT_NAME) 
     186 
     187    # If Apache's mod_rewrite had a whack at the URL, Apache set either 
     188    # SCRIPT_URL or REDIRECT_URL to the full resource URL before applying any 
     189    # rewrites. Unfortunately not every webserver (lighttpd!) passes this 
     190    # information through all the time, so FORCE_SCRIPT_NAME, above, is still 
     191    # needed. 
     192    script_url = environ.get('SCRIPT_URL', u'') 
     193    if not script_url: 
     194        script_url = environ.get('REDIRECT_URL', u'') 
     195    if script_url: 
     196        return force_unicode(script_url[:-len(environ.get('PATH_INFO', ''))]) 
     197    return force_unicode(environ.get('SCRIPT_NAME', u'')) 
     198 
  • django/trunk/django/core/handlers/modpython.py

    r7995 r8015  
    55from django.core import signals 
    66from django.core.handlers.base import BaseHandler 
     7from django.core.urlresolvers import set_script_prefix 
    78from django.dispatch import dispatcher 
    89from django.utils import datastructures 
     
    1617    def __init__(self, req): 
    1718        self._req = req 
     19        # FIXME: This isn't ideal. The request URI may be encoded (it's 
     20        # non-normalized) slightly differently to the "real" SCRIPT_NAME 
     21        # and PATH_INFO values. This causes problems when we compute path_info, 
     22        # below. For now, don't use script names that will be subject to 
     23        # encoding/decoding. 
    1824        self.path = force_unicode(req.uri) 
     25        root = req.get_options().get('django.root', '') 
     26        self.django_root = root 
     27        # req.path_info isn't necessarily computed correctly in all 
     28        # circumstances (it's out of mod_python's control a bit), so we use 
     29        # req.uri and some string manipulations to get the right value. 
     30        if root and req.uri.startswith(root): 
     31            self.path_info = force_unicode(req.uri[len(root):]) 
     32        else: 
     33            self.path_info = self.path 
    1934 
    2035    def __repr__(self): 
     
    101116                'CONTENT_TYPE':      self._req.content_type, # This may be wrong 
    102117                'GATEWAY_INTERFACE': 'CGI/1.1', 
    103                 'PATH_INFO':         self._req.path_info, 
     118                'PATH_INFO':         self.path_info, 
    104119                'PATH_TRANSLATED':   None, # Not supported 
    105120                'QUERY_STRING':      self._req.args, 
     
    109124                'REMOTE_USER':       self._req.user, 
    110125                'REQUEST_METHOD':    self._req.method, 
    111                 'SCRIPT_NAME':       None, # Not supported 
     126                'SCRIPT_NAME':       self.django_root, 
    112127                'SERVER_NAME':       self._req.server.server_hostname, 
    113128                'SERVER_PORT':       self._req.server.port, 
     
    154169            self.load_middleware() 
    155170 
     171        set_script_prefix(req.get_options().get('django.root', '')) 
    156172        dispatcher.send(signal=signals.request_started) 
    157173        try: 
  • django/trunk/django/core/handlers/wsgi.py

    r7995 r8015  
    88from django import http 
    99from django.core import signals 
    10 from django.core.handlers.base import BaseHandler 
     10from django.core.handlers import base 
     11from django.core.urlresolvers import set_script_prefix 
    1112from django.dispatch import dispatcher 
    1213from django.utils import datastructures 
     
    7576class WSGIRequest(http.HttpRequest): 
    7677    def __init__(self, environ): 
     78        script_name = base.get_script_name(environ) 
     79        path_info = force_unicode(environ.get('PATH_INFO', '/')) 
    7780        self.environ = environ 
    78         self.path = force_unicode(environ['PATH_INFO']) 
     81        self.path_info = path_info 
     82        self.path = '%s%s' % (script_name, path_info) 
    7983        self.META = environ 
     84        self.META['PATH_INFO'] = path_info 
     85        self.META['SCRIPT_NAME'] = script_name 
    8086        self.method = environ['REQUEST_METHOD'].upper() 
    8187 
     
    179185    raw_post_data = property(_get_raw_post_data) 
    180186 
    181 class WSGIHandler(BaseHandler): 
     187class WSGIHandler(base.BaseHandler): 
    182188    initLock = Lock() 
    183189    request_class = WSGIRequest 
     
    195201            self.initLock.release() 
    196202 
     203        set_script_prefix(base.get_script_name(environ)) 
    197204        dispatcher.send(signal=signals.request_started) 
    198205        try: 
  • django/trunk/django/core/urlresolvers.py

    r7995 r8015  
    77    (view_function, function_args, function_kwargs) 
    88""" 
     9 
     10import re 
    911 
    1012from django.http import Http404 
     
    1214from django.utils.encoding import iri_to_uri, force_unicode, smart_str 
    1315from django.utils.functional import memoize 
    14 import re 
     16from django.utils.thread_support import currentThread 
    1517 
    1618try: 
     
    2123_resolver_cache = {} # Maps urlconf modules to RegexURLResolver instances. 
    2224_callable_cache = {} # Maps view and url pattern names to their view functions. 
     25 
     26# SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for 
     27# the current thread (which is the only one we ever access), it is assumed to 
     28# be empty. 
     29_prefixes = {} 
    2330 
    2431class Resolver404(Http404): 
     
    292299    return get_resolver(urlconf).resolve(path) 
    293300 
    294 def reverse(viewname, urlconf=None, args=None, kwargs=None): 
     301def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None): 
    295302    args = args or [] 
    296303    kwargs = kwargs or {} 
    297     return iri_to_uri(u'/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs)) 
     304    if prefix is None: 
     305        prefix = get_script_prefix() 
     306    return iri_to_uri(u'%s%s' % (prefix, get_resolver(urlconf).reverse(viewname, 
     307            *args, **kwargs))) 
    298308 
    299309def clear_url_caches(): 
     
    302312    _resolver_cache.clear() 
    303313    _callable_cache.clear() 
     314 
     315def set_script_prefix(prefix): 
     316    """ 
     317    Sets the script prefix for the current thread. 
     318    """ 
     319    if not prefix.endswith('/'): 
     320        prefix += '/' 
     321    _prefixes[currentThread()] = prefix 
     322 
     323def get_script_prefix(): 
     324    """ 
     325    Returns the currently active script prefix. Useful for client code that 
     326    wishes to construct their own URLs manually (although accessing the request 
     327    instance is normally going to be a lot cleaner). 
     328    """ 
     329    return _prefixes.get(currentThread(), u'/') 
     330 
  • django/trunk/django/http/__init__.py

    r7995 r8015  
    3232        self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {} 
    3333        self.path = '' 
     34        self.path_info = '' 
    3435        self.method = None 
    3536 
     
    443444    else: 
    444445        return s 
     446 
  • django/trunk/django/test/client.py

    r7995 r8015  
    191191            'QUERY_STRING':      '', 
    192192            'REQUEST_METHOD':    'GET', 
    193             'SCRIPT_NAME':       None
     193            'SCRIPT_NAME':       ''
    194194            'SERVER_NAME':       'testserver', 
    195195            'SERVER_PORT':       80, 
  • django/trunk/django/utils/translation/trans_real.py

    r7995 r8015  
    99 
    1010from django.utils.safestring import mark_safe, SafeData 
    11  
    12 try: 
    13     import threading 
    14     hasThreads = True 
    15 except ImportError: 
    16     hasThreads = False 
    17  
    18 if hasThreads: 
    19     currentThread = threading.currentThread 
    20 else: 
    21     def currentThread(): 
    22         return 'no threading' 
     11from django.utils.thread_support import currentThread 
    2312 
    2413# Translations are cached in a dictionary for every language+app tuple. 
  • django/trunk/docs/fastcgi.txt

    r7294 r8015  
    8080list of all the available options. 
    8181 
    82 You'll need to specify either a ``socket``, ``protocol`` or both ``host`` and ``port``. 
    83 Then, when you set up your Web server, you'll just need to point it at the host/por
    84 or socket you specified when starting the FastCGI server. 
     82You'll need to specify either a ``socket``, ``protocol`` or both ``host`` and 
     83``port``. Then, when you set up your Web server, you'll just need to point i
     84at the host/port or socket you specified when starting the FastCGI server. 
    8585 
    8686Protocols 
     
    209209 
    210210.. _mod_rewrite: http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html 
     211 
     212Django will automatically use the pre-rewrite version of the URL when 
     213constructing URLs with the ``{% url %}`` template tag (and similar methods). 
    211214 
    212215lighttpd setup 
     
    337340.. _modpython: ../modpython/#serving-the-admin-files 
    338341 
     342Forcing the URL prefix to a particular value 
     343============================================ 
     344 
     345Because many of these fastcgi-based solutions require rewriting the URL at 
     346some point inside the webserver, the path information that Django sees may not 
     347resemble the original URL that was passed in. This is a problem if the Django 
     348application is being served from under a particular prefix and you want your 
     349URLs from the ``{% url %}`` tag to look like the prefix, rather than the 
     350rewritten version, which might contain, for example, ``mysite.fcgi``. 
     351 
     352Django makes a good attempt to work out what the real script name prefix 
     353should be. In particular, if the webserver sets the ``SCRIPT_URL`` (specific 
     354to Apache's mod_rewrite), or ``REDIRECT_URL`` (set by a few servers, including 
     355Apache + mod_rewrite in some situations), Django will work out the original 
     356prefix automatically. 
     357 
     358In the cases where Django cannot work out the prefix correctly and where you 
     359wan the original value to be used in URLs, you can set the 
     360``FORCE_SCRIPT_NAME`` setting in your main ``settings`` file. This sets the 
     361script name uniformly for every URL served via that settings file. Thus you'll 
     362need to use different settings files is you want different sets of URLs to 
     363have different script names in this case, but that is a rare situation. 
     364 
     365As an example of how to use it, if your Django configuration is serving all of 
     366the URLs under ``'/'`` and you wanted to use this setting, you would set 
     367``FORCE_SCRIPT_NAME = ''`` in your settings file. 
     368 
  • django/trunk/docs/modpython.txt

    r7294 r8015  
    3636        PythonHandler django.core.handlers.modpython 
    3737        SetEnv DJANGO_SETTINGS_MODULE mysite.settings 
     38        PythonOption django.root /mysite 
    3839        PythonDebug On 
    3940    </Location> 
     
    4546Django mod_python handler." It passes the value of ``DJANGO_SETTINGS_MODULE`` 
    4647so mod_python knows which settings to use. 
     48 
     49**New in Django development version:** Because mod_python does not know we are 
     50serving this site from underneath the ``/mysite/`` prefix, this value needs to 
     51be passed through to the mod_python handler in Django, via the ``PythonOption 
     52django.root ...`` line. The value set on that line (the last item) should 
     53match the string given in the ``<Location ...>`` directive. The effect of this 
     54is that Django will automatically strip the ``/mysite`` string from the front 
     55of any URLs before matching them against your ``URLConf`` patterns. If you 
     56later move your site to live under ``/mysite2``, you will not have to change 
     57anything except the ``django.root`` option in the config file. 
     58 
     59When using ``django.root`` you should make sure that what's left, after the 
     60prefix has been removed, begins with a slash. Your URLConf patterns that are 
     61expecting an initial slash will then work correctly. In the above example, 
     62since we want to send things like ``/mysite/admin/`` to ``/admin/``, we need 
     63to remove the string ``/mysite`` from the beginning, so that is the 
     64``django.root`` value. It would be an error to use ``/mysite/`` (with a 
     65trailing slash) in this case. 
    4766 
    4867Note that we're using the ``<Location>`` directive, not the ``<Directory>`` 
     
    6079        PythonHandler django.core.handlers.modpython 
    6180        SetEnv DJANGO_SETTINGS_MODULE mysite.settings 
     81        PythonOption django.root /mysite 
    6282        PythonDebug On 
    6383        **PythonPath "['/path/to/project'] + sys.path"** 
  • django/trunk/docs/settings.txt

    r7965 r8015  
    578578 
    579579.. _Testing Django Applications: ../testing/ 
     580 
     581FORCE_SCRIPT_NAME 
     582------------------ 
     583 
     584Default: ``None`` 
     585 
     586If not ``None``, this will be used as the value of the ``SCRIPT_NAME`` 
     587environment variable in any HTTP request. This setting can be used to override 
     588the server-provided value of ``SCRIPT_NAME``, which may be a rewritten version 
     589of the preferred value or not supplied at all. 
    580590 
    581591IGNORABLE_404_ENDS 
  • django/trunk/tests/regressiontests/requests/tests.py

    r7294 r8015  
    2121...        super(FakeModPythonRequest, self).__init__(*args, **kwargs) 
    2222...        self._get = self._post = self._meta = self._cookies = {} 
    23 >>> class Dummy: pass 
    24 ... 
     23>>> class Dummy: 
     24...     def get_options(self): 
     25...         return {} 
    2526>>> req = Dummy() 
    2627>>> req.uri = 'bogus'