【问题标题】:dynamically loading django apps at runtime在运行时动态加载 django 应用程序
【发布时间】:2014-07-24 12:58:23
【问题描述】:

是否可以在运行时动态加载 Django 应用程序?通常,应用程序在初始化时加载,使用 settings.py 中的 INSTALLED_APPS 元组。但是,是否可以在运行时加载其他应用程序?我在不同的情况下遇到这个问题。例如,在测试期间会出现一种情况,我想动态加载或卸载应用程序。

为了使问题更具体,假设我有一个名为 apps 的目录,我在其中放置了我的应用程序,并且我想自动安装其中的任何新应用程序,而无需手动编辑 settings.py。

这很容易。按照

中的示例代码

Django: Dynamically add apps as plugin, building urls and other settings automatically

我们将下面的代码放在settings.py 中,以循环遍历应用目录中所有子目录的名称,并像这样递增settings.py 中的INSTALLED_APPS 元组:

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 shell 中,我可以使用类似的东西

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,即 from myproject import settings

然后reloadsettings 在任何app 目录更改后使用python 内置。尽管现在将 settings.INSTALLED_APPS 更改为包含新添加的应用程序,但这最终没有任何区别。例如,

from django.db import models
models.get_apps()

仅显示 apps 中的原始应用程序,而不显示新添加的应用程序,同样

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

不会看到新添加的应用。

如上所述,我正在考虑这种情况,特别是在我动态添加或删除应用程序的测试环境中。

附言。我使用 Django 1.6,但根据 @RickyA 的建议,我发现 Django 在 1.7 中对应用程序的处理方式发生了一些重大变化

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

我仍然不确定这对我面临的问题可能意味着什么。

【问题讨论】:

  • 我们讨论的是哪个版本的 Django? 1.7 对应用程序进行了大修。
  • 我使用的是 1.6。我不知道 1.7 有任何变化。我会检查一下。
  • 虽然仍处于测试阶段:(
  • 这里有问题吗? - 否则我很想说The answer is Yes,并将其标记为关闭。
  • 有一个问题,我已经编辑了我的原始帖子,希望尽可能清楚。

标签: python django django-settings django-apps


【解决方案1】:

可以在 Django >= 1.7(以及当前的 3.0)中动态加载卸载应用程序在测试中 override_settings()装饰者:

@override_settings(INSTALLED_APPS=[...])  # added or removed some apps
class MyTest(TestCase):
    # some tests with these apps
    def test_foo(self):
        pass

自 2014 年 9 月以来就有可能,而不是在问题之前。

django.apps 也在 Django >= 1.7 中解决了该问题的许多其他主题。一般来说:在当前的 Django 中,启动时的动态配置很容易。在生产环境中不建议在启动后动态加载和卸载,即使它最终可以工作。

【讨论】:

    【解决方案2】:

    使用 Django 2.2 对我有用

    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, )
    apps.app_configs = OrderedDict()
    apps.apps_ready = apps.models_ready = apps.loading = apps.ready = False
    apps.clear_cache()
    apps.populate(settings.INSTALLED_APPS)
    
    management.call_command('makemigrations', new_app_name, interactive=False)
    
    management.call_command('migrate', new_app_name, interactive=False)
    

    【讨论】:

    • 谢谢你!但是你在哪里找到文档知道你必须把 apps_ready, models_ready, loading, ready 放到 False ?你能解释一下原因吗? Django 那部分的文档非常模糊......并且可以正确地做相反的事情吗? (卸载又名卸载)
    • 我真的很高兴知道相关文档
    【解决方案3】:

    是的! Python 中的任何东西(或几乎所有东西)都是可能的。您应该使用 os.walk() 来获取应用路径中的所有文件夹、子文件夹和文件,以便获取包括嵌套应用在内的所有应用。

    def get_installed_apps():
        from os import walk, chdir, getcwd
        previous_path = getcwd()
        master = []
        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:
                    app_path = f"{root.replace(BASE_DIR + '/', '').replace('/', '.')}.apps"
                    print(app_path)
                    master.append(app_path)
        chdir(previous_path)
        return master
    
    
    print(get_installed_apps())
    

    【讨论】:

      【解决方案4】:

      关于如何加载尚未加载的应用程序的 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's 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)
      

      【讨论】:

      • makemigrations 在动态调用时是一个错误的命令。您的迁移应在开发期间创建,并随动态应用程序一起提供。所以最好只打电话给migrate
      • 你是对的,但是 makemigrations 是我的项目的一个(特殊)要求,因为我正在将模型从一个实例移动到一个远程实例
      • 明白。也很有趣。所以您正在即时迁移模型?那行得通吗?
      • 这只是一个概念证明,但它确实有效;此应用程序的实例也交换模型和实际数据。
      【解决方案5】:

      回答我自己的问题...

      虽然我没有针对此问题的完全通用的解决方案,但我确实有一个足以在测试期间动态加载应用程序的解决方案。

      基本解决方案很简单,我在a wee little bixly blog找到了。

      继续上面的示例,如果我在 django shell 中并想添加和加载一些添加到我的 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)
      

      【讨论】:

      • 这不再适用于 Django 2.0。 django.db.models.loading 不再可用。
      猜你喜欢
      • 1970-01-01
      • 2010-12-07
      • 1970-01-01
      • 2011-10-15
      • 1970-01-01
      • 2020-01-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多