Django 1.11 中间件如何正确重定向

Django 1.11 middleware how to redirect properly

我无法解决中间件的问题。如果用户尚未设置其公司,我正在从事的项目应该将所有访问网站任何部分的请求重定向到 add_company 页面。为此,我正在编写一个自定义中间件。但是,以下代码不起作用:

class ProjectTrackingMiddleWare(MiddlewareMixin):
    ''' 
        Note that this Middleware should come after all other middlewares 
        in order to 
    '''
    def __init__(self, get_response=None):
        ''' One-time configuration called when the web-server starts '''
        self.get_response = get_response

    def __call__(self, request):
        ''' To be executed on every request.  '''

        the_request = self.process_request(request)
        response = self.get_response(request)  

        return response

    def process_request(self, request):

        the_user = request.user
        print ('the_user: ', the_user)

        ## CASE 1: User is not authenticated -->  Redirect to login page
        if the_user.
            return HttpResponseRedirect(reverse('accounts:login'))      #  <-- This part works

         the_company = the_user.userprofile.employer    
        if the_user and not the_company:
            print ('the company: ', the_company)
            return HttpResponseRedirect(reverse('project_profile:add_company'))   #<-- Does NOT redirect correctly

        if the_user.projects.all().count() > 1:
            request.project = the_user.projects.all().order_by('-date_modified')[0]

        return request

中间件不会重定向到 add_company 页面。相反,如果作为未设置项目的登录用户尝试访问页面,Django 让我访问该页面:

当我尝试访问主页时,重定向没有发生。但是,如果我要访问站点上的 /development/ 页面,则会出现以下错误:

Exception Type: TypeError at /development/

Exception Value: context must be a dict rather than HttpResponseRedirect.

当我查看上下文时,它是:

Variable    Value
context     <HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="/project/add/">
kwargs      {'autoescape': True}
request     <WSGIRequest: GET '/development/'>

当我尝试访问 /development/ 页面时似乎发生了重定向,因为 url="/project/add/"> 表明它确实在 HttpResponseRedirect 上获取。

所以,这是我的问题: 1) 似乎在 process_request 中重定向是可以的,还是不推荐?如果没有,那么我应该在哪里检查这些全局变量并将用户重定向到正确的设置?

2) 如何获得与客户端类似的重定向链:

client.get(reverse(‘dashboard’), follow = True)

编辑 1: 正如 Iain 的评论中所建议的,我应该将 the_request 传递给 get_response 方法。但是,这并没有解决问题。 Django 现在抱怨 HttpResponseRedirect 没有属性路径:

'HttpResponseRedirect' object has no attribute 'path'

奇怪的是,如果我根据公司的存在重定向到登录名 url,我也会收到此错误,但如果我将匿名用户重定向到登录名 url, 我没有得到这个错误:

## Redirects successfully when the user is anonymous. If the user is logged-in already, it skips it as expected
if the_user.is_anonymous():
    return HttpResponseRedirect(reverse('accounts:login'))
### Throws attribute excpetion: 'HttpResponseRedirect' object has no attribute 'path'
if not the_company:
    return HttpResponseRedirect(reverse('accounts:login'))

看来错误与请求对象有关。但是,我现在无法弄清楚。有什么想法吗?

您编写中间件的方式不太合理。您的 process_request 方法返回一个 Response 对象,然后您将其传递给 get_response。这是无效的 - get_response 需要一个 请求 ,而不是 响应

由于您希望在某些情况下使响应短路,因此如果您想要重定向,则需要完全跳过调用 get_response。这意味着您的检查需要在调用 get_response 之前进行。像这样:

def __call__(self, request):
    # Perform your checks here, and redirect if necessary
    the_user = request.user
    ## CASE 1: User is not authenticated -->  Redirect to login page
    if not the_user:
        return HttpResponseRedirect(reverse('accounts:login'))
    the_company = the_user.userprofile.employer    
    if not the_company:
        return HttpResponseRedirect(reverse('project_profile:add_company'))  

    if the_user.projects.all().count() > 1:
        request.project = the_user.projects.all().order_by('-date_modified')[0]

    # Now that you've done your checks, let the rest of the request process run
    response = self.get_response(request)  
    return response

(删除 process_request 方法,因为它不再使用)。

我最终解决这个问题的方法是使用 process_view 钩子。

AUTHENTICATION_EXEMPT_URLS = (
                           reverse('accounts:signup').lstrip('/'),   # reverse part returns '/accounts/login' and lstrip('/') drops the '/': 'accounts/login'
                           reverse('accounts:login').lstrip('/'),
                           reverse('accounts:logout').lstrip('/'),
                        )

COMPANY_EXEMPT_URLS =  (   reverse('project_profile:add_company').lstrip('/'), 
                       reverse('accounts:signup').lstrip('/'),
                       reverse('accounts:login').lstrip('/'),
                       reverse('accounts:logout').lstrip('/'),
                   )


class MyRedirectMiddleware(MiddlewareMixin):

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):    
        response = self.get_response(request)
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        assert hasattr(request, 'user')       # being logged in is required
        path = request.path_info.lstrip('/')

        if not request.user.is_authenticated():

            if not any(url for url in AUTHENTICATION_EXEMPT_URLS if url==path):    
            # if the url is not in any of the list's urls, redirect to authentication page
                return redirect(settings.LOGIN_URL)

        #  Note that userprofile is created using a signal currently at user_profile/models.py 
        the_company = request.user.userprofile.employer
        if request.user.is_authenticated() and not the_company:
            # user should setup a copmany first:
            if not any( url for url in COMPANY_EXEMPT_URLS if url==path):
                return redirect(reverse('project_profile:add_company'))

我应该感谢 YouTube tutorial 并感谢所有花时间提供帮助的人,你们太棒了。