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 происходят следующие вещи:

  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)
Вернуться на верх