django源码简析——后台程序入口

django源码简析——后台程序入口

  这一年一直在用云笔记,平时记录一些tips或者问题很方便,所以也就不再用博客进行记录,还是想把最近学习到的一些东西和大家作以分享,也能够对自己做一个总结。工作中主要基于django框架,进行项目的开发,我是主要做后台相关比较多一些,熟悉django的同学知道,django的后台进程通常通过下面这种方式运行:

python manage.py app [options]

  我们假设当前的项目名为myproject,这里app表示要运行的app名称,具体为django项目中module/management/commands中定义的进程文件名,options表示一些可选的参数。以python manage app为例,看下它的运行原理。manage.py是在项目创建之后,自动生成的一个py文件,它的定义如下:

if __name__ == "__main__":os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")from django.core.management import execute_from_command_lineexecute_from_command_line(sys.argv)

  execute_from_command_line 方法用于读取命令行参数,并执行相应的app程序代码:

def execute_from_command_line(argv=None):"""A simple method that runs a ManagementUtility."""utility = ManagementUtility(argv)utility.execute()

  从这里可以看出,实际上app程序是通过 ManagementUtility.execute() 方法来执行的。execute方法定义在django.core.manage.__init__.py中:

def execute(self):try:subcommand = self.argv[1]except IndexError:subcommand = 'help'  # Display help if no arguments were given.
parser = CommandParser(None, usage="%(prog)s subcommand [options] [args]", add_help=False)parser.add_argument('--settings')parser.add_argument('--pythonpath')parser.add_argument('args', nargs='*')  # catch-alltry:options, args = parser.parse_known_args(self.argv[2:])handle_default_options(options)except CommandError:pass  # Ignore any option errors at this point.
no_settings_commands = ['help', 'version', '--help', '--version', '-h','compilemessages', 'makemessages','startapp', 'startproject',]try:settings.INSTALLED_APPSexcept ImproperlyConfigured as exc:self.settings_exception = excif subcommand in no_settings_commands:settings.configure()if settings.configured:if subcommand == 'runserver' and '--noreload' not in self.argv:try:autoreload.check_errors(django.setup)()except Exception:passelse:django.setup()self.autocomplete()if subcommand == 'help':if '--commands' in args:sys.stdout.write(self.main_help_text(commands_only=True) + '\n')elif len(options.args) < 1:sys.stdout.write(self.main_help_text() + '\n')else:self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])elif subcommand == 'version' or self.argv[1:] == ['--version']:sys.stdout.write(django.get_version() + '\n')elif self.argv[1:] in (['--help'], ['-h']):sys.stdout.write(self.main_help_text() + '\n')else:self.fetch_command(subcommand).run_from_argv(self.argv)

   我们来分解一下这段程序,subcommnad是python manage.py后的参数,即子程序名,argv[0]表示manage.py。这里如果没有指定,那么子程序默认为help。接着通过CommandParser来解析随后的参数,app子程序名之后的参数,这里我们默认没有其他参数。接着在try语句中执行 settings.INSTALLED_APPS,这句乍看上去很是不解,没有赋值,没有输出,注意settings是django.conf.__init__.py中定义的一个LazySettings对象,LazySettings继承自LazyObject类,它重写了__getattr__和__setattr__方法,那么在调用settings.INSTALLED_APPS时,会通过其自定义的__getattr__方法实现:

settings = LazySettings()# django.conf.__init__.py
class LazySettings(LazyObject):# other functions ...def _setup(self, name=None):settings_module = os.environ.get(ENVIRONMENT_VARIABLE)if not settings_module:desc = ("setting %s" % name) if name else "settings"raise ImproperlyConfigured("Requested %s, but settings are not configured. You must either define the environment variable %s or call settings.configure() before accessing settings."% (desc, ENVIRONMENT_VARIABLE))self._wrapped = Settings(settings_module)def __getattr__(self, name):if self._wrapped is empty:self._setup(name)return getattr(self._wrapped, name)# other functions ...

  _setup方法从当前环境变量中获取ENVIRONMENT_VARIABLE("DJANGO_SETTINGS_MODULE"),这个值在manage.py文件中已经定义:

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my_project.settings")

  通过getter/setter方法,对settings对象的操作转到其私有成员self._wrapped对象的调用上,这里在第一次使用settings对象时,将其私有成员self._wrapped初始化为Settings类实例,其构造函数如下:

# django.conf.__init__.py

class
Settings(BaseSettings):def __init__(self, settings_module):# update this dict from global settings (but only for ALL_CAPS settings)for setting in dir(global_settings):if setting.isupper():setattr(self, setting, getattr(global_settings, setting))# store the settings module in case someone later caresself.SETTINGS_MODULE = settings_modulemod = importlib.import_module(self.SETTINGS_MODULE)tuple_settings = ("ALLOWED_INCLUDE_ROOTS","INSTALLED_APPS","TEMPLATE_DIRS","LOCALE_PATHS",)self._explicit_settings = set()for setting in dir(mod):if setting.isupper():setting_value = getattr(mod, setting)if (setting in tuple_settings andisinstance(setting_value, six.string_types)):raise ImproperlyConfigured("The %s setting must be a tuple. Please fix your settings." % setting)setattr(self, setting, setting_value)self._explicit_settings.add(setting)if not self.SECRET_KEY:raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.")if ('django.contrib.auth.middleware.AuthenticationMiddleware' in self.MIDDLEWARE_CLASSES and'django.contrib.auth.middleware.SessionAuthenticationMiddleware' not in self.MIDDLEWARE_CLASSES):warnings.warn("Session verification will become mandatory in Django 1.10. Please add 'django.contrib.auth.middleware.SessionAuthenticationMiddleware' ""to your MIDDLEWARE_CLASSES setting when you are ready to opt-in after reading the upgrade considerations in the 1.8 release notes.",RemovedInDjango110Warning)if hasattr(time, 'tzset') and self.TIME_ZONE:zoneinfo_root = '/usr/share/zoneinfo'if (os.path.exists(zoneinfo_root) and notos.path.exists(os.path.join(zoneinfo_root, *(self.TIME_ZONE.split('/'))))):raise ValueError("Incorrect timezone setting: %s" % self.TIME_ZONE)os.environ['TZ'] = self.TIME_ZONEtime.tzset()# other functions ...

  这里传递给settings_module的参数值为my_project.settings,构造函数会先通过global_settings来设置其属性,接着读取my_project.settings,设置其特定的属性,主要有ALLOWED_INCLUDE_ROOTS、INSTALLED_APPS、TEMPLATE_DIRS、LOCALE_PATHS这几个key,这几个key的解释如下:

  • ALLOWED_INCLUDE_ROOTS, 默认值为 () (即空元组,在global_settings中),它表示嵌入文件根路径的字符串——只有在某字符串存在于该元组的情况下,Django的 {% ssi %} 模板标签才会嵌入以其为前缀的文件。 这样做是出于安全考虑,从而使模板作者不能访问到他们不该访问的文件。
  • INSTALLED_APPS,默认同样为空元组,它表示项目中哪些 app 处于激活状态。元组中的字符串,除了django默认自带的命令之外,就是我们自己定义的app,也就是用python manage.py所启动的app了。
  • TEMPLATE_DIRS,默认同样为空元组,它表示模板文件的处处路径。
  • LOCALE_PATHS,默认同样为空元组,它表示Django将在这些路径中查找包含实际翻译文件的<locale_code>/LC_MESSAGES目录

  代码中使用了importlib.import_module这个方法,它支持程序动态引入以'.'分割的目录层次,比如importlib.import_module('django.core.management.commands.migrate'),这里该方法引入了myproject.settings模块,加载settings配置文件中上述4个key的值。接着校验中间件和时区的配置信息,完成全局实例settings中self._wrapped属性的初始化,最终通过__getattr__方法,将加载到的INSTALLED_APPS信息返回。回到execute函数,这里的全局settings实例以及初始化完毕,我们的subcommand不是runserver(runserver的情况下来之后再分析),接着运行django.setup()方法:

# django.__init__.py

def
setup():from django.apps import appsfrom django.conf import settingsfrom django.utils.log import configure_loggingconfigure_logging(settings.LOGGING_CONFIG, settings.LOGGING)apps.populate(settings.INSTALLED_APPS)

   这里setup函数配置日志信息,并且加载settings.INSTALLED_APPS中的自定义模块以及models模块,保存在django.apps中,这是一个全局的Apps类实例,用以注册或者说存储项目中的INSTALLED_APPS模块信息。我们来看下apps.populate方法:

class Apps(object):# other functions ...def populate(self, installed_apps=None):if self.ready:returnwith self._lock:if self.ready:returnif self.app_configs:raise RuntimeError("populate() isn't reentrant")for entry in installed_apps:if isinstance(entry, AppConfig):app_config = entryelse: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_configcounts = 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))self.apps_ready = Truefor 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 = Truefor app_config in self.get_app_configs():app_config.ready()self.ready = True# other functions ...

  for循环中,使用AppConfig.create(entry) 加载installed_apps里面的各模块,并保存在app_cofigs中,注意create方法是AppConfig类的classmethod,用以实现工厂模式,它根据installed_apps中的模块构造出 AppConfig(app_name, app_module) 这样的实例,其中app_name表示INSTALLED_APPS中指定的应用字符串,app_module表示根据app_name加载到的module。当加载的模块中有定义default_app_config时,那么会构造其表示的类对象,例如我们在django项目中会用到的用户认证鉴权模块,在INSTALLED_APPS中配置为'django.contrib.auth',当在import_module此模块时,实际django.contrib.auth是一个python的package,在__init__.py文件中有定义了default_app_config = 'django.contrib.auth.apps.AuthConfig',那么最终会构造apps.py中定义的AuthConfig类实例,这些default_app_config对应的类同样继承自AppConfig。在AppConfig实例的初始化方法中,会记录这些应用的标签、文件路径等信息,最终将这些实例会保存在其属性app_configs中。接着每个AppConfig实例会加载其指定模块的models,all_models定义为all_models = defaultdict(OrderedDict),defaultdict会创建表示一个类似dict的实例,在构造时可以指定字典中元素值的默认类型,这里用OrderedDict来指定其默认的类型,OrderedDict是dict的子类,它可以记录元素添加到字典中的顺序,保证元素有序,因此在获取all_models中的元素时,当key不存在时,会创建一个OrderedDict对象,我们来看下models是如何加载的:

for app_config in self.app_configs.values():all_models = self.all_models[app_config.label]app_config.import_models(all_models)MODELS_MODULE_NAME = 'models'def import_models(self, all_models):self.models = all_modelsif 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进来。再回到execute方法中,如果python manage.py之后传递的是非help或者version这种帮助信息,那么会执行到语句:

self.fetch_command(subcommand).run_from_argv(self.argv)

   fetch_command方法内部先通过get_commands方法,从全局的apps对象中获取之前加载到的INSTALLED_APPS模块对应的management/commands包:

# django.core.management.__init__.py

@lru_cache.lru_cache(maxsize=None)
def get_commands():commands = {name: 'django.core' for name in find_commands(upath(__path__[0]))}if not settings.configured:return commandsfor app_config in reversed(list(apps.get_app_configs())):path = os.path.join(app_config.path, 'management')commands.update({name: app_config.name for name in find_commands(path)})return commandsclass ManagementUtility(object):# other functions ...def fetch_command(self, subcommand):commands = get_commands()try:app_name = commands[subcommand]except KeyError:# This might trigger ImproperlyConfigured (masked in get_commands)
            settings.INSTALLED_APPSsys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" %(subcommand, self.prog_name))sys.exit(1)if isinstance(app_name, BaseCommand):# If the command is already loaded, use it directly.klass = app_nameelse:klass = load_command_class(app_name, subcommand)return klass# other functions ...

 

  注意方法定义在django.core.management._init_.py文件中,get_commands方法中的__path__[0]是其__init__.py的绝对路径,这里通过find_commands首先将django.core.management.commands目录下的模块引入进来,像我们常用的一些基础模块(通过python manage.py进行调用)比如startpp、migrate、compilemessages、runserver、shell等都在此目录下。加载完这些基础模块之后,接着加载apps中的自定义的commands模块,即INSTALLED_APPS对应的各个模块。再根据subcommand从中这些包中获取到对应的Command,返回Command类对象。django后台服务中的Command继承自BaseCommand,并且实现了各自业务的handle方法。

  接着,通过返回的对象调用其run_from_argv方法,从名称可以看出,这个方法是通过命令行参数,进行函数调用的:

def run_from_argv(self, argv):self._called_from_command_line = Trueparser = self.create_parser(argv[0], argv[1])if self.use_argparse:options = parser.parse_args(argv[2:])cmd_options = vars(options)# Move positional args out of options to mimic legacy optparseargs = cmd_options.pop('args', ())else:options, args = parser.parse_args(argv[2:])cmd_options = vars(options)handle_default_options(options)try:self.execute(*args, **cmd_options)except Exception as e:if options.traceback or not isinstance(e, CommandError):raiseif isinstance(e, SystemCheckError):self.stderr.write(str(e), lambda x: x)else:self.stderr.write('%s: %s' % (e.__class__.__name__, e))sys.exit(1)finally:connections.close_all()

   我们知道 fetch_command 返回的Command对象继承自BaseCommand,那么不同的后台任务可能需要不同的参数信息,在run_from_argv方法中,通过调用create_parser方法,Command子类将不同的参数信息进行设置,再通过执行execute方法,最终调用子类Command对象中定义的handle方法,完成自定义项目中业务逻辑的实现。

 

posted on 2017-02-16 16:30 Tourun 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/Tour/p/6403833.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/284604.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【BIM入门实战】最新《建筑制图与识图》复习题带参考答案(一)

文章目录 一、单选题二、填空题三、简答题四、绘图题五、阅读总平面图,完成问题一、单选题 1.在图纸右下角用以说明设计单位、图名、设计负责人等内容的表格为(B )。 A. 会签栏 B. 图标 C. 图框 D. 图纸目录 2.正面投影与侧面投影应保持( C )的关系。 A.长度相等且对正…

yum安装openoffice

安装方法如下&#xff1a;1、首先安装openoffice.org套件yum groupinstall "Office/Productivity" 安装好后&#xff0c;由于采用默认安装&#xff0c;语言是英文&#xff0c;我可以通过以下方法来查找来安装中文语言包。yum list openoffice* ....openoffice.org-la…

常见RGB格式

计算机世界中&#xff0c;最终对于颜色和画面的显示&#xff0c;更多的采用的是RGB模式&#xff0c;这里记录一下常见的RGB格式。任何计算机设备以及智能终端等&#xff0c;呈现在我们眼前的色彩实际上便是红绿蓝三基色不同的组合&#xff0c;RGB实际上就是三基色的组合&#x…

《ASP.NET Core 6框架揭秘》实例演示[04]:自定义依赖注入框架

ASP.NET Core框架建立在一个依赖注入框架之上&#xff0c;已注入的方式消费服务已经成为了ASP.NET Core基本的编程模式。为了使读者能够更好地理解原生的注入框架框架&#xff0c;我按照类似的设计创建了一个简易版本的依赖注入框架&#xff0c;并它命名为“Cat”。本篇提供的四…

【QGIS入门实战精品教程】4.8:QGIS如何下载SRTM数字高程模型DEM?

本文讲解QGIS中下载SRTM数字高程模型DEM,以黑龙江省塔河县为例。 图幅效果: 最终效果: 文章目录 1. 下载安装STRM Download插件2. 加载矢量数据,读取范围3. 下载STRM4. DEM拼接5. DEM裁剪1. 下载安装STRM Download插件 点击【插件】→【管理并安装插件】。 在搜索框中输入…

Win11 恢复设置Win10任务栏、快速启动栏及右键菜单(Win11 22000.100版本测试通过)

恢复方法 按下边路径添加 UndockingDisabled项&#xff0c;DWORD (32-bit)值为1&#xff1a; [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell\Update\Packages] "UndockingDisabled"dword:00000001显示效果 已知问题 开始按钮点击无反应&a…

Redis数据类型应用场景及具体方法总结

StringsStrings 数据结构是简单的key-value类型&#xff0c;value其实不仅是String&#xff0c;也可以是数字。使用Strings类型&#xff0c;你可以完全实现目前 Memcached 的功能&#xff0c;并且效率更高。还可以享受Redis的定时持久化&#xff0c;操作日志及 Replication等功…

几种距离的计算

http://www.tinysoft.com.cn/TSDN/HelpDoc/display.tsl?id12831 http://www.3566t.com/news/kvqa/1172028.html转载于:https://www.cnblogs.com/wangduo/p/5526003.html

vue vue-router vuex element-ui axios 写一个代理平台的学习笔记(十一)构思商品页面...

在写商品页面product.vue之前&#xff0c;我应该思考一下&#xff0c;商品页面要实现那些功能&#xff0c;该不如布局&#xff1f;要实现的功能 1、所有商品列表的展示2、分类商品的列表展示 3、搜索商品或得列表展示4、单一商品的详细页面5、商品列表分页功能6、还没想到的...…

【ArcGIS Pro微课1000例】0019:ArcGIS Pro从海洋的视角看世界---海洋投影(Spilhaus Projection)

从海洋的视角看世界: 世界地图大多是以陆地为主要载体,如果以海洋为主角,就需要使用一种海洋投影。该投影以Spilhaus博士的名称命名。ArcGIS Pro自2.5版本以来提供了Spilhaus Projection。 投影效果预览: 接下来演示ArcGIS Pro 2.8中海洋投影的转换方法: 1. 新建一个工程…

有人撸了个网页版win11,惊艳!

演示地址&#xff1a;https://win11.blueedge.me/ Github地址&#xff1a;https://github.com/blueedgetechno/windows11

Vue3+.NET6+C#10,最近优质前后端分离项目汇总

据说80%的WEB开发都是管理后台&#xff0c;一套开源的优秀管理后台开发模板堪称福音&#xff01;分享一套Vue3 Axios TS Vite Element Plus .NET 6 WebAPI JWT SqlSugar的前后端分离架构的通用管理后台源码数据库脚本&#xff0c;还有与之配套录制的一组视频教程&#xff0c;全…

九九乘法表

问题描述&#xff1a;打印乘法表如图&#xff1a;1*112*12 2*243*13 3*26 3*394*14 4*28 4*312 4*4165*15 5*210 5*315 5*420 5*5256*16 6*212 6*318 6*424 6*530 6*6367*17 7*214 7*321 7*428 7*535 7*642 7…

C++ 对象的内 存布局(下)

原文地址&#xff1a;http://blog.csdn.net/haoel/article/details/3081385 (注:看本文的时候由于宿舍快断电了,来不及细看,所以怕自己忘记,先贴出来.不排除文章有错误,大家自己测试一下.) 重复继承 下面我们再来看看&#xff0c;发生重复继承的情况。所谓重复继承&#xff0c;…

用python快速合并代码(方便软著申请)

Title: This is a file for …… Author: JackieZheng Date: 2021-09-08 09:43:58 LastEditTime: 2021-09-08 21:14:22 LastEditors: Please set LastEditors Description: FilePath: \\pythonCode\\mergeCodeFile.py import os# 允许提取的文件类型 include_file_types[.php,…

【GIS风暴】一文彻底弄懂数字地形(DEM、DOM、TDOM、DSM)的区别与联系

在2021自然资源部发布的《实景三维中国建设技术大纲(2021版)》中,空间数据部分包括“数字高程模型(DEM)、数字表面模型(DSM)、数字正射影像(DOM)、真正射影像(TDOM)、倾斜摄影三维模型、激光点云等。” 那么到底什么是DEM、DOM、TDOM、DSM,它们之间又有什么用的区别…

na+mb与gcd

蒜头君和花椰妹在玩一个游戏&#xff0c;他们在地上将 nn 颗石子排成一排&#xff0c;编号为 11 到 nn。开始时&#xff0c;蒜头君随机取出了 22 颗石子扔掉&#xff0c;假设蒜头君取出的 22 颗石子的编号为 aa, bb。游戏规则如下&#xff0c;蒜头君和花椰妹 22 人轮流取石子&a…

什么是“异步 Request-Reply”模式?编程如何实现?

在某些情况下&#xff0c;WEB API 可能需要很长时间来处理请求&#xff0c;而客户端如果一直等待工作完成是不可行的&#xff0c;比如连接超时等。这时&#xff0c;可以使用“异步 Request-Reply 模式”。异步 Request-Reply 模式异步 Request-Reply 模式是指&#xff1a;在后端…

【测绘程序设计】Excel度分秒(° ‘ “)转换度(°)模板附代码超实用版

在实际工作中,无论是ArcGIS中,还是CASS中,作图时需要将GPS实测的经纬度度分秒( ’ ")坐标转换为度(),在前面的文章中介绍了C#中将度分秒转为度的转换程序,本文讲解在Excel中快速度分秒( ’ ")转换度(),提高工作效率。 文章目录 准备工作编写代码注意事…

IO扩展控件(System.IO.Abstractions)

刚看到这个Namespace的时候还以为是.Net Framework里自带的包&#xff0c;结果查了一圈无任何结果。果断上Github搜索&#xff0c;一击即中 https://github.com/tathamoddie/System.IO.Abstractions先翻译下开发者给出的简单说明&#xff0c;今后再慢慢使用类似于System.Web.Ab…