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时会发生以下情况:

  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)