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

原文:http://www.cnblogs.com/Tour/p/6403833.html

这一年一直在用云笔记,平时记录一些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")fromdjango.core.managementimportexecute_from_command_line

execute_from_command_line(sys.argv)

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

defexecute_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中:

defexecute(self):try:

subcommand= self.argv[1]exceptIndexError:

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)exceptCommandError:pass#Ignore any option errors at this point.no_settings_commands=['help','version','--help','--version','-h','compilemessages','makemessages','startapp','startproject',

]try:

settings.INSTALLED_APPSexceptImproperlyConfigured as exc:

self.settings_exception=excifsubcommandinno_settings_commands:

settings.configure()ifsettings.configured:ifsubcommand =='runserver'and'--noreload'notinself.argv:try:

autoreload.check_errors(django.setup)()exceptException:passelse:

django.setup()

self.autocomplete()ifsubcommand =='help':if'--commands'inargs:

sys.stdout.write(self.main_help_text(commands_only=True) +'\n')eliflen(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])elifsubcommand =='version'orself.argv[1:] == ['--version']:

sys.stdout.write(django.get_version()+'\n')elifself.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__.pyclassLazySettings(LazyObject):#other functions ...def_setup(self, name=None):

settings_module=os.environ.get(ENVIRONMENT_VARIABLE)ifnotsettings_module:

desc= ("setting %s"% name)ifnameelse"settings"raiseImproperlyConfigured("Requested %s, but settings are not configured.You must either define the environment variable %sor call settings.configure() before accessing settings."%(desc, ENVIRONMENT_VARIABLE))

self._wrapped=Settings(settings_module)def__getattr__(self, name):ifself._wrappedisempty:

self._setup(name)returngetattr(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

classSettings(BaseSettings):def__init__(self, settings_module):#update this dict from global settings (but only for ALL_CAPS settings)forsettingindir(global_settings):ifsetting.isupper():

setattr(self, setting, getattr(global_settings, setting))#store the settings module in case someone later caresself.SETTINGS_MODULE =settings_module

mod=importlib.import_module(self.SETTINGS_MODULE)

tuple_settings=("ALLOWED_INCLUDE_ROOTS","INSTALLED_APPS","TEMPLATE_DIRS","LOCALE_PATHS",

)

self._explicit_settings=set()forsettingindir(mod):ifsetting.isupper():

setting_value=getattr(mod, setting)if(settingintuple_settingsandisinstance(setting_value, six.string_types)):raiseImproperlyConfigured("The %s setting must be a tuple.Please fix your settings."%setting)

setattr(self, setting, setting_value)

self._explicit_settings.add(setting)ifnotself.SECRET_KEY:raiseImproperlyConfigured("The SECRET_KEY setting must not be empty.")if('django.contrib.auth.middleware.AuthenticationMiddleware'inself.MIDDLEWARE_CLASSESand'django.contrib.auth.middleware.SessionAuthenticationMiddleware'notinself.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 afterreading the upgrade considerations in the 1.8 release notes.",

RemovedInDjango110Warning

)ifhasattr(time,'tzset')andself.TIME_ZONE:

zoneinfo_root='/usr/share/zoneinfo'if(os.path.exists(zoneinfo_root)andnotos.path.exists(os.path.join(zoneinfo_root,*(self.TIME_ZONE.split('/'))))):raiseValueError("Incorrect timezone setting: %s"%self.TIME_ZONE)

os.environ['TZ'] =self.TIME_ZONE

time.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将在这些路径中查找包含实际翻译文件的/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

defsetup():fromdjango.appsimportappsfromdjango.confimportsettingsfromdjango.utils.logimportconfigure_logging

configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)

apps.populate(settings.INSTALLED_APPS)

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

classApps(object):#other functions ...defpopulate(self, installed_apps=None):ifself.ready:returnwith self._lock:ifself.ready:returnifself.app_configs:raiseRuntimeError("populate() isn't reentrant")forentryininstalled_apps:ifisinstance(entry, AppConfig):

app_config=entryelse:

app_config=AppConfig.create(entry)ifapp_config.labelinself.app_configs:raiseImproperlyConfigured("Application labels aren't unique,""duplicates: %s"%app_config.label)

self.app_configs[app_config.label]=app_config

counts=Counter(

app_config.nameforapp_configinself.app_configs.values())

duplicates=[nameforname, countincounts.most_common()ifcount > 1]ifduplicates:raiseImproperlyConfigured("Application names aren't unique, duplicates: %s"%",".join(duplicates))

self.apps_ready=Trueforapp_configinself.app_configs.values():

all_models=self.all_models[app_config.label]

app_config.import_models(all_models)

self.clear_cache()

self.models_ready=Trueforapp_configinself.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是如何加载的:

forapp_configinself.app_configs.values():

all_models=self.all_models[app_config.label]

app_config.import_models(all_models)

MODELS_MODULE_NAME='models'defimport_models(self, all_models):

self.models=all_modelsifmodule_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)defget_commands():

commands= {name:'django.core'fornameinfind_commands(upath(__path__[0]))}ifnotsettings.configured:returncommandsforapp_configinreversed(list(apps.get_app_configs())):

path= os.path.join(app_config.path,'management')

commands.update({name: app_config.namefornameinfind_commands(path)})returncommandsclassManagementUtility(object):#other functions ...deffetch_command(self, subcommand):

commands=get_commands()try:

app_name=commands[subcommand]exceptKeyError:#This might trigger ImproperlyConfigured (masked in get_commands)settings.INSTALLED_APPS

sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n"%(subcommand, self.prog_name))

sys.exit(1)ifisinstance(app_name, BaseCommand):#If the command is already loaded, use it directly.klass =app_nameelse:

klass=load_command_class(app_name, subcommand)returnklass#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方法,从名称可以看出,这个方法是通过命令行参数,进行函数调用的:defrun_from_argv(self, argv):

self._called_from_command_line=True

parser= self.create_parser(argv[0], argv[1])ifself.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)exceptException as e:ifoptions.tracebackornotisinstance(e, CommandError):raiseifisinstance(e, SystemCheckError):

self.stderr.write(str(e),lambdax: 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方法,完成自定义项目中业务逻辑的实现。这一年一直在用云笔记,平时记录一些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")fromdjango.core.managementimportexecute_from_command_line

execute_from_command_line(sys.argv)

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

defexecute_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中:

defexecute(self):try:

subcommand= self.argv[1]exceptIndexError:

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)exceptCommandError:pass#Ignore any option errors at this point.no_settings_commands=['help','version','--help','--version','-h','compilemessages','makemessages','startapp','startproject',

]try:

settings.INSTALLED_APPSexceptImproperlyConfigured as exc:

self.settings_exception=excifsubcommandinno_settings_commands:

settings.configure()ifsettings.configured:ifsubcommand =='runserver'and'--noreload'notinself.argv:try:

autoreload.check_errors(django.setup)()exceptException:passelse:

django.setup()

self.autocomplete()ifsubcommand =='help':if'--commands'inargs:

sys.stdout.write(self.main_help_text(commands_only=True) +'\n')eliflen(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])elifsubcommand =='version'orself.argv[1:] == ['--version']:

sys.stdout.write(django.get_version()+'\n')elifself.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__.pyclassLazySettings(LazyObject):#other functions ...def_setup(self, name=None):

settings_module=os.environ.get(ENVIRONMENT_VARIABLE)ifnotsettings_module:

desc= ("setting %s"% name)ifnameelse"settings"raiseImproperlyConfigured("Requested %s, but settings are not configured.You must either define the environment variable %sor call settings.configure() before accessing settings."%(desc, ENVIRONMENT_VARIABLE))

self._wrapped=Settings(settings_module)def__getattr__(self, name):ifself._wrappedisempty:

self._setup(name)returngetattr(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

classSettings(BaseSettings):def__init__(self, settings_module):#update this dict from global settings (but only for ALL_CAPS settings)forsettingindir(global_settings):ifsetting.isupper():

setattr(self, setting, getattr(global_settings, setting))#store the settings module in case someone later caresself.SETTINGS_MODULE =settings_module

mod=importlib.import_module(self.SETTINGS_MODULE)

tuple_settings=("ALLOWED_INCLUDE_ROOTS","INSTALLED_APPS","TEMPLATE_DIRS","LOCALE_PATHS",

)

self._explicit_settings=set()forsettingindir(mod):ifsetting.isupper():

setting_value=getattr(mod, setting)if(settingintuple_settingsandisinstance(setting_value, six.string_types)):raiseImproperlyConfigured("The %s setting must be a tuple.Please fix your settings."%setting)

setattr(self, setting, setting_value)

self._explicit_settings.add(setting)ifnotself.SECRET_KEY:raiseImproperlyConfigured("The SECRET_KEY setting must not be empty.")if('django.contrib.auth.middleware.AuthenticationMiddleware'inself.MIDDLEWARE_CLASSESand'django.contrib.auth.middleware.SessionAuthenticationMiddleware'notinself.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 afterreading the upgrade considerations in the 1.8 release notes.",

RemovedInDjango110Warning

)ifhasattr(time,'tzset')andself.TIME_ZONE:

zoneinfo_root='/usr/share/zoneinfo'if(os.path.exists(zoneinfo_root)andnotos.path.exists(os.path.join(zoneinfo_root,*(self.TIME_ZONE.split('/'))))):raiseValueError("Incorrect timezone setting: %s"%self.TIME_ZONE)

os.environ['TZ'] =self.TIME_ZONE

time.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将在这些路径中查找包含实际翻译文件的/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

defsetup():fromdjango.appsimportappsfromdjango.confimportsettingsfromdjango.utils.logimportconfigure_logging

configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)

apps.populate(settings.INSTALLED_APPS)

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

classApps(object):#other functions ...defpopulate(self, installed_apps=None):ifself.ready:returnwith self._lock:ifself.ready:returnifself.app_configs:raiseRuntimeError("populate() isn't reentrant")forentryininstalled_apps:ifisinstance(entry, AppConfig):

app_config=entryelse:

app_config=AppConfig.create(entry)ifapp_config.labelinself.app_configs:raiseImproperlyConfigured("Application labels aren't unique,""duplicates: %s"%app_config.label)

self.app_configs[app_config.label]=app_config

counts=Counter(

app_config.nameforapp_configinself.app_configs.values())

duplicates=[nameforname, countincounts.most_common()ifcount > 1]ifduplicates:raiseImproperlyConfigured("Application names aren't unique, duplicates: %s"%",".join(duplicates))

self.apps_ready=Trueforapp_configinself.app_configs.values():

all_models=self.all_models[app_config.label]

app_config.import_models(all_models)

self.clear_cache()

self.models_ready=Trueforapp_configinself.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'defimport_models(self, all_models):

self.models=all_modelsifmodule_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)defget_commands():

commands= {name:'django.core'fornameinfind_commands(upath(__path__[0]))}ifnotsettings.configured:returncommandsforapp_configinreversed(list(apps.get_app_configs())):

path= os.path.join(app_config.path,'management')

commands.update({name: app_config.namefornameinfind_commands(path)})returncommandsclassManagementUtility(object):#other functions ...deffetch_command(self, subcommand):

commands=get_commands()try:

app_name=commands[subcommand]exceptKeyError:#This might trigger ImproperlyConfigured (masked in get_commands)settings.INSTALLED_APPS

sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n"%(subcommand, self.prog_name))

sys.exit(1)ifisinstance(app_name, BaseCommand):#If the command is already loaded, use it directly.klass =app_nameelse:

klass=load_command_class(app_name, subcommand)returnklass#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方法,从名称可以看出,这个方法是通过命令行参数,进行函数调用的:

defrun_from_argv(self, argv):

self._called_from_command_line=True

parser= self.create_parser(argv[0], argv[1])ifself.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)exceptException as e:ifoptions.tracebackornotisinstance(e, CommandError):raiseifisinstance(e, SystemCheckError):

self.stderr.write(str(e),lambdax: 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方法,完成自定义项目中业务逻辑的实现。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容