大家好,这是皮爷给大家带来的最新的学习Python能干啥?之Django教程的进阶版。
在之前《用Django全栈开发》系列专辑里面,皮爷详细的阐述了如何编写一个完整的网站,具体效果可以浏览线上网站:Peekpa.com
从进阶篇开始,每一篇文章都是干货满满,干的不行。这一节,我们来说:Django的初始化启动。
Peekpa.com的官方地址:http://peekpa.com
获取整套教程源码唯一途径,关注『皮爷撸码』,回复『peekpa.com』
皮爷的每一篇文章,都配置相对应的代码。这篇文章的代码对应的Tag是“Advanced_08”。
Django启动流程
众所周知,如果要启动一个Django项目,我们使用的命令是:
$ python manage.py runserver
命令后面的尖括号里面的内容,是选择性填写的。其实,最关键的就是runserver
这个命令。
这个命令是在manage.py
里面。
所以,我们来到项目目录下的manage.py
文件里,看到:
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Peekpa.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv) # 这里运行
最关键的是最后一句:
execute_from_command_line(sys.argv) # 这里运行
步骤一
然后点进去我们看源码:
def execute_from_command_line(argv=None):
"""Run a ManagementUtility."""
utility = ManagementUtility(argv)
utility.execute()
这里其实是初始化了一个实例,然后调用了execute()
方法。
这个方法其实挺长的,在django.core.management.__init__
文件里。
步骤二
这个execute()
方法里面,最重要的是以下方法:
def execute(self):
....内容太多忽略....
django.setup()
....内容太多忽略....
步骤三
在上面的这个django.setup()
方法中,代码就会调用下面这个方法:
def setup(set_prefix=True):
....内容太多忽略....
apps.populate(settings.INSTALLED_APPS)
可以看到,在这个里面,最终调用的是apps.populate()
,而括号里面的内容,正式我们settings.py里面的INSTALLED_APPS
那个列表。进去这个方法我们看到,这个方法超级长,但是有三处重点:
def populate(self, installed_apps=None):
"""
Load application configurations and models.
Import each application module and then each model module.
It is thread-safe and idempotent, but not reentrant.
"""
....内容太多忽略....
# Phase 1: initialize app configs and import app modules.
....内容太多忽略....
# 创建实例
app_config = AppConfig.create(entry)
....略....
# Phase 2: import models modules.
....内容太多忽略....
app_config.import_models()
....内容太多忽略....
# Phase 3: run ready() methods of app configs.
....内容太多忽略....
app_config.ready()
....内容太多忽略....
这里主要有三点:
- 第一步创建AppConfig实例,这里传入的就是那个
INSTALLED_APPS
数组;
可以看到打了断点之后,这里的变量是这样:
- 第二部则是import models,如果有submodel,这里还会再次深度导入;
可以看到,这里我们打断点到了datacenter里面,这里就有两个Model:
- 最后一步就是调用
ready()
方法,这一步就是在上一步的基础之上,调用方法app_config.ready()
而已,之后我们会使用到。
目前这个步骤三
就算是走完了,我们返回到步骤二execute()
中,接下来的关键代码是这个:
def execute(self):
....内容太多忽略....
self.autocomplete()
....内容太多忽略....
这里的self.autocomplete()
,这里面只是用来处理subcommand
的。
接着我们就走到了excute()
最后一步:
def execute(self):
....内容太多忽略....
if subcommand == 'help':
if '--commands' in args:
sys.stdout.write(self.main_help_text(commands_only=True) + '\n')
elif not options.args:
sys.stdout.write(self.main_help_text() + '\n')
else:
self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])
# Special-cases: We want 'django-admin --version' and
# 'django-admin --help' to work, for backwards compatibility.
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)
步骤四
我们这里的runserver
,走的就是最后这一句:self.fetch_command(subcommand).run_from_argv(self.argv)
,所以,我们来看一下这个run_from_argv()
方法:
def run_from_argv(self, argv):
....内容太多忽略....
self.execute(*args, **cmd_options)
....内容太多忽略....
步骤五
这里面最关键的就是self.execute(*args, **cmd_options)
,
所以,我们点进去django.core.management.base.py
文件,看这个代码:
def execute(self, *args, **options):
....内容太多忽略....
output = self.handle(*args, **options)
....内容太多忽略....
步骤六
这里面需要看的步骤就是self.handle(*args, **options)
。所以我们点进去,发现代码不太对,正确的代码位置是在django.core.management.commands.runserver.py
文件里。
def handle(self, *args, **options):
....内容太多忽略....
self.run(**options)
最后一句,self.run(**options)
才是我们要看的代码。
步骤七
这个run()
方法就是紧接着的代码:
def run(self, **options):
....内容太多忽略....
autoreload.run_with_reloader(self.inner_run, **options)
所以,接下来我们要看的方法就是autoreload.run_with_reloader()
步骤八
这个run_with_reloader()
方法其实很有意思:
def run_with_reloader(main_func, *args, **kwargs):
signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
try:
if os.environ.get(DJANGO_AUTORELOAD_ENV) == 'true':
reloader = get_reloader()
logger.info('Watching for file changes with %s', reloader.__class__.__name__)
start_django(reloader, main_func, *args, **kwargs)
else:
exit_code = restart_with_reloader()
sys.exit(exit_code)
except KeyboardInterrupt:
pass
在这里环境里面的DJANGO_AUTORELOAD_ENV
默认是false的所以会先进入restart_with_reloader()
,在里面把环境设置为true,再执行subprocess.call函数重新运行命令行参数,所以,当我们第二次运行到这里的时候,代码就会直接走到start_django(reloader, main_func, *args, **kwargs)
这里:
所以,我们进入start_django()
方法去看一下。
步骤九
注意这个方法传入了一个方法main_func
,在start_django()
方法里面,其实就是启动一个线程,然后运行里面的方法:
def start_django(reloader, main_func, *args, **kwargs):
....内容太多忽略....
django_main_thread = threading.Thread(target=main_func, args=args, kwargs=kwargs, name='django-main-thread')
django_main_thread.setDaemon(True)
django_main_thread.start()
....内容太多忽略....
所以,我们这个时候应该去查看一下这个main_func
变量的值:inner_run
,所以,我们调到了步骤七
中去查看inner_run
。
步骤十
在inner_run()
中,主要功能就是如下:
def inner_run(self, *args, **options):
....内容太多忽略....
# 检查数据迁移
self.check_migrations()
....内容太多忽略....
#在这个方法里调用get_internal_wsgi_application方法
handler = self.get_handler(*args, **options)
....内容太多忽略....
这里面,self.check_migrations()
这个则是检查数据迁移,之后的方法self.get_handler()
则是关键方法,这个里面调用了get_internal_wsgi_application
。所以,我们就得去看get_internal_wsgi_application()
方法里面的内容。
走到这里的时候,其实在控制台已经打印出来了我们平时看到的Django启动信息:
步骤十一
get_internal_wsgi_application()
方法里面则有核心内容:
def get_internal_wsgi_application():
....内容太多忽略....
app_path = getattr(settings, 'WSGI_APPLICATION')
....内容太多忽略....
return import_string(app_path)
....内容太多忽略....
这里面先是获取了settings的路径,然后返回获取WSGI对象,最后一步调用的方法则是django.core.servers.basehttp.py
文件下的run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
方法
步骤十二
最后一步:
def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
....内容太多忽略....
httpd.serve_forever()
最后可以看到,代码运行的是httpd.serve_forever()
方法,相当于socket的recv方法,等待客户端的连接。
至此,Django的启动完结。总共十二步,过程比较繁琐,但是该有的还是都有。
Django初始化回调
那么接下来,我们来引出今天的重点,就是初始化的回调。
其实细心的同学已经看到,在之前的步骤三
当中,就有一个ready()方法,我们这里就是要说这个read()
方法该如何写。
每一个Django中的App都相当于一个Component,而每一个Component都会有一个初始化的回调。具体的操作步骤如下:
- 在应用的
apps.py
文件里面,配置name
变量和实现ready()
方法逻辑; - 在应用的
__init__.py
文件里面,配置default_app_config
变量; - 最基本的操作,在
settings.py
里面的INSTALLED_APPS
中,配置变量。
所以,我们这里就把apps/basefunction/global_peekpa.py
目录下的peekpa_config
初始化过程,放到basefunction
应用中。遵循上面的三步走战略:
第一步,那就是应该在apps/basefunction/apps.py
文件里面完成name
的配置和read()
方法:
class BasefunctionConfig(AppConfig):
name = 'app.basefunction'
def ready(self):
from .global_peekpa import init_peekpa
init_peekpa()
这里有一点很关键:name
这里,这里之前默认的是name = 'basefunction'
,但是我们这里需要改成:name = 'app.basefunction'
。这里的名字一定要写对,依据就是我们把所有的Django应用都移到了apps文件下,所以,我们这里的名字也应该加上apps.
。
还有一点就是注意ready()方法里面的导入,官网在这里支出,最好在这里做操作保存数据库的操作,而且导入方法也是在方法中导入,并不是在文件刚开始就导入。
第二步,在apps/basefunction/__init__.py
里面添加default_app_config
变量:
default_app_config = 'apps.basefunction.apps.BasefunctionConfig'
这里面的值就是直接指到apps/basefunction/apps.py
里面的BasefunctionConfig
。
第三步,就是在Peekpa/settings.py
里面的INSTALLED_APPS
配置basefunction
这个app,其实如果你之前没动过这里的话,就不必太关心:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'apps.peekpauser',
'apps.poster',
'apps.datacenter',
'apps.exchangelink',
'apps.basefunction', # 这里是相关配置
]
之后我们启动程序,然后就能看到init()方法打印出来的log。
但是这里看到,peekpa_init_start
和
peekpa_init_end
打印了两边。其实这里就是我们之前分析的启动流程中,restart_with_reloader
部分启动了两次,解决方法,就是在启动命令python manage.py runserver
后面加一个--noreload
。
在PyChar里面如何添加--noreload
,非常的简单,到启动设置里面,然后把No Reload
打钩就可以。
之后,我们再启动,就会发现不会再打印两遍了:
看到命令行后面直接加了--noreload
了。
看到这里,太干了,所以我们来总结一下。
技术总结
最后总结一下,
Django如何初始化回调:
- Django的启动过程,非常复杂, 一般也就是先检测,然后获取一个uWSGI对象,最后启动socket等待;
- 针对每一个Application,Django都可以实现初始化回调;
- 初始化回调三步走,第一步就是在
apps.py
文件里面设置正确的name
,还有实现read()
方法; - 第二步就是在
__init__.py
文件里面配置default_app_config
,指向apps.py
文件里面的类; - 第三步就是在
settings.py
文件里面配置INSTALLED_APPS
; - 进阶篇的Django初始化启动及回调总结完毕。
获取整套教程源码唯一途径,关注『皮爷撸码』,回复『peekpa.com』
长按下图二维码关注,如文章对你有启发或者能够帮助到你,欢迎点赞,在看,转发三连走一发,这是对我原创内容输出的最大肯定。