I am using django 2.0.8 and Python 3.5. I want to be able to send and receive custom signals when an object is saved to the database.
I have followed the Django documentation on listening to signals and also the core signals bundled with Django - however, I am unable to get my example to work.
This is what I have so far:
myapp/models.py
from django.db import models
import django.dispatch
my_signal = django.dispatch.Signal(providing_args=["name"])
class Foo(models.Model):
name = models.CharField(max_length=16)
def save(self, *args, **kwargs):
try:
# Call the "real" save() method.
super().save(*args, **kwargs)
# Fire off signal
my_signal.send(sender=self.__class__, name=self.name)
except Exception as e:
print ('Exception:', e)
#pass
myapp/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Foo
#receiver(post_save, sender=Foo)
def foo_handler(sender, **kwargs):
print('Foo Signal Recieved!')
print(kwargs)
myapp/app.py
class MyappConfig(AppConfig):
name = 'myapp'
label = 'myapp'
def ready(self):
import myapp.signals
Sample usage
from myapp.models import Foo
foo = Foo(name='Homer Simpson')
foo.save() # Object is saved, but event is not fired!
Can anyone explain why the signal is not being fired?
It seems you need two features supplied by Django. signal and contenttypes.
So read the doc first
The model Activity is related to contenttypes,seems you miss object_id Field, which indicate which model instance is being crud.
For every crud action, An Activity instance is being created.This part is just the code written in signal.py
signal: signal have to connect each concrete model. Fortunately,See the source code of decorator receiver.
We have a signal list [post_save,post_delete] and a model list (FoodooChile, FooBarChile) to connect .
In post_save,argument created indicate the action is create or update.
At last, Usually we import the signal file in urls.py, maybe not the best practice.
It is also related to your settings.py. use 'myapp.apps.MyappConfig' replace myapp in settings.py,or define default_app_config = 'myapp.apps.MyappConfig' in myapp/__init__.py. The link above in comments describe this in detail
In the myapp.signals you have a receiver that handels the post_save signal (#receiver(post_save, sender=Foo)) it doesn't connect to your signal.
Make sure you are using your app config in the __init__.py of you application default_app_config = 'myapp.apps.MyappConfig'
To connect to the signal you created try this in your signals.py file:
#receiver(my_signal)
def my_handler(name, **kwargs):
print(name)
You are reinventing the wheel, but only putting it on one side of the cart, so to speak.
the post_save signal is always sent on save, so defining your own signal is overkill. I know you got the argument there, but the receiver has the sender argument already, which is the saved object, so you can just do sender.name and you got the value you need.
Apart from that, you have a syntax error, your custom save function for your model is not indented. I don't know if this is a formatting error in your question or if that is how it looks in your code. Either way, should work if you just drop your custom signal.
Model
from django.db import models
import django.dispatch
class Foo(models.Model):
name = models.CharField(max_length=16)
Signals
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Foo
#receiver(post_save, sender=Foo)
def foo_handler(sender, **kwargs):
print(sender.name)
App
class MyappConfig(AppConfig):
name = 'myapp'
label = 'myapp'
def ready(self):
import myapp.signals
Sample
from myapp.models import Foo
foo = Foo(name='Homer Simpson')
foo.save()
Related
In our Django project, we have a receiver function for post_save signals sent by the User model:
#receiver(post_save, sender=User)
def update_intercom_attributes(sender, instance, **kwargs):
# if the user is not yet in the app, do not attempt to setup/update their Intercom profile.
if instance.using_app:
intercom.update_intercom_attributes(instance)
This receiver calls an external API, and we'd like to disable it when generating test fixtures with factory_boy. As far as I can tell from https://factoryboy.readthedocs.io/en/latest/orms.html#disabling-signals, however, all one can do is mute all post_save signals, not a specific receiver.
At the moment, the way we are going about this is by defining an IntercomMixin which every test case inherits from (in the first position in the inheritance chain):
from unittest.mock import patch
class IntercomMixin:
#classmethod
def setUpClass(cls):
cls.patcher = patch('lucy_web.lib.intercom.update_intercom_attributes')
cls.intercomMock = cls.patcher.start()
super().setUpClass()
#classmethod
def tearDownClass(cls):
super().tearDownClass()
cls.patcher.stop()
However, it is cumbersome and repetitive to add this mixin to every test case, and ideally, we'd like to build this patching functionality into the test factories themselves.
Is there any way to do this in Factory Boy? (I had a look at the source code (https://github.com/FactoryBoy/factory_boy/blob/2d735767b7f3e1f9adfc3f14c28eeef7acbf6e5a/factory/django.py#L256) and it seems like the __enter__ method is setting signal.receivers = []; perhaps this could be modified so that it accepts a receiver function and pops it out of the the signal.receivers list)?
For anyone looking for just this thing and finding themselves on this question you can find the solution here: https://stackoverflow.com/a/26490827/1108593
Basically... call #factory.django.mute_signals(post_save) on the test method itself; or in my case the setUpTestData method.
Test:
# test_models.py
from django.test import TestCase
from django.db.models.signals import post_save
from .factories import ProfileFactory
import factory
class ProfileTest(TestCase):
#classmethod
#factory.django.mute_signals(post_save)
def setUpTestData(cls):
ProfileFactory(id=1) # This won't trigger user creation.
...
Profile Factory:
#factories.py
import factory
from factory.django import DjangoModelFactory
from profiles.models import Profile
from authentication.tests.factories import UserFactory
class ProfileFactory(DjangoModelFactory):
class Meta:
model = Profile
user = factory.SubFactory(UserFactory)
This allows your factories to keep working as expected and the tests to manipulate them as needed to test what they need.
In case you want to mute all signals of a type, you can configure that on your factory directly. For example:
from django.db.models.signals import post_save
#factory.django.mute_signals(post_save)
class UserFactory(DjangoModelFactory):
...
I want to build an notification system in my django project. So I started to create an new app called notification. To create the notification I have to listen to the actions of the other models of my project. To reach this purpose I created in my notification app a signal handler :
in notification/signals.py
def create_subscription(sender, **kwargs):
pass
I connect this handler to my signal in my notification/apps.py
from django.apps import AppConfig
from django.db.models.signals import post_save
from notification.signals import create_subscription
from django.conf import settings
class NotificationConfig(AppConfig):
name = 'notification'
def ready(self):
post_save.connect(create_subscription, sender=settings.AUTH_USER_MODEL, dispatch_uid="create_subscription")
This works fine. I used my custom User model defined in my settings.
But whenever I want to use another model of my project, like :
from django.apps import AppConfig
from django.db.models.signals import post_save
from notification.signals import create_subscription
from member.models import Participation
class NotificationConfig(AppConfig):
name = 'notification'
def ready(self):
post_save.connect(create_subscription, sender=Participation, dispatch_uid="create_subscription")
I get an AppRegistryNotReady error, no matter which model I use.
I checked the order of declaration of my settings.INSTALLED_APPS, 'member' is declared before 'notification'.
When referring to the User model by passing threw the settings.AUTH_USER_MODEL it's working fine, but when referring directly to the model it creates an error.
Any ideas?
Although you can’t import models at the module-level where AppConfig classes are defined, you can import them in ready(), using either an import statement or get_model().
You need to do like
class NotificationConfig(AppConfig):
name = 'notification'
def ready(self):
from member.models import Participation
post_save.connect(create_subscription, sender=Participation, dispatch_uid="create_subscription")
For more info
Let's say i need to call my own function def do_stuff() after i save model. If that model would be in app which i have created, that would be no problem. I could do:
def save(self, *args, **kwargs):
super(Post, self).save(*args, **kwargs)
do_stuff()
But i need to call save() in 3rd party app when model is saved. I can think of only copying all project to my local directory and append save() method, but that is not nice since i have to copy all app code. Is there any nicer way to do this?
EDITED:
apps.py:
from django.apps import AppConfig
class SubscriptionConfig(AppConfig):
def ready(self):
import subscription.signals
signals.py:
from django.db.models.signals import post_save
from django.dispatch import receiver
from djangocms_blog.models import Post
#receiver(post_save, sender=Post)
def send_emails(instance, **kwargs):
print 'instance %s' %instance
__init__.py:
default_app_config = 'subscription.apps.SubscriptionConfig'
There are signals that are dispatched after some certain events. One of them, post_save (or pre_save if you want to do something just before the object is saved) would work in your case.
To be more specific, create a signals.py in your app's folder:
from django.db.models.signals import post_save
from django.dispatch import receiver
from your_project.your_app.models import YourModel
#receiver(post_save, sender=YourModel)
def do_stuff(instance, **kwargs):
# instance here is your object, you can use or modify it
instance.title = "New title"
# don't forget to save your object if you edit
instance.save()
And then make sure this signals.py is imported in somewhere. It is recommended to do it in app's apps.py:
from django.apps import AppConfig
class YourAppConfig(AppConfig):
name = 'your_projects.your_app'
verbose_name = "Your app's verbose name"
def ready(self):
from your_project.your_app import signals
As last step, make sure your app uses the AppConfig you defined there. Open app's __init__.py and put this:
default_app_config = 'your_projects.your_app.apps.YourAppConfig'
Now, every time the signal you selected dispatches, your handler function will be run.
You can use post_save signal. It will be invoked after model is saved.
Following is my code in the signals.py file placed in the package where the auth model is defined.
#receiver(post_migrate, sender=settings.AUTH_USER_MODEL)
def define_groups(sender, **kwargs):
# Create groups
Group.objects.get_or_create(name='Promoter')
Group.objects.get_or_create(name='Client')
Group.objects.get_or_create(name='Superuser')
Group.objects.get_or_create(name='Staff')
The documentation (https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#referencing-the-user-model) states that it should be set as
sender=settings.AUTH_USER_MODEL
while this only works for post_save as mentioned in the documentation example.
I've already tried get_user_model() and also directly using the my_custom_user.models.
get_user_model() returns an error, while setting models as sender works just fine, as -
from . import models
#receiver(post_syncdb, sender=models)
def define_groups(sender, **kwargs):
# Create groups
Group.objects.get_or_create(name='Promoter')
Group.objects.get_or_create(name='Client')
Group.objects.get_or_create(name='Superuser')
Group.objects.get_or_create(name='Staff')
But according to documentation this is not the right way to refer a custom user model and is just an ugly workaround.
Would someone please be able to help me with a solution so i can add these Groups with the first migration of user model.
Thank You
EDIT : using get_user_model() returns the following error -
django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
The sender for the post_migrate method is never a model (custom or otherwise), it is the AppConfig instance for the app which was installed.
The docs give the following example for connecting your signal handler in the ready method.
from django.apps import AppConfig
from django.db.models.signals import post_migrate
def my_callback(sender, **kwargs):
# Your specific logic here
pass
class MyAppConfig(AppConfig):
...
def ready(self):
post_migrate.connect(my_callback, sender=self)
Similarly, the sender for post_sync_db signal (note the signal is deprecated) is the module containing the models which were installed.
So far I have signals.py with following content:
from django.db.models.signals import post_delete, post_save
from django.core.signals import request_finished
from django.dispatch import receiver
from students.models import Student
#receiver(post_save, sender=Student)
def track_saved_objects(sender, **kwargs):
print 'i am here'
new_instance = kwargs['instance']
print new_instance
#receiver(request_finished)
def my_callback(sender, **kwargs):
print("Request finished!")
And I can not get why signals do not register and nothing happening with this code, nothing printed that means my signals are inactive
In docs I've found something about AppConfig.ready() but still can not get where else should I register my signals
As e-nouri said in the comments, this information is in the docs. If you scroll down from the Connecting Receiver Functions section to the "Where should this code live?" note, you'll see they should live in the AppConfig as you alluded to in your question.
In your application, you'll need to create an apps.py file if you don't have one created already. In it you'll define the config for your app. Here's an example that includes the signals registration.
from django.apps import AppConfig
class ExampleAppConfig(AppConfig):
name = 'example'
verbose_name = "Example Application"
def ready(self):
# To avoid putting the signals code in the __init__.py file or
# models.py file, we import the signals module here.
# https://docs.djangoproject.com/en/1.7/topics/signals/#connecting-receiver-functions
from example import signals