Django signal only works when debug=True, DJANGO 3.2.4

Viewed 423

I've been looking everywhere and I coulnd't find any reference about this, my Django model signal only works when the debug=True, but it doesn't work if debug=False, this occur both on localhost and production server.

My settings looks like this:

settings.py

from pathlib import Path
import os
import environ

env = environ.Env()

environ.Env.read_env()

BASE_DIR = Path(__file__).resolve().parent.parent

#production
STATIC_ROOT = 'https://d1u356tnw52tcs.cloudfront.net/'


SECRET_KEY = env("SECRET_KEY_PROD")

DEBUG = True

ALLOWED_HOSTS = ['*']

CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = (
)


# Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'django.contrib.postgres',
    'sellrapp',
    'stock_management',
    'corsheaders',
    'drf_yasg',
    'optimized_image',
    'csvexport',
    'kronos',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'corsheaders.middleware.CorsMiddleware'
]

ROOT_URLCONF = '****.urls'


WSGI_APPLICATION = '****.wsgi.application'


DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': env("NAME_PROD"),
        'USER': env("USER_PROD"),
        'PASSWORD': env("PASSWORD_PROD"),
        'HOST': env("HOST_PROD"),
        'PORT': env("PORT_PROD"),
    }
}

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'Asia/Singapore'

USE_I18N = True

USE_L10N = True

USE_TZ = True


WEB_BASE_URL = 'https://****.com/'
BASE_URL_LIVE_CAMPAIGN = WEB_BASE_URL + "product/"

if set to debug=False, the signal wouldn't triggered, and there's no any error trown.

signals.py

from django.dispatch import receiver
from django.db.models.signals import pre_save, post_save, pre_delete
from .models import ManualWithdrawals


@receiver(pre_delete, sender=ManualWithdrawals)
def manual_wd(sender, instance, **kwargs):
    order = ManualWithdrawals.objects.get(id=instance.id)
    consumer_order = order.order_id_list.all()
    sample_order = order.sample_order_id_list.all()
    total = str(instance.total_withdrawals).replace(".", "")
    total = int(total)
    if instance.order_id_list:
        for order in consumer_order:
            if instance.type == "reseller" and order.reseller_commission_status != "paid":
                order.reseller_commission_status = "ready for collection"
                order.save()
            if instance.type == "merchant" and order.merchant_commission_status != "paid":
                order.merchant_commission_status = "ready for collection"
                order.save()
        # updating sample order if any
    if instance.sample_order_id_list:
        for order in sample_order:
            if instance.type == "merchant" and order.merchant_commission_status != "paid":
                order.merchant_commission_status = "ready for collection"
                order.save()
3 Answers

This worked for me:

Pass weak=False when you call the signal’s connect() or @receiver.


Django Signals mention warning that:

Note also that Django stores signal handlers as weak references by default, so if your handler is a local function, it may be garbage collected. To prevent this, pass weak=False when you call the signal’s connect().

Thus, it is possible that your receiver function is getting garbage-collected.

For example, try using: @receiver(pre_delete, sender=ManualWithdrawals, weak=False)

Turn out that the problem happening when I'm updating to a new version of psycopg, somehow after updating my psycopg to the latest version now it's working just fine.

Just to add more insights to @kaustubh-trivei answer.

When handler is registered this way:

from django.db.models.signals import post_save

def create_and_register_handler(model):
    def handler(sender, instance=None, created=False, **kwargs):
        ...

    post_save.connect(handler, sender=model)

then weak=False is manadatory, otherwise it gets garbage collected and unregistered immediately.

Due to implementation detail, when settings.DEBUG is True, Django creates one extra reference to the handler, which prevents the handler from being garbage collected and unregistered.

Implementation detail: when settings.DEBUG is True, handler's arguments get validated by django.utils.inspect module, which uses functools.lru_cache() decorator internally, which leads to the handler being stored as a key in lru cache dict.

Related