пятница, 26 июля 2013 г.

Выполнение заданного кода при старте Django-приложения или management команды.

Иногда возникает необходимость выполнить некую инициализацию при запуске Django-проекта. Например зарегистрировать обработчики сигналов, создать подключения к внешним сервисам и др. Поскольку в самом Django нет специального сигнала сообщающего например о том, что настройки проекта были загружены и инициализированы, то приходится прибегать к другим методам запуска своего кода.

Как правило в таких случаях для этого вставляют необходимые импорты в urls.py. Но это сработает только в случае запуска сервера или прямого импорта этих файлов. При выполнении management команд загрузка urls.py не выполняется. Ещё ситуация может осложнятся необходимостью использовать в коде инициализации настроек из django.conf.settings. В этом случае мы не можем запустить инициализацию из файла settings.py, т.к. это приведёт к циклическому импорту.
Возможным решением может быть размещение кода инициализации в __init__.py проекта, но для этого необходимо, что бы путь к settings.py был добавлен в enviroment до импорта django.conf.settings. Для себя я сделал модификацию этого способа не требующую указания пути к файлу настроек внутри __init__.py проекта (у меня это выполняется в manage.py и в wsgi.py). Вот код который я добавил в __init__.py моего Django-проекта:

def run_statrup_scripts():
    from django.conf import settings
    from django.utils.importlib import import_module
    from django.utils.module_loading import module_has_submodule

    for app in settings.INSTALLED_APPS:
        mod = import_module(app)
        try:
            module = '%s.onstartup' % app
            import_module(module)
        except:
            if module_has_submodule(mod, 'onstartup'):
                raise


def patch_settings():
    try:
        from django.conf import LazySettings
    except ImportError:
        return

    if getattr(patch_settings, 'patched', False):
        return

    orig_setup = LazySettings._setup

    def _setup(self):
        orig_setup(self)
        run_statrup_scripts()

    LazySettings._setup = _setup
    patch_settings.patched = True


patch_settings()

Работает он следующим образом. При импорте __init__.py проекта выполняется патчинг метода LazySettings._setup() отвечающего за загрузку и инициализацию настроек проекта. При первом же обращении к настройкам они загрузятся и будет выполнена функция run_stratup_scripts(), которая ищет во всех приложениях, указанных в настройке INSTALLED_APPS, модуль с именем onstartup и импортирует, его если он там есть.
Теоретически у этого способа может быть одна проблема - он не сработает если вы умудритесь запустить какой либо код в вашем проекте, который сам или косвенно не обращается к настройкам из django.conf.settings.

Если у вас проблемы с использованием этого кода в __init__.py проекта или вы не хотите патчить код Django, то можно создать минимальное приложение (содержит только __init__.py и пустой  models.py), добавить ему в __init__.py определение функции run_statrup_scripts() и её вызов. Затем добавить это приложение в INSTALLED_APPS. Но тут тоже есть один минус - приложение будет импортированно джангой уже после того как первый запрос пройдёт обработку всех Middleware (по крайней мере у меня именно так и получилось).

1 комментарий:

  1. а не проще запихать все что нужно исполнить до запуска проекта в bat или sh? включить окружение, выполнить кастомные команды:python manage.py --do_it_before_start_of_project with_args_and_kwargs...

    ОтветитьУдалить