应用程序

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

这个注册表叫做 app,它在 django.app 中可用:

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

项目和应用程序

术语 项目 描述了一个 Django 网络应用。项目的 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)。

配置应用程序

配置一个应用程序,需要继承 AppConfig,将子类的点分隔路径加入 INSTALLED_APPS 中。

INSTALLED_APPS 包含当前路径到一个应用程序模块的路径时,Django 会检查该模块下的 default_app_config 变量。

如果已经定义,它是当前路径到到达该应用程序子类 AppConfig 的路径。

如果没有 default_app_config,Django 会使用基类 AppConfig

default_app_config 允许 Django 1.7 之前的应用程序(例如 django.contrib.admin)在不要求用户更新 INSTALLED_APPS 的情况下就可以加入 AppConfig 特性。

新的应用程序应该避免使用 default_app_config。相反,它们应该在 INSTALLED_APPS 中显式地配置对应 AppConfig 子类的完整点分隔路径。

对于应用程序作者

如果你正在创建一个名为 “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"

你可以通过如下方式使应用程序默认加载这个 AppConfig

# rock_n_roll/__init__.py

default_app_config = 'rock_n_roll.apps.RockNRollConfig'

INSTALLED_APPS 包含 'rock_n_roll' 时会使用 RockNRollConfig。这允许你充分利用 AppConfig 的功能特性,无需使你的用户去更新他们的 INSTALLED_APPS 配置。除此之外,最好避免使用 default_app_config,而是使用后续介绍的在 INSTALLED_APPS 中指定应用配置类。

你也可以告诉你的用户在他们的 INSTALLED_APPS 配置中放入 'rock_n_roll.apps.RockNRollConfig'。你甚至可以提供几个不同的 AppConfig 子类,让用户通过他们的 INSTALLED_APPS 配置选择其中一个行为。

惯例是将配置类放在应用程序名为 apps 的子模块中。但是,这不是 Django 强制规定的。

你必须包含 name 属性,Django 用它决定这个配置会应用于哪个应用。你定义任何在 AppConfig API 参考中记录的属性。

注解

若你在应用的 __init__.py 中导入了应用注册信息,名称 apps 会与子模块 apps 冲突。最好的办法是将此段带入移入子模块,再导入它。折中方案是导入后取个别名:

from django.apps import apps as django_apps

对于应用程序使用者

项目中直接使用 “Rock ’n’ roll”,其名字会是 anthology,但是你可能期望显示 “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',
    # ...
]

同样,在名为 app 的子模块中定义项目配置类是一种约定俗成的惯例,但不强求。

应用程序配置

class AppConfig[源代码]

应用程序配置对象存储了应用的元数据。某些属性可以在 AppConfig 的子类中配置。而其它 Django 设置好的配置是只读的。

可配置属性

AppConfig.name

指向此应用程序的完整的 Python 格式的路径,如 'django.contrib.admin'

此属性定义配置适用的应用程序。每个 AppConfig 子类都必须包含此项。

它必须在整个 Django 项目中唯一。

AppConfig.label

应用程序简称,如 'admin'

此属性允许在两个应用标签冲突时重命名其中一个的标签名。默认是 name 的最后一段。必须是一个有效的 Python 标识符。

它必须在整个 Django 项目中唯一。

AppConfig.verbose_name

应用程序容易被人理解的名称,如 “Administration”。

此属性默认值为 label.title()

AppConfig.path

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

大多数情况下,Django 能自动检测并设置此属性,但你也能在 AppConfig 子类中申明此属性,显式地重写它。很少情况下要这么做;例如,若应用包是一个拥有多个路径的 命名空间

只读属性

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()[源代码]

为该应用返回一个可迭代的 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() 实现中与数据库交互。这包括了那些会通过 django.db.connection 执行查询 (save()delete(),管理器方法,等等)和原生查询的模型方法。你的 ready() 方法会在每个管理命令初始化阶段被执行。例如,虽然测试数据库的配置与生成环境配置是分开的, manager.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)

返回 Model 于给定的 app_labelmodel_name。作为快捷方式,本方法也接受一个单一参数,形式为 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 的 WSGI 支持运行 HTTP 服务。
  • 调用一个管理命令时。

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

应用注册的初始化过程分三个阶段完成。在每个阶段,Django 根据应用在 INSTALLED_APPS 中的顺序依次处理。

  1. 首先,Django 导入 INSTALLED_APPS 中各项条目。

    如果是应用程序配置类,Django 导入应用程序的根包,由其 name 属性定义。如果是 Python 包,Django 会创建一个默认的应用配置。

    在这情况下,你的代码不应该导入任何模型!

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

    严格来说,一旦应用配置完成加载后,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() 会是一个 bug,因为翻译会在导入时进行,而不是在每次请求时根据活动语言进行。)

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

    如果你忘记在一个单独的 Python 脚本中调用函数 django.setup(),也会发生异常。

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

    为了消除这些问题,你应该最大限度地减少模型模块之间的依赖关系,并在导入时尽可能减少复杂度。 为了避免在导入时执行代码,可以将其移入函数并缓存结果。 代码将在你首次需要结果时执行。 这个概念被称为“惰性求值”。

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