Why Django model signals are not working?
Asked Answered
S

7

66

I am trying to create activity streams of users from their status.

models:

class Status(models.Model):
    body = models.TextField(max_length=200)
    image = models.ImageField(blank=True, null=True, upload_to=get_upload_file_name)
    privacy = models.CharField(max_length=1,choices=PRIVACY, default='F')
    pub_date = models.DateTimeField(auto_now_add=True, auto_now=False)
    user = models.ForeignKey(User)

class Activity(models.Model):
    actor = models.ForeignKey(User)
    action = models.CharField(max_length=100)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')
    pub_date = models.DateTimeField(auto_now_add=True, auto_now=False)

However, although I create a new status, it does not create a new activity from the post_save signal.

signals:

from django.contrib.contenttypes.models import ContentType
from django.db.models.signals import post_save
from status.models import Status
from models import Activity

def create_activity_item(sender, instance, signal, *args, **kwargs):
    if kwargs.get('created', True):
        ctype = ContentType.objects.get_for_model(instance)

        if ctype.name == 'Status':
            action = ' shared '

            activity = Activity.objects.get_or_create(
                actor = instance.user,
                action = action,
                content_type = ctype,
                object_id = instance.id,
                pub_date = instance.pubdate
            )

post_save.connect(create_activity_item, sender=Status)

What am I doing wrong? Please help me solve this problem. I will be very much grateful. Thank you.

Update:

However doing like this creates the activity:

@receiver(post_save, sender=Status)
def create(sender, instance, **kwargs):
    if kwargs.get('created',True):
        ctype = ContentType.objects.get_for_model(instance)
        activity = Activity.objects.get_or_create(
            actor = instance.user,
            action = ' shared ',
            content_type = ctype,
            object_id = instance.id,
            pub_date = instance.pub_date
        )

Why doesn't the above works then?

Spicebush answered 25/1, 2015 at 9:35 Comment(0)
T
167

Seems like your post_save.connect is not executed. You should import signals somewhere. For django 1.7 it is recommended to do this in the app's config ready() function. Read the "Where should this code live?" side note in the docs.

For example if your app is called activity:

activity/__init__.py

default_app_config = 'activity.apps.ActivityAppConfig'

activity/apps.py

from django.apps import AppConfig

class ActivityAppConfig(AppConfig):
    name = 'activity'

    def ready(self):
        import activity.signals

And don't forget to add dispatch_uid to your connect() call:

post_save.connect(create_activity_item, sender=Status,
                  dispatch_uid="create_activity_item")

UPDATE: name attribute of ContentType is always in lower case. So you should change the if statement to:

if ctype.name == 'status':
Tessellated answered 25/1, 2015 at 9:55 Comment(9)
hello. I did as you did in the answer. But it is still not creating any activity. In the __init__.py i added default_app_config = 'activities.apps.ActivityAppConfig' as activities is the name of the app. And then added a new apps.py file in the app while changing the name field of ActivityAppConfig to 'activities'. And then add dispatch_uid.Spicebush
Yes! Now its working. One more thing. Do I always have to import signals by creating apps.py? Why doesn't it work without it?Spicebush
No, apps.py is recommended in official docs but not required. For example you can import signals in app/__init__.py. BTW I prefer define/connect signals right in the models.py :-)Tessellated
Ok, will remember that! Thank you so much!Spicebush
This answer should be on the django documentation.Christyna
the django docs have this, but they make it seem so optional, little do you know nothing works unless all the moons are alighnedGoren
I fixed my issue because I was not registering the config correctly inside the INSTALLED_APPS. I defined 'users.apps.UsersConfig' instead of just 'users' then worked for me.Castrate
Just to remark: your IDE (PyCharm), might say that import activity.signals is not used, but don't be fooled, cause Django really uses it for making the signals work for your model. This answer saved me a couple of hours of stress. Thank you.Appetence
docs.djangoproject.com/en/4.1/topics/signals/…Beestings
S
22

If you write everything in signals.py correctly but not working, then check these steps... (suppose in an app named AppName)

  1. in __init__.py, put the line

    default_app_config = 'AppName.apps.AppnameConfig'
    
  2. in apps.py file, put the block

    from django.apps import AppConfig
    
    
    class AppnameConfig(AppConfig):
        name = 'AppName'
    
        def ready(self):
            import AppName.signals
    
Syndic answered 2/1, 2022 at 15:1 Comment(2)
this worked and I didn't have to add the lines in init.pyCoelho
Thanks for the answer, it works (without the init.py). But I am wondering why we need to do that, and why it doesn't work out of the box with Django ... :/Lampoon
M
17

let's say your apps name is blog, in the settings.py file of your project ensure to register the blog app in the INSTALLED_APP variable of your main project's settings.py file as blog.apps.BlogConfig and not just blog. That worked for me.

Maraschino answered 3/8, 2020 at 20:21 Comment(2)
Anyone knows why this works?Acosta
you also need to add in apps.py >> def ready(self): import activity.signalsSunnisunnite
V
8

The simple, scalable, repeatable, reusable answer is...

If you plan to use signals (ex: signals.py) within an app (ex: posts), simply get in the habit of adding this method to your apps.py AppConfig class everytime.

def ready(self):
    from . import signals

You don't need to touch __init__.py like others have said. Your signals will then work.

Visser answered 6/12, 2022 at 22:23 Comment(1)
this is enough for Django 4.2Ossa
S
3

Without touching apps.py this worked for me.

class MyModel(models.Model):
    """ MyModel fields go """
    body = models.TextField(max_length=200)
    pub_date = models.DateTimeField(auto_now_add=True, auto_now=False)


def post_save_actions(sender, instance, created, **kwargs):
    if created:
        pass
        # post save actions if new instance is created,
        # do something with `instance` or another models
        # be careful about circular imports. \m/

and the signals hook,

post_save.connect(post_save_user_actions, sender=MyModel)
Schoolbag answered 25/2, 2017 at 12:45 Comment(1)
1 up for # be careful about circular imports. \m/Grantley
O
0

You should add

default_app_config = 'activity.apps.ActivityAppConfig'

in __init__.py file

Orle answered 28/8, 2023 at 17:31 Comment(0)
P
0

First try with these configurations

  1. in __init__.py file

    default_app_config = 'AppName.apps.AppNameConfig'
    
  2. in apps.py file

    from django.apps import AppConfig
    
    
    class AppNameConfig(AppConfig):
        name = 'AppName'
    
        def ready(self):
            from . import signals
            # import AppName.signals
    

But if your application still not working, then don't write app configurations in __init__.py file, include your app configrations in settings.py file

    INSTALLED_APPS = [
      'AppName.apps.AppNameConfig',
    ]
Polysyllable answered 1/11, 2023 at 18:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.