应用程序

Django 包含一个已安装应用程序的注册表,用于存储配置并提供内省。它还维护一个可用的 模型 列表。

此注册表称为 apps,可在 django.apps 中找到

>>> from django.apps import apps
>>> apps.get_app_config("admin").verbose_name
'Administration'

项目和应用程序

术语项目描述了一个 Django Web 应用程序。项目 Python 包主要由一个设置模块定义,但通常包含其他内容。例如,当你运行 django-admin startproject mysite 时,你将获得一个 mysite 项目目录,其中包含一个 mysite Python 包,其中包含 settings.pyurls.pyasgi.pywsgi.py。项目包通常会扩展为包括固定装置、CSS 和模板等内容,这些内容与特定应用程序无关。

项目的根目录(包含 manage.py 的目录)通常是项目中所有未单独安装的应用程序的容器。

术语应用程序描述了一个 Python 包,它提供了一些功能集。应用程序 可以在 各种项目中重复使用。

应用程序包括模型、视图、模板、模板标记、静态文件、URL、中间件等的某种组合。它们通常通过 INSTALLED_APPS 设置以及其他机制(例如 URLconf、MIDDLEWARE 设置或模板继承)连接到项目中。

了解 Django 应用程序是一组与框架的各个部分交互的代码非常重要。没有所谓的 Application 对象。但是,在一些地方,Django 需要与已安装的应用程序交互,主要用于配置,也用于内省。这就是应用程序注册表为每个已安装的应用程序在 AppConfig 实例中维护元数据的原因。

没有限制,项目包不能同时被视为应用程序并具有模型等(这需要将其添加到 INSTALLED_APPS 中)。

配置应用程序

要配置应用程序,请在应用程序中创建一个 apps.py 模块,然后在那里定义 AppConfig 的子类。

INSTALLED_APPS 包含应用程序模块的点分路径时,默认情况下,如果 Django 在 apps.py 子模块中找到一个 AppConfig 子类,它将使用该配置作为应用程序。可以通过将 AppConfig.default 设置为 False 来禁用此行为。

如果 apps.py 模块包含多个 AppConfig 子类,Django 将查找其中 AppConfig.defaultTrue 的一个。

如果未找到 AppConfig 子类,将使用基础 AppConfig 类。

或者,INSTALLED_APPS 可能包含指向配置类的点分路径,以明确指定它

INSTALLED_APPS = [
    ...,
    "polls.apps.PollsAppConfig",
    ...,
]

对于应用程序作者

如果您要创建一个名为“Rock ’n’ roll”的可插入应用程序,以下是如何为管理员提供一个恰当的名称

# rock_n_roll/apps.py

from django.apps import AppConfig


class RockNRollConfig(AppConfig):
    name = "rock_n_roll"
    verbose_name = "Rock ’n’ roll"

INSTALLED_APPS 包含 'rock_n_roll' 时,RockNRollConfig 将自动加载。如果您需要阻止此操作,请在类定义中将 default 设置为 False

您可以提供具有不同行为的多个 AppConfig 子类。要告诉 Django 默认使用哪一个,请在其定义中将 default 设置为 True。如果您的用户想要选择非默认配置,他们必须在 INSTALLED_APPS 设置中将 'rock_n_roll' 替换为指向该特定类的点分路径。

属性 AppConfig.name 告诉 Django 此配置适用于哪个应用程序。您可以在 AppConfig API 参考中定义任何其他属性。

AppConfig 子类可以在任何地方定义。apps.py 约定仅仅允许 Django 在 INSTALLED_APPS 包含应用程序模块路径(而不是配置类路径)时自动加载它们。

注意

如果您的代码在应用程序的 __init__.py 中导入应用程序注册表,则名称 apps 将与 apps 子模块冲突。最佳做法是将该代码移至子模块并导入它。解决方法是以不同的名称导入注册表

from django.apps import apps as django_apps

对于应用程序用户

如果您在名为 anthology 的项目中使用“Rock ’n’ roll”,但希望它显示为“Jazz Manouche”,您可以提供您自己的配置

# anthology/apps.py

from rock_n_roll.apps import RockNRollConfig


class JazzManoucheConfig(RockNRollConfig):
    verbose_name = "Jazz Manouche"


# anthology/settings.py

INSTALLED_APPS = [
    "anthology.apps.JazzManoucheConfig",
    # ...
]

此示例显示了位于名为 apps.py 的子模块中的特定于项目的配置类。这是一个约定,而不是要求。可以在任何地方定义 AppConfig 子类。

在这种情况下,INSTALLED_APPS 必须包含配置类的点分路径,因为它位于应用程序外部,因此无法自动检测到。

应用程序配置

class AppConfig

应用程序配置对象存储应用程序的元数据。一些属性可以在 AppConfig 子类中配置。其他属性由 Django 设置,并且只读。

可配置属性

AppConfig.name

应用程序的完整 Python 路径,例如 'django.contrib.admin'

此属性定义配置适用于哪个应用程序。它必须在所有 AppConfig 子类中设置。

它在 Django 项目中必须是唯一的。

AppConfig.label

应用程序的简称,例如 'admin'

当两个应用程序具有冲突的标签时,此属性允许重新标记应用程序。它默认为 name 的最后一个组件。它应为有效的 Python 标识符。

它在 Django 项目中必须是唯一的。

警告

在为应用程序应用迁移后更改此属性,将导致项目发生重大更改,或者在可重用应用程序的情况下,导致该应用程序的任何现有安装发生重大更改。这是因为 AppConfig.label 在数据库表和迁移文件中用于在依赖项列表中引用应用程序时。

AppConfig.verbose_name

应用程序的可读名称,例如“管理”。

此属性默认为 label.title()

AppConfig.path

应用程序目录的文件系统路径,例如 '/usr/lib/pythonX.Y/dist-packages/django/contrib/admin'

在大多数情况下,Django 可以自动检测并设置此项,但你也可以在 AppConfig 子类中提供一个明确的覆盖作为类属性。在少数情况下这是必需的;例如,如果应用包是具有多个路径的 命名空间包

AppConfig.default

将此属性设置为 False 以防止 Django 自动选择一个配置类。当 apps.py 仅定义一个 AppConfig 子类,但你不想 Django 默认使用它时,这很有用。

将此属性设置为 True 以告诉 Django 自动选择一个配置类。当 apps.py 定义多个 AppConfig 子类,并且你希望 Django 默认使用其中一个时,这很有用。

默认情况下,此属性未设置。

AppConfig.default_auto_field

要在该应用中添加的模型的隐式主键类型。你可以使用它来将 AutoField 作为第三方应用程序的主键类型。

默认情况下,这是 DEFAULT_AUTO_FIELD 的值。

只读属性

AppConfig.module

应用程序的根模块,例如 <module 'django.contrib.admin' from 'django/contrib/admin/__init__.py'>

AppConfig.models_module

包含模型的模块,例如 <module 'django.contrib.admin.models' from 'django/contrib/admin/models.py'>

如果应用程序不包含 models 模块,则它可能是 None。请注意,与数据库相关的信号(例如 pre_migratepost_migrate)仅对具有 models 模块的应用程序发出。

方法

AppConfig.get_models(include_auto_created=False, include_swapped=False)

返回此应用程序的 Model 类的可迭代对象。

要求应用程序注册表已完全填充。

AppConfig.get_model(model_name, require_ready=True)

返回具有给定 model_nameModelmodel_name 不区分大小写。

如果此应用程序中不存在此类模型,则引发 LookupError

除非 require_ready 参数设置为 False,否则需要完全填充应用程序注册表。 require_ready 的行为与 apps.get_model() 中完全相同。

AppConfig.ready()

子类可以覆盖此方法以执行初始化任务,例如注册信号。它在注册表完全填充后立即被调用。

虽然你无法在定义 AppConfig 类的模块级别导入模型,但你可以在 ready() 中使用 import 语句或 get_model() 导入它们。

如果你正在注册 model signals,则可以按其字符串标签引用发送者,而不是使用模型类本身。

示例

from django.apps import AppConfig
from django.db.models.signals import pre_save


class RockNRollConfig(AppConfig):
    # ...

    def ready(self):
        # importing model classes
        from .models import MyModel  # or...

        MyModel = self.get_model("MyModel")

        # registering signals with the model's string label
        pre_save.connect(receiver, sender="app_label.MyModel")

警告

尽管您可以按上述方式访问模型类,但请避免在 ready() 实现中与数据库进行交互。这包括执行查询的模型方法(save()delete()、管理器方法等),以及通过 django.db.connection 进行的原始 SQL 查询。您的 ready() 方法将在每个管理命令启动期间运行。例如,即使测试数据库配置与生产设置分开,manage.py test 仍会对您的生产数据库执行一些查询!

注意

在通常的初始化过程中,ready 方法仅由 Django 调用一次。但在某些特殊情况下,尤其是在修改已安装应用程序的测试中,ready 可能被调用多次。在这种情况下,请编写幂等方法,或在 AppConfig 类上设置一个标志,以防止重新运行应该只执行一次的代码。

命名空间包作为应用程序

没有 __init__.py 文件的 Python 包称为“命名空间包”,并且可能分布在 sys.path 上不同位置的多个目录中(请参阅 PEP 420)。

Django 应用程序需要一个基本文件系统路径,Django(取决于配置)将在其中搜索模板、静态资产等。因此,命名空间包只有在满足以下条件之一时才能成为 Django 应用程序

  1. 命名空间包实际上只有一个位置(即,不分布在多个目录中)。
  2. 用于配置应用程序的 AppConfig 类具有 path 类属性,这是 Django 将用作应用程序的唯一基本路径的绝对目录路径。

如果这两个条件都不满足,Django 将引发 ImproperlyConfigured

应用程序注册表

apps

应用程序注册表提供以下公共 API。未在下面列出的方法被视为私有方法,并且可能会在不经通知的情况下更改。

apps.ready

布尔属性,在注册表完全填充并且所有 AppConfig.ready() 方法都被调用后设置为 True

apps.get_app_configs()

返回 AppConfig 实例的可迭代对象。

apps.get_app_config(app_label)

返回具有指定 app_label 的应用程序的 AppConfig。如果不存在此类应用程序,则引发 LookupError

apps.is_installed(app_name)

检查注册表中是否存在具有指定名称的应用程序。 app_name 是应用程序的完整名称,例如 'django.contrib.admin'

apps.get_model(app_label, model_name, require_ready=True)

返回具有给定 app_labelmodel_nameModel。作为快捷方式,此方法还接受形式为 app_label.model_name 的单个参数。 model_name 不区分大小写。

如果不存在此类应用程序或模型,则引发 LookupError。当使用仅包含一个点的单个参数调用时,引发 ValueError

除非 require_ready 参数设置为 False,否则需要应用程序注册表完全填充。

require_ready 设置为 False 允许在填充应用程序注册表时查找模型,特别是在导入模型的第二阶段。然后 get_model() 具有与导入模型相同的效果。主要用例是使用设置(例如 AUTH_USER_MODEL)配置模型类。

require_readyFalse 时,get_model() 返回一个可能无法完全正常工作的模型类(例如,可能缺少反向访问器),直到应用程序注册表完全填充为止。因此,最好将 require_ready 保留为 True 的默认值,只要有可能。

初始化过程

应用程序如何加载

当 Django 启动时,django.setup() 负责填充应用程序注册表。

setup(set_prefix=True)[源代码]

通过以下方式配置 Django

  • 加载设置。
  • 设置日志记录。
  • 如果 set_prefix 为 True,则将 URL 解析器脚本前缀设置为 FORCE_SCRIPT_NAME(如果已定义),否则设置为 /
  • 初始化应用程序注册表。

此函数自动调用

  • 通过 Django 的 ASGI 或 WSGI 支持运行 HTTP 服务器时。
  • 调用管理命令时。

在其他情况下必须显式调用它,例如在纯 Python 脚本中。

在 Django 5.0 中更改

当应用程序在应用程序注册表完全填充之前与数据库交互时,会引发 RuntimeWarning

应用程序注册表分三个阶段初始化。在每个阶段,Django 按 INSTALLED_APPS 的顺序处理所有应用程序。

  1. 首先,Django 导入 INSTALLED_APPS 中的每个项目。

    如果是应用程序配置类,Django 会导入应用程序的根包,该包由 name 属性定义。如果是 Python 包,Django 会在 apps.py 子模块中查找应用程序配置,否则会创建一个默认应用程序配置。

    在此阶段,你的代码不应导入任何模型!

    换句话说,应用程序的根包和定义应用程序配置类的模块不应导入任何模型,即使是间接导入也不行。

    严格来说,Django 允许在加载应用程序配置后导入模型。但是,为了避免对 INSTALLED_APPS 的顺序施加不必要的约束,强烈建议在此阶段不要导入任何模型。

    一旦此阶段完成,操作应用程序配置的 API(例如 get_app_config())即可使用。

  2. 然后,Django 会尝试导入每个应用程序的 models 子模块(如果存在)。

    你必须在应用程序的 models.pymodels/__init__.py 中定义或导入所有模型。否则,应用程序注册表可能无法在此处完全填充,这可能会导致 ORM 出现故障。

    一旦此阶段完成,操作模型的 API(例如 get_model())即可使用。

  3. 最后,Django 会运行每个应用程序配置的 ready() 方法。

故障排除

以下是初始化期间你可能遇到的常见问题

  • AppRegistryNotReady:当导入应用程序配置或模型模块触发依赖于应用程序注册表的代码时,就会发生这种情况。

    例如,gettext() 使用应用程序注册表在应用程序中查找翻译目录。要在导入时进行翻译,您需要gettext_lazy()。 (使用gettext()将是一个错误,因为翻译将在导入时发生,而不是根据活动语言在每个请求中发生。)

    在模型模块的导入时间使用 ORM 执行数据库查询也会触发此异常。在所有模型可用之前,ORM 无法正常运行。

    如果您忘记在独立 Python 脚本中调用django.setup(),也会发生此异常。

  • ImportError: cannot import name ... 如果导入顺序最终进入循环,就会发生这种情况。

    为了消除此类问题,您应该最大程度地减少模型模块之间的依赖关系,并在导入时尽可能少地进行工作。为了避免在导入时执行代码,您可以将其移到函数中并缓存其结果。当您第一次需要其结果时,将执行该代码。这个概念被称为“惰性求值”。

  • django.contrib.admin 自动执行已安装应用程序中 admin 模块的自动发现。要防止这种情况,请将您的 INSTALLED_APPS 更改为包含 'django.contrib.admin.apps.SimpleAdminConfig' 而不是 'django.contrib.admin'

  • RuntimeWarning: Accessing the database during app initialization is discouraged. 此警告针对在应用程序准备就绪之前执行的数据库查询触发,例如在模块导入期间或在 AppConfig.ready() 方法中。不建议进行此类过早的数据库查询,因为它们将在每个管理命令启动期间运行,这会减慢项目启动速度,可能缓存陈旧数据,甚至在挂起迁移时可能失败。

    例如,一个常见的错误是进行数据库查询来填充表单字段选项

    class LocationForm(forms.Form):
        country = forms.ChoiceField(choices=[c.name for c in Country.objects.all()])
    

    在上面的示例中,查询 Country.objects.all() 在模块导入期间执行,因为 QuerySet 被迭代。为了避免警告,表单可以使用 ModelChoiceField

    class LocationForm(forms.Form):
        country = forms.ModelChoiceField(queryset=Country.objects.all())
    

    为了更容易地找到触发此警告的代码,您可以让 Python 将警告视为错误 以显示堆栈跟踪,例如使用 python -Werror manage.py shell

返回顶部