Django 源码阅读(三): app的加载过程

Python 2017-11-21

起步

上一篇介绍到了 Settings 的懒加载机制,配置懒加载也就完成了,程序就回到 execute 函数,接下去就是运行 django.setup() 函数了。

django.setup() 启动程序

这个代码在 django/__init__.py 中:

def setup(set_prefix=True):
    from django.apps import apps
    from django.conf import settings
    from django.utils.log import configure_logging

    configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
    apps.populate(settings.INSTALLED_APPS)

configure_logging 配置日志信息,跳过。加载 settings.INSTALLED_APPS 中的自定义模块以及 models 模块,并保存在 django.apps 中,这是一个全局的 Apps 类实例。这是一个已经安装应用的注册表,这个注册表存储着配置信息以及用来自省,同时也维护这模型的列表。这个类在 django.apps.registry 中定义:

apps = Apps(installed_apps=None)

其中,populate(self, installed_apps=None) 是它的主要方法,这个方法导入每个应用模块,再导入每个模型。这个函数是线程安全的。

def populate(self, installed_apps=None):
    if self.ready:  #该注册表是否已经被填充
        return

    with self._lock:
        if self.ready:
            return

        # app_config应该是处于原始的状态,否则下面的代码将不能保证这个顺序匹配INSTALLED_APPS中的顺序。
        if self.app_configs:
            raise RuntimeError("populate() isn't reentrant")

        # Load app configs and app modules.
        for entry in installed_apps:# 迭代已安装apps的每一项
            if isinstance(entry, AppConfig):
                app_config = entry
            else:
                # 实例化每个app的配置管理对象
                app_config = AppConfig.create(entry)
            if app_config.label in self.app_configs:
                raise ImproperlyConfigured(
                    "Application labels aren't unique, "
                    "duplicates: %s" % app_config.label)

            self.app_configs[app_config.label] = app_config

        # Check for duplicate app names.
        counts = Counter(
            app_config.name for app_config in self.app_configs.values())
        duplicates = [
            name for name, count in counts.most_common() if count > 1]
        if duplicates:# 检测app_config的唯一性,不允许模块名重复
            raise ImproperlyConfigured(
                "Application names aren't unique, "
                "duplicates: %s" % ", ".join(duplicates))

        self.apps_ready = True # 应用 apps 加载完毕

        # 导入模型模块
        for app_config in self.app_configs.values():
            all_models = self.all_models[app_config.label]
            app_config.import_models(all_models)

        self.clear_cache()

        self.models_ready = True
        # 运行apps configs的ready()方法
        for app_config in self.get_app_configs():
            app_config.ready()

        self.ready = True

for 循环中,使用 AppConfig.create(entry) 加载 settings.INSTALLED_APPS 里面的各模块,并保存在 self.app_configs 中。实例化每个app的配置管理对象有什么好处呢?注意,create方法是 classmethod 的,这是一个工厂模式,它根据参数来构造出 AppConfig(app_name, app_module) 这样的实例。其中 app_name 表示 INSTALLED_APPS 中指定的应用字符串,app_module 表示根据 app_name 加载到的module。

AppConfig 实例的初始化方法中,会记录这些应用的标签文件路径等信息,最终将这些实例会保存在其属性 app_configs 中。接着每个 AppConfig 实例会加载其指定模块的 modelsall_models 定义为 all_models = defaultdict(OrderedDict)

模型的加载

模型的加载在 populate 函数里体现为:

# Load models.
for app_config in self.app_configs.values():
    all_models = self.all_models[app_config.label]
    app_config.import_models(all_models)

具体的加载函数在 AppConfigimport_models 中:

MODELS_MODULE_NAME = 'models'
def import_models(self, all_models):
    self.models = all_models # 因此它会再次更新self.all_models,然后all_models会跟着发生变化,然后这里的self.models也会跟着发生变化。

    if module_has_submodule(self.module, MODELS_MODULE_NAME):
        models_module_name = '%s.%s' % (self.name, MODELS_MODULE_NAME)
        self.models_module = import_module(models_module_name)

在module指定的目录或者 package 中,查找是否有定义 models 模块,并将其 import 进来, 最终呈现出来的是:

OrderedDict([
    ('permission', <class 'django.contrib.auth.models.Permission'>), 
    ('group_permissions', <class 'django.contrib.auth.models.Group_permissions'>), 
    ('group', <class 'django.contrib.auth.models.Group'>), 
    ('user_groups', <class 'django.contrib.auth.models.User_groups'>), 
    ('user_user_permissions', <class 'django.contrib.auth.models.User_user_permissions'>), 
    ('user', <class 'django.contrib.auth.models.User'>)
])

通过呈现结果的对象来看,self.models 已经装载了这些 已经加载好 的模型对象。


本文由 hongweipeng 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

如果对您有用,您的支持将鼓励我继续创作!