flask-bootstrap 优雅配置国内加速源

在不修改 flask-bootstrap package 的情况下优雅使用加速源

@@ 2020-12-14 @@

Preface

flask-bootstrap 是用于 flask 框架中一个 pip 包,通过 jinja2 模板继承特性可以快速开发 bootstrap。

但这个包默认采用的是 cloudflare 源,默认会包含 bootstrap.css、bootstrap.js、jquery.js,此外还可以包含 html5shiv、respond.js,这个可以通过一定方法查看,比如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
In [1]: from flask import Flask
In [2]: from flask_bootstrap import Bootstrap

In [3]: app = Flask(__name__)
In [4]: bootstrap = Bootstrap(app)

In [5]: app.extensions
Out[5]: 
{'bootstrap': {'cdns': {'local': <flask_bootstrap.StaticCDN at 0x743831b0>,
   'static': <flask_bootstrap.StaticCDN at 0x743b9e50>,
   'bootstrap': <flask_bootstrap.ConditionalCDN at 0x743a4fb0>,
   'jquery': <flask_bootstrap.ConditionalCDN at 0x743dd550>,
   'html5shiv': <flask_bootstrap.ConditionalCDN at 0x743cf3d0>,
   'respond.js': <flask_bootstrap.ConditionalCDN at 0x743cf5d0>,
   'bootcdn_bootstrap': <flask_bootstrap.ConditionalCDN at 0x743cf0f0>,
   'bootcdn_jquery': <flask_bootstrap.ConditionalCDN at 0x743cf4b0>}},
 'nav_renderers': {'bootstrap': ('flask_bootstrap.nav', 'BootstrapRenderer'),
  None: ('flask_bootstrap.nav', 'BootstrapRenderer')}}

但是在国内,cloudflare 的速度真是令人感动。

纵观一圈网上的解决方法,总体来说分两种思路:(1) 在 __init__.py 暴力修改 (2) 修改 flask-bootstrap package

第一种方法代码量较大,并且暴力修改代码使其可读性大大降低;第二种方法在环境变了或者包更新了的情况都需要重新配置,太麻烦了。

Inplement

一个包的主体部分是不会变的,那么我们在项目主入口用类继承重写一下方法就行了,并且在模板继承时重写 bootstrap/base.html 中的 styles 和 scripts

Recette

这里采用 bootcdn 作范例

首先要读源码,了解 flask-bootstrap 这个包怎么运行,我就不写怎么读了(

包目录

~/venv/lib/python3.x/site-packages/flask_bootstrap

我们知道 Python 在 import flask-bootstrap 时,其实就是 import 了这个目录下的__init__.py再通过对象方法调用相应的类,from flask-bootstrap import Bootstrap 就是直接导入了 Bootstrap 这个类

我们需要用到__init__.py中以下类/包变量

Bootstrap, ConditionalCDN, WebCDN, StaticCDN, BOOTSTRAP_VERSION, JQUERY_VERSION

Initialization

1
2
from flask import Flask
from flask_bootstrap import Bootstrap, ConditionalCDN, WebCDN, StaticCDN, BOOTSTRAP_VERSION, JQUERY_VERSION

Rewrite inherited class function

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Bootstrap_with_bootcdn(Bootstrap): # 子类
    def init_app(self, app):             # 重写
        super().init_app(app)            # 继承
        local = StaticCDN('bootstrap.static', rev=True)
        static = StaticCDN()
        def lwrap(cdn, primary=static):
            return ConditionalCDN('BOOTSTRAP_SERVE_LOCAL', primary, cdn)
        bootcdn_bootstrap = lwrap(WebCDN('//cdn.bootcdn.net/ajax/libs/twitter-bootstrap/%s/' % BOOTSTRAP_VERSION), local)
        bootcdn_jquery = lwrap(WebCDN('//cdn.bootcdn.net/ajax/libs/jquery/%s/' % JQUERY_VERSION), local)
        app.extensions['bootstrap']['cdns']['bootcdn_bootstrap'] = bootcdn_bootstrap
        app.extensions['bootstrap']['cdns']['bootcdn_jquery'] = bootcdn_jquery

我在此处加入了两条键,分别是bootcdn_bootstrapbootcdn_jquery,在后面模板重写需要用到

如果你想加入别的 CDN,只需要按照上面的规则添加就行了

再将其实例化

1
2
app = Flask(__name__)
bootstrap = Bootstrap_with_bootcdn(app)

Rewrite inherited template block

1
2
3
4
5
6
7
8
{% block styles %}
<link href="{{ bootstrap_find_resource('css/bootstrap.css', cdn='bootcdn_bootstrap') }}" rel="stylesheet">    
{% endblock %}

{% block scripts %}
<script src="{{ bootstrap_find_resource('jquery.js', cdn='bootcdn_jquery') }}"></script>
<script src="{{ bootstrap_find_resource('js/bootstrap.js', cdn='bootcdn_bootstrap') }}"></script>
{% endblock %}

我们知道 super() 是类继承用的函数,但是这在模板继承也可以用到,并且具有非常类似的作用,如果不在 block 中加入 super(),会默认使用子模板的同名 block 覆盖父模板中的同名 block

这里的 bootstrap_find_resource 方法中的属性 cdn 要对应我们之前重写的类属性

Others - Optimization

我们可以将以上代码打包放进config.py,这是我比较推荐的一种方式

./config.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from flask_bootstrap import Bootstrap, ConditionalCDN, WebCDN, StaticCDN, BOOTSTRAP_VERSION, JQUERY_VERSION

class Bootstrap_with_bootcdn(Bootstrap):
    def init_app(self, app):
        super().init_app(app)
        local = StaticCDN('bootstrap.static', rev=True)
        static = StaticCDN()
        def lwrap(cdn, primary=static):
            return ConditionalCDN('BOOTSTRAP_SERVE_LOCAL', primary, cdn)
        bootcdn_bootstrap = lwrap(WebCDN('//cdn.bootcdn.net/ajax/libs/twitter-bootstrap/%s/' % BOOTSTRAP_VERSION), loca
l)
        bootcdn_jquery = lwrap(WebCDN('//cdn.bootcdn.net/ajax/libs/jquery/%s/' % JQUERY_VERSION), local)
        app.extensions['bootstrap']['cdns']['bootcdn_bootstrap'] = bootcdn_bootstrap
        app.extensions['bootstrap']['cdns']['bootcdn_jquery'] = bootcdn_jquery

./app.py

1
2
3
4
from config import Bootstrap_with_bootcdn

app = Flask(__name__)
bootstrap = Bootstrap_with_bootcdn(app)
CC BY-NC-SA 4.0 License