Consolidating multiple post_save signals with one receiver - django

So I read the Django source code (post 1.5) that you can now register multiple multiple signals to a receiver function:
def receiver(signal, **kwargs):
"""
A decorator for connecting receivers to signals. Used by passing in the
signal (or list of signals) and keyword arguments to connect::
#receiver(post_save, sender=MyModel)
def signal_receiver(sender, **kwargs):
...
#receiver([post_save, post_delete], sender=MyModel)
def signals_receiver(sender, **kwargs):
...
"""
... implementation code...
However, I want to register multiple post_save signals from different senders to the same function. Right now, I just call
post_save.connect(fn_name, model_name)
for each model that I have. Is there a better way to do this with the new Django 1.5 #receiver decorator capability?

You can do that using the #receiver decorator:
from django.dispatch import receiver
#receiver(post_save, sender=Model1)
#receiver(post_save, sender=Model2)
#receiver(post_save, sender=Model3)
def my_signal_handle(sender , **kwargs)
# some code here

Per the Django documentation on receivers, receivers by default do not need to be connected to a specific sender. So what you're describing is default Django functionality.
In other words, to do this using the #receiver decorator you simply don't specify a sender in the decorator. For example:
#receiver(post_save) # instead of #receiver(post_save, sender=Rebel)
def set_winner(sender, instance=None, created=False, **kwargs):
list_of_models = ('Rebel', 'Stormtrooper', 'Battleground')
if sender.__name__ in list_of_models: # this is the dynamic part you want
if created: # only run when object is first created
... set the winner ...
This assumes models that look like:
class Rebel(models.Model):
...
class Stormtrooper(models.Model):
...
class Battleground(models.Model):
...

You can skip model_name and you will connect to all models post_save. Then you can filter if you are in right model in the handler:
post_save.connect(foo)
def foo(sender, **kwargs):
if sender not in [FooModel, BarModel]:
return
... actual code ...
or you can filter based on field in model:
def foo(sender, **kwargs):
if not getattr(sender, 'process_by_foo', False):
return
... actual code ...

def receiver_with_multiple_senders(signal, senders, **kwargs):
"""
Based on django.dispatch.dispatcher.receiver
Allows multiple senders so we can avoid using a stack of
regular receiver decorators with one sender each.
"""
def decorator(receiver_func):
for sender in senders:
if isinstance(signal, (list, tuple)):
for s in signal:
s.connect(receiver_func, sender=sender, **kwargs)
else:
signal.connect(receiver_func, sender=sender, **kwargs)
return receiver_func
return decorator

Related

Creating a handler for user_activated signal

I want to receive a signal when user is activated (i.e. when auth_user.is_active becomes 1). I only want to receive this signal once, the very first time that the user is activated.
I have used the answer given to this question, and it works for me:
#receiver(pre_save, sender=User, dispatch_uid='get_active_user_once')
def new_user_activation_handler(sender, instance, **kwargs):
if instance.is_active and User.objects.filter(pk=instance.pk, is_active=False).exists():
logger.info('user is activated')
However this seems to be a customized signal, I believe django has a built-in user_activated signal. I have tried using the built-in signal but it does not fire:
signals.py:
from django_registration.signals import user_activated
#receiver(user_activated, sender=User, dispatch_uid='django_registration.signals.user_activated')
def new_user_activation_handler(sender, instance, **kwargs):
logger.info('user is activated')
Also this is what I have in apps.py:
class MyClassConfig(AppConfig):
name = 'myclass'
def ready(self):
logger.info('ready...')
import myclass.signals # wire up signals ?
Not sure why this signal is not being fired?
In order to get the above code running, I had to install django-registration package.
All the examples that I have seen have:
from registration.signals import user_activated
But in my case I have to use the a diferent namespace:
from django_registration.signals import user_activated
Not sure why...
You have wrong sender. Please see:
Replace:
#receiver(user_activated, sender=User, dispatch_uid='django_registration.signals.user_activated')
def new_user_activation_handler(sender, instance, **kwargs):
logger.info('user is activated')
With:
from django_registration.backends.activation.views import ActivationView
#receiver(user_activated, sender=ActivationView, dispatch_uid='django_registration.signals.user_activated')
def new_user_activation_handler(sender, instance, **kwargs):
logger.info('user is activated')

How to get user in Django signals [duplicate]

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?
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.
You can do that with the help of middleware. Create get_request.py in your app. Then
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)
Then add this middleware to your settings:
MIDDLEWARE = [
....
'<your_app>.get_request.RequestMiddleware',
]
Then add import to your signals:
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")
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
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
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.
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)
it's possible i guess.
in models.py
class _M(models.Model):
user = models.ForeignKey(...)
in views.py
def _f(request):
_M.objects.create(user=request.user)
in signals.py
#receiver(post_save, sender=_M)
def _p(sender, instance, created, **kwargs):
user = instance.user
No ?
Request object can be obtained from frame record by inspecting.
import inspect
request = [
frame_record[0].f_locals["request"]
for frame_record in inspect.stack()
if frame_record[3] == "get_response"
][0]
def get_requested_user():
import inspect
for frame_record in inspect.stack():
if frame_record[3] == 'get_response':
request = frame_record[0].f_locals['request']
return request.user
else:
return None
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',
)

Django REST API: run operations after put request

I am trying to find a clean way to process some task after successfully completes the PUT request for REST API. I am using post_update() function but its never being called. Here is my code
class portfolio_crud(generics.RetrieveUpdateDestroyAPIView):
lookup_field = 'id'
serializer_class = user_ticker_portfolio_serializer
def get_queryset(self):
return user_ticker_portfolio.objects.filter(user = self.request.user)
def put(self, request, *args, **kwargs):
print("got the put request to update portfolio")
return self.update(request, *args, **kwargs)
def post_update(self, serializer):
print("got the post save call") #never executed
Depends on what you want to do, but I usually use django's post_save hook as opposed to something on the viewset or serializer. Something like this:
from django.db.models.signals import post_save
#receiver(post_save, sender=YourPortolioClass)
def portfolio_post_save(sender, created, instance, raw, **kwargs):
""" We need to do something after updating a portfolio
"""
if created or raw:
return
# do your update stuff here.
Not sure if this will be much help to you (but I hope so) - I just use multithreading and create another thread before returning.
I don't see anything calling that function... but I am curious

Execute delete() within save() in Django

I'm working on a Django/Wagtail project. I'm trying to build a very customized feature that requires an object to be deleted when hitting the Save button when certain conditions are met.
I override the Save method:
def save(self, *args, **kwargs):
if condition:
return super(ArticleTag, self).delete()
else:
return super(ArticleTag, self).save(*args, **kwargs)
I know this looks very odd and completely anti-adviseable, but it is exactly the behavior I'm trying to achieve.
Is there a better or "correct" way to do this?
Are there other steps to exactly reproduce the behavior as if the user had hit Delete directly?
If the object already exists in your db, you can do as follows:
def save(self, *args, **kwargs):
if condition:
self.delete() # you do not need neither to return the deleted object nor to call the super method.
else:
return super(ArticleTag, self).save(*args, **kwargs)
Using signals receivers
signals.py
from django.dispatch import receiver
from django.db.models.signals import post_save
__all__ = ['check_delete_condition']
#receiver(post_save, sender="yourapp.yourmodel")
def check_delete_condition(instance, raw, created, using, updatefields, **kwargs):
if condition:
instance.delete()
in your apps.py you can't put the signals import
from .signals import *
#rest of code

Get current user log in signal in Django

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
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.
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
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()
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
In both signals, signal send three arguments,
Sender
Instance
Using
What you need is the Instant being modified...
def signal_catcher(sender, instance, **kwargs):
uname = instance.username
http://docs.djangoproject.com/en/dev/ref/signals/#pre-save