Accessing the user's request in a post_save signal
Solution 1
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.
Solution 2
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
Solution 3
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.
Solution 4
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
Solution 5
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))
Mo J. Mughrabi
Updated on February 17, 2022Comments
-
Mo J. Mughrabi over 2 years
I have done the below post_save signal in my project.
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")
The operation_by column, I want to get the user_id and store it. Any idea how can do that?
-
Mo J. Mughrabi over 13 yearshmm It make sense, I just reformatted my question to find out a way to do the job using alternative methods
-
Mo J. Mughrabi over 13 years
-
dmitri about 10 yearsCould you elaborate? How come:"all the instances I create had a reference to the user that creates them"?
-
kiril almost 10 yearsWhat I mean is that all the signals I use has reference to an instance (in the kwargs) that has a field 'created_by' or something similar, that refers to the user.
-
radtek over 9 yearsYou can do it when loading your view. In your post_save save the status you want to flag in the model. When loading the model in the view, check the status flag and do what you will with it before response. I do an api call post status, then set a model field called api_status with the appropriate status code. I check the code in my get and use the messages framework to notify the user of the status received from the call.
-
JoelC over 9 yearsA description of what you did there would be helpful.
-
MiniGunnR about 8 yearsI am using
post_save
. The model which is saving the data has a field calleduser
. I am usinginstance.user
to target the user. However, the condition over here is that no user can edit other user's instances. Thus, the logged in user is equal to theinstance.user
value. -
marxin almost 8 yearsThis is a very bad idea, never do it this way. With most cache configurations this is gonna lead to race condition.
-
varnothing over 7 yearsSounds clean approach. I want to find out how expensive is this.
-
sha256 over 7 yearsThis is totally WRONG. cache is not request specific
-
Julien over 7 yearsThis is terribly prone to race conditions. Which might be ok for a lot of use-cases, but it should be mentioned.
-
Daniel Dror almost 7 yearsJust a note, this is not necessarily a bad idea if you give a unique id as a key. My use-case is to add meta data from the request to an instance that is created via a signal, but needs data from the request. I dislike the idea of coupling this with a specific process. I would use the users PK field as
user_{pk}
and access it's metadata. this shouldn't create a race condition, and gives the intended result. -
Daniel Dror almost 7 yearsI would advise against using threading for this. This is feel very hacky, and is not application level code, since you don't have much control of those threads. I would use the cache as a communication backend here using a unique ID.
-
Agey over 6 years@electropoet did you find out how expensive is it? I'm thinking of implementing it as well, and so far i can not think of any side effects.
-
Scott Jungwirth over 6 yearsworked great for me, I didn't know what the view would be so I removed that check and it still worked fine.
-
digofreitas over 5 yearsIt's not "right" but it works great, so I think this is the right answer.
-
SaturnFromTitan over 5 yearsThat feels too hacky and will appear like black magic in the other app that's consuming the signal (accessing a field that doesn't exist in the model definition).
-
Yaroslav Varkhol over 5 yearsThat are the words from which the post starts: "You can do a small hack". Hack is hack, it can solve certain problem. Just be careful with it or find better and safer solution.
-
Mark Mishyn over 4 yearsI used it, works badly. Probably it's because one thread can process several requests.
-
Eugene Kovalev about 4 years@MarkMishyn do you imply that
current_user
can be overwritten by another request while the original request hasn't finished yet? -
Mark Mishyn about 4 years@EugeneKovalev Yes, I imply this scenario.
-
Ricardo about 4 yearsvery hacky solution, downvote because it's a very bad practice
-
codemastermind about 4 yearsThis does't work for me. Getting KeyError at /api/ 'request'
-
damd over 3 yearsIt might work sometimes, but it's an extreme hack and not meant to be used: downvote.
-
scorpionipx over 2 yearsAny downside to this method? I use it and everything seems fine.
-
Cl0ud-l3ss over 2 yearsTimeless answer
-
logicOnAbstractions over 2 yearsDJango is under no obligation to maintain the stack trace compatible with this approach. This code could break at any update really. Nice try, but downvoted as well.