2

So, in Django 1.7.7 a new way to handle signals was introduced. I'm using 1.7.7 with django_cms, running on Python 2.

I'm trying to implement this new way, and even though the documentation is scarce but straightforward enough, it just won't work. I think Django-CMS or one of its plugins has something to do with it.

What I'm trying to do is increase the counter of my CounterModel by 1 for each MyModel that is saved on the pre_save signal of the MyModel model.

I've concluded it just doesn't work because a raise Exception('it runs') in the increase_counter function does not get raised..

I have the following:

myapp/models/mymodel.py

from .counter_model import CounterModel
# Imports here

class MyModel(models.Model):
    name = models.CharField(max_length=128)
    categories = models.ManyToManyField(CounterModel)

myapp/models/counter_model.py

# Imports here

class CounterModel(models.Model):
    amount_of_mymodels = models.PositiveIntegerField(default=0)

myapp/signals.py

from .models.mymodel import MyModel
# Other imports here

@receiver(pre_save, sender=MyModel)
def increase_counter(sender, **kwargs):
    instance = kwargs.get('instance')
    for category in instance.categories.all():
        category.amount_of_mymodels += 1

myapp/apps.py

from django.apps import AppConfig
# Other imports here

class MyAppConfig(AppConfig):
    name = "myapp"

    def ready(self):
        import myapp.signals

myapp/__init__.py

default_app_config = 'myapp.apps.MyAppConfig'

The import signals in my apps.py is executed, because when i raise an Exception there it's raised in my console (in the ready() function).

Hope someone can clarify this issue i'm having!

BTW: I've also added myapp to my INSTALLED_APPS

UPDATE: I've tried the new signal approach in another project (Python 3, also Django 1.7) and it works fine. If anyone has any clue as to what could be causing the failure of signals in my other project please let me know! I'm going to try to debug this for now, every form of help is appreciated.

NOTE: For everyone thinking 'the for loop might be empty, print something', please note the following in the beginning of my question: I've concluded it just doesn't work because a raise Exception('it runs') in the increase_counter function does not get raised... Thanks!

ZvL
  • 793
  • 2
  • 11
  • 25
  • Are you saving the category object anywhere? – Daniel Roseman Jun 12 '15 at 12:55
  • Does it work that way ?`pre_save.connect(increase_counter, sender=MyModel)` I just remember when I tried signals annotations didn't work for me – deathangel908 Jun 12 '15 at 12:56
  • Does it work what way? If you modify a member of instance.categories.all, you need to save it, which you don't seem to be doing. – Daniel Roseman Jun 12 '15 at 13:02
  • 2
    @karthikr please read the doc for `AppConfig.ready` (https://docs.djangoproject.com/en/1.8/ref/applications/#django.apps.AppConfig.ready) – bruno desthuilliers Jun 12 '15 at 14:37
  • @DanielRoseman Yeah i'm saving it. I also tried modifying the `MyModel` object and saving it just for a test but that doesn't work either (just like the exceptions).. – ZvL Jun 12 '15 at 15:02
  • @deathangel908 Yeah i tried that as well.. Doesn't seem to work, i also tried using `connect()` directly in the `apps.py`. – ZvL Jun 12 '15 at 15:03
  • 2
    Have you actually tested/printed out something to check that your the loop `for category in instance.categories.all():` is executed? Don't you need to first save the MyModel instance THEN add the M2M? Could be that the M2M doesnt exist yet so `instance.categories.all()` is empty – domino Jun 15 '15 at 09:42
  • @domino Thanks for your reply. I've considered that! That's why i've tried `raise Exception('it runs')` (see the beginning of my question). The Exception is not raised (and i'm not raising it in the for loop, of course...) – ZvL Jun 15 '15 at 09:46
  • Have you considered overriding `MyModel.save()` instead of using signals ? This would be easier to read, debug and would not depend on a controversial functionality of Django (signals). – Weier Jun 15 '15 at 12:33
  • @Weier Yeah, the problem is i need the m2m_changed as well :( – ZvL Jun 15 '15 at 12:49
  • have you considered putting it in the post_save? your model would get saved and changes to the m2m would be made as well. – sj7 Jun 16 '15 at 11:32
  • @sj7 I'd like to do that but it won't work since none of the signals get called.. – ZvL Jun 16 '15 at 13:23
  • just curious why are you calling the import signals in the ready method? – sj7 Jun 16 '15 at 17:17
  • It should actually be `import myapp.signals`, i'll fix that! :) If you're asking in general, it's because that's the 'new way' of using signals i'm talking about. See [the django docs](https://docs.djangoproject.com/en/1.8/ref/applications/#django.apps.AppConfig.ready) for more info – ZvL Jun 16 '15 at 20:49

2 Answers2

4

Fix 1

I tested your code by putting the signals.py code into models.py and it works.

Why's that?

From Django docs:

Where should this code live?

Strictly speaking, signal handling and registration code can live anywhere you like, although it's recommended to avoid the application's root module and its models module to minimize side-effects of importing code.

In practice, signal handlers are usually defined in a signals submodule of the application they relate to. Signal receivers are connected in the ready() method of your application configuration class. If you're using the receiver() decorator, simply import the signals submodule inside ready().

Fix 2

If you don't want to put signals.py code in your models.py, you can do the following

class MyAppConfig(...):
   ...

    def ready(self):
        # you've to import your app's signals.py 
        # not Django's signals module
        import myapp.signals

EDIT

There are some awesome answers and alternatives on this question: the right place to keep my signals.py files in django

Community
  • 1
  • 1
xyres
  • 20,487
  • 3
  • 56
  • 85
  • Have you tested it with Django-CMS 3 in Python 2? Because the signals work in a different Django project that doesn't use Django-CMS and runs Python 3. They also work when using them from the models.py, but neither work in my current project. – ZvL Jun 15 '15 at 12:51
  • @ZvL Not with Django-CMS. – xyres Jun 15 '15 at 13:37
  • Unfortunately, i got as far as you on this. Thanks for your answer though! My guess is it has to do something with Django-CMS or one of the plugins i'm using. I'll change the title of my question to be more clear on the fact that it's probably related to Django-CMS. – ZvL Jun 16 '15 at 13:24
1

In all my CMS projects I've resorted to a simple import signals as the last line in my models.py because attempting the import anywhere else seems to cause issues. I don't like this approach, but I've never had any issues with signals being received & as of yet not found a better solution. Most of my projects where this is the case are on late CMS 3 builds with django 1.6 and 1.7.

This question is a little hard to follow given it's progression, so if I've missed anything, please comment.

markwalker_
  • 12,078
  • 7
  • 62
  • 99
  • 1
    Very strange, i upgraded to Django-CMS 3.1 and the handlers are being called now! Though i also tried your solution and it it worked as well so you deserve the bounty! Thanks man! – ZvL Jun 21 '15 at 11:07