【问题标题】:Accessing the user's request in a post_save signal在 post_save 信号中访问用户的请求
【发布时间】:2021-11-26 12:37:42
【问题描述】:

我在我的项目中完成了以下 post_save 信号。

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 并存储它。知道怎么做吗?

【问题讨论】:

  • django 不希望您访问模型和信号中的请求对象是有原因的。它将违反其关注点分离的理念。

标签: python django signals


【解决方案1】:

做不到。当前用户只能通过请求获得,这在使用纯模型功能时不可用。以某种方式访问​​视图中的用户。

【讨论】:

  • 嗯,有道理,我只是重新格式化了我的问题,以找到一种使用替代方法完成工作的方法
  • 加载视图时可以这样做。在您的 post_save 中保存您要在模型中标记的状态。在视图中加载模型时,检查状态标志并在响应之前对其进行操作。我做了一个 api 调用 post status,然后使用适当的状态代码设置一个名为 api_status 的模型字段。我检查了我的 get 中的代码并使用消息框架通知用户从调用中收到的状态。
  • 我正在使用post_save。保存数据的模型有一个名为user 的字段。我正在使用instance.user 来定位用户。但是,这里的条件是没有用户可以编辑其他用户的实例。因此,登录用户等于instance.user 值。
【解决方案2】:

我能够通过检查堆栈并查找视图然后查看视图的局部变量来获取请求来做到这一点。感觉有点像 hack,但确实有效。

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:
        # do stuff with the user variable

【讨论】:

  • 对我来说效果很好,我不知道视图会是什么,所以我删除了该检查,它仍然可以正常工作。
  • 这不是“正确的”,但效果很好,所以我认为这是正确的答案。
  • 非常老套的解决方案,投反对票,因为这是一种非常糟糕的做法
  • 这对我不起作用。在 /api/ 'request' 处获取 KeyError
  • 它有时可能会起作用,但这是一种极端的黑客行为,不应该被使用:downvote。
【解决方案3】:

伊格纳西奥是对的。 Django 的模型信号旨在通知其他系统组件有关与实例关联的事件及其尊重的数据,所以我猜你不能从模型post_save 信号访问请求数据是有效的,除非该请求数据存储在或与实例相关联。

我想有很多方法可以处理它,从更差到更好,但我想说这是创建基于类/基于函数的通用视图的主要示例,它将自动为您处理。

如果从 CreateViewUpdateViewDeleteView 继承的视图处理在需要审核的模型上运行的动词,则它们还从您的 AuditMixin 类继承。然后AuditMixin 可以挂钩到成功创建\更新\删除对象的视图并在数据库中创建一个条目。

非常有意义,非常干净,易于插拔,并且可以生出快乐的小马。反面?您要么必须使用即将发布的 Django 1.3 版本,要么必须花一些时间来修改基于函数的通用视图并为每个审计操作提供新视图。

【讨论】:

    【解决方案4】:

    为什么不添加这样的中间件:

    class RequestMiddleware(object):
    
        thread_local = threading.local()
    
        def process_request(self, request):
            RequestMiddleware.thread_local.current_user = request.user
    

    以及稍后在您的代码中(特别是在该主题的信号中):

    thread_local = RequestMiddleware.thread_local
    if hasattr(thread_local, 'current_user'):
        user = thread_local.current_user
    else:
        user = None
    

    【讨论】:

    • 听起来很干净的方法。我想知道这有多贵。
    • 我建议不要为此使用线程。这感觉很hacky,并且不是应用程序级代码,因为您对这些线程没有太多控制权。我会在这里使用缓存作为通信后端,使用唯一的 ID。
    • @electropoet 你知道它有多贵吗?我也在考虑实施它,到目前为止我还没有想到任何副作用。
    • 我用过,效果不好。可能是因为一个线程可以处理多个请求。
    • @EugeneKovalev 是的,我暗示这种情况。
    【解决方案5】:

    为了可追溯性,为您的模型添加两个属性(created_byupdated_by),在“updated_by”中保存最后修改记录的用户。然后在你的信号中你有用户:

    models.py:

    class Question(models.Model):
        question_text = models.CharField(max_length=200)
        pub_date = models.DateTimeField('date published')
        created_by = models. (max_length=100)
        updated_by = models. (max_length=100)
    

    views.py

        p = Question.objects.get(pk=1)
        p.question_text = 'some new text'
        p.updated_by = request.user
        p.save()
    

    信号.py

    @receiver(pre_save, sender=Question)
    def do_something(sender, instance, **kwargs):
        try:
            obj = Question.objects.get(pk=instance.pk)
        except sender.DoesNotExist:
            pass
        else:
            if not obj.user == instance.user: # Field has changed
                # do something
                print('change: user, old=%s new=%s' % (obj.user, instance.user))
    

    【讨论】:

    • 这非常容易出现竞争条件。对于很多用例来说这可能没问题,但应该提及。
    【解决方案6】:

    您也可以为此目的使用 django-reversion,例如

    from reversion.signals import post_revision_commit
    import reversion
    
    @receiver(post_save)
    def post_revision_commit(sender, **kwargs):
        if reversion.is_active():
            print(reversion.get_user())
    

    详细了解他们的 API https://django-reversion.readthedocs.io/en/stable/api.html#revision-api

    【讨论】:

      【解决方案7】:

      您可以通过覆盖您的模型save() 方法并将保存的实例上的用户设置为附加参数来做一个小技巧。为了从django_currentuser.middleware.ThreadLocalUserMiddleware 获取我使用get_current_authenticated_user() 的用户(参见https://pypi.org/project/django-currentuser/)。

      在你的models.py

      from django_currentuser.middleware import get_current_authenticated_user
      
      class YourModel(models.Model):
          ...
          ...
      
          def save(self, *args, **kwargs):
              # Hack to pass the user to post save signal.
              self.current_authenticated_user = get_current_authenticated_user()
              super(YourModel, self).save(*args, **kwargs)
      

      在你的signals.py

      @receiver(post_save, sender=YourModel)
      def your_model_saved(sender, instance, **kwargs):
          user = getattr(instance, 'current_authenticated_user', None)
      

      PS:不要忘记将'django_currentuser.middleware.ThreadLocalUserMiddleware' 添加到您的MIDDLEWARE_CLASSES

      【讨论】:

      • 这感觉太 hacky 了,并且在另一个消耗信号的应用程序中看起来像黑魔法(访问模型定义中不存在的字段)。
      • 这篇文章的开头是这样的:“你可以做一个小技巧”。 hack就是hack,它可以解决某些问题。小心它或找到更好、更安全的解决方案。
      【解决方案8】:

      您可以在中间件的帮助下做到这一点。在您的应用程序中创建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):
              # when response is ready, request should be flushed
              _requests.pop(current_thread().ident, None)
              return response
      
      
          def process_exception(self, request, exception):
              # if an exception has happened, request should be flushed too
               _requests.pop(current_thread().ident, None)
      

      然后将此中间件添加到您的设置中:

      MIDDLEWARE = [
          ....
          '<your_app>.get_request.RequestMiddleware',
      ]
      
      

      然后将导入添加到您的信号中:

      from django.db.models.signals import post_save
      from django.contrib.auth.models import User
      from <your_app>.get_request import current_request
      
      # CORE - SIGNALS
      # Core Signals will operate based on post
      
      def after_save_handler_attr_audit_obj(sender, **kwargs):
          print(Current User, 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()
      
      
      # 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")
      

      【讨论】:

      • 这种方法有什么缺点吗?我使用它,一切似乎都很好。
      【解决方案9】:

      我想你会想出来的,但是我遇到了同样的问题,我意识到 我创建的所有实例都引用了创建它们的用户(这就是你正在寻找的为)

      【讨论】:

      • 您能详细说明一下吗?为什么:“我创建的所有实例都引用了创建它们的用户”?
      • 我的意思是,我使用的所有信号都引用了一个实例(在 kwargs 中),该实例具有一个字段 'created_by' 或类似的东西,它指的是用户。
      【解决方案10】:
      context_processors.py
      
      from django.core.cache import cache
      
      def global_variables(request):
          cache.set('user', request.user)
      
      ----------------------------------
      in you model
      
      from django.db.models.signals import pre_delete
      from django.dispatch import receiver
      from django.core.cache import cache
      from news.models import News
      
      @receiver(pre_delete, sender=News)
      def news_delete(sender, instance, **kwargs):
          user = cache.get('user')
      
      in settings.py
      
      TEMPLATE_CONTEXT_PROCESSORS = (
          'web.context_processors.global_variables',
      )
      

      【讨论】:

      • 描述您在那里所做的事情会有帮助。
      • 这是一个非常糟糕的主意,千万不要这样做。对于大多数缓存配置,这将导致竞争条件。
      • 这是完全错误的。缓存不是特定于请求的
      • 请注意,如果您将唯一的 id 作为键,这不一定是一个坏主意。我的用例是将请求中的元数据添加到通过信号创建但需要来自请求的数据的实例中。我不喜欢将其与特定过程相结合的想法。我会将用户 PK 字段用作user_{pk} 并访问它的元数据。这不应该产生竞争条件,并给出预期的结果。
      猜你喜欢
      • 2014-07-10
      • 2019-06-12
      • 1970-01-01
      • 1970-01-01
      • 2017-04-06
      • 1970-01-01
      • 2011-07-31
      相关资源
      最近更新 更多