Доступ к запросу пользователя в сигнале post_save
Описание проблемы
У меня есть сигнал post_save
, настроенный в моем проекте на Django. Вот код:
from django.db.models.signals import post_save
from django.contrib.auth.models import User
# CORE - SIGNALS
# Core Signals will operate based on post
def after_save_handler_attr_audit_obj(sender, **kwargs):
print(User.get_profile())
if hasattr(kwargs['instance'], 'audit_obj'):
if kwargs['created']:
kwargs['instance'].audit_obj.create(operation="INSERT", operation_by=**USER.ID**).save()
else:
kwargs['instance'].audit_obj.create(operation="UPDATE").save()
# Connect the handler with the post save signal - Django 1.2
post_save.connect(after_save_handler_attr_audit_obj, dispatch_uid="core.models.audit.new")
В столбце operation_by
я хочу получить user_id
и сохранить его. У кого-нибудь есть идеи, как это можно реализовать?
5 ответ(ов)
Это невозможно сделать. Текущий пользователь доступен только через запрос, который недоступен при использовании исключительно функциональности модели. Попробуйте получить доступ к пользователю в представлении.
Вы можете сделать это с помощью промежуточного ПО (middleware). Создайте файл get_request.py
в вашем приложении и добавьте следующий код:
from threading import current_thread
from django.utils.deprecation import MiddlewareMixin
_requests = {}
def current_request():
return _requests.get(current_thread().ident, None)
class RequestMiddleware(MiddlewareMixin):
def process_request(self, request):
_requests[current_thread().ident] = request
def process_response(self, request, response):
# когда ответ готов, запрос должен быть очищен
_requests.pop(current_thread().ident, None)
return response
def process_exception(self, request, exception):
# если произошла исключительная ситуация, запрос также должен быть очищен
_requests.pop(current_thread().ident, None)
Затем добавьте это промежуточное ПО в ваши настройки:
MIDDLEWARE = [
....
'<ваше_приложение>.get_request.RequestMiddleware',
]
После этого добавьте импорт в ваши сигналы:
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from <ваше_приложение>.get_request import current_request
# ЯДРО - СИГНАЛЫ
# Ядро сигналов будет работать на основании post_save
def after_save_handler_attr_audit_obj(sender, **kwargs):
print("Текущий пользователь:", current_request().user)
print(User.get_profile())
if hasattr(kwargs['instance'], 'audit_obj'):
if kwargs['created']:
kwargs['instance'].audit_obj.create(operation="INSERT", operation_by=**USER.ID**).save()
else:
kwargs['instance'].audit_obj.create(operation="UPDATE").save()
# Подключаем обработчик к сигналу post_save
post_save.connect(after_save_handler_attr_audit_obj, dispatch_uid="core.models.audit.new")
Этот код позволяет вам получать текущий запрос пользователя в обработчиках сигналов. Убедитесь, что вы заменили <ваше_приложение>
на название вашего приложения.
Я смог это сделать, изучив стек вызовов и искомую вьюху, а затем посмотрев на локальные переменные вьюхи, чтобы получить объект request
. Это кажется немного "хаком", но сработало.
Вот пример кода, который я использовал:
import inspect, os
@receiver(post_save, sender=MyModel)
def get_user_in_signal(sender, **kwargs):
for entry in reversed(inspect.stack()):
if os.path.dirname(__file__) + '/views.py' == entry[1]:
try:
user = entry[0].f_locals['request'].user
except:
user = None
break
if user:
# выполняем действия с переменной user
Обратите внимание, что этот подход можно считать не самым элегантным, так как он полагается на внутренние детали реализации, которые могут измениться. Рассмотрите возможность более чистого способа передачи данных в сигнал, например, с использованием контекста. Однако в случаях, когда нет других вариантов, описанный метод может быть полезен.
Илья прав. Сигналы модели в Django предназначены для уведомления других компонентов системы о событиях, связанных с экземплярами и их данными. Поэтому я полагаю, что справедливо, что вы не можете, например, получить данные запроса из сигнала post_save
, если эти данные не были сохранены или связаны с экземпляром.
Существует множество способов решения этой задачи, от худшего к лучшему, но я бы сказал, что это прекрасный пример для создания классовых/функциональных обобщённых представлений, которые будут автоматически обрабатывать это для вас.
Ваши представления, унаследованные от CreateView
, UpdateView
или DeleteView
, должны дополнительно наследоваться от вашего класса AuditMixin
, если они обрабатывают операции, связанные с моделями, которые требуют аудита. AuditMixin
сможет подключаться к представлениям, которые успешно создают, обновляют или удаляют объекты, и создавать запись в базе данных.
Это имеет полный смысл, очень чисто, легко подключаемо и порождает положительные эмоции. Но есть и другая сторона? Вам либо нужно будет использовать скоро выходящую версию Django 1.3, либо потратить время на модификацию функциональных обобщённых представлений и создание новых для каждой операции аудита.
Ваша идея использовать middleware для хранения текущего пользователя в thread_local
- интересный подход, но он имеет некоторые потенциальные недостатки, которые стоит учитывать.
Во-первых, использование thread_local
может привести к проблемам с многопоточностью, особенно если вы работаете в окружении с высоким уровнем параллелизма, например, в асинхронных приложениях или в приложениях с использованием WSGI. В таких случаях данные, хранящиеся в потоковых переменных, могут стать недоступными или могут быть перезаписаны, что может привести к неожиданным ошибкам.
Во-вторых, использование такого подхода может усложнить отладку и тестирование вашего кода. Данные о текущем пользователе, хранящиеся в thread_local
, могут затруднить понимание того, откуда именно берётся информация о пользователе, что делает код менее читабельным и предсказуемым.
С точки зрения архитектуры, лучше придерживаться подходов, которые подразумевают явное явление зависимости пользователя. Например, можно использовать Django Request (или аналогичный объект) для передачи информации о текущем пользователе через функции и методы.
Если вы всё же решите использовать такой подход, убедитесь, что у вас есть хорошие методы для управления жизненным циклом ваших потоковых данных, чтобы избежать утечек памяти и других проблем.
'pip' не распознан как командa внутреннего или внешнего формата
Как выполнить поиск в стиле getattr() в шаблоне Django
Инструмент для выявленияCircular Imports в Python/Django?
PIL / JPEG библиотека: "декодировщик JPEG недоступен"
"_set" в объекте queryset в Django