Code

Ticket #285: script_name_path_info3.diff

File script_name_path_info3.diff, 19.6 KB (added by jmelesky, 7 years ago)

another patch revision, against [6300], including docs and suggestions from Malcom

Line 
1Index: django/http/__init__.py
2===================================================================
3--- django/http/__init__.py     (revision 6301)
4+++ django/http/__init__.py     (working copy)
5@@ -26,6 +26,7 @@
6     def __init__(self):
7         self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
8         self.path = ''
9+        self.full_path = ''
10         self.method = None
11 
12     def __repr__(self):
13@@ -71,7 +72,7 @@
14             location = self.get_full_path()
15         if not ':' in location:
16             current_uri = '%s://%s%s' % (self.is_secure() and 'https' or 'http',
17-                                         self.get_host(), self.path)
18+                                         self.get_host(), self.full_path)
19             location = urljoin(current_uri, location)
20         return location
21 
22Index: django/core/handlers/wsgi.py
23===================================================================
24--- django/core/handlers/wsgi.py        (revision 6301)
25+++ django/core/handlers/wsgi.py        (working copy)
26@@ -75,8 +75,12 @@
27 class WSGIRequest(http.HttpRequest):
28     def __init__(self, environ):
29         self.environ = environ
30-        self.path = force_unicode(environ['PATH_INFO'])
31+        self.path = force_unicode(environ.get('PATH_INFO', '/'))
32+        self.full_path = (force_unicode(environ.get('SCRIPT_NAME', ''))
33+                          + force_unicode(environ.get('PATH_INFO', '/')))
34         self.META = environ
35+        self.META['PATH_INFO'] = self.path
36+        self.META['SCRIPT_NAME'] = force_unicode(environ.get('SCRIPT_NAME', ''))
37         self.method = environ['REQUEST_METHOD'].upper()
38 
39     def __repr__(self):
40@@ -102,7 +106,7 @@
41             (get, post, cookies, meta)
42 
43     def get_full_path(self):
44-        return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '')
45+        return '%s%s' % (self.full_path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '')
46 
47     def is_secure(self):
48         return 'HTTPS' in self.environ and self.environ['HTTPS'] == 'on'
49Index: django/core/handlers/modpython.py
50===================================================================
51--- django/core/handlers/modpython.py   (revision 6301)
52+++ django/core/handlers/modpython.py   (working copy)
53@@ -14,7 +14,13 @@
54 class ModPythonRequest(http.HttpRequest):
55     def __init__(self, req):
56         self._req = req
57-        self.path = force_unicode(req.uri)
58+        self.full_path = force_unicode(req.uri)
59+        root = req.get_options().get('django.root', '')
60+        self._django_root = force_unicode(root)
61+        if root and req.uri.startswith(root):
62+            self.path = force_unicode(req.uri[len(root):])
63+        else:
64+            self.path = self.full_path
65 
66     def __repr__(self):
67         # Since this is called as part of error handling, we need to be very
68@@ -39,7 +45,7 @@
69             (self.path, get, post, cookies, meta)
70 
71     def get_full_path(self):
72-        return '%s%s' % (self.path, self._req.args and ('?' + self._req.args) or '')
73+        return '%s%s' % (self.full_path, self._req.args and ('?' + self._req.args) or '')
74 
75     def is_secure(self):
76         # Note: modpython 3.2.10+ has req.is_https(), but we need to support previous versions
77@@ -94,7 +100,7 @@
78                 'CONTENT_LENGTH':    self._req.clength, # This may be wrong
79                 'CONTENT_TYPE':      self._req.content_type, # This may be wrong
80                 'GATEWAY_INTERFACE': 'CGI/1.1',
81-                'PATH_INFO':         self._req.path_info,
82+                'PATH_INFO':         self.path,
83                 'PATH_TRANSLATED':   None, # Not supported
84                 'QUERY_STRING':      self._req.args,
85                 'REMOTE_ADDR':       self._req.connection.remote_ip,
86@@ -102,7 +108,7 @@
87                 'REMOTE_IDENT':      self._req.connection.remote_logname,
88                 'REMOTE_USER':       self._req.user,
89                 'REQUEST_METHOD':    self._req.method,
90-                'SCRIPT_NAME':       None, # Not supported
91+                'SCRIPT_NAME':       self._django_root,
92                 'SERVER_NAME':       self._req.server.server_hostname,
93                 'SERVER_PORT':       self._req.server.port,
94                 'SERVER_PROTOCOL':   self._req.protocol,
95Index: django/views/generic/create_update.py
96===================================================================
97--- django/views/generic/create_update.py       (revision 6301)
98+++ django/views/generic/create_update.py       (working copy)
99@@ -21,7 +21,7 @@
100     """
101     if extra_context is None: extra_context = {}
102     if login_required and not request.user.is_authenticated():
103-        return redirect_to_login(request.path)
104+        return redirect_to_login(request.full_path)
105 
106     manipulator = model.AddManipulator(follow=follow)
107     if request.POST:
108@@ -87,7 +87,7 @@
109     """
110     if extra_context is None: extra_context = {}
111     if login_required and not request.user.is_authenticated():
112-        return redirect_to_login(request.path)
113+        return redirect_to_login(request.full_path)
114 
115     # Look up the object to be edited
116     lookup_kwargs = {}
117@@ -163,7 +163,7 @@
118     """
119     if extra_context is None: extra_context = {}
120     if login_required and not request.user.is_authenticated():
121-        return redirect_to_login(request.path)
122+        return redirect_to_login(request.full_path)
123 
124     # Look up the object to be edited
125     lookup_kwargs = {}
126Index: django/views/debug.py
127===================================================================
128--- django/views/debug.py       (revision 6301)
129+++ django/views/debug.py       (working copy)
130@@ -345,7 +345,7 @@
131     </tr>
132     <tr>
133       <th>Request URL:</th>
134-      <td>{{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.path|escape }}</td>
135+      <td>{{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.full_path|escape }}</td>
136     </tr>
137     <tr>
138       <th>Exception Type:</th>
139@@ -634,7 +634,7 @@
140       </tr>
141       <tr>
142         <th>Request URL:</th>
143-      <td>{{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.path|escape }}</td>
144+      <td>{{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.full_path|escape }}</td>
145       </tr>
146     </table>
147   </div>
148Index: django/contrib/syndication/feeds.py
149===================================================================
150--- django/contrib/syndication/feeds.py (revision 6301)
151+++ django/contrib/syndication/feeds.py (working copy)
152@@ -25,7 +25,7 @@
153     def __init__(self, slug, request):
154         self.slug = slug
155         self.request = request
156-        self.feed_url = request.path
157+        self.feed_url = request.full_path
158         self.title_template_name = self.title_template or ('feeds/%s_title.html' % slug)
159         self.description_template_name = self.description_template or ('feeds/%s_description.html' % slug)
160 
161Index: django/contrib/comments/views/userflags.py
162===================================================================
163--- django/contrib/comments/views/userflags.py  (revision 6301)
164+++ django/contrib/comments/views/userflags.py  (working copy)
165@@ -19,7 +19,7 @@
166     comment = get_object_or_404(Comment,pk=comment_id, site__id__exact=settings.SITE_ID)
167     if request.POST:
168         UserFlag.objects.flag(comment, request.user)
169-        return HttpResponseRedirect('%sdone/' % request.path)
170+        return HttpResponseRedirect('%sdone/' % request.full_path)
171     return render_to_response('comments/flag_verify.html', {'comment': comment},
172         context_instance=RequestContext(request, extra_context, context_processors))
173 flag = login_required(flag)
174@@ -50,7 +50,7 @@
175             comment.save()
176             m = ModeratorDeletion(None, request.user.id, comment.id, None)
177             m.save()
178-        return HttpResponseRedirect('%sdone/' % request.path)
179+        return HttpResponseRedirect('%sdone/' % request.full_path)
180     return render_to_response('comments/delete_verify.html', {'comment': comment},
181         context_instance=RequestContext(request, extra_context, context_processors))
182 delete = login_required(delete)
183Index: django/contrib/admin/views/auth.py
184===================================================================
185--- django/contrib/admin/views/auth.py  (revision 6301)
186+++ django/contrib/admin/views/auth.py  (working copy)
187@@ -20,7 +20,7 @@
188             msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user}
189             if "_addanother" in request.POST:
190                 request.user.message_set.create(message=msg)
191-                return HttpResponseRedirect(request.path)
192+                return HttpResponseRedirect(request.full_path)
193             else:
194                 request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
195                 return HttpResponseRedirect('../%s/' % new_user.id)
196Index: django/contrib/admin/views/main.py
197===================================================================
198--- django/contrib/admin/views/main.py  (revision 6301)
199+++ django/contrib/admin/views/main.py  (working copy)
200@@ -276,7 +276,7 @@
201                     (pk_value, force_unicode(new_object).replace('"', '\\"')))
202             elif "_addanother" in request.POST:
203                 request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
204-                return HttpResponseRedirect(request.path)
205+                return HttpResponseRedirect(request.full_path)
206             else:
207                 request.user.message_set.create(message=msg)
208                 return HttpResponseRedirect(post_url)
209@@ -353,9 +353,9 @@
210             if "_continue" in request.POST:
211                 request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
212                 if '_popup' in request.REQUEST:
213-                    return HttpResponseRedirect(request.path + "?_popup=1")
214+                    return HttpResponseRedirect(request.full_path + "?_popup=1")
215                 else:
216-                    return HttpResponseRedirect(request.path)
217+                    return HttpResponseRedirect(request.full_path)
218             elif "_saveasnew" in request.POST:
219                 request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(new_object)})
220                 return HttpResponseRedirect("../%s/" % pk_value)
221@@ -778,7 +778,7 @@
222         # is screwed up with the database, so display an error page.
223         if ERROR_FLAG in request.GET.keys():
224             return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
225-        return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
226+        return HttpResponseRedirect(request.full_path + '?' + ERROR_FLAG + '=1')
227     c = template.RequestContext(request, {
228         'title': cl.title,
229         'is_popup': cl.is_popup,
230Index: django/contrib/admin/views/decorators.py
231===================================================================
232--- django/contrib/admin/views/decorators.py    (revision 6301)
233+++ django/contrib/admin/views/decorators.py    (working copy)
234@@ -22,7 +22,7 @@
235         post_data = _encode_post_data({})
236     return render_to_response('admin/login.html', {
237         'title': _('Log in'),
238-        'app_path': request.path,
239+        'app_path': request.full_path,
240         'post_data': post_data,
241         'error_message': error_message
242     }, context_instance=template.RequestContext(request))
243@@ -99,7 +99,7 @@
244                         return view_func(request, *args, **kwargs)
245                     else:
246                         request.session.delete_test_cookie()
247-                        return http.HttpResponseRedirect(request.path)
248+                        return http.HttpResponseRedirect(request.full_path)
249             else:
250                 return _display_login_form(request, ERROR_MESSAGE)
251 
252Index: django/contrib/admin/views/doc.py
253===================================================================
254--- django/contrib/admin/views/doc.py   (revision 6301)
255+++ django/contrib/admin/views/doc.py   (working copy)
256@@ -27,7 +27,7 @@
257 
258 def bookmarklets(request):
259     # Hack! This couples this view to the URL it lives at.
260-    admin_root = request.path[:-len('doc/bookmarklets/')]
261+    admin_root = request.full_path[:-len('doc/bookmarklets/')]
262     return render_to_response('admin_doc/bookmarklets.html', {
263         'admin_url': "%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root),
264     }, context_instance=RequestContext(request))
265Index: django/contrib/databrowse/plugins/objects.py
266===================================================================
267--- django/contrib/databrowse/plugins/objects.py        (revision 6301)
268+++ django/contrib/databrowse/plugins/objects.py        (working copy)
269@@ -8,7 +8,7 @@
270     def model_view(self, request, model_databrowse, url):
271         # If the object ID wasn't provided, redirect to the model page, which is one level up.
272         if url is None:
273-            return http.HttpResponseRedirect(urlparse.urljoin(request.path, '../'))
274+            return http.HttpResponseRedirect(urlparse.urljoin(request.full_path, '../'))
275         easy_model = EasyModel(model_databrowse.site, model_databrowse.model)
276         obj = easy_model.object_by_pk(url)
277         return render_to_response('databrowse/object_detail.html', {'object': obj, 'root_url': model_databrowse.site.root_url})
278Index: django/contrib/databrowse/sites.py
279===================================================================
280--- django/contrib/databrowse/sites.py  (revision 6301)
281+++ django/contrib/databrowse/sites.py  (working copy)
282@@ -110,7 +110,7 @@
283 
284         `url` is the remainder of the URL -- e.g. 'comments/comment/'.
285         """
286-        self.root_url = request.path[:len(request.path) - len(url)]
287+        self.root_url = request.full_path[:len(request.full_path) - len(url)]
288         url = url.rstrip('/') # Trim trailing slash, if it exists.
289 
290         if url == '':
291Index: django/contrib/auth/views.py
292===================================================================
293--- django/contrib/auth/views.py        (revision 6301)
294+++ django/contrib/auth/views.py        (working copy)
295@@ -47,7 +47,7 @@
296         return render_to_response(template_name, {'title': _('Logged out')}, context_instance=RequestContext(request))
297     else:
298         # Redirect to this page until the session has been cleared.
299-        return HttpResponseRedirect(next_page or request.path)
300+        return HttpResponseRedirect(next_page or request.full_path)
301 
302 def logout_then_login(request, login_url=None):
303     "Logs out the user if he is logged in. Then redirects to the log-in page."
304@@ -75,7 +75,7 @@
305                 form.save(domain_override=request.META['HTTP_HOST'])
306             else:
307                 form.save(email_template_name=email_template_name)
308-            return HttpResponseRedirect('%sdone/' % request.path)
309+            return HttpResponseRedirect('%sdone/' % request.full_path)
310     return render_to_response(template_name, {'form': oldforms.FormWrapper(form, new_data, errors)},
311         context_instance=RequestContext(request))
312 
313@@ -90,7 +90,7 @@
314         errors = form.get_validation_errors(new_data)
315         if not errors:
316             form.save(new_data)
317-            return HttpResponseRedirect('%sdone/' % request.path)
318+            return HttpResponseRedirect('%sdone/' % request.full_path)
319     return render_to_response(template_name, {'form': oldforms.FormWrapper(form, new_data, errors)},
320         context_instance=RequestContext(request))
321 password_change = login_required(password_change)
322Index: django/contrib/flatpages/views.py
323===================================================================
324--- django/contrib/flatpages/views.py   (revision 6301)
325+++ django/contrib/flatpages/views.py   (working copy)
326@@ -25,7 +25,7 @@
327     # logged in, redirect to the login page.
328     if f.registration_required and not request.user.is_authenticated():
329         from django.contrib.auth.views import redirect_to_login
330-        return redirect_to_login(request.path)
331+        return redirect_to_login(request.full_path)
332     if f.template_name:
333         t = loader.select_template((f.template_name, DEFAULT_TEMPLATE))
334     else:
335Index: django/middleware/common.py
336===================================================================
337--- django/middleware/common.py (revision 6301)
338+++ django/middleware/common.py (working copy)
339@@ -33,7 +33,7 @@
340 
341         # Check for a redirect based on settings.APPEND_SLASH and settings.PREPEND_WWW
342         host = request.get_host()
343-        old_url = [host, request.path]
344+        old_url = [host, request.full_path]
345         new_url = old_url[:]
346         if settings.PREPEND_WWW and old_url[0] and not old_url[0].startswith('www.'):
347             new_url[0] = 'www.' + old_url[0]
348Index: AUTHORS
349===================================================================
350--- AUTHORS     (revision 6301)
351+++ AUTHORS     (working copy)
352@@ -319,6 +319,7 @@
353     ymasuda@ethercube.com
354     Jarek Zgoda <jarek.zgoda@gmail.com>
355     Cheng Zhang
356+    John Melesky
357 
358 A big THANK YOU goes to:
359 
360Index: docs/request_response.txt
361===================================================================
362--- docs/request_response.txt   (revision 6301)
363+++ docs/request_response.txt   (working copy)
364@@ -24,6 +24,15 @@
365 All attributes except ``session`` should be considered read-only.
366 
367 ``path``
368+    A string representing the path to the requested page relative to
369+    the Django root, not including the domain. For WSGI servers, this
370+    means it does not include SCRIPT_NAME. For mod_python servers,
371+    this does not include the user-configurable django.root
372+    PythonOption.
373+
374+    Example: ``"/bands/the_beatles/"``
375+
376+``full_path``
377     A string representing the full path to the requested page, not including
378     the domain.
379 
380@@ -112,6 +121,7 @@
381         * ``REQUEST_METHOD`` -- A string such as ``"GET"`` or ``"POST"``.
382         * ``SERVER_NAME`` -- The hostname of the server.
383         * ``SERVER_PORT`` -- The port of the server.
384+        * ``SCRIPT_NAME`` -- The location of the Django app (e.g. "/mysite")
385 
386 ``user``
387     A ``django.contrib.auth.models.User`` object representing the currently
388@@ -157,7 +167,7 @@
389    ``request.POST`` has the given key.
390 
391 ``get_full_path()``
392-   Returns the ``path``, plus an appended query string, if applicable.
393+   Returns the ``full_path``, plus an appended query string, if applicable.
394 
395    Example: ``"/music/bands/the_beatles/?print=true"``
396 
397Index: docs/modpython.txt
398===================================================================
399--- docs/modpython.txt  (revision 6301)
400+++ docs/modpython.txt  (working copy)
401@@ -36,6 +36,7 @@
402         PythonHandler django.core.handlers.modpython
403         SetEnv DJANGO_SETTINGS_MODULE mysite.settings
404         PythonDebug On
405+        PythonOption django.root /mysite
406     </Location>
407 
408 ...and replace ``mysite.settings`` with the Python import path to your Django
409@@ -45,6 +46,11 @@
410 Django mod_python handler." It passes the value of ``DJANGO_SETTINGS_MODULE``
411 so mod_python knows which settings to use.
412 
413+The PythonOption tells Django: "Pass all requests through to the application as
414+if they were rooted at '/', but generate absolute URLs under '/mysite'." This
415+way, Django applications can be installed in any subdirectory URL without
416+having the full path hardwired in.
417+
418 Note that we're using the ``<Location>`` directive, not the ``<Directory>``
419 directive. The latter is used for pointing at places on your filesystem,
420 whereas ``<Location>`` points at places in the URL structure of a Web site.
421@@ -60,6 +66,7 @@
422         PythonHandler django.core.handlers.modpython
423         SetEnv DJANGO_SETTINGS_MODULE mysite.settings
424         PythonDebug On
425+        PythonOption django.root /mysite
426         **PythonPath "['/path/to/project'] + sys.path"**
427     </Location>
428