对Flask感兴趣的,能够看下这个视频教程:http://study.163.com/course/courseLearn.htm?courseId=1004091002css
# 从 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()
使用 debug 模式有不少好处:html
- 将报错信息显示到浏览器上,而不须要进入编辑器中查看报错信息,方便开发者查看错误
- 当检测到程序代码(.py文件)发生改动,程序会自动加载而不须要手动重启服务器
对 flask 程序使用 debug 模式有 2 种方式,以下:前端
在 app.run()
中直接传入一个关键字参数: app.run(debug=True)
。python
在相同目录下,新建一个 python
文件,建议命名为 config,并在里面指定该程序配置了 DEBUG
模式,即 `config.py 文件的内容以下:mysql
config.py # encoding:utf-8 DEBUG = True # SECRET_KEY # SQLALCHEMY_DB # 数据库的一些参数配置
而后在主 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()
config.py
文件的用处很是大,须要掌握这种配置方法,在后期的 SECRET_KEY
和 SQLALCHEMY_DB
(与数据库有关)都须要在这个文件中作配置.web
如:
http://localhost:8000/article/abc
和http://localhost:8000/article/def
中,两条 URL 的参数不一样,咱们能够获取这个参数并渲染后返回客户浏览器sql
如何在 flask 中使用参数?代码以下数据库
@app.route('/article/<id>') def article(id): return u'<h1>你请求的参数是:%s<h1>' % id
- 参数须要放置在两个尖括号中
- 视图函数中须要放和 URL 参数同名的参数
正转指的是:在获取到用户输入的 URL 后将该 URL 映射到对应的视图函数中,让对应的视图函数去处理该用户的请求;
反转指的是:与正转相反,经过视图函数来查找对应的 URL。
反转的做用是:1. 在页面重定向的时候会使用 URL 反转;2. 在模板中会使用 URL 反转编程
实现反转的方法:
url_for
模块用 url_for('FunctionName')
反转
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()
做用:在用户访问某些须要登陆的页面时,若是用户没登陆,则可让他重定向到登陆页面
实现:
import redirect,url_for redirect(url_for('login'))
模板实际上就是一些被编写好的具备必定格式的 html
文件。
在 pycharm 左侧一栏,项目下有两个文件夹: static
和 template
,分别用于存放静态文件(如css,js,img文件)和模板文件(如html),因此咱们的 html 文件应该放在 template
文件夹下。
如何在主程序中调用模板文件呢?
template
文件夹下新建一个 html 文件render_template
模块render_template('abc.html')
,注意不用写路径,flask 会自动去 template
文件夹下查找 abc.html
,但有文件夹除外代码以下:
import render_template return render_template('index.html')
在 web 项目开发的大多数状况下,咱们须要在 html 文件中从后台程序传入一些参数,而后将带有这些参数的 html 文件返回浏览器。这时候就须要在 html 文件中引用这些后台的参数,方法是 {{ Params }}
用 2 个花括号括起来,同时还要在后台程序作一些传参的动做。具体以下:
后端传参:
render_template('index',username=u'蚂蚁有毒',gender=u'男',age=18)
前端引用:
<h1>用户名:{{ username }}</h1> <h1>性别:{{ gender }}</h1> <h1>年龄:{{ age }}</h1>
可是要是参数愈来愈多,则代码会变得很复杂,可读性差,管理难度大。那么咱们能够用一个字典(DICT)来定义一组参数。以下:
user = { 'username':id, 'gender':u'男', 'age':18 } # 调用时传入一个关键字参数便可 return render_template('index.html',**user)
上面所演示的都是调用一些简单的参数,若是在更复杂的环境下,如调用类的属性呢?或者是调用字典中的字典的值呢?应该怎么作?
在主程序中能够先定义一个类并实例化:
class Person(object){ name = u'蚂蚁有毒' gender = u'男' age = 18 } p = Person()
再定义一个字典:
content = { 'person':p, 'websites':{ 'baidu':'www.baidu.com', 'google':'www.google.com' } }
而后传参时进行调用:
return render_template('index',**content)
最后在 index.html
中调用:
<p>姓名:{{person.name}}</p> <p>性别:{{person.gender}}</p> <p>年龄:{{person.age}}</p> <hr> <p>百度:{{websites.baidu}}</p> <p>{{websites.google}}</p>
实际上,咱们还能够在模板(html文件)中嵌入python的代码:{% code %}
,这是 jinja2
的语法,能够嵌入 if 语句和 for 语句来在 html 文件中执行相关的逻辑操做。
主程序代码:
# 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)
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>
for 循环的语法,在 html 中调用 for 语句和 if 语句的语法是同样的,都是在 {% %}
中写入 for 关键字。
咱们能够借助 html 中的 for 语句来遍历一些变量(List or Dict or Truple)。
主程序代码:
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)
html 代码:
{% for k,v in user.items() %} <p>{{ k }}:{{ v }}</p> {% endfor %} <hr> {% for website in websites %} <p>{{ website }}</p> {% endfor %}
完整代码参照上一节
题目:渲染一个 四大名著 给前端,而后前端用一个表格展现出来。
先在主程序中定义一个变量用于存放四大名著的基本信息:
books = { u'三国演义':{ 'author':u'罗贯中', 'price':109 }, u'西游记':{ 'author':u'吴承恩', 'price':120 }, u'红楼梦':{ 'author':u'曹雪芹', 'price':113 }, u'水浒传':{ 'author':u'施耐庵', 'price':135 } }
再在主程序中将其传给前端 html 文件
return render_template('index.html',books = books)
最后在前端模板中调用
<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>
过滤器能够理解为 Linux 中的管道符 |
,将获取的变量通过管道符后筛选出想要的内容。在 flask 中有不少过滤器,这里介绍 2 个比较经常使用的过滤器:default
和 length
。要注意的是,过滤器只能针对变量{{ params }}
使用。
default 过滤器的做用:若是当前变量不存在,可使用指定的默认值
对于 default 过滤器,咱们作一个实验:若是用户有头像则显示本身的头像,若是用户没有头像则显示默认头像。
在 html 文件中使用以下所示: <img src="{{ avatar | default('https://i.imgur.com/ROhBvig.png') }}"> 该行代码表示: 若是后端主程序有传递 avatar 变量过来,那么就使用 avatar 变量的值; 若是后端主程序没有传递 avatar 变量过来,那么就使用 default 过滤器指定的内容 对于本例而言,default 后面跟的图片地址应该是默认头像的地址,avatar 变量内保存的值应该是用户头像
能够统计有长度属性的变量的长度。语法与 default 过滤器同样,但不用在后面跟上指定的变量。对于 length 过滤器,咱们作一个实验:统计评论的数量并显示评论内容。
主程序代码:
comments = [ { 'user':u'蚂蚁有毒', 'content':u'我不喜欢这个东西' }, { 'user':u'杨烺', 'content':u'有同感,我也是' } ] return render_template('index.html',comments = comments)
html 模板代码:
<p> 评论数:({{ comments | length }}) </p> <hr> {% for comment in comments %} <li> {{ comment.user }}: </li> <ol> {{ comment.content }} </ol> {% endfor %}
此外还有其余不少过滤器,能够自行查找资料。
继承的概念和面向对象编程的类的继承是同样的,只不过在这里继承的对象是模板。能够建立一个经常使用模板,而且定义相关接口,能够供其余模板所使用。继承的做用是:能够把一些公共的代码放在父模板中,避免编写重复的代码。
当建立好了一个模板后,在子模板中可使用
{% extends 'base.html' %}
来继承该模板
可是若是我要在子模板中编写本身所特有的内容,应该怎么办?这时候就须要在父模板中写一个接口,来让子模板实现:
- 在父模板须要的地方定义接口的方式是:
{% block abc %}{% endblock %}
- 在子模板中一样须要写上
{% block abc %} code {% endblock %}
,而且在code
处写子模板须要的代码。- 须要注意的是,子模板必须在父模板定义的接口中写代码,否则没有做用。以下所示:
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 %}
咱们若是想要实现:点击一个按钮就能跳转到另外一个页面上。那么咱们就要使用连接跳转技术,在 flask 中,能够经过 <a href={{ url_for('视图函数名') }}>哈哈</a>
来实现
css,js,img 这 3 个文件都属于静态文件,都要放在项目的 static
文件夹下。若是咱们要对模板中的一些内容进行渲染,如:对 <a>
标签的内容进行渲染,那么咱们能够将本来写在 <head>
标签中的 <style>
标签的内容放到 css
文件中,再在模板中使用 url_for()
将 css
文件的渲染方式连接进来。步骤以下:
1. 加载 css 文件:
<link>
标签
- 在
static
文件夹下建立一个新的文件夹css
,再在css
文件夹下建立一个 css 文件,可随意命名(好记就行),如:head.css
。在使用了
<a></a>
标签的模板中,若想引用 css 文件的内容,能够用url_for()
连接进来,但格式与连接网址略有不一样。正确连接静态文件的方式为:在模板 <head> 标签范文内: <link rel=''stylesheet href='{{ url_for('static',filename='css/head.css') }}'> 注意!文件路径
2. 加载 img 文件
<img>
标签
- 第一步和加载 css 文件的第一步同样,建立文件夹和文件:
/static/img/index.jpg
第二步在模板中须要插入图片的地方使用以下代码:
<img src='{{ url_for('static',filename='img/index.jpg') }}' alt=''>
3. 加载 js 文件
<script>
标签
- 第一步和加载 css 文件的第一步同样,建立文件夹和文件:
static/js/index.js
第二步在模板中须要插入脚本的地方使用以下代码:
<script src='{{ url_for('static',filename='js/index.js') }}'>
通常静态文件也就这三样,使用方法如上所示。
安装 MySQL
数据库咱们使用的是 MySQL
,下载地址:https://dev.mysql.com/downloads/mysql/
,下载社区版就好了。注意要下载安装包(.msi)而不是压缩包(.zip)
若是在安装过程当中提示咱们要安装 windows 插件,那就按照它提示的网址去下载,没有给网址的话就百度谷歌吧。
安装 MySQL-python
MySQL-python 是一个驱动或者说是一个插件,若是咱们想要经过 python 去操做 MySQL 就须要借助这个软件去作。在 Windows 下安装 MySQL-python 的步骤以下:进入 python 的 flask 虚拟环境并启动:
cd C:\Virtualenv\flask-env\Script\ active pip install mysql-python
www.lfd.uci.edu/~gohlke/pythonlibs/#mysql-python
这个网站上下载一个非官方的插件MySQL_python-1.2.5-cp27-none-win_amd64.whl
,来使 mysql-python 支持 Windows 。能够下载到 Virtualenv 所在盘的任意目录。安装这个插件:进入到该文件所在目录执行命令 pip install MySQL_python-1.2.5-cp27-none-win_amd64.whl
便可。
安装 Flask-SQLALchemy
安装
cd C:\Virtualenv\flask-env\Script\ active pip install flask-sqlalchemy
先建立一个数据库:
进入 MySQL 的命令行 -> 输入密码 -> create database [db_demo1(数据库名)] charset utf8;
在主 app 程序中从 flask_sqlalchemy
导入 SQLAlchemy
类并初始化
from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) db = SQLAlchemy(app)
在 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)
在主 app 文件中,添加配置文件
import config
app.config.from_objece(config)
作测试,看有没有出现问题
db.create_all()
对于一个表,咱们能够建立一个类(模型)来与之对应(一张表对应一个类),以下表:
先定义好表格是怎么样的
articel 表: create table article( id int primary key autoincrement, title varchar(100) not null, content text not null )
再建立出对应的(模型)类
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)
将全部建立的模型(类)都映射到数据库中成为一个个的表格
db.create_all()
链接数据库的总结:
- 模型(类)需继承自
db.Medel
,表格中的字段必需要用db.Column
映射数据类型:
db.Integer 表明 int, db.String(length) 表明 varchar,须要指定长度 db.Text 表明 text
其余属性:
primary_key=True 表明 主键 nullabel=True 表明 可为空,默承认以 autoincrement=True 表明 主键自增加将全部的模型(类)都在数据库中建立:
db.create_all()
对数据库的增删改查,通常都在视图函数里面写,这样在浏览器上对某个 URL 进行访问时,就会执行对应的视图函数里面的代码,从而达到操做数据库的目的。
flask 对数据库的增删改查,包括提交,都使用的是 db.session
操做。
增长
article1 = Article(title='Monday',content='something')
db.session.add(article1)
db.session.commit()
删除
article1 = Article.query.filter(Article.title=='Monday').firsr()
db.session.delete(article1)
db.session.commit()
修改
article1 = Article.query.filter(Article.title=='Monday').firsr()
article1.title = 'Sunday'
db.session.commit()
查询
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
建立 2 张表:user
和 article
,其中 article
的 author_id
引用是 user
的 id
,即 2 者是外键关系。
数据库语句建立:
建立用户表: 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` # 用外键相关联 )
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')) # 外键关系
实例化模型(类)
由于 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()
需求:查询 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
当对两个 table
的关系用了 relationship
关联外键以后,那么 article
的 author
就没必要在实例化的时候指定了,因此当你添加一篇文章,能够进行以下操做:
article1 = Article(title='111',content='222') article1.author = User.query.filter(User.username=='myyd').first().id
简介:Flask_Script
可让程序经过命令行的形式来操做 Flask
,例如:经过命令跑一个开发版本的服务器、设置数据库,定时任务等等。要使用 Flask_Script
,能够在 Flask
虚拟环境中经过 pip install flask-script
安装最新版本。
新建一个 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()
命令行调用 manage.py
文件:
在虚拟环境的命令行下,用 python manage.py command
执行 manage.py
文件下的某个程序,如:python manage.py runserver
和 python manage.py stopserver
分别会执行 manage.py
文件中的 runserver()
和 stopserver()
方法。
若是有一些关于数据库的操做,咱们能够放在一个文件中执行。如 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() 中引号内的值,调用子命令的方法就是这种格式
总结:
若是直接在主 manage.py
文件中写命令,调用时只须要执行 python manage.py command_name
便可
若是将一些命令集中在另外一个文件中,那么就须要输入一个父命令,好比 python manage.py db init
例子:两个文件的完整代码以下
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'数据库迁移完成。'
以前咱们都是将数据库的模型(类)放在主 app 文件中,可是随着项目愈来愈大,若是对于加入的新的模型,咱们还放在主 app 文件中,就会使主 app 文件愈来愈大,同时也愈来愈很差管理。因此咱们能够新建一个专门存放模型的文件。如 models.py
文件用来专门存放模型的文件。
将本来在主 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)
执行以上文件,会报错。
报错提示:ImportError: cannot import name Article
,出现此类报错,先排查路径和导入的内容是否有错,若保证没错,则极可能是出现循环引用。
报错缘由:循环引用,即 models_sep.py
引用了 models.py
中的 Article
,而 models.py
又引用了 models_sep.py
中的 db
,从而形成循环引用。
解决循环引用:
解决方法:将 db
放在另外一个文件 exts.py
中,而后 models_sep.py
和 models.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)
总结:
分开 models 的目的是:让代码更方便管理。
解决循环引用的方法:把 db 放在一个单独文件中如 exts.py
,让主 app 文件
和 models 文件
都从 exts.py
中引用。
在以上基础上,咱们将模型(类)映射到数据库中,调用 db.create_all()
进行映射。而后运行,发现报错:No application found. Either work inside a view function or push an application context.
,是由于在 db 的上下文中,没有将 app 推入到栈中。
当用户访问了服务器,则服务器会将当前 app 并加载到 app 栈中;若是用户没有访问服务器,那么即便 app 已经建立,可是没有被服务器加载到 app 栈中。若这时候运行服务器,则 app 栈中没有加载 app,当 db.init_app(app)
去 app 栈中取 app 对象的时候没有成功获取,因此报错。
缘由:
状况一:db = SQLAlchemy(app)
这句代码运行的时候,会将 app 推到app栈中;db.create_all()
就能够经过 app 栈获取到栈顶元素 app 了。db = SQLAlchemy()
时没有传入 app,因此没有将 app 推到栈中;db.init_app(app)
将 app 与 db 绑定时,会去 app 栈中取 app,可是没有获取到,因此报错。解决方法:将 app 手动推到 app 栈中:
# 将 db.create_all() 替换为如下代码 with app.app_context(): db.create_all()
这个时候若是咱们的模型(类)要根据需求添加一个做者
字段,这时候咱们须要去修改模型 Article
,修改完成咱们须要再映射一遍。可是对于 flask-sqlalchemy
而言,当数据库中存在了某个模型(类)后,再次映射不会修改该模型的字段,即再次映射不会奏效。
传统解决办法:
在数据库中删除该模型对应的表格,再将带有新字段的模型从新进行映射。
很显然,这种方式明显很简单粗暴,很是不安全,由于在企业中一个数据库中的表格是含有大量数据的,若是删除可能会形成重大损失。因此咱们须要一个能够动态修改模型字段的方法,使用 flask-migrate
。先安装:在虚拟环境下使用命令 pip install flask-migrate
便可。
flask-migrate
动态修改模型字段使用 flask-migrate
的最简单方法是:借助 flask-script
使用命令行来对 flask-migrate
进行操做。一共有好几个步骤,分别说明一下:
新建文件 manage.py
:
新建 manage.py
文件后:
导入相应的包并初始化 manager
from flask_script import Manager from migrate_demo import app from flask_migrate import Migrate,MigrateCommand from exts import db manager = Manager(app)
要使用 flask_migrate
必须绑定 app
和 db
migrate = Migrate(app,db)
把 MigrateCommand
命令添加到 manager
中
manager.add_command('db',MigrateCommand)
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()
主 app 文件不须要再对模型进行映射,因此能够将如下语句给删除:
with app.app_context(): db.create_all() # 将模型映射到数据库中
在 manage.py
文件中,导入须要映射的模型(类):
由于在主 app 文件中已经再也不须要对模型进行映射,而对模型的操做是在 manage.py
文件中进行的,包括 flask-migrate
动态映射,因此要导入须要映射的模型。
from models import Article
完成以上步骤后,便可到命令行中更新数据库中的模型了:
python manage.py db init
,初始化 flask-migrate
环境,仅在第一次执行的时候使用。python manage.py db migrate
,生成迁移文件python manage.py db upgrade
,将迁移文件映射到数据库的表格中背景知识:HTTP 协议是无状态的,每次链接断开后再从新链接,服务器是不记得浏览器的历史状态的。也就是说:即便第一次浏览器访问服务器时登录成功后,只要断开与服务器的链接,服务器就会忘记浏览器的信息,那么当浏览器与服务器再次成功链接时,服务器不知道浏览器的相关信息。
cookie
而 cookie 的出现就是为了解决这个问题:当浏览器第一次在服务器上登陆成功时,服务器会将一些数据(cookie)返回给浏览器,而后浏览器将 cookie 保存在本地,第二次访问服务器时浏览器会自动地将上次存储的 cookie 发送给服务器,服务器经过 cookie 判断浏览器的信息从而提供更好的服务(如自动记录用户上次浏览的位置等)。
服务器和浏览器交互 cookie 的过程对用户而言是透明的。
cookie 信息保存在用户的浏览器中。
session
session 的做用和 cookie 相似,都是为了保存用户的相关信息。不一样的是:cookie 是将用户的信息保存在用户的浏览器本地,而 session 是将用户的信息保存在服务器上。相对而言,session 比 cookie 更加安全和稳定,可是会占用服务器的一些资源,不过影响不大。同时,session 还支持设置过时时间,也从另外一方面保证了用户帐号的安全。
flaks 中 session 的工做机制是:服务器将用户的敏感信息加密后存入 session 中,而后把 session 放在 cookie 中,再将 cookie 返回给浏览器,下次浏览器访问的时候将 cookie 发送给服务器,服务器再根据浏览器发送的 cookie 取出里面的 session,再从 session 中取出用户的信息。
这样作能够节省服务器的一些资源。在安全问题上,由于用户的信息先通过加密再存入 session 中的,因此仍是有必定的安全性能的。
想要在 flask
中操做 session
,咱们必须先导入 session
模块:from flask import session
。
当用户访问某一个 URL
的时候,相应的视图函数就会有相应的动做,咱们能够在视图函数中进行 session
的操做。实际上操做 session
的方法和操做字典是同样的。
不过对 session
进行操做以前,要先设置一个 SECRET_KEY
,这个 SECRET_KEY
就是用来对用户的信息进行加密的密钥,加密后的数据再保存到 session
中。设置 SECRET_KEY
的方法有两种:
- 新建一个
config
文件,在config
文件中添加SECRET_KEY = 'xxx'
这条语句,再在主 app 文件中使用config
文件的配置app.config.from_object(config)
就好了。- 直接在主 app 文件中:
app.config['SECRET_KEY'] = 'xxx'
但实际上,SECRET_KEY
要求用 24 个字符来赋值,咱们可使用 os 模块,即 import os
。
session 添加数据:
session['username'] = 'myyd'
session 获取数据:
有 2 种方式,第一种和操做字典的方法同样:return session['username]
;第二种是用session
中的 get() 方法:session.get('username)
。
推荐使用第二种方式:session.get('username')
,由于当 username
不存在时,第一种方法会抛出异常,而第二种方法是返回 None。
session 删除数据:
session.pop('username')
对于 session
的数据删除操做,咱们能够设计一个小实验,即在删除先后分别打印 session
中的数据,看看结果如何。
session 清除全部数据:
session.clear()
一样能够设计一个和删除数据同样的小实验,来对清除操做进行验证。
源代码以下:
# 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 中的数据进行解密,因此服务器获取失败,不能返回值。
GET 和 POST 的区别:
使用场景
GET 请求:单纯从服务器获取资源,没有对服务器产生影响,即没有对服务器的数据进行修改。
POST 请求:从服务器获取资源,而且传给服务器一些须要处理的数据。服务器对浏览器传过来的数据进行相关逻辑判断或者处理后返回给浏览器。此时是对服务器产生了某些影响的,而不是单纯的访问浏览器某些资源。
传参
GET 请求:请求的参数跟在 URL 后面并用 ?
号链接。
POST 请求:请求的参数不是放在 URL 后,而是将参数放在 Form 表单中。
经过一个小例子来帮助理解:
在 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') # 获取并返回
注意这个 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')
来获取用户提交的参数。
参考代码以下:
# 主 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>
POST 请求能够借助模拟登陆的过程来作一个小实验:在 login.html
中建立一个表单,该表单要指定后台处理的视图函数,同时还须要指定提交请求的方式为 POST。还要在主 app 程序的视图函数中显式地标注该视图函数支持 POST 和 GET。
在 login.html
中建立表单:
建立代码略,后面一块儿给出。
若是仅仅作了 render_template
的映射和 url_for
的反转,还不够,由于视图函数 login()
还未支持 POST
请求方式,而默认的视图函数只能支持 GET
请求。因此还须要进行以下配置:
@app.route('/login/',method=['GET','POST'])
须要注意的是:
咱们能够在 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')
源代码:
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>
获取一切和请求相关的信息,使用 request
对象。如:
获取请求方法:request.method 获取GET的参数:request.args 获取POST的参数:request.form
对于须要支持 POST 请求的视图函数,要在其装饰器上显式指定:
@app.route('/login/',methods=['GET','POST'])
而且要在模板里 <input>
标签中,用 name
来标识 value
的 key
,方便后台获取。同时在写表单 form
的时候要指定 action
为用来处理的视图函数名。
钩子函数能够插入到执行过程之间,即在正常的程序执行过程当中插入钩子函数的执行。举歌例子,原来是执行了 A 函数后就接着执行 B 函数,即执行过程为 A->B
;可是因为一个钩子函数 C 的介入,那么执行过程改变为 A->C->B
。这里介绍 2 个钩子函数 before_request
和 context_processor
。
before_request
顾名思义,就是在 request
执行以前执行,实际上就是在视图函数执行以前执行的。不过 before_request
和 route
同样,只是一个装饰器,它的做用就是把须要的在视图函数执行以前执行的动做放入一个函数并对该函数实现'钩子'
的功能。
在执行每一个视图函数以前都先执行 before_request
定义的函数。
经过一个小实验,就能知道 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
以前执行的。
这边扩展一下小案例。
需求:网站中有一个look
页面须要登陆以后才能访问,利用 before_request
来实现对用户判断用户是否登陆。
思路:
定义一个首页 index.html
,一个登陆页面 login.html
和一个查看页面 look
以及他们对应的视图函数。
在 index
视图函数中返回 index.html
页面,并在 index.html
中定义两个 <a>
标签用以分别跳转至 login.html
和 look.html
。
<a href="{{ url_for('login') }}">点击登陆</a> <a href="{{ url_for('look') }}">点击查看</a>
在 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'用户名或密码错误!'
在 before_request
中定义 My_before_request
,而后取出 session
的信息放入对象 g
中。
@app.before_request def My_before_request(): if session.get('username'): g.username = session.get('username')
在 look
是视图函数中,判断根据对象 g
提供的信息判断该用户是否已经登陆,若登陆则返回 look.html
页面;若没登陆则重定向至 login.html
页面。
@app.route('/look/') def look(): if hasattr(g,'username'): return u'修改为功!' else: return redirect(url_for('login'))
完整代码以下:
# 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>
在首页的模板 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>
在 v3.bootcss.com
中的组件下找本身想要的导航条