Code

Ticket #285: script_name_path_info4.diff

File script_name_path_info4.diff, 20.1 KB (added by jmelesky, 7 years ago)

Another revision, against [6316]. This one actually passes the test suite.

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