Accessing the user's request in a post_save signal

余生颓废 提交于 2019-11-30 06:05:50

Can't be done. The current user is only available via the request, which is not available when using purely model functionality. Access the user in the view somehow.

I was able to do it by inspecting the stack and looking for the view then looking at the local variables for the view to get the request. It feels like a bit of a hack, but it worked.

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

Ignacio is right. Django's model signals are intended to notify other system components about events associated with instances and their respected data, so I guess it's valid that you cannot, say, access request data from a model post_save signal, unless that request data was stored on or associated with the instance.

I guess there are lots of ways to handle it, ranging from worse to better, but I'd say this is a prime example for creating class-based/function-based generic views that will automatically handle this for you.

Have your views that inherit from CreateView, UpdateView or DeleteView additionally inherit from your AuditMixin class if they handle verbs that operate on models that need to be audited. The AuditMixin can then hook into the views that successfully create\update\delete objects and create an entry in the database.

Makes perfect sense, very clean, easily pluggable and gives birth to happy ponies. Flipside? You'll either have to be on the soon-to-be-released Django 1.3 release or you'll have to spend some time fiddlebending the function-based generic views and providing new ones for each auditing operation.

ALEXANDER GOMEZ HIGUITA

For traceability add two attributes to your Model(created_by and updated_by), in "updated_by" save the last user who modified the record. Then in your signal you have the user:

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()

signals.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))

You could also use django-reversion for this purpose, e.g.

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())

Read more on their API https://django-reversion.readthedocs.io/en/stable/api.html#revision-api

Why not adding a middleware with something like this :

class RequestMiddleware(object):

    thread_local = threading.local()

    def process_request(self, request):
        RequestMiddleware.thread_local.current_user = request.user

and later in your code (specially in a signal in that topic) :

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

I imagine you would have figured this out, but I had the same problem and I realised that all the instances I create had a reference to the user that creates them (which is what you are looking for)

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',
)

You can do a small hack by overriding you model save() method and setting the user on the saved instance as additional parameter. To get the user I used get_current_authenticated_user() from django_currentuser.middleware.ThreadLocalUserMiddleware (see https://pypi.org/project/django-currentuser/).

In your 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)

In your signals.py:

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

PS: Don't forget to add 'django_currentuser.middleware.ThreadLocalUserMiddleware' to your MIDDLEWARE_CLASSES.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!