Django 中间件:在数据库查询包装器中访问“self.request.user”时出现递归错误
Django Middleware: RecursionError when accessing `self.request.user` in database query wrapper
我正在测试我的数据库查询中间件 (Django docs here) on a sample django app with a Postgres db. The app is the cookiecutter boilerplate。我使用中间件的目标只是记录所有数据库查询的用户 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 table 存在,我的中间件也位于我的中间件的最底部(包括 "django.contrib.sessions.middleware.SessionMiddleware"
和 "django.contrib.auth.middleware.AuthenticationMiddleware"
)
这是怎么回事?
写self.request.user
时会发生以下情况:
- 检查
request
的属性 _cached_user
,如果存在则返回缓存的用户,如果不存在则返回缓存的用户 auth.get_user
随请求一起调用。
- Django 使用会话密钥检查会话以获取用于当前用户的身份验证后端。在这里,如果您使用的是基于数据库的会话,则会触发 数据库查询 。
- 使用上述身份验证后端 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)
我正在测试我的数据库查询中间件 (Django docs here) on a sample django app with a Postgres db. The app is the cookiecutter boilerplate。我使用中间件的目标只是记录所有数据库查询的用户 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 table 存在,我的中间件也位于我的中间件的最底部(包括 "django.contrib.sessions.middleware.SessionMiddleware"
和 "django.contrib.auth.middleware.AuthenticationMiddleware"
)
这是怎么回事?
写self.request.user
时会发生以下情况:
- 检查
request
的属性_cached_user
,如果存在则返回缓存的用户,如果不存在则返回缓存的用户auth.get_user
随请求一起调用。 - Django 使用会话密钥检查会话以获取用于当前用户的身份验证后端。在这里,如果您使用的是基于数据库的会话,则会触发 数据库查询 。
- 使用上述身份验证后端 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)