应用

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项目目录,其中包含一个具有settings.pyurls.pyasgi.pywsgi.pymysite Python 包。项目包通常会扩展为包含诸如与特定应用程序无关的固定装置、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/apps.py

from django.apps import AppConfig


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

RockNRollConfig将在INSTALLED_APPS包含'rock_n_roll'时自动加载。如果需要阻止此操作,请在类定义中将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的项目中使用“摇滚乐”,但希望它显示为“爵士曼努什”,您可以提供您自己的配置。

# 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[source]

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

可配置属性

AppConfig.name

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

此属性定义配置适用的应用。它必须在所有AppConfig子类中设置。

它在 Django 项目中必须唯一。

AppConfig.label

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

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

它在 Django 项目中必须唯一。

警告

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

AppConfig.verbose_name

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

此属性默认为 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[source]

要添加到此应用程序中模型的隐式主键类型。您可以使用它将 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)[source]

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

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

AppConfig.get_model(model_name, require_ready=True)[source]

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

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

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

AppConfig.ready()[source]

子类可以覆盖此方法以执行初始化任务,例如注册信号。一旦注册表完全填充,就会调用它。

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

如果您正在注册 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)[source]

通过以下方式配置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:当导入应用程序配置或models模块触发依赖于应用程序注册表的代码时,就会发生这种情况。

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

    在models模块中导入时执行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

返回顶部