0

Доступ к запросу пользователя в сигнале post_save

15

Описание проблемы

У меня есть сигнал 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 ответ(ов)

0

Это невозможно сделать. Текущий пользователь доступен только через запрос, который недоступен при использовании исключительно функциональности модели. Попробуйте получить доступ к пользователю в представлении.

0

Вы можете сделать это с помощью промежуточного ПО (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")

Этот код позволяет вам получать текущий запрос пользователя в обработчиках сигналов. Убедитесь, что вы заменили <ваше_приложение> на название вашего приложения.

0

Я смог это сделать, изучив стек вызовов и искомую вьюху, а затем посмотрев на локальные переменные вьюхи, чтобы получить объект 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

Обратите внимание, что этот подход можно считать не самым элегантным, так как он полагается на внутренние детали реализации, которые могут измениться. Рассмотрите возможность более чистого способа передачи данных в сигнал, например, с использованием контекста. Однако в случаях, когда нет других вариантов, описанный метод может быть полезен.

0

Илья прав. Сигналы модели в Django предназначены для уведомления других компонентов системы о событиях, связанных с экземплярами и их данными. Поэтому я полагаю, что справедливо, что вы не можете, например, получить данные запроса из сигнала post_save, если эти данные не были сохранены или связаны с экземпляром.

Существует множество способов решения этой задачи, от худшего к лучшему, но я бы сказал, что это прекрасный пример для создания классовых/функциональных обобщённых представлений, которые будут автоматически обрабатывать это для вас.

Ваши представления, унаследованные от CreateView, UpdateView или DeleteView, должны дополнительно наследоваться от вашего класса AuditMixin, если они обрабатывают операции, связанные с моделями, которые требуют аудита. AuditMixin сможет подключаться к представлениям, которые успешно создают, обновляют или удаляют объекты, и создавать запись в базе данных.

Это имеет полный смысл, очень чисто, легко подключаемо и порождает положительные эмоции. Но есть и другая сторона? Вам либо нужно будет использовать скоро выходящую версию Django 1.3, либо потратить время на модификацию функциональных обобщённых представлений и создание новых для каждой операции аудита.

0

Ваша идея использовать middleware для хранения текущего пользователя в thread_local - интересный подход, но он имеет некоторые потенциальные недостатки, которые стоит учитывать.

Во-первых, использование thread_local может привести к проблемам с многопоточностью, особенно если вы работаете в окружении с высоким уровнем параллелизма, например, в асинхронных приложениях или в приложениях с использованием WSGI. В таких случаях данные, хранящиеся в потоковых переменных, могут стать недоступными или могут быть перезаписаны, что может привести к неожиданным ошибкам.

Во-вторых, использование такого подхода может усложнить отладку и тестирование вашего кода. Данные о текущем пользователе, хранящиеся в thread_local, могут затруднить понимание того, откуда именно берётся информация о пользователе, что делает код менее читабельным и предсказуемым.

С точки зрения архитектуры, лучше придерживаться подходов, которые подразумевают явное явление зависимости пользователя. Например, можно использовать Django Request (или аналогичный объект) для передачи информации о текущем пользователе через функции и методы.

Если вы всё же решите использовать такой подход, убедитесь, что у вас есть хорошие методы для управления жизненным циклом ваших потоковых данных, чтобы избежать утечек памяти и других проблем.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь