Search code examples
djangodjango-modelsdjango-signals

Decoupling Django signals from classes that touch the DB


I have one app that dispatches a signal, let's call it signal_x. In another app, a @receiver for signal_x is defined, and in it's apps.py's ready() function is the file containing the receiver imported.

The @receiver function has one issue though. It has a dependency to an object, let's call it object_y, that has in it's constructor a dependency to the DB. With the values extracted from the DB, some heavy objects are built. These heavy_objects must be in memory, for very fast computations and thus they can't be lazily loaded as it would take too long when they're used for the first time. self.location_ids can't be lazily loaded either, as it's used by the two compute_heavy_... functions.

Everything works completely fine until we drop the database, create it again and run manage.py makemigrations. Now makemigrations will fail because Django first runs django.setup(), which runs the ready() function of each app. When it reaches the ready() function with the import it will, as expected, import the file and try creating object_y. But object_y needs models which don't yet exist and thus the error. How could I elegantly solve/circumvent this?

class Recommender:
    def __init__(self):
        self.location_ids = self.get_location_ids()
        self.heavy_object1 = self.compute_heavy_object1(location_ids)
        self.heavy_object2 = self.compute_heavy_object2(..., location_ids)

    def get_location_ids():
        locations = LocationNode.objects.filter(level=1, parent_id=1)
        return [location.id for location in locations]

... meantime in another file

object_y = Recommender()

@receiver(signal_x, dispatch_uid="update_state")
def recompute(sender, **kwargs):
    client_id = kwargs['client_id']
    # prepare some things
    heavy_recompute(object_y, client_id)

Solution

  • This is a common use case. Since you didn't provide so much code, it's difficult to give you a very accurate answer, but, consider this:

    • In a AppConfig.ready() function, you should always use self.get_model() to retrieve a reference to a model defined in another app. This will ensure the app registry is up-to-date if require_ready=True (the default). Outside a AppConfig method, you can use apps.get_model()
    • Don't create a model instance at module level (object_y = HeavyObject()). Note: I don't know if HeavyObject is a Django model or not. But in general, you may declare object_y = None by default and initialize the variable later