flask基础

对Flask感兴趣的,能够看下这个视频教程:http://study.163.com/course/courseLearn.htm?courseId=1004091002css

1. 第一个 flask 程序

# 从 flask 框架中导入 flask 类
from flask import Flask

# 用 flask() 初始化一个 flask 对象,并赋给 app
# 需传递一个参数 __name__
#  1. 方便 flask 框架去寻找资源
#  2. 方便 flask 插件去定位问题
app = Flask(__name__)


# @app.route() 是一个装饰器,做用是对 url 与 视图函数进行映射
# 将 `/` 映射到 hello_world() 函数上
# 即用户访问 http://example:80/ 的时候,用 hello_world() 函数来响应
@app.route('/')
def hello_world():
    return 'Hello World!'


# 若是当前文件做为程序入口,那么就执行 app.run() 
if __name__ == '__main__':
    # app.run() 是启动一个应用服务器来响应用户请求并不断监听   
    app.run()

2. 使用 debug 模式

使用 debug 模式有不少好处:html

  1. 将报错信息显示到浏览器上,而不须要进入编辑器中查看报错信息,方便开发者查看错误
  2. 当检测到程序代码(.py文件)发生改动,程序会自动加载而不须要手动重启服务器

对 flask 程序使用 debug 模式有 2 种方式,以下:前端

2.1 在 app.run() 中使用

app.run() 中直接传入一个关键字参数: app.run(debug=True)python

2.2 在配置文件中使用

  1. 在相同目录下,新建一个 python 文件,建议命名为 config,并在里面指定该程序配置了 DEBUG 模式,即 `config.py 文件的内容以下:mysql

    config.py
    
     # encoding:utf-8
     DEBUG = True
     # SECRET_KEY    
     # SQLALCHEMY_DB     # 数据库的一些参数配置
  2. 而后在主 app 文件中导入这个文件并配置到 app 中,主 app 文件内容以下:jquery

    First_Flask.py
    
     # encoding:utf-8
     from flask import Flask
     import config           # 导入 config 配置文件
    
     app = Flask(__name__)
     app.config.from_object(config)      # 将该配置文件的配置信息应用到 app 中
    
     @app.route('/')
     def hello_world():
         a = 3
         b = 0
         c = a/b
         return 'Hello,World.'
    
     if __name__ == '__main__':
         app.run()
  3. config.py 文件的用处很是大,须要掌握这种配置方法,在后期的 SECRET_KEYSQLALCHEMY_DB (与数据库有关)都须要在这个文件中作配置.web

3. URL 传参到视图

  1. 参数的做用:能够在相同的 URL 可是指定不一样的参数时,来加载不一样的数据

如: http://localhost:8000/article/abchttp://localhost:8000/article/def 中,两条 URL 的参数不一样,咱们能够获取这个参数并渲染后返回客户浏览器sql

  1. 如何在 flask 中使用参数?代码以下数据库

    @app.route('/article/<id>')
     def article(id):
         return u'<h1>你请求的参数是:%s<h1>' % id
    1. 参数须要放置在两个尖括号中
    2. 视图函数中须要放和 URL 参数同名的参数

4. URL 反转

正转指的是:在获取到用户输入的 URL 后将该 URL 映射到对应的视图函数中,让对应的视图函数去处理该用户的请求;
反转指的是:与正转相反,经过视图函数来查找对应的 URL。
反转的做用是:1. 在页面重定向的时候会使用 URL 反转;2. 在模板中会使用 URL 反转编程

实现反转的方法:

  1. 在 flask 框架中导入 url_for 模块
  2. url_for('FunctionName') 反转

  3. First_Flask.py 源码以下:

    # encoding:utf-8
     from flask import Flask,url_for
     import config
    
     app = Flask(__name__)
     app.config.from_object(config)
    
     @app.route('/')
     def hello_world():
         print url_for('article',id='123')
         print url_for('my_list')
         return 'Hello,World.'
    
     @app.route('/article/<id>/')
     def article(id):
         return u'<h1>你请求的参数是:%s<h1>' % id
    
     @app.route('/list/')
     def my_list():
         return '<h1>list</h1>'
    
     if __name__ == '__main__':
         app.run()

5. 页面跳转和重定向

  1. 做用:在用户访问某些须要登陆的页面时,若是用户没登陆,则可让他重定向到登陆页面

  2. 实现:

    import redirect,url_for
     redirect(url_for('login'))

2、jinja2 模板

1. 模板渲染和参数

1.1 模板渲染

模板实际上就是一些被编写好的具备必定格式的 html 文件。

在 pycharm 左侧一栏,项目下有两个文件夹: statictemplate,分别用于存放静态文件(如css,js,img文件)和模板文件(如html),因此咱们的 html 文件应该放在 template 文件夹下。

如何在主程序中调用模板文件呢?

  1. template 文件夹下新建一个 html 文件
  2. 在主程序中导入 render_template 模块
  3. 调用语法:render_template('abc.html'),注意不用写路径,flask 会自动去 template 文件夹下查找 abc.html,但有文件夹除外

代码以下:

import render_template
return render_template('index.html')

1.2 参数

在 web 项目开发的大多数状况下,咱们须要在 html 文件中从后台程序传入一些参数,而后将带有这些参数的 html 文件返回浏览器。这时候就须要在 html 文件中引用这些后台的参数,方法是 {{ Params }} 用 2 个花括号括起来,同时还要在后台程序作一些传参的动做。具体以下:

  1. 后端传参:

    render_template('index',username=u'蚂蚁有毒',gender=u'男',age=18)
  1. 前端引用:

    <h1>用户名:{{ username }}</h1>
     <h1>性别:{{ gender }}</h1>
     <h1>年龄:{{ age }}</h1>

可是要是参数愈来愈多,则代码会变得很复杂,可读性差,管理难度大。那么咱们能够用一个字典(DICT)来定义一组参数。以下:

user = {
    'username':id,
    'gender':u'男',
    'age':18
}

# 调用时传入一个关键字参数便可
return render_template('index.html',**user)

2. 模板中访问属性和字典

上面所演示的都是调用一些简单的参数,若是在更复杂的环境下,如调用类的属性呢?或者是调用字典中的字典的值呢?应该怎么作?

  1. 在主程序中能够先定义一个类并实例化:

    class Person(object){
         name = u'蚂蚁有毒'
         gender = u'男'
         age = 18
     }
     p = Person()
  2. 再定义一个字典:

    content = {
         'person':p,
         'websites':{
             'baidu':'www.baidu.com',
             'google':'www.google.com'
         }
     }
  3. 而后传参时进行调用:

    return render_template('index',**content)
  4. 最后在 index.html 中调用:

    <p>姓名:{{person.name}}</p>
     <p>性别:{{person.gender}}</p>
     <p>年龄:{{person.age}}</p>
     <hr>
     <p>百度:{{websites.baidu}}</p>
     <p>{{websites.google}}</p>

3. 模板中的 if 和 for

实际上,咱们还能够在模板(html文件)中嵌入python的代码:{% code %},这是 jinja2 的语法,能够嵌入 if 语句和 for 语句来在 html 文件中执行相关的逻辑操做。

3.1 if 语句的操做

  1. 主程序代码:

    # encoding:utf-8
    
     from flask import Flaskrender_template
    
     app = Flask(__name__)
     app.config.from_object(config)
    
     @app.route('/<is_login>')
     def index(is_login):
         if is_login == '1':
             user = {
                 'username':u'蚂蚁有毒',
                 'age':18
             }
             return render_template('index.html',user=user)
         else:
             return render_template('index.html')
    
     if __name__ == '__main__':
         app.run(debug=True)
  2. html 代码:

    <!DOCTYPE html>
     <html lang="en">
     <head>
         <meta charset="UTF-8">
         <title>蚂蚁有毒的首页</title>
     </head>
     <body>
         <!-- 若是用户存在而且年龄大于18就显示用户名 -->
         {% if user and user.age > 18 %}
             <a href="#">{{ user.username }}</a>
             <a href="#">注销</a>
         {% else %}
             <a href="#">登陆</a>
             <a href="#">注册</a>
         {% endif %}
    
         <h1>欢迎来到蚂蚁有毒的首页。</h1>
     </body>
     </html>

3.2 for 语句的操做

3.2.1 基本用法

for 循环的语法,在 html 中调用 for 语句和 if 语句的语法是同样的,都是在 {% %} 中写入 for 关键字。
咱们能够借助 html 中的 for 语句来遍历一些变量(List or Dict or Truple)。

  1. 主程序代码:

    users = {
         'username':u'蚂蚁有毒',
         'gender':u'男',
         'age':18
     }
     websites = ['www.baidu.com','www.google.com','www.qq.com']
     return render_template('index.html',user=users,website=websites)
  2. html 代码:

    {% for k,v in user.items() %}
         <p>{{ k }}:{{ v }}</p>
     {% endfor %}
     <hr>
     {% for website in websites %}
         <p>{{ website }}</p>
     {% endfor %}

完整代码参照上一节

3.2.2 练习

题目:渲染一个 四大名著 给前端,而后前端用一个表格展现出来。

  1. 先在主程序中定义一个变量用于存放四大名著的基本信息:

    books = {
         u'三国演义':{
             'author':u'罗贯中',
             'price':109
         },
         u'西游记':{
             'author':u'吴承恩',
             'price':120
         },
         u'红楼梦':{
             'author':u'曹雪芹',
             'price':113
         },
         u'水浒传':{
             'author':u'施耐庵',
             'price':135
         }
     }
  2. 再在主程序中将其传给前端 html 文件

    return render_template('index.html',books = books)
  3. 最后在前端模板中调用

    <table>
             <tbody>
                 <tr>
                     <th>书名</th>
                     <th>做者</th>
                     <th>价格</th>
                 </tr>
                 {% for k,v in books.items() %}
                 <tr>
                     <td>{{ k }}</td>
                     <td>{{ v.author }}</td>
                     <td>{{ v.price }}</td>
                 </tr>
                 {% endfor %}
             </tbody>
         </table>

4. 过滤器

过滤器能够理解为 Linux 中的管道符 |,将获取的变量通过管道符后筛选出想要的内容。在 flask 中有不少过滤器,这里介绍 2 个比较经常使用的过滤器:defaultlength。要注意的是,过滤器只能针对变量{{ params }}使用。

4.1 default

default 过滤器的做用:若是当前变量不存在,可使用指定的默认值

对于 default 过滤器,咱们作一个实验:若是用户有头像则显示本身的头像,若是用户没有头像则显示默认头像。

在 html 文件中使用以下所示:

<img src="{{ avatar | default('https://i.imgur.com/ROhBvig.png') }}">

该行代码表示:
    若是后端主程序有传递 avatar 变量过来,那么就使用 avatar 变量的值;
    若是后端主程序没有传递 avatar 变量过来,那么就使用 default 过滤器指定的内容

对于本例而言,default 后面跟的图片地址应该是默认头像的地址,avatar 变量内保存的值应该是用户头像

4.2 length

能够统计有长度属性的变量的长度。语法与 default 过滤器同样,但不用在后面跟上指定的变量。对于 length 过滤器,咱们作一个实验:统计评论的数量并显示评论内容。

  1. 主程序代码:

    comments = [
         {
             'user':u'蚂蚁有毒',
             'content':u'我不喜欢这个东西'
         },
         {
             'user':u'杨烺',
             'content':u'有同感,我也是'
         }
     ]
    
     return render_template('index.html',comments = comments)
  2. html 模板代码:

    <p>
         评论数:({{ comments | length }})
     </p>
     <hr>
     {% for comment in comments %}
         <li>
             {{ comment.user }}:
         </li>
         <ol>
             {{ comment.content }}
         </ol>
     {% endfor %}
  3. 此外还有其余不少过滤器,能够自行查找资料。

5. 继承和使用 block

5.1 继承

继承的概念和面向对象编程的类的继承是同样的,只不过在这里继承的对象是模板。能够建立一个经常使用模板,而且定义相关接口,能够供其余模板所使用。继承的做用是:能够把一些公共的代码放在父模板中,避免编写重复的代码。

当建立好了一个模板后,在子模板中可使用 {% extends 'base.html' %} 来继承该模板

5.2 使用 block

可是若是我要在子模板中编写本身所特有的内容,应该怎么办?这时候就须要在父模板中写一个接口,来让子模板实现:

  1. 在父模板须要的地方定义接口的方式是:{% block abc %}{% endblock %}
  2. 在子模板中一样须要写上 {% block abc %} code {% endblock %},而且在 code 处写子模板须要的代码。
  3. 须要注意的是,子模板必须在父模板定义的接口中写代码,否则没有做用。以下所示:
1. 父模板(Base.html):

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        {% block title %}
            <title>Base</title>
        {% endblock %}
        <style>
            .nav{
                background: #3a3a3a;
                height: 65px;
            }
            ul{
                overflow: hidden;
            }
            ul li{
                float: left;
                list-style: none;
                padding: 0 10px;
                line-height: 65px;
            }
            ul li a{
                color: #fff;
            }
        </style>
    </head>
    <body>
    
        <div class="nav">
            <ul>
                <li><a href="#">首页</a></li>
                <li><a href="#">发布问答</a></li>
            </ul>
        </div>
    
    {% block content %}{% endblock %}
    
    </body>
    </html>

2. 子模板(index.html):

    {% extends 'base.html' %}
    
    {% block title %}
        <title>首页</title>
    {% endblock %}
    
    {% block content %}
        <h1>蚂蚁有毒的首页</h1>
    {% endblock %}

6. URL 连接和加载静态文件

6.1 URL 连接

咱们若是想要实现:点击一个按钮就能跳转到另外一个页面上。那么咱们就要使用连接跳转技术,在 flask 中,能够经过 <a href={{ url_for('视图函数名') }}>哈哈</a> 来实现

6.2 加载静态文件

css,js,img 这 3 个文件都属于静态文件,都要放在项目的 static 文件夹下。若是咱们要对模板中的一些内容进行渲染,如:对 <a> 标签的内容进行渲染,那么咱们能够将本来写在 <head> 标签中的 <style> 标签的内容放到 css 文件中,再在模板中使用 url_for()css 文件的渲染方式连接进来。步骤以下:

1. 加载 css 文件<link>标签

  1. static 文件夹下建立一个新的文件夹 css,再在 css 文件夹下建立一个 css 文件,可随意命名(好记就行),如:head.css
  2. 在使用了 <a></a> 标签的模板中,若想引用 css 文件的内容,能够用 url_for() 连接进来,但格式与连接网址略有不一样。正确连接静态文件的方式为:

    在模板 <head> 标签范文内:
     <link rel=''stylesheet href='{{ url_for('static',filename='css/head.css') }}'>
     注意!文件路径

2. 加载 img 文件<img>标签

  1. 第一步和加载 css 文件的第一步同样,建立文件夹和文件:/static/img/index.jpg
  2. 第二步在模板中须要插入图片的地方使用以下代码:

    <img src='{{ url_for('static',filename='img/index.jpg') }}' alt=''>

3. 加载 js 文件<script>标签

  1. 第一步和加载 css 文件的第一步同样,建立文件夹和文件:static/js/index.js
  2. 第二步在模板中须要插入脚本的地方使用以下代码:

    <script src='{{ url_for('static',filename='js/index.js') }}'>

通常静态文件也就这三样,使用方法如上所示。


3、数据库

1. 背景知识和软件安装

  1. 安装 MySQL

    数据库咱们使用的是 MySQL,下载地址:https://dev.mysql.com/downloads/mysql/,下载社区版就好了。注意要下载安装包(.msi)而不是压缩包(.zip)

    若是在安装过程当中提示咱们要安装 windows 插件,那就按照它提示的网址去下载,没有给网址的话就百度谷歌吧。

  2. 安装 MySQL-python

    MySQL-python 是一个驱动或者说是一个插件,若是咱们想要经过 python 去操做 MySQL 就须要借助这个软件去作。在 Windows 下安装 MySQL-python 的步骤以下:
    1. 进入 python 的 flask 虚拟环境并启动:

      cd C:\Virtualenv\flask-env\Script\
       active
       pip install mysql-python
    2. 安装的时候会报错,咱们须要去 www.lfd.uci.edu/~gohlke/pythonlibs/#mysql-python 这个网站上下载一个非官方的插件MySQL_python-1.2.5-cp27-none-win_amd64.whl,来使 mysql-python 支持 Windows 。能够下载到 Virtualenv 所在盘的任意目录。
    3. 安装这个插件:进入到该文件所在目录执行命令 pip install MySQL_python-1.2.5-cp27-none-win_amd64.whl 便可。

  3. 安装 Flask-SQLALchemy

    1. 介绍
      Flask-SQLALchemy 是一套 ORM(Object Relationship Mapping,模型关系映射) 框架。

      好处:可让咱们操做数据库就和操做对象同样简单,很是方便。由于一个表就抽象成一个类,一条数据就抽象成该类的一个对象。这样一来,咱们去操做数据库的时候,就不用去写什么 select 之类的数据库语句,而是能够直接用 python 操做实例对象就好了。
    2. 安装

      cd C:\Virtualenv\flask-env\Script\
       active
       pip install flask-sqlalchemy

2. SQLAlchemy 链接数据库

  1. 先建立一个数据库:

    进入 MySQL 的命令行 -> 输入密码 -> create database [db_demo1(数据库名)] charset utf8;

  2. 在主 app 程序中从 flask_sqlalchemy 导入 SQLAlchemy 类并初始化

    from flask_sqlalchemy import SQLAlchemy
     app = Flask(__name__)
     db = SQLAlchemy(app)
  3. config.py 文件中配置数据库信息并链接

    DIALECT = 'mysql'
     DRIVER = 'mysqldb'
     USERNAME = 'root'
     PASSWORD = 'root'
     HOST = '127.0.0.1'
     PORT = '3306'
     DATABASE = 'db_demo1'
    
     SQLALCHEMY_DATABASE_URI = "{}+{}://{}:{}@{}:{}/{}?charset=utf8".format(DIALECT, DRIVER, USERNAME, PASSWORD, HOST, PORT, DATABASE)
  4. 在主 app 文件中,添加配置文件

    import config
    app.config.from_objece(config)

  5. 作测试,看有没有出现问题

    db.create_all()

3. SQLAlchemy 模型与表映射

对于一个表,咱们能够建立一个类(模型)来与之对应(一张表对应一个类),以下表:

  1. 先定义好表格是怎么样的

    articel 表:
     create table article(
         id int primary key autoincrement,
         title varchar(100) not null,
         content text not null
     )
  2. 再建立出对应的(模型)类

    class Article(db.Model):    # 必定要继承自 db.Model
         __tablename__ = 'article'   # 指定表名,默认为类的小写字符
         id = db.Column(db.Integer,primary_key=True,autuincrement=True)      # 表中的每一个字段都要用 Column 建立,而后指定数据类型主键自增加等属性
         title = db.Column(db.String(100),nullable=False)
         content = db.Column(db.Text,nullable=False)
  3. 将全部建立的模型(类)都映射到数据库中成为一个个的表格

    db.create_all()

链接数据库的总结:

  1. 模型(类)需继承自 db.Medel,表格中的字段必需要用 db.Column 映射
  2. 数据类型:

    db.Integer          表明      int,
     db.String(length)   表明      varchar,须要指定长度
     db.Text             表明      text
  1. 其余属性:

    primary_key=True    表明      主键
     nullabel=True       表明      可为空,默承认以
     autoincrement=True  表明      主键自增加
  2. 将全部的模型(类)都在数据库中建立:

    db.create_all()

4. 数据库的增删改查

对数据库的增删改查,通常都在视图函数里面写,这样在浏览器上对某个 URL 进行访问时,就会执行对应的视图函数里面的代码,从而达到操做数据库的目的。

flask 对数据库的增删改查,包括提交,都使用的是 db.session 操做。

  1. 增长

    1. 建立一个表中的数据,即实例化模型(类):article1 = Article(title='Monday',content='something')
    2. 将 add 操做添加到事务中:db.session.add(article1)
    3. 更新到数据库中:db.session.commit()
  2. 删除

    1. 把要删除的数据查找出来:article1 = Article.query.filter(Article.title=='Monday').firsr()
    2. 把找出来的数据删除:db.session.delete(article1)
    3. 作事务的提交:db.session.commit()
  3. 修改

    1. 把要修改的数据查找出来:article1 = Article.query.filter(Article.title=='Monday').firsr()
    2. 把找出来的数据作修改:article1.title = 'Sunday'
    3. 作事务的提交:db.session.commit()
  4. 查询

    result = Article.query.filter(Article.title=='Monday')      # 查找是针对模型(类)的,使用 query 属性,该属性继承自 db.Model,并用 filter() 来过滤条件
     article1 = result.first()       # 实际上筛选出来的对象是放在一个 list 中,可用 .first() 取 list 中的第一个值
     print 'title:%s' % article.title
     print 'content:%s' % article.content

5. SQLAlchemy 的外键约束

建立 2 张表:userarticle,其中 articleauthor_id 引用是 userid,即 2 者是外键关系。

  1. 数据库语句建立:

    建立用户表:
     create table users(
         id int primary key autoincrement
         username varchar(20) not null
     )
    
     建立文章表:
     create table article(
         id int primary key autoincrement
         title varchar(100) not null
         content text not null
         author_id int,
         foreign key `author_id` reference `users.id`    # 用外键相关联
     )
  2. flask-sqlalchemy 模型建立:

    建立用户表:
     class Users(db.Model):
         __tablename__ = 'users'
         id = db.Column(db.Integer,primary_key=True,autoincrement=True)
         usernmae = db.Column(db.String(20),nullable=False)
    
     建立文章表:
     class Article(db.Model):
         __tablename__ = 'article'
         id = db.Column(db.Integer,primary_key=True,autoincrement=True)
         title = db.Column(db.String(100),nullabel=False)
         content = ab.Column(db.Text,nullabel=False)
         author_id = db.Column(db.Integer,db.ForeignKey('users.id'))     # 外键关系
  3. 实例化模型(类)

    由于 article 依赖与 user.id 存在,因此先建立 user,在建立 article。

    user1 = User(username='user1')
     db.session.add(user1)
     db.session.commit()
    
     article = Article(title='aaa',content='bbb',author_id=1)
     db.session.add(article)
     db.session.commit()
  4. 需求:查询 title ='aaa' 的文章的做者

    4.1 传统方法

    article = Article.query.filter(Article.title=='aaa').first()    # 找出标题为 aaa 的文章
     author = article.author_id      # 找出文章的做者
    
     user = User.query.filter(User.id==author).fitst()       # 找出 id 为做者 id 的做者
    
     print 'username:%s' % user.id       # 打印做者名

    固然了,这个方法实在是太过于繁琐,做为一个优秀的框架模型,SQLAlchemy 是能够用更先进的方法去找的,以下所示:

    4.2 用 SQLAlchemy 语法

    article = Article.query.filter(Article.title=='aaa')
     article.author.username     # 找出标题为 aaa 的文章做者
    
     user = User.query.filter(User.username=='user1')
     user1.articles      # 找出做者 user1 写过的全部文章

    固然了,这个只是理想中的方法,这样的方式更方便咱们去获取需求,不过 SQLAlchemy 已经实现了这种方法,只须要作以下映射便可使用:

    # 先在 Article 模型中添加属性
     author = db.relationship('User',backref=db.backref('articles'))
    
     # 再查找某篇文章的做者
     article = Article.query.filter(Article.title=='aaa').first()
     print 'username : %s' % article.author.username
    
     # 查找某个做者的文章
     user = User.query.filter(User.id=='1').first()
     print u'%s 的文章:'% user.username
     for article in user.articles:
         print article.title
  5. 当对两个 table 的关系用了 relationship 关联外键以后,那么 articleauthor 就没必要在实例化的时候指定了,因此当你添加一篇文章,能够进行以下操做:

    article1 = Article(title='111',content='222')
     article1.author = User.query.filter(User.username=='myyd').first().id

6. flask_script 命令行操做

简介:Flask_Script 可让程序经过命令行的形式来操做 Flask,例如:经过命令跑一个开发版本的服务器、设置数据库,定时任务等等。要使用 Flask_Script,能够在 Flask 虚拟环境中经过 pip install flask-script 安装最新版本。

6.1 编写 flask_script 脚本代码

  1. 新建一个 manage.py 文件,将代码写在该文件中,而不是写在主 app 文件中。内容以下:

    # encoding:utf-8
    
     from flask_script import Manager        # 从 flask_script 导入 Manager 
     from flask_script_demo1 import app      # 从 flask_script_demo1 导入 app 
    
     manage = Manager(app)       # 初始化 app 
    
     @manage.command     # 装饰器
     def runserver():        # 执行命令的程序写在这个函数下
         print u'服务器跑起来了。'
    
     @manage.command     # 装饰器
     def stopserver():       # 执行命令的程序写在这个函数下
         print u'服务器关闭了。'
    
     if __name__ == '__main__':
         manage.run()
  2. 命令行调用 manage.py 文件:

    在虚拟环境的命令行下,用 python manage.py command 执行 manage.py 文件下的某个程序,如:python manage.py runserverpython manage.py stopserver 分别会执行 manage.py 文件中的 runserver()stopserver() 方法。

6.2 从其余命令文件中调用命令

  1. 若是有一些关于数据库的操做,咱们能够放在一个文件中执行。如 db_script.py 文件:

    # encoding: utf-8
    
     from flask_script import Manager
    
     # 由于本文件不是做为主 app 文件,因此不须要写 if __name__ == '__main__'
     # 也不须要在初始化的时候传入 app 文件
    
     DBManage = Manager()
    
     @DBManage.command
     def init():
         print u'服务器初始化完成。'
    
     @DBManage.command
     def migrate():
         print u'数据库迁移完成。'

    这时候要想用上 db_script.py 里定义的命令,须要在主 manage.py 文件中导入该文件并引用该文件的命令:

    from db_scripts import DBManage     # 导入 db_script.py 文件
     manage.add_command('db',DBManage)   # 引用该文件
    
     # 以后要是想使用 db_script.py 中的命令,命令行中就要经过 python manage.py db init 来调用
     # 其中,db 是 manage.add_command() 中引号内的值,调用子命令的方法就是这种格式
  2. 总结:

    若是直接在主 manage.py 文件中写命令,调用时只须要执行 python manage.py command_name 便可

    若是将一些命令集中在另外一个文件中,那么就须要输入一个父命令,好比 python manage.py db init

  3. 例子:两个文件的完整代码以下

    1. manage.py
    
     # encoding:utf-8
    
     from flask_script import Manager
     from flask_script_demo1 import app
     from db_scripts import DBManage
     manage = Manager(app)
    
     @manage.command
     def runserver():
         print u'服务器跑起来了。'
    
     @manage.command
     def stopserver():
         print u'服务器中止了。'
    
     manage.add_command('db',DBManage)
    
     if __name__ == '__main__':
         manage.run()
    
    
     2. db_script.py
    
     # encoding: utf-8
     from flask_script import Manager
    
     DBManage = Manager()
    
     @DBManage.command
     def init():
         print u'服务器初始化完成。'
    
     @DBManage.command
     def migrate():
         print u'数据库迁移完成。'

7. 分开 Models 和解决循环引用

以前咱们都是将数据库的模型(类)放在主 app 文件中,可是随着项目愈来愈大,若是对于加入的新的模型,咱们还放在主 app 文件中,就会使主 app 文件愈来愈大,同时也愈来愈很差管理。因此咱们能够新建一个专门存放模型的文件。如 models.py 文件用来专门存放模型的文件。

  1. 将本来在主 app 文件中定义的模型(类)移动到 models.py 文件中,可是会提示错误,因此咱们在 models.py 文件中导入主 app 文件的 db:from models_sep.py import db,两个文件的完整代码下所示:

    1. # models_sep.py 文件
    
     from flask import Flask
     from flask_sqlalchemy import SQLAlchemy
     from models import Article
    
     app = Flask(__name__)
     db = SQLAlchemy(app)
     db.create_all()
    
     @app.route('/')
     def index():
         return 'index!'
    
     if __name__ == '__main__':
         app.run()
    
     2. # models.py 文件
    
     from flask_script_demo1 import db
    
     class Article(db.Model):
         __tablename__ = 'articles'
         id = db.Column(db.Integer,primary_key=True,autoincrement=True)
         title = db.Column(db.String(100),nullable=False)
         content = db.Column(db.Text,nullable=False)
  2. 执行以上文件,会报错。

    报错提示:ImportError: cannot import name Article,出现此类报错,先排查路径和导入的内容是否有错,若保证没错,则极可能是出现循环引用。

    报错缘由:循环引用,即 models_sep.py 引用了 models.py 中的 Article,而 models.py 又引用了 models_sep.py 中的 db,从而形成循环引用。

  3. 解决循环引用:

    解决方法:将 db 放在另外一个文件 exts.py 中,而后 models_sep.pymodels.py 都从 exts.py 中引用 db 变量,这样就能够打破引用的循环。

    三个文件的代码以下:

    1. # exts.py 文件
    
     from flask_sqlalchemy import SQLAlchemy
     db = SQLAlchemy()
    
     2. # models_sep.py 文件
    
     from flask import Flask
     from models import Article
     from exts import db
     import config
    
     app = Flask(__name__)
     app.config.from_object(config)
     db.init_app(app)
    
     @app.route('/')
     def index():
         return 'index'
    
     if __name__ == '__main__':
         app.run()
    
     3. # models.py 文件
    
     from exts import db
    
     class Article(db.Model):
         __tablename__ = 'articles'
         id = db.Column(db.Integer,primary_key=True,autoincre)
         title = db.Column(db.String(100),nullable=False)
         content = db.Column(db.Text,nullable=False)
  4. 总结:

    分开 models 的目的是:让代码更方便管理。

    解决循环引用的方法:把 db 放在一个单独文件中如 exts.py ,让主 app 文件models 文件都从 exts.py 中引用。

8. flask-migrate 数据库迁移

在以上基础上,咱们将模型(类)映射到数据库中,调用 db.create_all() 进行映射。而后运行,发现报错:No application found. Either work inside a view function or push an application context.,是由于在 db 的上下文中,没有将 app 推入到栈中。

8.1 所谓上下文(flask独有的特性):

当用户访问了服务器,则服务器会将当前 app 并加载到 app 栈中;若是用户没有访问服务器,那么即便 app 已经建立,可是没有被服务器加载到 app 栈中。若这时候运行服务器,则 app 栈中没有加载 app,当 db.init_app(app) 去 app 栈中取 app 对象的时候没有成功获取,因此报错。

  1. 缘由:

    状况一:
    1. db = SQLAlchemy(app) 这句代码运行的时候,会将 app 推到app栈中;
    2. 而后 db.create_all() 就能够经过 app 栈获取到栈顶元素 app 了。
    状况二:
    1. 使用分开 models 后,db = SQLAlchemy() 时没有传入 app,因此没有将 app 推到栈中;
    2. 那么在主 app 文件中经过 db.init_app(app) 将 app 与 db 绑定时,会去 app 栈中取 app,可是没有获取到,因此报错。
  2. 解决方法:将 app 手动推到 app 栈中:

    # 将 db.create_all() 替换为如下代码
     with app.app_context():
         db.create_all()

8.2 migrate 数据库迁移:

这个时候若是咱们的模型(类)要根据需求添加一个做者字段,这时候咱们须要去修改模型 Article,修改完成咱们须要再映射一遍。可是对于 flask-sqlalchemy 而言,当数据库中存在了某个模型(类)后,再次映射不会修改该模型的字段,即再次映射不会奏效。

传统解决办法:

在数据库中删除该模型对应的表格,再将带有新字段的模型从新进行映射。

很显然,这种方式明显很简单粗暴,很是不安全,由于在企业中一个数据库中的表格是含有大量数据的,若是删除可能会形成重大损失。因此咱们须要一个能够动态修改模型字段的方法,使用 flask-migrate。先安装:在虚拟环境下使用命令 pip install flask-migrate 便可。

8.3 使用 flask-migrate 动态修改模型字段

使用 flask-migrate 的最简单方法是:借助 flask-script 使用命令行来对 flask-migrate 进行操做。一共有好几个步骤,分别说明一下:

  1. 新建文件 manage.py

    新建 manage.py 文件后:

    1. 导入相应的包并初始化 manager

      from flask_script import Manager
       from migrate_demo import app
       from flask_migrate import Migrate,MigrateCommand
       from exts import db
       manager = Manager(app)
    2. 要使用 flask_migrate 必须绑定 appdb

      migrate = Migrate(app,db)
    3. MigrateCommand 命令添加到 manager

      manager.add_command('db',MigrateCommand)
    4. manage.py 文件代码以下:

      from flask_script import Manager
       from migrate_demo import app
       from flask_migrate import Migrate,MigrateCommand
       from exts import db
      
       manager = Manager(app)
      
       # 1. 要使用 flask_migrate 必须绑定 app 和 db
       migrate = Migrate(app,db)
      
       # 2. 把 MigrateCommand 命令添加到 manager 中
       manager.add_command('db',MigrateCommand)
      
       if __name__ == '__main__':
           manager.run()
  2. 主 app 文件不须要再对模型进行映射,因此能够将如下语句给删除:

    with app.app_context():
         db.create_all()         # 将模型映射到数据库中
  3. manage.py 文件中,导入须要映射的模型(类):

    由于在主 app 文件中已经再也不须要对模型进行映射,而对模型的操做是在 manage.py 文件中进行的,包括 flask-migrate 动态映射,因此要导入须要映射的模型。

    from models import Article
  4. 完成以上步骤后,便可到命令行中更新数据库中的模型了:

    1. python manage.py db init,初始化 flask-migrate 环境,仅在第一次执行的时候使用。
    2. python manage.py db migrate,生成迁移文件
    3. python manage.py db upgrade,将迁移文件映射到数据库的表格中

背景知识:HTTP 协议是无状态的,每次链接断开后再从新链接,服务器是不记得浏览器的历史状态的。也就是说:即便第一次浏览器访问服务器时登录成功后,只要断开与服务器的链接,服务器就会忘记浏览器的信息,那么当浏览器与服务器再次成功链接时,服务器不知道浏览器的相关信息。

  1. cookie

    而 cookie 的出现就是为了解决这个问题:当浏览器第一次在服务器上登陆成功时,服务器会将一些数据(cookie)返回给浏览器,而后浏览器将 cookie 保存在本地,第二次访问服务器时浏览器会自动地将上次存储的 cookie 发送给服务器,服务器经过 cookie 判断浏览器的信息从而提供更好的服务(如自动记录用户上次浏览的位置等)。

    服务器和浏览器交互 cookie 的过程对用户而言是透明的。

    cookie 信息保存在用户的浏览器中。

  2. session

    session 的做用和 cookie 相似,都是为了保存用户的相关信息。不一样的是:cookie 是将用户的信息保存在用户的浏览器本地,而 session 是将用户的信息保存在服务器上。相对而言,session 比 cookie 更加安全和稳定,可是会占用服务器的一些资源,不过影响不大。同时,session 还支持设置过时时间,也从另外一方面保证了用户帐号的安全。

9.2 flask 中 session 的工做机制

flaks 中 session 的工做机制是:服务器将用户的敏感信息加密后存入 session 中,而后把 session 放在 cookie 中,再将 cookie 返回给浏览器,下次浏览器访问的时候将 cookie 发送给服务器,服务器再根据浏览器发送的 cookie 取出里面的 session,再从 session 中取出用户的信息。

这样作能够节省服务器的一些资源。在安全问题上,由于用户的信息先通过加密再存入 session 中的,因此仍是有必定的安全性能的。

9.3 操做 session

想要在 flask 中操做 session,咱们必须先导入 session 模块:from flask import session

当用户访问某一个 URL 的时候,相应的视图函数就会有相应的动做,咱们能够在视图函数中进行 session 的操做。实际上操做 session 的方法和操做字典是同样的。

不过对 session 进行操做以前,要先设置一个 SECRET_KEY,这个 SECRET_KEY 就是用来对用户的信息进行加密的密钥,加密后的数据再保存到 session 中。设置 SECRET_KEY 的方法有两种:

  1. 新建一个 config 文件,在 config 文件中添加 SECRET_KEY = 'xxx' 这条语句,再在主 app 文件中使用 config 文件的配置 app.config.from_object(config) 就好了。
  2. 直接在主 app 文件中:app.config['SECRET_KEY'] = 'xxx'

但实际上,SECRET_KEY 要求用 24 个字符来赋值,咱们可使用 os 模块,即 import os

  1. session 添加数据:

    session['username'] = 'myyd'
  2. session 获取数据:

    有 2 种方式,第一种和操做字典的方法同样:return session['username];第二种是用session 中的 get() 方法:session.get('username)

    推荐使用第二种方式:session.get('username'),由于当 username 不存在时,第一种方法会抛出异常,而第二种方法是返回 None。

  3. session 删除数据:

    session.pop('username')

    对于 session 的数据删除操做,咱们能够设计一个小实验,即在删除先后分别打印 session 中的数据,看看结果如何。

  4. session 清除全部数据:

    session.clear()

    一样能够设计一个和删除数据同样的小实验,来对清除操做进行验证。

  5. 源代码以下:

    # encoding:utf-8
    
     from flask import Flask,session
     import os   # 该模块用于生成随机字符串
    
     app = Flask(__name__)
     app.config['SECRET_KEY'] = os.urandom(24)   # SECRET_KEY 为随机生成的字符串
    
     @app.route('/')
     def index():
         session['username'] = 'myyd'        # 添加 session 数据
         return 'index'
    
     @app.route('/get/')
     def get():
         username2 = session.get('username')     # 获取 session 数据
         return username2
    
     @app.route('/delete/')
     def delete():
         print session.get('username')
         session.pop('username')             # 删除 session 中指定的数据
         print session.get('username')
         return 'success.'
     @app.route('/clear/')
     def clear():
         print session.get('username')
         session.clear()                     # 清除 session 中全部的数据
         print session.get('username')
         return 'success.'
    
     if __name__ == '__main__':
         app.run(debug=True)

    以上是对 session 的一些基本操做。

同时还须要注意一点的是:

若服务器重启后,且浏览器没有从新获取 session 时,咱们直接去获取浏览器的 session,会报错!
ValueError: View function did not return a response,提示视图函数没有返回值。

这是由于:python 是脚本语言,运行时从头开始解释,则当服务器重启后,SECRET_KEY 会被重置,服务器想要读取浏览器发送过来的 session 中的数据时,会使用新的 SECRET_KEY 进行解密,而新的 SECRET_KEY 并不可以对 session 中的数据进行解密,因此服务器获取失败,不能返回值。

10. GET 、POST 和 钩子函数

10.1 概述

GET 和 POST 的区别:

  1. 使用场景

    GET 请求:单纯从服务器获取资源,没有对服务器产生影响,即没有对服务器的数据进行修改。

    POST 请求:从服务器获取资源,而且传给服务器一些须要处理的数据。服务器对浏览器传过来的数据进行相关逻辑判断或者处理后返回给浏览器。此时是对服务器产生了某些影响的,而不是单纯的访问浏览器某些资源。

  2. 传参

    GET 请求:请求的参数跟在 URL 后面并用 ? 号链接。

    POST 请求:请求的参数不是放在 URL 后,而是将参数放在 Form 表单中。

10.2 使用 GET

  1. 经过一个小例子来帮助理解:

    index.html 首页设置一个 <a> 标签,跳转至 search 的页面,同时在该 <a> 标签中携带参数 q=hello,在服务器端获取浏览器请求的参数并返回给浏览器。

    先建立一个首页 index.html 并在主 app 程序中用 render_template 返回首页:

    from flask import render_template   # 导入该特性
    
     return render_template('index.html')    # 在视图函数 index 中返回首页

    同时在首页用 <a> 标签跳转到 search 页面,并携带参数传给服务器的 search() 视图函数。

    <a href="{{ url_for('search',q=hello) }}">点击查询</a>

    而后在主 app 程序的 search 视图函数中,用 request 对象获取用户提交的值,不过要先从 flask 中导入该特性

    from flask import request   # 导入
    
     return 'the parameter you have submit is:%s' % request.args.get('q')    # 获取并返回
  2. 注意这个 request 对象:

    request 能够获取用户提交的请求参数,包括 GET 和 POST 提交的参数信息。reques 对象实际上一个字典,其获取的数据是以字典的格式保存下来的。以下所示:

    # 若是提交的是这样的数据
     request.args = {
         'q':'hello'
         'a':'world'
     }
     # 那么打印出来是下面这个样子的
     print requesr.args
     ImmutableMultiDict([('q', u'hello'), ('a', u'world')])

    因此咱们能够向字典同样经过 request.args.get('q') 来获取用户提交的参数。

  3. 参考代码以下:

    # 主 app 文件
     from flask import Flask,request,render_template
    
     @app.route('/')
     def index():
         return render_template('index.html')
    
     @app.route('/search/')
     def search():
         return u'你要查询的参数是:%s' % request.args.get('q')
    
     # index.html 文件
    
     <a href="{{ url_for('search',q='hello') }}">点击查询</a>

10.3 使用 POST

POST 请求能够借助模拟登陆的过程来作一个小实验:在 login.html 中建立一个表单,该表单要指定后台处理的视图函数,同时还须要指定提交请求的方式为 POST。还要在主 app 程序的视图函数中显式地标注该视图函数支持 POST 和 GET。

  1. login.html 中建立表单:

    建立代码略,后面一块儿给出。

    若是仅仅作了 render_template 的映射和 url_for 的反转,还不够,由于视图函数 login() 还未支持 POST 请求方式,而默认的视图函数只能支持 GET 请求。因此还须要进行以下配置:

    @app.route('/login/',method=['GET','POST'])

    须要注意的是:

    1. 不能只写 POST,由于只写 POST 则该视图函数只支持 POST;
    2. 而咱们去请求 login 网页的时候用的是 GET,向 login 提交数据的时候才用 POST,因此 login() 必须同时支持两种方法
  2. 咱们能够在 login() 这个视图函数中根据用户的请求方式来判断用户是否登陆,从而返回不一样的内容。

    由于用户请求 login 这个页面的时候是用 GET,而在 login 页面中进行登陆所提交的请求是 POST。获取用户请求提交的方法,也是用 request 对象:request.methods

    这里要注意,咱们获取 POST 请求的参数时,虽然也是用 request 对象,可是这里应该用 request.form.get('username'),并且在 login.html 的表单中,须要为 <input> 标签指定名字这样才能够在后台经过 name 获取到内容。因此 login() 视图函数应该这么写:

    @app.route('/login/,methods=['GET','POST'])
     def login():
         if request.method == 'GET':
             return render_template('login.html')
         else:
             return u'你好,%s' % request.form.get('username')
  3. 源代码:

    1. 主 app 文件:
    
     # encoding:utf-8
     from flask import Flask,render_template,request
     app = Flask(__name__)
    
     @app.route('/')
     def index():
         return render_template('index.html')
    
     @app.route('/search/')
     def search():
         return u'你要查询的参数是:%s' % request.args.get('q')
    
     @app.route('/login/',methods=['GET','POST'])
     def login():
         if request.method == 'GET':
             return render_template('login.html')
         else:
             return u'你好,%s' % request.form.get('username')
    
     if __name__ == '__main__':
         app.run(debug=True)
    
     2. index.html 文件
    
     <!DOCTYPE html>
     <html lang="en">
     <head>
         <meta charset="UTF-8">
         <title>index</title>
     </head>
     <body>
         <a href="{{ url_for('search',q='hello') }}">点击查询</a>
         <a href="{{ url_for('login') }}">点击登陆</a>
     </body>
     </html>
    
     3. login.html 文件
    
     <!DOCTYPE html>
     <html lang="en">
     <head>
         <meta charset="UTF-8">
         <title>login</title>
     </head>
     <body>
     <form action="{{ url_for('login') }}" method="post">
         <table>
             <tbody>
                 <tr>
                     <td>用户名:</td>
                     <td><input type="text" placeholder="请输入用户名" name="username"></td>
                 </tr>
                 <tr>
                     <td>密码:</td>
                     <td><input type="password" placeholder="请输入密码" name="password"></td>
                 </tr>
                 <tr>
                     <td></td>
                     <td><input type="submit" value="登陆"></td>
                 </tr>
             </tbody>
         </table>
     </form>
     </body>
     </html>

10.4 总结 GET 和 POST

  1. 获取一切和请求相关的信息,使用 request 对象。如:

    获取请求方法:request.method
     获取GET的参数:request.args
     获取POST的参数:request.form
  2. 对于须要支持 POST 请求的视图函数,要在其装饰器上显式指定:

    @app.route('/login/',methods=['GET','POST'])

    而且要在模板里 <input> 标签中,用 name 来标识 valuekey,方便后台获取。同时在写表单 form 的时候要指定 action 为用来处理的视图函数名。

10.5 钩子函数

钩子函数能够插入到执行过程之间,即在正常的程序执行过程当中插入钩子函数的执行。举歌例子,原来是执行了 A 函数后就接着执行 B 函数,即执行过程为 A->B;可是因为一个钩子函数 C 的介入,那么执行过程改变为 A->C->B。这里介绍 2 个钩子函数 before_requestcontext_processor

10.5.1 before_request

before_request 顾名思义,就是在 request 执行以前执行,实际上就是在视图函数执行以前执行的。不过 before_requestroute 同样,只是一个装饰器,它的做用就是把须要的在视图函数执行以前执行的动做放入一个函数并对该函数实现'钩子'的功能。

在执行每一个视图函数以前都先执行 before_request 定义的函数。

  1. 经过一个小实验,就能知道 before_request 的功能了:

    from flask import Flask
    
     app = Flask(__name__)
    
     @app.route('/')
     def index():
         print 'index'
         return 'index'
    
     @app.before_request
     def My_before_request():
         print 'hello,world'
    
     if __name__ == '__main__':
         app.run(debug=True)

    能够在 pycharm 的控制台中看到:先打印了 hello,world,才打印的 index;说明了 My_before_request() 是在 index 以前执行的。

  2. 这边扩展一下小案例。

    需求:网站中有一个look页面须要登陆以后才能访问,利用 before_request 来实现对用户判断用户是否登陆。

    思路:

    1. 定义一个首页 index.html,一个登陆页面 login.html 和一个查看页面 look 以及他们对应的视图函数。

    2. index 视图函数中返回 index.html 页面,并在 index.html 中定义两个 <a> 标签用以分别跳转至 login.htmllook.html

      <a href="{{ url_for('login') }}">点击登陆</a>
       <a href="{{ url_for('look') }}">点击查看</a>
    3. login 视图函数中对请求的方法作判断:

      GET 方法表明用户须要获取到这个页面,则返回 login.html 页面让用户登陆;

      POST 方法表明用户提交了登陆的信息,则判断用户是否合法;若合法则发送 session,若非法则提示非法。

      @app.route('/login/',methods=['GET','POST'])
       def login():
           if request.method == 'GET':
               return render_template('login.html')
           else:
               username = request.form.get('username')
               password = request.form.get('password')
               if username == 'MYYD' and password == '123456':
                   session['username'] = username
                   return u'登陆成功!'
               else:
                   return u'用户名或密码错误!'
    4. before_request 中定义 My_before_request,而后取出 session 的信息放入对象 g 中。

      @app.before_request
       def My_before_request():
           if session.get('username'):
               g.username = session.get('username')
    5. look 是视图函数中,判断根据对象 g 提供的信息判断该用户是否已经登陆,若登陆则返回 look.html 页面;若没登陆则重定向至 login.html 页面。

      @app.route('/look/')
       def look():
           if hasattr(g,'username'):
               return u'修改为功!'
           else:
               return redirect(url_for('login'))
  3. 完整代码以下:

    # hook.py
    
     from flask import Flask,render_template,request,session,redirect,url_for,g
     import os
    
     app = Flask(__name__)
     app.config['SECRET_KEY'] = os.urandom(24)
    
     @app.route('/')
     def index():
         return render_template('index.html')
    
     @app.route('/login/',methods=['GET','POST'])
     def login():
         if request.method == 'GET':
             return render_template('login.html')
         else:
             username = request.form.get('username')
             password = request.form.get('password')
             if username == 'MYYD' and password == '123456':
                 session['username'] = username
                 return u'登陆成功!'
             else:
                 return u'用户名或密码错误!'
    
     @app.route('/look/')
     def look():
         if hasattr(g,'username'):
             return u'修改为功!'
         else:
             return redirect(url_for('login'))
    
     @app.before_request
     def My_before_request():
         if session.get('username'):
             g.username = session.get('username')
    
     if __name__ == '__main__':
         app.run(debug=True)
    
    
     # index.html
    
     <!DOCTYPE html>
     <html lang="en">
     <head>
         <meta charset="UTF-8">
         <title>Index</title>
     </head>
     <body>
         <a href="{{ url_for('login') }}">点击登陆</a>
         <a href="{{ url_for('look') }}">点击查看</a>
     </body>
     </html>
    
    
     # login.html
    
     <!DOCTYPE html>
     <html lang="en">
     <head>
         <meta charset="UTF-8">
         <title>Login</title>
     </head>
     <body>
     <form action="" method="post">
         <table>
             <tbody>
                 <tr>
                     <td>用户名:</td>
                     <td><input type="text" name="username"></td>
                 </tr>
                 <tr>
                     <td>密码:</td>
                     <td><input type="password" name="password"></td>
                 </tr>
                 <tr>
                     <td></td>
                     <td><input type="submit" value="登陆"></td>
                 </tr>
             </tbody>
         </table>
     </form>
     </body>
     </html>
10.5.2 context_processor

11. 实战练习-蚂蚁有毒问答平台

  1. 在首页的模板 index.html 中,先粘贴如下内容:

    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
         <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
         <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
  2. v3.bootcss.com 中的组件下找本身想要的导航条

相关文章
相关标签/搜索