Catch post_save signal - django

Django 3.0.5.
apps.py
from django.apps import AppConfig
from django.db.models.signals import post_save
from django.dispatch import receiver
class NewsConfig(AppConfig):
name = 'news'
def ready(self):
from .models import News # Breakpoint 0
#receiver(post_save, sender=News)
def handle_news_save(sender, **kwargs):
print("Working")
a = 0 # Breakpoint 1
models.py
class News(models.Model):
news_text = models.TextField()
settings.py
INSTALLED_APPS = [
...
'news.apps.NewsConfig',
]
The problem
At Breakpoint 0 the interpreter stops when I run the application. That is at the next line Django gets to know that I'm catching the signal.
But when I save an instance of News in admin site, at Breakpoint 1 the interpreter doesn't stop. And, of course, no printing happens.
Could you help me catch the signal?

Ok i tried it out and played with signals a little, here's what i found out:
As the question state this way of defining does not work it seems to not register the signal correctly. I have no clue why it doesn't.
I guess the convention about signals would be anyways to move them to a signals.py file and then in the apps.py you only import them.
signals.py:
from .models import News # Breakpoint 0
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=News)
def handle_news_save(sender, **kwargs):
print("Working")
a = 0 # Breakpoint 1
apps.py:
from django.apps import AppConfig
from django.db.models.signals import post_save
from django.dispatch import receiver
class PollsConfig(AppConfig):
name = 'polls'
def ready(self):
import polls.signals
and then it works

Related

Django migrating from scratch fails: 'no such table', after adding signal receivers

I added some signal receivers to my code and everything was working fine, until I pushed it to version control and the CI/CD pipeline failed. On trying to migrate, it would complain with:
django.db.utils.OperationalError: no such table: badges_badge
But the migrations were working on my machine!
The CI/CD starts from scratch though, so I tried deleting my db.sqlite3 locally, then tried to re-migrate with python manage.py migrate:
django.db.utils.OperationalError: no such table: badges_badge
So migrating from an existing db worked, but not from a new one.
My signals.py:
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from badges.badge_stuff import badges
#receiver(post_save)
def update_badges(sender, **kwargs):
for badge in badges:
badge.update()
My apps.py:
from django.apps import AppConfig
class BadgesConfig(AppConfig):
name = 'badges'
def ready(self):
# Register signal listeners.
from . import signals
Why would it work with an existing db, but not when initialising it? How to fix this?
I had this issue and it was because of how I was registering my signals.
The problem is that the code in apps.ready() runs before any migrations, so if signals.py depends on models that don't exist in the db yet (e.g. when migrating from scratch), it'll fail. This might happen when your signals.py imports other modules which then depend on your models.
Here, badges.badge_stuff.badges imports the Badge model, which is created on first migration. So it cannot find it.
To fix this, we can use the post_migrate signal to register all of our signals after any migrations, so any necessary models will be created before any signal code runs.
Modify the above to:
from django.apps import AppConfig
from django.core.signals import pre_save, post_migrate
class BadgesConfig(AppConfig):
name = 'badges'
def register_signals(self, **kwargs):
# If your signals are decorated with #receiver, the line below is all you need
from . import signals
# Otherwise, explicitly connect the signal handler
pre_save.connect(signals.my_callback)
def ready(self):
post_migrate.connect(self.register_signals, sender=self)
And hopefully running your migrations should now work!
Remember to register your app in INSTALLED_APPS using this new AppConfig in settings.py:
INSTALLED_APPS = [
...
'badges.apps.BadgesConfig',
# Or just 'badges' for Django 4.0+
...
]

IPN is not received anymore

I have a problem with my IPN sandbox PayPal. A few days ago it sent the ipn after a test-purchase but since a few days ago it doesn’t sent them anymore. What can I do to fix that error?
signals.py
from django.shortcuts import get_object_or_404
from .models import CourierPayment
from paypal.standard.ipn.signals import valid_ipn_received
from django.dispatch import receiver
#receiver(valid_ipn_received)
def payment_notification(sender, **kwargs):
ipn = sender
if ipn.payment_status == 'Completed':
# payment was successful
payment = get_object_or_404(CourierPayment, id=ipn.invoice)
if payment.price == ipn.mc_gross:
# mark the order as paid
payment.paid = True
payment.save()
apps.py
from django.apps import AppConfig
class CourierConfig(AppConfig):
name = 'courier'
def ready(self):
# import signal handlers
import courier.signals
init.py
default_app_config = 'courier.apps.CourierConfig'

"ModuleNotFoundError", How to fix it?

from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import Profile
#receiver(post_save, sender=User)
def create_profile(receiver, instance, created, **kwargs):
if created:
Profile.objects.create(User=instance)
#receiver(post_save, sender=User)
def save_profile(receiver, instance, **kwargs):
instance.Profile.save()
apps.py
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'
def ready(self):
import users.signals
Error in server
File "C:\Users\Mian.G\Desktop\django_admin\users\apps.py", line 7, in
ready
import users.signals ModuleNotFoundError: No module named 'users.signals'
You've probably already solved this, as I see the post is almost 2 months old. But just in case you haven't.....is it possible you have created the 'signals.py' file in the 'users' sub directory instead of the 'users' root directory? I know from your code snippet that I am following the same tutorial and I was getting the same error because I hadn't created the 'signals.py' file in the 'users' app directory, but rather in the 'users' sub directory. If your 'signals.py' file is in the 'users' folder containing only 4 HTML files (login, logout, profile, register) then it is in the wrong location. Move it to the 'users' app directory and problem solved.

how to resolve circular import involving haystack?

Haystack
haystack_signal_processor let you use custom signal processor to initiate index for certain models.
I have in my settings.py
HAYSTACK_SIGNAL_PROCESSOR='my_app.signals.MySignalProcessor'
(this imports signals.py so. this is settings -> signals)
then inside my signals.py I have
from my_app.models import my_model # to connect my_model
And my_app.models.py has from django.conf import settings
(signals -> models -> settings)
How do I resolve this circular import?
taken from https://github.com/PitonFoundation/atlas/commit/cc0abcb
Instead of importing the model on top of your signals.py file, import the models in the methods of your custom SignalProcessor using get_model:
from django.db.models.loading import get_model
class MySignalProcessor(signals.BaseSignalProcessor):
def setup(self):
MyModel = get_model('myApp', 'MyModel')
models.signals.post_save.connect(self.handle_save, sender=MyModel)

The right place to keep my signals.py file in a Django project

Based on Django's documentation I was reading, it seems like signals.py in the app folder is a good place to start with, but the problem I'm facing is that when I create signals for pre_save and I try to import the class from model it conflicts with the import in my model.
# models.py
from django.contrib.auth.models import User
from django.db import models
from django.utils.translation import gettext as _
from signals import *
class Comm_Queue(CommunicatorAbstract):
queue_statuses = (
('P', _('Pending')),
('S', _('Sent')),
('E', _('Error')),
('R', _('Rejected')),
)
status = models.CharField(max_length=10, db_index=True, default='P')
is_html = models.BooleanField(default=False)
language = models.CharField(max_length=6, choices=settings.LANGUAGES)
sender_email = models.EmailField()
recipient_email = models.EmailField()
subject = models.CharField(max_length=100)
content = models.TextField()
# signals.py
from django.conf import settings
from django.db.models.signals import pre_save
from django.dispatch import receiver
from models import Comm_Queue
#receiver(pre_save, sender=Comm_Queue)
def get_sender_email_from_settings(sender, **kwargs):
obj=kwargs['instance']
if not obj.sender_email:
obj.sender_email='%s' % settings.ADMINS[0][1]
This code will not run because I import Comm_Queue inside signals.py and I also import the signals inside models.py.
Can anyone advice on how I could over come this issue?
Regards
If you're using Django<=1.6 I'd recommend Kamagatos solution: just import your signals at the end of your models module.
For future versions of Django (>=1.7), the recommended way is to import your signals module in your app's config ready() function:
my_app/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'my_app'
def ready(self):
import my_app.signals
my_app/__init__.py
default_app_config = 'my_app.apps.MyAppConfig'
Original answer, for Django < 1.7:
You can register the signals by importing signals.py in the app's __init__.py file:
# __init__.py
import signals
This will allow to import models.py from signals.py without circular import errors.
One problem with this approach is that it messes up the coverage results if you're using coverage.py.
Related discussion
Edit: For Django >= 1.7:
Since AppConfig was introduced, the recommended way of importing signals is in its init() function. See Eric Marcos' answer for more details.
To solve your problem you just have to import signals.py after your model definition. That's all.
I also put signals in signals.py file and also have this code snippet that loads all signals:
# import this in url.py file !
import logging
from importlib import import_module
from django.conf import settings
logger = logging.getLogger(__name__)
signal_modules = {}
for app in settings.INSTALLED_APPS:
signals_module = '%s.signals' % app
try:
logger.debug('loading "%s" ..' % signals_module)
signal_modules[app] = import_module(signals_module)
except ImportError as e:
logger.warning(
'failed to import "%s", reason: %s' % (signals_module, str(e)))
This is for project, I'm not sure if it works at app level.
In old Django versions would be fine to put the signals on the __init__.py or maybe in the models.py(although at the end models will be way to large for my taste).
With Django 1.9, it is better I think, to place the signals on a signals.py file and import them with the apps.py, where they are going to be loaded after loading the model.
apps.py:
from django.apps import AppConfig
class PollsConfig(AppConfig):
name = 'polls'
def ready(self):
from . import signals # NOQA
You can also divide your signals on signals.py and handlers.py in another folder within your model named signals as well, but for me that is just over engineering. Take a look at Placing Signals
This only applies if you have your signals in a separate signals.py file
In completely agree with the answer of #EricMarcos but it should be stated that the django docs explicitly advice not to use the default_app_config variable (although it is not wrong). For current versions, correct way would be:
my_app/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'my_app'
def ready(self):
import my_app.signals
settings.py
(Make sure you don't just have your app name in installed apps but instead the relative path to your AppConfig)
INSTALLED_APPS = [
'my_app.apps.MyAppConfig',
# ...
]
I'm guessing that you're doing that so your signals are registered, so that they're found somewhere. I just put my signals right in a models.py file normally.
An alternative is to import the callback functions from signals.py and connect them in models.py:
signals.py
def pre_save_callback_function(sender, instance, **kwargs):
# Do stuff here
model.py
# Your imports here
from django.db.models.signals import pre_save
from yourapp.signals import pre_save_callback_function
class YourModel:
# Model stuff here
pre_save.connect(pre_save_callback_function, sender=YourModel)
Ps: Importing YourModel in signals.py will create a recursion; use sender, instead.
Ps2: Saving the instance again in the callback function will create a recursion. You can make a control argument in .save method to control it.