Code

Ticket #16360: 16360.2.diff

File 16360.2.diff, 17.8 KB (added by jezdez, 3 years ago)

Latest state

Line 
1diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
2index 7b7531a..73d180c 100644
3--- a/django/conf/global_settings.py
4+++ b/django/conf/global_settings.py
5@@ -412,6 +412,11 @@ DEFAULT_INDEX_TABLESPACE = ''
6 # Default X-Frame-Options header value
7 X_FRAME_OPTIONS = 'SAMEORIGIN'
8 
9+# The import name of the WSGI application. If this is `None` the default
10+# 'django.core.handlers.wsgi.application' is used. Otherwise this shall
11+# point to an actual WSGI application.
12+WSGI_APPLICATION = None
13+
14 ##############
15 # MIDDLEWARE #
16 ##############
17diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py
18index 3a2243f..59a2d22 100644
19--- a/django/conf/project_template/settings.py
20+++ b/django/conf/project_template/settings.py
21@@ -99,6 +99,9 @@ MIDDLEWARE_CLASSES = (
22 
23 ROOT_URLCONF = '{{ project_name }}.urls'
24 
25+# The WSGI application used by Django's runserver etc.
26+WSGI_APPLICATION = '{{ project_name }}.wsgi.application'
27+
28 TEMPLATE_DIRS = (
29     # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
30     # Always use forward slashes, even on Windows.
31diff --git a/django/conf/project_template/wsgi.py b/django/conf/project_template/wsgi.py
32new file mode 100644
33index 0000000..7e37f60
34--- /dev/null
35+++ b/django/conf/project_template/wsgi.py
36@@ -0,0 +1,25 @@
37+"""
38+WSGI config for {{ project_name }} project.
39+
40+This module contains the actual WSGI application to be used by Django's
41+development server and the production environment as the global variable
42+named ``application`` and is enabled by setting the ``WSGI_APPLICATION``
43+setting to '{{ project_name }}.wsgi.application'.
44+
45+Of course usually you would have an actual Django WSGI application here,
46+but it also might make sense to replace the whole Django WSGI application
47+with a custom one that later delegates to the Django one (for instance if
48+you want to combine a Django application with an application of another
49+framework).
50+"""
51+from django.core.management import setup_settings
52+
53+settings = setup_settings(__name__)
54+
55+# The application object is used by the development server
56+# as well as a WSGI server configured to use this file.
57+from django.core.handlers.wsgi import application
58+
59+# Apply other WSGI middlewares here.
60+# from helloworld.wsgi import HelloWorldApplication
61+# application = HelloWorldApplication(application)
62diff --git a/django/contrib/staticfiles/handlers.py b/django/contrib/staticfiles/handlers.py
63index 962b835..d9f6833 100644
64--- a/django/contrib/staticfiles/handlers.py
65+++ b/django/contrib/staticfiles/handlers.py
66@@ -12,7 +12,9 @@ class StaticFilesHandler(WSGIHandler):
67     WSGI middleware that intercepts calls to the static files directory, as
68     defined by the STATIC_URL setting, and serves those files.
69     """
70-    def __init__(self, application, base_dir=None):
71+    def __init__(self, application, base_dir=None, enabled=True):
72+        # If not enabled this directly proxies to the given application.
73+        self.enabled = enabled
74         self.application = application
75         if base_dir:
76             self.base_dir = base_dir
77@@ -63,6 +65,6 @@ class StaticFilesHandler(WSGIHandler):
78         return super(StaticFilesHandler, self).get_response(request)
79 
80     def __call__(self, environ, start_response):
81-        if not self._should_handle(environ['PATH_INFO']):
82-            return self.application(environ, start_response)
83-        return super(StaticFilesHandler, self).__call__(environ, start_response)
84+        if self.enabled and self._should_handle(environ['PATH_INFO']):
85+            return super(StaticFilesHandler, self).__call__(environ, start_response)
86+        return self.application(environ, start_response)
87diff --git a/django/contrib/staticfiles/management/commands/runserver.py b/django/contrib/staticfiles/management/commands/runserver.py
88index c6e56d2..d4e332c 100644
89--- a/django/contrib/staticfiles/management/commands/runserver.py
90+++ b/django/contrib/staticfiles/management/commands/runserver.py
91@@ -21,7 +21,5 @@ class Command(BaseRunserverCommand):
92         handler = super(Command, self).get_handler(*args, **options)
93         use_static_handler = options.get('use_static_handler', True)
94         insecure_serving = options.get('insecure_serving', False)
95-        if (settings.DEBUG and use_static_handler or
96-                (use_static_handler and insecure_serving)):
97-            handler = StaticFilesHandler(handler)
98-        return handler
99+        enabled = use_static_handler and (settings.DEBUG or insecure_serving)
100+        return StaticFilesHandler(handler, enabled=enabled)
101diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
102index a6c8044..5364e65 100644
103--- a/django/core/handlers/base.py
104+++ b/django/core/handlers/base.py
105@@ -242,8 +242,8 @@ def get_script_name(environ):
106     Returns the equivalent of the HTTP request's SCRIPT_NAME environment
107     variable. If Apache mod_rewrite has been used, returns what would have been
108     the script name prior to any rewriting (so it's the script name as seen
109-    from the client's perspective), unless DJANGO_USE_POST_REWRITE is set (to
110-    anything).
111+    from the client's perspective), unless the FORCE_SCRIPT_NAME setting is
112+    set (to anything).
113     """
114     from django.conf import settings
115     if settings.FORCE_SCRIPT_NAME is not None:
116diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
117index 56d2ba6..540484d 100644
118--- a/django/core/handlers/wsgi.py
119+++ b/django/core/handlers/wsgi.py
120@@ -1,3 +1,63 @@
121+"""
122+WSGI entrypoints
123+
124+Overview
125+--------
126+
127+The motivation behind this module is to expose the WSGI application that
128+exists in Django to the actual Django user project. The idea is that you
129+have a documented way to apply WSGI middlewares that are then being used by
130+both the internal WSGI development server as well as any other WSGI server
131+in production.
132+
133+The way it works is that there are a handful of WSGI application objects:
134+
135+-   django.core.handlers.wsgi.WSGIHandler: this is the implementation of the
136+    actual Django WSGI dispatcher and it should be considered an
137+    implementation detail. While users are free to subclass and extend it,
138+    it's not necessarily supported.
139+-   django.core.handlers.wsgi.application: this is the documented WSGI
140+    application which is used by default.
141+-   django.core.handlers.wsgi.django_application: this is the actual WSGI
142+    application that is passed to the servers. It's internally proxying to
143+    the configured WSGI application and will most likely be a (decorated)
144+    version of the `application`.
145+
146+The reason why we have multiple applications here is because of the
147+ability to apply middlewares.  Basically `application` is the WSGI
148+application without any middlewarse applied, `django_application` is a
149+proxy to the base application + all applied middlewares.
150+
151+The middlewares are added according to the WSGI_APPLICATION configuration
152+setting which points to a module that imports the `application` as
153+`application` and can apply middlewares.
154+
155+If the WSGI_APPLICATION setting is ``None`` (which is the case for upgraded
156+applications from before we had exposed WSGI support) instead of the regular
157+application, the `application` is used.
158+
159+The WSGI_APPLICATION
160+--------------------
161+
162+The WSGI_APPLICATION configuration setting points to an actual WSGI
163+application.  By default the project generator will configure that a file
164+called `projectname.wsgi` exists and contains a global variable named
165+`application`. The contents look like this::
166+
167+    from django.core.handlers.wsgi import application
168+
169+This can be used to apply WSGI middlewares::
170+
171+    from helloworld.wsgi import HelloWorldApplication
172+    application = HelloWorldApplication(application)
173+
174+Of course usually you would have an actual Django WSGI application there, but
175+it also might make sense to replace the whole Django WSGI application with
176+a custom one that later delegates to the Django one (for instance if you
177+want to combine a Django application with an application of another framework).
178+
179+"""
180+from __future__ import with_statement
181 import sys
182 from threading import Lock
183 try:
184@@ -7,10 +67,12 @@ except ImportError:
185 
186 from django import http
187 from django.core import signals
188+from django.core.exceptions import ImproperlyConfigured
189 from django.core.handlers import base
190 from django.core.urlresolvers import set_script_prefix
191 from django.utils import datastructures
192 from django.utils.encoding import force_unicode, iri_to_uri
193+from django.utils.importlib import import_module
194 from django.utils.log import getLogger
195 
196 logger = getLogger('django.request')
197@@ -190,13 +252,12 @@ class WSGIRequest(http.HttpRequest):
198     FILES = property(_get_files)
199     REQUEST = property(_get_request)
200 
201+
202 class WSGIHandler(base.BaseHandler):
203     initLock = Lock()
204     request_class = WSGIRequest
205 
206     def __call__(self, environ, start_response):
207-        from django.conf import settings
208-
209         # Set up middleware if needed. We couldn't do this earlier, because
210         # settings weren't available.
211         if self._request_middleware is None:
212@@ -242,3 +303,36 @@ class WSGIHandler(base.BaseHandler):
213         start_response(status, response_headers)
214         return response
215 
216+application = WSGIHandler()
217+
218+
219+class DjangoWSGIApplication(object):
220+    """
221+    Implements a proxy to the actual WSGI application as configured
222+    by the user in settings.WSGI_APPLICATION which will usually be the
223+    `application`, optionally with middlewares applied.
224+    """
225+    def __init__(self):
226+        self._instance = None
227+        self._instance_lock = Lock()
228+
229+    def _load_application(self):
230+        from django.conf import settings
231+        app_path = getattr(settings, 'WSGI_APPLICATION')
232+        if app_path is None:
233+            return application
234+        try:
235+            module_name, attr = app_path.rsplit('.', 1)
236+            return getattr(import_module(module_name), attr)
237+        except (ImportError, AttributeError), e:
238+            raise ImproperlyConfigured("WSGI application '%s' could not "
239+                                       "be loaded: %s" % (app_path, e))
240+
241+    def __call__(self, environ, start_response):
242+        if self._instance is None:
243+            with self._instance_lock:
244+                self._instance = self._load_application()
245+        return self._instance(environ, start_response)
246+
247+# the proxy used in deployment
248+django_application = DjangoWSGIApplication()
249diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py
250index 1345eaf..d4b51cc 100644
251--- a/django/core/management/__init__.py
252+++ b/django/core/management/__init__.py
253@@ -392,10 +392,10 @@ def setup_environ(settings_mod, original_settings_path=None):
254     # way. For example, if this file (manage.py) lives in a directory
255     # "myproject", this code would add "/path/to/myproject" to sys.path.
256     if '__init__.py' in settings_mod.__file__:
257-        p = os.path.dirname(settings_mod.__file__)
258+        path = os.path.dirname(settings_mod.__file__)
259     else:
260-        p = settings_mod.__file__
261-    project_directory, settings_filename = os.path.split(p)
262+        path = settings_mod.__file__
263+    project_directory, settings_filename = os.path.split(path)
264     if project_directory == os.curdir or not project_directory:
265         project_directory = os.getcwd()
266     project_name = os.path.basename(project_directory)
267@@ -425,6 +425,42 @@ def setup_environ(settings_mod, original_settings_path=None):
268 
269     return project_directory
270 
271+
272+def setup_settings(path):
273+    """
274+    Configures the settings first by looking at the environment variable
275+    DJANGO_SETTINGS_MODULE or if that fail tries to find it with the
276+    given module path.
277+
278+    Example:
279+
280+    from django.core.management import setup_settings
281+
282+    setup_settings(__name__)
283+    setup_settings('wsgi')
284+    setup_settings('mysite.wsgi')
285+    """
286+    try:
287+        # Check if DJANGO_SETTINGS_MODULE is already given
288+        settings_module = os.environ['DJANGO_SETTINGS_MODULE']
289+        if not settings_module:
290+            raise KeyError
291+        settings = import_module(settings_module)
292+    except KeyError:
293+        # Or try importing the settings module two levels up,
294+        # so if name is 'mysite.wsgi', it'll try 'mysite.settings'
295+        try:
296+            settings = import_module('settings', path)
297+        except ImportError:
298+            # two levels up because name contains the submodule
299+            settings = import_module('..settings', path)
300+    setup_environ(settings)
301+
302+    # Return the settings module
303+    from django.conf import settings
304+    return settings
305+
306+
307 def execute_from_command_line(argv=None):
308     """
309     A simple method that runs a ManagementUtility.
310diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py
311index 2e693e7..c84bf30 100644
312--- a/django/core/management/commands/runserver.py
313+++ b/django/core/management/commands/runserver.py
314@@ -5,7 +5,7 @@ import sys
315 import socket
316 
317 from django.core.management.base import BaseCommand, CommandError
318-from django.core.handlers.wsgi import WSGIHandler
319+from django.core.handlers.wsgi import django_application
320 from django.core.servers.basehttp import AdminMediaHandler, run, WSGIServerException
321 from django.utils import autoreload
322 
323@@ -37,7 +37,7 @@ class BaseRunserverCommand(BaseCommand):
324         """
325         Returns the default WSGI handler for the runner.
326         """
327-        return WSGIHandler()
328+        return django_application
329 
330     def handle(self, addrport='', *args, **options):
331         self.use_ipv6 = options.get('use_ipv6')
332diff --git a/django/core/servers/fastcgi.py b/django/core/servers/fastcgi.py
333index 9f80d2b..442df44 100644
334--- a/django/core/servers/fastcgi.py
335+++ b/django/core/servers/fastcgi.py
336@@ -139,7 +139,7 @@ def runfastcgi(argset=[], **kwargs):
337         return False
338 
339     # Prep up and go
340-    from django.core.handlers.wsgi import WSGIHandler
341+    from django.core.handlers.wsgi import django_application
342 
343     if options["host"] and options["port"] and not options["socket"]:
344         wsgi_opts['bindAddress'] = (options["host"], int(options["port"]))
345@@ -178,7 +178,7 @@ def runfastcgi(argset=[], **kwargs):
346         fp.write("%d\n" % os.getpid())
347         fp.close()
348 
349-    WSGIServer(WSGIHandler(), **wsgi_opts).run()
350+    WSGIServer(django_application, **wsgi_opts).run()
351 
352 if __name__ == '__main__':
353     runfastcgi(sys.argv[1:])
354diff --git a/docs/howto/deployment/modwsgi.txt b/docs/howto/deployment/modwsgi.txt
355index de3a5b6..90d7ba3 100644
356--- a/docs/howto/deployment/modwsgi.txt
357+++ b/docs/howto/deployment/modwsgi.txt
358@@ -24,26 +24,28 @@ the details about how to use mod_wsgi. You'll probably want to start with the
359 Basic configuration
360 ===================
361 
362-Once you've got mod_wsgi installed and activated, edit your ``httpd.conf`` file
363-and add::
364+Once you've got mod_wsgi installed and activated, edit your ``httpd.conf``
365+file and add::
366 
367-    WSGIScriptAlias / /path/to/mysite/apache/django.wsgi
368+    WSGIScriptAlias / /path/to/mysite/wsgi.py
369 
370-The first bit above is the url you want to be serving your application at (``/``
371-indicates the root url), and the second is the location of a "WSGI file" -- see
372-below -- on your system, usually inside of your project. This tells Apache
373-to serve any request below the given URL using the WSGI application defined by that file.
374+The first bit above is the url you want to be serving your application at
375+(``/`` indicates the root url), and the second is the location of a "WSGI
376+file" -- see below -- on your system, usually inside of your project. This
377+tells Apache to serve any request below the given URL using the WSGI
378+application defined by that file.
379 
380 Next we'll need to actually create this WSGI application, so create the file
381 mentioned in the second part of ``WSGIScriptAlias`` and add::
382 
383-    import os
384     import sys
385+    from django.core.management import setup_settings
386 
387-    os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
388+    setup_settings(__name__)
389 
390-    import django.core.handlers.wsgi
391-    application = django.core.handlers.wsgi.WSGIHandler()
392+    # The application object is used by the development server
393+    # as well as a WSGI server configured to use this file.
394+    from django.core.handlers.wsgi import application
395 
396 If your project is not on your ``PYTHONPATH`` by default you can add::
397 
398@@ -51,9 +53,9 @@ If your project is not on your ``PYTHONPATH`` by default you can add::
399     if path not in sys.path:
400         sys.path.append(path)
401 
402-just below the ``import sys`` line to place your project on the path. Remember to
403-replace 'mysite.settings' with your correct settings file, and '/path/to/mysite'
404-with your own project's location.
405+just below the ``import sys`` line to place your project on the path.
406+Remember to replace 'mysite.settings' with your correct settings file,
407+and ``'/path/to/mysite'`` with your own project's location.
408 
409 .. _serving-files:
410 
411diff --git a/docs/howto/deployment/uwsgi.txt b/docs/howto/deployment/uwsgi.txt
412index 11cb51c..d3e631e 100644
413--- a/docs/howto/deployment/uwsgi.txt
414+++ b/docs/howto/deployment/uwsgi.txt
415@@ -112,7 +112,7 @@ Starting the server
416 Example command line for a Web server that understand the uWSGI protocol::
417 
418     uwsgi --chdir=/path/to/your/project
419-        --module='django.core.handlers.wsgi:WSGIHandler()' \
420+        --module='wsgi:application' \
421         --env DJANGO_SETTINGS_MODULE=settings \
422         --master --pidfile=/tmp/project-master.pid \
423         --socket=127.0.0.1:49152 \      # can also be a file
424@@ -128,7 +128,8 @@ Example command line for a Web server that understand the uWSGI protocol::
425 Django specific options are:
426 
427 * ``chdir``: should be the path to your project
428-* ``module``: uwsgi module to use
429+* ``module``: WSGI module to use, probably the ``wsgi`` module which
430+  :djadmin:`startproject` creates.
431 * ``pythonpath``: optional path to your project virtualenv
432 * ``env``: should contain at least ``DJANGO_SETTINGS_MODULE``
433