Django Middleware: RecursionError при обращении к `self.request.user` в обертке запроса к базе данных
Я тестирую свой промежуточный модуль запросов к базе данных (Django docs here) на примере приложения django с базой данных Postgres. Приложение представляет собой котел 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) закомментировать, все работает нормально. Однако я обнаружил, что отсутствие комментария или любой тип взаимодействия с полем user в объекте self.request вызывает ошибку рекурсии:
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 существует, и мой middleware также находится в самом низу моих middlewares (которые включают "django.contrib.sessions.middleware.SessionMiddleware" и "django.contrib.auth.middleware.AuthenticationMiddleware")
Я никак не могу понять, что здесь не так. Я что-то упустил? TIA!
При написании 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)