Django 隐藏重定向

3 投票
2 回答
1100 浏览
提问于 2025-04-18 04:00

我想创建一个在应用内重定向的机制,这种重定向并不是直接跳转,而是加载另一个视图函数。比如说,我访问 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 个回答

0

这是一个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
1

你需要在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轻松实现。

撰写回答