22

I need to perform some fairly simple tasks after my Django environment has been "fully loaded".

More specifically I need to do things like Signal.disconnect() some Django Signals that are setup by my third party library by default and connect my own Signals and I need to do some "monkey patching" to add convenience functions to some Django models from another library.

I've been doing this stuff in my Django app's __init__.py file, which seems to work fine for the monkey patching, but doesn't work for my Signal disconnecting. The problem appears to be one of timing--for whatever reason the Third Party Library always seems to call its Signal.connect() after I try to Signal.disconnect() it.

So two questions:

Do I have any guarantee based on the order of my INSTALLED_APPS the order of when my app's __init__.py module is loaded?

Is there a proper place to put logic that needs to run after Django apps have been fully loaded into memory?

Chris W.
  • 37,583
  • 36
  • 99
  • 136
  • 1
    possible duplicate of [Where to put Django startup code?](http://stackoverflow.com/questions/2781383/where-to-put-django-startup-code) – Ned Batchelder Mar 26 '11 at 01:21
  • it is a duplicate of this: http://stackoverflow.com/questions/5439769/where-to-disconnect-the-default-product-search-listener-in-satchmo or the other way around. – Mike Ramirez Mar 26 '11 at 10:11

4 Answers4

14

In Django 1.7 Apps can implement the ready() method: https://docs.djangoproject.com/en/dev/ref/applications/#django.apps.AppConfig.ready

guettli
  • 25,042
  • 81
  • 346
  • 663
  • 2
    This only works when you are in a position to modify `settings.INSTALLED_APPS`. A lot of usecases that result in this question do not have that luxury. – Markus Unterwaditzer Sep 06 '19 at 16:29
7

My question is a more poorly phrased duplicate of this question: Where To Put Django Startup Code. The answer comes from that question:

Write middleware that does this in init and afterwards raise django.core.exceptions.MiddlewareNotUsed from the init, django will remove it for all requests...

See the Django documentation on writing your own middleware.

Community
  • 1
  • 1
Chris W.
  • 37,583
  • 36
  • 99
  • 136
  • 4
    I found this does everything on first request which is horrible ... it should do it on startup. – Pykler Apr 19 '13 at 15:53
  • 1
    Agreed. Doing it at the first request defeats the purpose of preloading or preparing anything. – Tobia Apr 24 '14 at 15:45
5

I had to do the following monkey patching. I use django 1.5 from github branch. I don't know if that's the proper way to do it, but it works for me.

I couldn't use middleware, because i also wanted the manage.py scripts to be affected.

anyway, here's this rather simple patch:

import django
from django.db.models.loading import AppCache

django_apps_loaded = django.dispatch.Signal()

def populate_with_signal(cls):
    ret = cls._populate_orig()
    if cls.app_cache_ready():
        if not hasattr(cls, '__signal_sent'):
            cls.__signal_sent = True
            django_apps_loaded.send(sender=None)
    return ret

if not hasattr(AppCache, '_populate_orig'):
    AppCache._populate_orig = AppCache._populate
    AppCache._populate = populate_with_signal

and then you could use this signal like any other:

def django_apps_loaded_receiver(sender, *args, **kwargs):
    # put your code here.
django_apps_loaded.connect(django_apps_loaded_receiver)
leech
  • 8,293
  • 7
  • 62
  • 78
toudi
  • 804
  • 7
  • 11
  • Interesting. I like that this works for `management` commands, but there's so many "protected" attributes accessed here, I would be worried about it breaking between Django versions. – Chris W. Jan 22 '13 at 17:54
  • actually, the only django-specific protected attrubute is 'AppCache._populate'. The rest are my own invention ;) Up until now (django1.5) there doesn't seem to be a cleaner way to do this. and believe me i would like it to be clean. i found django ticket: https://code.djangoproject.com/ticket/3591 and i also found this file at github: https://github.com/ptone/django/blob/app-loading/django/apps/signals.py however since it's a fork i'm not risking using it unless it's officialy part of django. – toudi Jan 22 '13 at 22:22
  • Where would the best spot to drop this monkeypatch ... urls.py – Pykler Apr 19 '13 at 15:56
  • actually, i have a separate django app, called signals and it's the last one in INSTALLED_APPS list. This above patch lands in signals :: models.py file. When you use urls.py, manage.py scripts won't be affected (and this wasn't a desired solution for me) – toudi Jul 24 '13 at 09:56
  • 1
    Good stuff - thanks. I found though that "cls.app_cache_ready()" in "populate_with_signal" always returned False, so the signal was never sent. I've tried to get around this by removing the "cls.app_cache_ready()" and calling "django.db.models.get_models()" as soon as I've connected my signal receiver to django_apps_loaded, in the hope that get_models forces the AppCache to then get populated. This seems to work so far (I'm using django 1.5.1) – jcdude Jan 29 '14 at 14:56
3

As far as I know there's no such thing as "fully loaded". Plenty of Django functions include import something right in the function. Those imports will only happen if you actually invoke that function. The only way to do what you want would be to explicitly import the things you want to patch (which you should be able to do anywhere) and then patch them. Thereafter any other imports will re-use them.

Ben Jackson
  • 90,079
  • 9
  • 98
  • 150
  • Using this to run database queries on existing data didn't work with SQL in memory testing :( – leech May 07 '13 at 20:04