Flask基础


虚拟环境解决的问题

我们都是通过pip install x方式安装

我们用新版本和旧版本冲突,可以通过虚拟环境解决

虚拟环境原理

虚拟环境相当于一个抽屉,在这个抽屉中安装的任何软件包都不会影响 到其他抽屉。并且在项目 中,我可以指定这个项目的虚拟环境来配合我的项目。比如我们现在有一个项 目是基于 Flask 1.0.x 版本,又有一个项目是基于 Flask 0.9.x 的版本, 那么这时候就可以创建两个虚拟环境,在这两个虚拟环境中分别安装 Flask 1.0.x 和 Flask 0.9.x 来适配我们的项目

安装virtualenv

pip install virtualenv

创建虚拟环境

virtualenv [虚拟环境名字]
momo_env

简单的理解为在当前目录下创建好了一个文件夹,文件中有一堆东西而 已

进入环境

虚拟环境创建好后,可以进入到这个虚拟环境,然后安装第三方包

  1. windows 进入虚拟环境,进入到scripts文件夹,执行activate

  2. Linux 、 Unix : 进 入 虚 拟 环 境 : source /path/to/virtualenv/bin/activate 一旦你进入到了这个虚拟环境中,你安装包,卸载包都是在这个虚拟环境中,不会影响到外面

deactivate 退出虚拟环境

创建虚拟环境时候指定python解释器

通过 -p 参数指定

virtualenv -p  [python 的根目录,也就是环变量]

问题 1:使用 virtualenv 创建环境的时候,当前目录在哪儿就在哪儿创建,会
导致以后到处都是(C 盘 D 盘)环境,不便管理。
问题 2:需要记住环境目录所在,并使用 cd 命令 有可能 多次进入指定环境才
能激活,过程有点繁琐,
不便操作

virtualenvwrapper的安装使用

virtualenvwrapper 这个软件包可以让我们管理虚拟环境变得更加简单。不用 再跑到某个目录下通 过 virtualenv 来创建虚拟环境,并且激活的时候也要跑到具体的目录下去激 活

安装

Linux    pip install virtualenvwrapper-win
Win      pip install virtualenvwrapper

创建虚拟环境

mkvirtualenv 虚拟环境名字

直接在当前用户下创建一个虚拟环境,将这个虚拟环境安装到了这个目录,并且切换到

切换虚拟环境

workon [虚拟环境名字]

退出当前虚拟环境

deactivate

删除某个虚拟环境

rmvirtualenv [虚拟环境名字]

进入到虚拟环境

 cdvirtualenv

列出虚拟环境

lsvirtualenv
注意 1: 修改 mkvirtualenv 的默认路径: 在 我的电脑->右键->属性->高级系统设置->环境变量->系统变量 中添加一个 参数 WORKON_HOME ,将这个参数的值设置为你需要的路径
配置虚拟环境管理器工作目录
# 配置环境变量:
# 控制面板 => 系统和安全 => 系统 => 高级系统设置 => 环境变量 => 系统变量 => 点击新建 => 填入变量名与值
变量名:WORKON_HOME  变量值:自定义存放虚拟环境的绝对路径
eg: WORKON_HOME: D:\Virtualenvs

# 同步配置信息:
# 去向Python3的安装目录 => Scripts文件夹 => virtualenvwrapper.bat => 双击
注意 2:
创建虚拟环境的时候指定 Python 版本:
在使用 mkvirtualenv 的时候,可以指定 --python 的参数来指定具体的
python 路径:
mkvirtualenv --python==C:\Python36\python.exe hy_en

认识Web

互联网三大基石

URL : Uniform Resource Locator  的简写,统一资源定位符。
一个 URL  由以下几部分组成:
    scheme://host:port/path/?query-string=xxx#anchor
scheme:代表的是访问的协议,一般为 http  或者 https  以及 ftp  等。
host:主机名,域名,比如 www.baidu.com  。
port:端口号。当你访问一个网站的时候,浏览器默认使用80端口。
path:查找路径。比如: www.jianshu.com/trending/now  ,后面的 trending/now  就
是 path  。
query-string:查询字符串,也叫请求参数,比如: www.baidu.com/s?wd=python  ,后面的 wd=python  就是查
询字符串。
anchor:锚点,后台一般不用管,前端用来做页面定位的。
注意: URL  中的所有字符都是 ASCII  字符集,如果出现非 ASCII  字符,比如中文,浏览器会进行
编码再进行传输。
如:https://baike.baidu.com/item/%E9%BB%84%E6%99%93%E6%98%8E/6597?fr=aladdin#6

web服务器和应用服务器以及web应用框架

web服务器:负责处理http请求,响应静态文件,常见的有 Apache  , Nginx  以及微软的 IIS

应用服务器:负责处理逻辑的服务器。比如 php  、 python  的代码,是不能直接通过 nginx  这种web服务器来处理的,只能通过应用服务器来处理,常见的应用服务器有 uwsgi  、 tomcat  等。

web应用框架:一般使用某种语言,封装了常用的 web  功能的框架就是web应用框
架, flask  、 Django  以及Java中的 SSM(Spring+SpringMVC+Mybatis)  框架都是web应用框架。

Content-type和Mime-type的作用和区别:

两者都是指定服务器和客户端之间传输数据类型

Content-type:既可以指定传输数据类型,也可以指定数据编码类型,例如: text/html;charset=utf-8

Mime-type:不能指定传输的数据编码类型。例如: text/html

常用的数据类型如下:
text/html(默认的,html文件)
text/plain(纯文本)
text/css(css文件)
text/javascript(js文件)
application/x-www-form-urlencoded(普通的表单提交)
multipart/form-data(文件提交)
application/json(json传输)
application/xml(xml文件)

Flask简介:

是Python的一个WEB框架

此外,还有Django、tornado、cherryPy、WEB2PY

官方网站:http://flask.pocoo.org/

中文网站:https://dormousehole.readthedocs.org

github上的flask:https://github.com/pallets

github上的flask作者:https://github.com/mitsuhiko

flask  是一款非常流行的 Python Web  框架,出生于2010年,作者是 Armin Ronacher  ,本来这个项

目只是作者在愚人节的一个玩笑,后来由于非常受欢迎,进而成为一个正式的项目。目前为止最新

的版本是 1.0.2  。

flask  自2010年发布第一个版本以来,大受欢迎,深得开发者的喜爱,并且在多个公司已经得到

了应用,flask能如此流行的原因,可以分为以下几点:

   1.微框架、简洁、只做他需要做的,给开发者提供了很大的扩展性。

   2.Flask和相应的插件写得很好,用起来很爽。

   3.开发效率非常高,比如使用 SQLAlchemy  的 ORM  操作数据库可以节省开发者大量书写 sql     的时间。

 

Flask  的灵活度非常之高,他不会帮你做太多的决策,一些你都可以按照自己的意愿进行更改。

 比如:

   1.使用 Flask  开发数据库的时候,具体是使用 SQLAlchemy  还是 MongoEngine  ,选择权完全掌握在你自己的手中。区别于 Django  , Django  内置了非常完善和丰富的功能,并且如果你想替

换成你自己想要的,要么不支持,要么非常麻烦。

   2.把默认的 Jinja2  模板引擎替换成其他模板引擎都是非常容易的。

第一个flask程序:

用 pycharm2018.2.2 新建一个 flask 项目,新建项目的截图如下:

用 pycharm2017.1.4 新建一个 flask 项目,新建项目的截图如下:

#从flask包中导入Flask类
#Flask这个类是项目的核心,以后很多操作都基于这个类的对象
#注册url、注册蓝图等都是基于这个类的对象
from flask import Flask

#创建一个Flask对象,传递__name__参数进去
#__name__参数的作用:
#1.可以规定模版和静态文件的查找路径
#2.以后一些flask插件,如Flask-SQLAlchemy如果报错了,可通过__name__参数找到具体错误位置
app = Flask(__name__)


#@app.route:是一个装饰器
#@app.route('/')就是将url中 / 映射到hello_world设个视图函数上面
#以后你访问我这个网站的 / 目录的时候  会执行hello_world这个函数,然后将这个函数的返回值返回给浏览器
#如:www.bjsxt.com/----->hello_world 函数执行
@app.route('/')
def hello_world():
   # return 'Hello World!'
   return '尚学堂'

#如:www.bjsxt.com/list----->my_list 函数执行
@app.route('/list')
def my_list():
    return 'my list'


#如果这个文件是作为一个主文件运行,那么就执行app.run()方法
# app.run():Flask中的一个测试应用服务器
#也就是启动这个网站
if __name__ == '__main__':
    #默认为5000端口
    app.run()
        #app.run(port=8000)

Pycharm开启debug模式和修改host:

在代码中制作一个错误:

@app.route(**'/'**)

**def** hello_world():

  a = 1

  b = 0

  c = a/b

  **return** **'Hello World!'

启动并访问:

控制台倒是给出了错误提示信息,但是 我们希望在浏览器也能有相应的提示信息

对于 pycharm2017,可这样处理

**if** __name__ == **'__main__'**:

#app.run()

#PyCharm2017.xx 开启debug调式模式

 app.run(debug=**True**)

在 Pycharm 2018 中,如果想要开启 debug 模式和更改端口号,则需要编辑项目配置。直接在 app.run 中更改是无效的。示例图如下:

此时,注意 运行技巧

看结果

看浏览器访问结果

总结1:为什么需要开启DEBUG模式

  1. 1. 如果开启了DEBUG模式,那么在代码中如果抛出了异常,在浏览器的页面中可以看到具体的错误信息,以及具体的错误代码位置。方便开发者调试。
    
    2. 如果开启了DEBUG模式,那么以后在`Python`代码中修改了任何代码,只要按`ctrl+s`,`flask`就会自动的重新加载最新代码。不需要手动点击重新运行。
    
     
    

总结2:配置DEBUG模式的四种方式

  1. 1. 在`app.run()`中传递一个参数`debug=True`就可以开启`DEBUG`模式。
    
    ?    pycharm2017适用  但pycharm2018需要配置项目信息才行
    
    2. 给`app.deubg=True`也可以开启`debug`模式。
    
       如:
    
       app = Flask(__name__)
    
      app.debug = **True**
    
      pycharm2017适用  但pycharm2018不适用
    
    3. 通过配置参数的形式设置DEBUG模式:`app.config.update(DEBUG=True)`。
    
      如:
    
    app = Flask(__name__)
    
    app.config.update(DEBUG=**True**)
    
     pycharm2017适用  但pycharm2018不适用
    
    4. 通过配置文件的形式设置DEBUG模式:`app.config.from_object(config)`。
    
      如:
    

pycharm2017适用 但pycharm2018不适用

扩展点:了解即可

如果想要在网页上调试代码,那么应该输入PIN码。

注意问题

从导入文件修改配置
# app.debug = False
# app.config.update(DEBUG=True)
# app.config.from_object(config)
# 以上方法都不生效,在新的版本里

# app.config.from_pyfile('config.py') 从文件中导入配置
# app.config.from_pyfile('config.txt') 普通的txt文件也可以
# 如果文件名写错了,可以添加一个silent=True
# app.config.from_pyfile('config.txt',silent=True)

配置文件的使用(两种)

方式1:

使用app.config.from_object的方式加载配置文件:

  1. 导入`import config`。

  2. 使用`app.config.from_object(config)`。

方式2:

 使用'app.config.from_pyfile'的方式加载配置文件

  这种方式不需要'import',直接使用'app.config.from_pyfile("config.py")'

  注意这时 必须要写文件的全名,后缀名不能少

  1.这种方式加载配置文件 ,不局限于".py"文件,普通的".txt"也可以。

  2.这种方式,可以传递'silent=True',那么这个静态文件没有找到的时候,不会抛出异常

模版中使用url_for

模版中使用url_for
      模版中的`url_for`跟我们后台视图函数中的`url_for`使用起来基本是一模一样的。也是传递视图函数的名字,也可以传递参数。使用的时候,需要在`url_for`左右两边加上一个`{{ url_for('func') }}`
传参也分为两种方式
路径式传参:
python文件如:
@app.route('/accounts/login//')
def login(name):
    print(name)
    return render_template('login.html')
html页面使用如:
登录4
点击变为:
http://127.0.0.1:5000/accounts/login/momo/?p1=abc&p2=ddd

查询式传参:
html页面使用如:
登录3
点击变为:
http://127.0.0.1:5000/accounts/login/?p1=abc&p2=ddd


URL与函数的映射:

URL与函数的映射:
一个 URL  要与执行函数进行映射,使用的是 @app.route  装饰器。 @app.route  装饰器中,可以指定 URL  的规则来进行更加详细的映射。比如现在要映射一个文章详情的 URL  ,文章详情的 URL  是 /article/id/  ,id有可能为1、2、3...,那
么可以通过以下方式:
@app.route('/article//')
def article_detail(id):
    return '您请求的文章是:%s'% id
其中   ,尖括号是固定写法,语法为   , variable  默认的数据类型是字符串。如果需要指定类型,则要写成   ,其中 converter  就是类型名称,可以有以下几种:
1. string:如果没有指定具体的数据类型,那么默认就是使用`string`数据类型。
2. int:数据类型只能传递`int`类型。
3. float:数据类型只能传递`float`类型。
4. path:数据类型和`string`有点类似,都是可以接收任意的字符串,但是`path`可以接收路径,也就是说可以包含斜杠。
5. uuid:数据类型只能接收符合`uuid`的字符串。`uuid`是一个全宇宙都唯一的字符串,一般可以用来作为表的主键。
6. any:数据类型可以在一个`url`中指定多个路径。例如:
# /blog//
# /user//
@app.route('//')
def detail(url_path,id):
    if url_path=='blog':
        return "博客详情:%s" %id
    else :
        return "用户详情:%s" %id
        

URL传递参数的两种方式:

URL传递参数的两种方式:
两种方式传递参数
第一种:/路径/参数,(就是将参数嵌入到路径中),就是上面讲的。
第二种:/路径?参数名1=参数值1&参数名2=参数值2...,如:
http://127.0.0.1:5000/test/?wd=python&ie=ok
from flask import Flask,request
@app.route('/test/')
def test():
    wd = request.args.get('wd')
    ie = request.args.get('ie')
    print('ie:',ie)
    return '您通过查询字符串的方式传递的参数是:%s' % wd
额外补充:如果是 post  方法,则可以通过 request.form.get('id')  来进行获取。

使用总结:
如果你的这个页面的想要做`SEO`优化,就是被搜索引擎搜索到,那么推荐使用第一种形式(path的形式)。如果不在乎搜索引擎优化,那么就可以使用第二种(查询字符串的形式)。
# @app.route('/list/')
# def my_list():
#     return 1

# @app.route('/aid//')
# def article(id):
#     return 'aid' + id


# @app.route('/aid2//')  # 默认参数是str,自己可以定义数据类型
# def article(id):
#     return 'id是%s' % id

# @app.route('/aid2//')  # 默认参数是str,自己可以定义数据类型
# def article(id):
#     return 'id是%s' % id


# @app.route('/aid2//')  # 默认参数是str,自己可以定义数据类型
# def article(id):
#     return 'id是%s' % id

# @app.route('///')  # 默认参数是str,自己可以定义数据类型
# def article(module, id):
#     return 'id是%s' % id
url_for 好处
# 将来如果修改了url ,但是没有修改url函数名,就不用导出替换url了,路径变化的概率要高于函数名变化
# 函数接收两个以上参数,接收函数名作为第一个参数
# 接收对应url规则命名参数
# 如果出现其他参数,则会添加url 后面作为查询参数
# 自动转义特殊字符


# @app.route('/list', methods=['GET', 'POST'])  # 默认参数是str,自己可以定义数据类型
# def list7():
#     if request.method == 'GET':
#         username = request.args.get('username')
#         password = request.args.get('password')
#         # username = request.form.get('username')
#         # password = request.form.get('password')
#
#         return render_template('index1.html')
#     elif request.method == 'POST':
#         username = request.form.get('username')
#         password = request.form['password']
#
#         return 'post请求方式接收到参数 %s %s' % (username, password)

url_for使用详解

一般我们通过一个 URL  就可以执行到某一个函数。如果反过来,我们知道一个函数,怎么去获得
这个 URL  呢? url_for  函数就可以帮我们实现这个功能。
 
url_for()  :函数接收两个及以上的参数,他接收函数名作为第一个参数,接收对应URL规则的命名参数,如果还出现其他的参数,则会添加到 URL  的后面作为查询参数。
如:
@app.route('/post/list//')
def my_list(page):
    return 'my list'

@app.route('/')
def hello_world():
# 构建出来的url:/post/my_list/2?num=8
return url_for('my_list',page=2,num=8)
# return "/post/list/2?num=8"

为什么选择`url_for` 而不选择直接在代码中拼 URL  的原因有两点:
1. 将来如果修改了 URL  ,但没有修改该 URL  对应的函数名,就不用到处去替换 URL  了。
2.  url_for()  函数会转义一些特殊字符和 unicode  字符串,这些事情 url_for  会自动的帮我们
搞定。
如:
@app.route('/login/')
def login():
    return 'login'

@app.route('/')
def hello_world():
return url_for('login', next='/')
# /login/?next=/
# 会自动的将/编码,不需要手动去处理。
# url=/login/?next=%2F
# @app.route('/')
# def hello_world():
#     # a = 1/0
#     return url_for('tel')


# @app.route('/')
# def list1():
#     return url_for('list2', next='/哈哈')


# 将来如果修改了url ,但是没有修改url函数名,就不用导出替换url了,路径变化的概率要高于函数名变化
# 函数接收两个以上参数,接收函数名作为第一个参数
# 接收对应url规则命名参数
# 如果出现其他参数,则会添加url 后面作为查询参数
# 自动转义特殊字符

# @app.route('/ss/list2')
# def list2():
#     return 'list2'
# @app.route('/list2/')
# def list2(page):
#     return page
# @app.route('/list2/')
# def list2():
#     return

自定义URL转换器

自定义URL转换器的步骤:
转换器是一个类,且必须继承自werkzeug.routing.BaseConverter。
1. 实现一个类,继承自`BaseConverter`。
2. 在自定义的类中,重写`regex`,也就是这个变量的正则表达式。
3. 将自定义的类,映射到`app.url_map.converters`上。理解为加入字典DEFAULT_CONVERTERS中
    比如:
         app.url_map.converters['tel']=TelephoneConveter

注意:to_python方法和to_url方法的作用
 1.在转换器类中,实现to_python(self,value)方法,这个方法的返回值,将会传递到 view函数中作为参数。
 2.在转换器类中,实现to_url(self,values)方法,这个方法的返回值,将会在调用url_for函数的时候生成符合要求的URL形式。
# 自定义url转换器,在路径中,能匹配一个电话号码的类型参数
# 实现一个类,继承 'BaseConverter'
# 在自定义中的类,重写 regex 也就是这个变量的正则表达式
# 将自定义类,映射到app.url_map.converters上,理解为加入字典

#
# class TelephoneCoverNum(BaseConverter):
#     regex = r'1[0-9]\d{9}'
#
#
# app.url_map.converters['tel'] = TelephoneCoverNum
#
#
# @app.route('/telephone/')
# def tel(num):
#     return '%s' % num


@app.route('/new_list//')
def new_list(modules):
    # modules 是路径参数
    # 需要对modules进行拆分
    # 拆分后需要去数据库做查询操作
    lm = modules.split('+')

    return '%s' % lm

必须会的细节知识

class LiCovert(BaseConverter):
    def to_python(self, value: str):
        # 重写 to_python
        # 可以对value重新加工处理
        return value

app.url_map.converters['li'] = LiCovert


@app.route('/new_list//')
def new_list(modules):
    # modules 是路径参数
    # 需要对modules进行拆分
    # 拆分后需要去数据库做查询操作
    # lm = modules.split('+')
    print(modules)
    # print(modules[1])

    return '%s' % modules
在局域网中让其他电脑访问我的网站:
       如果想在同一个局域网下的其他电脑访问自己电脑上的Flask网站,那么可以设置`host='0.0.0.0'`才能访问得到。如:
   app.run(host='0.0.0.0')

指定端口号:
        Flask项目,默认使用`5000`端口。如果想更换端口,那么可以设置`port=9000`。如:
       app.run(host='0.0.0.0',port=9000)

url唯一:
在定义url的时候,一定要记得在最后加一个斜杠。
1. 如果不加斜杠,那么在浏览器中访问这个url的时候,如果最后加了斜杠,那么就访问不到。这样用户体验不太好。
2. 搜索引擎会将不加斜杠的和加斜杠的视为两个不同的url。而其实加和不加斜杠的都是同一个url,那么就会给搜索引擎造成一个误解。加了斜杠,就不会出现没有斜杠的情况。

`GET`请求和`POST`请求:
在网络请求中有许多请求方式,比如:GET、POST、DELETE、PUT请求等。那么最常用的就是`GET`和`POST`请求了。
1. `GET`请求:只会在服务器上获取资源,不会更改服务器的状态。这种请求方式推荐使用`GET`请求。
2. `POST`请求:会给服务器提交一些数据或者文件。一般POST请求是会对服务器的状态产生影响,那么这种请求推荐使用POST请求。
3. 关于参数传递:
    `GET`请求:把参数放到`url`中,通过`?xx=xxx`的形式传递的。因为会把参数放到url中,一眼就能看到你传递给服务器的参数。这样不太安全。
    `POST`请求:把参数放到`Form Data`中。会把参数放到`Form Data`中,避免了被偷瞄的风险,但是如果别人想要偷看你的密码,那么其实可以通过抓包的形式。因为POST请求可以提交一些数据给服务器,比如可以发送文件,那么这就增加了很大的风险。所以POST请求,对于那些有经验的黑客来讲,其实是更不安全的。
4. 在`Flask`中,`route`方法,默认将只能使用`GET`的方式请求这个url,如果想要设置自己的请求方式,那么应该传递一个`methods`参数。
  如:
@app.route('/login/',methods=['GET','POST'])
def login():
    if request.method =='GET':
        return render_template('login.html')
    else:
        return "success"

页面跳转和重定向

重定向分为永久性重定向和'暂时性重定向',在页面上体现的操作就是浏览器会从一个页面自动跳转到另外一个页面。比如用户访问了一个需要权限的页面,但是该用户当前并没有登录,因此我们应该给他重定向到登录页面。

永久性重定向: http  的状态码是 301  ,多用于旧网址被废弃了要转到一个新的网址确保用户的访问,最经典的就是京东网站,你输入 www.jingdong.com  的时候,会被重定向到 www.jd.com  ,因为 jingdong.com  这个网址已经被废弃了,被改成 jd.com  ,所以这种情况下应该用永久重定向。


暂时性重定向:http  的状态码是 302  ,表示页面的暂时性跳转。比如访问一个需要权限的网址,如果当前用户没有登录,应该重定向到登录页面,这种情况下,应该用暂时性重定向。用淘宝网演示为例进行讲解。

flask中重定向:重定向是通过 redirect(location,code=302)  这个函数来实现的, location  表示需要重定向到的 URL  ,应该配合之前讲的 url_for()  函数来使用, code  表示采用哪个重定向,默认是 302  也即 暂时性重定向  ,可以修改成 301  来实现永久性重定向。
例如:
from flask import Flask,request,url_for,redirect
app = Flask(__name__)
@app.route('/')
def hello_world():
    return 'Hello World!'

@app.route('/login/')
def login():
    return '这是登录页面'

#falsk中重定向
@app.route('/profile/')
def proflie():
    if request.args.get('name'):
        return '个人中心页面'
    else:
        # return redirect(url_for('login'))
        return redirect(url_for('login'),code=302)

if __name__ == '__main__':
    app.run(debug=True)

# ----------------------------------
# 页面跳转和重定向
#
# @app.route('/login/')
# def login():
#     return '登录页面'
#
#
# @app.route('/profile/')
# # 如果已经登录,去查看个人信息
# # 如果没有登录,则跳转到登录页面
# def profile():
#     uname = request.args.get('uname')
#     if uname and uname == 'momo':
#         return '登录成功'
#     else:
#         return redirect(url_for('login'),code=302)


视图函数Response返回值详解

视图函数Response返回值详解
关于响应(Response)
视图函数中可以返回以下类型的值:
1.Response  对象及其子类对象
2.字符串。其实 Flask  是根据返回的字符串类型,重新创建一个 werkzeug.wrappers.Response  对象, Response  将该字符串作为主体,状态码为200, MIME  类型为 text/html  ,然后返回该 Response  对象
3.元组。元组中格式是 (response,status,headers)  。 response  为一个字符串, status  值是
状态码, headers  是一些响应头。即(响应体,状态码,头部信息),也不一定三个都要写,写两个也是可以的。
4.如果不是以上三种类型。即如果视图函数返回的数据,不是字符串,也不是元组,也不是Response对象,那么就会将返回值传给` Response.force_type(rv,request.environ) `,然后再将`force_type`的返回值返回给前端。

自定义的`Response`对象的步骤
1. 继承自`Response`类。
2. 实现方法`force_type(cls,rv,environ=None)`。
3. 指定`app.response_class`为你自定义的`Response`对象。

如:自定义的`Response`对象将视图中返回的字典,转换成json对象,然后返回

class JSONResponse(Response):
      @classmethod
      def force_type(cls, response, environ=None):
          """
          这个方法只有视图函数返回 非字符串  非Response对象  非元组时才会调用
          response:视图函数的返回值
          """
          if  isinstance(response,dict):
              response = jsonify(response)
          #调用父类方法
          return super(JSONResponse, cls).force_type(response,environ)

app.response_class = JSONResponse


@app.route('/')
def resp1():
    content = '哈哈'
    resp = Response(content, status=200)
    resp.set_cookie('username', 'momo')
    resp.set_cookie('password', '123')
    # resp.content_type = 'str'
    return resp


# Response 对resp 对象加工后返回

class JSONResponse(Response):
    @classmethod
    def force_type(cls, response, environ=None):
        if isinstance(response, dict):
            # res = json.dumps(response)
            # 将字典类型数据转成一个长得像JSON对象,其实不是json
        # 满足类型是字典,再对其做加工操作
            resp = jsonify(response) # 转成json  #     将字典类型数据转成一个JSON对象
            return super(JSONResponse,cls).force_type(resp) # force_type里面必须传入一个json 格式的字符串
        
        

app.response_class = JSONResponse


@app.route('/myfile/')
def myfile():
    return {'sex':'man','age':18}

JinJa2 模板简介:

    在之前的意节中,视图园数只是直接返回文本,而在实际生产环境中其实根少这样用,因为实际的页面大多是带有样式和夏杂逻辑的HTML代码,这可以让浏览器演染出非常漂亮的页面。目前市面上有非常参的模板系统,其中最知名最好用的就是时2和州ak?,我们先来看一下这两个模板的特点和不同:
    1,J列a2:1n时a是日本寺庙的意思,并且寺庙的英文是tp1e和模板的英文tmp1ate的发音类似。|31n5a2是认的仿D时翰80模板的一个横板号引辈,由1致的作者开发。它速度快,被广泛使用,并且提供了可选的箱模板来保证执行环缓的安全,它有以下优点:
    ·让前演开发者和后端开发者工作分离。
    。减少F1k项目代码的调合性,页面逻辑放在模板中,业务逻辑放在视图逊数中,将页面逻福和业务逻辑解耦有利厅代码的维护。
    ·提供了控制语句、继承等高级功能,减少开发的复杂度。
    2.Marko:arko是另一个知名的模板。他从Django、3in时a2等模板倡鉴了很多语法和AP列,他有以下优点:
    ·性能和312相近,在这里可以看到:
    。有大型网站在使用,有成功的案例。Redd1t和豆着都在使用。
    。有知名的web框架支持,ylons和Pyranid这两个web框架内置模板就是ako。
    ·支持在模板中写几乎原生的Pythoni语法的代码,对Python.工程师比较友好,开发效率高,·自带完整的暖存系统。当然也提供了非常好的扩展借口,與容易切换成其他的暖存系统。
    Updated 2 days

jinja2模版介绍和查找路径

jinja2模版介绍和查找路径
       模板是一个 web  开发必备的模块。因为我们在渲染一个网页的时候,并不是只渲染一个纯文本字符串,而是需要渲染一个有富文本标签的页面。这时候我们就需要使用模板了。在 Flask  中,配
套的模板是 Jinja2  , Jinja2  的作者也是 Flask  的作者。这个模板非常的强大,并且执行效率
高。
不建议去官网学习,多,乱。
官网:http://jinja.pocoo.org/

查找路径:
1. 在渲染模版的时候,默认会从项目根目录下的`templates`目录下查找模版。
2. 如果不想把模版文件放在`templates`目录下,那么可以在`Flask`初始化的时候指定`template_folder`来指定模版的路径。

Flask渲染 Jinja  模板:
要渲染一个模板,通过 render_template 方法即可,如:

from flask import Flask,render_template
app = Flask(__name__)
@app.route('/')
def hello_world():
    return render_template('index.html')

模版传参及其技巧

模版传参及其技巧
1. 在使用`render_template`渲染模版的时候,可以传递关键字参数(命名参数)。以后直接在模版中使用就可以了。
模版传参
from flask import Flask,render_template
app = Flask(__name__)
  @app.route('/')
def hello_world():
    return  render_template('index.html',uname='momo')

页面取参



    
    SXT


     从模版中渲染的数据
     
{{ uname}} 2. 如果你的参数项过多,那么可以将所有的参数放到一个字典中,然后在传这个字典参数的时候,使用两个星号,将字典打散成关键字参数(也叫命名参数)。 如: @app.route('/') def hello_world(): context = { 'uname': 'momo', 'age': 18, 'country': 'china', 'childrens': { 'name': 'mjz', 'height': '62cm' } } return render_template('index.html',**context) 注意访问技巧:{{childrens.name}} 或者{{childrens['name'}}}

模版中使用url_for

模版中使用url_for
      模版中的`url_for`跟我们后台视图函数中的`url_for`使用起来基本是一模一样的。也是传递视图函数的名字,也可以传递参数。使用的时候,需要在`url_for`左右两边加上一个`{{ url_for('func') }}`
传参也分为两种方式
路径式传参:
python文件如:
@app.route('/accounts/login//')
def login(name):
    print(name)
    return render_template('login.html')
html页面使用如:
登录4
点击变为:
http://127.0.0.1:5000/accounts/login/momo/?p1=abc&p2=ddd

查询式传参:
html页面使用如:
登录3
点击变为:
http://127.0.0.1:5000/accounts/login/?p1=abc&p2=ddd


Jinja2过滤器


过滤器基本使用
举个例子,哪里用到了过滤器
访问:https://www.bufanbiz.com/,说明提示时间处就用到了过滤器


简单使用一下:

python文件如:
@app.route('/')
def hello_world():
    return render_template('index.html',postion=-1)

'''
html页面如:



    
    SXT


    

过滤器的基本使用

位置的绝对值为[未使用过滤器]:{{ postion}}

位置的绝对值为[使用过滤器]:{{ postion|abs}}

''' 简单总结: 1. 有时候我们想要在模版中对一些变量进行处理,那么就必须需要类似于Python中的函数一样,可以将这个值传到函数中,然后做一些操作。在模版中,过滤器相当于是一个函数,把当前的变量传入到过滤器中,然后过滤器根据自己的功能,再返回相应的值,之后再将结果渲染到页面中。 2. 基本语法:`{{ variable|过滤器名字 }}`。使用管道符号`|`进行组合。

过滤器介绍:

过滤器介绍:
        过滤器是通过管道符号(|)进行使用的,例如:{{ name|length }},将返回name的长度。过滤器相当于是一个函数,把当前的变量传入到过滤器中,然后过滤器根据自己的功能,再返回相应的值,之后再将结果渲染到页面中。Jinja2中内置了许多过滤器,
查看所有过滤器:http://jinja.pocoo.org/docs/dev/templates/#builtin-filters
现对一些常用的过滤器进行讲解:
abs(value):返回一个数值的绝对值。 例如:-1|abs。
default(value,default_value,boolean=false):如果当前变量没有值,则会使用参数中的值来代替。name|default('xiaotuo')——如果name不存在,则会使用xiaotuo来替代。boolean=False默认是在只有这个变量为undefined的时候才会使用default中的值,如果想使用python的形式判断是否为false,则可以传递boolean=true。也可以使用or来替换。
escape(value)或e:转义字符,会将<、>等符号转义成HTML中的符号。例如:content|escape或content|e。
first(value):返回一个序列的第一个元素。names|first。
format(value,*arags,**kwargs):格式化字符串。例如以下代码:
  {{ "%s" - "%s"|format('Hello?',"Foo!") }}将输出:Helloo? - Foo!

last(value):返回一个序列的最后一个元素。示例:names|last。
length(value):返回一个序列或者字典的长度。示例:names|length。
join(value,d='+'):将一个序列用d这个参数的值拼接成字符串。
safe(value):如果开启了全局转义,那么safe过滤器会将变量关掉转义。示例:content_html|safe。
int(value):将值转换为int类型。
float(value):将值转换为float类型。
lower(value):将字符串转换为小写。
upper(value):将字符串转换为小写。
replace(value,old,new): 替换将old替换为new的字符串。
truncate(value,length=255,killwords=False):截取length长度的字符串。
striptags(value):删除字符串中所有的HTML标签,如果出现多个空格,将替换成一个空格。
trim:截取字符串前面和后面的空白字符。
string(value):将变量转换成字符串。
wordcount(s):计算一个长字符串中单词的个数。


default过滤器详解

default过滤器详解
使用场景:个性签名
               用户设置了签名: 写什么就是什么
               用户没设置签名:默认给出“此人狠懒,暂无任何签名”

例:
#default过滤器的使用
@app.route('/')
def hello_world():
    context={
        'postion':-1
    }
    return render_template('index.html',**context)

html页面



    
    SXT


    

过滤器的基本使用

个性签名[使用过滤器]:{{ signature|default('此人很懒,没有任何说明')}}

访问结果: 但若将上述的例子代码稍微改一改,例: #default过滤器的使用 @app.route('/') def hello_world(): context={ 'postion':-1, 'signature':None } return render_template('index.html',**context) html页面 SXT

过滤器的基本使用

个性签名[使用过滤器]:{{ signature|default('此人很懒,没有任何说明')}}

访问结果: 若此时需要让default过滤器生效的话,需要在default中添加'boolean=True' 如:

个性签名[使用过滤器]:{{ signature|default('此人很懒,没有任何说明',boolean=True)}}

default过滤器使用总结: 使用方式`{{ value|default('默认值') }}`。 如果value这个`key`不存在,那么就会使用`default`过滤器提供的默认值。 如果value这个`key`存在,就不会使用`default`过滤器提供的默认值。但对于value的一些特殊值(例如:None、空字符串、空列表、空字典等),想使用`default`过滤器提供的默认值。,那么就必须要传递另外一个参数`{{ value|default('默认值',boolean=True) }}`。 扩展: 可以使用`or`来替代`default('默认值',boolean=True)`。例如:`{{ signature or '此人很懒,没有留下任何说明' }}`。 如:两种写法等价

个性签名[使用过滤器]:{{ signature|default('此人很懒,没有任何说明',boolean=True)}}

个性签名[使用过滤器]:{{ signature or '此人很懒,没有任何说明'}}

常用过滤器讲解

常用过滤器讲解
abs(value):返回一个数值的绝对值。 例如:-1|abs。
default(value,default_value,boolean=false):如果当前变量没有值,则会使用参数中的值来代替。name|default('xiaotuo')——如果name不存在,则会使用xiaotuo来替代。boolean=False默认是在只有这个变量为undefined的时候才会使用default中的值,如果想使用python的形式判断是否为false,则可以传递boolean=true。也可以使用or来替换。
escape(value)或e:转义字符,会将<、>等符号转义成HTML中的符号。例如:content|escape或content|e。
first(value):返回一个序列的第一个元素。names|first。
format(value,*arags,**kwargs):格式化字符串。例如以下代码:
  {{ "%s" - "%s"|format('Hello?',"Foo!") }}将输出:Helloo? - Foo!

last(value):返回一个序列的最后一个元素。示例:names|last。
length(value):返回一个序列或者字典的长度。示例:names|length。
join(value,d=u''):将一个序列用d这个参数的值拼接成字符串。
safe(value):如果开启了全局转义,那么safe过滤器会将变量关掉转义。示例:content_html|safe。
int(value):将值转换为int类型。
float(value):将值转换为float类型。
lower(value):将字符串转换为小写。
upper(value):将字符串转换为小写。
replace(value,old,new): 替换将old替换为new的字符串。
truncate(value,length=255,killwords=False):截取length长度的字符串。
striptags(value):删除字符串中所有的HTML标签,如果出现多个空格,将替换成一个空格。
trim:截取字符串前面和后面的空白字符。
string(value):将变量转换成字符串。
wordcount(s):计算一个长字符串中单词的个数。


总结:自动转译过滤器
jinja2模版 默认全局开启了 自动转译功能
1. `safe`过滤器:可以关闭一个字符串的自动转义。
2. `escape`过滤器:对某一个字符串进行转义。
3. `autoescape`jinja标签,可以对他里面的代码块关闭或开启自动转义。
    {% autoescape off/on %}
        ...代码块
    {% endautoescape %}



自定义过滤器

自定义模版过滤器:
只有当系统提供的过滤器不符合需求后,才须自定义过滤器
过滤器本质上就是一个函数。
如果在模版中调用这个过滤器,那么就会将这个变量的值作为第一个参数传给过滤器这个函数,
然后函数的返回值会作为这个过滤器的返回值。
需要使用到一个装饰器:`@app.template_filter('过滤器名称')`
例如:将新闻中出现的 所有“十大酷刑” 删除掉
#将模版设置为自动加载模式
app.config['TEMPLATES_AUTO_RELOAD']=True
@app.template_filter('cut')
def cut(value):
    value=value.replace("十大酷刑",'')
    return value

使用:

使用自定义过滤器:{{新闻内容值|cut}}

过滤器案例
from datetime import datetime
#需求:操作发布新闻 与现在的时间间隔
@app.template_filter('handle_time')
def handle_time(time):
      """
       time距离现在的时间间隔
       1. 如果时间间隔小于1分钟以内,那么就显示“刚刚”
       2. 如果是大于1分钟小于1小时,那么就显示“xx分钟前”
       3. 如果是大于1小时小于24小时,那么就显示“xx小时前”
       4. 如果是大于24小时小于30天以内,那么就显示“xx天前”
       5. 否则就是显示具体的时间 2018/10/20 16:15
       """
      if isinstance(time, datetime):
          now = datetime.now()
          timestamp = (now - time).total_seconds()
          if timestamp < 60:
              return "刚刚"
          elif timestamp >= 60 and timestamp < 60 * 60:
              minutes = timestamp / 60
              return "%s分钟前" % int(minutes)
          elif timestamp >= 60 * 60 and timestamp < 60 * 60 * 24:
              hours = timestamp / (60 * 60)
              return '%s小时前' % int(hours)
          elif timestamp >= 60 * 60 * 24 and timestamp < 60 * 60 * 24 * 30:
              days = timestamp / (60 * 60 * 24)
              return "%s天前" % int(days)
          else:
              return time.strftime('%Y/%m/%d %H:%M')
      else:
          return time

控制语句

if语句详解
所有的控制语句都是放在{% ... %}中,并且有一个语句{% endxxx %}来进行结束,Jinja中常用的控制语句有if/for..in..,现逐一对他们进行讲解:
if:if语句和python中的类似,可以使用>,<,<=,>=,==,!=来进行判断,也可以通过and,or,not,()来进行逻辑合并操作,以下看例子:
{% if uname =='momo' %}
   

莫莫

{% else %}

露露

{% endif %} {% if age >= 18 %}

{{ age }}岁,成年人,能进入网吧

{% else %}

{{ age }}岁,未成年人,禁止能进入网吧

{% endif %} 注意: `if`条件判断语句必须放在`{% if statement %}`中间,并且还必须有结束的标签`{% endif %}`。
控制语句

for循环语句详解
for...in...:for循环可以遍历任何一个序列包括列表、字典、元组。并且可以进行反向遍历,以下将用几个例子进行解释:
普通的遍历  列表:
    {% for user in users%}
  • {{ user}}
  • {% endfor %}
遍历字典: {% for key in person.keys() %} {{ key}} {% endfor %} {% for val in person.values() %} {{ val}} {% endfor %} {% for item in person.items() %} {{ item}} {% endfor %} {% for key,value in person.items() %} {{ value}} {% endfor %} 如果序列中没有值的时候,进入else,反向遍历用过滤器 reverse:
    {% for user in users|reverse %}
  • {{ user}}
  • {% else %}
  • 没有任何用户
  • {% endfor %}
并且Jinja中的for循环还包含以下变量,可以用来获取当前的遍历状态: | 变量 | 描述 | | --- | --- | | loop.index | 当前迭代的索引(从1开始) | | loop.index0 | 当前迭代的索引(从0开始) | | loop.first | 是否是第一次迭代,返回True或False | | loop.last | 是否是最后一次迭代,返回True或False | | loop.length | 序列的长度 | 总结: 在`jinja2`中的`for`循环,跟`python`中的`for`循环基本上是一模一样的。也是`for...in...`的形式。并且也可以遍历所有的序列以及迭代器。但是唯一不同的是,`jinja2`中的`for`循环没有`break`和`continue`语句。
for99乘法表案例:
代码如下:

    {% for x in range(1,10) %}
         
            {% for y in range(1,x + 1) %}
               
            {% endfor %}
         
    {% endfor %}
{{y}}*{{x}}={{ x*y }}

宏的概念及基本使用

宏的概念及基本使用
宏:
      模板中的宏跟python中的函数类似,可以传递参数,但是不能有返回值,可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量,以下将用一个例子来进行解释:
1. 定义宏:
    ```html
{% macro input(name,value="",type="text") %}
    
{% endmacro %}
    ```
2.使用宏:
 ```html
用户名: {{ input('username')}}
密码: {{ input('pwd',type='password') }}
{{ input(value='提交',type='submit') }}
``` 3.访问结果:

宏的导入和注意事项

宏的导入和注意事项
引入:实际开发中,不会把宏在一个页面内定义 并直接使用
        一般把宏定义放到一个专门的文件夹中,方便进行统一管理
       之后,哪一个页面需要使用某个宏,需要导入宏才能使用

导入宏两种方式:
1. `from '宏文件的路径' import 宏的名字 [as xxx]`。
   如:
  {% from "macros/macros.html" import input as inp %}
2. `import "宏文件的路径" as xxx  [with  context]`。
   如:
  {% import "macros/macros.html" as macros  with context %}

注意两点:
1. 宏文件路径,不要以相对路径去寻找,都要以`templates`作为绝对路径去找。
2. 如果想要在导入宏的时候,就把当前模版的一些参数传给宏所在的模版,那么就应该在导入的时候使用`with context`。
   如:
    {% import "macros/macros.html" as macros  with context %}


include标签使用详解

include标签使用详解
include标签:
1. 这个标签相当于是直接将指定的模版中的代码复制粘贴到当前位置。
2. `include`标签,如果想要使用父模版中的变量,直接用就可以了,不需要使用`with context`。
3. `include`的路径,也是跟`import`一样,直接从`templates`根目录下去找,不要以相对路径去找。

如:



    
    SXT


    
    {% include "common/head.html" %}
    
这是首页内容 {{ major }}

{% include "common/footer.html" %}

set和with语句以及模版中定义变量

set和with语句以及模版中定义变量
set语句:
在模版中,可以使用`set`语句来定义变量。示例如下:
```html

{% set  uname='momo'%}

用户名:{{ uname }}

``` 一旦定义了这个变量,那么在后面的代码中,都可以使用这个变量,就类似于Python的变量定义是一样的。 with语句: `with`语句定义的变量,只能在`with`语句块中使用,超过了这个代码块,就不能再使用了。示例代码如下: ```html {% with classroom='python202'%}

班级:{{ classroom }}

{% endwith %} ``` 注意: 关于定义的变量,`with`语句也不一定要跟一个变量,可以定义一个空的`with`语句, 需要在指定的区域才能使用的情况,可以set与with组合使用。 ```html {% with %} {% set pname='李思思' %}

娱乐县县长:{{ pname }}

{% endwith %} ```

加载静态文件

静态文件:css文件 js文件 图片文件等文件

加载静态文件使用的是url_for函数。然后第一个参数需要为static,第二个参数需要为一个关键字参数filename='路径'

语法:

{{ url_for("static",filename='xxx') }}

例如:

注意:

路径查找,要以当前项目的static目录作为根目录。

模版继承详解

模版继承详解

为什么需要模版继承:
       模版继承可以把一些公用的代码单独抽取出来放到一个父模板中。以后子模板直接继承就可以使用了。这样可以重复的利用代码,并且以后修改起来也比较方便。

模版继承语法:
       使用`extends`语句,来指明继承的父模板。父模板的路径,也是相对于`templates`文件夹下的绝对路径。示例代码如下:
{% extends "base.html" %}

block语法:
       一般在父模版中,定义一些公共的代码。子模板可能要根据具体的需求实现不同的代码。这时候父模版就应该有能力提供一个接口,让子模板来实现。从而实现具体业务需求的功能。

在父模板中:
```html
{% block block的名字 %}
{% endblock %}
```
在子模板中:
```html
{% block block的名字 %}
子模板中的代码
{% endblock %}
```

调用父模版代码block中的代码:
        默认情况下,子模板如果实现了父模版定义的block。那么子模板block中的代码就会覆盖掉父模板中的代码。如果想要在子模板中仍然保持父模板中的代码,那么可以使用`{{ super() }}`来实现。示例如下:
父模板:
```html
 {% block block_body %}
          

我是 父模版block_body处的内容

{% endblock %} ``` 子模板: ```html {% block block_body%} {{ super() }}

我是 子模版block_body处的内容

{% endblock %} ``` 调用另外一个block中的代码: 如果想要在另外一个模版中使用其他模版中的代码。那么可以通过`{{ self.其他block名字() }}`就可以了。示例代码如下: ```html {% block title %} sxt首页 {% endblock %} {% block block_body%} {{ self.title() }}

我是 子模版block_body处的内容

{% endblock %} ``` 其他注意事项: 1. 子模板中的代码,第一行,应该是`extends`。 2. 子模板中,如果要实现自己的代码,应该放到block中。如果放到其他地方,那么就不会被渲染。

add_url_rule和app.route原理剖析

add_url_rule和app.route原理剖析
`add_url_rule(rule,endpoint=None,view_func=None)`
         这个方法用来添加url与视图函数的映射。如果没有填写`endpoint`,那么默认会使用`view_func`的名字作为`endpoint`。以后在使用`url_for`的时候,就要看在映射的时候有没有传递`endpoint`参数,如果传递了,那么就应该使用`endpoint`指定的字符串,如果没有传递,那么就应该使用`view_func`的名字。
     例:
def my_list():
    return "我是列表页"

app.add_url_rule('/list/',endpoint='sxt',view_func=my_list)

`app.route(rule,**options)`装饰器:
这个装饰器底层,其实也是使用`add_url_rule`来实现url与视图函数映射的。

from flask import Flask,url_for
app = Flask(__name__)

@app.route('/',endpoint='hello')
def hello_world():
    #构建url  :/list/
    #研究app.add_url_rule()方法,若方法中【没有加】上endpoint时,可通过原来的函数名构建url,即url_for('原函数名')
    # print(url_for('my_list'))
    #研究app.add_url_rule()方法,若方法中【加上】endpoint时,不能再通过原来的函数名构建url,而需要endpoint的值才行
    # 即url_for('endpoint值')
    print(url_for('li'))
    return 'Hello World!'

def my_list():
    return   "这是列表页"

#通过app对象的add_url_rule方法 来完成url与视图函数的映射
app.add_url_rule('/list/',endpoint='li',view_func = my_list)

#讨论:add_url_rule()方法  与@app.route()装饰器的关系
#结论:@app.route()装饰器 底层就是借助于add_url_rule()方法来实现的

#请求上下文对象     项目一启动就会执行
with  app.test_request_context():
    # print(url_for('hello_world'))
    print(url_for('hello'))

if __name__ == '__main__':
    app.run(debug=True)


类视图

类视图
     之前我们接触的视图都是函数,所以一般简称视图函数。其实视图也可以基于类来实现,类视图的好处是支持继承,但是类视图不能跟函数视图一样,写完类视图还需要通过app.add_url_rule(url_rule,view_func)来进行注册。以下先对标准类视图进行讲解:

1.标准类视图使用步骤
1. 标准类视图,必须继承自`flask.views.View`.
2. 必须实现`dipatch_request`方法,以后请求过来后,都会执行这个方法。这个方法的返回值就相当于是之前的函数视图一样。也必须返回`Response`或者子类的对象,或者是字符串,或者是元组。
3. 必须通过`app.add_url_rule(rule,endpoint,view_func)`来做url与视图的映射。`view_func`这个参数,需要使用类视图下的`as_view`类方法类转换:`ListView.as_view('list')`。
4. 如果指定了`endpoint`,那么在使用`url_for`反转的时候就必须使用`endpoint`指定的那个值。如果没有指定`endpoint`,那么就可以使用`as_view(视图名字)`中指定的视图名字来作为反转。


from flask import Flask,views,url_for

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

#定义一个类视图
class  ListView(views.View):
    def dispatch_request(self):
        return "这是List列表"

#注册类视图
app.add_url_rule('/list/',endpoint='mlist',view_func=ListView.as_view('my_list'))


with  app.test_request_context():
    #若注册url时,没有指定endpoint,使用as_view()方法中的名称  来构建url
    # print(url_for('my_list'))
    # 若注册url时,有指定endpoint,就不能再使用as_view()方法中的名称 来构建url,而要使用endpoint的值来构建url
    print(url_for('mlist'))

2.类视图的好处
     1.可以继承,把一些共性的东西抽取出来放到父视图中,子视图直接拿来用就可以了。
     2.但是也不是说所有的视图都要使用类视图,这个要根据情况而定。视图函数用得最多

#类视图的好处:
#需求:以后有 好几个url,都需要返回json对象的格式
class  ListView2(views.View):
    def  getData(self):
        raise  NotImplementedError

    def  dispatch_request(self):
        return  jsonify(self.getData())

class  JSONView(ListView2):
    def getData(self):
        return {'uname':'momo','age':'22'}


class  JSONView2(ListView2):
    def getData(self):
        return {'bname':'水浒传','price':'89'}

app.add_url_rule('/json/',view_func=JSONView.as_view('my_json'))
app.add_url_rule('/json2/',view_func=JSONView2.as_view('my_json2'))

3.标准类视图使用场景







#需求2:有好几个url,跳转到到不同的页面时,会带一个相同的参数过去

# #登录功能
# class   LoginView(views.View):
#     def dispatch_request(self):
#         return    render_template('login.html',ads="茅台酒   998")
#
# #注册功能
# class   RegisterView(views.View):
#     def dispatch_request(self):
#        return    render_template('register.html',ads="茅台酒   998")


#改进:突出类视图的好处
class   ADSView(views.View):
    def  __init__(self):
        super(ADSView, self).__init__()
        self.context={
            'ads':'华为   5G'
        }


#登录功能
class   LoginView(ADSView):
    def dispatch_request(self):
        self.context.update({'pid':'好牛的一本书'})
        return    render_template('login.html',**self.context)

#注册功能
class   RegisterView(ADSView):
    def dispatch_request(self):
       return    render_template('register.html',**self.context)


app.add_url_rule('/login/',view_func=LoginView.as_view('login'))
app.add_url_rule('/register/',view_func=RegisterView.as_view('register'))

基于调度方法的类视图

基于调度方法的类视图
1. 基于方法的类视图,是根据请求的`method`来执行不同的方法的。如果用户是发送的`get`请求,那么将会执行这个类的`get`方法。如果用户发送的是`post`请求,那么将会执行这个类的`post`方法。其他的method类似,比如`delete`、`put`。

2. 这种方式,可以让代码更加简洁。所有和`get`请求相关的代码都放在`get`方法中,所有和`post`请求相关的代码都放在`post`方法中。就不需要跟之前的函数一样,通过`request.method == 'GET'`。

代码演示:

#定义一个基于方法调度的  类视图
class  LoginView(views.MethodView):
    def get(self):
        return  render_template('login.html')
    def  post(self):
        #模拟实现
        #拿到前端页面传过来的  账号  和密码 去数据库做查询操作 查询到 (跳转主页面) ,反之跳转到login.html页面并给出错误提示信息
        uname = request.form['uname']
        pwd = request.form['pwd']
        if  uname=="momo"  and  pwd =="123":
            return  render_template('index.html')
        else:
            return  render_template('login.html',error="用户名或者密码错误")

# 注册类视图
app.add_url_rule('/login/', view_func=LoginView.as_view('my_login'))

改进1:
#改进1
class  LoginView(views.MethodView):
    def get(self,error=None):
        return  render_template('login.html',error=error)
    def  post(self):
        #模拟实现
        #拿到前端页面传过来的  账号  和密码 去数据库做查询操作 查询到 (跳转主页面) ,反之跳转到login.html页面并给出错误提示信息
        uname = request.form['uname']
        pwd = request.form['pwd']
        if  uname=="momo"  and  pwd =="123":
            return  render_template('index.html')
        else:
            return  self.get(error="用户名或者密码错误")
 
# 注册类视图
app.add_url_rule('/login/', view_func=LoginView.as_view('my_login'))


html页面
账号:
密码:
{# {{ error }}#} {# 优化写法 :判断 #} {% if error %} {{ error }} {% endif %}
改进2: # 改进2 :基于调度方法的类视图 ,通常get()方法处理get请求,post()方法处理post请求,为了便于管理,不推荐post方法和get方法互相调用 class LoginView(views.MethodView): def __jump(self,error=None): return render_template('login.html', error=error) def get(self, error=None): return self.__jump() def post(self): # 模拟实现 #拿到前端页面传过来的 账号 和密码 去数据库做查询操作 查询到 (跳转主页面) ,反之跳转到login.html页面并给出错误提示信息 uname = request.form['uname'] pwd = request.form['pwd'] if uname == "momo" and pwd == "123": return render_template('index.html') else: return self.__jump(error="用户名或者密码错误") # 注册类视图 app.add_url_rule('/login/', view_func=LoginView.as_view('my_login'))

类视图中使用装饰器

类视图中使用装饰器

    简言之,python装饰器就是用于拓展【原来函数功能】的一种函数,这个【函数的特殊之处在于它的返回值也是一个函数】,使用python装饰器的 【好处】就是在不用更改原函数的代码前提下给函数增加新的功能。

1. 在视图函数中使用自定义装饰器,那么自己定义的装饰器必须放在`app.route`下面。否则这个装饰器就起不到任何作用。
例:
定义一个装饰器
#需求:查看设置个人信息时,只有检测到用户已经登录了才能查看,若没有登录,则无法查看并给出提示信息
def login_requierd(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        username = request.args.get("username")
        if username and username =='momo':
             return func(*args,**kwargs)
        else:
            return  '请先登录'
    return wrapper
 
使用自定义装饰器:
@app.route('/settings/')
@login_requierd
def settings():
    return '这是设置界面'

2. 在类视图中使用装饰器,需要重写类视图的一个类属性`decorators`,这个类属性是一个列表或者元组都可以,里面装的就是所有的装饰器。

定义一个装饰器
#需求:查看设置个人信息时,只有检测到用户已经登录了才能查看,若没有登录,则无法查看并给出提示信息
def login_requierd(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        username = request.args.get("username")
        if username and username =='momo':
             return func(*args,**kwargs)
        else:
            return  '请先登录'
    return wrapper
 
使用自定义装饰器:
class  ProfileView(views.View):
    decorators = [login_requierd]
    def dispatch_request(self):
        return '这是个人中心界面'

    app.add_url_rule('/profile/',view_func=ProfileView.as_view('profile'))


蓝图的基本使用

蓝图的基本使用
蓝图:
    之前我们写的url和视图函数都是处在同一个文件,如果项目比较大的话,这显然不是一个合理的结构,而蓝图可以优雅的帮我们实现这种需求。
1. 蓝图的作用就是让我们的Flask项目更加模块化,结构更加清晰,为了更好的管理项目  让项目达到分层解耦 而产生的。可以将相同模块的视图函数放在同一个蓝图下,同一个文件中,方便管理。
2. 基本语法:
    * 在蓝图文件中导入Blueprint:
from flask import Blueprint
user_bp = Blueprint('user',__name__)
    * 在主app文件中注册蓝图:
from flask import Flask
from blueprints.user import user_bp

app = Flask(__name__)
app.register_blueprint(user_bp)

3. 如果想要某个蓝图下的所有url都有一个url前缀,那么可以在定义蓝图的时候,指定url_prefix参数:
  user_bp = Blueprint('user',__name__,url_prefix='/user')
#个人中心的 url与视图函数
@user_bp.route('/profile/')
def profile():
    return '个人中心页面'

#个人设置中心的 url与视图函数
@user_bp.route('/settings/')
def settings():
    return '个人设置页面'
   
注意: 在定义url_prefix的时候,要注意后面的斜杠,如果给了,那么以后在定义url与视图函数的时候,就不要再在url前面加斜杠了。

访问结果如:




蓝图中模版文件寻找规则

蓝图中模版文件寻找规则
4. 蓝图模版文件的查找:
    * 如果项目中的templates文件夹中有相应的模版文件,就直接使用了。
    * 如果项目中的templates文件夹中没有相应的模版文件,那么就到在定义蓝图的时候指定的路径中寻找。并且蓝图中指定的路径可以为相对路径,相对的是当前这个蓝图文件所在的目录。比如:
news_bp = Blueprint('news',__name__,url_prefix='/news',template_folder='news_page')
项目截图为:


        
因为这个蓝图文件是在blueprints/news.py,那么就会到blueprints这个文件夹下的news_page文件夹中寻找模版文件。

小总结:
  常规:蓝图文件在 查找模版文件时,会以templates为根目录进行查找  
        news_bp =  Blueprint('news',__name__,url_prefix='/news')
  注意1:个性化coder   喜欢在【创建蓝图对象的时候】  指定 模版文件的查找路径 如下:
        news_bp =  Blueprint('news',__name__,url_prefix='/news',template_folder='news_page')
  注意2:只有确定templates目录下没有对应的 html文件名的时候,才会去蓝图文件指定的目录下查找,指定才会生效
  注意3:若templates目录下,有一个与蓝图文件指定的目录下同名的一个 html文件时,优先走templates目录下的东西

蓝图中静态文件寻找规则

蓝图中静态文件寻找规则
5. 蓝图中静态文件的查找规则:

    * 在模版文件中,加载静态文件,如果使用url_for('static'),那么就只会在app指定的静态文件夹目录下查找 静态文件。如:
html:

寻找结果如图:


  
  * 如果在加载静态文件的时候,指定的蓝图的名字,比如`news.static`,那么就会到这个蓝图指定的static_folder下查找静态文件。如:
python:
from flask import  Blueprint,render_template,url_for
news_bp = Blueprint('news',__name__,url_prefix='/news',template_folder='news_page',static_folder='news_page_static')

html:

寻找结果如图:



小总结:
蓝图文件查找方式1【掌握】:查找静态文件时,正常情况下,会以static为根目录进行查找


蓝图文件查找方式2【了解】:查找静态文件时,非正常情况下,需要用url_for('蓝图的名字.static'),
然后会去蓝图对象在创建时指定的静态文件夹目录下 去查找静态文件
news_bp =  Blueprint('news',__name__,url_prefix='/news',template_folder='news_page',static_folder='news_statics')


url_for反转蓝图注意事项

url_for反转蓝图注意事项

6. url_for反转蓝图中的视图函数为url:
     如果使用蓝图,那么以后想要反转蓝图中的视图函数为url,那么就应该在使用url_for的时候指定这个蓝图名字。 app类中、模版中、同一个蓝图类中都是如此。否则就找不到这个endpoint。
    如app类 blueprint_demo.py中:
        #如下写法:才找得到 url_for('蓝图名称.方法名')
    print(url_for('news.news_list'))#/news/list/

    如模版/templates/index.html中:
      新闻列表 OK写法
   {# 新闻列表 no Ok写法#}

    如同一个蓝图类/blueprints/news.py中:
     from flask import  Blueprint,render_template,url_for
news_bp = Blueprint('news',__name__,url_prefix='/news',template_folder='news_page',static_folder='news_page_static')

@news_bp.route('/list/')
def news_list():
     print(url_for('news.news_detail')) #/news/detail/
     return render_template('news_list.html')

@news_bp.route('/detail/')
def news_detail():
    return '新闻详情页面'


子域名实现详解

子域名实现详解
蓝图实现子域名:
1. 使用蓝图技术。
2. 在创建蓝图对象的时候,需要传递一个`subdomain`参数,来指定这个子域名的前缀。
        例如:cms_bp= Blueprint('cms',__name__,subdomain='cms')
3. 需要在主app文件中,需要配置app.config的SERVER_NAME参数。例如:
    app.config['SERVER_NAME']='momo.com:5000'
4. 在`C:\Windows\System32\drivers\etc`下,找到hosts文件,然后添加域名与本机的映射。 域名和子域名都需要做映射。例如:
127.0.0.1	momo.com
127.0.0.1	cms.momo.com

注意:
    * ip地址不能有子域名。
    * localhost也不能有子域名。

1.什么是cookie:

在网站中,http请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。

cookie的出现就是为了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的cookie数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是哪个了。

cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用cookie只能存储一些小量的数据。

2. cookie的有效期:

? 服务器可以设置cookie的有效期,以后浏览器会自动的清除过期的cookie。

3. cookie有域名的概念:

? 只有访问同一个域名,才会把之前相同域名返回的cookie携带给服务器。也就是说,访问百度的时候,不会把新浪网站的cookie发送给百度。

4.画个图演示Cookie的工作过程

。。。。。。

1. 设置cookie:
设置cookie是在Response的对象上设置。
`flask.Response`对象有一个`set_cookie`方法,可以通过这个方法来设置`cookie`信息。
key,value形式设置信息

2.查看cookie信息
在Chrome浏览器中查看cookie的方式:
    * 方式1.借助于 开发调式工具进行查看
    * 方式2.在Chrome的设置界面->高级设置->内容设置->所有cookie->找到当前域名下的cookie。 

3. 删除cookie:
    方式1:通过`Response对象.delete_cookie`,指定cookie的key,就可以删除cookie了。
    方式2:在客户端浏览器  人为的删除(清除浏览器浏览历史记录后,很多网站之前免密登录的都不好使了)

4. 设置cookie的有效期:

5. 设置cookie的有效域名:

演示代码如下:
from flask import Flask,request,Response

app = Flask(__name__)

@app.route('/')
def hello_world():
    resp = Response("SXT")
    #设置Cookie信息
    resp.set_cookie('username','momo')
    resp.set_cookie('pwd','123456')
    return resp

@app.route('/del/')
def delete_cookie():
    resp = Response("删除cookie")
    #删除Cookie信息
    resp.delete_cookie('username')
    return resp

if __name__ == '__main__':
    app.run(debug=True)


4. 设置cookie的有效期:

4. 设置cookie的有效期:
    * max_age:以秒为单位,距离现在多少秒后cookie会过期。
    * expires:为datetime类型。这个时间需要设置为格林尼治时间,相对北京时间来说 会自动+8小时
    * 如果max_age和expires都设置了,那么这时候以max_age为标准。
    * 默认的过期时间:如果没有显示的指定过期时间,那么这个cookie将会在浏览器关闭后过期。
 
 注意:
max_age在IE8以下的浏览器是不支持的。
expires虽然在新版的HTTP协议中是被废弃了,但是到目前为止,所有的浏览器都还是能够支持,所以如果想要兼容IE8以下的浏览器,那么应该使用expires,否则可以使用max_age。


演示代码如下:

from flask import Flask,request,Response
from datetime import datetime,timedelta
app = Flask(__name__)
@app.route('/createCookie/')
def createCookie():
    resp = Response("服务器端通过Response对象创建Cookie信息  并返回给客户端  并保存在客户端")
    #设置Cookie的有效期【存活时间】方式1 :max_age=以秒为单位【距离现在多少秒后cookie会过期】
    # resp.set_cookie('uname',"momo",max_age=1200)
    #设置Cookie的有效期【存活时间】方式2 : expires= datetime类型。
    #这个时间需要设置为格林尼治时间,相对北京时间来说 会自动+8小时
    # ex = datetime(year=2019, month=2, day=28, hour=0, minute=0, second=0)
    ex = datetime(year=2019, month=2, day=27, hour=16, minute=0, second=0)

    # resp.set_cookie('uname', "momo", expires=ex)
    ex2 =datetime.now() + timedelta(days=30,hours=16)
    #如果max_age和expires都设置了,那么这时候以max_age为标准
    # resp.set_cookie('pwd',"123456",max_age=300,expires=ex2)
    resp.set_cookie('pwd',"123456",expires=ex2)
    return resp

if __name__ == '__main__':
    app.run(debug=True)






5. 设置cookie的有效域名:

5. 设置cookie的有效域名:
cookie默认是只能在主域名下使用。
如果想要在子域名下使用,那么应该给`set_cookie`传递一个参数`domain='.momo.com'`,这样其他子域名才能访问到这个cookie信息。

代码演示如下:

from flask import Blueprint,request

bp = Blueprint('cms',__name__,subdomain='cms')

@bp.route('/')
def index():
    # request.args
    # request.form
    # request.files
    uname = request.cookies.get('uname')
    return uname or "没有获取到cookie"

windows上的hosts文件如下:


from flask import Flask,Response,request
from datetime import datetime,timedelta
from cmsblueprint  import bp
app = Flask(__name__)

app.register_blueprint(bp)
app.config['SERVER_NAME'] = 'momo.com:5000'

@app.route('/createCookie/')
def createCookie():
    resp = Response("服务器端通过Response对象创建Cookie信息  并返回给客户端  并保存在客户端")
    ex = datetime(year=2019, month=2, day=27, hour=16, minute=0, second=0)
    ex2 =datetime.now() + timedelta(days=30,hours=16)
    resp.set_cookie('pwd',"123456",expires=ex2)

    #给Cookie信息设置 存活时间  【有效域名---》子域名】
    resp.set_cookie('uname', "momo", expires=ex,domain=".momo.com")
    return resp


if __name__ == '__main__':
    app.run(debug=True)

session:

session:
session技术  也叫 会话技术。

1. session的基本概念:
     session和cookie的作用有点类似,都是为了存储用户相关的信息,都是为了解决http协议无状态的这个特点。不同的是,cookie信息是存储在客户端,而session信息是存储在服务器端。
     需要注意的是,不同的语言,不同的框架,有不同的实现。虽然底层的实现不完全一样,但目的都是让服务器端能方便的存储数据而产生的。
     session的出现,是为了解决cookie存储数据不安全的问题的。

2. session的跟踪机制跟cookie有关:
Flask框架中,session的跟踪机制跟Cookie有关,这也就意味着脱离了Cookie,session就不好使了。
因为session跟踪机制跟cookie有关,所以,要分服务器端和客户端分别起到什么功能来理解。

    * session工作过程:
服务器端可以采用类似于mysql、redis等技术来存储session信息。
原理是,客户端发送验证信息过来(比如用户名和密码),服务器验证成功后,
把用户的相关信息存储到服务器端的session中(可想象为一个容器),
再通过盐的机制,盐起到混淆原数据的作用(类似于加密),
然后随机生成一个唯一的session_id,用来标识(用户名和密码)并存储到session中,
之后再把这个session_id存储到cookie中返回给浏览器。

浏览器以后再请求我们服务器的时候,就会把这个session_id通过Cookie技术自动的发送给服务器,
服务器端再从cookie中提取session_id,然后从服务器的session容器中找到这个用户的相关信息。
这样就可以达到安全识别用户的需求了。

存储在服务器的数据会更加的安全,不容易被窃取。
但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器已经发展至今,一些session信息还是绰绰有余的。
        
       *服务器端功能:
        1.把用户的相关信息存储到服务器端的session中
        2.通过盐的机制加密随机生成一个唯一的session_id,用来标识用户相关信息并将session_id也存入session中
        3.把session作为cookie的key,session_id作为cookie的value创建cookie信息并返回给客户端
        4.客户端发送第二次以后的请求时,获取cookie信息,与服务器端session容器中session_id对比,得出指定  用户信息。

    * 客户端功能:
         1.通过cookie存储session加密后的session_id信息
         2.以后浏览器再请求服务器的时候,就会自动的把cookie信息(包含session_id)发送给服务器

3.扩展面试题:
若客户端禁用了浏览器的Cookie功能,session功能想继续保留,该咋整?给出你的实现思路(能代码实现最好)

Flask操作session:

Flask操作session:

1. 设置session:
通过`flask.session`就可以操作session了。操作`session`就跟操作字典是一样的。
session['uname']='momo'。

2. 获取session:
  也是类似字典,
  session.get(key)。

3. 删除session中的值:
  也是类似字典。可以有2种方式删除session中的值。
    * session.pop(key)。
    * session.clear():删除session中所有的值。

4. 设置session的有效期:
 如果没有设置session的有效期。那么默认就是浏览器关闭后过期。
 如果设置session.permanent=True,那么就会默认在31天后过期。
 如果不想在31天后过期,按如下步骤操作
1:session.permanent=True
2:可以设置`app.config['PERMANENT_SESSION_LIFETIME'] =   timedelta(hour=2)`在两个小时后过期。


代码演示如下:

from flask import Flask,session,Response
import os
from datetime import timedelta
app = Flask(__name__)

#设置SECRET_KEY
app.config['SECRET_KEY'] = os.urandom(24)
# 5.设置session的有效期方式2【指session可以往后活多长时间】
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)

# 1.设置session
@app.route('/')
def  index():
    session['uname'] = 'momo'
    session['pwd'] = '123'
    #底层
    # resp = Response()
    # resp.set_cookie('session')

    #4.设置session的有效期方式1【持久化 31天】
    session.permanent = True
    # print(type(session))
    return 'Hello World!'

#2.获取session
@app.route('/getSession/')
def getSession():
    uname = session.get('uname')
    pwd = session.get('pwd')
    print(pwd)
    return uname or '没有session'


#3.删除session
@app.route('/deleteSession/')
def deleteSession():
    #删除指定的key的session
    # session.pop('uname')
    #删除session中的所有的key 【删除所有】
    session.clear()
    return '删除成功'

if __name__ == '__main__':
    app.run(debug=True)

Local对象隔离线程间的对象_即ThreadLocal变量

Local对象隔离线程间的对象_即ThreadLocal变量
1.Local对象:
在Flask中,类似于`request`对象,其实是绑定到了一个`werkzeug.local.Local`对象上。
这样,即使是同一个对象,那么在多个线程中都是隔离的。类似的对象还有`session`以及`g`对象。		    
       from werkzeug.local import Local
       #flask=werkzeug + sqlalchemy + jinja2
       

2.ThreadLocal变量:
Python提供了ThreadLocal 变量,它本身是一个全局变量,
但是每个线程却可以利用它来保存属于自己的私有数据,
这些私有数据对其他线程也是不可见的。

3.总结:
只要满足绑定到"local"或"Local"对象上的属性,在每个线程中都是隔离的,那么他就叫做`ThreadLocal`对象,也叫'ThreadLocal'变量。

4.代码演示:

from threading import Thread,local
local =local()
local.request = '具体用户的请求对象'
class MyThread(Thread):
    def run(self):
        local.request = 'haha'
        print('子线程:',local.request)

mythread = MyThread()
mythread.start()
mythread.join()
print('主线程:',local.request)


from werkzeug.local import Local
local = Local()
local.request = '具体用户的请求对象'
class MyThread(Thread):
    def run(self):
        local.request = 'tantan'
        print('子线程:',local.request)

mythread = MyThread()
mythread.start()
mythread.join()

print('主线程:',local.request)
    

Flask_app上下文详解:

Flask_app上下文详解:
app上下文,也叫应用上下文。
应用上下文:
应用上下文是存放到一个`LocalStack`的栈中。和应用app相关的操作就必须要用到应用上下文,
比如通过`current_app`获取当前的这个`app`名字。

注意1:
在视图函数中,不用担心应用上下文的问题。因为视图函数要执行,那么肯定是通过访问url的方式执行的,
那么这种情况下,Flask底层就已经自动的帮我们把应用上下文都推入到了相应的栈中。

注意2:
如果想要在视图函数外面执行相关的操作,
比如获取当前的app名称,那么就必须要手动推入应用上下文:
   第一种方式:便于理解的写法

from flask import Flask,current_app

app = Flask(__name__)

#app上下文
app_context = app.app_context()
app_context.push()
print(current_app.name)

@app.route('/')
def hello_world():
    print(current_app.name) #获取应用的名称
    return 'Hello World!'

if __name__ == '__main__':
    app.run(debug=True)
   第二种方式:用with语句

from flask import Flask,current_app

app = Flask(__name__)

#app上下文
#换一种写法
with app.app_context():
   print(current_app.name)

@app.route('/')
def hello_world():
    print(current_app.name) #获取应用的名称
    return 'Hello World!'


if __name__ == '__main__':
    app.run(debug=True)

Flask_request上下文详解:

Flask_request上下文详解:
请求上下文:
请求上下文也是存放到一个`LocalStack`的栈中。
和请求相关的操作就必须用到请求上下文,比如使用`url_for`反转视图函数。

注意1:
在视图函数中,不用担心请求上下文的问题。
因为视图函数要执行,那么肯定是通过访问url的方式执行的,
那么这种情况下,Flask底层就已经自动的帮我们把应用上下文和请求上下文都推入到了相应的栈中。

注意2:
如果想要在视图函数外面执行相关的操作,
比如反转url,那么就必须要手动推入请求上下文:
    底层代码执行说明:
    * 推入请求上下文到栈中,会首先判断有没有应用上下文,
    * 如果没有那么就会先推入应用上下文到栈中,
    * 然后再推入请求上下文到栈中

from flask import Flask,url_for

app = Flask(__name__)

#request上下文
@app.route('/hi')
def hi():
    print(url_for('my_list')) #获取构建得到的url
    return 'Hello World!'
@app.route('/list/')
def my_list():
    return '返回列表'

# print(url_for('my_list')) #获取构建得到的url
# with app.app_context():
#     print(url_for('my_list'))
#查看报错源码

with app.test_request_context():
    #手动推入一个请求上下文到请求上下文栈中
    #如果当前app应用上下文栈中没有app应用上下文
    #那么会首先推入一个app应用上下文到栈中
    print(url_for('my_list'))

if __name__ == '__main__':
    app.run(debug=True)

总结:
为什么上下文需要放在栈中?
1.应用上下文:Flask底层是基于werkzeug,werkzeug是可以包含多个app的,所以这时候用一个栈来保存。
如果你在使用app1,那么app1应该是要在栈的顶部,如果用完了app1,那么app1应该从栈中删除。方便其他代码使用下面的app。

2.如果在写测试代码,或者离线脚本的时候,我们有时候可能需要创建多个请求上下文,这时候就需要存放到一个栈中了。使用哪个请求上下文的时候,就把对应的请求上下文放到栈的顶部,用完了就要把这个请求上下文从栈中移除掉。

Flask_线程隔离的g对象使用详解:

Flask_线程隔离的g对象使用详解:
保存为全局对象g对象的好处:
g对象是在整个Flask应用运行期间都是可以使用的。
并且也跟request一样,是线程隔离的。
这个对象是专门用来存储开发者自己定义的一些数据,方便在整个Flask程序中都可以使用。
一般使用就是,将一些经常会用到的数据绑定到上面,以后就直接从g上面取就可以了,而不需要通过传参的形式,这样更加方便。

g对象使用场景:有一个工具类utils.py 和 用户办理业务:
def funa(uname):
    print('funa  %s' % uname)

def funb(uname):
    print('funb %s' % uname)

def func(uname):
    print('func %s' % uname)

 用户办理业务:
from flask import Flask,request
from  utils import  funa,funb,func
app = Flask(__name__)

#Flask_线程隔离的g对象使用详解
@app.route("/profile/")
def my_profile():
    #从url中取参
    uname = request.args.get('uname')
    #调用功能函数办理业务
    funa(uname)
    funb(uname)
    func(uname)
    #每次都得传参 麻烦,引入g对象进行优化
    return "办理业务成功"

if __name__ == '__main__':
    app.run(debug=True)

优化工具类utils.py:

from flask import g

def funa():
    print('funa  %s' % g.uname)

def funb():
    print('funb %s' % g.uname)

def func():
    print('func %s' % g.uname)
 优化用户办理业务:
from flask import Flask,request,g
from utils import  funa,funb,func
app = Flask(__name__)

#Flask_线程隔离的g对象使用详解
@app.route("/profile/")
def my_profile():
    #从url中取参
    uname = request.args.get('uname')
    #调用功能函数办理业务
    # funa(uname)
    # funb(uname)
    # func(uname)
    #每次都得传参 麻烦,引入g对象进行优化
    g.uname = uname
    funa()
    funb()
    func()
    return "办理业务成功"

if __name__ == '__main__':
    app.run(debug=True)

Flask_钩子函数概念_常见的钩子函数:

Flask_钩子函数概念_常见的钩子函数:

1.钩子函数概念:
   在Flask中钩子函数是使用特定的装饰器装饰的函数。为什么叫做钩子函数呢,是因为钩子函数可以在正常执行的代码中,插入一段自己想要执行的代码。那么这种函数就叫做钩子函数。

2.常见的钩子函数:
before_first_request:处理项目的第一次请求之前执行。
     @app.before_first_request
      def first_request():
          print 'first time request'

before_request:在每次请求之前执行。通常可以用这个装饰器来给视图函数增加一些变量。请求已经到达了Flask,但是还没有进入到具体的视图函数之前调用。一般这个就是在视图函数之前,我们可以把一些后面需要用到的数据先处理好,方便视图函数使用。
        @app.before_request
         def before_request():
             if not hasattr(g,'glo1'):
                   setattr(g,'glo1','想要设置的')

teardown_appcontext:不管是否有异常,注册的函数都会在每次请求之后执行。
      @app.teardown_appcontext
      def teardown(exc=None):
           if exc is None:
               db.session.commit()
           else:
              db.session.rollback()
           db.session.remove()

template_filter:在使用Jinja2模板的时候自定义过滤器。
    @app.template_filter("upper")
    def upper_filter(s):
       return s.upper()

context_processor:上下文处理器。使用这个钩子函数,必须返回一个字典。这个字典中的值在所有模版中都可以使用。这个钩子函数的函数是,如果一些在很多模版中都要用到的变量,那么就可以使用这个钩子函数来返回,而不用在每个视图函数中的`render_template`中去写,这样可以让代码更加简洁和好维护。
    @app.context_processor
    def context_processor():
        if hasattr(g,'user'):
            return {"current_user":g.user}
        else:
           return {}

errorhandler:errorhandler接收状态码,可以自定义返回这种状态码的响应的处理方法。在发生一些异常的时候,比如404错误,比如500错误,那么如果想要优雅的处理这些错误,就可以使用`errorhandler`来出来。
    @app.errorhandler(404)
    def page_not_found(error):
        return 'This page does not exist',404

Flask_before_first_request和before_request详解:

Flask_before_first_request和before_request详解:
1.before_first_request:处理项目的第一次请求之前执行。
  
from flask import Flask,request,session,current_app,url_for,g
import os

app = Flask(__name__)

@app.route('/')
def hello_world():
    print("hi")
    return "hello world "

@app.before_first_request
def first_request():
    print('hello world')


if __name__ == '__main__':
    app.run(debug=True)

2.before_request:在每次请求之前执行。通常可以用这个装饰器来给视图函数增加一些变量。
请求已经到达了Flask,但是还没有进入到具体的视图函数之前调用。一般这个就是在视图函数之前,
我们可以把一些后面需要用到的数据先处理好,方便视图函数使用。

from flask import Flask,request,session,current_app,url_for,g
import os

app = Flask(__name__)
app.config['SECRET_KEY']=os.urandom(24)  #加盐  混淆原数据的作用
@app.route('/')
def hello_world():
    print("hi")
    session['uname']="momo"
    return "hello world "

@app.route('/li')
def mylist():
    print("mylist")
    # print("直接取出",g.user)
    if hasattr(g,"user"):
        print("条件取出", g.user)
    return "hello world "

@app.before_request
def before_request():
    # print('在视图函数执行之前执行的钩子函数')
    # 场景:若用户已经登录了,验证时把用户名放入session中,之后取出来,放入钩子函数,以后访问的视图函数中可直接取出来使用
   uname = session.get('uname')
   print(uname)
   if uname:
       g.user = uname

if __name__ == '__main__':
    app.run(debug=True)

钩子函数context_processor详解:

钩子函数context_processor详解:
1.context_processor:上下文处理器。使用这个钩子函数,必须返回一个字典。这个字典中的值在所有模版中都可以使用。这个钩子函数的函数是,如果一些在很多模版中都要用到的变量,那么就可以使用这个钩子函数来返回,而不用在每个视图函数中的`render_template`中去写,这样可以让代码更加简洁和好维护。

2.代码演示:

from flask import Flask,request,session,current_app,url_for,g,render_template
import os

app = Flask(__name__)
app.config['SECRET_KEY']=os.urandom(24)  #加盐  混淆原数据的作用
@app.route('/')
def hello_world():
    print("hi")
    session['uname']="momo"
    # return "hello world "
    return  render_template("index.html")
@app.route('/li')
def mylist():
    print("mylist")
    # print("直接取出",g.user)
    if hasattr(g,"user"):
        print("条件取出", g.user)
    # return "hello world "
    return render_template('list.html')

@app.before_request
def before_request():
    # print('在视图函数执行之前执行的钩子函数')
    # 场景:若用户已经登录了,验证时把用户名放入session中,之后取出来,放入钩子函数,以后访问的视图函数中可直接取出来使用
   uname = session.get('uname')
   print(uname)
   if uname:
       g.user = uname

@app.context_processor
def context_processor():
    if hasattr(g,'user'):
        return {"current_user":g.user}
    else:
        return {}


if __name__ == '__main__':
    app.run(debug=True)

钩子函数errorhandler详解:

钩子函数errorhandler详解:
1.errorhandler:errorhandler接收状态码,可以自定义返回这种状态码的响应的处理方法。在发生一些异常的时候,比如404错误,比如500错误,那么如果想要优雅的处理这些错误,就可以使用`errorhandler`来出来。
需要注意几点:
    * 在errorhandler装饰的钩子函数下,记得要返回相应的状态码。
    * 在errorhandler装饰的钩子函数中,必须要写一个参数,来接收错误的信息,如果没有参数,就会直接报错。
    * 使用`flask.abort`可以手动的抛出相应的错误,比如开发者在发现参数不正确的时候可以自己手动的抛出一个400错误。

2.常见的500错误处理:
from flask import Flask,request,session,current_app,url_for,g,render_template,abort
import os

app = Flask(__name__)
app.config['SECRET_KEY']=os.urandom(24)  #加盐  混淆原数据的作用
@app.route('/')
def hello_world():
    print("hi")
    session['uname']="momo"
    #500
    print(g.user)
    return  render_template("index.html")


@app.errorhandler(500)
def server_error(error):
    return render_template('500.html'),500

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

3.常见的404错误处理:

from flask import Flask,request,session,current_app,url_for,g,render_template,abort
import os

app = Flask(__name__)

@app.route('/li')
def mylist():
    print("mylist")
    if hasattr(g,"user"):
        print("条件取出", g.user)
    return render_template('list.html')

@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'),404

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

4.Flask中的abort函数可以手动的抛出相应的错误(如400):

from flask import Flask,request,session,current_app,url_for,g,render_template,abort
import os

app = Flask(__name__)
app.config['SECRET_KEY']=os.urandom(24)  #加盐  混淆原数据的作用
@app.route('/')
def hello_world():
    print("hi")
    session['uname']="momo"
    return  render_template("index.html")

@app.errorhandler(400)
def args_error(error):
    return '您的参数不正确',400

#该功能需要先登录才能访问
@app.route('/setting/')
def setting():
    uname = session.get("uname")
    if uname :
        return '欢迎来到设置页面'
    else:
        # 如果没有登录,这时候我就让他跳转到400错误
        abort(400)
    return render_template('list.html')

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

Flask_信号机制:

Flask_信号机制:
1.信号机制:
     大白话来说,类似于两方属于敌对关系时,某人在敌对方阵营进行交谈,一旦遇到特殊情况,某人便会发送信号,他的同伙接收(监听)到他发的信号后,同伙便会做出一系列的应对策略(杀进去|撤退)。
    flask中的信号使用的是一个第三方插件,叫做blinker。通过pip list看一下,如果没有安装,通过以下命令即可安装blinker:
    pip install blinker

2.自定义信号步骤
  自定义信号可分为3步来完成。
  第一是创建一个信号,第二是监听一个信号,第三是发送一个信号。
  以下将对这三步进行讲解:
    1. 创建信号:定义信号需要使用到blinker这个包的Namespace类来创建一个命名空间。比如定义一个在访问了某个视图函数的时候的信号。示例代码如下:

    # Namespace的作用:为了防止多人开发的时候,信号名字冲突的问题
    from blinker import Namespace
    mysignal = Namespace()
    signal1 = mysignal.signal('信号名称')
  
    2. 监听信号:监听信号使用signal1对象的connect方法,在这个方法中需要传递一个函数,用来监听到这个信号后做该做的事情。示例代码如下:
     
    def func1(sender,uname):
        print(sender)
        print(uname)
    signal1.connect(func1)


    3. 发送信号:发送信号使用signal1对象的send方法,这个方法可以传递一些其他参数过去。示例代码如下:
  
signal1.send(uname='momo')

3.代码演示:
from flask import Flask
from blinker import Namespace
app = Flask(__name__)

#【1】信号机制   3步走
# Namespace:命名空间
#1.定义信号
sSpace = Namespace()
fire_signal = sSpace.signal('发送信号火箭')

#2.监听信号
def fire_play(sender):
    print(sender)
    print("start play")
fire_signal.connect(fire_play)

#3.发送一个信号
fire_signal.send()

if __name__ == '__main__':
    app.run(debug=True)

4.了解Flask中文网信号:

http://docs.jinkan.org/docs/flask/signals.html

Flask_信号使用场景_存储用户登录日志:

Flask_信号使用场景_存储用户登录日志:
1.信号使用场景
   定义一个登录的信号,以后用户登录进来以后
   就发送一个登录信号,然后能够监听这个信号
   在监听到这个信号以后,就记录当前这个用户登录的信息
   用信号的方式,记录用户的登录信息即登录日志

2.编写一个signals.py文件创建登录信号
from blinker  import  Namespace
from datetime import datetime
from flask import request,g

namespace  = Namespace()

#创建登录信号
login_signal = namespace.signal('login')

def login_log(sender):
    # 用户名  登录时间  ip地址
    now = datetime.now()
    ip = request.remote_addr
    log_data = "{uname}*{now}*{ip}".format(uname=g.uname, now=now, ip=ip)
    with open('login_log.txt','a') as f:
        f.write(log_data + "\n")
        f.close()

#监听信号
login_signal.connect(login_log)

3.使用信号存储用户登录日志

from flask import Flask,request,g

from signals import  login_signal
app = Flask(__name__)


@app.route('/login/')
def login():
    # 通过查询字符串的形式来传递uname这个参数
    uname = request.args.get('uname')
    if uname:
        g.uname = uname
        # 发送信号
        login_signal.send()
        return '登录成功!'
    else:
        return '请输入用户名!'


if __name__ == '__main__':
    app.run(debug=True)

Flask_内置信号:

Flask_内置信号:
flask内置了10个常用的信号。

1. template_rendered:模版渲染完成后的信号。

2. before_render_template:模版渲染之前的信号。

3. request_started:请求开始之前,在到达视图函数之前发送信号。

4. request_finished:请求结束时,在响应发送给客户端之前发送信号。

5. request_tearing_down:请求对象被销毁时发送的信号,即使在请求过程中发生异常也会发送信号。

6. got_request_exception:在请求过程中抛出异常时发送信号,异常本身会通过exception传递到订阅(监听)的函数中。一般可以监听这个信号,来记录网站异常信息。

7. appcontext_tearing_down:应用上下文被销毁时发送的信号。

8. appcontext_pushed:应用上下文被推入到栈上时发送的信号。

9. appcontext_popped:应用上下文被推出栈时发送的信号。

10. message_flashed:调用了Flask的`flash`方法时发送的信号。

代码演示:
template_rendered的使用:

from flask import Flask,request,g,template_rendered,got_request_exception,render_template

app = Flask(__name__)

#内置信号
#模版渲染完成后的信号。
def template_rendered_func(sender,template,context):
    print(sender) #发送者
    print(template) #跳转到的模版名称
    print(context) #跳转到模版时带过去的参数
template_rendered.connect(template_rendered_func) 

@app.route('/')
def hello_world():
    return render_template("index.html",data="momo")#去看发送信号的底层

if __name__ == '__main__':
    app.run(debug=True)

 got_request_exception的使用:
from flask import Flask,request,g,template_rendered,got_request_exception,render_template
app = Flask(__name__)


#内置信号
#got_request_exception:在请求过程中抛出异常时发送信号,异常本身会通过exception传递到订阅(监听)的函数中。
# 一般可以监听这个信号,来记录网站异常信息。
# def request_exception_log(sender,*args,**kwargs):  #掌握写参数技巧
#     print(sender)
#     print(args)
#     print(kwargs)

def request_exception_log(sender,exception):
    print(sender)
    print(exception) # division by zero

got_request_exception.connect(request_exception_log)

@app.route('/')
def hello_world():
    #制造bug
    a = 1/0
    return render_template("index.html",data="momo")

if __name__ == '__main__':
    app.run(debug=True)

WTForms介绍和基本使用:

WTForms介绍和基本使用:
1.WTForms介绍:
    这个插件库主要有两个作用。
    第一个是做表单验证,将用户提交上来的数据进行验证是否符合系统要求。
    第二个是做模版渲染。 (了解即可)
    官网:https://wtforms.readthedocs.io/en/latest/index.html

     Flask-WTF是简化了WTForms操作的一个第三方库。WTForms表单的两个主要功能是验证用户提交数据的合法性以及渲染模板。而Flask-WTF还包括一些其他的功能:CSRF保护,文件上传等。
     安装Flask-WTF默认也会安装WTForms,因此使用以下命令来安装Flask-WTF和WTForms:
pip install flask-wtf


2.WTForms做表单验证的基本使用:
    1. 自定义一个表单类,继承自wtforms.Form类。
    2. 定义好需要验证的字段,字段的名字必须和模版中那些需要验证的input标签的name属性值保持一致。
    3. 在需要验证的字段上,需要指定好具体的数据类型。
    4. 在相关的字段上,指定验证器。
    5. 以后在视图函数中,只需要使用这个表单类的对象,并且把需要验证的数据,也就是request.form传给这个表单类,再调用表单类对象.validate()方法进行,如果返回True,那么代表用户输入的数据都是符合格式要求的,Flase则代表用户输入的数据是有问题的。如果验证失败了,那么可以通过表单类对象.errors来获取具体的错误信息。

如注册页面register.html:



    
    某系统注册页面


用户名:
密码:
确认密码:
如app.py文件: from flask import Flask,request,render_template from wtforms import Form,StringField from wtforms.validators import Length,EqualTo app = Flask(__name__) class RegisterForm(Form): uname =StringField(validators=[Length(min=2,max=15,message='用户名长度必须在2-15之间')]) pwd = StringField(validators=[Length(min=6,max=12)]) pwd2 = StringField(validators=[Length(min=6,max=12),EqualTo("pwd")]) @app.route('/') def hello_world(): return render_template("register.html") @app.route('/register/',methods=['GET','POST']) def register(): if request.method == 'GET': return render_template('register.html') else: form = RegisterForm(request.form) if form.validate(): #验证 要么ok 要么no return "验证通过" else: print(form.errors) return "数据验证通不过" if __name__ == '__main__': app.run(debug=True)

WTForms常用验证器:

WTForms常用验证器:
   页面把数据提交上来,需要经过表单验证,进而需要借助验证器来进行验证,以下是常用的内置验证器:

1. Length:字符串长度限制,有min和max两个值进行限制。
2. EqualTo:验证数据是否和另外一个字段相等,常用的就是密码和确认密码两个字段是否相等。
3. Email:验证上传的数据是否为邮箱数据格式  如:223333@qq.com。
4. InputRequired:验证该项数据为必填项,即要求该项非空。
5. NumberRange:数值的区间,有min和max两个值限制,如果处在这两个数字之间则满足。
6. Regexp:定义正则表达式进行验证,如验证手机号码。
7. URL:必须是URL的形式 如http://www.bjsxt.com。
8. UUID:验证数据是UUID类型。

注意:
  数据项的类型,一般常用的有

from wtforms import Form,StringField,IntegerField,RadioField,BooleanField,SelectField,TextAreaField,DateField
class RegisterForm2(Form):
    uname = StringField(validators=[InputRequired()])
    age = IntegerField(validators=[NumberRange(18,40)])

代码演示:
register2.html文件



    
    注册页面2


  
邮箱:
用户名:
年龄:
手机号码:
个人主页:
uuid:
formscheck.py表单验证工具类文件 from wtforms import Form,StringField,IntegerField from wtforms.validators import Length,EqualTo,Email,InputRequired,NumberRange,Regexp,URL,UUID class RegisterForm(Form): uname =StringField(validators=[Length(min=2,max=15,message='用户名长度必须在2-15之间')]) pwd = StringField(validators=[Length(min=6,max=12)]) pwd2 = StringField(validators=[Length(min=6,max=12),EqualTo("pwd")]) class RegisterForm2(Form): email = StringField(validators=[Email()]) uname = StringField(validators=[InputRequired()]) age = IntegerField(validators=[NumberRange(18,40)]) phone = StringField(validators=[Regexp(r'1[34578]\d{9}')]) phomepage = StringField(validators=[URL()]) uuid = StringField(validators=[UUID()]) app.py文件 from flask import Flask,request,render_template from formscheck import RegisterForm,RegisterForm2 app = Flask(__name__) @app.route('/') def hello_world(): return render_template("register.html") #基本使用 @app.route('/register/',methods=['GET','POST']) def register(): if request.method == 'GET': return render_template('register.html') else: form = RegisterForm(request.form) if form.validate(): #验证 要么ok 要么no return "验证通过" else: print(form.errors) return "数据验证通不过" #常用验证器使用 @app.route('/register2/',methods=['GET','POST']) def login(): if request.method == 'GET': return render_template("register2.html") else: form = RegisterForm2(request.form) if form.validate(): return "验证OK" else: print(form.errors) return "验证失败" import uuid print(uuid.uuid4())#edbfa1d6-28f2-4f40-9111-528141a8e77d if __name__ == '__main__': app.run(debug=True)

WTForms自定义验证器:

WTForms自定义验证器:
只有当WTForms内置的验证器不够使的时候,才需要使用自定义验证器。
如果想要对表单中的某个字段进行更细化的验证,那么可以针对这个字段进行单独的验证。

自定义验证器步骤如下:
1. 定义一个方法,方法的名字规则是:`validate_字段名(self,field)`。
2. 在方法中,使用`field.data`可以获取到这个字段的具体的值。
3. 验证时,如果数据满足条件,那么可以什么都不做。如果验证失败,
   那么应该抛出一个`wtforms.validators.ValidationError`的异常,并且把验证失败的信息传到这个异常类中。

场景:验证码实现
关键代码演示:(实现验证码 验证)

from  flask import session
from wtforms import Form,StringField,IntegerField
from wtforms.validators import Length,EqualTo,Email,InputRequired,NumberRange,Regexp,URL,UUID,ValidationError

class RegisterForm2(Form):
    email = StringField(validators=[Email()])
    uname = StringField(validators=[InputRequired()])
    age = IntegerField(validators=[NumberRange(18,40)])
    phone = StringField(validators=[Regexp(r'1[34578]\d{9}')])
    phomepage = StringField(validators=[URL()])
    uuid = StringField(validators=[UUID()])
    code = StringField(validators=[Length(4,4)])
    #取到的值 和服务器上 session上存储的值对比
    def validate_code(self,field):
        print(field.data,session.get('code'))
        if field.data != session.get('code'):
            raise ValidationError('验证码不一致!')

WTForms渲染模版:

WTForms渲染模版:
渲染模版是WTForms的第二个作用,不过,我们只需要了解即可,不需要花太多精力和时间去研究它。
除非那一天你能达到真正的全栈,才有可能考虑使用它。

看代码:
formscheck.py文件

from wtforms import Form,StringField,IntegerField,BooleanField,SelectField,DateField
from wtforms.validators import Length,EqualTo,Email,InputRequired,NumberRange,Regexp,URL,UUID,ValidationError

 #渲染模版
class CreateForm(Form):
    uname = StringField("用户名:",validators = [InputRequired()])
    age = IntegerField("年龄:",validators = [NumberRange(18,40)])
    remember = BooleanField("记住我:")
    addr = SelectField('地址:',choices=[('bj',"北京"),('sj','上海'),('tj','天津')])

app.py文件
from flask import Flask,render_template
from formscheck import CreateForm

#WTForms渲染模版  了解
@app.route('/createform/')
def createform():
        form = CreateForm()
        return render_template('create_form.html',form=form)


if __name__ == '__main__':
    app.run(debug=True)

 create_form.html文件



    
    Title
        


     

WTForms创建表单

{{ form.uname.label }} {{ form.uname(class='hot') }}
{{ form.age.label }} {{ form.age() }}
{{ form.remember.label }} {{ form.remember() }}
{{ form.addr.label }} {{ form.addr() }}
代码结果: 若感兴趣,可看官网了解更多: https://wtforms.readthedocs.io/en/latest/index.html https://wtforms.readthedocs.io/en/latest/fields.html https://wtforms.readthedocs.io/en/latest/fields.html#field-definitions

Flask_上传文件_访问文件:

Flask_上传文件_访问文件:
1.上传文件步骤:
  1. 在模版html中,表单需要指定`encotype='multipart/form-data'`才能上传文件。
  2. 在后台如果想要获取上传的文件,那么应该使用`request.files.get('文件名')`来获取。
  3. 保存文件之前,先要使用`werkzeug.utils.secure_filename`来对上传上来的文件名进行一个过滤。能保证不会有安全问题。 
  4. 获取到上传上来的文件后,使用`文件对象.save(路径)`方法来保存文件。路径=完整路径=路径名+文件名

upload.html页面



    
    上传文件


   
头像:
描述:
app.py文件 from flask import Flask,request,render_template import os from werkzeug.utils import secure_filename app = Flask(__name__) UPLOAD_PATH = os.path.join(os.path.dirname(__file__),'images') @app.route('/upload/',methods=['GET','POST']) def upload(): if request.method == 'GET': return render_template('upload.html') else: desc = request.form.get("desc") pichead = request.files.get("pichead") filename = secure_filename(pichead.filename) #包装一下 保证文件安全 # pichead.save(os.path.join(UPLOAD_PATH,pichead.filename)) #可优化 pichead.save(os.path.join(UPLOAD_PATH,filename)) #已优化 print(desc) return '文件上传成功' if __name__ == '__main__': app.run(debug=True) 2.访问文件 从服务器上读取文件,应该定义一个url与视图函数,来获取指定的文件。 在这个视图函数中,使用`send_from_directory(文件的目录,文件名)`来获取。 from flask import Flask import os from flask import send_from_directory app = Flask(__name__) UPLOAD_PATH = os.path.join(os.path.dirname(__file__),'images') @app.route('/images//') def get_image(filename): return send_from_directory(UPLOAD_PATH,filename) if __name__ == '__main__': app.run(debug=True)

利用flask-wtf验证上传的文件:

利用flask-wtf验证上传的文件:

关键点:
1. 定义验证表单类的时候,对文件类型的字段,需要采用`FileField`这个类型,即wtforms.FileField。
2. 验证器需要从`flask_wtf.file`中导入。`flask_wtf.file.FileRequired`和`flask_wtf.file.FileAllowed`
3.`flask_wtf.file.FileRequired`是用来验证文件上传不能为空。
4.`flask_wtf.file.FileAllowed`用来验证上传的文件的后缀名, 如常见图片后缀 .jpg 和.png以及.gif等。
5. 在视图函数中,需要使用`from werkzeug.datastructures import CombinedMultiDict`来把`request.form`与`request.files`来进行合并。
6.最后使用 表单验证对象.validate()进行验证。

代码如下:
upload.html页面



    
    上传文件


   
头像:
描述:
formscheck.py文件 from wtforms import Form,FileField,StringField from wtforms.validators import InputRequired # flask_wtf from flask_wtf.file import FileRequired,FileAllowed class UploadForm(Form): pichead = FileField(validators=[FileRequired(),FileAllowed(['jpg','png','gif'])]) desc = StringField(validators=[InputRequired()]) app.py文件 from flask import Flask,request,render_template import os from werkzeug.utils import secure_filename from formscheck import UploadForm from werkzeug.datastructures import CombinedMultiDict app = Flask(__name__) UPLOAD_PATH = os.path.join(os.path.dirname(__file__),'images') #利用flask-wtf验证上传的文件 @app.route('/upload/',methods=['GET','POST']) def upload(): if request.method == 'GET': return render_template('upload.html') else: form = UploadForm(CombinedMultiDict([request.form,request.files])) if form.validate(): # desc = request.form.get("desc") # pichead = request.files.get("pichead") desc = form.desc.data pichead = form.pichead.data filename = secure_filename(pichead.filename) pichead.save(os.path.join(UPLOAD_PATH,filename)) print(desc) return '文件上传成功' else: print(form.errors) return "文件上传失败" if __name__ == '__main__': app.run(debug=True)

1.Restful接口规范介绍

1.Restful接口规范介绍
REST:Representational State Transfer,
REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。
是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。
它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次。
restful接口规范是用于在前端与后台进行通信的一套规范。使用这个规范可以让前后端开发变得更加轻松。

2.适用场景:一个系统的数据库数据,展现的平台有PC端、移动端、app端、ios端。
前端工程师:都遵循RESTful编程规范
后端工程师:都遵循RESTful编程规范
最终结果:开发效率高,便于管理

3.协议:用http或者https协议。

4.数据传输格式:
数据传输的格式应该都用json格式。

5.url链接规则:
url链接中,不能有动词,只能有名词。
并且对于一些名词,如果出现复数,那么应该在后面加s。
比如:获取新闻列表,应该使用`/news/`,而不应该使用/get_news/

6.HTTP请求方式:
GET:从服务器上获取资源。
POST:在服务器上新增或者修改一个资源。
PUT:在服务器上更新资源。(客户端提供所有改变后的数据)
PATCH:在服务器上更新资源。(客户端只提供需要改变的属性)
DELETE:从服务器上删除资源。

7.状态码:
状态码	原因描述	描述
200	OK	服务器成功响应客户端的请求。
400	INVALID REQUEST	用户发出的请求有错误,服务器没有进行新建或修改数据的操作
401	Unauthorized	用户没有权限访问这个请求
403	Forbidden	因为某些原因禁止访问这个请求
404	NOT FOUND	用户请求的url不存在
406	NOT Acceptable	用户请求不被服务器接收(比如服务器期望客户端发送某个字段,但是没有发送)。
500	Internal server error	服务器内部错误,比如遇到bug

Flask_RESTful的基本使用

1.介绍:
   优势:
Flask-Restful是一个专门用来写restful api的一个插件。
使用它可以快速的集成restful api接口功能。
在系统的纯api的后台中,这个插件可以帮助我们节省很多时间。
   缺点:
如果在普通的网站中,这个插件就没有优势了,因为在普通的网站开发中,是需要去渲染HTML代码的,
而Flask-Restful在每个请求中都是返回json格式的数据。

2.安装:

pip install flask-restful

3.基本使用:
定义Restful的类视图:
   1. 从`flask_restful`中导入`Api`,来创建一个`api`对象。
   2. 写一个类视图,让他继承自`Resource`类,然后在这个里面,
   使用你想要的请求方式来定义相应的方法,比如你想要将这个类视图只能采用`post`请求,那么就定义一个`post`方法。
   3. 使用`api.add_resource`来添加类视图与`url`。


from flask import Flask,url_for,render_template
from flask_restful import  Api,Resource
app = Flask(__name__)
api = Api(app)

class LoginView(Resource):
    def post(self):
        return {"flag":"yes"}
    def get(self):
        return {"flag":"no"}

# api.add_resource(LoginView,'/login/')
api.add_resource(LoginView,'/login/','/login2/',endpoint="login")

with app.test_request_context():
     # print(url_for('loginview'))
     print(url_for('login'))
@app.route('/')
def hello_world():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True)



4.注意:
如果你想返回json数据,那么就使用flask_restful,如果你是想渲染模版,那么还是采用之前的方式,就是`app.route`的方式。
url还是跟之前的一样,可以传递参数。也跟之前的不一样,可以指定多个url。
endpoint是用来给url_for反转url的时候指定的。如果不写endpoint,那么将会使用视图的名字的小写来作为endpoint。



Flask_RESTful功能之参数验证

Flask_RESTful功能之参数验证:
1.参数验证:也叫参数解析
   Flask-Restful插件提供了类似WTForms来验证提交的数据是否合法的包,叫做reqparse。

2.基本用法:(借助于测试工程师 常用的接口测试工具postman来检验)


from flask import Flask,url_for,render_template
from flask_restful import Api,Resource,reqparse,inputs
app = Flask(__name__)
api = Api(app)

class RegisterView(Resource):
    def post(self):
        #验证用户名
        #1.创建解析器对象
        parser = reqparse.RequestParser()
        #2.利用解析器对象添加 需要验证的参数
        parser.add_argument('uname',type=str,help='用户名验证错误!',required=True,trim=True)
                #3.利用解析器对象进行验证,若正确,直接返回验证后合格的参数值,若错误,抛异常信息给客户端
        args = parser.parse_args()
        print(args)
        return {"tips":"注册成功"}

api.add_resource(RegisterView,'/register/')

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

if __name__ == '__main__':
    app.run(debug=True)

3.其中add_argument方法使用详解

add_argument方法可以指定这个字段的名字,这个字段的数据类型等,验证错误提示信息等,具体如下:

default:默认值,如果这个参数没有值,那么将使用这个参数指定的默认值。
required:是否必须。默认为False,如果设置为True,那么这个参数就必须提交上来。
type:这个参数的数据类型,如果指定,那么将使用指定的数据类型来强制转换提交上来的值。
choices:固定选项。提交上来的值只有满足这个选项中的值才符合验证通过,否则验证不通过。
help:错误信息。如果验证失败后,将会使用这个参数指定的值作为错误信息。
trim:是否要去掉前后的空格。
其中的type,
可以使用python自带的一些数据类型(如str或者int),
也可以使用flask_restful.inputs下的一些特定的数据类型来强制转换。
比如一些常用的:
      url:会判断这个参数的值是否是一个url,如果不是,那么就会抛出异常。
      regex:正则表达式。
      date:将这个字符串转换为datetime.date数据类型。如果转换不成功,则会抛出一个异常。

4.代码案例:

from flask import Flask,url_for,render_template
from flask_restful import Api,Resource,reqparse,inputs
app = Flask(__name__)
api = Api(app)

#Flask_RESTful功能之参数验证
class RegisterView(Resource):
    def post(self):
        #用户名   密码   年龄 性别  出生日期   号码  个人主页
        # 1.创建解析器对象
        parser = reqparse.RequestParser()
        #2.利用解析器对象添加 需要验证的参数
        parser.add_argument('uname',type=str,help='用户名验证错误!',required=True,trim=True)
        parser.add_argument('pwd', type=str, help='密码验证错误!',default="123456")
        parser.add_argument('age',type=int,help='年龄验证错误!')
        parser.add_argument('gender',type=str,choices=['男','女','双性'])
        parser.add_argument('birthday',type=inputs.date,help='生日字段验证错误!')
        parser.add_argument('phone',type=inputs.regex(r'1[3578]\d{9}'))
        parser.add_argument('phomepage',type=inputs.url,help='个人中心链接验证错误!')
        #3.利用解析器对象进行验证,若正确,直接返回验证后合格的参数值,若错误,抛异常信息给客户端
        args = parser.parse_args()
        print(args)
        return {"tips":"注册成功"}

api.add_resource(RegisterView,'/register/')

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

if __name__ == '__main__':
    app.run(debug=True)

Flask_RESTful返回标准化参数:

Flask_RESTful返回标准化参数:

对于一个类视图,你可以指定好一些字段作标准化用于返回。
以后使用ORM模型或者自定义模型的时候,他会自动的获取模型中的相应的字段,
生成json格式数据,然后再返回给客户端。

这需要导入flask_restful.marshal_with装饰器。
还需要写一个字典变量,来指定需要返回的标准化字段,以及该字段的数据类型。

在get方法中,返回自定义对象的时候,flask_restful会自动的读取对象模型上的所有属性。
组装成一个符合标准化参数的json格式字符串返回给客户端。


代码演示:

from flask import Flask
from flask_restful import Api,Resource,fields,marshal_with
app = Flask(__name__)
api = Api(app)

#flask_restful返回标准化参数
class News(object):
    def __init__(self,title,content):
        self.title =title
        self.content =content

news = News('能力强的体现','能屈能伸')

class NewsView(Resource):
    resource_fields ={
        'title': fields.String,
        'content':fields.String
    }
    @marshal_with(resource_fields)
    def get(self):
        # restful规范中,要求,定义好了返回的参数个数 和 内容
        # return {'title':"世界太大",'content':"可钱包太小"}
        #好处1:体现规范化,即使content这个参数没有值,也应该返回,返回一个null回去
        # return {'title':"世界太大"}
        #好处2:体现规范化,还可以返回一个对象模型回去
        return news

api.add_resource(NewsView,'/news/')


if __name__ == '__main__':
    app.run(debug=True)

Flask_RESTful返回标准化参数强化

Flask_RESTful返回标准化参数强化:

1.重命名属性和默认值:
需求1:
       有的时候你对外给出的属性名和模型内部的属性名不相同时,
       可使用 attribute可以配置这种映射。
       比如想要返回模型对象user.username的值,但是在返回给外面的时候,想以uname返回去。

需求2:
       在返回某些字段的时候,有时候可能没有值,但想给一个值用以提示,
       那么这时候可以在指定fields的时候使用default指定默认值

from flask import Flask,url_for,render_template
from flask_restful import Api,Resource,reqparse,inputs,fields,marshal_with
app = Flask(__name__)
api = Api(app)

#flask_restful返回标准化参数强化
class User():
    def __init__(self,username,age):
        self.username=username
        self.age=age
        self.signature=None

class UserView(Resource):
    resource_fields={
        #1.重命名属性
        'uname':fields.String(attribute='username'),
        'age':fields.Integer,
        #2.默认值
        'signature':fields.String(default='此人很懒,什么也没写')
    }

    @marshal_with(resource_fields)
    def get(self):
        user = User('莫莫',18)
        return user

api.add_resource(UserView,'/user/')

if __name__ == '__main__':
    app.run(debug=True)


2.复杂的参数结构:
大型的互联网项目中,返回的数据格式,是比较复杂的结构。
如:小米商城,
https://www.mi.com/
https://a.huodong.mi.com/flashsale/getslideshow

总结,无非就是key对应的value又是一个json,或者key对应的是一个列表,列表中的每项都是json
那么可以使用一些特殊的字段来实现。
如在一个字段中放置一个列表,那么可以使用fields.List,
如在一个字段下面又是一个字典,那么可以使用fields.Nested。

from flask import Flask,url_for,render_template
from flask_restful import Api,Resource,reqparse,inputs,fields,marshal_with
app = Flask(__name__)
api = Api(app)

#flask_restful返回标准化参数强化
#复杂的参数结构
#实体间关系有   1:1   1:n    n:n(转为2个1:n)
#新闻系统后台  用户  新闻   新闻标签
class User():
    def __init__(self,id,uname,age):
        self.id = id
        self.uname = uname
        self.age = age
    def __repr__(self):
        return "".format(id=self.id, uname=self.uname,age=self.age)

class News():
    def __init__(self,id,title,content):
        self.id=id
        self.title=title
        self.content=content
        #关系映射
        self.author=None
        self.tags=[]
    def __repr__(self):
        return ""\
            .format(id=self.id, title=self.title,content=self.content,author=self.author,tags=self.tags)

class NewsTag():
    def __init__(self,id,name):
        self.id=id
        self.name=name
    def __repr__(self):
        return ''.format(id =self.id , name=self.name)

def createData():
    user = User(110,'莫莫',30)
    tag1 = NewsTag(200,"要闻")
    tag2 = NewsTag(210,"娱乐")
    news =News(300,'吴京征服了世界上海拔最高的派出所','4月23日中午11点,吴京发了一条微博,配文“世界上海拔最高的派出所”,并@了另外一名演员张译。微博中有两张图片,第一张是吴京和张译两人坐在地上的合照,背后几个大字“中国边防”。第二张则是两人与派出所民警们的合照。 ')
    news.author = user   #绑定新闻作者
    news.tags.append(tag1) #绑定新闻标签1
    news.tags.append(tag2) #绑定新闻标签2
    print(news)
    return news

class NewsView2(Resource):
    resource_fields={
        'id':fields.Integer,
        'title': fields.String,
        'content': fields.String,
        #如在一个字段下面又是一个字典,那么可以使用fields.Nested({...})
        'author': fields.Nested({
            'id': fields.Integer,
            'uname': fields.String,
            'age':fields.Integer
        }),
        #如要在一个字段中放置一个列表,那么可以使用fields.List(fields.Nested({...}))
        'tags': fields.List(fields.Nested({
            'id': fields.Integer,
            'name': fields.String
        }))
    }

    @marshal_with(resource_fields)
    def get(self):
        news = createData()
        return news

api.add_resource(NewsView2,'/news2/')


if __name__ == '__main__':
    app.run(debug=True)

Flask_RESTful结合蓝图使用和渲染模版

Flask_RESTful结合蓝图使用和渲染模版:

1.Flask_RESTful结合蓝图使用
在蓝图中,如果使用Flask_RESTful,
那么在创建Api对象的时候,使用蓝图对象,不再是使用`app`对象了

蓝图news.py文件

from flask import url_for,render_template,Blueprint,make_response,Response
from flask_restful import Api,Resource,reqparse,inputs,fields,marshal_with
import json
news_bp = Blueprint('news',__name__,url_prefix='/news')
api = Api(news_bp)
#1.flask-restful结合蓝图使用

#复杂的参数结构
#实体间关系有   1:1   1:n    n:n(转为2个1:n)
#新闻系统后台  用户  新闻   新闻标签
class User():
    def __init__(self,id,uname,age):
        self.id = id
        self.uname = uname
        self.age = age
    def __repr__(self):
        return "".format(id=self.id, uname=self.uname,age=self.age)

class News():
    def __init__(self,id,title,content):
        self.id=id
        self.title=title
        self.content=content
        #关系映射
        self.author=None
        self.tags=[]
    def __repr__(self):
        return ""\
            .format(id=self.id, title=self.title,content=self.content,author=self.author,tags=self.tags)

class NewsTag():
    def __init__(self,id,name):
        self.id=id
        self.name=name
    def __repr__(self):
        return ''.format(id =self.id , name=self.name)

def createData():
    user = User(110,'莫莫',30)
    tag1 = NewsTag(200,"要闻")
    tag2 = NewsTag(210,"娱乐")
    news =News(300,'吴京征服了世界上海拔最高的派出所','4月23日中午11点,吴京发了一条微博,配文“世界上海拔最高的派出所”,并@了另外一名演员张译。微博中有两张图片,第一张是吴京和张译两人坐在地上的合照,背后几个大字“中国边防”。第二张则是两人与派出所民警们的合照。 ')
    news.author = user   #绑定新闻作者
    news.tags.append(tag1) #绑定新闻标签1
    news.tags.append(tag2) #绑定新闻标签2
    print(news)
    return news

class NewsView2(Resource):
    resource_fields={
        'id':fields.Integer,
        'title': fields.String,
        'content': fields.String,
        #如在一个字段下面又是一个字典,那么可以使用fields.Nested({...})
        'author': fields.Nested({
            'id': fields.Integer,
            'uname': fields.String,
            'age':fields.Integer
        }),
        #如要在一个字段中放置一个列表,那么可以使用fields.List(fields.Nested({...}))
        'tags': fields.List(fields.Nested({
            'id': fields.Integer,
            'name': fields.String
        }))
    }

    @marshal_with(resource_fields)
    def get(self):
        news = createData()
        return news

api.add_resource(NewsView2,'/news2/')

app.py文件

from flask import Flask,url_for,render_template

from blueprints.news import news_bp
app = Flask(__name__)
app.register_blueprint(news_bp)

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

if __name__ == '__main__':
    app.run(debug=True)

2.Flask_RESTful渲染模版
如果在Flask_RESTful的类视图中想要返回html片段代码,或者是整个html文件代码,即渲染模版的意思。
那么就应该使用`api.representation`这个装饰器来定义一个函数,
在这个函数中,应该对`html`代码进行一个封装,再返回。

from flask import url_for,render_template,Blueprint,make_response,Response
from flask_restful import Api,Resource,reqparse,inputs,fields,marshal_with
import json
news_bp = Blueprint('news',__name__,url_prefix='/news')
api = Api(news_bp)

#2. 使用flask-restful渲染模版
class ListView(Resource):
    def get(self):
        return render_template('index.html')
api.add_resource(ListView,'/list/')

# 渲染模版经过修改后,能支持html和json
@api.representation('text/html')
def output_html(data,code,headers):
    if isinstance(data,str):
        # 在representation装饰的函数中,必须返回一个Response对象
        # resp = make_response(data)
        resp =Response(data)
        return resp
    else:
        return Response(json.dumps(data),mimetype='application/json')



Flask数据库

安装MySQL以及注意事项

安装MySQL以及注意事项
在Windows下安装MySQL:
1.在MySQL的官网下载MySQL数据库
     https://dev.mysql.com/downloads/windows/installer/5.7.html。

2.然后双击安装,如果出现以下错误,

则到https://www.microsoft.com/en-us/download/confirmation.aspx?id=17113下载.net framework。然后安装。之后继续安装mysql。

3.在安装过程中,如果提示没有Microsoft C++ 2013,那么就到以下网址下载安装即可:http://download.microsoft.com/download/9/0/5/905DBD86-D1B8-4D4B-8A50-CB0E922017B9/vcredist_x64.exe


4.然后做好用户名与密码的配置即可。

SQLAlchemy连接数据库

SQLAlchemy连接数据库
1.SQLAlchemy介绍和基本使用
      数据库是一个网站的基础。Flask可以使用很多种数据库。比如MySQL,MongoDB,SQLite,PostgreSQL等。这里我们以MySQL为例进行讲解。而在Flask中,如果想要操作数据库,我们可以使用ORM来操作数据库,使用ORM操作数据库将变得非常简单。我们会以 mysql + SQLAlchemy 组合进行讲解。
在讲解Flask中的数据库操作之前,先确保你已经安装了以下软件:
mysql:如果是在windows上,到官网下载。如果是ubuntu,通过命令sudo apt-get install mysql-server libmysqlclient-dev -yq进行下载安装。
MySQLdb:MySQLdb是用Python来操作mysql的包,因此通过pip来安装,命令如下:pip install mysql-python。如果您用的是Python 2.x,请安装MySQLdb。
pymysql:pymysql是用Python来操作mysql的包,因此通过pip来安装,命令如下:pip3 install pymysql。如果您用的是Python 3,请安装pymysql。
SQLAlchemy:SQLAlchemy是一个数据库的ORM框架,我们在后面会用到。安装命令为:pip3 install SQLAlchemy。

2.通过SQLAlchemy连接数据库
首先来看一段代码:
from sqlalchemy import create_engine

# 数据库的配置变量
HOSTNAME = '127.0.0.1'
PORT     = '3306'
DATABASE = 'xt_flask'
USERNAME = 'root'
PASSWORD = 'root'
DB_URI = 'mysql+mysqldb://{}:{}@{}:{}/{}'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)

# 创建数据库引擎
engine = create_engine(DB_URI)

#创建连接
with engine.connect() as con:
    rs = con.execute('SELECT 1')
    print rs.fetchone()

首先从sqlalchemy中导入create_engine,用这个函数来创建引擎,然后用engine.connect()来连接数据库。其中一个比较重要的一点是,通过create_engine函数的时候,需要传递一个满足某种格式的字符串,对这个字符串的格式来进行解释:

   dialect+driver://username:password@host:port/database?charset=utf8

dialect是数据库的实现,比如MySQL、PostgreSQL、SQLite,并且转换成小写。driver是Python对应的驱动,如果不指定,会选择默认的驱动,比如MySQL的默认驱动是MySQLdb。username是连接数据库的用户名,password是连接数据库的密码,host是连接数据库的域名,port是数据库监听的端口号,database是连接哪个数据库的名字。如果以上输出了1,说明SQLAlchemy能成功连接到数据库。

3.用SQLAlchemy执行原生SQL(扩展):
我们将上一个例子中的数据库配置选项单独放在一个constants.py的文件中,看以下例子:
from sqlalchemy import create_engine
from constants import DB_URI

#连接数据库
engine = create_engine(DB_URI,echo=True)

# 使用with语句连接数据库,如果发生异常会被捕获
with engine.connect() as con:
    # 先删除users表
    con.execute('drop table if exists authors')
    # 创建一个users表,有自增长的id和name
    con.execute('create table authors(id int primary key auto_increment,'name varchar(25))')
    # 插入两条数据到表中
    con.execute('insert into persons(name) values("abc")')
    con.execute('insert into persons(name) values("xiaotuo")')
    # 执行查询操作
    results = con.execute('select * from persons')
    # 从查找的结果中遍历
    for result in results:
        print(result)

4.必须掌握:
from sqlalchemy import create_engine
#准备连接数据库基本信息
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'first_sqlalchemy'
USERNAME = 'root'
PASSWORD = 'root'

#dialect+driver://username:password@host:port/database?charset=utf8
#按照上述的格式来 组织数据库信息
DB_URI ="mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)

#创建数据库引擎
engine = create_engine(DB_URI)

#创建连接
conn = engine.connect()

# 判断是否连接成功
result =conn.execute('select 1')
print(result)
print(result.fetchone())


ORM介绍

ORM介绍
1. ORM:Object Relationship Mapping
2. 大白话:对象模型与数据库表的映射

python代码 和SQL代码角度理解:
class Person(object):
    name = 'xx'
    age = 18
    country ='xx'

# Person类 -> 数据库中的一张表
# Person类中的属性  -> 数据库中一张表字段
# Person类的一个对象 -> 数据库中表的一条数据

# p = Person('xx',xx)
# p.save()

#create table person(name varchar(200),age int,country varchar(100))


SQLAlchemy
        随着项目的越来越大,采用写原生SQL的方式在代码中会出现大量重复的SQL语句,那么,问题,就出现了:
1.SQL语句重复利用率不高,越复杂的SQL语句条件越多,代码越长,会出现很多相近的SQL语句。
2.很多SQL语句 是在业务逻辑中拼接出来的,如果数据库需要更改,就要去修改这些逻辑,这会很容易漏掉对某些SQL语句的修改。
3.写SQL时容易忽略web安全问题,造成隐患。

ORM,全称Object Relational Mapping,中文名叫做对象关系映射,通过ORM我们可以通过类的方式去操作数据库而不用再写原生的SQL语句,通过把表映射成类,把行作为实例,把字段作为属性,ORM在执行对象操作的时候最终还是会把对象的操作转换为数据库的原生语句,但使用ORM有许多优点:
1.易用性:使用ORM做数据库开发可以有效减少重复SQL语句的概率,写出来的模型也更加直观、清晰
2.性能损耗小:ORM转换成底层数据库操作指令确实会有一些开销。但是从实际情况来看,这种性能损耗很少(不足5%),只要不是针对性能有严苛的要求,综合考虑开发效率、代码阅读性,带来的好处远大于性能损耗,而且项目越大作用越明显。
3.设计灵活:可以轻松的写出复杂的查询。
4.可移植性:SQLAlchemy封装了底层的数据库实现,支持多个关系数据库引擎,包括流行的Mysql、PostgreSQL和SQLite,可以非常轻松的切换数据库。

定义ORM模型并将其映射到数据库中

定义ORM模型并将其映射到数据库中
1. 用`declarative_base`根据`engine`创建一个ORM基类。
   from sqlalchemy.ext.declarative import declarative_base
      engine = create_engine(DB_URI)
   Base = declarative_base(engine)

2. 用这个`Base`类作为基类来写自己的ORM类。要定义`__tablename__`类属性,来指定这个模型映射到数据库中的表名。
class Person(Base):
    __tablename__ ='person'

3. 创建属性来映射到表中的字段,所有需要映射到表中的属性都应该为Column类型:
class Person(Base):
    __tablename__ ='person'
    #2.在这个ORM模型中创建一些属性,来跟表中的字段进行 一一 映射。这些属性必须是sqlalchemy给我们提供好的数据类型
    id = Column(Integer,primary_key=True,autoincrement=True)
    name = Column(String(50))
    age = Column(Integer)
    country = Column(String(50))

4. 使用`Base.metadata.create_all()`来将模型映射到数据库中。
         Base.metadata.create_all()
5. 一旦使用`Base.metadata.create_all()`将模型映射到数据库中后,即使改变了模型的字段,也不会重新映射了。

SQLAlchemy对数据的增删改查操作

SQLAlchemy对数据的增删改查操作
用session做数据的增删改查操作:
1. 构建session对象:所有和数据库的ORM操作都必须通过一个叫做`session`的会话对象来实现,通过以下代码来获取会话对象:
from sqlalchemy.orm import sessionmaker
engine = create_engine(DB_URI)
Base = declarative_base(engine)
session = sessionmaker(engine)()

2. 添加对象:
    * 创建对象,也即创建一条数据:
           p1 = Person(name='momo1',age=19,country='china')
    * 将这个对象添加到`session`会话对象中:
      session.add(p1)
    * 将session中的对象做commit操作(提交):
      session.commit()
    * 一次性添加多条数据:
p1 = Person(name='momo1',age=19,country='china')
p2 = Person(name='momo2',age=20,country='china')
session.add_all([p1,p2])
session.commit()

3. 查找对象:
    # 查找某个模型对应的那个表中所有的数据:
    all_person = session.query(Person).all()
    # 使用filter_by来做条件查询
    all_person = session.query(Person).filter_by(name='momo1').all()
    # 使用filter来做条件查询
    all_person = session.query(Person).filter(Person.name=='momo1').all()
    # 使用get方法查找数据,get方法是根据id来查找的,只会返回一条数据或者None
    person = session.query(Person).get(primary_key)
    # 使用first方法获取结果集中的第一条数据
    person = session.query(Person).first()
    
4. 修改对象:首先从数据库中查找对象,然后将这条数据修改为你想要的数据,最后做commit操作就可以修改数据了。
  person = session.query(Person).first()
 person.name = 'lulu'
 session.commit()

5. 删除对象:将需要删除的数据从数据库中查找出来,然后使用`session.delete`方法将这条数据从session中删除,最后做commit操作就可以了。
person = session.query(Person).first()
session.delete(person)
session.commit()


SQLAlchemy常用数据类型:

SQLAlchemy常用数据类型:

1. Integer:整形,映射到数据库中是int类型。
2. Float:浮点类型,映射到数据库中是float类型。他占据的32位。
3. Double:双精度浮点类型,映射到数据库中是double类型,占据64位 (SQLALCHEMY中没有)。
4. String:可变字符类型,映射到数据库中是varchar类型.
5. Boolean:布尔类型,映射到数据库中的是tinyint类型。
6. DECIMAL:定点类型。是专门为了解决浮点类型精度丢失的问题的。在存储钱相关的字段的时候建议大家都使用这个数据类型。并且这个类型使用的时候需要传递两个参数,第一个参数是用来标记这个字段总能能存储多少个数字,第二个参数表示小数点后有多少位。
7. Enum:枚举类型。指定某个字段只能是枚举中指定的几个值,不能为其他值。在ORM模型中,使用Enum来作为枚举,示例代码如下:
    class News(Base):
        __tablename__ = 'news'
        tag = Column(Enum("python",'flask','django'))

    在Python3中,已经内置了enum这个枚举的模块,我们也可以使用这个模块去定义相关的字段。示例代码如下:
    class TagEnum(enum.Enum):
        python = "python"
        flask = "flask"
        django = "django"

    class News(Base):
        __tablename__ = 'news'
        id = Column(Integer,primary_key=True,autoincrement=True)
        tag = Column(Enum(TagEnum))

    news = News(tag=TagEnum.flask)
   
8. Date:存储时间,只能存储年月日。映射到数据库中是date类型。在Python代码中,可以使用`datetime.date`来指定。
9. DateTime:存储时间,可以存储年月日时分秒毫秒等。映射到数据库中也是datetime类型。在Python代码中,可以使用`datetime.datetime`来指定。
10. Time:存储时间,可以存储时分秒。映射到数据库中也是time类型。在Python代码中,可以使用`datetime.time`来至此那个。示例代码如下:
    class News(Base):
        __tablename__ = 'news'
        create_time = Column(Time)

    news = News(create_time=time(hour=11,minute=11,second=11))
   
11. Text:存储长字符串。一般可以存储6W多个字符。如果超出了这个范围,可以使用LONGTEXT类型。映射到数据库中就是text类型。
12. LONGTEXT:长文本类型,映射到数据库中是longtext类型。

演示整体代码如下:
from  sqlalchemy  import  create_engine,Column,Integer,String,Float,Enum,Boolean,DECIMAL,Text,Date,DateTime,Time
from  sqlalchemy.ext.declarative  import declarative_base
from  sqlalchemy.dialects.mysql  import LONGTEXT
from  sqlalchemy.orm  import  sessionmaker

import  enum
from  datetime import date
from  datetime import datetime
from  datetime import time
#准备数据库的一堆信息    ip  port    user  pwd   数据库的名称   按要求组织格式
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'first_sqlalchemy'
USERNAME = 'root'
PASSWORD = 'root'

#dialect+driver://username:password@host:port/database?charset=utf8
#按照上述的格式来 组织数据库信息
DB_URI ="mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".\
    format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)

#创建数据库引擎
engine = create_engine(DB_URI)
#创建会话对象
session =  sessionmaker(engine)()
#定义一个枚举类
class   TagEnum(enum.Enum):
     python="PYHTON2"
     flask="FLASK2"
     django ="DJANGO"


#创建一个ORM模型     说明基于sqlalchemy  映射到mysql数据库的常用字段类型有哪些?
Base = declarative_base(engine)
class News(Base):
    __tablename__='news'
    id = Column(Integer,primary_key=True,autoincrement=True)
    price1 = Column(Float)  #存储数据时存在精度丢失问题
    price2 = Column(DECIMAL(10,4))
    title = Column(String(50))
    is_delete =Column(Boolean)
    tag1 =Column(Enum('PYTHON','FLASK','DJANGO'))  #枚举常规写法
    tag2 =Column(Enum(TagEnum)) #枚举另一种写法
    create_time1=Column(Date)
    create_time2=Column(DateTime)
    create_time3=Column(Time)
    content1 =Column(Text)
    content2 =Column(LONGTEXT)

# Base.metadata.drop_all()
# Base.metadata.create_all()

#新增数据到表news中
# a1 = News(price1=1000.0078,price2=1000.0078,title='测试数据',is_delete=True,tag1="PYTHON",tag2=TagEnum.flask,
#              create_time1=date(2018,12,12),create_time2=datetime(2019,2,20,12,12,30),create_time3=time(hour=11,minute=12,second=13),
#              content1="hello",content2 ="hello   hi   nihao")

a1 = News(price1=1000.0078,price2=1000.0078,title='测试数据',is_delete=False,tag1="PYTHON",tag2=TagEnum.python,
             create_time1=date(2018,12,12),create_time2=datetime(2019,2,20,12,12,30),create_time3=time(hour=11,minute=12,second=13),
             content1="hello",content2 ="hello   hi   nihao")
session.add(a1)
session.commit()


Column常用参数:

Column常用参数:

1. primary_key:True设置某个字段为主键。
2. autoincrement:True设置这个字段为自动增长的。
3. default:设置某个字段的默认值。在发表时间这些字段上面经常用。
4. nullable:指定某个字段是否为空。默认值是True,就是可以为空。
5. unique:指定某个字段的值是否唯一。默认是False。
6. onupdate:在数据更新的时候会调用这个参数指定的值或者函数。在第一次插入这条数据的时候,不会用onupdate的值,只会使用default的值。常用于是`update_time`字段(每次更新数据的时候都要更新该字段值)。
7. name:指定ORM模型中某个属性映射到表中的字段名。如果不指定,那么会使用这个属性的名字来作为字段名。如果指定了,就会使用指定的这个值作为表字段名。这个参数也可以当作位置参数,在第1个参数来指定。
    title = Column(String(50),name='title',nullable=False)
    title = Column('my_title',String(50),nullable=False)

代码演示如下:
from sqlalchemy import create_engine,Column,Integer,Float,Boolean,DECIMAL,Enum,Date,DateTime,Time,String,Text
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

from datetime import date
from datetime import datetime
from datetime import time
#在Python3中才有enum这个模块,在python2中没有
import  enum

HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'first_sqlalchemy'
USERNAME = 'root'
PASSWORD = 'root'

DB_URI ="mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)

engine = create_engine(DB_URI)
Base = declarative_base(engine)

session = sessionmaker(engine)()



class News(Base):
    __tablename__ = 'news'
    id = Column(Integer,primary_key=True,autoincrement=True)
    create_time = Column(DateTime,default=datetime.now)
    read_count = Column(Integer,default=11)
    title = Column(String(50),name='my_title',nullable=False)
    telephone = Column(String(11),unique=True)
    update_time = Column(DateTime,onupdate=datetime.now,default=datetime.now)
Base.metadata.drop_all()
Base.metadata.create_all()

news= News()

session.add(news)
session.commit()

#下面是测试onupdate参数的
# news= session.query(News).first()
# news.title = '123'
# session.commit()
   

query函数可传递的参数一共有3种:

query函数可传递的参数一共有3种:

1. 模型名。指定查找这个模型中所有的属性(对应查询表为全表查询)。
2. 模型中的属性。可以指定只查找某个模型的其中几个属性。
3. 聚合函数。
    * func.count:统计行的数量。
    * func.avg:求平均值。
    * func.max:求最大值。
    * func.min:求最小值。
    * func.sum:求和。
    `func`上,其实没有任何聚合函数。但是因为他底层做了一些魔术,只要mysql中有的聚合函数,都可以通过func调用。

代码演示如下:

from  sqlalchemy  import  create_engine,Column,Integer,String,Float,Enum,Boolean,DECIMAL,Text,\
    Date,DateTime,Time,func
from  sqlalchemy.ext.declarative  import declarative_base
from  sqlalchemy.dialects.mysql  import LONGTEXT
from  sqlalchemy.orm  import  sessionmaker
import  random
import  enum
from  datetime import date
from  datetime import datetime
from  datetime import time
#准备数据库的一堆信息    ip  port    user  pwd   数据库的名称   按要求组织格式
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'first_sqlalchemy'
USERNAME = 'root'
PASSWORD = 'root'

#dialect+driver://username:password@host:port/database?charset=utf8
#按照上述的格式来 组织数据库信息
DB_URI ="mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".\
    format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)

#创建数据库引擎
engine = create_engine(DB_URI)
#创建会话对象
session =  sessionmaker(engine)()

#创建一个ORM模型
Base = declarative_base(engine)
class News(Base):
    __tablename__='news'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title =Column(String(50),nullable=False)
    price = Column(Integer)

    def  __repr__(self):
        return  ""%(self.title,self.price)
# Base.metadata.drop_all()
# Base.metadata.create_all()

#新增测试数据
# for  x in range(1,6):
#     a = News(title="标题%s"%x,price =random.randint(1,100))
#     session.add(a)
# session.commit()

#query()函数中能写什么参数    3种
#1.模型名
# results = session.query(News).all()
# print(results)

#2.模型名中的属性。  返回的列表中的元素是 元组类型数据
# results = session.query(News.title,News.price).all()
# print(results)


#3.mysql聚合函数
# r = session.query(func.count(News.id)).first()
# print(r)

# r = session.query(func.max(News.price)).first()
# print(r)

# r = session.query(func.min(News.price)).first()
# print(r)


# r = session.query(func.avg(News.price)).first()
# print(r)

r = session.query(func.sum(News.price)).first()
print(r)

filter过滤条件:

filter过滤条件:
过滤是数据提取的一个很重要的功能,以下对一些常用的过滤条件进行解释,并且这些过滤条件都是只能通过filter方法实现的:
1. equals : ==
    news= session.query(News).filter(News.title == "title1").first()
    
2. not equals  :  !=
    query(User).filter(User.name != 'ed')

3. like  & ilike   [不区分大小写]:
    query(User).filter(User.name.like('%ed%'))

4. in:
    query(User).filter(User.name.in_(['ed','wendy','jack']))

5. not in:
    query(User).filter(~User.name.in_(['ed','wendy','jack']))

6.  is null:
    query(User).filter(User.name==None)
    # 或者是
    query(User).filter(User.name.is_(None))

7. is not null:
    query(User).filter(User.name != None)
    # 或者是
    query(User).filter(User.name.isnot(None))

8. and:
    query(User).filter(and_(User.name=='ed',User.fullname=='Ed Jones'))
    # 或者是传递多个参数
    query(User).filter(User.name=='ed',User.fullname=='Ed Jones')
    # 或者是通过多次filter操作
    query(User).filter(User.name=='ed').filter(User.fullname=='Ed Jones')

9. or:
    query(User).filter(or_(User.name=='ed',User.name=='wendy'))

如果想要查看orm底层转换的sql语句,可以在filter方法后面不要再执行任何方法直接打印就可以看到了。比如:
        news = session.query(News).filter(or_(News.title=='abc',News.content=='abc'))
        print(news)

代码演示:
from sqlalchemy import create_engine,Column,Integer,Float,Boolean,DECIMAL,Enum,\
    Date,DateTime,Time,String,Text,func,or_,and_
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import random

HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'first_sqlalchemy'
USERNAME = 'root'
PASSWORD = 'root'

DB_URI ="mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)

engine = create_engine(DB_URI)
Base = declarative_base(engine)

session = sessionmaker(engine)()
class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    price = Column(Float,nullable=False)
    content = Column(Text)
    def __repr__(self):
        return "" % self.title

#共两种查询 过滤的方法
# r1 = session.query(News).filter(News.id == 1).first()
# print(r1)
# r2 =session.query(News).filter_by(id = 2).first()
# print(r2)

# 1. equal
# news = session.query(News).filter(News.title == "title0").first()
# print(news)

# 2. not equal
# news= session.query(News).filter(News.title != 'title0').all()
# print(news)

# 3. like & ilike(不区分大小写)
# news= session.query(News).filter(News.title.ilike('title%')).all()
# print(news)


# 4. in:
# for xxx in xxx
# def _in()
# news= session.query(News).filter(News.title.in_(['title1','title2'])).all()
# print(news)


#5. not in
# news= session.query(News).filter(~News.title.in_(['title1','title2'])).all()
# print(news)
# news= session.query(News).filter(News.title.notin_(['title1','title2'])).all()
# print(news)

#6. is null
#修改表,添加一个列content,操作数据
# news= session.query(News).filter(News.content==None).all()
# print(news)

#7. is not null
# news= session.query(News).filter(News.content!=None).all()
# print(news)


#8. and
# news= session.query(News).filter(News.title=='title5',News.content=='abc').all()
# print(news)
# 或者
# news= session.query(News).filter(and_(News.title=='title5',News.content=='abc')).all()
# print(news)

#9.or
# news= session.query(News).filter(or_(News.title=='title3',News.content=='abc')).all()
# print(news)

表关系:

表关系:
表之间的关系存在三种:一对一、一对多、多对多。
而SQLAlchemy中的ORM也可以模拟这三种关系。
因为一对一其实在SQLAlchemy中底层是通过一对多的方式模拟的,所以先来看下一对多的关系:


外键:
使用SQLAlchemy创建外键非常简单。在从表中增加一个字段,指定这个字段外键的是哪个表的哪个字段就可以了。从表中外键的字段,必须和主表的主键字段类型保持一致。
示例代码如下:
# 主表 / 从表
# user/news
class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    uname = Column(String(50),nullable=False)

    def __repr__(self):
        return "" % self.uname

class News(Base):
    __tablename__ = 'news'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    content = Column(Text,nullable=False)
    uid = Column(Integer,ForeignKey("user.id"))

    def __repr__(self):
        return "" % (self.title,self.content)

外键约束有以下几项: 
1. RESTRICT:若子表中有父表对应的关联数据,删除父表对应数据,会阻止删除。默认项
2. NO ACTION:在MySQL中,同RESTRICT。 
3. CASCADE:级联删除。 
4. SET NULL:父表对应数据被删除,子表对应数据项会设置为NULL。

演示代码如下:
from sqlalchemy import create_engine,Column,Integer,Float,Boolean,DECIMAL,Enum,\
    Date,DateTime,Time,String,Text,func,or_,and_,ForeignKey
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import random

HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'first_sqlalchemy'
USERNAME = 'root'
PASSWORD = 'root'

DB_URI ="mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)

engine = create_engine(DB_URI)
Base = declarative_base(engine)

session = sessionmaker(engine)()

# 父表/从表
# user/news
class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    uname = Column(String(50),nullable=False)

    def __repr__(self):
        return "" % self.uname

class News(Base):
    __tablename__ = 'news'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    content = Column(Text,nullable=False)

    # uid = Column(Integer,ForeignKey("user.id",ondelete='RESTRICT'))
    # uid = Column(Integer,ForeignKey("user.id",ondelete='NO ACTION'))
    # uid = Column(Integer,ForeignKey("user.id",ondelete='CASCADE'))
    uid = Column(Integer,ForeignKey("user.id",ondelete='SET NULL'))

    def __repr__(self):
        return "" % (self.title,self.content)


Base.metadata.drop_all()
Base.metadata.create_all()

user = User(uname='momo')
session.add(user)
session.commit()

news1= News(title='AAA',content='123',uid=1)
news2= News(title='BBB',content='456',uid=1)
session.add_all([news1,news2])
session.commit()

ORM关系之一对多:

ORM关系之一对多:
        mysql级别的外键,还不够爽,必须拿到一个表的外键,然后通过这个外键再去另外一张表中查找,这样太麻烦了。
        SQLAlchemy提供了一个`relationship`,这个类可以定义属性,以后在访问相关联的表的时候就直接可以通过属性访问的方式就可以访问得到了。另外,可以通过`backref`来指定反向访问的属性名称。newss是指有多篇新闻。他们之间的关系是一个“一对多”的关系。

演示代码如下:

from  sqlalchemy  import  create_engine,Column,Integer,String,Float,Enum,Boolean,DECIMAL,Text,\
    Date,DateTime,Time,func,and_,or_,ForeignKey
from  sqlalchemy.ext.declarative  import declarative_base
from  sqlalchemy.dialects.mysql  import LONGTEXT
from  sqlalchemy.orm  import  sessionmaker,relationship
import  random
import  enum
from  datetime import date
from  datetime import datetime
from  datetime import time
#准备数据库的一堆信息    ip  port    user  pwd   数据库的名称   按要求组织格式
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'first_sqlalchemy'
USERNAME = 'root'
PASSWORD = 'root'

#dialect+driver://username:password@host:port/database?charset=utf8
#按照上述的格式来 组织数据库信息
DB_URI ="mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".\
    format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)

#创建数据库引擎
engine = create_engine(DB_URI)
#创建会话对象
session =  sessionmaker(engine)()

#创建ORM模型
Base = declarative_base(engine)
# 主表 / 从表
# user/news
class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    uname = Column(String(50),nullable=False)

    # newss=relationship("News")  #这种写法不是最优的,通常会把它通过反向声明的方式写在“多”的那一方
    def __repr__(self):
        return "" % self.uname

class News(Base):
    __tablename__ = 'news'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    content = Column(Text,nullable=False)
    #外键
    uid = Column(Integer,ForeignKey("user.id"))

    #正向author = relationship("User")
    #正向 和 反向在一起 表明两个模型之间的关系
    author = relationship("User",backref="newss")

    def __repr__(self):
        return "" % (self.title,self.content)


# Base.metadata.drop_all()
# Base.metadata.create_all()

#需求1:查询 第一篇新闻的 作者是谁
# news= session.query(News).first()
# print(news)
# print(news.uid) #1
# user = session.query(User).get(news.uid)
# print(user.uname)

#上述的需求  能够被实现  但是太麻烦,引入relationship进行查询优化
# news = session.query(News).first()
# print(news.author)
# print(news.author.uname)

#需求2:查询xx作者的所有文章
user = session.query(User).first()
print(user.newss)


ORM关系之一对一:

 ORM关系之一对一:
        在sqlalchemy中,如果想要将两个模型映射成一对一的关系,那么应该在父模型中,指定引用的时候,要传递一个`uselist=False`这个参数进去。就是告诉父模型,以后引用这个从模型的时候,不再是一个列表了,而是一个对象了。示例代码如下:
方式1:
class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    uname = Column(String(50),nullable=False)
    extend = relationship("UserExtend",uselist=False)

class UserExtend(Base):
    __tablename__ = 'user_extend'
    id = Column(Integer, primary_key=True, autoincrement=True)
    school = Column(String(50))
    uid = Column(Integer,ForeignKey("user.id"))
    user = relationship("User")

方式2:(用得较多)
当然,也可以借助`sqlalchemy.orm.backref`来简化代码:
class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    uname = Column(String(50),nullable=False)

class UserExtend(Base):
    __tablename__ = 'user_extend'
    id = Column(Integer, primary_key=True, autoincrement=True)
    school = Column(String(50))
    uid = Column(Integer,ForeignKey("user.id"))
    user = relationship("User",backref=backref("extend",uselist=False))

演示代码如下:

from sqlalchemy  import  create_engine,Column,Integer,String,Float,DECIMAL,Boolean,Enum,Date,DateTime,Time,Text
from sqlalchemy import func,and_,or_,ForeignKey
from  sqlalchemy.ext.declarative  import  declarative_base
from sqlalchemy.dialects.mysql import  LONGTEXT
from sqlalchemy.orm  import  sessionmaker,relationship,backref
from datetime import date,datetime,time
#在python 3.x中  有enum模块
import  enum
import random

#准备连接数据库基本信息
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'first_sqlalchemy'
USERNAME = 'root'
PASSWORD = 'root'

#dialect+driver://username:password@host:port/database?charset=utf8
#按照上述的格式来 组织数据库信息
DB_URI ="mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)

#创建数据库引擎
engine = create_engine(DB_URI)

Base = declarative_base(engine)
session = sessionmaker(engine)()

# 主表 / 从表
# user/news      1:n
# user/user_extend    1:1
#表1
class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    uname = Column(String(50),nullable=False)

    #添加属性   优化2表查询操作
    # newss =relationship("News")   #这种写法不是最优的,通常会把它通过反向声明的方式写在“多”的那一方

    #1:1关系的表示方式1
    # extend =relationship("UserExtend",uselist=False)

    def __repr__(self):
        return "" % self.uname

#表3
class UserExtend(Base):
    __tablename__ = 'user_extend'
    id = Column(Integer, primary_key=True, autoincrement=True)
    school = Column(String(50))
    #外键
    uid = Column(Integer,ForeignKey("user.id"))

    #1:1关系的表示方式1
    # user = relationship("User")
    # 1:1关系的表示方式2
    user = relationship("User",backref = backref("extend",uselist=False))

#表2
class News(Base):
    __tablename__ = 'news'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    content = Column(Text,nullable=False)
    #SQLALchemy实现外键的方法
    uid = Column(Integer,ForeignKey("user.id"))  #默认删除策略为 :RESTRICT

    #添加属性  优化2表查询操作
    #正向
    # author = relationship("User")
    #最终:会把正向   和反向  关系 写在一起
    author = relationship("User",backref="newss")

    def __repr__(self):
        return "" % (self.title,self.content)

#创建表
# Base.metadata.drop_all()
# Base.metadata.create_all()

#需求:ORM层面外键  和一对一关系实现
#好处1:添加数据     User    添加 UserExtend
# user = User(uname="wangwu")
# ux = UserExtend(school="京南大学")
# user.extend = ux
# # print(type(user.extend))
# session.add(user)
# session.commit()

#好处1:添加数据       UserExtend  添加  User
# ux = UserExtend(school="武汉大学")
# user2 = User(uname="李四")
# ux.user = user2
# print(type(ux.user))
# session.add(ux)
# session.commit()

#好处2:查询数据
user3 = session.query(User).first()
print(user3.uname)
print(user3.extend.school)

ORM关系之多对多:

ORM关系之多对多:

1. 多对多的关系需要通过一张中间表来绑定他们之间的关系。
2. 先把两个需要做多对多的模型定义出来
3. 使用Table定义一个中间表,中间表一般就是包含两个模型的外键字段就可以了,并且让他们两个来作为一个“复合主键”。
4. 在两个需要做多对多的模型中随便选择一个模型,定义一个relationship属性,来绑定三者之间的关系,在使用relationship的时候,需要传入一个secondary=中间表对象名

代码演示如下:
from sqlalchemy import create_engine,Column,Integer,Float,Boolean,DECIMAL,Enum,\
    Date,DateTime,Time,String,Text,func,or_,and_,ForeignKey,Table
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker,relationship,backref
import random

HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'first_sqlalchemy'
USERNAME = 'root'
PASSWORD = 'root'

DB_URI ="mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)

engine = create_engine(DB_URI)
Base = declarative_base(engine)

session = sessionmaker(engine)()
#表3 中间表
news_tag = Table(
    "news_tag",
    Base.metadata,
    Column("news_id",Integer,ForeignKey("news.id"),primary_key=True),
    Column("tag_id",Integer,ForeignKey("tag.id"),primary_key=True)
)

#表1
class News(Base):
    __tablename__ = 'news'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)

    #产生关系 写法1
    # tags = relationship("Tag",backref="newss",secondary=news_tag)
    def __repr__(self):
        return "" % self.title
#表2
class Tag(Base):
    __tablename__ = 'tag'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)

    # 产生关系 写法2
    newss = relationship("News",backref="tags",secondary=news_tag)
    def __repr__(self):
        return "" % self.name

# 1. 先把两个需要做多对多的模型定义出来
# 2. 使用Table定义一个中间表,中间表一般就是包含两个模型的外键字段就可以了,并且让他们两个来作为一个“复合主键”。
# 3. 在两个需要做多对多的模型中随便选择一个模型,定义一个relationship属性,来绑定三者之间的关系,
# 4. 在使用relationship的时候,需要传入一个secondary=中间表对象名。

Base.metadata.drop_all()
Base.metadata.create_all()

#添加数据的好处
news1 = News(title="世界第一")
news2 = News(title="世界第二")

tag1 = Tag(name='要闻')
tag2 = Tag(name='娱乐')

news1.tags.append(tag1)
news1.tags.append(tag2)

news2.tags.append(tag1)
news2.tags.append(tag2)

session.add(news1)
session.add(news2)

session.commit()

#查询数据的好处
news3 = session.query(News).first()
print(news3.tags)

tag = session.query(Tag).first()
print(tag.newss)

ORM层面删除数据注意事项:

ORM层面删除数据注意事项:ORM层面删除数据注意事项:
ORM层面删除数据,会无视mysql级别的外键约束。
直接会将对应的数据删除,然后将从表中的那个外键设置为NULL。
如果想要避免这种行为,应该将从表中的外键的`nullable=False`。

演示代码如下:
from sqlalchemy import create_engine,Column,Integer,Float,Boolean,DECIMAL,Enum,\
    Date,DateTime,Time,String,Text,func,or_,and_,ForeignKey,Table
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker,relationship,backref
import random

HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'first_sqlalchemy'
USERNAME = 'root'
PASSWORD = 'root'

DB_URI ="mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)

engine = create_engine(DB_URI)
Base = declarative_base(engine)

session = sessionmaker(engine)()
class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    uname = Column(String(50),nullable=False)

class News(Base):
    __tablename__ = 'news'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50),nullable=False)
    uid = Column(Integer,ForeignKey("user.id"),nullable=False)

    author = relationship("User",backref='newss')

Base.metadata.drop_all()
Base.metadata.create_all()

user = User(uname='momo')
news= News(title='hello world')
news.author = user
session.add(news)
session.commit()

#若  uid = Column(Integer,ForeignKey("user.id")) 没有声明非空,删除父表能成功,会将子表数据 对应项置为空
#解决  若uid = Column(Integer,ForeignKey("user.id"),nullable=False) 会被阻止删除父表
user = session.query(User).first()
session.delete(user)
session.commit()

ORM层面的relationship方法中cascade1:

ORM层面的relationship方法中cascade1:

   在SQLAlchemy,只要将一个数据添加到session中,和他相关联的数据都可以一起存入到数据库中了。
这些是怎么设置的呢?其实是通过relationship的时候,有一个关键字参数cascade可以设置这些属性,
cascade属性值为:
save-update:默认选项。在添加一条数据的时候,会把其他和他相关联的数据都添加到数据库中。这种行为就是save-update属性影响的。
delete:表示当删除某一个模型中的数据的时候,是否也删掉使用relationship和他关联的数据。
delete-orphan:表示当对一个ORM对象解除了父表中的关联对象的时候,自己便会被删除掉。当然如果父表中的数据被删除,自己也会被删除。这个选项只能用在一对多上,并且还需要在子模型中的relationship中,增加一个single_parent=True的参数。
merge:默认选项。当在使用session.merge,合并一个对象的时候,会将使用了relationship相关联的对象也进行merge操作。
expunge:移除操作的时候,会将相关联的对象也进行移除。这个操作只是从session中移除,并不会真正的从数据库中删除。
all:是对save-update, merge, refresh-expire, expunge, delete几种的缩写。


代码演示:
from sqlalchemy import create_engine,Column,Integer,Float,Boolean,DECIMAL,Enum,\
    Date,DateTime,Time,String,Text,func,or_,and_,ForeignKey,Table
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker,relationship,backref
import random

HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'first_sqlalchemy'
USERNAME = 'root'
PASSWORD = 'root'

DB_URI ="mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)

engine = create_engine(DB_URI)
Base = declarative_base(engine)

session = sessionmaker(engine)()

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    uname = Column(String(50),nullable=False)

    # articles =relationship("Article",cascade="save-update,delete") #放入Article中去优化
    # comments = relationship("Comment") #放入Comment中去优化

class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
    uid = Column(Integer,ForeignKey("user.id"),nullable=False)

    # author = relationship("User",backref="articles") #cascade默认为save-update
    # author = relationship("User",backref="articles",cascade="save-update") #明文指定为save-update
    #delete:表示当删除某一个模型中的数据的时候,是否也删掉使用relationship和他关联的数据。默认不删除关联数据
    # author = relationship("User",cascade="save-update,delete") #明文指定为save-update 和delete
    #优化写法
    author = relationship("User",backref=backref("articles",cascade="save-update,delete"),cascade="save-update,delete")



class Comment(Base):
    __tablename__ = 'comment'
    id = Column(Integer, primary_key=True, autoincrement=True)
    content = Column(Text,nullable=False)
    uid = Column(Integer,ForeignKey("user.id"))

    #author = relationship("User")
    # 优化写法
    author = relationship("User",backref=backref("comments"))

def add_data():
    Base.metadata.drop_all()
    Base.metadata.create_all()
    user = User(uname="momo")
    article = Article(title="华为5G")
    article.author = user
    session.add(article)

    #引入comment表
    comment = Comment(content='你少说风凉话')
    comment.author = user
    session.add(comment)
    session.commit()

def oper_data():
    #relationship里边的cascade  可通过Article影响User
    # article = session.query(Article).first()
    # session.delete(article)
    # session.commit()

    #反过来通过User也能影响Article
    user = session.query(User).first()
    session.delete(user)
    session.commit()

#总结1:relationship里边的cascade  可通过Article影响User,反过来通过User也能影响Article
#总结2:relationship里边的cascade 只会影响当前类User中relationship的模型Article,不会影响模型Comment
if __name__ == '__main__':
    # add_data()
    oper_data()

#ORM层面的relationship方法中cascade使用1

ORM层面的relationship方法中cascade2:

ORM层面的relationship方法中cascade2:

   在SQLAlchemy,只要将一个数据添加到session中,和他相关联的数据都可以一起存入到数据库中了。
这些是怎么设置的呢?其实是通过relationship的时候,有一个关键字参数cascade可以设置这些属性,
cascade属性值为:
save-update:默认选项。在添加一条数据的时候,会把其他和他相关联的数据都添加到数据库中。这种行为就是save-update属性影响的。
delete:表示当删除某一个模型中的数据的时候,是否也删掉使用relationship和他关联的数据。
delete-orphan:表示当对一个ORM对象解除了父表中的关联对象的时候,自己便会被删除掉。当然如果父表中的数据被删除,自己也会被删除。这个选项只能用在一对多上,并且还需要在子模型中的relationship中,增加一个single_parent=True的参数。
merge:默认选项。当在使用session.merge,合并一个对象的时候,会将使用了relationship相关联的对象也进行merge操作。
expunge:了解即可。移除操作的时候,会将相关联的对象也进行移除。这个操作只是从session中移除,并不会真正的从数据库中删除。
all:是对save-update, merge, expunge, delete几种的缩写。


代码演示:
from sqlalchemy import create_engine,Column,Integer,Float,Boolean,DECIMAL,Enum,\
    Date,DateTime,Time,String,Text,func,or_,and_,ForeignKey,Table
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker,relationship,backref
import random

HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'first_sqlalchemy'
USERNAME = 'root'
PASSWORD = 'root'

DB_URI ="mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)

engine = create_engine(DB_URI)
Base = declarative_base(engine)

session = sessionmaker(engine)()

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    uname = Column(String(50),nullable=False)

class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
    uid = Column(Integer,ForeignKey("user.id"),nullable=False)

    #优化写法
    # author = relationship("User",backref=backref("articles",cascade="save-update,delete"),cascade="save-update,delete")
    #delete-orphan的使用 ,注意:delete-orphan依赖于delete
    # author = relationship("User",backref=backref("articles",cascade="save-update,delete,delete-orphan"),
    #                       cascade="save-update",single_parent=True)
    #merge的使用
    # author = relationship("User",backref=backref("articles",cascade="save-update,delete,delete-orphan,merge"),
    #                       cascade="save-update",single_parent=True)
    #all的使用  :包含了save-update, merge, expunge, delete几种功能
    author = relationship("User",backref=backref("articles",cascade="all"),
                          cascade="save-update",single_parent=True)
class Comment(Base):
    __tablename__ = 'comment'
    id = Column(Integer, primary_key=True, autoincrement=True)
    content = Column(Text,nullable=False)
    uid = Column(Integer,ForeignKey("user.id"))

    author = relationship("User")
    # 优化写法
    author = relationship("User",backref=backref("comments"))



def add_data():
    Base.metadata.drop_all()
    Base.metadata.create_all()
    user = User(uname="momo")
    article = Article(title="华为5G")
    article.author = user
    session.add(article)

    #引入comment表
    comment = Comment(content='你少说风凉话')
    comment.author = user
    session.add(comment)
    session.commit()

def oper_data():
    #relationship里边的cascade  可通过Article影响User
    # article = session.query(Article).first()
    # session.delete(article)
    # session.commit()

    #反过来通过User也能影响Article
    # user = session.query(User).first()
    # session.delete(user)
    # session.commit()

    #测试delete-orphan的使用
    user = session.query(User).first()
    user.articles = []
    session.commit()


#测试 merge   先查看user中的id数据  执行完该功能函数后   再查看user中的id数据
def oper_data_merge():
    user = User(id=1,uname = "莫莫")
    #merge:默认选项,合并成功
    # session.merge(user)
    #merge:当在使用session.merge,合并一个对象的时候,会将使用了relationship相关联的对象也进行merge操作。
    article = Article(id=1,title="5G还没普及起来,4G怎么变慢了呢?")
    user.articles.append(article)
    session.merge(user)
    session.commit()

#测试expunge  移除操作的时候,会将相关联的对象也进行移除。这个操作只是从session中移除,并不会真正的从数据库中删除。
def oper_data_expunge():
    user = User(uname='花花')
    article = Article(title='哈哈哈')
    article.author = user

    session.add(user)
    session.add(article)
    session.expunge(user)  #总结:add方法  与expunge的作用相反
    session.commit()

if __name__ == '__main__':
    # add_data()
    # oper_data()
    # oper_data_merge()
    oper_data_expunge()
#ORM层面的relationship方法中cascade使用2
#总结:ondelete的删除策略必须掌握,同时了解orm层面的cascade的操作即可