Get current user log in signal in Django
Asked Answered
M

5

23

I am just using the admin site in Django. I have 2 Django signals (pre_save and post_save). I would like to have the username of the current user. How would I do that? It does not seem I can send a request Or I did not understand it.

Thanks

Minstrel answered 18/1, 2011 at 8:9 Comment(0)
T
12

If you are using the admin site why not use a custom model admin

class MyModelAdmin( admin.ModelAdmin ):
    def save_model( self, request, obj, form, change ):
        #pre save stuff here
        obj.save()
        #post save stuff here



admin.site.register( MyModel, MyModelAdmin )

A signal is something that is fired every time the object is saved regardless of if it is being done by the admin or some process that isn't tied to a request and isn't really an appropriate place to be doing request based actions

Torrietorrin answered 16/11, 2011 at 4:20 Comment(3)
In Feb, 2015. Is this still the only way to get user's request in Django signal?Pontine
This is the correct answer, coupled with ModelAdmin.exclude for required prepopulated fields is very useful.Tva
This will work for some but not all cases, e.g. changes to m2m relationships can only be hooked via signals.Revocation
S
41

Being reluctant to mess around with thread-local state, I decided to try a different approach. As far as I can tell, the post_save and pre_save signal handlers are called synchronously in the thread that calls save(). If we are in the normal request handling loop, then we can just walk up the stack to find the request object as a local variable somewhere. e.g.

from django.db.models.signals import pre_save
from django.dispatch import receiver

@receiver(pre_save)
def my_callback(sender, **kwargs):
    import inspect
    for frame_record in inspect.stack():
        if frame_record[3]=='get_response':
            request = frame_record[0].f_locals['request']
            break
    else:
        request = None
    ...

If there's a current request, you can grab the user attribute from it.

Note: like it says in the inspect module docs,

This function relies on Python stack frame support in the interpreter, which isn’t guaranteed to exist in all implementations of Python.

Samora answered 15/1, 2012 at 23:41 Comment(6)
You sir, are a legend. Brilliant solution, works just as advertised.Vincentvincenta
pros and cons of this approach ?Depalma
Pros - Does work, simpler to implement and avoids thread locals (may or may not be a bad thing) Cons - Performance hit (has to scan the call stack potentially for each signal), won't work via tests - you will need to add additional logic to extract request objects from your testsRevocation
I have changed it to reversed(inspect.stack()) cos from overal 62 iteration in my Django project get_response is found on iteration 49, which tells us that scan in reverced direction should be more profitable.Integrand
The time to reverse de whole 62 items + scan the ~10 first is slower than scan the ~49 first. Do some tests with timeit. But I'm surprised how few difference there is.Braces
Good quick solution!Unwarranted
T
12

If you are using the admin site why not use a custom model admin

class MyModelAdmin( admin.ModelAdmin ):
    def save_model( self, request, obj, form, change ):
        #pre save stuff here
        obj.save()
        #post save stuff here



admin.site.register( MyModel, MyModelAdmin )

A signal is something that is fired every time the object is saved regardless of if it is being done by the admin or some process that isn't tied to a request and isn't really an appropriate place to be doing request based actions

Torrietorrin answered 16/11, 2011 at 4:20 Comment(3)
In Feb, 2015. Is this still the only way to get user's request in Django signal?Pontine
This is the correct answer, coupled with ModelAdmin.exclude for required prepopulated fields is very useful.Tva
This will work for some but not all cases, e.g. changes to m2m relationships can only be hooked via signals.Revocation
D
4

You can use a middleware to store the current user: http://djangosnippets.org/snippets/2179/

Then you would be able to get the user with get_current_user()

Disdain answered 13/2, 2011 at 13:57 Comment(2)
That solution relies on Thread Locals. Those are considered 'bad' for various reasons. #3227680Poppyhead
Just a comment on what @AaronC.deBruyn said - the link he posted actually disagrees with his statement, and popular opinion concludes the use of thread locals for this purpose is acceptable.Revocation
P
3

We can solve this problem using middleware classes. Create singleton class in where will be storing user variable.

class Singleton(type):
    '''
        Singleton pattern requires for GetUser class
    '''
    def __init__(cls, name, bases, dicts):
        cls.instance = None

    def __call__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = super(Singleton, cls).__call__(*args, **kwargs)
        return cls.instance 


class NotLoggedInUserException(Exception):
    '''
    '''
    def __init__(self, val='No users have been logged in'):
        self.val = val
        super(NotLoggedInUser, self).__init__()

    def __str__(self):
        return self.val

class LoggedInUser(object):
    __metaclass__ = Singleton

    user = None

    def set_user(self, request):
        if request.user.is_authenticated():
            self.user = request.user

    @property
    def current_user(self):
        '''
            Return current user or raise Exception
        '''
        if self.user is None:
            raise NotLoggedInUserException()
        return self.user

    @property
    def have_user(self):
    return not user is None

Create own middleware class that will be setting user for LoggedInUser instance,and insert out middleware after 'django.contrib.auth.middleware.AuthenticationMiddleware' in settings.py

from useranytimeaccess import LoggedInUser
class LoggedInUserMiddleware(object):
    '''
        Insert this middleware after django.contrib.auth.middleware.AuthenticationMiddleware
    '''
    def process_request(self, request):
        '''
            Returned None for continue request
        '''
        logged_in_user = LoggedInUser()
        logged_in_user.set_user(request)
        return None

In signals import LoggedInUser class and get current user

logged_in = LoggedInUser()
user = logged_in.user
Peaceable answered 19/9, 2011 at 10:20 Comment(0)
C
0

Will I do it like this I am still looking for a better solution if I am not using the admin site

@receiver(post_save, sender=FlowList)
def flow_list_post_save(sender, instance, created, **kwargs):
     import inspect
     request = None
     try:
        request = next((frame[0].f_locals['request'] for frame in inspect.stack() if frame[3] == 'get_response'), None)
     except StopIteration:
            pass
Consalve answered 5/8, 2023 at 8:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.