Flask视图、模板、模型

Day01

Flask前期了解:

为什么要使用框架:

web网站发展至今,特别是服务器端,涉及到的知识、内容,非常广泛。这对程序员的要求会越来越高。如果采用成熟,稳健的框架,那么一些基础的工作,比如,网络操作、数据库访问、会话管理等都可以让框架来处理,那么程序开发人员可以把精力放在具体的业务逻辑上面。使用Web框架开发Web应用程序可以降低开发难度,提高开发效率。
总结一句话:避免重复造轮子(wheel)。

Flask框架的诞生:

Flask诞生于2010年,是Armin ronacher(人名)用Python语言基于Werkzeug工具箱编写的轻量级Web开发框架。它主要面向需求简单的小应用。

Flask本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login),都需要用第三方的扩展库来实现。比如可以用Flask-extension加入ORM、窗体验证工具,文件上传、身份验证等。Flask没有默认使用的数据库,你可以选择MySQL,也可以用NoSQL。其 WSGI 工具箱采用 Werkzeug(路由模块) ,模板引擎则使用 Jinja2 。

Python最出名的框架要数Django,此外还有Flask、Tornado等框架。虽然Flask不是最出名的框架,但是Flask应该算是最灵活的框架之一,这也是Flask受到广大开发者喜爱的原因。

基于python的web (微) 框架

重量级框架 django
	为了方便业务程序的开发,提供了丰富的工具及其组件
轻量级框架 flask
	只提供web核心功能,自由灵活,高度定制,用到什么就可以选择什么样的第三方库来实现其功能;

官方文档

http://flask.pocoo.org/docs/0.12/      英文
http://docs.jinkan.org/docs/flask/     中文

flask依赖库

flask依赖的基础的三个库
 jinja2 模板引擎(就是可以用模板语法和html标签结合生成想要的网页源代码)
 Werkzeug  WSGI (web server gateway interface)工具集:
 Werkzeug 是一个 WSGI(在 Web 应用和多种服务器之间的标准 Python 接口) 工具集
 
 Itsdangerous 基于Django的签名模块
 现在不仅仅是三个  但是依然前两个有用
 安装时候会出现6个 看看就可以了

flask流行的主要原因

 1 有非常齐全的官方文档,上手非常方便
 2 有非常好的扩展机制和第三方扩展环境,工作中常见的软件都会有对应的扩展,动手实现扩展
 也很容易
 3 社区活跃度非常高    flask的热度已经超过django好几百了
 4 微型框架的形式给了开发者更大的选择空间

MVC简介:

MVC的全拼为Model-View-Controller,最早由TrygveReenskaug在1978年提出,是施乐帕罗奥多研究中心(Xerox PARC)在20世纪80年代为程序语言Smalltalk发明的一种软件设计模式,是为了将传统的输入(input)、处理(processing)、输出(output)任务运用到图形化用户交互模型中而设计的。随着标准输入输出设备的出现,开发人员只需要将精力集中在业务逻辑的分析与实现上。后来被推荐为Oracle旗下Sun公司Java EE平台的设计模式,并且受到越来越多的使用ColdFusion和PHP的开发者的欢迎。现在虽然不再使用原来的分工方式,但是这种分工的思想被沿用下来,广泛应用于软件工程中,是一种典型并且应用广泛的软件架构模式。后来,MVC的思想被应用在了Web开发方面,被称为Web MVC框架。

MVC框架的核心思想是:解耦,让不同的代码块之间降低耦合,增强代码的可扩展性和可移植性,实现向后兼容。

当前主流的开发语言如Java、PHP、Python中都有MVC框架。

Web MVC各部分的功能
M:全拼为Model,主要封装对数据库层的访问,对数据库中的数据进行增、删、改、查操作。

V:全拼为View,用于封装结果,生成页面展示的html内容。

C:全拼为Controller,用于接收请求,处理业务逻辑,与Model和View交互,返回结果。

MVC的处理逻辑图:

概括性总结MVC:

一种软件设计架构风范
Model
	数据的封装,数据的抽象,用来操作数据的入口
View
	视图,主要用来呈现给用户的
Controller
	控制器,主要用来接收用户请求(输入),并且协调模型和视图
	用来做视图和模型之间的数据的交互
核心理念
	解耦
实现结果
	将数据操作,页面展示,逻辑处理进行了拆分

MTV也叫做MVT

Models
	封装数据操作
	数据库的表和字段的定义
Template
	模板
	用来展示数据
Views
	视图函数
	相当于controller
	接收请求,协调模型和模板

MTV的处理逻辑图:

Flask基本准备使用前的工作:

创建Flask的虚拟环境:

在开发中每个项目都有自己的独立环境,所以需要virtualenv创建环境
一般公司的开发都在ubuntu下,所以安装如下:
1. $ sudo apt install virtualenv
sudo apt install python-pip
注意:如果没有apt 那么解决办法 sudo apt update 更新源中的软件包
  包管理工具
       apt update 更新源中的软件包
       apt install xxx  安装指定的软件xxx
       apt remove xxx 卸载软件(仅仅卸载软件)
       apt autoremove xxx 卸载软件(会卸载软件和没有用的依赖包)
接着下载virtualenv的管理工具:
2. sudo pip install virtualenvwrapper  注意有丢包的问题
virtualenvwrapper.sh安装路径
       /usr/local/bin/
编辑bashrc文件
3. sudo vim ~/.bashrc 
4.打开之后,进行下面编辑:
#python
export WORKON_HOME = /home/xxx(用户名字)/.virtualenvs
5 注意需要创建.virtualenv的文件夹  
mkdir $HOME/.virtualenvs 用来存放虚拟环境
source xxx(virtualenvwrapper.sh安装路径) virtualenvwrapper.sh 激活路径
例如:
#python
export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 就是每次创建的虚拟环境都是依靠python3的
export WORKON_HOME=$HOME/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh

6 激活创建后后的环境变量 
source .bashrc
7 创建3.x的虚拟环境
mkvirtualenv xxx(虚拟环境的名字) -p /usr/bin/python3
退出虚拟环境 
       deactivate
进入虚拟环境 
       workon ENV_NAME(虚拟环境的名字)
查询路径
whereis python3

对于pip管理python包的操作:
pip install xxx 安装某一个软件
       pip uninstall xxx 卸载某一个软件
       pip list 列出所有的依赖包
       pip freeze 列出自己安装的所有的依赖包

web网站的工作方式(http的请求方式):

Flask开始正文:

创建flask的虚拟环境
	mkvirtualenv Flaskpython1901 -p /usr/bin/python3
查看虚拟环境
	pip freeze
	pip list

虚拟环境迁移
	pip freeze > requirements.txt
		迁出
	pip install -r requirements.txt
		迁入
安装
	pip install flask
创建项目
	mkdir python1901 mkdir Flaskday01  mkdir FirstFlask  vim HelloFlask.py
	代码结构
                from flask import Flask

                # 创建flask应用的对象,__name__如果以当前文件作为启动文件那么__name__值是__main__,如果不是启动文件那么它值的就是当前模块的名字。

                app = Flask(__name__)

                @app.route("/index/")
                def index():
                    return "helloflask"
                if __name__ == "__main__":
                    app.run()
    启动服务器  python  文件名字.py
	默认端口号  5000  只允许本机链接

如果想修改端口号那么可以在run方法中添加参数
	在启动的时候可以添加参数  在run()中
	debug
		是否开启调试模式,开启后修改过python代码自动重启
		如果修改的是html/js/css 那么不会自动重启
	host
		主机,默认是127.0.0.1 指定为0.0.0.0代表本机ip
	port
		指定服务器端口号
	threaded
		是否开启多线程
# 修改服务器端口号  可以在run方法中添加port属性
    # 注意 注意port的数据类型 可以是整型  可以是字符串
    # app.run(port=8888)
    # 注意 默认情况下只允许本机访问  可以修改run方法中的host属性值为0.0.0.0
    # app.run(port='8888',host='0.0.0.0')
    # 默认情况下 修改python代码必须要重启服务器
    # 如果不想重启服务器  那么需要加一个debug参数
    # 注意的是  修改python代码 可以通过保存重启服务器 那么如果是html css  js等前端
    # 代码 那么就必须重启服务器
    # app.run(port='8888',debug=True,threaded=True)

PIN码
	全称Personal Identification Number.就是SIM卡的个人识别密码。手机的PIN码是保护SIM卡的一种安全措施,防止别人盗用SIM卡,如果启用了开机PIN码,那么每次开机后就要输入4到8位数PIN码。
在输入三次PIN码错误时,手机便会自动锁卡,并提示输入PUK码解锁,需要使用服务密码拨打运营商客服热线,客服会告知初始的PUK码,输入PUK码之后就会解锁PIN码。

Flask的脚本命令行参数:

命令行参数:作用就是可以在启动程序的时候进行修改主机ip和端口号等等。
	1.安装
		pip install flask-script
		如果安装的慢可以用豆瓣源进行安装:
 pip install flask-script -i https://pypi.douban.com/simple
		作用
			启动命令行参数可以在脚本中添加自己喜欢的参数;
	2.初始化
		修改  app.py文件为manager.py
	3.将app对象交给manager来管理(和Django很相似)
	注意:Ctrl + Alt + Space  快速导入任意类 或者 倒包的快捷键  ctrl + space/alter + enter
		manager = Manager(app=app)
		修改 文件.run()为manager.run()
	4.运行
		python manager.py runserver -p xxx -h xxxx -d -r
	参数注意:不可以大写。
    - p  端口 port
    - h  主机  host
    - d  调试模式  debug
    - r   重启(重新加载) reload(restart)
    
    基本使用:
	index返回字符串
		@app.route('/index/')
        def index():
            return 'index'
	模板first.html
		@app.route('/first/')
        def hello():
            return render_template("test.html")返回值的类型类也是字符串;
	静态文件css
	     注意
	     <link rel="stylesheet" href="/static/css/hello.css">
	     
 对于开始启动项目会出现以下错误状态码:
      404 路径错误
      405 请求方式错误
      500 服务器错误
      302 重定向
      301 永久重定向

Flask的简单运行过程:

所有Flask程序必须有一个程序实例。

Flask调用视图函数后,会将视图函数的返回值作为响应的内容,返回给客户端。一般情况下,响应内容主要是字符串和状态码。

当客户端想要获取资源时,一般会通过浏览器发起HTTP请求。此时,Web服务器使用WSGI(Web Server Gateway Interface)协议,把来自客户端的所有请求都交给Flask程序实例。WSGI是为 Python 语言定义的Web服务器和Web应用程序之间的一种简单而通用的接口,它封装了接受HTTP请求、解析HTTP请求、发送HTTP,响应等等的这些底层的代码和操作,使开发者可以高效的编写Web应用。

程序实例使用Werkzeug来做路由分发(URL请求和视图函数之间的对应关系)。根据每个URL请求,找到具体的视图函数。 在Flask程序中,路由的实现一般是通过程序实例的route装饰器实现。route装饰器内部会调用add_url_route()方法实现路由注册。

调用视图函数,获取响应数据后,把数据传入HTML模板文件中,模板引擎负责渲染响应数据,然后由Flask返回响应数据给浏览器,最后浏览器处理返回的结果显示给客户端。

Flask基础结构

项目
   App
        templates
            模板
            默认也需要和项目保持一致
        static
            静态资源
            默认需要和我们的项目保持一致,在一个路径中,指的Flask对象创建的路径
        views
        models
        坑点一:起初视图函数是在manager.py文件中,如果把视图函数改到views.py文件中那么再执行过程中manager.py和其他的文件的惠出现找不到路径的问题。出现如下:
Not Found
The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again
后台回报出404错误;
        第二个坑--封装__init__文件--
在注册蓝图之前,将app对象的初始化在__init__.py文件中,从而进行封装。
from flask import Flask
def create_app():
    app = Flask(__name__)
    return app
之后在manager.py输入:
from App import create_app
app = create_app()

路由分发机制:

在flask中路由分发
1、如果路由名字后面没有写/   那么请求的路径后面就不可以写/
2、如果路由名字后面写了/  那么请求的路径后面怎么写都可以
# 所以在route()中路由名字一般情况下 /路由名字/
@app.route('/index/')
#    路由的名字和视图函数的名字 一般情况下一致,这个是开发的经验;
def index():
    return 'index'

对于解决路由路径:

1、在注册之前在__init__.py中进行app对象的初始化;
from flask import Flask
def create_app():
    app = Flask(__name__)
    return app
之后在manager.py输入:
from App import create_app
app = create_app()
2、蓝图
	1. 宏伟蓝图(宏观规划)
	2. 蓝图也是一种规划,主要用来规划urls(路由)
	3. 蓝图基本使用
  	- 安装
     - pip install flask-blueprint
     - 初始化蓝图   blue = Blueprint('first',__name__)需要在view视图函数中进行初始化;
     - 调用蓝图进行路由注册  app.register_blueprint(blueprint=blue)
 
 代码:
views文件中:
from flask import Blueprint
# 初始化蓝图
blue = Blueprint('first',__name__)
@blue.route("/")
def hello_world():
    return "Hello"

manager文件中:
from App.views import blue
# 注册蓝图
app.register_blueprint(blueprint=blue)

视图函数:

# 使用蓝图的坑   蓝图对象的创建和使用必须都要在views里
@app.route('/helloint/')
def helloint():
    # 视图函数的返回值类型 必须是一个字符串 元祖  response实例对象 不能是一个整数
    # return 1
    return 'this is return value'

# 执行一个视图函数 返回的是一个页面
@app.route('/hellotemplate/')
def hellotemplate():
    # 跳转到一个页面
    # return render_template('hellotemplate.html')
#     从数据库中将查询到的数据 方到页面中
    name = '本夕'
    return render_template('hellotemplate.html',name=name)
一般括号中的变量的命名与函数中变量是同名,开发经验而已,然而在模板中接收的键(也就是括号中的变量参数)

引用模板文件:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
{# link链接最低需要 rel、href属性   script链接最低需要 type、src属性#}
    <link rel="stylesheet" href="/static/css/hellotemplate.css">
</head>
<body>
{#    ul>li*5   按table键#}

    <ul>
        <li>许嵩</li>
        <li>汪苏泷</li>
        <li>徐良</li>
        <li>星弟</li>
        <li>小贱</li>
    </ul>
<!--老板说了  这需要睡3秒 如果尾款收到了那么就将此行代码删除-->
{# 修改了页面中的html  css  js ...需要重启服务器#}
    <h3>{{ name }}</h3>
</body>
</html>

浏览器带参数请求:

带参数的请求
	从客户端或者浏览器发过来的请求带参数
	      @blue.route('/getstudents/<id>/')
          def getstudents(id):
              return '学生%s'+id
	路由中包含路径参数
		语法
			<converter:var_name>
				书写的converter可以省略,默认类型就是string
		converter
			string
				接收的时候也是str, 匹配到 / 的时候是匹配结束
				@blue.route('/getperson/<string:name>/')
                def getperson(name):
                    print(name)
                    print(type(name))
                    return name
			path
				接收的时候也是str, / 只会当作字符串中的一个字符处理,并且返回getperson1后面的所有string类型的参数;
				@blue.route('/getperson1/<path:name>/')
                  def getperson1(name):
                      print(name)
                      print(type(name))
                      return name
			int
			     @blue.route('/makemoney/<int:money>/')
                  def makemoney(money):
                      print(type(money))
                      return '1'
			float参数写的时候必须是一个float数;
				@blue.route('/makemoneyfloat/<float:money>/')
                  def makemoney(money):
                      print(type(money))
                      return '1'
			uuid
				uuid 类型,一种格式
				@blue.route(('/getuu/'))
                  def getuu():
                      uu = uuid.uuid4()
                      print(uu)
                      return str(uu)

                  ------------------------------------
                  @blue.route('/getuuid/<uuid:uuid>/')
                  def getuuid(uuid):
                      print(uuid)
                      print(type(uuid))
                      return '2'
			any
				任意一个
				已提供选项的任意一个 而不能写参数外的内容  注意的是参数必须是字符串  如果不加双引号,必须是字符,不可以写整数 ,注意/必须写
				@blue.route('/getany/<any(a,b):p>/')
                def getany(p):
                    return '1'

请求方式
	postman
		一种模拟请求工具
			方法参数中添加methods=['GET','POST']
	安装
		https://blog.csdn.net/Shyllin/article/details/80257755
	1. 默认支持GET,HEAD,OPTIONS
	2. 如果想支持某一请求方式,需要自己手动指定
	3. 在route方法中,使用methods=["GET","POST","PUT","DELETE"]应该都是大写,因为在前后端分离中要求大小写的区别;

简易的注册跳转页面:

# 视图函数的设置
# 执行一个视图函数 然后跳转到注册页面
# 输入用户名字 密码 然后点击注册  执行一个请求  然后输出注册成功
@blue.route('/toregister/')
def toregister():
    return  render_template('register.html')
# 默认flask中允许使用get options head请求方式 不允许使用post请求方式
# methods允许访问的请求方式
# 如果定义了methods 那么默认的请求方式将失效
# mothods常用的有5中 post get patch put delete
@blue.route('/register/',methods=['POST','Get'])
def register():
    return '注册成功'

#模板设置
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{#get 请求 参数是显示到浏览器的地址路径上的 不安全
 post 请求 参数不会显示
  get请求参数的数据量 相对于比较 大概2k左右
  post 参数的数据量  相对于比较大
  表单默认请求方式是get
#}

<form action="/register/" method="get">
    name:<input type="text" name="name" placeholder="请输入用户名字"><br>
    password:<input type="password" name="password" placeholder="请输入密码"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

反向解析:

反向解析,常用在模板中:
	获取请求资源路径
	 url_for(蓝图的名字.方法名字)
	 @blue.route("/heheheheheheheheheehhehe/", methods=["GET","POST","PUT"])
      def hehe():
          return "呵呵哒"

      @blue.route("/gethehe/")
      def get_hehe():
          p = url_for("first.hehe")
          return p
应用:
views文件中
# 反向解析的应用
# 执行一个路由 跳转到登录页面  然后点击登录 返回欢迎光临
@blue.route('/tologin/')
def tologin():
    return render_template('login.html')
@blue.route('/login/',methods=['get','post'])
def login():
    return '欢迎光临红浪漫'

模板:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="{{ url_for('blue.login') }}" method="post">
        name:<input type="text" name="name" placeholder="please input your name"><br>
        pass:<input type="password" name="password" placeholder="please input your password"><br>
        <button>提交</button>
    </form>
</body>
</html>

request

request是一个Python中请求的内置对象
内置对象:不需要创建就可以直接使用的对象
属性
	1.method  **
		请求方法
	2.base_url
		去掉get参数的url
	3.host_url
		只有主机和端口号的url
	4.url
		完整的请求地址
	5.remote_addr **
		请求的客户端地址
	6.args  **
         - get请求参数的包装,args是一个ImmutableMultiDict对象,类字典结构对象
         - 数据存储也是key-value
         - 最外层是元组,次外层是大列表,列表中的元素是元组,元组中左边是key,右边是              value
         如:ImmutableMultiDict([('name', 'zhanshang'), ('name', 'wangwu')])
	7.form   **
         - 存储结构和args一致
         - 默认是接收post参数
         - 还可以接收 PUT,PATCH参数
	8.files   **   
	form标签中有一个参数 enctype=maltipart/form-data  请求方式必须是post  使用files接     收文件上传
	9.headers	
		请求头
	10.path
		路由中的路径
	11.cookies
		请求中的cookie
	12.session	
		与request类似,也是一个内置对象,可以直接打印 print(session)

代码:

# request对象的属性
@blue.route('/testrequest/',methods=['get','post'])
def testrequest():
    # 获取请求的方式
    print(request.method)

    print(request.base_url)
    print(request.host_url)
    print(request.url)
    print(request.path)
    # 反扒
    print(request.remote_addr)
    # request.args.get方法的作用是获取get请求方式的参数
    # 如果参数中 有2个或者2个以上的参数名字一致那么只是返回第一个符合条件的参数值
    # 如果想获取所有的参数值 那么调用的方法是getlist  getlist方法的返回值类型是列表
    print(request.args.get('name'))
    print(request.args.getlist('name'))
    # post请求方式  如何获取请求参数
    print(request.form.get('name'))
    print(request.form.get('age'))
    print(request.headers)
    return 'testrequest'



@blue.route('/upload/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/uploaded_file.txt')
        
如果你想知道上传前文件在客户端的文件名是什么,你可以访问 filename 属性。但请记住, 永远不要信任这个值,这个值是可以伪造的。如果你要把文件按客户端提供的文件名存储在服务器上,那么请把它传递给 Werkzeug 提供的 secure_filename() 函数:    
from flask import request
from werkzeug import secure_filename
@blue.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/' + secure_filename(f.filename))

Response

对于视图函数Response的返回类型,有如下几种方式:
字符串、元组、response。
1、返回的值为字符串类型:
		如果只是单一的返回字符串,就是返回内容或者数据,在return返回值中还可以有第二个返回,就是放的是状态码
		@blue.route('/response/')
         def get_response():
               return '德玛西亚',404
	    render_template 函数渲染模板将模板变成字符串,返回给浏览器;	
		@blue.route('/rendertemplate/')
        def render_temp():
              resp = render_template('Response.html')
              print(resp)
              print(type(resp))
              return rese,500

2、返回response类型:
	     make_response函数
		Response对象
		返回内容
		状态码
		@blue.route('/makeresponse/')
		def  make_resp():
              resp = make_response('<h2>xxxxxxxx</h2>',502)
              print(resp)
              print(type(resp))
              return rese
              
	    redirect函数
		重定向
		@blue.route('/redirect/')
        def  make_redir():
             return redirect('/makeresponse/')
		重定向结合反向解析 url_for
		@blue.route('/redirect/')
		def  make_redir():
       		return redirect(url_for('first.make_resp'))
	   Response()函数

代码:

# response
1、返回字符串类型:
# return 返回的是字符串
@blue.route('/testresponse/')
def testresponse():
    return 'testresponse',500

# render_template方法返回值类型也是一个字符串
@blue.route('/testresponse1/')
def testresponse1():
    res = render_template('index.html')
    print(res)
    print(type(res))
    return res

2、返回response类型:
# return后面也可以返回一个response类型的对象
# make_response方法返回的是response类型的对象
@blue.route('/testresponse2/')
def testresponse2():
    res = make_response('<h1>哈哈</h1>')
    print(type(res))
    return res

@blue.route('/testresponse3/')
def testresponse3():
    # redirect
    # 重定向的方法的返回值类型是response对象
    res = redirect(url_for('blue.testresponse2'))
    print(type(res))
    return res

@blue.route('/testresponse4/')
def testresponse4():
    res = Response('呵呵')
    print(type(res))
    return res

异常

abort抛出异常:
	直接抛出 显示错误状态码  终止程序运行
	abort(404)
	eg:
		@blue.route('/makeabort/')
         def make_abort():
              abort(404/502)
              return '天还行'

捕获异常:
	@blue.errorhandler()
		- 异常捕获
		- 可以根据状态或 Exception进行捕获
		- 函数中要包含一个参数,参数用来接收异常信息
	eg:
	@blue.errorhandler(502)
    def handler502(exception):
        return '不能让你看到状态码'

代码:
# 异常
@blue.route('/testabort/')
def testabort():
    abort(404)
    return 'testabort'

# errorhandler参数必须是抛出的状态码  如果没有抛出将不会执行
@blue.errorhandler(404)
# 注意函数必须有一个参数 该参数是exception
def testhandler(exception):
    return '没问题'

会话技术

1.请求过程Request开始,到Response结束
2.连接都是短连接
3.延长交互的生命周期
4.将关键数据记录下来
5.Cookie是保存在浏览器端/客户端的状态管理技术
6.Session是服务器端的状态管理技术

会话机制流程图:

cookie

Cookie
	1.客户端会话技术
	2.所有数据存储在客户端
	3.以key-value进行数据存储层
	4.服务器不做任何存储
	5.特性
		支持过期时间
			max_age
			expries
		根据域名进行cookie存储
		不能跨网站(域名)
		不能跨浏览器
		自动携带本网站的所有cookie
	6.cookie是服务器操作客户端的数据
	7.通过Response进行操作


cookie登陆使用
	设置cookie    response.set_cookie('username',username)
		response
	获取cookie    username = request.cookies.get('username','游客')
		request
	删除cookie     response.delete_cookie('username')
		response


代码:
# @blue.route('/tologincookie/')
# def tologincookie():
#     return render_template('logincookie.html')
#
#
# @blue.route('/logincookie/',methods=['get','post'])
# def logincookie():
#     # 获取post请求的参数的值
#     # get方法里面应该写的是标签的name属性值
#     # 标签的name属性值 是 <input type='text' name='name'>
#     # 如果后端想获取前端页面的属性值  那么get方法中就应该写 标签的name的值
#     name = request.form.get('name')
#
#     # redirect方法的返回值类型就是repsonse对象
#     response = redirect(url_for('blue.toindexcookie'))
#
#     response.set_cookie('name',name)
#
#     return response
#
# # 跳转到indexcookie.html
# @blue.route('/toindexcookie/')
# def toindexcookie():
#     return render_template('indexcookie.html')

# 先创建一个登录页面
# 在登录页面中输入用户名字
# 点击登录
# 跳转到indexcookie.html
# 该页面中 有一句话 欢迎xxx来到红浪漫洗浴
@blue.route('/tologincookie/')
def tologincookie():
    return render_template('logincookie.html')



@blue.route('/logincookie/',methods = ['get','post'])
def logincookie():
    # 获取的文本框中的内容
    name = request.form.get('name')
    # 反向解析 获取的是路由地址  redirect是执行另一个路由
    # url_for(‘blue.indexcookie’)===>/indexcookie/
    # redirect('/indexcookie/')
    response = redirect(url_for('blue.indexcookie'))
    # 将name存储到了cookie上
    response.set_cookie('name',name)
    return response

# 跳转到index页面
@blue.route('/indexcookie/')
def indexcookie():
    # 获取cookie中的name
    name = request.cookies.get('name','游客')
    return render_template('indexcookie.html',name=name)


@blue.route('/logoutcookie/')
def logoutcookie():

    response = redirect(url_for('blue.indexcookie'))

    response.delete_cookie('name')

    return response


模板:
logincookie.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{#提交要触发的是form表单中action的值#}
{#{{ url_for('blue.logincookie') }} 是反向解析  反向解析的结果是路由路径 阿#}
    <form action="{{ url_for('blue.logincookie') }}" method="post">
        name:<input type="text" name="name" placeholder="please input your name"><br>
        <button>提交</button>
    </form>
</body>
</html>

indexcookie.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    欢迎{{ name }}来到红浪漫,拖鞋手牌拿好,楼上二楼左拐。。。。<br>
    <a href="{{ url_for('blue.logoutcookie') }}">退出</a>
</body>
</html>

session

Session
	1.服务端会话技术
	2.所有数据存储在服务器中
	3.默认存在服务器的内存中
		- django默认做了数据持久化(存在了数据库中)
	4.存储结构也是key-value形势,键值对
	【注】单纯的使用session是会报错的,需要使用在__init__方法中配置
    app.config['SECRET_KEY']='110'
    # session缓存的位置
    app.config['SESSION_TYPE']='redis'
    app.config['SESSION_KEY_PREFIX'] = 'python1901'
    Session(app=app)
    # session = Session()
    # session.init_app(app=app)
    # No module named 'redis'  没有redis的模块 需要pip install redis

session的使用步骤:

flask-session
       使用步骤:
              1 pip install flask-session
              2 app.config['SESSION_TYPE']='redis'
              3 Session(app=app)/session = Session() session.init_app(app=app)

              查看redis  keys *
              查看session生命周期  ttl session


session登陆使用
	设置    session['username'] = username
	获取    session.get('username')
	删除
		   response.delete_cookie('session')
		   session.pop('username')


代码:
# seesion
@blue.route('/tologinsession/')
def tologinsession():
    return render_template('loginsession.html')


@blue.route('/loginsession/',methods = ['get','post'])
def loginsession():
    # 获取的文本框中的内容
    name = request.form.get('name')
    # 反向解析 获取的是路由地址  redirect是执行另一个路由
    # url_for(‘blue.indexcookie’)===>/indexcookie/
    # redirect('/indexcookie/')
    response = redirect(url_for('blue.indexsession'))
    # 将name存储到了cookie上
    session['name'] = name
    return response

# 跳转到index页面
@blue.route('/indexsession/')
def indexsession():
    # 获取cookie中的name
    name = session.get('name','游客')
    return render_template('indexsession.html',name=name)


@blue.route('/logoutsession/')
def logoutsession():

    response = redirect(url_for('blue.indexsession'))

    session.pop('name')

    return response

loginsession.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{#提交要触发的是form表单中action的值#}
{#{{ url_for('blue.logincookie') }} 是反向解析  反向解析的结果是路由路径 阿#}
    <form action="{{ url_for('blue.loginsession') }}" method="post">
        name:<input type="text" name="name" placeholder="please input your name"><br>
        <button>提交</button>
    </form>
</body>
</html>
    
indexsession.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    欢迎{{ name }}来到红浪漫,拖鞋手牌拿好,楼上二楼左拐。。。。<br>
    <a href="{{ url_for('blue.logoutcookie') }}">退出</a>
</body>
</html>

session持久化问题

Session
	- django中对session做了持久化,存储在数据库中
    - 可以修改到redis中
		flask中没有对默认session进行任何处理
    - flask-session 可以实现session的数据持久化
    - 各种位置,更推荐使用redis
        - 缓存在磁盘上的时候,管理磁盘文件使用lru, 最近最少使用原则
	服务端会话技术
	Flask中没有对默认Session进行处理,默认存在内存中
	Session需要持久化  Redis中
	实现方案
		插件 flask-session
		pip install flask-session
		在国内源安装
			pip install flask-sessin -i https://pipy.douban.com/simple
		初始化Session对象 
			配置init中app.config['SESSION_TYPE'] = 'redis'
				持久化的位置
			初始化
				创建session的对象有2中方式 分别是以下两种
				1 Session(app=app)
				2 se = Session()   se.init_app(app = app)
			安装redis
				pip install redis
			需要配置SECRET_KEY='110'
			其他配置--视情况而定
				app.config['SESSION_KEY_PREFIX']='flask'
		查看redis内容
			redis-cli
			keys *
			get key
	    session生存时间31天	
		ttl session
			flask的session的生存时间是31天,django的session生存时间是15天

Template模板:

在前面的示例中,视图函数的主要作用是生成请求的响应,这是最简单的请求。实际上,视图函数有两个作用:处理业务逻辑和返回响应内容。在大型应用中,把业务逻辑和表现内容放在一起,会增加代码的复杂度和维护成本。本节学到的模板,它的作用即是承担视图函数的另一个作用,即返回响应内容。 模板其实是一个包含响应文本的文件,其中用占位符(变量)表示动态部分,告诉模板引擎其具体值需要从使用的数据中获取。使用真实值替换变量,再返回最终得到的字符串,这个过程称为“渲染”。Flask使用Jinja2这个模板引擎来渲染模板。Jinja2能识别所有类型的变量,包括{}。 Jinja2模板引擎,Flask提供的render_template函数封装了该模板引擎,render_template函数的第一个参数是模板的文件名,后面的参数都是键值对,表示模板中变量对应的真实值。

Jinja2官方文档(http://docs.jinkan.org/docs/jinja2/)

变量

在模板中{{ variable }}结构表示变量,是一种特殊的占位符,告诉模板引擎这个位置的值,在渲染模板时从视图函数中获取数据;Jinja2除了能识别基本类型的变量,还能识别{};

<p>{{mydict['key']}}</p>
<p>{{mylist[1]}}</p>
<p>{{mylist[myintvar]}}</p>

from flask import Blueprint, render_template
blue = Blueprint('blue',__name__)
@blue.route('/')
def index():
    mydict = {'key':'silence is gold'}
    mylist = ['Speech', 'is','silver']
    myintvar = 0
    return render_template('vars.html',
                           mydict=mydict,
                           mylist=mylist,
                           myintvar=myintvar
                           )

反向路由:

Flask提供了url_for()辅助函数,可以使用程序URL映射中保存的信息生成URL;url_for()接收视图函数名作为参数,返回对应的URL;

如调用url_for(‘index’,_external=True)返回的是绝对地址,在下面这个示例中是http://127.0.0.1:5000/index。

@app.route('/index')
def index():
    return render_template('index.html')

@app.route('/user/')
def redirect():
    return url_for('index',_external=True)

过滤器

过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,这就用到了过滤器。 过滤器的使用方式为:变量名 | 过滤器。 过滤器名写在变量名后面,中间用 | 分隔。如:{{variable | capitalize}},这个过滤器的作用:把变量variable的值的首字母转换为大写,其他字母转换为小写。 其他常用过滤器如下:

字符串操作:

safe:禁用转义;

<p>{{ '<em>hello</em>' | safe }}</p>

capitalize:把变量值的首字母转成大写,其余字母转小写;

 <p>{{ 'hello' | capitalize }}</p>

lower:把值转成小写;

<p>{{ 'HELLO' | lower }}</p>

upper:把值转成大写;

 <p>{{ 'hello' | upper }}</p>

title:把值中的每个单词的首字母都转成大写;

  <p>{{ 'hello' | title }}</p>

trim:把值的首尾空格去掉;

  <p>{{ ' hello world ' | trim }}</p>

reverse:字符串反转;

 <p>{{ 'olleh' | reverse }}</p>

format:格式化输出;

  <p>{{ '%s is %d' | format('name',17) }}</p>

striptags:渲染之前把值中所有的HTML标签都删掉;

<p>{{ '<em>hello</em>' | striptags }}</p>

列表操作

first:取第一个元素

<p>{{ [1,2,3,4,5,6] | first }}</p>

last:取最后一个元素

 <p>{{ [1,2,3,4,5,6] | last }}</p>

length:获取列表长度

<p>{{ [1,2,3,4,5,6] | length }}</p>

sum:列表求和

 <p>{{ [1,2,3,4,5,6] | sum }}</p>

sort:列表排序

<p>{{ [6,2,3,1,5,4] | sort }}</p>

语句块过滤(不常用):

 {% filter upper %}
    this is a Flask Jinja2 introduction
  {% endfilter %}

自定义过滤器:

过滤器的本质是函数。当模板内置的过滤器不能满足需求,可以自定义过滤器。自定义过滤器有两种实现方式:一种是通过Flask应用对象的add_template_filter方法。还可以通过装饰器来实现自定义过滤器。

自定义的过滤器名称如果和内置的过滤器重名,会覆盖内置的过滤器。

实现方式一:通过调用应用程序实例的add_template_filter方法实现自定义过滤器。该方法第一个参数是函数名,第二个参数是自定义的过滤器名称。

def filter_double_sort(ls):
    return ls[::2]
app.add_template_filter(filter_double_sort,'double_2')

实现方式二:用装饰器来实现自定义过滤器。装饰器传入的参数是自定义的过滤器名称。

@app.template_filter('db3')
def filter_double_sort(ls):
    return ls[::-3]

Web表单:

web表单是web应用程序的基本功能。

它是HTML页面中负责数据采集的部件。表单有三个部分组成:表单标签、表单域、表单按钮。表单允许用户输入数据,负责HTML页面数据采集,通过表单将用户输入的数据提交给服务器。

在Flask中,为了处理web表单,我们一般使用Flask-WTF扩展,它封装了WTForms,并且它有验证表单数据的功能。

WTForms支持的HTML标准字段

字段对象 说明
StringField 文本字段
TextAreaField 多行文本字段
PasswordField 密码文本字段
HiddenField 隐藏文本字段
DateField 文本字段,值为datetime.date格式
DateTimeField 文本字段,值为datetime.datetime格式
IntegerField 文本字段,值为整数
DecimalField 文本字段,值为decimal.Decimal
FloatField 文本字段,值为浮点数
BooleanField 复选框,值为True和False
RadioField 一组单选框
SelectField 下拉列表
SelectMultipleField 下拉列表,可选择多个值
FileField 文本上传字段
SubmitField 表单提交按钮
FormField 把表单作为字段嵌入另一个表单
FieldList 一组指定类型的字段

WTForms常用验证函数

验证函数 说明
DataRequired 确保字段中有数据
EqualTo 比较两个字段的值,常用于比较两次密码输入
Length 验证输入的字符串长度
NumberRange 验证输入的值在数字范围内
URL 验证URL
AnyOf 验证输入值在可选列表中
NoneOf 验证输入值不在可选列表中

使用Flask-WTF需要配置参数SECRET_KEY。

CSRF_ENABLED是为了CSRF(跨站请求伪造)保护。 SECRET_KEY用来生成加密令牌,当CSRF激活的时候,该设置会根据设置的密匙生成加密令牌。

在HTML页面中直接写form表单:

#模板文件
<form method='post'>
    <input type="text" name="username" placeholder='Username'>
    <input type="password" name="password" placeholder='password'>
    <input type="submit">
</form>

视图函数中获取表单数据:

from flask import Flask,render_template,request

@app.route('/login',methods=['GET','POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        print username,password
    return render_template('login.html',method=request.method)

使用Flask-WTF实现表单。

配置参数:

app.config['SECRET_KEY'] = 'silents is gold'

模板页面:

<form method="post">
        #设置csrf_token
        {{ form.csrf_token() }}
        {{ form.us.label }}
        <p>{{ form.us }}</p>
        {{ form.ps.label }}
        <p>{{ form.ps }}</p>
        {{ form.ps2.label }}
        <p>{{ form.ps2 }}</p>
        <p>{{ form.submit() }}</p>
        {% for x in get_flashed_messages() %}
            {{ x }}
        {% endfor %}
 </form>

视图函数:

#coding=utf-8
from flask import Flask,render_template,\
    redirect,url_for,session,request,flash

#导入wtf扩展的表单类
from flask_wtf import FlaskForm
#导入自定义表单需要的字段
from wtforms import SubmitField,StringField,PasswordField
#导入wtf扩展提供的表单验证器
from wtforms.validators import DataRequired,EqualTo
app = Flask(__name__)
app.config['SECRET_KEY']='1'

#自定义表单类,文本字段、密码字段、提交按钮
class Login(Form):
    us = StringField(label=u'用户:',validators=[DataRequired()])
    ps = PasswordField(label=u'密码',validators=[DataRequired(),EqualTo('ps2','err')])
    ps2 = PasswordField(label=u'确认密码',validators=[DataRequired()])
    submit = SubmitField(u'提交')

@app.route('/login')
def login():
    return render_template('login.html')

#定义根路由视图函数,生成表单对象,获取表单数据,进行表单数据验证
@app.route('/',methods=['GET','POST'])
def index():
    form = Login()
    if form.validate_on_submit():
        name = form.us.data
        pswd = form.ps.data
        pswd2 = form.ps2.data
        print name,pswd,pswd2
        return redirect(url_for('login'))
    else:
        if request.method=='POST':
            flash(u'信息有误,请重新输入!')
        print form.validate_on_submit()

    return render_template('index.html',form=form)
if __name__ == '__main__':
    app.run(debug=True)

控制语句

常用的几种控制语句:

模板中的if控制语句,模板中的for循环语句

# 列表的遍历
@blue.route('/scorelist/')
def scorelist():
    content = 'girl'
    scorelist1 = [100,90,80,70,60]
    content1 = '<h1>蔡徐坤</h1>'
    return render_template('scorelist.html',scorelist1=scorelist1,content=content,content1=content1)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
        {% for s in scorelist1 %}
{#            {{ loop.index }}:{{ loop.index0 }}#}
            {% if loop.first %}
                   <li style="color:#3cff3f;">{{ s }}</li>
            {% elif loop.last %}
                   <li style="color:red;">{{ s }}</li>
            {% else %}
                  <li style="color:blueviolet;">{{ s }}</li>
            {% endif %}
        {% endfor %}
    </ul>
    {{ content|upper|reverse }}
    {{ content1|safe }}
</body>
</html>

宏、继承、包含

类似于python中的函数,宏的作用就是在模板中重复利用代码,避免代码冗余,然而Django的模板中没有此定义。

Jinja2支持宏,还可以导入宏,需要在多处重复使用的模板代码片段可以写入单独的文件,再包含在所有模板中,以避免重复。

定义宏

{% macro input() %}
  <input type="text"
         name="username"
         value=""
         size="30"/>
{% endmacro %}

调用宏

{{ input() }}

定义带参数的宏

{% macro input(name,value='',type='text',size=20) %}
    <input type="{{ type }}"
           name="{{ name }}"
           value="{{ value }}"
           size="{{ size }}"/>
{% endmacro %}

调用宏,并传递参数

{{ input(value='name',type='password',size=40)}}

把宏单独抽取出来,封装成html文件,其它模板中导入使用

文件名可以自定义macro.html

{% macro function() %}
    <input type="text" name="username" placeholde="Username">
    <input type="password" name="password" placeholde="Password">
    <input type="submit">
{% endmacro %}

在其它模板文件中先导入,再调用

{% import 'macro.html' as func %}
{% func.function() %}

模板继承:

模板继承是为了重用模板中的公共内容。一般Web开发中,继承主要使用在网站的顶部菜单、底部。这些内容可以定义在父模板中,子模板直接继承,而不需要重复书写。

{% block top %}``{% endblock %}标签定义的内容,相当于在父模板中挖个坑,当子模板继承父模板时,可以进行填充。

子模板使用extends指令声明这个模板继承自哪?父模板中定义的块在子模板中被重新定义,在子模板中调用父模板的内容可以使用super()。

父模板:base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>瞎写的</title>
{#block结构标签#}
    {% block ext_css %}
    {% endblock %}
</head>
<body>
    {% block header %}
    头部
    {% endblock %}

    {% block content %}
    文本
    {% endblock %}

    {% block footer %}
    底部
    {% endblock %}

    {% block ext_js %}
    js代码
    {% endblock %}
</body>
</html>

子模板继承:

base_a.html

{% extends 'base.html' %}

{% block footer %}
    为什么今天这么蒙。。。 浑浑噩噩  感觉好象没洗澡
{% endblock %}

{% block header %}
    苍满的天涯是我的爱
{% endblock %}

{% block ext_js %}
    绵绵的青山脚下花正开
{% endblock %}

{% block content %}
    什么歌声最亚封疆大吏封疆大吏方
{% endblock %}

base_b.html

{% extends 'base_a.html' %}


{% block header %}
    {{ super() }} 调用父类,防止被子类覆盖;
    梁博  表态
{% endblock %}


{% block footer %}
    {% include 'include.html' %}
{% endblock %}

include.html

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>

模板继承使用时注意点:

不支持多继承。

为了便于阅读,在子模板中使用extends时,尽量写在模板的第一行。

不能在一个模板文件中定义多个相同名字的block标签。

当在页面中使用多个block标签时,建议给结束标签起个名字,当多个block嵌套时,阅读性更好。

包含(Include)

Jinja2模板中,除了宏和继承,还支持一种代码重用的功能,叫包含(Include)。它的功能是将另一个模板整个加载到当前模板中,并直接渲染。

示例:

include的使用结果看上面的代码:

{\% include 'hello.html' %}

包含在使用时,如果包含的模板文件不存在时,程序会抛出TemplateNotFound异常,可以加上ignore missing关键字。如果包含的模板文件不存在,会忽略这条include语句。

示例:

include的使用加上关键字ignore missing

{\% include 'hello.html' ignore missing %}

宏、继承、包含:

宏(Macro)、继承(Block)、包含(include)均能实现代码的复用。

继承(Block)的本质是代码替换,一般用来实现多个页面中重复不变的区域。

宏(Macro)的功能类似函数,可以传入参数,需要定义、调用。

包含(include)是直接将目标模板文件整个渲染出来。

Flask中的特殊变量和方法:

在Flask中,有一些特殊的变量和方法是可以在模板文件中直接访问的。

config 对象:

config 对象就是Flask的config对象,也就是 app.config 对象。

{{ config.SQLALCHEMY_DATABASE_URI }}

request 对象:

就是 Flask 中表示当前请求的 request 对象,request对象中保存了一次HTTP请求的一切信息。

request常用的属性如下:

属性 说明 类型
data 记录请求的数据,并转换为字符串 *
form 记录请求中的表单数据 MultiDict
args 记录请求中的查询参数 MultiDict
cookies 记录请求中的cookie信息 Dict
headers 记录请求中的报文头 EnvironHeaders
method 记录请求使用的HTTP方法 GET/POST
url 记录请求的URL地址 string
files 记录请求上传的文件 *

{{ request.url }}

url_for 方法:

url_for() 会返回传入的路由函数对应的URL,所谓路由函数就是被 app.route() 路由装饰器装饰的函数。如果我们定义的路由函数是带有参数的,则可以将这些参数作为命名参数传入。

{{ url_for('index') }}

{{ url_for('post', post_id=1024) }}

get_flashed_messages方法:

返回之前在Flask中通过 flash() 传入的信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用 get_flashed_messages() 方法取出。

{% for message in get_flashed_messages() %}
    {{ message }}
{% endfor %}

Models模型:

数据模型开发前的初始配置:

数据库的设置

Web应用中普遍使用的是关系模型的数据库,关系型数据库把所有的数据都存储在表中,表用来给应用的实体建模,表的列数是固定的,行数是可变的。它使用结构化的查询语言。关系型数据库的列定义了表中表示的实体的数据属性。比如:商品表里有name、price、number等。 Flask本身不限定数据库的选择,你可以选择SQL或NOSQL的任何一种。也可以选择更方便的SQLALchemy,类似于Django的ORM。SQLALchemy实际上是对数据库的抽象,让开发者不用直接和SQL语句打交道,而是通过Python对象来操作数据库,在舍弃一些性能开销的同时,换来的是开发效率的较大提升。

SQLAlchemy是一个关系型数据库框架,它提供了高层的ORM和底层的原生数据库的操作。flask-sqlalchemy是一个简化了SQLAlchemy操作的flask扩展。

1.数据交互的封装
2.Flask默认并没有提供任何数据库操作的API,Flask中可以自己的选择数据,用原生语句实现功能,但是原生SQL缺点:
      代码利用率低,条件复杂代码语句越过长,有很多相似语句;
      一些SQL是在业务逻辑中拼出来的,修改需要了解业务逻辑;
	 也可以选择ORM方便对数据的操作
		SQLAlchemy
		MongoEngine
		将对象的操作转换为原生SQL
		优点:
			 易用性,可以有效减少重复SQL;
              性能损耗少;
              设计灵活,可以轻松实现复杂查询;
              移植性好;
3.Flask中并没有提供默认ORM:
	ORM 对象关系映射
	通过操作对象,实现对数据的操作


使用步骤:1.安装pip install flask-sqlalchemy包
         2.在models文件中创建SQLALCHEMY对象
		db = SQLAlchemy()
		3.在init文件中进行配置:	
config中配置SQLALCHEMY_DATABASE_URI
dialect+driver://username:password@host:port/database
数据库 + 驱动 :// 用户:密码@ 主机:端口/数据库
app.config['SQLALCHEMY_DATABASE_URI']='mysql+pymysql://root:1234@localhost:3306/flask1901'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=False
因为需要db来调用属性 db.init_app(app=app)
db.init_app(app=app)            

代码实现:

1 安装sqlalchemy  pip install flask-sqlalchemy
2 在models.py文件中创建SQLALCHEMY对象
# 模型的创建需要继承 db.Model
# 2个坑 + 1个大坑
# from App import db
# db对象 如果放到了方法里,那么是不可以导入到其他模块的
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
# User模型 应该对应着 表
# 模型的属性应该对应表的字段
class User(db.Model):
    # flask中 模型必须有主键ser could not assemble any primary key columns
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    # (in table 'user', column 'name'): VARCHAR requires a length on dialect mysql
    # flask的模型中的字符串必须有长度
    name = db.Column(db.String(32))
    age = db.Column(db.Integer)

3.在__init__.py文件进行配置:
from flask import Flask
from App.models import db
def create_app():
    app = Flask(__name__)
    # 数据库的链接路径
    # dialect + driver: // username: password @ host:port / database
    # 方言     驱动           用户名子    密码      主机   3306   数据库的名字
    app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:1234@localhost:3306/flask1901'
    # 当使用sqlalchemy的时候  启动服务器会报错
    # SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True or False to suppress this warning.
    # 在将来版本更新的过程中可能会遇到的错误
    # 如果还报错 有2中情况  要么单词打错  要么放在了创建对象之后了
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    db.init_app(app=app)
    return app

4.views.py文件中调用视图函数:
from flask import Blueprint
from App import db
blue = Blueprint('blue',__name__)
# 创建一个模型,然后执行一个方法,就会生成相应的表
@blue.route('/createuser/')
def createuser():
    db.create_all()
    return '创建成功'

5.manager.py
from flask_script import Manager
from App import create_app
from App.views import blue
app = create_app()
manager = Manager(app=app)
app.register_blueprint(blueprint=blue)
if __name__ == '__main__':
    manager.run()

使用:
	定义模型:继承Sqlalchemy对象中的model
	定义字段:主键一定要添加,所需要字段
              语法:db.Column( db.类型(),约束 )
    创建
	db.create_all()
	删除
	db.drop_all()
	修改表名
	__tablename__ = "Worker"
    数据操作
        创建对象
        添加
            db.session.add(对象)
            db.session.commit()
        查询
            模型.query.all()

项目拆分

项目拆分
	开发环境
		开发环境
		测试环境
		演示环境-类似线上环境也叫做预生产环境
		线上环境 也叫做 生产环境
	Flask轻量级框架,在一个文件中实现所有功能
	拆分项目
		MTV的样子
		规划项目结构
			manager.py
				程序入口
				app的创建
				Manager (flask-script管理对象)
				可以接收命令行参数
			App
				__init__
					创建Flask对象app
					加载settings文件
					调用init_ext方法
					调用init_blue方法
				settings
					App运行的环境配置
					运行环境
				ext(扩展的,额外的)
					用来初始化第三方的各种插件
					Sqlalchemy对象初始化 数据库
					Session初始化
				views
					蓝图
					创建
					注册到app上
				models
					定义模型

__init__.py
from flask import Flask
from App import settings
from App.ext import init_ext

def create_app():
    app = Flask(__name__)

    # session
    # app.config['SECRET_KEY']='114'
    # app.config['SESSION_TYPE']='redis'
    # Session(app=app)
    # 建立与settings文件的关键
    # settings.ENV_NAME.get('develop')获取的是settings文件中的value值
    # app.config.from_object(DevelopConfig)将类加载过来
    app.config.from_object(settings.ENV_NAME.get('develop'))

    # sqlalchemy
    # db.init_app(app=app)

    init_ext(app)

    return app

ext.py
# 用来存放第三方扩展库
from flask_session import Session
from App.models import db

def init_ext(app):
    app.config['SECRET_KEY']='114'
    app.config['SESSION_TYPE']='redis'
    Session(app=app)
    db.init_app(app=app)

setting.py
# 配置数据库环境
def get_database_uri(DATABASE):
    dialect = DATABASE.get('dialect') or 'mysql'
    driver = DATABASE.get('driver') or 'pymysql'
    username = DATABASE.get('username') or 'root'
    password = DATABASE.get('password') or '1234'
    host = DATABASE.get('host') or 'localhost'
    port = DATABASE.get('port') or '3306'
    database = DATABASE.get('database') or 'flaskday03'
    # dialect+driver://username:password@host:port/database
    return '{}+{}://{}:{}@{}:{}/{}'.format(dialect,driver,username,password,host,port,database)

class Config():

    # 测试环境需要打开Test
    # 测试环境除外 需要打开Debug
    Test = False
    Debug = False
    SQLALCHEMY_TRACK_MODIFICATIONS = False

# 每一套数据环境 都不一致  链接的是不同的数据库
class DevelopConfig(Config):
    Debug = True
    DATABASE={
        'dialect':'mysql',
        'driver':'pymysql',
        'username':'root',
        'password':'1234',
        'host':'localhost',
        'port':'3306',
        'database':'flaskday03'
    }
    SQLALCHEMY_DATABASE_URI = get_database_uri(DATABASE)


class TestConfig(Config):
    Test = True
    DATABASE = {
        'dialect': 'mysql',
        'driver': 'pymysql',
        'username': 'root',
        'password': '1234',
        'host': 'localhost',
        'port': '3306',
        'database': 'flaskday03'
    }
    SQLALCHEMY_DATABASE_URI = get_database_uri(DATABASE)


class ShowConfig(Config):
    Debug = True
    DATABASE = {
        'dialect': 'mysql',
        'driver': 'pymysql',
        'username': 'root',
        'password': '1234',
        'host': 'localhost',
        'port': '3306',
        'database': 'flaskday03'
    }
    SQLALCHEMY_DATABASE_URI = get_database_uri(DATABASE)


class ProductConfig(Config):
    Debug = True
    DATABASE = {
        'dialect': 'mysql',
        'driver': 'pymysql',
        'username': 'root',
        'password': '1234',
        'host': 'localhost',
        'port': '3306',
        'database': 'flaskday03'
    }
    SQLALCHEMY_DATABASE_URI = get_database_uri(DATABASE)


ENV_NAME = {
    'develop':DevelopConfig,
    'test':TestConfig,
    'show':ShowConfig,
    'product':ProductConfig
}

models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Student(db.Model):
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(32))

views.py
from flask import Blueprint

from App.models import db, Student

blue = Blueprint('blue',__name__)

@blue.route('/')
def index():
    return 'index'


@blue.route('/createstudent/')
def createstudent():
    db.create_all()
    return 'ok'

@blue.route('/savestudent/')
def savestudent():
    s = Student()
    s.name = '小明'

    db.session.add(s)
    db.session.commit()
    return 'ok'

manager.py
from flask_script import Manager
from App import create_app
from App.views import blue
app = create_app()
manager = Manager(app=app)
app.register_blueprint(blueprint=blue)
if __name__ == '__main__':
    manager.run()

数据库基本操作

在Flask-SQLAlchemy中,插入、修改、删除操作,均由数据库会话管理。会话用db.session表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用commit()方法提交会话。

数据库会话是为了保证数据的一致性,避免因部分更新导致数据不一致。提交操作把会话对象全部写入数据库,如果写入过程发生错误,整个会话都会失效。

数据库会话也可以回滚,通过db.session.rollback()方法,实现会话提交数据前的状态。

在Flask-SQLAlchemy中,查询操作是通过query对象操作数据。最基本的查询是返回表中所有数据,可以通过过滤器进行更精确的数据库查询。

DDL 数据定义语言 data ding language

 专门用来操作表的  create  drop alter

DML 数据操纵语言 data make language

 专门用来操作记录的 insert delete update

DQL 数据查询语言

 专门用来查询记录的 select

TCL 事务

 commit roolback

将数据添加到会话中示例:

user = User(name='python')
db.session.add(user)
db.session.commit()

在视图函数中定义模型类

from flask import Flask
from flask_sqlalchemy import SQLAlchemy


app = Flask(__name__)

#设置连接数据库的URL
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/Flask_test'

#设置每次请求结束后会自动提交数据库中的改动
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
#查询时会显示原始SQL语句
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)

class Role(db.Model):
    # 定义表名
    __tablename__ = 'roles'
    # 定义列对象
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    us = db.relationship('User', backref='role')

    #repr()方法显示一个可读字符串
    def __repr__(self):
        return 'Role:%s'% self.name

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True, index=True)
    email = db.Column(db.String(64),unique=True)
    pswd = db.Column(db.String(64))
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

    def __repr__(self):
        return 'User:%s'%self.name
if __name__ == '__main__':
    db.drop_all()
    db.create_all()
    ro1 = Role(name='admin')
    ro2 = Role(name='user')
    db.session.add_all([ro1,ro2])
    db.session.commit()
    us1 = User(name='wang',email='wang@163.com',pswd='123456',role_id=ro1.id)
    us2 = User(name='zhang',email='zhang@189.com',pswd='201512',role_id=ro2.id)
    us3 = User(name='chen',email='chen@126.com',pswd='987654',role_id=ro2.id)
    us4 = User(name='zhou',email='zhou@163.com',pswd='456789',role_id=ro1.id)
    db.session.add_all([us1,us2,us3,us4])
    db.session.commit()
    app.run(debug=True)

DML模型操作

创建表:

db.create_all()

删除表

db.drop_all()


增
		db.session.add()将对象增加session中,然后用commit进行提交:
			   @blue.route("/addperson/")
                def add_person():
                    p = Person()
                    p.p_name = "小明"
                    p.p_age = 15
                    db.session.add(p)
                    db.session.commit()
                    return "添加成功"
		db.session.add_all()
		        eg:
		        @blue.route("/addpersons/")
                def app_persons():
                    persons = []
                    for i in range(5):
                        p = Person()
                        p.p_name = "猴子请来的救兵%d" % random.randrange(100)
                        p.p_age = random.randrange(70)
                        persons.append(p)
                    db.session.add_all(persons)
                    db.session.commit()
                    return "添加成功"

# 插入一条数据
ro1 = Role(name='admin')
db.session.add(ro1)
db.session.commit()
#再次插入一条数据
ro2 = Role(name='user')
db.session.add(ro2)
db.session.commit()

# 一次插入多条数据
us1 = User(name='wang',email='wang@163.com',pswd='123456',role_id=ro1.id)
us2 = User(name='zhang',email='zhang@189.com',pswd='201512',role_id=ro2.id)
us3 = User(name='chen',email='chen@126.com',pswd='987654',role_id=ro2.id)
us4 = User(name='zhou',email='zhou@163.com',pswd='456789',role_id=ro1.id)
db.session.add_all([us1,us2,us3,us4])
db.session.commit()

删除
	db.session.delete(对象)
		基于查询


修改
	db.session.add(对象)
		基于查询

常用的SQLAlchemy查询过滤器

过滤器 说明
filter() 把过滤器添加到原查询上,返回一个新查询
filter_by() 把等值过滤器添加到原查询上,返回一个新查询
limit 使用指定的值限定原查询返回的结果
offset() 偏移原查询返回的结果,返回一个新查询
order_by() 根据指定条件对原查询结果进行排序,返回一个新查询
group_by() 根据指定条件对原查询结果进行分组,返回一个新查询

常用的SQLAlchemy查询执行器

方法 说明
all() 以列表形式返回查询的所有结果
first() 返回查询的第一个结果,如果未查到,返回None
first_or_404() 返回查询的第一个结果,如果未查到,返回404
get() 返回指定主键对应的行,如不存在,返回None
get_or_404() 返回指定主键对应的行,如不存在,返回404
count() 返回查询结果的数量
paginate() 返回一个Paginate对象,它包含指定范围内的结果

查询:filter_by精确查询

返回名字等于wang的所有人

User.query.filter_by(name='wang').all()

first()返回查询到的第一个对象

User.query.first()

all()返回查询到的所有对象

User.query.all()

filter模糊查询,返回名字结尾字符为g的所有数据。

User.query.filter(User.name.endswith('g')).all()

get(),参数为主键,如果主键不存在没有返回内容

User.query.get()

逻辑非,返回名字不等于wang的所有数据。

User.query.filter(User.name!='wang').all()

逻辑与,需要导入and,返回and()条件满足的所有数据。

from sqlalchemy import and_
User.query.filter(and_(User.name!='wang',User.email.endswith('163.com'))).all()

逻辑或,需要导入or_

from sqlalchemy import or_
User.query.filter(or_(User.name!='wang',User.email.endswith('163.com'))).all()

not_ 相当于取反

from sqlalchemy import not_
User.query.filter(not_(User.name=='chen')).all()

查询数据后删除

user = User.query.first()
db.session.delete(user)
db.session.commit()
User.query.all()

更新数据

user = User.query.first()
user.name = 'dong'
db.session.commit()
User.query.first()

使用update

User.query.filter_by(name='zhang').update({'name':'li'})

关联查询示例:角色和用户的关系是一对多的关系,一个角色可以有多个用户,一个用户只能属于一个角色。

查询角色的所有用户:

#查询roles表id为1的角色
ro1 = Role.query.get(1)
#查询该角色的所有用户
ro1.us

查询用户所属角色:

#查询users表id为3的用户
us1 = User.query.get(3)
#查询用户属于什么角色
us1.role

获取单个数据
	get
		主键值
		获取不到不会抛错
		person = Person.query.get(3)
        db.session.delete(person)
        db.session.commit()
	first
		person = Person.query.first()


获取结果集
	xxx.query.all
		persons = Person.query.all()
	xxx.query.filter_by
		persons = Person.query.filter_by(p_age=15)
	xxx.query.filter
		persons = Person.query.filter(Person.p_age < 18)
		persons = Person.query.filter(Person.p_age.__le__(15))
		persons = Person.query.filter(Person.p_name.startswith("小"))
		persons = Person.query.filter(Person.p_name.endswith("1"))
		persons = Person.query.filter(Person.p_name.contains("1"))
		persons = Person.query.filter(Person.p_age.in_([15, 11]))

数据筛选
	order_by
		persons = Person.query.order_by("-p_age")
	limit
		persons = Person.query.limit(5)
	offset
		persons = Person.query.offset(5).order_by("-id")
	offset和limit不区分顺序,offset先生效
		persons = Person.query.order_by("-id").limit(5).offset(5)
		persons = Person.query.order_by("-id").limit(5)
		 persons = Person.query.order_by("-id").offset(17).limit(5)
	order_by 需要先调用执行
		 persons = Person.query.order_by("-id").offset(17).limit(5)


pagination
	分页器
	需要想要的页码
	每一页显示多少数据
	原生
	persons = Person.query.offset((page_num - 1) * page_per).limit(page_per)
	参数(page,page_per,False(是否抛异常)
	persons = Person.query.paginate(page_num, page_per, False).items


逻辑运算
	与
		    and_     filter(and_(条件))
			huochelist = kaihuoche.query.filter(and_(kaihuoche.id == 1,kaihuoche.name == 'lc'))

	或
		or_          filter(or_(条件))
			huochelist = kaihuoche.query.filter(or_(kaihuoche.id == 1,kaihuoche.name =='lc'))

	非
		not_         filter(not_(条件))  注意条件只能有一个
			huochelist = kaihuoche.query.filter(not_(kaihuoche.id == 1))

	in
		    huochelist = kaihuoche.query.filter(kaihuoche.id.in_([1,2,4]))

常用的SQLAlchemy字段类型

类型名 python中类型 说明
Integer int 普通整数,一般是32位
SmallInteger int 取值范围小的整数,一般是16位
BigInteger int或long 不限制精度的整数
Float float 浮点数
Numeric decimal.Decimal 普通整数,一般是32位
String str 变长字符串
Text str 变长字符串,对较长或不限长度的字符串做了优化
Unicode unicode 变长Unicode字符串
UnicodeText unicode 变长Unicode字符串,对较长或不限长度的字符串做了优化
Boolean bool 布尔值
Date datetime.date 时间
Time datetime.datetime 日期和时间
LargeBinary str 二进制文件

常用的SQLAlchemy列选项

选项名 说明
primary_key 如果为True,代表表的主键
unique 如果为True,代表这列不允许出现重复的值
index 如果为True,为这列创建索引,提高查询效率
nullable 如果为True,允许有空值,如果为False,不允许有空值
default 为这列定义默认值

常用的SQLAlchemy关系选项

选项名 说明
backref 在关系的另一模型中添加反向引用
primary join 明确指定两个模型之间使用的联结条件
uselist 如果为False,不使用列表,而使用标量值
order_by 指定关系中记录的排序方式
secondary 指定多对多中记录的排序方式
secondary join 在SQLAlchemy中无法自行决定时,指定多对多关系中的二级联结条件

自定义模型类

定义模型

模型表示程序使用的数据实体,在Flask-SQLAlchemy中,模型一般是Python类,继承自db.Model,db是SQLAlchemy类的实例,代表程序使用的数据库。

类中的属性对应数据库表中的列。id为主键,是由Flask-SQLAlchemy管理。db.Column类构造函数的第一个参数是数据库列和模型属性类型。

如下示例:定义了两个模型类,作者和书名。

#定义模型类-作者
class Author(db.Model):
    __tablename__ = 'author'
    id = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(32),unique=True)
    email = db.Column(db.String(64))
    au_book = db.relationship('Book',backref='author')
    def __str__(self):
        return 'Author:%s' %self.name

#定义模型类-书名
class Book(db.Model):
    __tablename__ = 'books'
    id = db.Column(db.Integer,primary_key=True)
    info = db.Column(db.String(32),unique=True)
    leader = db.Column(db.String(32))
    au_book = db.Column(db.Integer,db.ForeignKey('author.id'))
    def __str__(self):
        return 'Book:%s,%s'%(self.info,self.lead)

创建表 db.create_all()

查看author表结构 desc author

查看books表结构 desc books

flask-migrate

数据库迁移

在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。

更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。

在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。

为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。

首先要在虚拟环境中安装Flask-Migrate。

pip install flask-migrate

文件:database.py

#coding=utf-8
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate,MigrateCommand
from flask_script import Shell,Manager

app = Flask(__name__)
manager = Manager(app)

app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/Flask_test'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)

#第一个参数是Flask的实例,第二个参数是Sqlalchemy数据库实例
migrate = Migrate(app,db) 

#manager是Flask-Script的实例,这条语句在flask-Script中添加一个db命令
manager.add_command('db',MigrateCommand)

#定义模型Role
class Role(db.Model):
    # 定义表名
    __tablename__ = 'roles'
    # 定义列对象
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    def __repr__(self):
        return 'Role:'.format(self.name)

#定义用户
class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)
    def __repr__(self):
        return 'User:'.format(self.username)
if __name__ == '__main__':
    manager.run()

创建迁移仓库

#这个命令会创建migrations文件夹,所有迁移文件都放在里面。
python database.py db init

创建迁移脚本

自动创建迁移脚本有两个函数,upgrade()函数把迁移中的改动应用到数据库中。downgrade()函数则将改动删除。自动创建的迁移脚本会根据模型定义和数据库当前状态的差异,生成upgrade()和downgrade()函数的内容。对比不一定完全正确,有可能会遗漏一些细节,需要进行检查.

#创建自动迁移脚本
python database.py db migrate -m 'initial migration'

更新数据库

python database.py db upgrade

回退数据库

回退数据库时,需要指定回退版本号,由于版本号是随机字符串,为避免出错,建议先使用python database.py db history命令查看历史版本的具体版本号,然后复制具体版本号执行回退。

python database.py db downgrade 版本号

迁移的具体步骤:

使用
	安装
		pip install flask-migrate
	初始化
		创建migrate对象
		需要使用app 和 db初始化  在ext文件中
			 migrate = Migrate()
			 migrate.init_app(app=app, db=db)
		懒加载初始化
		结合flask-script使用
		在manager文件上添加command (MigrateCommand)
			manager.add_command("db", MigrateCommand)
	python manager.py db xxx
		init     第一次使用
		migrate  生成迁移文件
				不能生成
					模型定义完成从未调用,创建对象
					数据库已经有模型记录
		upgrade  升级
		downgrade 降级
	创建用户文件
		python manager.py db migrate  --message ‘创建用户’

李晶:

__init__.py
from flask import Flask
from App import settings
from App.ext import init_ext

def create_app(envname):
    app = Flask(__name__)     app.config.from_object(settings.ENV_NAME.get(envname))
    init_ext(app)
    return app

ext.py
'''
flask-script
flask-blueprint
flask-session
flask-sqlalchemy
'''
from flask_migrate import Migrate
from flask_session import Session
from App.models import db

def init_ext(app):
    # session
    app.config['SECRET_KEY']='10010'
    app.config['SESSION_TYPE']='redis'
    Session(app=app)

    # sqlalchemy
    db.init_app(app=app)

    # migrate
    migrate = Migrate()
    # 注意参数的个数
    migrate.init_app(app=app,db=db)
    
   
models.py

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

# orm  对象关系映射
# 通过python代码能创建表 删除表
# flask-migrate 数据库迁移 --》通过一些命令行参数 去直接的操作模型 不再书写视图函数


class Animal(db.Model):
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(32))

class Goods(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(32))


class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(32))
    p_age = db.Column(db.Integer,default=18)

    
    
setting.py

def get_database_uri(DATABASE):
    dialect = DATABASE.get('dialect') or 'mysql'
    driver = DATABASE.get('driver') or 'pymysql'
    username = DATABASE.get('username') or 'root'
    password = DATABASE.get('password') or '1234'
    host = DATABASE.get('host') or 'localhost'
    port = DATABASE.get('port') or '3306'
    database = DATABASE.get('database') or 'flaskday03'

    return '{}+{}://{}:{}@{}:{}/{}'.format(dialect,driver,username,password,host,port,database)



class Config():
    Test = False
    Debug = False
    SQLALCHEMY_TRACK_MODIFICATIONS = False


class DevelopConfig(Config):
    Debug = True

    DATABASE = {
        'dialect':'mysql',
        'driver':'pymysql',
        'username':'root',
        'password':'1234',
        'host':'127.0.0.1',
        'port':'3306',
        'database':'flaskday03'
    }
    # SQLALCHEMY_DATABASE_URI是数据库链接的路径
    SQLALCHEMY_DATABASE_URI = get_database_uri(DATABASE)

class TestConfig(Config):
    Test = True

    DATABASE = {
        'dialect': 'mysql',
        'driver': 'pymysql',
        'username': 'root',
        'password': '1234',
        'host': '127.0.0.1',
        'port': '3306',
        'database': 'flaskday03'
    }
    SQLALCHEMY_DATABASE_URI = get_database_uri(DATABASE)


class ShowConfig(Config):
    Debug = True

    DATABASE = {
        'dialect':'mysql',
        'driver':'pymysql',
        'username':'root',
        'password':'1234',
        'host':'127.0.0.1',
        'port':'3306',
        'database':'flaskday03'
    }
    SQLALCHEMY_DATABASE_URI = get_database_uri(DATABASE)


class ProductConfig(Config):
    Debug = True

    DATABASE = {
        'dialect':'mysql',
        'driver':'pymysql',
        'username':'root',
        'password':'1234',
        'host':'127.0.0.1',
        'port':'3306',
        'database':'flaskday03'
    }
    SQLALCHEMY_DATABASE_URI = get_database_uri(DATABASE)


ENV_NAME = {
    'develop':DevelopConfig,
    'test':TestConfig,
    'show':ShowConfig,
    'product':ProductConfig,
    'default':DevelopConfig,
}




views.py
from _operator import not_

from flask import Blueprint, request, render_template

from App.models import Goods, Person, db

blue = Blueprint('blue',__name__)

@blue.route('/')
def hello_world():
    goods = Goods()
    person = Person()
    return 'Hello World!'


@blue.route('/saveperson/')
def saveperson():
    p = Person()
    p.name = '蔡徐坤'
    p.age = 18
    db.session.add(p)
    # flask的增删改 一定要commit
    db.session.commit()
    return 'ok'

@blue.route('/savelist/')
def savelist():
    p = Person()
    p.name = '赵丽颖'
    p1 = Person()
    p1.name = '大牛'

    p_list = []
    p_list.append(p)
    p_list.append(p1)

    db.session.add_all(p_list)
    db.session.commit()
    return 'ok'

@blue.route('/deleteperson/')
def deleteperson():
    p = Person.query.get(1)
    db.session.delete(p)
    db.session.commit()
    return 'ok'

@blue.route('/updateperson/')
def updateperson():
    p = Person.query.get(2)
    p.name = '大妞'
    db.session.add(p)
    db.session.commit()
    return 'ok'


# 查询单个数据
@blue.route('/getone/')
def getone():
#     查询   模型.query.xxx
# 返回的是模型类型
#     p = Person.query.get(3)
#     print(p.name)
#     print(type(p))
    p = Person.query.first()
    print(p.name)
    print(type(p))
    return 'getone'

# 获取结果集
@blue.route('/getresult/')
def getresult():
 #     获取的全部数据
 #    ps = Person.query.all()
 #    for p in ps:
 #        print(p.name)
 #    print(ps)
 #    # list
 #    print(type(ps))
 #    return 'ok'
#     条件查询  filter   filter_by
#    filter 和 filter_by的使用
#    filter在参数中需要使用模型.属性 == 值
#    filter_by在参数中需要使用 属性 = 值
#    flask中条件过滤的方法返回值类型是 basequery对象
#    django中条件过滤的方法的返回值类型是  queryset
#      p1 = Person.query.filter(Person.age == 18)
#      print(p1)
#      print(type(p1))
#      p = Person.query.filter_by(age = 18)
#      print(p)
#      print(type(p))
#
#     lt ==>  little   than
#     lte  ==>  litte  than  equals
#     gt  ==>  great   than
#     gte  ==> great than  equals
#      p = Person.query.filter(Person.age.__lt__(20))
#      p = Person.query.filter(Person.age.__le__(20))
#      p = Person.query.filter(Person.age.__gt__(20))
#      p = Person.query.filter(Person.age.__ge__(20))
#      p = Person.query.filter(Person.name.startswith('张'))
#      p = Person.query.filter(Person.name.endswith('三'))
#      p = Person.query.filter(Person.name.contains('王'))
     p = Person.query.filter(Person.name.in_(['李四','王五']))
     for p1 in p:
         print(p1.name,p1.age)
     print(p)
     print(type(p))
     return 'ok'

# 数据筛选
@blue.route('/getselect/')
def getselect():
    # p = Person.query.order_by('age')
    # 降序问题。。。。。。。
    # p = Person.query.order_by('-p_age')
    # 取前几个数据
    # p = Person.query.limit(5)
    # 除了前几个的数据
    # p = Person.query.offset(5)
    #  当offset和limit同时使用的时候 永远都是offset在前  limit在后
    # 空的    跳过前三个  取3个
    # p = Person.query.limit(3).offset(3)
    # 跳过前三个  取3个
    # p = Person.query.offset(3).limit(3)
    # 语法错误
    # p = Person.query.offset(3).limit(3).order_by('p_age')
    # 当order_by 和 offset limit连用的时候 那么 order_by 必须放在前面
    # p = Person.query.order_by('p_age').offset(3).limit(3)
    # 降序
    # p = Person.query.order_by(db.desc(Person.p_age))
    p = Person.query.order_by(-Person.p_age)
    for p1 in p:
        print(p1.id,p1.name,p1.p_age)
    return 'ok'

# 分页
# page 第几页     per_page/pagesize   页的大小

@blue.route('/getpage/')
def getpage():
    # 在浏览器的地址栏上输入  127.0.0.1:5000/getpage?page=1&per_page=3
    page = int(request.args.get('page'))

    per_page = int(request.args.get('per_page'))

    #  1    0  3
    #  2    3  3
    #  3    6  3
    # offset(0).limit(3)   123
    # offset(3).limit(3)   456
    # offset(6).limit(3)   789
    # persons = Person.query.offset((page-1)*per_page).limit(per_page)
    # paginate方法的返回值类型是 pagination对象  该对象不可以直接用于迭代  如果
    # 想遍历该对象 那么需要调用items
    # persons = Person.query.paginate(page,per_page,False).items
    persons = Person.query.paginate(page=page,per_page=per_page,error_out=False)
    return render_template('personlist.html',persons=persons,page=page,per_page=per_page)


# 逻辑运算
@blue.route('/getlogic/')
def getlogic():
    # persons = Person.query.filter(or_(Person.p_age==18,Person.p_age==19))
    # persons = Person.query.filter(and_(Person.p_age==18,Person.name=='大牛'))
    persons = Person.query.filter(not_(Person.id == 5))
    print(persons)
    print(type(persons))
    for person in persons:
        print(person.name,person.p_age)

    return 'ok'




manager.py
from flask_migrate import MigrateCommand
from flask_script import Manager

from App import create_app
from App.views import blue

app = create_app('develop')

manager = Manager(app=app)

manager.add_command('db',MigrateCommand)

app.register_blueprint(blueprint=blue)


if __name__ == '__main__':
    manager.run()

    
 
数据库迁移
       flask-migrate
           使用步骤:1 pip install flask-migrate
                   2 migrate = Migrate()
                   3 migrate.init_app(app=app,db=db)
                   4 manager.add_command('db',MigrateCommand)

                   5 开始迁移
                          python manager.py db init
                          python manager.py db migrate
                          python manager.py db upgrade
                          python manager.py db downgrade

           当不能生成迁移文件的时候:
                          1 引用模型
                          2 删除迁移的表和迁移文件


personlist.py

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
    {% for person in persons %}
           <li>{{ person.name }}</li>
    {% endfor %}
    </ul>
        <a href="{{ url_for('blue.getpage') }}?page={{ page - 1 }}&per_page={{ per_page }}">上一页</a>

    <a href="{{ url_for('blue.getpage') }}?page={{ page + 1 }}&per_page={{ per_page }}">下一页</a>
</body>
</html>

模型关系

一对多
class Parent(db.Model):
    id=db.Column(db.Integer,primary_key=True,autoincrement=True)
    name=db.Column(db.String(30),unique=True)
    children=db.relationship("Child",backref="parent",lazy=True)
    def __init__(self):
        name=self.name

class Child(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30), unique=True)
    parent_id = db.Column(db.Integer, db.ForeignKey('parent.id'))
    def __init__(self):
        name = self.name


relationship函数
	      sqlalchemy对关系之间提供的一种便利的调用方式,关联不同的表;
 backref参数
          对关系提供反向引用的声明,在Address类上声明新属性的简单方法,之后可以在my_address.person来获取这个地址的person;
lazy参数
          'select'(默认值)
              SQLAlchemy 会在使用一个标准 select 语句时一次性加载数据;
          'joined'
              让 SQLAlchemy 当父级使用 JOIN 语句是,在相同的查询中加载关系;
          'subquery'
              类似 'joined' ,但是 SQLAlchemy 会使用子查询;
          'dynamic':
              SQLAlchemy 会返回一个查询对象,在加载这些条目时才进行加载数据,大批量数据查询处理时推荐使用。
ForeignKey参数
	      代表一种关联字段,将两张表进行关联的方式,表示一个person的外键,设定上必须要能在父表中找到对应的id值
	      
添加
	eg:@blue.route('/add/')
        def add():
            p = Parent()
            p.name = '张三'
            c = Child()
            c.name = '张四'
            c1 = Child()
            c1.name = '王五'
            p.children = [c,c1]

            db.session.add(p)
            db.session.commit()

            return 'add success'
查
      eg:
      主查从 --》 Parent--》Child
      @blue.route('/getChild/')
      def getChild():
          clist = Child.query.filter(Parent.id == 1)
          for c in clist:
              print(c.name)
          return 'welcome to red remonce'
      从查主
      @blue.route('/getParent/')
      def getParent():
          p = Parent.query.filter(Child.id == 2)
          print(type(p))
          print(p[0].name)
          return '开洗'


一对一
   一对一需要设置relationship中的uselist=Flase,其他数据库操作一样。


多对多
class User(db.Model):
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(32))
    age = db.Column(db.Integer,default=18)

class Movie(db.Model):
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(32))

class Collection(db.Model):
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    u_id = db.Column(db.Integer,db.ForeignKey(User.id))
    m_id = db.Column(db.Integer,db.ForeignKey(Movie.id))
    
    购物车添加
    @blue.route('/getcollection/')
	def getcollection():
          u_id = int(request.args.get('u_id'))
          m_id = int(request.args.get('m_id'))
          c = Collection.query.filter(Collection.u_id == u_id).filter_by(m_id = m_id)

          if c.count() > 0:
              print(c.first().u_id,c.first().m_id)
              # print(c)
              # print(type(c))
              # print('i am if')
              return '已经添加到了购物车中'
          else:
              c1 = Collection()
              c1.u_id = u_id
              c1.m_id = m_id
              db.session.add(c1)
              db.session.commit()
              return 'ok'
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值