0

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?

platelminto
  • 318
  • 3
  • 16
  • Found this as the (likely) underlying issue for a couple unanswered questions, here I directly explain the actual cause. Questions are: [72445980](https://stackoverflow.com/q/72445980), [66478569](https://stackoverflow.com/q/66478569), and could help people stumbling onto [34548768](https://stackoverflow.com/q/34548768). – platelminto Sep 20 '22 at 11:51

1 Answers1

0

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+
    ...
]
platelminto
  • 318
  • 3
  • 16