Django中间件:当访问' self.request. error . '数据库查询包装器中的用户



我正在测试我的数据库查询中间件(Django文档在这里)的样本Django应用程序与Postgres数据库。这款应用是cookiecutter的样板。我使用中间件的目的只是记录所有数据库查询的用户ID。使用Python3.9和Django3.2.13。我的中间件代码如下:

# Middleware code
import logging
import django
from django.db import connection
django_version = django.get_version()
logger = logging.getLogger(__name__)

class Logger:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
with connection.execute_wrapper(QueryWrapper(request)):
return self.get_response(request)
class QueryWrapper:
def __init__(self, request):
self.request = request
def __call__(self, execute, sql, params, many, context):
# print(self.request.user)
return execute(sql, params, many, context)

如果print(self.request.user.id)被注释掉,一切正常。然而,我发现取消注释,或者与self.request对象中的user字段进行任何类型的交互,都会导致递归错误:

RecursionError at /about/
maximum recursion depth exceeded
Request Method: GET
Request URL:    http://127.0.0.1:8000/about/
Django Version: 3.2.13
Exception Type: RecursionError
Exception Value:    
maximum recursion depth exceeded
Exception Location: /opt/homebrew/lib/python3.9/site-packages/django/db/models/sql/query.py, line 192, in __init__
Python Executable:  /opt/homebrew/opt/python@3.9/bin/python3.9
Python Version: 3.9.13

在错误页面中,后面跟着多次重复下面的错误:

During handling of the above exception ('SessionStore' object has no attribute '_session_cache'), another exception occurred:
/opt/homebrew/lib/python3.9/site-packages/django/contrib/sessions/backends/base.py, line 233, in _get_session
return self._session_cache …
During handling of the above exception ('SessionStore' object has no attribute '_session_cache'), another exception occurred:
/opt/homebrew/lib/python3.9/site-packages/django/contrib/sessions/backends/base.py, line 233, in _get_session
return self._session_cache …

从咨询其他SO帖子,似乎访问user字段应该工作良好。我已经检查了django_session表存在,我的中间件也位于我的中间件的最底部(包括"django.contrib.sessions.middleware.SessionMiddleware""django.contrib.auth.middleware.AuthenticationMiddleware")

怎么了?

当您编写self.request.user时,会发生以下情况:

  1. 检查request是否有_cached_user属性,如果存在,则返回缓存的用户,如果不调用auth.get_user,则返回请求。
  2. 使用会话密钥,Django检查会话以获取当前用户使用的认证后端。在这里,如果你使用基于数据库的会话,一个数据库查询是解雇。
  3. 使用上面的认证后端,Django生成一个数据库查询获取当前用户的ID。

如上所述,除非有缓存命中,否则该进程将导致数据库查询。

使用数据库插装,您已经在数据库查询周围安装了一个包装器,问题是这个包装器本身试图使更多的查询(试图获得当前用户),导致它调用自己。一种解决方案是在安装包装器函数之前获取当前用户:

class Logger:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
_user_authenticated = request.user.is_authenticated # Making sure user is fetched (Lazy object)
with connection.execute_wrapper(QueryWrapper(request)):
return self.get_response(request)

class QueryWrapper:
def __init__(self, request):
self.request = request
def __call__(self, execute, sql, params, many, context):
print(self.request.user)
return execute(sql, params, many, context)

最新更新