Flask总结

本文详细介绍了Flask框架的各个方面,从环境搭建、配置、请求响应到路由、视图函数、模板渲染、数据库操作、会话机制、文件上传、分页、API开发等,深入浅出地讲解了Flask的使用和项目构建。同时涵盖了Nginx、uwsgi、缓存、日志等部署和优化知识。
1.web框架
web后端:
Web框架:
vue,jquery,bootstrap

flask:
django:
tornado:

flask:
方向:
1. mtv 带模板html 模版语言
2. 前后端分离  只负责写接口 api
   REST: get  post  put  patch  delete
    
搭建框架:
1.要有虚拟环境:
 linux: virtualenv  virtualenvwrapper
        配置文件的修改:  .Envs
        .bashrc
        ...


 windows:pip install virtualenvwrapper-win
         mkvirutalenv mytest1  新建虚拟环境
         workon 虚拟环境名
         deactivate  退出虚拟环境
         rmvirutalenv 虚拟环境名

2. 新建虚拟环境:
   将项目切换到虚拟环境上

   点击终端:(flaskenv) C:\Users\running\Desktop\GPDay41\代码\day41_flask>
   说明进入到虚拟环境中....
2.Flask框架
from flask import Flask
import settings

app = Flask(__name__)
# 根据配置文件设置
app.config.from_object(settings)
# 装饰器  路由  URL  http://127.0.0.1:5000/
@app.route('/')
def index():   # 视图函数   mtv: view 视图   函数
    return '大家好!哈哈哈哈哈'
def deal():
    return ''

# WSGI: Python Web Server Gateway Interface  是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口
# flask 内置服务器 
if __name__ == '__main__':
    # host 设置为'0.0.0.0'  可以让外界访问   debug=True----当代吗改变时自动加载,默认关闭
    app.run(host='0.0.0.0', port=5000, debug=True)  # 设置端口号最好在启动之前设置
3.flask环境搭建
from flask import Flask
import settings

app = Flask(__name__)
app.config.from_object(settings)
# 多个路由
@app.route('/')
def hello_world1():
    return 'Hello World111111!'

@app.route('/index')
def hello_world2():
    return 'Hello World222222!'

@app.route('/test')
def hello_world3():
    return '<font color="red"> Hello World333333! </font>'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)
    
3
app=  Flask(__name__)
1.关联服务器并启动
  app.run(host='',port=5000)

  在终端: python app.py
2. app.route('/')  注册路由
   def func():
      return .....
4.配置
# 配置文件
ENV = 'development'
DEBUG = True

配置:
Environment: production
 production   ----》 正在
 development  ---》开发
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off

debug mode:on

run(deubg=True)  ----> mode:on
on的模式: 代码有改变则服务器重启(重新加载程序)
5.导入配置文件
在app启动前引入文件(两种)1. app.config.from_object(settings)
    2. app.config.from_pyfile('settings.py')

flask是基于Werkzeug工具包的一个web服务框架,所以flask里视图函数的实现,实际上是对werkzeug的一层封装

路由结合视图函数
    @app.route('/')
    def hello_world():  # ---->视图函数
        return 'HELLO hello world!hello kitty!'

#endpoint 相当于起了个别名
@app.route('/abc',endpoint='abc1')
def show_abc():
    return '<h1>abc</h1>'
# route就是将函数与add_url_rule进行了装饰
def show_name():
    return '千锋教育'
app.add_url_rule('/name', view_func=show_name)
路径(路由)的变量规则:
   转换器:
         str 默认
         int  整型
         float 浮点型
         path  类似str,但是可以识别'/'
         uuid  识别uuid类型的字符串

   @app.route('/news/<int:num>')   ----><int:num>表示的就是一个变量

# url_for:反向解析
@app.route('rule',endpoint='value')
def func():
    pass

url_for('endpoint')  -----------> 根据endpoint找到路由rule
6.请求和响应
请求request,响应response

请求:request
from flask import request
  client 发出的请求

  request对象   只要有请求则会产生一个request对象

  request.method  获取请求的方式
  request.args.get('key',默认值)    ----> get
  request.form.get('key',默认值)    ----> post
  request.values ----> [dictA,dictB]  dictA---GET   dictB--POST
  request.get_json()  application/json 类型数据
  request.get_data()  将数据构成一个字节字符串

  request.remote_addr  获取远程的ip地址
    print(request.path)  # /
    print(request.full_path)  # /?name=admin
    print(request.url)  # http://127.0.0.1:5000/?name=admin
    print(request.base_url)  # http://127.0.0.1:5000/
    print(request.url_root)
    print(request.method)  # 'GET'
    print(request.query_string)  # b'name=admin'

响应:response

   在视图函数的返回值后面可以跟:
   1.string   系统会自动将str转成一个response对象
   2.make_reponse( ) 构建response对象,可以给response对象添加一些头部信息
   3.jsonify(datas)  将datas转成json的格式  dict默认使用此函数
   4.render_template()
   5.redirect()
    
注:make_response()
注意:make_response 想要返回页面,不能直接写做:make_response('hello.html'),必须用render_template('hello.html')形式。
例一:
@blue.route('/makeresponse/')
def make_response_function():
    response = make_response('<h2>羞羞哒</h2>')
    return response, 404
例二:
@blue.route('/makeresponse/')
def make_response_function():
    temp = render_template('hello.html')
    response = make_response(temp)
    return response
返回状态码的方式: 1》 response = make_response(temp, 200)
				2return response, 200
8.web中状态码

1.web中的请求状态码:

201创建或更新数据成功

204删除数据成功

301永久重定向,多次发请求时,直接进入定向后的网址,相当于一次请求
302临时重定向,一次请求时,请求完后,进入定向的网址,相当于两次请求

303表示对post请求进行重定向

307表示对get请求进行重定向
400 前端请求错误

401 用户未进行登录认证

403 无访问权限
404请求未找到
405 请求方式不允许

406 服务器无法返回请求制定的accept字段格式。

503 服务异常,可能是服务器负载过重

507 服务器存储错误

  1. 请求方式post,get,head,options,put,delete区别
    get请求方式一般用于查询数据。
    post请求一般用于表单或json数据更改,安全性更高。可以在请求体中增加一些数据。

put 一般用于更新数据

delete请求用于删除数据

options 查询信息,查询服务端的支持的请求方式。

9.路由
# @router() 与 add_url_rule(rule, endpoint, f, **options)
from flask import Flask
route:
    def route(self, rule, **options):
        def decorator(f):
            self.add_url_rule(rule, endpoint, f, **options)
            return f
        return decorator

@app.route('/index')
def index():
    return 'welcome everyone!'
# 这个装饰器其实就是将rule字符串跟视图函数进行了绑定,通过add_url_rule()实现的绑定   
# 等效的
def index():
    return 'welcome everyone!'
app.add_url_rule('/index', view_func=index)
2.路由的变量规则:

string  (缺省值) 接受任何不包含斜杠的文本   *
int      接受正整数  *
float    接受正浮点数
path     类似 string ,但可以包含斜杠
uuid     接受 UUID 字符串
# 字符串类型不可自定,是默认的,而其他类型必须自定

@app.route('/add/<int:num>')
def add(num):
    print('--->', type(num))
    result = num + 10
    return str(result)
@app.route('/add1/<float:money>')
def add1(money):
    print('====>', type(money))
    return str(money)
@app.route('/index/<path:p>')
def get_path(p):
    print('******>', type(p))  # str类型
    print(p)
    return p
@app.route('/test/<uuid:uid>')  # 必须传递uuid的格式,uuid模块, uuid.uuid4() ---->UUID类型
def test(uid):
    print('#######>>>>>', type(uid))
    return '获取唯一的标识码'
注意:uuid类型----------
import uuid

uid = uuid.uuid4()
print(uid)
print(type(uid))

uid1 = str(uid)
print(type(uid1))
uid1 = uid1.replace('-','')
print(uid1)
10.有参数的路由
@app.route('/getcity/<key>')  # key就是一个变量名,默认是字符串类型的
def get_city(key):  # 参数是必须添加的
    print(type(key))
    return data.get(key)
11.视图函数
#类型一:
@app.route('/index')
def index():
    return {'a': '<h1>北京</h1>', 'b': '上海', 'c': '深圳'}  # application/json,标签内容原样显示
#类型二:
@app.route('/index1')
def index1():
    return '<h1>北京</h1>' 
#类型三:
@app.route('/index2')
def index2():
    # ValueError: not enough values to unpack (expected 2, got 1)
    # return ('beijing', 'shanghai', 'shenzhen')
    return ('beijing',200)

#类型四:
@app.route('/index3')
def index3():
    return Response('<h1>大家想好中午吃什么了吗?</h1>')  # 返回的Response对象

# 返回值:  # Content-Type:text/html; charset=utf-8
# return 后面返回的字符串其实也是做了一个response对象的封装。最终的返回结果还是response对象
# return 的返回值可以是:string dict pulple response对象 WSGI
# 必须要有返回值,不能为None
返回值总结:
1.str
2.dict
3.Response
4.render_template()
5.redirect()
6.make_response()
12.路由路径
# 所有的路由搜索规则都是自上而下搜索,在写路由的是有定义的路由是唯一的。
# 路由中定义'/',无论请求的URL是否带有/,都可以执行视图函数。如果请求的是有/,浏览器做了一次重定向
@app.route('/projects/')   
def projects():
    return 'The project page'
@app.route('/projects')  # 请求路由中如果添加了/     http://127.0.0.1:5000/projects/  显示Not Found
def about():
    return 'The about page'
# 不建议带/,我们希望请求路径是惟一的,
# 以上面这种方式写,第二个将永远不会执行
13.渲染模板
# render_template 渲染模板,就是将h5文件转换为字符串,<class 'str'>
@app.route('/register')
def register():
    r = render_template('register.html')  # 默认去模板文件夹中找文件夹的,怎么就知道文件夹就是templates?
    # print(r)
    return r
# 注意:一个pycharm的bug,h5文件写了,但显示不出来,报错,需要重启或重新创建h5文件
14.get 与 post
@app.route('/register2', methods=['GET', 'POST'])
def register2():  # 获取页面提交的内容
    print(request.full_path)  # /register2?username=zhangsan&address=Beijing
    print(request.path)  # /register2
    print(request.args)  # dict类型  d = {'a':'aaa','b':'7878'}  d.get('b')  只能取到get请求的
    # print(request.args.get('username'))   # 获取值
    # print(request.args.get('address'))
    print(request.form)  # 如果请求方法是post则需要通过request.form取值
    print(request.form.get('username'))
    print(request.form.get('address'))
    return '进来了'
15.重定向
redirect() 
返回值为response对象,Supported codes are 301, 302, 303, 305, 307, and 308300 is not supported 
作用:redirects the client to the target location
参数:(location, code=302, Response=None)
工作过程:1.向浏览器返回 状态码和location
		2.浏览器接收到状态码后,重定向后请求location地址,服务器响应请求地址
		
url_for('地址别名')
作用:反向解析(通过别名解析地址),返回一个地址
可以写为:
		return  redirect(url_for('地址别名')) 
16.模板-1
render_template(template_name_or_list, **context)  渲染模板

模板:(网页)
模板的语法:
1. 在模板中获取view中传递的变量值:{{ 变量名key }}
2. 向模板传递参数: render_template('模板名字',key=value,key=value)

@app.route('/show')
def show():
    name = '沈凯'  # str
    age = 18  # int
    friends = ['建义', '陈璟', '小岳岳', '郭麒麟']  # list
    dict1 = {'gift': '大手镯', 'gift1': '鲜花', 'gift2': '费列罗'}  # dict
    # 创建对象
    girlfriend = Girl('美美', '安徽阜阳')
    return render_template('show.html', name=name, age=age, gender='男', friends=friends, dict1=dict1, girl=girlfriend)
参数总结:参数可以有多个,可以是 str list int dict 自定义类型 json 
模板:
    {{ list.0 }}{{ list[0] }}
    {{ dict.key }}{{ dict.get(key) }}
    {{ girl.name }}{{ 对象.属性 }}

3.  控制快:
 {% if  条件 %}

 {% endif %}

 {% if  条件 %}
     条件为True
 {% else %}
     条件为False
 {% endif %}


 {% for 变量 in 可迭代的对象 %}
    for循环要做的任务

 {% endfor %}

可以使用loop变量
loop.index  序号从1开始
loop.index0  序号从0开始

loop.revindex  reverse  序号是倒着的
loop.revindex0

loop.first 布尔类型   是否是第一行
loop.last  布尔类型   是否是第二行

4
17.过滤器
# 过滤器的本质就是函数
模板语法中过滤器:
{{ 变量名 | 过滤器(*args) }}

{{ 变量名 | 过滤器 }}

常见的过滤器:
1。 safe : 禁用转译
msg = '<h1>520快乐!</h1>'
return render_template('show_2.html', girls=girls, users=users, msg=msg)
不想让其转译:
{{ msg | safe }}
2。 capitalize:单词的首字母大写
{{ n1 | capitalize }}
3。lower和upper
大小写的转换
4。title 一句话中每个单词的首字母大写
 msg = 'She is a beautiful girl'
 {{ msg | title}}
5。reverse  翻转
{{ n1 | reverse}}
6format
{{ '%s is %d years old' | format('lily',18) }}
7.truncate 字符串截断

list的操作:
{# 列表过滤器的使用 #}
{{ girls | first }}<br>
{{ girls | last }}<br>
{{ girls | length }}<br>
{#{{ girls | sum }} 整型的计算 #}
{{ [1,3,5,7,9] | sum }}<br>
{{ [1,8,5,7,3] | sort }}<br>


dict:
{% for v in users.0.values() %}   ---->获取值
    <p>{{ v }}</p>
{% endfor %}

<hr>
{% for k in users.0.keys() %}   ----》获取键
    <p>{{ k }}</p>
{% endfor %}

<hr>
{% for k,v in users.0.items() %}  ---》获取键值
    <p>{{ k }}---{{ v }}</p>
{% endfor %}


2》 自定义过滤器:
# 过滤器本质就是函数
1. 通过flask模块中的add_template_filter方法
    a. 定义函数,带有参数和返回值
    b. 添加过滤器  app.add_template_filter(function,name='')
    c. 在模板中使用: {{ 变量 | 自定义过滤器 }}
2。使用装饰器完成
    a. 定义函数,带有参数和返回值
    b. 通过装饰器完成,@app.template_filter('过滤器名字')装饰步骤一的函数
    c. 在模板中使用: {{ 变量 | 自定义过滤器 }}
例:
# 第一种方式
def replace_hello(value):
    print('------>', value)
    value = value.replace('hello', '')
    print('======>', value)
    return value.strip()  # 将 替换的结果返回

app.add_template_filter(replace_hello, 'replace') // 此处replace为replace_hello的别名


# 第二种方式 装饰器
@app.template_filter('listreverse')
def reverse_list(li):
    temp_li = list(li)
    temp_li.reverse()
    return temp_li

18.模板复用
1》继承:
模板继承:
需要模版继承的情况:
1。 多个模板具有完全相同的顶部和底部
2。 多个模板具有相同的模板内容,但是内容中部分不一样
3。 多个模板具有完全相同的模板内容

标签:
{% block 名字 %}

{% endblock %}

1.定义父模板
2.子模板继承父模板
步骤:
父模板:
1。 定义一个base.html的模板
2。 分析模板中哪些是变化的比如:{% block title %}父模板的title{% endblock %}
    对变化的部分用block进行"预留位置"也称作:挖坑
3。注意:样式和脚本 需要提前预留
    {% block mycss %}{% endblock %}
    {% block myjs %}{% endblock %}

子使用父模板:
1{% extends '父模板的名称' %}将父模板继承过来
2。 找到对应的block(坑)填充,每一个block都是有名字的。

2》include
include: 包含
在A,B,C页面都共同的部分,但是其他页面没有这部分。
这个时候考虑使用include
步骤:
1。先定义一个公共的模板部分,xxx.html
2。谁使用则include过来, {% include '文件夹/xxx.html' %}

3》宏(macro)
宏:macro
1。把它看作是jinja2的一个函数,这个函数可以返回一个HTML字符串
2。目的:代码可以复用,避免代码冗余

定义两种方式:
1。在模板中直接定义:
    类似: macro1.html  中定义方式
2。将所有宏提取到一个模板中:macro.html
   谁想使用谁导入:
   {% import 'macro.html' as xxx %}
   {{ xxx.宏名字(参数) }}
例:
{#  定义宏  #}
{% macro form(action,value='登录',method='post') %}
    <form action="{{ action }}" method="{{ method }}">
        <input type="text" placeholder="用户名" name="username"><br>
        <input type="password" placeholder="密码" name="password"><br>
        <input type="submit" value="{{ value }}">
    </form>
{% endmacro %}

{# 调用宏 #}
{{ form('/') }}
    
 # 分文件调用宏   
{% import 'macro/macro.html' as func %}
{{ func.form('/welcome',value='注册') }}
19 创建项目
  1. 创建项目并设置settings.py文件

  2. 删除app.py中的部分内容

  3. 创建包apps,并在包中__init__.py中创建app的工厂函数

    def create_app():
        app = Flask(__name__)  # app是一个核心对象
        app.config.from_object(settings)  # 加载配置
        # 蓝图
        return app
    
  4. 在apps中创建项目的程序包比如user,goods,order等

  5. 以user为例,在user包中创建view.py,在view.py中定义蓝图

    user_bp = Blueprint('user', __name__)
    # 	其中第一个参数是蓝图的名字,第二个参数是import_name
    
  6. 使用蓝图定义路由和视图函数

    @user_bp.route('/')
    def user_center():
        return render_template('user/show.html', users=users)
    

    注意在蓝图中进行了模板的加载,而模板的文件夹默认是跟flask对象是同级的,如果不是同级的注意在Flask对象创建的时候初始化设置

  7. 注册蓝图到app对象上

    def create_app():
        app = Flask(__name__,template_folder='../templates',static_folder='../static')  # app是一个核心对象
        app.config.from_object(settings)  # 加载配置
        # 蓝图
        app.register_blueprint(user_bp)  # 将蓝图对象绑定到app上
    
        print(app.url_map)
    
        return app
    
  8. 补充删除操作的步骤:

  • 点击删除按钮或者删除链接
  • 给链接添加单击事件,可以通过js或者jquery
 <a href="javascript:;" onclick="del('{{ user.username }}')">删除</a>
  • 在js中定义函数:
 function del(username){
            // console.log(username)
            // location 地址栏对象
            location.href = '/del?username='+username
        }
  • 触发/del的路由函数

    @user_bp.route('/del')
    def del_user():
        # 获取你传递的username
        username = request.args.get('username')
        # 根据username找到列表中的user对象
        for user in users:
            if user.username == username:
                # 删除user
                users.remove(user)
                return redirect('/')
        else:
            return '删除失败'
    
20 设置变量
1.set :相当于设置全局变量
{% set username ='zhangsan' %}
{{ username }}

2.with: 相当于设置局部变量
{% with num=1000  %}
   {{ num }}
{% endwith %}
21.蓝图
from app.user.views import user_bp  # 先导入才能绑定
# 将蓝图绑到app上
   app.register_blueprint(user_bp)
# 创建用户蓝图对象
user_bp=Blueprint('user',__name__)
# 其中第一个参数是蓝图的名字,第二个参数是import_name
22 完整的创建项目
步骤:
1。配置数据库的连接路径
# mysql+pymysql://user:password@hostip:port/databasename
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:root@127.0.0.1:3306/flaskday05'

2。flask-sqlalchemy的搭建:
创建包ext
   __init__.py中添加:

   db = SQLAlchemy()   ---->必须跟app联系

   def create_app():
        ....
        db.init_app(app)

        return app
3. flask-migrate的配置:
migrate = Migrate(app=app, db=db)
manager.add_command('db', MigrateCommand)


4.创建模型:
  models.py

  模型就是类,经常称作模型类

  class User(db.Model):      ------> user表
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(15), nullable=False)
    password = db.Column(db.String(12), nullable=False)
    phone = db.Column(db.String(11), unique=True)
    rdatetime = db.Column(db.DateTime, default=datetime.now)
    
5.使用命令:
    a. ************敲黑板***************
       在app.py 中导入模型:from apps.user.models import User

    b. 在终端使用命令:db
        python3 app.py db init   -----》 产生一个文件夹migrations
        python3 app.py db migrate -----> 自动产生了一个版本文件
         项目
          | ---apps
          | ---ext
          | ---migrations    python3 app.py db init     只需要init一次
                   |---versions   版本文件夹
                        |---71edde7ee937_.py    ---》  python3 app.py db migrate  迁移
                        |---cc0dca61130f_.py
                                                      python3 app.py db upgrade 同步
                                                      python3 app.py db downgrade 降级

6.添加数据:以注册为例:
# 模板,视图与模型结合
# 1>. 找到模型类并创建对象
user = User()
# 2>. 给对象的属性赋值
user.username = username
user.password = password
user.phone = phone
# 添加
# 3>.将user对象添加到session中(类似缓存)
db.session.add(user)
# 4>.提交数据
db.session.commit()
23 数据库操作
1.查询:
查询所有: 模型类.query.all()    ~  select * from user;
如果有条件的查询:
         模型类.query.filter_by(字段名 =)   ~  select * from user where 字段=值;
         模型类.query.filter_by(字段名 =).first()   ~  select * from user where 字段=值 limit..;

select * from user where age>17 and gender='男';
select * from user where username like 'zhang%';
select * from user where rdatetime> xxx and rdatetime < xxx;

         模型类.query.filter()  里面是布尔的条件   模型类.query.filter(模型名.字段名 ==)
         模型类.query.filter_by()  里面是一个等值   模型类.query.filter_by(字段名 =)


***** 模型类.query.filter() ******
1. 模型类.query.filter().all()   -----> 列表
2. 模型类.query.filter().first()  ----->对象
3.User.query.filter(User.username.endswith('z')).all()   select * from user where username like '%z';
  User.query.filter(User.username.startswith('z')).all()  # select * from user where username like 'z%';
  User.query.filter(User.username.contains('z')).all()  # select * from user where username like '%z%';
  User.query.filter(User.username.like('z%')).all()

  多条件:
  from sqlalchemy import or_, and_,not_
  并且: and_    获取: or_   非: not_
  User.query.filter(or_(User.username.like('z%'), User.username.contains('i'))).all()
   类似: select * from user where username like 'z%' or username like '%i%';

  User.query.filter(and_(User.username.contains('i'), User.rdatetime.__gt__('2020-05-25 10:30:00'))).all()
   # select * from user where username like '%i%' and rdatetime < 'xxxx'

  补充:__gt__,__lt__,__ge__(gt equal),__le__ (le equal)  ----》通常应用在范围(整型,日期)
       也可以直接使用 >  <  >=  <=  !=

  User.query.filter(not_(User.username.contains('i'))).all()

  18 19 20 17 21 22 ....
  select * from user where age in [17,18,20,22];


排序:order_by

    user_list = User.query.filter(User.username.contains('z')).order_by(-User.rdatetime).all()  # 先筛选再排序
    user_list = User.query.order_by(-User.id).all()  对所有的进行排序
    注意:order_by(参数)1。 直接是字符串: '字段名'  但是不能倒序
    2。 填字段名: 模型.字段    order_by(-模型.字段)  倒序

限制: limit
    # limit的使用 + offset
    # user_list = User.query.limit(2).all()   默认获取前两条
    user_list = User.query.offset(2).limit(2).all()   跳过2条记录再获取两条记录


 总结:
 1. User.query.all()  所有用户
 2. User.query.get(主键)  一个用户
 3. User.query.filter()   *   ???????
     如果要检索的字段是字符串(varchar,db.String):
       User.username.startswith('')
       User.username.endswith('')
       User.username.contains('')
       User.username.like('')
       User.username.in_(['','',''])
       User.username == 'zzz'
    如果要检索的字段是整型或者日期类型:
       User.age.__lt__(18)
       User.rdatetime.__gt__('.....')
       User.age.__le__(18)
       User.age.__ge__(18)
       User.age.between(15,30)

     多个条件一起检索: and_, or_
     非的条件: not_

     排序:order_by()
     获取指定数量: limit() offset()
 4. User.query.filter_by()

 2.删除:
 两种删除:
 1。逻辑删除(定义数据库中的表的时候,添加一个字段isdelete,通过此字段控制是否删除)
 id = request.args.get(id)
 user = User.query.get(id)
 user.isdelete = True
 db.session.commit()

 2。物理删除(彻底从数据库中删掉)
 id = request.args.get(id)
 user = User.query.get(id)
 db.session.delete(user)
 db.session.commit()
3.更新:
 id = request.args.get(id)
 user = User.query.get(id)
 # 修改对象的属性
 user.username= xxxx
 user.phone =xxxx
 # 提交更改
 db.session.commit()
4.增加:
 id = request.args.get(id)
 username = request.args.get('username')
 password = request.args.get('password')
 user = User.query.get(id)
 # 创建对象(表)
 user = User()
 user.username = username
 user.password = password
 # 添加
 db.session.add(user)
 db.session.commit()
24 多表连接
主要理解多张数据库表的关系。

明确:一个项目肯定会有多张表,确定表与表之间的关系最重要。
在开始项目前必须要确定表与表的关系
> 在flask的框架中如何体现1对多的模型关系?
> 就是通过外键ForignKey和relationship体现。ForignKey是给映射关系说的,relationship是给模板使用的。
------> 一对多:
class User(db.Model):
    ....
    articles = db.relationship('Article', backref='user')  # 写一个就行
    ...
---》 多对多
》方式一:
   ```python
tags = db.Table('tags',
    db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')),
    db.Column('page_id', db.Integer, db.ForeignKey('page.id'))
   )
   
class Page(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    tags = db.relationship('Tag', secondary=tags,backref=db.backref('pages', lazy='dynamic'))
   
class Tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(20),nullable=False)
   ```
   
》方式二:
```python
class Page(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    tags = db.relationship('Tag', secondary='Page_tag',
        backref='pages')

class Tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(20),nullable=False)
    
class Page_tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
    db.Column('page_id', db.Integer, db.ForeignKey('page.id'))
```
25 flask-bootstrap
# flask-bootstrap的使用:
使用flask-bootstrap:
步骤:
1. pip install flask-bootstrap
2. 进行配置:
   ```python
    from flask-bootstrap import Bootstrap
    bootstrap = Bootstrap()
    # 在__init__.py中进行初始化: 
    ###### 初始化bootstrap
 
    bootstrap.init_app(app=app)
   ``
3. 内置的block:

​```python
{% block title %}首页{% endblock %}
{% block navbar %} {% endblock %}
{% block content %} {% endblock %}
{% block styles %} {% endblock %}
{% block srcipts %} {% endblock %}
{% block head %} {% endblock %}
{% block body %} {% endblock %}
​```
4. 创建base.py

   ```python
   {% extends "bootstrap/base.html" %}
   {% block title %}首页{% endblock %}
   {% block styles %}
       {{ super() }}
       <style>
          .....
       </style>
   {% endblock %}
   
   {% block navbar %}
   	....
      
   {% endblock %}
   
   {% block content %}
       {% block newcontent %}
           <h1>Hello, Bootstrap</h1>
       {% endblock %}
       {% block footer %}
           <p id="myfoot">京ICP备11008000号-6京公网安备11010802020853</p>
       {% endblock %}
   {% endblock %}
5. 子模板继承父模板:

{% extends ‘base.html’ %}
{% block title %}
博客首页
{% endblock %}
{% block styles %}
{{ super() }}

{% endblock %}

{% block newcontent %}











{% endblock %}


26 会话机制-cookie
# 会话机制

## (1)cookie

  在网站中,HTTP请求是无状态的。也就是说,即使第一次用户访问服务器并登录成功后,第二次请求服务器依然不知道当前发起请求的是哪个用户。cookie的出现就是为了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,浏览器将这些数据保存在本地。当用户发起第二次请求的时候,浏览器自动的将上次请求得到的cookie数据携带给服务器,服务器通过这些cookie数据就能分辨出当前发起请求的是哪个用户了。cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4K,因此使用cookie只能存储一些少量的数据。

## (2)session

  session与cookie的作用有点类似,都是为了存储用户相关的信息。不同的是,cookie是存储在本地浏览器,session存储在服务器。存储在服务器的数据会更加的安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源。

## (3)cookie和session的结合使用

  web开发发展至今,cookie和session的使用已经出现了一些非常成熟的方案。在如今的市场和企业里,一般有两种存储方式:

- 存储在服务器:通过cookie存储一个session_id,然后具体的数据则保存在session中。当用户已经登录时,会在浏览器的cookie中保存一个session_id,下次再次请求的时候,会把session_id携带上来,服务器根据session_id在session库中获取用户的session数据,从而能够辨别用户身份,以及得到之前保存的状态信息。这种专业术语叫做server side session(服务器端会话).
- 将session数据加密,然后存储在cookie中。这种专业术语叫做client side session(客户端会话),flask采用的就是这种方式,但是也可以替换成其它形式.



## 实现方式:

1. ### cookie方式:

-   保存:set_cookie
 通过response对象保存。
  response = redirect(xxx)
  response = render_template(xxx)
  response = Response()
  response = make_response()
  
    response = jsonify()
通过对象调用方法
​  response.set_cookie(key,value,max_age)
其中max_age表示过期时间,单位是秒
也可以使用expires设置过期时间,expires=datetime.now()+timedelta(hour=1)

-   获取:cookies.get(key) 

  通过request对象获取。
      request.form.get()
      request.args.get()
      cookie也在request对象中
  > ​ request.cookies.get(key) ----> value
  
 - 删除:delete_cookie(key)
   通过response对象删除。 把浏览器中的key=value删除了
    response = redirect(xxx)
    response = render_template(xxx)
    response = Response()
    response = make_response()
    response = jsonify()
   通过对象调用方法
> ​  response.delete_cookie(key)
27 会话机制-session
2.session方式:
session:  是在服务器端进行用户信息的保存。一个字典
注意:
使用session必须要设置配置文件,在配置文件中添加 -- SECRET_KEY='xxxxx',
添加SECRET_KEY的目的就是用于sessionid的加密。如果不设置会报错。

-   设置:
      如果要使用session,需要直接导入:
      from flask import session

​    把session当成字典使用,因此:session[key]=value
​    就会将key=value保存到session的内存空间
​    并会在响应的时候自动在response中自动添加有一个cookie:session=加密后的id 

- 获取
       用户请求页面的时候就会携带上次保存在客户端浏览器的cookie值,其中包含session=加密后的id
       获取session值的话通过session直接获取,因为session是一个字典,就可以采用字典的方式获取即可。
       value = session[key] 或者 value = session.get(key)

    > ​     这个时候大家可能会考虑携带的cookie怎么用的????
    > ​     其实是如果使用session获取内容,底层会自动获取cookie中的sessionid值,
    > ​     进行查找并找到对应的session空间

- 删除

    >  session.clear()  删除session的内存空间和删除cookie
    > ​ del session[key]  只会删除session中的这个键值对,不会删除session空间和cookie

28 短信验证码
1.短信息发送:
浏览器请求---手机号---》服务器--手机号--》【阿里云短信服务】---验证码---》用户手机
         《--------返回验证码和状态码《------------- 
    ---手机号和验证码---》 服务器(开始处理)     

# 发送短信息
@user_bp1.route('/sendMsg')
def send_message():
    phone = request.args.get('phone')
    # 补充验证手机号码是否注册,去数据库查询

    SECRET_ID = "dcc535cbfaefa2a24c1e6610035b6586"  # 产品密钥ID,产品标识
    SECRET_KEY = "d28f0ec3bf468baa7a16c16c9474889e"  # 产品私有密钥,服务端生成签名信息使用,请严格保管,避免泄露
    BUSINESS_ID = "748c53c3a363412fa963ed3c1b795c65"  # 业务ID,易盾根据产品业务特点分配
    api = SmsSendAPIDemo(SECRET_ID, SECRET_KEY, BUSINESS_ID)
    params = {
        "mobile": phone,
        "templateId": "10084",
        "paramType": "json",
        "params": "json格式字符串"
    }
    ret = api.send(params)  # ret 为字典
    # phone 为变量,存储的使用户登录手机号
    session[phone] = '189075'
    return jsonify(code=200, msg='短信发送成功!')

    # if ret is not None:
    #     if ret["code"] == 200:
    #         taskId = ret["result"]["taskId"]
    #         print("taskId = %s" % taskId)
    #         session[phone]='189075'
    #         return jsonify(code=200, msg='短信发送成功!')
    #     else:
    #         print("ERROR: ret.code=%s,msg=%s" % (ret['code'], ret['msg']))
    #         return jsonify(code=400, msg='短信发送失败!')
29 登录权限验证
2.登录权限的验证
只要走center路由,判断用户是否是登录状态,如果用户登录了,可以正常显示页面,如果用户没有登录
则自动跳转到登录页面进行登录,登录之后才可以进行查看。


30 Flask钩子函数
3.钩子函数:
直接应用在app上:
before_first_request
before_request
after_request
teardown_request

应用到蓝图:
before_app_first_request
before_app_request
after_app_request
teardown_app_request
31 文件上传
3.文件上传
 A. 本地上传
    注意:
    表单:  enctype="multipart/form-data"
 <form action="提交的路径" method="post" enctype="multipart/form-data">
        <input type="file" name="photo" class="form-control">
        <input type="submit" value="上传相册" class="btn btn-default">
 </form>
   view视图函数:
   photo = request.files.get('photo')   ----》photo是FileStorage

   属性和方法:FileStorage = 》fs
   fs.filename
   fs.save(path)  ----> path上传的路径os.path.join(UPLOAD_DIR,filename)
   fs.read()  ----> 将上传的内容转成二进制方式

 B. 上传到云端(对象存储)
    本地的资源有限或者是空间是有限的

    https://developer.qiniu.com/kodo/sdk/1242/python  ---》参照python SDK

    util.py:

    def upload_qiniu():
        #需要填写你的 Access Key 和 Secret Key
        access_key = 'Access_Key'
        secret_key = 'Secret_Key'
        #构建鉴权对象
        q = Auth(access_key, secret_key)
        #要上传的空间
        bucket_name = 'Bucket_Name'
        #上传后保存的文件名
        key = 'my-python-logo.png'
        #生成上传 Token,可以指定过期时间等
        token = q.upload_token(bucket_name, key, 3600)
        #要上传文件的本地路径
        localfile = './sync/bbb.jpg'
        ret, info = put_file(token, key, localfile)
        return ret,info

        ---->put_data()  适用于从filestorage里面读取数据实现上传
        ---->put_file()  指定文件路径上传

    def delete_qiniu():
        pass

32 分页
分页:
    print(pagination.items)  # [<Article 4>, <Article 3>, <Article 2>]
    print(pagination.page)  # 当前的页码数
    print(pagination.prev_num)  # 当前页的前一个页码数
    print(pagination.next_num)  # 当前页的后一页的页码数
    print(pagination.has_next)  # True
    print(pagination.has_prev)  # True
    print(pagination.pages)  # 总共有几页
    print(pagination.total)  # 总的记录条数
33 Nginx
1.特点:
nginx:
        1。轻量级
        2。并发能力强
        3。高度模块化
        4。负载均衡
        5。反向代理
2.指令:
	nginx 	-t		不运行,仅测试配置文件
	nginx   -c     configpath	从指定路径加载配置文件
	nginx   -t    -c    configpath      测试指定配置文件

****启动Nginx
	nginx 	[ -c  configpath]   默认配置目录:/etc/nginx/nginx.conf
信息查看
	nginx 	-v
	nginx	-V
查看进程:
	ps -ef |grep nginx
****控制Nginx
	nginx -s  signal
               stop 	快速关闭
               quit		优雅的关闭
               reload	重新加载配置

通过系统管理
	systemctl  status  nginx	查看nginx状态
	systemctl  start    nginx	启动nginx服务
	systemctl  stop     nginx   关闭nginx服务
	systemctl  enable nginx	设置开机自启
	systemctl  disable nginx	禁止开机自启


34 使用缓存:
1. Cache对象
   from flask-caching import Cache
   cache = Cache()
2.
config = {
    'CACHE_TYPE': 'redis',
    'CACHE_REDIS_HOST': '127.0.0.1',
    'CACHE_REDIS_PASSWORD': 961202,
    'CACHE_REDIS_PORT': 6379
}
def create_app():
    .....
    cache.init_app(app,config)

3. 设置缓存:
   cache.set(key,value,timeout=second)   ----> flask_cache_pic_abc
   cache.set_many([(key,value),(key,value),(key,value),...])

   获取缓存值:
   cache.get(key)  --->value
   cache.get_many(key1,key2,...)

   删除缓存:
   cache.delete(key)
   cache.delete_many(key1,key2,...)
   cache.clear()

视图函数缓存:
@app.route("/")
@cache.cached(timeout=50)
def index():
    return render_template('index.html')
35 uwsgi
uwsgi : web服务器,多线程处理的不错
	1. pip install uwsgi
	2. 工程目录下创建   uwsgi.ini 配置文件
	3. 书写配置信息
	4. 使用uwsgi服务器(推荐写绝对路径)
		- 启动	uwsgi    --ini    uwsgi.ini
		- 停止	uwsgi    --stop   uwsgi.pid
#--------------
#列出已安装的包
pip freeze or pip list
#导出requirements.txt
pip freeze > <目录>/requirements.txt
pip install -r requirements.txt
#卸载包
pip uninstall -r requirements.txt
#国内pypi镜像
V2EX:pypi.v2ex.com/simple
豆瓣:http://pypi.douban.com/simple
中国科学技术大学:http://pypi.mirrors.ustc.edu.cn/simple/
#指定单次安装源
pip install <包名> -i http://pypi.v2ex.com/simple
#指定全局安装源
在unix和macos,配置文件为:$HOME/.pip/pip.conf
在windows上,配置文件为:%HOME%\pip\pip.ini
    [global]
    timeout = 6000
      index-url = http://pypi.douban.com/simple
36 flask-WTF
https://flask-wtf.readthedocs.io/en/stable/#
https://wtforms.readthedocs.io/en/2.3.x/

wtform
flask-wtf:集成了wtform,csrf的保护和文件上传功能,图形验证码。

###使用:
1。安装:
pip3 install Flask-WTF

全局使用csrf保护,
csrf = CSRFProtect(app=app)
必须需要设置SECRET_KEY这个配置项
app.config['SECRET_KEY'] = 'fgfhdf4564'

2。定义form.py:
在文件中中添加:
class UserForm(FlaskForm):
    name = StringField('name', validators=[DataRequired()])
    password =PasswordField('password',validators=[DataRequired(),Length(min=3,max=9,message='密码最小3位最大9位')])
    recaptcha = StringField(label='验证码')

各种:Field类型
StringField
PasswordField
IntegerField
DecimalField
FloatField
BooleanField
RadioField
SelectField
DatetimeField

各种的验证(验证器):
验证器函数	说明
DataRequired/Required	代表内容是必填项 DataRequired(message='用户信息不能为空')
Email	验证电子邮件地址,要求正则模式 : ^.+@([^.@][^@]+)$
EqualTo	比较两个字段的值,常用于确认密码情况 EqualTo('要确认字段名',message='xxx')
IPAddress	验证IPV4网络地址 参数默认ipv4=True,ipv6=False
Length	验证输入字符串的长度 参数类型是字符串Length(min=6,max=30,message='个人信息的简介6-30个字')
NumberRange	验证输入的值在数字范围内
NumberRange(min=6,max=90,message='年龄为6-90岁')
Optional	无输入值时跳过其他验证函数
Regexp	使用正则表达式验证输入值 参数regex='正则模式'
URL	验证 URL URL(message='请输入正确的url地址')]
正则模式是^[a-z]+://(?P<host>[^/:]+)(?P<port>:[0-9]+)?(?P<path>\/.*)?$
AnyOf	确保输入值在可选值列表中
NoneOf	确保输入值不在可选值列表中
*****自定义验证器:
class UserForm(FlaskForm):
    name = StringField('name', validators=[DataRequired()])
    def validate_name(self, data):
        # print('----->', type(self.name))
        # print('=====>', type(data))  # <class 'wtforms.fields.core.StringField'>
        if self.name.data[0].isdigit(): # data[0]是取用户名的第一个字母
            raise ValidationError('用户名不能以数字开头')
    def validate_recaptcha(self, data):
        input_code = data.data
        code = session.get('valid')
        if input_code.lower() != code.lower():
            raise ValidationError('验证码错误')
    def validate_phone(self,data):
        input_phone = data.data
        # print('----####---->',input_phone)  #18438613151
        pattern = r'^1\d{9}1$'
        res = re.match(pattern,input_phone)
        if not res:
            raise ValidationError('电话号码必须以一开始且结尾')

3.使用:
 视图中:
   .....
   form =UserForm()
   return render_template('user.html',form=form)

 模板中:
    <form action='' method=''>
#  *****注意: 1. form.validate_on_submit() 只有在数据正确 并且验证csrf通过时 返回True
      {{form.csrf_token}}    # 不可少,负责无法提交
      {{form.name}}
      {{form.password}}
      <input type='submit' value=''/>
    </form>

4. 提交验证:
@app.route('/',methods=['GET','POST'])
def hello_world():
    uform = UserForm()
    if uform.validate_on_submit():   ------->主要通过validate_on_submit进行校验
    	name=uform.name.data
    	password=uform.password.data
    	phone=uform.phone.data
        print(name,password,phone)
        return '提交成功!'

    return render_template('user.html', uform=uform)


##文件上传:
1。定义form
class UserForm(FlaskForm):
    。。。。。。
    # 上传使用的就是FileField,如果需要指定上传文件的类型需要使用:FileAllowed
    icon = FileField(label='用户头像', validators=[FileRequired(), FileAllowed(['jpg', 'png', 'gif'], message='必须是图片文件格式')])

2。模板中的使用同其他类型的字段,但是必须在form上面:enctype="multipart/form-data"

3。视图函数中如果验证成功,通过:
        icon = uform.icon.data   -----》icon是FileStorage类型
        filename = secure_filename(icon.filename)
        icon.save(os.path.join(UPLOAD_DIR, filename))

37 bootstrap和wtf
1.app.py:
    # form与bootstrap结合使用
    @app.route('/user', methods=['GET', 'POST'])
    def boot_form_user():
        uform = MyForm()
        return  render_template('boot.html',uform=uform)
2.index.html:
{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf%}

{% block styles %}
    {{ super() }}
{% endblock %}

{% block content %}
    <form action="{{ url_for('submit') }}" method="post" enctype="multipart/form-data">
        {{ wtf.quick_form(uform, button_map={'submit_button': 'primary'},form_type='horizontal',horizontal_columns=('lg',2,5)) }}
    </form>

{% endblock %}
38 Flash消息
(5) Flash消息
Flask的核心特性Flash实现: 为了解决用户提交了有一项错误的登录表单后,服务器发回的响应重新渲染了登录表单,并在表单上面显示一个消息,提示用户用户名或密码错误。常常用于表单信息提示.

flash() 作用是每次调用flash()都会触发一个消息所以可能有多个消息在排队等待显示。

get_flashed_messages() 函数获取传入的消息 其实就是一个列表, 获取的消息在下次调用时不会再次返回,因此 Flash 消息只显示一次,然后就消失了
###使用

from flask import flask,get_flashed_messages
@app.route('/register/',method=['GET','POST'])
def register():
    form = Register()
    if form.validate_on_submit():
        flash('注册成功')
        return redirect(url_for('index'))
    return render_template('form.html',form=form)

###在模板中获取
#写法一:
{% for message in get_flashed_messages() %}
	 <div class="alert alert-success alert-dismissible" role="alert">
            <strong>success!</strong>{{ message }}
     </div>
{% endfor %}
#写法二:
{% with messages = get_flashed_messages(with_categories=True) %}
# messages 包含category和message
    {% if messages %}
        <ul>
            {% for category, message in messages %}
                <li class="{{ category }}">{{ message }}</li>
            {% endfor %}
        </ul>
    {% endif %}
{% endwith %}
####
flash使用当然不仅仅限于单一消息的传递,flash的源码定义为 :

def flash(message, category='message'):
因此可以看到不仅能传递消息message还能将传递的消息进行类别category选择

get_flashed_messages同样有接受信息类别的属性with_categories=设置为True

def get_flashed_messages(with_categories=False, category_filter=[]):
####例如:
if content.split('\n') <= 2:
  flash(message=u'警告',category='warning')
elif content.split('\n') >= 5:
  flash(message=u'不允许的操作',category='danger')

{% block content %}
  {% for category,message in get_flashed_message(with_category=True) %}
  <div class="alert alert-{{ category }} alert-dismissable">
    <button type="button" class="close" data-dismiss="alert">&tims;</button>
    {{ message }}
  </div>
  {% endfor %}
{% endblock %}
1. 闪现:
    记住:
    1>在一个请求结束的时候添加flash
        flash('恭喜!验证成功啦!','info')
        flash('哈哈哈','error')
        flash(username,'warning')
    2>在当前请求中渲染获取或者仅仅下一个请求中可以获取。

    添加闪现:(后面的类型是可选择的)
    flash('恭喜!验证成功啦!','info')
    flash('哈哈哈','error')
    flash(username,'warning')

    获取闪现内容:
    get_flashed_messages(with_categories=[True/False])
    get_flashed_messages(category_filter=["error"])  可选的
    有针对性的获取对应类型的闪现消息
39 日志
日志:
    1.使用app自带,
      app.logger.info('')
      app.logger.debug('A value for debugging')
      app.logger.warning('A warning occurred (%d apples)', 42)
      app.logger.error('An error occurred')

    2.通过logging进行创建:
    import logging

    logger = logging.getLogger('name')  # 默认flask的名字叫:app
    logger = logging.getLogger('app')

    保存到文件:
    1。logging.basicConfig(filename='log.txt', filemode='a', level=logging.WARNING,
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    2。logger.setLevel(level=logging.INFO)
    handler = logging.FileHandler("log1.txt")
    handler.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    3。使用logger.info('message')
40 flask-restful-1
什么是RESTful架构:
(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词(GET,POST,PUT,DELETE,[PATCH]),对服务器端资源进行操作,实现"表现层状态转化"。

REST 是面向资源的,这个概念非常重要,而资源是通过 URI 进行暴露。
REST 系统的特征:
    1.客户-服务器(Client-Server),提供服务的服务器和使用服务的客户需要被隔离对待。
    2.无状态(Stateless),来自客户的每一个请求必须包含服务器处理该请求所需的所有信息。换句话说,服务器端不能存储来自某个客户的某个请求中的信息,并在该客户的其他请求中使用。
    3.可缓存(Cachable),服务器必须让客户知道请求是否可以被缓存。(Ross:更详细解释请参考 理解本真的REST架构风格 以及 StackOverflow 的这个问题 中对缓存的解释。)
    4.分层系统(Layered System),服务器和客户之间的通信必须被这样标准化:允许服务器和客户之间的中间层(Ross:代理,网关等)可以代替服务器对客户的请求进行回应,而且这些对客户来说不需要特别支持。
    5.统一接口(Uniform Interface),客户和服务器之间通信的方法必须是统一化的。(Ross:GET,POST,PUT.DELETE, etc)
    6.支持按需代码(Code-On-Demand,可选),服务器可以提供一些代码或者脚本(Ross:Javascrpt,flash,etc)并在客户的运行环境中执行。这条准则是这些准则中唯一不必必须满足的一条。(Ross:比如客户可以在客户端下载脚本生成密码访问服务器。)


前后端分离:
前端: app,小程序,pc页面

后端: 没有页面,mtv: 模型模板视图  去掉了t模板。
      mv:模型 视图
      模型的使用:跟原来的用法相同
      视图: api构建视图
  步骤:
     1. pip3 install flask-restful

     2.创建api对象
      api = Api(app=app)
      api = Api(app=蓝图对象)
      
     3.定义类视图:
      from flask_restful import Resource
      class xxxApi(Resource):
        def get(self,uid):
            pass

        def post(self):
            pass

        def put(self):
            pass

        def delete(self):
            pass
      4. 添加路由
      api.add_resource(xxxApi,'/user')
      api.add_resource(xxxApi,'/user/<int:uid>')
      api.add_resource(xxxApi,'/goods',endpoint='goods')
      api.add_resource(xxxApi,'/goods',)

参照:http://www.pythondoc.com/Flask-RESTful/quickstart.html
https://flask-restful.readthedocs.io/en/latest/

41 restful-2
Request Parsing():
进:当浏览器提交数据时,服务器要验证数据
----------------进:请求参数传入-------------------

步骤:
1。创建RequestParser对象:
# 参数解析
parser = reqparse.RequestParser(bundle_errors=True)  # 获得解析对象,bundle_errors 显示所有错误信息(help)
2。给解析器添加参数:
    通过parser.add_argument('名字'type=类型,required=是否必须填写,help=错误的提示信息,location=表明获取的位置form就是post表单提交)
    注意在type的位置可以添加一些正则的验证等。type:str|int|inputs.regex()|FileStorage|...
    例如:
    parser.add_argument('username', type=str, required=True, help='必须输入用户名', location=['form'])
    parser.add_argument('password', type=inputs.regex(r'^\d{6,12}$'), required=True, help='必须输入6~12位数字密码',
                        location=['form'])
    parser.add_argument('phone', type=inputs.regex(r'^1[356789]\d{9}$'), location=['form'], help='手机号码格式错误')
    parser.add_argument('hobby', action='append')  # ['篮球', '游戏', '旅游']
    parser.add_argument('icon', type=FileStorage, location=['files'])
只要添加上面的内容,就可以控制客户端的提交,以及提交的格式。
3。在请求的函数中获取数据:
  可以在get,post,put等中获取数据,通过parser对象.parse_args()
  # 获取数据
  args = parser.parse_args()
  args是一个字典底层的结构中,因此我们获取具体的数据时可以通过get
  username = args.get('username')
  password = args.get('password')


42 restful-3
------------输出-----------------
1.需要定义字典,字典的格式就是给客户端看的格式
user_fields = {
    'id': fields.Integer,
    'username': fields.String(default='匿名'),
    'pwd': fields.String(attribute='password'),
    'udatetime': fields.DateTime(dt_format='rfc822')
}

客户端能看到的是: id,username,pwd,udatetime这四个key
默认key的名字是跟model中的模型属性名一致,如果不想让前端看到命名,则可以修改
但是必须结合attribute='模型的字段名'

自定义fields
1。必须继承Raw
2。重写方法:
   def format(self):
        return 结果
# 自定义:
class IsDelete(fields.Raw):
    def format(self, value):
        print('------------------>', value) # value值为iddelete字段的值
        return '删除' if value else '未删除'
user_fields = {
    。。。
    'isDelete1': IsDelete(attribute='isdelete'), # attribute='isdelete' 必须和数据库对应
    。。。
}

URI:
xxxlist ----->点击具体的一个获取详情 ------> 详情

定义两个user_fields,
1.用于获取用户的列表信息结构的fields:
user_fields_1 = {
    'id': fields.Integer,
    'username': fields.String(default='匿名'),
    'uri': fields.Url(endpoint='single_user', absolute=True) ---》参数使用的就是endpoint的值
}

2。具体用户信息展示的fields
user_fields = {
    'id': fields.Integer,
    'username': fields.String(default='匿名'),
    'pwd': fields.String(attribute='password'),
    'isDelete': fields.Boolean(attribute='isdelete'),
    'isDelete1': IsDelete(attribute='isdelete'),
    'udatetime': fields.DateTime(dt_format='rfc822')
}

涉及endpoint的定义:
api.add_resource(UserSimpleResource, '/user/<int:id>', endpoint='single_user')

出:
return data
注意:data必须是符合json格式
{
    "username": "shali",
    "nums": 1,
    "friends": [
        {
            "id": 3,
            "username": "bom",
            "password": "123456",
            "phone": "13562958862",
            "udatetime": "Tue, 09 Jun 2020 20:42:04 -0000",
            "isDelete": "已注销"
        }
    ]
}
如果直接返回,不能有自定义的对象User,Friend,。。。。

如果有这种对象,需要:marchal(),marchal_with()帮助进行转换。
1。marchal(对象,对象的fields格式)  # 对象的fields格式是指字典的输出格式
   marchal([对象,对象],对象的fields格式)

2。marchal_with() 作为装饰器修饰请求方法

    @marshal_with(user_friend_fields)
    def get(self, id):
        。。。。
        return data

 函数需要参数,参数就是最终数据输出的格式

 参数: user_friend_fields,类型是:dict类型
 例如:
 user_friend_fields = {
    'username': fields.String,
    'nums': fields.Integer,
    'friends': fields.List(fields.Nested(user_fields))
}

fields.Nested(fields.String)  ----> ['aaa','bbb','bbbc']
fields.Nested(user_fields)  -----> user_fields是一个字典结构,将里面的每一个对象转成user_fields
-----[user,user,user]

参考面试题:https://www.cnblogs.com/Utopia-Clint/p/10824238.html
43 跨域问题
一、跨域问题来源于JavaScript的"同源策略",即只有 协议+主机名+端口号 (如存在)相同,
则允许相互访问。也就是说JavaScript只能访问和操作自己域下的资源,
不能访问和操作其他域下的资源。跨域问题是针对JS和ajax的,html本身没有跨域问题。
后端:
1。使用第三方扩展(推荐)
flask-cors

from flask_cors import CORS
cors= CORS()

与app进行绑定
cors.init_app(app=app,supports_credentials=True) # supports_credentials 令牌

2。response = make_response()
response.headers['Access-Control-Allow-Origin']='*'
response.headers['Access-Control-Allow-Methods']='GET,POST'
response.headers['Access-Control-Allow-Headers']='x-request-with,Content-type'
return response

44 restful-蓝图
二、蓝图与api使用:
user_bp = Blueprint('user', __name__)

api = Api(user_bp)

class xxxxApi(Resource):
    pass

api.add_resource(xxxxApi,'/xxxx')

45 练习api
手机验证码:
需要 Redis cache
pip install redis
pip install flask-caching


sms_parser = reqparse.RequestParser()
sms_parser.add_argument('mobile', type=inputs.regex(r'^1[356789]\d{9}$'), help='手机号码格式错误', required=True,location=['form', 'args'])

# 发送手机验证码类视图
class SendMessageApi(Resource):
    def post(self):
        args = sms_parser.parse_args()
        mobile = args.get('mobile')
        ret, code = send_duanxin(mobile)
        # 验证是否发送成功
        if ret is not None:
            if ret["code"] == 200:
                # cache.set(key,value,timeout=second)
                cache.set(mobile, code, timeout=1800)
                return jsonify(code=200, msg='短信发送成功!')
        else:
            print("ERROR: ret.code=%s,msg=%s" % (ret['code'], ret['msg']))
            return jsonify(code=400, msg='短信发送失败!')

#copy() 继承
lr_parser = sms_parser.copy()
lr_parser.add_argument('code', type=inputs.regex(r'^\d{4}$'), help='必须输入四位数字验证码', required=True, location='form')

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值