Динамическая загрузка django-приложений во время выполнения

Можно ли динамически загружать приложения django во время выполнения? Обычно приложения загружаются при инициализации с использованием кортежа INSTALLED_APPS в settings.py. Однако возможно ли загрузить дополнительные приложения во время выполнения? Я сталкиваюсь с этой проблемой в разных ситуациях. Например, одна ситуация возникает во время тестирования, когда я хочу динамически загружать или выгружать приложения.

Чтобы сделать проблему более конкретной, представьте, что у меня есть каталог с названием apps куда я помещаю свои приложения, и я хотел бы автоматически установить любое новое приложение, которое появляется там, без ручного редактирования settings.py.

Это достаточно просто. Следуя примеру кода в

Django: динамически добавлять приложения в виде плагина, автоматически создавать URL-адреса и другие параметры

мы поместили следующий код в settings.py чтобы можно было перебирать имена всех подкаталогов в каталоге приложения и увеличивать кортеж INSTALLED_APPS в settings.py следующим образом:

APPS_DIR = '/path_to/apps/'

for item in os.listdir(APPS_DIR):
    if os.path.isdir(os.path.join(APPS_DIR, item)):
        app_name = 'apps.%s' % item
    if app_name not in INSTALLED_APPS:
        INSTALLED_APPS += (app_name, )

После этого, если бы я был в оболочке Django, я мог бы что-то вроде

from django.conf import settings

и приложения будут перечислены в settings.INSTALLED_APPS. И если бы я сделал

from django.core import management
management.call_command('syncdb', interactive=False)

это создаст необходимые таблицы БД для приложений.

Однако, если бы я сейчас добавил несколько приложений в каталог apps/ без перезапуска, они не были бы перечислены в settings.INSTALLED_APPS, и поэтому последующий вызов syncdb не имел бы никакого эффекта.

Я хотел бы знать, есть ли что-то, что я мог бы сделать - без перезапуска - чтобы перезагрузить настройки и загрузить/установить новые приложения.

Я пытался напрямую импортировать мои settings.py, т.е. из настроек импорта myproject

и затем reload эти settings с помощью встроенного Python после любого изменения каталога app. Несмотря на то, что settings.INSTALLED_APPS теперь изменяется, чтобы включить новые приложения, это в конечном итоге не имеет значения. Например,

from django.db import models
models.get_apps()

показывает только оригинальные приложения в apps а не недавно добавленные и аналогично

management.call_command('syncdb', interactive=False)

не будет видеть недавно добавленные приложения.

Как я уже говорил выше, я размышлял об этой ситуации, особенно в контексте тестирования, где я динамически добавлял или удалял приложения.

Ps. Я работаю с django 1.6, но по совету @RickyA я вижу, что в django есть некоторые существенные изменения в обработке приложений в 1.7

https://docs.djangoproject.com/en/1.7/ref/applications/

Я все еще не уверен, что это может означать для проблемы, с которой я сталкиваюсь.

Ответ 1

Чтобы ответить на мой собственный вопрос...

Хотя у меня нет полностью общего решения этой проблемы, у меня есть тот, который достаточен для динамической загрузки приложений во время тестирования.

Базовое решение простое, и я нашел его в маленьком блоке с бикс-блогами.

Продолжая мой пример выше, если я был в оболочке django и хотел добавить и загрузить некоторые новые приложения, которые были добавлены в мой каталог apps, я мог бы сделать

import os
from django.conf import settings
from django.db.models import loading
from django.core import management

APPS_DIR = '/path_to/apps/'

for item in os.listdir(APPS_DIR):
    if os.path.isdir(os.path.join(APPS_DIR, item)):
        app_name = 'apps.%s' % item
    if app_name not in settings.INSTALLED_APPS:
        settings.INSTALLED_APPS += (app_name, )

а затем

loading.cache.loaded = False
management.call_command('syncdb', interactive=False)

Ответ 2

Обновление для Django 1.8 о том, как загрузить приложение, которое еще не загружено

from collections import OrderedDict
from django.apps import apps
from django.conf import settings
from django.core import management

new_app_name = "my_new_app"

settings.INSTALLED_APPS += (new_app_name, )
# To load the new app let reset app_configs, the dictionary
# with the configuration of loaded apps
apps.app_configs = OrderedDict()
# set ready to false so that populate will work 
apps.ready = False
# re-initialize them all; is there a way to add just one without reloading them all?
apps.populate(settings.INSTALLED_APPS)

# now I can generate the migrations for the new app
management.call_command('makemigrations', new_app_name, interactive=False)
# and migrate it
management.call_command('migrate', new_app_name, interactive=False)

Ответ 3

Спасибо за информацию. Мне нужно сделать сам пользовательский интерфейс (браузер). Мне нужно получить valur от пользователя и создать приложение на основе этого. Возможно ли это в этом случае?

Ответ 4

Да! Все (или почти все) в Python возможно. Вы должны использовать os.walk(), чтобы получить все папки, подпапки и файлы в пути ваших приложений, чтобы получить все ваши приложения, включая вложенные.

def get_installed_apps():
    from os import walk, chdir, getcwd
    previous_path = getcwd()
    APPS_ROOT_PATH = '/my/project/apps/folder'
    chdir(APPS_ROOT_PATH)
    for root, directories, files in walk(top=getcwd(), topdown=False):
        for file in files:
            if 'apps.py' in file:
                print(f"{root.replace(BASE_DIR + '/', '').replace('/', '.')}.apps")
    chdir(previous_path)
    return