用Django全栈开发(进阶篇)——08. 浅聊Django初始化以及回调

大家好,这是皮爷给大家带来的最新的学习Python能干啥?之Django教程的进阶版

在之前《用Django全栈开发》系列专辑里面,皮爷详细的阐述了如何编写一个完整的网站,具体效果可以浏览线上网站:Peekpa.com

从进阶篇开始,每一篇文章都是干货满满,干的不行。这一节,我们来说:Django的初始化启动。

Peekpa.com的官方地址:http://peekpa.com

获取整套教程源码唯一途径,关注『皮爷撸码』,回复『peekpa.com』

皮爷的每一篇文章,都配置相对应的代码。这篇文章的代码对应的Tag是“Advanced_08”。

title.jpeg

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数组;

可以看到打了断点之后,这里的变量是这样:

001.png
  • 第二部则是import models,如果有submodel,这里还会再次深度导入;

可以看到,这里我们打断点到了datacenter里面,这里就有两个Model:

002.png
  • 最后一步就是调用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)这里:

003.png

所以,我们进入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启动信息:

004.png

步骤十一

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都会有一个初始化的回调。具体的操作步骤如下:

  1. 在应用的apps.py文件里面,配置name变量和实现ready()方法逻辑;
  2. 在应用的__init__.py文件里面,配置default_app_config变量;
  3. 最基本的操作,在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。

005.png

但是这里看到,peekpa_init_start
peekpa_init_end打印了两边。其实这里就是我们之前分析的启动流程中,restart_with_reloader部分启动了两次,解决方法,就是在启动命令python manage.py runserver后面加一个--noreload

在PyChar里面如何添加--noreload,非常的简单,到启动设置里面,然后把No Reload打钩就可以。

006.png
007.png

之后,我们再启动,就会发现不会再打印两遍了:

008.png

看到命令行后面直接加了--noreload了。

看到这里,太干了,所以我们来总结一下。

技术总结

最后总结一下,

Django如何初始化回调:

  1. Django的启动过程,非常复杂, 一般也就是先检测,然后获取一个uWSGI对象,最后启动socket等待;
  2. 针对每一个Application,Django都可以实现初始化回调;
  3. 初始化回调三步走,第一步就是在apps.py文件里面设置正确的name,还有实现read()方法;
  4. 第二步就是在__init__.py文件里面配置default_app_config,指向apps.py文件里面的类;
  5. 第三步就是在settings.py文件里面配置INSTALLED_APPS;
  6. 进阶篇的Django初始化启动及回调总结完毕。

获取整套教程源码唯一途径,关注『皮爷撸码』,回复『peekpa.com』

长按下图二维码关注,如文章对你有启发或者能够帮助到你,欢迎点赞在看转发三连走一发,这是对我原创内容输出的最大肯定。

底部图片.png

你可能感兴趣的:(用Django全栈开发(进阶篇)——08. 浅聊Django初始化以及回调)