美文网首页用Django开发Peekpa.com
用Django全栈开发(进阶篇)——08. 浅聊Django初始

用Django全栈开发(进阶篇)——08. 浅聊Django初始

作者: 皮爷撸码 | 来源:发表于2020-08-05 16:42 被阅读0次

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

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

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

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

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

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

    title.jpeg

    Django启动流程

    众所周知,如果要启动一个Django项目,我们使用的命令是:

    $ python manage.py runserver <xx.xx.xx.xx:xx>
    

    命令后面的尖括号里面的内容,是选择性填写的。其实,最关键的就是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初始

        本文链接:https://www.haomeiwen.com/subject/rhctdktx.html