Django 隐藏重定向
我想创建一个在应用内重定向的机制,这种重定向并不是直接跳转,而是加载另一个视图函数。比如说,我访问 localhost:8000/a
,Django 会渲染这个视图,而这个视图的最后一行代码是:
return HttpResponseRedirect('/b')
这样我的浏览器就会跳转到 localhost:8000/b
,Django 会渲染这个视图并把结果返回给我。
现在我想要的是只在服务器端进行这种操作。当我访问 localhost:8000/a
时,如果 Django 决定要重定向到 /b
,它就会渲染 /b
的视图,经过所有的中间件等处理(就像正常的重定向一样),然后返回响应,而用户根本看不到任何重定向的过程。
我把这种重定向叫做 HttpResponseSmartRedirect
,它的样子是这样的:
class HttpResponseSmartRedirect(HttpResponseRedirect):
pass
现在我想为这个创建一个中间件(这个中间件在所有中间件中最后执行),在 process_response
方法中检查响应是否是 HttpResponseSmartRedirect
类的实例。我的问题是,当我遇到这种情况时,如何实际生成来自这个 URL 的视图响应?我可以通过使用 resolve()
从 urlsolvers
获取视图函数,但我不知道如何让它经过所有中间件,并生成一个与普通重定向得到的响应完全相同的结果。最糟糕的做法是直接调用 urllib2.get(url)
,但看到那行代码我就想摧毁我的电脑。
有没有什么好的方法可以做到这一点,而不需要调用那些丑陋的 urllib
呢?
2 个回答
这是一个BaseHandler的例子(在不重定向的情况下添加斜杠,替代CommonMiddleware,适用于Django 2.1):
from django.http import HttpResponsePermanentRedirect, HttpRequest
from django.core.handlers.base import BaseHandler
from django.middleware.common import CommonMiddleware
from django.conf import settings
class HttpSmartRedirectResponse(HttpResponsePermanentRedirect):
pass
class CommonMiddlewareAppendSlashWithoutRedirect(CommonMiddleware):
""" This class converts HttpSmartRedirectResponse to the common response
of Django view, without redirect.
"""
response_redirect_class = HttpSmartRedirectResponse
def __init__(self, *args, **kwargs):
# create django request resolver
self.handler = BaseHandler()
# prevent recursive includes
old = settings.MIDDLEWARE
name = self.__module__ + '.' + self.__class__.__name__
settings.MIDDLEWARE = [i for i in settings.MIDDLEWARE if i != name]
self.handler.load_middleware()
settings.MIDDLEWARE = old
super(CommonMiddlewareAppendSlashWithoutRedirect, self).__init__(*args, **kwargs)
def process_response(self, request, response):
response = super(CommonMiddlewareAppendSlashWithoutRedirect, self).process_response(request, response)
if isinstance(response, HttpSmartRedirectResponse):
if not request.path.endswith('/'):
request.path = request.path + '/'
# we don't need query string in path_info because it's in request.GET already
request.path_info = request.path
response = self.handler.get_response(request)
return response
你需要在Django处理请求的代码底部重新注入原始请求。
请求的处理是从 BaseHandler.get_response
开始的,这个方法在 django.core.handlers.base
中。
所以解决方案大概是这样的(未经测试!):
from django.core.handlers.base import BaseHandler
class SmartRedirectMiddleware(object):
def process_response(self, request, response):
if isinstance(response, HttpSmartRedirectResponse):
handler = BaseHandler()
request.url = response.redirect_url
resp = handler.get_response(request)
return resp
在实际使用中,你需要防止重定向循环和无限递归的问题。
或者你可以换个方法来解决这个问题——让浏览器根据重定向自己重新获取数据。如果你想让这个过程对用户不可见,可以通过一些客户端的JavaScript轻松实现。