[Django 0-1] Apps模块

Apps 源码分析

Apps 下主要有两个类: AppConfigApps.

目录结构

apps/                      # 应用目录
├── __init__.py            # 应用初始化文件
├── config.py              # AppConfig 类
├── registry.py            # Apps 类

AppConfig

位于 apps/config.py 文件中, 主要用来定义应用的配置信息和初始化过后的过程函数。

属性

  • name: 应用的名称, 一般是应用的包名.
  • module: 应用的模块, 一般是应用的 __init__.py 文件.
  • apps: 应用管理器, 用来管理应用的注册和配置信息.
  • label: 应用的标签, 包名的最后一部分, 如django.contrib.admin => admin.
  • verbose_name: 应用的显示名称, 一般是应用的名称的首字母大写形式.
  • path: 应用的路径, 一般是应用的包路径.
  • models_module: 应用的模型模块, 通过import_models加载,在 app 初始化完成前,该属性为None.
  • models: 应用的模型 mapping[str, models.Model], 通过import_models加载,在 app 初始化完成前,该属性为None.

重要函数

  • create(): 类方法,返回一个AppConfig实例,通过外部的 entry 字符串,加载目录下的 apps 中的子类并初始化返回,可以通过设置default属性来控制实例化哪个AppConfig子类。

class AppConfig:

    ...

    @classmethod
    def create(cls, entry):
        """
        Factory that creates an app config from an entry in INSTALLED_APPS.
        """
        # create() eventually returns app_config_class(app_name, app_module).
        app_config_class = None
        app_name = None
        app_module = None

        # If import_module succeeds, entry points to the app module.
        try:
            app_module = import_module(entry)
        except Exception:
            pass
        else:
            # If app_module has an apps submodule that defines a single
            # AppConfig subclass, use it automatically.
            # To prevent this, an AppConfig subclass can declare a class
            # variable default = False.
            # If the apps module defines more than one AppConfig subclass,
            # the default one can declare default = True.
            if module_has_submodule(app_module, APPS_MODULE_NAME):
                mod_path = "%s.%s" % (entry, APPS_MODULE_NAME)
                mod = import_module(mod_path)
                # Check if there's exactly one AppConfig candidate,
                # excluding those that explicitly define default = False.
                app_configs = [
                    (name, candidate)
                    for name, candidate in inspect.getmembers(mod, inspect.isclass)
                    if (
                        issubclass(candidate, cls)
                        and candidate is not cls
                        and getattr(candidate, "default", True)
                    )
                ]
                if len(app_configs) == 1:
                    app_config_class = app_configs[0][1]
                else:
                    # Check if there's exactly one AppConfig subclass,
                    # among those that explicitly define default = True.
                    app_configs = [
                        (name, candidate)
                        for name, candidate in app_configs
                        if getattr(candidate, "default", False)
                    ]
                    if len(app_configs) > 1:
                        candidates = [repr(name) for name, _ in app_configs]
                        raise RuntimeError(
                            "%r declares more than one default AppConfig: "
                            "%s." % (mod_path, ", ".join(candidates))
                        )
                    elif len(app_configs) == 1:
                        app_config_class = app_configs[0][1]

            # Use the default app config class if we didn't find anything.
            if app_config_class is None:
                app_config_class = cls
                app_name = entry

        # If import_string succeeds, entry is an app config class.
        if app_config_class is None:
            try:
                app_config_class = import_string(entry)
            except Exception:
                pass
        # If both import_module and import_string failed, it means that entry
        # doesn't have a valid value.
        if app_module is None and app_config_class is None:
            # If the last component of entry starts with an uppercase letter,
            # then it was likely intended to be an app config class; if not,
            # an app module. Provide a nice error message in both cases.
            mod_path, _, cls_name = entry.rpartition(".")
            if mod_path and cls_name[0].isupper():
                # We could simply re-trigger the string import exception, but
                # we're going the extra mile and providing a better error
                # message for typos in INSTALLED_APPS.
                # This may raise ImportError, which is the best exception
                # possible if the module at mod_path cannot be imported.
                mod = import_module(mod_path)
                candidates = [
                    repr(name)
                    for name, candidate in inspect.getmembers(mod, inspect.isclass)
                    if issubclass(candidate, cls) and candidate is not cls
                ]
                msg = "Module '%s' does not contain a '%s' class." % (
                    mod_path,
                    cls_name,
                )
                if candidates:
                    msg += " Choices are: %s." % ", ".join(candidates)
                raise ImportError(msg)
            else:
                # Re-trigger the module import exception.
                import_module(entry)

        # Check for obvious errors. (This check prevents duck typing, but
        # it could be removed if it became a problem in practice.)
        if not issubclass(app_config_class, AppConfig):
            raise ImproperlyConfigured("'%s' isn't a subclass of AppConfig." % entry)

        # Obtain app name here rather than in AppClass.__init__ to keep
        # all error checking for entries in INSTALLED_APPS in one place.
        if app_name is None:
            try:
                app_name = app_config_class.name
            except AttributeError:
                raise ImproperlyConfigured("'%s' must supply a name attribute." % entry)

        # Ensure app_name points to a valid module.
        try:
            app_module = import_module(app_name)
        except ImportError:
            raise ImproperlyConfigured(
                "Cannot import '%s'. Check that '%s.%s.name' is correct."
                % (
                    app_name,
                    app_config_class.__module__,
                    app_config_class.__qualname__,
                )
            )

        # Entry is a path to an app config class.
        return app_config_class(app_name, app_module)

  • ready(): 实例方法,在应用初始化完成后调用,一般用来注册信号和其他初始化操作。

Apps

属性

  • all_models: 存放app_label.model_nameModel的 mapping, 在 Model.new中调用apps.register_model完成注册。
  • app_configs: 存放AppConfig实例的 mapping。
  • stored_app_configs: 栈存放当前状态
  • apps_ready: 标志位,表示是否已经完成应用初始化。
  • models_ready: 标志位,表示是否已经完成模型初始化。
  • ready: 标志位,表示是否已经完成初始化。
  • _lock: 锁,用来控制并发访问。
  • loading: 标志位,表示是否正在加载。
  • _pending_operations: 存放延迟 Model 注册的操作。应对模型的关联问题。

重要函数

  • populate(): 实例方法,
  1. 根据传入的installed_apps遍历初始化应用,如果已经是 AppConfig,则直接使用;否则调用create方法创建.将初始化完成的 AppConfig 实例存入app_configs中。在此确保app 名字不允许重复
# Phase 1: initialize app configs and import app modules.
for entry in installed_apps:
    if isinstance(entry, AppConfig):
        app_config = entry
    else:
        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
    app_config.apps = self

# Check for duplicate app names.
# 可以用counter来统计重复
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:
    raise ImproperlyConfigured(
        "Application names aren't unique, "
        "duplicates: %s" % ", ".join(duplicates)
    )
  1. 初始化各应用的模型
# Phase 2: import models modules.
for app_config in self.app_configs.values():
    app_config.import_models()

self.clear_cache()

self.models_ready = True
  1. 完成初始化
# Phase 3: run ready() methods of app configs.
for app_config in self.get_app_configs():
    # 调用每个appconfig的ready方法,完成自定义初始化步骤
    app_config.ready()

self.ready = True
self.ready_event.set()
  • get_models(): 实例方法,返回所有注册的模型列表,带缓存。

# This method is performance-critical at least for Django's test suite.
@functools.cache
def get_models(self, include_auto_created=False, include_swapped=False):
    """
    Return a list of all installed models.

    By default, the following models aren't included:

    - auto-created models for many-to-many relations without
        an explicit intermediate table,
    - models that have been swapped out.

    Set the corresponding keyword argument to True to include such models.
    """
    self.check_models_ready()

    result = []
    for app_config in self.app_configs.values():
        result.extend(app_config.get_models(include_auto_created, include_swapped))
    return result
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值