Flask框架漫漫谈(未结...)

Flask

1.介绍

Flask诞生于2010年,是Armin ronacher(人名)用 Python 语言基于 Werkzeug 工具箱编写的轻量级Web开发框架。

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

其 WSGI 工具箱采用 Werkzeug(路由模块),模板引擎则使用 Jinja2。这两个也是 Flask 框架的核心。

官网: https://flask.palletsprojects.com/en/1.1.x/

官方文档: [http://docs.jinkan.org/docs/flask/]

2.准备工作

2.1创建虚拟环境

mkvirtualenv flask -p python3
或
mkvirtualenv flask

安装flask

pip3 install flask==0.12.5

2.2创建flask项目

与django框架不同,flask不会自动创建项目目录,需要手动创建启动项目的管理文件

创建项目文件夹flask_demo,

用pycharm打开该文件夹,选择创建好的虚拟环境

在这里插入图片描述

创建一个flask框架的主程序,main.py

from flask import Flask  
app = Flask(__name__)

@app.route('/')
def index():
  return 'hello world'

if __name__=='__mian__':
  #app.run(debug=True,port=8000,host='0.0.0.0')  #可以指定两个参数debug和端口(默认端口是5000)
  app.run()

注释:

#导入Flask类
from flask import Flask

"""
import_name      Flask程序所在的包(模块),传 __name__ 就可以
                 其可以决定 Flask 在访问静态文件时查找的路径
static_path      静态文件访问路径(不推荐使用,使用 static_url_path 代替)
static_url_path  静态文件访问路径,可以不传,默认为:/ + static_folder
static_folder    静态文件存储的文件夹,可以不传,默认为 static
template_folder  模板文件存储的文件夹,可以不传,默认为 templates
"""
app = Flask(import_name=__name__)

#编写路由视图
@app.route('/')
def index():
  return '<h2>这是一个二级标题</h2>'

#加载项目配置
class Config(object):
  #开启调试模式
  DEBUG=True
  
#注册,flask中支持多种配置方式,通过app.config来进行加载;
app.config.from_object(Config)

#app.run中可以指定配置,端口,ip等
if __name__=='__main__':
  #运行flask
  app.run(host='0.0.0.0',port=5000,debug=True)

3.路由的基本定义

路由:一种绑定url访问地址和应用程序[视图]的一对一映射关系

1.路由和视图唯一性

路由和视图的名称必须全局唯一,不能出现重复,否则报错

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

2.url中传递路由参数的两种方式

路由参数就是url路径的一部分

方式一.任意路由参数接收

[没有限定类型]路由传递参数

@app.route('/user/<user_id>')  #传递多个参数(/user/<user_id>-<mobile>),在进行路径上传递的时候两个实参数之间用-隔开
def user_info(user_id): #这里参数名必须和路由上的参数名保持一致
  return 'hello_%s' % user_id

方式二:限定类型传递参数
[限定类型]路由传递参数

限定路由参数的类型,flask系统自带转换器编写在werkzeug.routing.py文件中。底部可以看到以下字典:

DEFAULT_CONVERTERS = {
   "default": UnicodeConverter,
   "string": UnicodeConverter,
   "any": AnyConverter,
   "path": PathConverter,
   "int": IntegerConverter,
   "float": FloatConverter,
   "uuid": UUIDConverter,
}
转换器名称描述
string默认类型,接受不带斜杠的任何文本
int接受正整数
float接受正浮点值
path接收string但也接受斜线
uuid接受UUID(通用唯一识别码)字符串 xxxx-xxxx-xxxxx-xxxxx

代码:

#flask提供了路由转换器可以对路由参数进行限定
@app.route('/user/<int:user_id>')
def user_info(user_id):
  return 'hello_%s' % user_id

自定义路由参数转换器

在 web 开发中,可能会出现限制用户访问规则的场景,那么这个时候就需要用到正则匹配,根据自己的规则去限定请求参数再进行访问

(1)导入转换器基类:在 Flask 中,所有的路由的匹配规则都是使用转换器对象进行记录

(2)自定义转换器:自定义类继承于转换器基类

(3)添加转化器到默认的转化器字典中,也就和原来的int .float等放在一块

(4)使用自定义转化器实现自定义匹配规则
限定了正则的格式:

#(1)导入转换器基类
from werkzeug.routing import BaseConverter
#(2)自定义正则转换器
class MobileConverter(BaseConverter):
  '''手机号类型的限制'''
  def __init__(self,**kwargs,*args):
    super().__init__(**kwargs,*args):  #在初始化中必须调用父类对象进行初始化
      self.regex='^1[3-9]\d{9}'  #regex必须有参数
#(3)把自定义的转化器添加到flask默认的转化器字典中;进行注册
#app.url_map.converters['别名']=转换器类名
app.url_map.converters['mob'] = MobileConverter

#(4)调用自定义转化器
@app.route('/user/<mob:mobile>')
def index(mobile):
  return mobile 

不限定正则,直接在路由里面写正则参数:

#(1)导入转换器基类
from werkzeug.routing import BaseConverter
#(2)自定义正则转换器
class RegexConverter(BaseConverter):
  def __init__(self,map,*args):
    super().__init__(map)  #map相当于map_url
    self.regex=args[0]
#(3)把自定义的转化器添加到flask默认的转化器字典中;
app.url_map.converters['re'] = RegexConverter
#(4)调用自定义转化器
@app.route(rule='/user/<re('^1[1-9]\d{9}'):mobile>)
	def user(mobile):
      print(app.url_map)  #获取所有的路由列表
      return email     

3.路由限定请求方式

from flask import Flask,request
@app.route(rule='/user',methods=['post','put','get','delete','patch']) #没有写第二个参数methods默认只能由get方法请求过来
def user():
  print(request.method)  # 获取本次客户端的http请求方法
  print(request.query_string) #获取本次客户端的查询字符串
  print(request.path)  #获取本次客户端请求的路由路径部分[去掉域名端口] /user
  print(request.url)  #获取本次客户端请求的http url地址 http://127.0.0.1:5000/user?user=1
  return request.method

视图与url分离:

from flask import Flask
app=Flask(__name__)
def index():
	return 'ok'
app.add_url_rule(rule='/',view_func='index')
if __name__=='__main__':
	app.run(debug=True)	

4.http的请求与响应

文档: http://docs.jinkan.org/docs/flask/api.html#flask.request

请求

1.request

(1)代表flask中当前请求的request对象

(2)作用:在视图函数中期初本次请求的数据

(3)导入:from flask import request

(4)代码位置:from flask.app import Request

2.常用属性
属性说明类型
data记录请求体的数据,并转换为字符串
只要是通过其他属性无法识别转换的请求体数据
最终都是保留到data属性中
bytes类型
form记录请求中的html表单数据MultiDict
args记录请求中的查询字符串,也可以是query_stringMultiDict
cookies记录请求中的cookie信息Dict
headers记录请求中的请求头EnvironHeaders
method记录请求使用的HTTP方法GET/POST
url记录请求的URL地址string
files记录请求上传的文件列表*
json记录ajax请求的json数据json
from flask import Flask,request

app = Flask(__name__)

@app.route('/')
def index():
    return ('hello,flask')

#携带查询参数
@app.route(rule='/args',methods=['post','get'])
def args():
  #
    print(request.args)  #  ImmutableMultiDict([('name', 'haci'), ('age', '12'), ('hobby', 'shopping'), ('hobby', 'study')])
    #思路来源from collection import OrderedDict
    # 把ImmutableMultiDict转换成普通字典,转换过程如果出现一个键对应多个值的情况,则取第一个值
    
    print(request.args.to_dict())  # {'name': 'haci', 'age': '12', 'hobby': 'shopping'}
    print(request.args.to_dict(flat=False))  #  {'name': ['haci'], 'age': ['12'], 'hobby': ['shopping', 'study']}
    print(request.args.get('name'))  #  haci
    #getlist方法获取多选框的数据列表
    print(request.args.getlist('hobby'))  #  ['shopping', 'study']
    #获取请求方法
    print(request.method)  #  GET
    #获取请求url路径
    print(request.url)  #  http://127.0.0.1:5000/args?name=haci&age=12&hobby=shopping&hobby=study
    
    print(request.path)  #   /args

    return 'ok'

#请求体数据data
@app.route(rule='/data',methods=['put','patch','post'])
def data():
    """接受客户端发送过来的请求体数据,是request.json,request.form,request.files等无法接受的数据,全部会保留到这里"""
    print(request.data)
    # 接受表单提交的数据
    print(request.form)   #ImmutableMultiDict([('name', 'liu'), ('age', '234')])
    ## 接受ajax或其他客户端提交过来的json数据
    print(request.json)  #{'name': 'haci', 'age': 12}
    # 接受上传文件
    print(request.files['video'])  #postman中测试,form_data中选择file格式上传
    # 获取请求头信息
    print(request.headers)  #Content-Type
    print(request.headers['company']) #  获取自定义请求头

    return 'ok'

# 声明和加载配置
class Config(object):
    DEBUG=True
app.config.from_object(Config)

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

响应

flask默认支持2中响应方式:

数据响应: 默认响应html文本,也可以返回 JSON格式,或其他格式

页面响应:重定向(重定向到外站,url_for站内跳转–视图之间的跳转)

1.数据响应

(1)响应html文本

from flask import make_response

@app.route('/')
def index():
  return "<img src=''>"  #调用了make_response,是下面一句的简写
  #return make_response("<img src=''>") #先判断是不是response的实力对象,如果不是就把它实例化一下
  #return Response('ok')  #make_response本质上就是Response

(2)返回json数据

在 Flask 中可以直接使用 jsonify 生成一个 JSON 的响应

form flask import Flask,request,jsonify

@app.route('/')
def index():
  data={'name':'小白白','age':23}
  return jsonify(data)

(3)返回其他类型数据

# 关于Response常用的参数
    # Response(response="内容", status="http响应状态码",headers=自定义响应头,mimetype="数据格式")
    #return Response(response="ok",status=201,headers={"company":"hello"})
# 返回图片信息
    with open('./1.zip',"rb") as f:
        content=f.read()
    # 判断权限,身份...
    return Response(response=content,mimetype="application/zip")
2.重定向

(1)重定向到外部网站

from flask import Flask,request,redirect
app=Flask(__name__)
@app.route('/')
def user():
  return redirect('http://www.baidu.com')

(2)重定向到自己写的视图函数

可以直接填写自己的url路径;也可以使用url_for生成指定视图函数所对应的url

from flask import url_for
@app.route('/')
def user1():
  return 'my life'

@app.user2('/user2')
def user2():
  return redirect(url_for('user1',name='xiaoming '))  #携带查询参数

重定向到带有参数的视图函数

#路由传递参数
@app.route('/user/<user_id>')
def user_info(user_id):
  return 'hello_%s' % user_id

#重定向
@app.demo4():
  return redirect(url_for(endpoint='user_info',user_id=100))  #携带的参数为路径的一部分

3.自定义状态码和响应头

在 Flask 中,可以很方便的返回自定义状态码,以实现不符合 http 协议的状态码,例如:status code: 666

@app.route('/demo4')
def demo4():
    return '状态码为 666', 400
  
"""还可以使用make_response创建Response对象,然后通过response对象返回数据"""
from flask import make_response
@app.route("/rep")
def index7():
    response = make_response("ok")
    print(response)
    response.headers["Company"] = "oldboy" # 自定义响应头
    response.status_code = 201 # 自定义响应状态码
    return response

5.Http的会话控制

http会话,就是客户端浏览器和服务端网站之间一次完整的交互过程,会话开始于用户通过浏览器第一次访问服务端网站;会话结束于用户关闭浏览器.

会话控制,是在客户端浏览器和服务端网站之间,进行多次http请求响应之间,记录,跟踪和识别用户的信息而已.

http是一种无状态协议,浏览器请求服务器是无状态的.

无状态:指一次用户请求时,浏览器、服务器无法知道之前这个用户做过什么,每次请求都是一次新的请求。

无状态原因:浏览器与服务器是使用 socket 套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的 socket 连接,而且服务器也会在处理页面完毕之后销毁页面对象

实现状态保持主要有两种类型:

  • 在客户端存储信息使用url,Cookietoken令牌[jwt.csrf,oauth]
  • 在服务器端存储信息使用Session

cookie

Cookie是由服务器端生成,发送给客户端浏览器,浏览器会将Cookie的key/value保存,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。Cookie的key/value可以由服务器端自己定义。

Cookie是存储在浏览器中的一段纯文本信息,建议不要存储敏感信息如密码,因为电脑上的浏览器可能被其它人使用.

Cookie基于域名安全,不同域名的Cookie是不能互相访问的.
(如访问luffy.com时向浏览器中写了Cookie信息,使用同一浏览器访问baidu.com时,无法访问到luffy.com写的Cookie信息)

浏览器的同源策略针对cookie也有限制作用.
当浏览器请求某网站时,会将本网站下所有Cookie信息提交给服务器,所以在request中可以读取Cookie信息

在这里插入图片描述

设置cookie:
  response=make_response('ok')
  response.set_cookie('键','值',有效期)
获取cookie:
  request.cookies
  request.cookies.get('username')
删除cookie:(把对应名称的cookie设置为过期时间,就可以删除对应的cookie)
  response.set_cookie('键','',0)  

代码:(设置cookie,获取cookie,删除cookie)

from flask import Flask,make_response,request

app = Flask(__name__)


@app.route('/set_cookie')
def set_cookie():
    """设置cookie"""
    response = make_response('ok')
    # response.set_cookie(key='',value='',max_age)
    response.set_cookie('username','haci',100)
    response.set_cookie('age','100')
    return response

@app.route('/get_cookie')
def get_cookie():
    """获取cookie"""
    print(request.cookies)
    print(request.cookies.get('username'))
    print(request.cookies['age'])
    return 'ok'

@app.route('/del_cookie')
def del_cookie():
    '''删除cookie'''
    response=make_response('ok')
    #把对应名称的cookie设置为过期时间,则可以达到删除cookie
    response.set_cookie('age','',0)
    return response

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

session

session相关配置文档:https://dormousehole.readthedocs.io/en/latest/config.html?highlight=session_cookie_path

session是密文存储,且体积小,session依赖于cookie,session_id一般默认通过cookie来保存到客户端.

在这里插入图片描述
flask中的session需要加密,所以使用session之前必须配置SECRET_KEY选项,否则报错.

session的有效期默认是会话器,会话结束了,session就废弃了.

设置session:
  session['键'] = 值
获取session:
  session.get('键')
删除session:
  del session['键']
  session.clear()  #清除所有

代码:

from flask import Flask,session,Session

app = Flask(__name__)

#加载配置
class Config():
    SECRET_KEY = '123wertyhngf'
    DEBUG = True

app.config.from_object(Config)

@app.route(rule='/set_session')
def set_session():
    """设置session"""
    session['username'] = 'haci'
    session['age'] = 18
    session['info'] = {
        'number':90,
        'gender':1,
    }
    return ''

@app.route('/get_session')
def get_session():
    """获取session"""
    print(session.get('username'))
    print(session['age'])
    print(app.session_cookie_name)
    return 'ok'

@app.route('/del_session')
def del_session():
    try:
        """删除session"""
        del session['username']
        #session.clear()   # 删除所有
    except:
        pass
    return ''

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

查看当前flask默认支持的所有配置项

print(app.config)

"""
<Config {
 'DEBUG': False,
 'TESTING': False,
 'PROPAGATE_EXCEPTIONS': None,
 'PRESERVE_CONTEXT_ON_EXCEPTION': None,
 'SECRET_KEY': None,
 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31),
 'USE_X_SENDFILE': False,
 'LOGGER_NAME': '__main__',
 'LOGGER_HANDLER_POLICY': 'always',
 'SERVER_NAME': None,
 'APPLICATION_ROOT': None,
 'SESSION_COOKIE_NAME': 'session',
 'SESSION_COOKIE_DOMAIN': None,
 'SESSION_COOKIE_PATH': None,
 'SESSION_COOKIE_HTTPONLY': True,
 'SESSION_COOKIE_SECURE': False,
 'SESSION_REFRESH_EACH_REQUEST': True,
 'MAX_CONTENT_LENGTH': None,
 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200),
 'TRAP_BAD_REQUEST_ERRORS': False,
 'TRAP_HTTP_EXCEPTIONS': False,
 'EXPLAIN_TEMPLATE_LOADING': False,
 'PREFERRED_URL_SCHEME': 'http',
 'JSON_AS_ASCII': True,
 'JSON_SORT_KEYS': True,
 'JSONIFY_PRETTYPRINT_REGULAR': True,
 'JSONIFY_MIMETYPE': 'application/json',
 'TEMPLATES_AUTO_RELOAD': None
"""

6.请求钩子

在客户端和服务端交互的过程中,有些准备工作或扫尾工作需要处理,比如:

  • 在请求开始时,建立数据库连接;

  • 在请求开始时,根据需求进行权限校验;

  • 在请求结束时,指定数据的交互格式;

    为了避免每个视图函数写重复功能的代码,Flask提供了通设置的功能,即请求钩子:

    请求钩子以装饰器的形式实现,Flask支持如下戏中请求钩子:

    (1)before_first_request:在处理第一个请求前执行[项目初始化时的钩子],可以编写一些初始化项目的代码,例如,数据库初始化,加载一些可以延后引入的全局配置

    (2)before_request:在每次请求之前执行;如果在某修饰的函数中反悔了一个响应,视图函数将不再被调用

    (3)after_request:如果没有抛出错误,则在每次请求后执行;接受一个参数(视图函数做出的响应);在此函数中可以对象用值在返回之前做最后一步的修改处理

    (4)teardown_request:在每次请求后执行;接受一个参数:错误信息,如果有相关错误抛出;需要设置flask的配置DEBUG=False,teardown_request才会接受到异常对象。

    代码:

    from flask import Flask,request,make_response
    
    app = Flask(__name__)
    
    class Config():
        DEBUG=True
    app.config.from_object(Config)
    
    @app.before_first_request
    def first_request():
        print('1.项目启动以后,首次被请求时,[项目全局初始化工作]')
    
    @app.before_request
    def before_request():
        print('2.每次客户端请求时,都会自动执行,常用于记录访问日志,进行权限判断,身份识别,访问限流...')
    
    @app.after_request
    def after_request(response):
        # after_request执行以后,必须要返回结果给客户端!!
        print('4.每次视图执行以后,会自动执行')
    
        return response
    
    @app.teardown_request
    def teardown_request(exc):
        print('5.after_request完成以后,如果有发生异常,在关闭DEBUG模式的情况下可以接受异常对象,进行异常的记录,异常通知')
        print(exc)
    
    @app.route('/')
    def index():
        print('3.视图执行了.......')
        return 'success'
    
    if __name__ == '__main__':
        app.run()
    

    7.异常捕获

    主动抛出HTTP异常

    abort方法:抛出一个给定状态代码的 HTTPException 或者 指定响应,例如想要用一个页面未找到异常来终止请求,你可以调用 abort(404)

    abort(400)
    abort(500)
    

    捕获错误

    errorhandler装饰器:

    注册一个错误处理程序,当程序抛出指定错误状态码或者指定异常的时候,就会调用该装饰器所装饰的方法.

    参数:错误状态码或者指定异常

    from flask import Flask,abort
    app = Flask(__name__)  #创建一个flask应用
    
    #加载配置
    class Config():
        DEBUG=True
    app.config.from_object(Config)
    
    #在主函数中抛出400的错误abort(400),在此处进行定义来捕获该错误
    @app.errorhandler(400)
    def error_400(e):
        return 'you are worry!!!'
    
    #捕获指定异常类型ZeroDivisionError
    @app.errorhandler(ZeroDivisionError)
    def zero_division_error(e):
        return '除数不能为0'
    
    @app.route('/')
    def index():
        # abort(400)
        # 7/0
        return 'ok'
    
    if __name__=='__main__':
        app.run()
    

    7.context

    执行上下文:即语境,语意,在程序中可以理解为在代码执行到某一行时,根据之前代码所做的操作以及下文即将要执行的逻辑,可以决定在当前时刻下可以使用到的变量,或者可以完成的事情。

    Flask中上下文对象:相当于一个容器,保存了 Flask 程序运行过程中的一些信息[变量、函数、类与对象等信息]。

    Flask中有两种上下文,请求上下文(request context)和应用上下文(application context)。

    1.application指的是创建Flask对象:app=Flask(__name__)的app;
    2.request指的是每次http请求发生时,WSGI server调用Flask.__call__()之后,在Flask对象内部创建的Request对象;
    3.application表示用于响应WSGI请求的应用本身,request表示每次http请求;
    4.application的生命周期大于request,一个application存活期间,可能发生多次http请求,所以也会有多个request
    5.请求上下文:保存了客户端和服务器交互的数据,一般来自于客户端;应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如路由列表,程序名、数据库连接、应用信息等
    

    请求上下文(request context)

    思考:在视图函数中,如何取到当前请求的相关数据?比如:请求地址,请求方式,cookie等等

    在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文的对象,保存了当前本次请求的相关数据,请求上下文对象有:request、session

    • request
      • 封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get(‘user’),获取的是get请求的参数。
    • session
      • 用来记录请求会话中的信息,针对的是用户信息。举例:session[‘name’] = user.id,可以记录用户信息。还可以通过session.get(‘name’)获取用户信息。

    请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用

    应用上下文(application context)

    它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中操作当前falsk应用对象 app 的代理(人),所谓local proxy。它的作用主要是帮助 request 获取当前的flask应用相关的信息,它是伴 request 而生,随 request 而灭的。

    应用上下文对象有:current_app,g

    current_app

    应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:

    • 应用的启动脚本是哪个文件,启动时指定了哪些参数
    • 加载了哪些配置文件,导入了哪些配置
    • 连接了哪个数据库
    • 有哪些可以调用的工具类、常量
    • 当前flask应用在哪个机器上,哪个IP上运行,内存多大
    from flask import Flask
    #创建flask应用
    app = Flask(__name__)
    
    #声明和加载配置
    class Config():
      DEBUG=True
    app.config.from_object(Config)
    
    #编写路由视图
    @app.route(rule='/')
    def index():
      print(current_app.config)
      print(current_app.url_map)
      
      return 'ok'
    
    if __name__=='__main__':
      app.run()
    

g变量

g作为flask程序全局的一个临时变量,充当着中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别

from flask import Flask,g,current_app

app = Flask(__name__)

class Config():
    DEBUG=True
app.config.from_object(Config)


def func2():
    print('func2')
    print(g.abc)

def func1():
    print('func1')
    func2()

@app.route('/')
def index():
    g.abc='视图中的数据'
    func1()
    return 'ok'

if __name__ == '__main__':
    with app.app_context():
        print(g)
    app.run()

8.Flask-script扩展

文档: https://flask-script.readthedocs.io/en/latest/

安装命令:

pip install flask-script

集成 Flask-Script到flask应用中,创建一个主应用程序,一般我们叫manage.py/run.py/main.py都行。

1.通过终端来控制flask项目的运行,类似于django的manage.py

from flask import Flask
from flask_script import Manager

app= Flask(__name__)

class Config():
    DEBUG=True
app.config.from_object(Config)

# 注册终端脚本工具到app中
manager = Manager(app)

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

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

终端启动程序命令:

# 端口和域名不写,默认为127.0.0.1:5000
python run.py runserver
# 通过-h设置启动域名,-p设置启动端口
python run.py runserver -h127.0.0.1 -p8000

2.为当前应用程序添加脚本命令

1.引入Command基类
2.创建命令类必须直接或间接继承Command,并子啊内部实现run方法,同时如果有自定义的其他参数,则必须实现__init__
3.将创建的命令注册,并设置调用终端别名

代码:

from flask import Flask
from flask_script import Manager,Command,Option

app=Flask(__name__)


"""基于flask_script创建自定义终端命令"""
class HelloCommand(Command):
    option_list = [
        Option('--name','-n',help='名称'),
        Option('--num','-m',help='数量')
    ]

    def run(self,name,num):
        print('name=%s' % name)
        print(num)
        print('命令执行了')


# 注册终端脚本工具到app中
manager = Manager(app)
manager.add_command('hello',HelloCommand)

class Config():
    DEBUG=True
app.config.from_object(Config)


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

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


执行:

python main.py hello
python main.py hello -n=100 -m=23  #添加了option_list后给run函数中传递参数

3.自定义脚手架

from flask import Flask
from flask_script import Manager,Command,Option
import os
app = Flask(__name__)

class Config():
    DEBUG = True
        
app.config.from_object(Config)
class BluePrintCommand(Command):
    option_list=[
        Option('--name','-n',help='蓝图名称')
    ]
    def run(self,name=None):
        if name is None:
            print('蓝图名称不能为空')
            return
        if not os.path.isdir(name):
            os.mkdir(name)
        open('%s/views.py' % name,'w')
        open('%s/models.py' % name,'w')
        with open('%s/urls.py' % name,'w') as f:
            f.write('''from . import views
urlpatterns=[
            
]
            ''')
       
manager = Manager(app)
manager.add_command('blue',BluePrintCommand)

@app.route('/')
def index():
    manager.run()

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

9.Jinja2模板引擎

Flask内置的模板语言,它的设计思想来源于 Django 的模板引擎,并扩展了其语法和一系列强大的功能。

渲染模版函数

Flask提供的 render_template 函数封装了该模板引擎;

render_template 函数的第一个参数是模板的文件名,后面的参数都是键值对,表示模板中变量对应的真实值

9.1模板的基本使用

1.在flask对象应用创建的时候,设置存放模板的文件夹,template_folder参数

app=Flask(__name__,template_folder='templates')

2.在视图函数中返回模板时,需要使用引入的render_template

return render_template('index.html')

9.2加载模板并传递数据到模板

在模板中,{{}} 来表示变量名,这种 {{}} 语法叫做 变量代码块
在视图函数中在render_template的第二个参数中存放的是要传递到模板中的键值对

main.py

from flask import Flask,render_template
app = Flask(__name__,template_folder='templates')
class Config():
    DEBUG = True
app.config.from_object(Config)
@app.route('/')
def index():
    data ={}
    data['title'] = '我的第一个flask网页'
    data['num'] = 100
    return render_template('index1.html',**data)

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

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>{{title}}</h1>
    <h2>{{num}}</h2>
</body>
</html>

9.2使用模板语句if/for

{%for i in data%}
{%endfor%}

{%if%}
{%endif%}

for循环中的特殊变量:

变量描述
loop.index当前循环迭代的次数(从 1 开始)
loop.index0当前循环迭代的次数(从 0 开始)
loop.revindex到循环结束需要迭代的次数(从 1 开始)
loop.revindex0到循环结束需要迭代的次数(从 0 开始)
loop.first如果是第一次迭代,为 True 。
loop.last如果是最后一次迭代,为 True 。
loop.length序列中的项目数。
loop.cycle在一串序列间期取值的辅助函数。见下面示例程序。

Main.py

from flask import Flask,render_template

app=Flask(__name__,template_folder='templates')

@app.route('/')
def index():
    data={}
    data['data_list']=['this','that','he','she']
    data['info']={
        'name':'haci',
        'age':12,
    }
    data['data_dict']=[
        {'id':1,'name':'第一个课程'},
        {'id':2,'name':'第二个课程'},
        {'id':3,'name':'第四个课程'},
        {'id':4,'name':'第个课程'},
        {'id':5,'name':'第五个课程'}
    ]
    return render_template('index2.html',**data)

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

Index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p>访问字典数据中的成员</p>
    <h3>{{info["name"]}}</h3>
    <h3>{{info.age}}</h3>
    <p>访问列表数中的成员,点语法不支持负数下标</p>
    <p>{{data_list.0}}</p>
    <p>{{data_list[0]}}</p>
    <p>{{data_list[-1]}}</p>
    <p>判断</p>
    {%if info.age < 18%}
    <p>年龄太小了</p>
    {%elif info.age < 40%}
    <p>年龄刚好</p>
    {%else%}
    <p>result</p>
    {%endif%}
    <p>for循环</p>

    <table border="1" align="center" width="680px">
        <tr>
            <th>序号</th>
            <th>ID</th>
            <th>名称</th>
        </tr>
         {%for data in data_dict%}
            {%if loop.index%2 %}  #隔行换色
            <tr bgcolor="#ff7f50">
            {%else%}
        <tr>
            {%endif%}
            <td>{{loop.index0}}</td>
            <td>{{data.id}}</td>
            <td>{{data.name}}td>
        </tr>
        {%endfor%}
    </table>
    <ul>
        {%for data in data_list%}
            {%if loop.first%} 
            <li style="background-color: #000;color: coral">{{data}}</li>
            {%elif loop.last%}
            <li style="background-color:#000000;color: aqua;">{{data}}</li>
            {%else%}
            <li>{{data}}</li>
            {%endif%}
        {%endfor%}
    </ul>
</body>
</html>

9.4模板中显示内置变量

request,   #flask中代表当前请求的request对象
config, config.DEBUG,  #从模板中直接访问Flask当前的config对象:
session,session[],  #为Flask的session对象,显示session数据
url_for('...'),  #url_for会根据传入的路由器函数名,返回该路由对应的URL,在模板中始终使用url_for()就可以安全的修改路由绑定的URL,则不比担心模板中渲染出错的链接
  #{{ url_for('index', post_id=1)}}  -->/1

g变量   #在视图函数中设置g变量的 name 属性的值,然后在模板中直接可以取出

Main.py

from flask import Flask,render_template,session

app=Flask(__name__,template_folder='templates')

class Config():
    DEBUG=True
    SECRET_KEY='123dhsjadhaslde'

app.config.from_object(Config)

@app.route('/')
def index():
    data={}
    session['username']='haci'
    return render_template('index3.html',**data)
if __name__=='__main__':
    app.run()

Index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p>{{request.url}}</p>
    <p>{{request.path}}</p>
    <p>{{session}}</p>
    <p>{{session.username}}</p>
    <p>{{request.method}}</p>
    <p>{{url_for("index")}}</p>
    <p>{{config}}</p>
    <p>{{config.DEBUG}}</p>
</body>
</html>

设置当前项目中模板语言:

image-20201121190815781

9.5过滤器

1.常见的内置过滤器

字符串操作:

(1)safe:禁止转义

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

(2)lower:把值转成小写

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

(3)upper:把值转成大写

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

(5)reverse:字符串反转

(6)format:格式化输出

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

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

<p>{{ '<em>hello</em>' | striptags }}</p>
<p>{{ "如果x<y,z>x,那么x和z之间是否相等?" | striptags }}</p>  //出现大于号小于号,容易误删内容

(8)truncate:字符截断

<p>{{ '床前明月光,疑是地上霜。' | truncate(5,False,'...', 0)}}</p>

列表操作

(1)first:取出第一个元素

(2)last

(3)length:获取列表长度

(4)sum:列表求和

(5)sort:列表排序

main.py

from flask import Flask,render_template

app=Flask(__name__,template_folder='templates')

@app.route('/')
def index():
    data={}
    data['msg1']='gdgds7271hsahs'
    data['msg2']='my life is so beautiful'
    data['msg3']=[1,2,3,8,4,3,2]
    data['msg4']='<a href="http://www.baidu.com">baidu</a>'
    return render_template('index4.html',**data)

if __name__=='__main__':
    app.run(debug=True)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p>{{msg1 | upper}}}</p>
    <p>{{msg2 | title}}</p>
    <p>{{msg2 | capitalize}}</p>
    <p>{{msg2 | reverse}}</p>
    <h1>列表操作</h1>
    <p>{{msg3 | unique | list}}</p>
<!--    [1, 2, 3, 8, 4]-->
    <p>{{msg3 | first}}</p>
    <p>{{msg3 | last}}</p>
    <p>{{msg3 | length}}</p>
    <p>{{msg3 | sum}}</p>
    <p>{{msg3 | sort}}</p>

    <p>{{msg4 | safe}}</p>
    <p>{{msg4}}</p>
    <p>{{msg4 | striptags}}</p>

</body>
</html>
2.自定义过滤器

过滤器的本质是函数。当模板内置的过滤器不能满足需求,可以自定义过滤器。自定义过滤器有两种实现方式:

  • 一种是通过Flask应用对象的 add_template_filter 方法
  • 通过装饰器来实现自定义过滤器

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

示例:给手机号进行部分屏蔽

#注意:

静态文件的存储需要进行设置:
在实例化Flask对象的时候,用static_folder参数来保存当前flask应用静态资源的文件夹;static_url_path来指定当前flask应用提供给外界访问该静态资源的路径前缀,必须以/开头

main.py

from flask import Flask,render_template

app = Flask(__name__,# 当前flask应用运行的模块
            template_folder='templates',
            static_folder='static',# 当前flask应用保存静态资源[css/js/img/音视频]
            static_url_path='/static', # 当前flask应用提供给外界访问的路径前缀,必须以/开头
            )
class Config():
    DEBUG=True
    SECRET_KEY='dhsajdhaud71237bsa'
app.config.from_object(Config)

#自定义过滤器
def do_mobile(content,string):
    return content[:3] + string + content[-3:]
#将过滤器注册到app  
app.add_template_filter(do_mobile,'mobile')

@app.route('/')
def index():
    data={}
    data['img']='<img src="/static/images/1.jpg">'
    data['info_list']=[
        {'id':14,'name':'xiaobaibai','num':'18434391584'},
        {'id':26,'name':'hsci','num':'13935228374'}
    ]
    return render_template('index5.html',**data)
if __name__=='__main__':
    app.run(debug=True)

Index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 <p>{{img | safe}}</p>

 <table border="1px" width="680px">
     <tr>
         <th>序号</th>
         <th>id</th>
         <th>姓名</th>
         <th>手机号</th>
     </tr>
     {%for info in info_list%}
     <tr>
         <td>{{loop.index}}</td>
         <td>{{info.id}}</td>
         <td>{{info.name}}</td>
         <td>{{info.num | mobile('*****')}}</td>
     </tr>
     {%endfor%}
 </table>
</body>
</html>

9.6模板的继承

在模板中,可能会遇到以下情况:

  • 多个模板具有完全相同的顶部和底部内容
  • 多个模板中具有相同的模板代码内容,但是内容中部分值不一样
  • 多个模板中具有完全相同的 html 代码块内容

像遇到这种情况,可以使用 JinJa2 模板中的 继承 来进行实现

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

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

  • 子模板使用 extends 指令声明这个模板继承自哪个模板

    {%extends 'base.html'%}
    {%block title%}
    填充自己的内容
    {{super()}}  #继承父模板中的内容
    {%endblock title%}
    
  • 父模板中定义的块在子模板中被重新定义,在子模板中调用父模板的内容可以使用super()

Main.py

from flask import Flask,render_template

app=Flask(__name__,
          static_folder='static',
          static_url_path='/static',
          template_folder='templates'
          )

@app.route('/')
def index():

    return render_template('index6.html')

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

Base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
    {%block title%}
       模板标题
    {%endblock title%}
    </title>
</head>
<body>

    {%block content%}
        <p>父级模板的content</p>
    {%endblock content%}

</body>
</html>

Index.html

{%extends "base.html%"}

{%block content%}
    {{ super() }}
    <p>子模板的内容</p>
    {{ super() }}
{% endblock content %}

9.7在 Flask 项目中解决 CSRF 攻击

pip install flask_wtf

在 Flask 中, Flask-wtf 扩展有一套完善的 csrf 防护体系,对于我们开发者来说,使用起来非常简单

  1. 设置应用程序的 secret_key,用于加密生成的 csrf_token 的值
# 1. session加密的时候已经配置过了.如果没有在配置项中设置,则如下:
app.secret_key = "#此处可以写随机字符串#"

# 2. 也可以写在配置类中。
class Config(object):
    DEBUG = True
    SECRET_KEY = "dsad32DASSLD*13%^32"
    
"""加载配置"""
app.config.from_object(Config)
  1. 导入 flask_wtf.csrf 中的 CSRFProtect 类,进行初始化,并在初始化的时候关联 app
from flask.ext.wtf import CSRFProtect
CSRFProtect(app)

3.在表单中使用 CSRF 令牌:

<form method="post" action="/">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
</form>

main.py

from flask import Flask,render_template,request
from flask_wtf import CSRFProtect


app=Flask(__name__,template_folder='templates')

csrf=CSRFProtect(app)

app.config['SECRET_KEY']='hashdasdh7283das73has'
@app.route('/')
def index():
    data={}
    return render_template('index7.html',**data)

@app.route('/login',methods=['POST'])
def login():
    print(request.form)
    return 'ok'
if __name__=='__main__':
    app.run(debug=True)

Index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="{{url_for('login')}}" method="post">
    <input type="hidden" name="csrf_token" value="{{csrf_token()}}">
    账号:<input type="text" name="username" value=""> <br>
    密码: <input type="password" name="password" value=""> <br>
    <input type="submit" value="login">

</form>
</body>
</html>

10.数据库操作

ORM 全拼Object-Relation Mapping,中文意为 对象-关系映射。主要实现模型对象到关系数据库数据的映射

优点 :

  • 只需要面向对象编程, 不需要面向数据库编写代码.
    • 对数据库的操作都转化成对类属性和方法的操作.
    • 不用编写各种数据库的sql语句.
  • 实现了数据模型与数据库的解耦, 屏蔽了不同数据库操作上的差异.
    • 不再需要关注当前项目使用的是哪种数据库。
    • 通过简单的配置就可以轻松更换数据库, 而不需要修改代码.

缺点 :

  • 相比较直接使用SQL语句操作数据库,有性能损失.
  • 根据对象的操作转换成SQL语句,根据查询的结果转化成对象, 在映射过程中有性能损失.

Flask-SQLAlchemy

flask默认提供模型操作,但是并没有提供ORM,所以一般开发的时候我们会采用flask-SQLAlchemy模块来实现ORM操作。

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

SQLAlchemy: https://www.sqlalchemy.org/

中文文档: https://www.osgeo.cn/sqlalchemy/index.html

安装 flask-sqlalchemy【清华源】

pip install flask-sqlalchemy -i https://pypi.tuna.tsinghua.edu.cn/simple

如果连接的是 mysql 数据库,需要安装 mysqldb 驱动

pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple

安装flask-mysqldb时,注意

安装 flask-mysqldb的时候,python底层依赖于一个底层的模块 mysql-client模块
如果没有这个模块,则会报错如下:

Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-21hysnd4/mysqlclient/

解决方案:

sudo apt-get install libmysqlclient-dev python3-dev

运行上面的安装命令如果再次报错如下:
   dpkg 被中断,您必须手工运行 ‘sudo dpkg --configure -a’ 解决此问题。

则根据提示执行命令以下命令,再次安装mysqlclient
	sudo dpkg --configure -a
	apt-get install libmysqlclient-dev python3-dev

解决了mysqlclient问题以后,重新安装 flask-mysqldb即可。
pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple

数据库连接设置

数据库连接设置

  • 在 Flask-SQLAlchemy 中,数据库使用URL指定,而且程序使用的数据库必须保存到Flask配置对象的 SQLALCHEMY_DATABASE_URI 键中

    config.py,配置文件代码:

class Config(object):
    DEBUG = True
    SECRET_KEY = "*(%#4sxcz(^(#$#8423"
    # 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
    SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1:3306/students?charset=utf8mb4"
  • 其他设置:
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
#查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
  • 配置完成需要去 MySQL 中创建项目所使用的数据库
$ mysql -uroot -p123
mysql > create database students charset=utf8mb4;

常用的SQLAlchemy字段类型

模型字段类型名python中数据类型说明
Integerint普通整数,一般是32位
SmallIntegerint取值范围小的整数,一般是16位
BigIntegerint或long不限制精度的整数
Floatfloat浮点数
Numericdecimal.Decimal普通数值,一般是32位
Stringstr变长字符串
Textstr变长字符串,对较长或不限长度的字符串做了优化
Unicodeunicode变长Unicode字符串
UnicodeTextunicode变长Unicode字符串,对较长或不限长度的字符串做了优化
Booleanbool布尔值
Datedatetime.date日期
Timedatetime.time时间
DateTimedatetime.datetime日期和时间
LargeBinarystr二进制文件内容

常用的SQLAlchemy列约束选项

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

数据库基本操作

  • 在Flask-SQLAlchemy中,添加、修改、删除操作,均由数据库会话管理。
    • 会话用 db.session 表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用 db.commit() 方法提交会话。
  • 在 Flask-SQLAlchemy 中,查询操作是通过 query 对象操作数据。
    • 最基本的查询是返回表中所有数据,可以通过过滤器进行更精确的数据库查询。
1.定义模型类(创建和删除表)
from flask import Flask,render_template,request

from flask_sqlalchemy import SQLAlchemy
app=Flask(__name__)

class Config():
    DEBUG=True
    #数据库连接配置
    SQLALCHEMY_DATABASE_URI='mysql://all:123456@127.0.0.1:3306/students?charset=utf8mb4'
    #动态追踪修改设置,如未设置只会提示警告
    SQLALCHEMY_TRACK_MODIFICATIONS=True
    #显示原始SQL语句
    SQLCHEMY_ECHO=True

app.config.from_object(Config)

# 初始化SQLAlchemy
db=SQLAlchemy()  # 初始化数据库操作对象
db.init_app(app)  # 初始化数据库链接

"""创建模型类"""
class Student(db.Model):
    # 表结构声明
    __tablename__='tb_student'
     # 字段声明
    id=db.Column(db.Integer,primary_key=True,comment='主键ID')
    name=db.Column(db.String(250),comment='姓名')
    age=db.Column(db.Integer,comment='年龄')
    sex=db.Column(db.Boolean,default=True,comment='性别')
    money=db.Column(db.DECIMAL(8,2),nullable=True,comment='钱包')
	
  	# 自定义方法
    def __repr__(self):
        return self.name

class Teacher(db.Model):
    __tablename__='tb_teacher'
    id=db.Column(db.Integer,primary_key=True,comment='ID')
    name=db.Column(db.String(250),comment='姓名')
    sex=db.Column(db.Boolean,default=False,comment='性别')
    option=db.Column(db.Enum("讲师","助教","班主任"),default='讲师',comment="教职")

    def __repr__(self):
        return self.name

class Course(db.Model):
    __tablename__='tb_course'
    id=db.Column(db.Integer,primary_key=True,comment='ID')
    name=db.Column(db.String(250),unique=True,comment='课程名称')
    price=db.Column(db.Numeric(6,2))

    def __repr__(self):
        return self.name

@app.route('/')
def index():

    return 'ok'

if __name__=="__main__":
    with app.app_context():
        db.create_all()   # 根据模型创建所有的数据表
        #db.drop_all()  # 删除模型对应的所有数据表
    app.run()
2.数据库基本操作(视图函数中)

添加数据:

#添加一条数据:
 student1=Student(name='xiaobaibai',age=12,money=567)
    db.session.add(student1)
    db.session.commit()
    
#批量添加多条数据:
data_list=[
        Student(name='haci',age=43,money=897),
        Student(name='haci222',age=23,money=90),
        Student(name='dhjsa',sex=True,money=82822,age=25)
    ]
    db.session.add_all(data_list)
    db.session.commit()

查询数据:

#根据主键ID查询一条数据,如果ID不存在,则返回None不会报错!
    student=Student.query.get(100)
    if student is None:
      print('the studnet dosenot exist!!!')
    else:
        print(student,type(student))   #Student的对象
        print(student.name,student.age)

#根据查询条件获取一条数据        
# student=Student.query.filter(Student.id==1)  					#不能使用get
    print(student)      #打印的是sql语句
    print(student[0],'----',type(student[0]))    #Student的对象
    print(student.first(),'=====')
    print(student.first().name)
    
#根据查询条件获取多条数据
    #模型.query.filter(模型.字段==条件值).all()
    studnet_list=Student.query.filter(Student.id<5).all()
    print(studnet_list)
    for i in studnet_list:
        print(i.age,i.money)    

更新数据:

#先查询后修改
    student=Student.query.filter(Student.name=='haci').first()
    student.money+=1000
    db.session.commit()
    
#直接根据条件修改(乐观锁---边查询边修改)
    Student.query.filter(Student.name=='xiaobaibai').update({Student.money:2000})
    Student.query.filter(Student.name=='haci').update({Student.money:Student.money+890})
    db.session.commit()
        

删除数据:

# 先查询后删除
    student=Student.query.filter(Student.age==23).first()
    db.session.delete(student)
    db.session.commit()

# 直接根据条件进行删除操作
    Student.query.filter(Student.id==1).delete()
    db.session.commit()
3.数据库的进阶查询
1.filter设置判断条件查询
==	,		>=,		<,		>,		<=,		!=
2.filter设置模糊查询
#like模糊条件
# 模型.字段.like("%值%")  等价于  模型.字段.contains("值")    包含xxx
# 模型.字段.like("值%")   等价于  模型.字段.startswith("值")  以xxx开头
# 模型.字段.like("%值")   等价于  模型.字段.endswith("值")    以xxx结尾
# 模型.字段.like("__")    值长度为2个字符的.几个下划线代表几个字符
3.filter多条件查询
from sqlalchemy import and_,or_,not_
#and_
student_list=Student.query.filter(and_(Student.age==12,Student.sex==1)).all()
#等价于
student_list=Student.query.filter(Student.age==12,Student.sex==1).all()
#or
stduent_list=Student.query.filter(or_(Student.age==23,Student.name=='haci')).all()
#not_
student_list=Student.query.filter(not_(Student.age==23)).all()
#in_  值范围查询
student_list=Student.query.filter(Student.age.in_([23,14,6])).all()
4.filter_by查询
filter_by只支持一个符号作为判断条件,而且字段左边不需要声明模型类型;
可用于获取单条数据也可用于获取多条数据;
student=Student.query.filter_by(name=='haci').all()
5.order_by结果排序
#order_by(模型.字段.desc())   db.desc(模型.字段)    倒序
#order_by(模型.字段.asc())    db.asc(模型.字段)     升序

Student_list=Student.query.order_by(Student.age.desc()).all()
student_list=Student.query.order_by(db.desc(Student.money)).all()
6.count 统计结果数量
ret=Student.query.filter(Student.age==12).count()

7.limit对结果数量进行限制,offset对开始位置进行限制
student_list=Student.query.order_by(Student.money).offset(1).limit(3).all()
8.paginate分页器
#paginate(page=当前页面,per_page=每一页数据量,max_per_page=每一页最大数据量)
#当前页码.默认是从request.args['per_page']中读取,如果当前参数没有设置值,则默认为1;
#每页的数据量.默认是100条
分页器提供了request.args['per_page']和request.args['page']给客户端,客户端可以进行设置来限定;
#代码:

main.py

from flask import Flask,jsonify,render_template,request
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__,template_folder='templates')

class Config():
    DEBUG=True
    JSON_AS_ASCII=False
    SQLALCHEMY_DATABASE_URI='mysql://all:123456@127.0.0.1:3306/students?charset=utf8mb4'
    SQLALCHEMY_TRACK_MODIFICATIONS=True
    SQLALCHEMY_ECHO=True

app.config.from_object(Config)
db=SQLAlchemy()
db.init_app(app)

"""创建模型类"""
class Student(db.Model):
    __tablename__ = "tb_student"
    id = db.Column(db.Integer, primary_key=True,comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    age = db.Column(db.Integer, comment="年龄")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    money = db.Column(db.DECIMAL(8,2), nullable=True, comment="钱包")

    def __repr__(self):
        return self.name

class Teacher(db.Model):
    __tablename__ = "tb_teacher"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    option = db.Column(db.Enum("讲师","助教","班主任"), default="讲师", comment="教职")

    def __repr__(self):
        return self.name

class Course(db.Model):
    __tablename__ = "tb_course"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), unique=True, comment="课程名称")
    price = db.Column(db.Numeric(6, 2))

    def __repr__(self):
        return self.name

@app.route('/')
def index():
    """
    filter设置判断条件
    ==,
    >=,
    <
    >
    <=
    !=
    """
    # student=Student.query.filter(Student.name=='xiaobaibai').first()
    # if student is None:
    #     return jsonify({'error':100404,'errormsg':''})
    # print(student)
    # print(student.age)

    """filter设置模糊查询"""
    #like模糊条件
    #模型.字段.like("%值%")  等价于  模型.字段.contains("值")    包含xxx
    # 模型.字段.like("值%")   等价于  模型.字段.startswith("值")  以xxx开头
    # 模型.字段.like("%值")   等价于  模型.字段.endswith("值")    以xxx结尾
    # 模型.字段.like("__")    值长度为2个字符的.几个下划线代表几个字符
    # student_list=Student.query.filter(Student.name.like('%a%')).all()
    # student_list=Student.query.filter(Student.name.startswith('xiao')
    # student_list=Student.query.filter(Student.name.like('____')).all()
    # print(student_list)

    '''
    filter_by
    filter_by 只支持一个等号作为判断条件,而且字段左边不需要声明模型类名
    可以用于获取一条数据,也可以获取多条数据
    '''
    # student=Student.query.filter_by(name='haci')[0]
    # student=Student.query.filter_by(name='haci').all()

    '''
    filter多条件查询
    '''
    from sqlalchemy import and_,or_,not_
    '''and_'''
    # student_list=Student.query.filter(and_(Student.age==12,Student.sex==1)).all()
    # student_list=Student.query.filter(Student.age==12,Student.sex==1).all()
    '''or_'''
    # student_list=Student.query.filter(or_(Student.name=='haci',Student.age==12)).all()
    '''not_'''
    # student_list=Student.query.filter(not_(Student.name.startswith('ha'))).all()
    '''值范围查询  in_'''
    # student_list=Student.query.filter(Student.age.in_([23,12,56])).all()
    '''order_by结果排序'''
    # order_by(模型.字段.desc())   db.desc(模型.字段)    倒序
    # order_by(模型.字段.asc())    db.asc(模型.字段)     升序
    # student_list=Student.query.order_by(Student.money.desc()).all()
    # student_list=Student.query.order_by(db.desc(Student.money)).all()
    # print(student_list)
    """count 统计结果数量"""
    # ret=Student.query.filter(Student.sex==1).count()
    # print(ret)
    '''limit 结果数量进行限制'''
    '''offset 对查询开始位置进行设置'''
    #对学生的钱包从大到小排序,第2-3名的学生
    # student_list=Student.query.order_by(Student.money.desc()).offset(1).limit(2).all()
    # print(student_list)
    '''paginate分页器'''
    #paginate(page=当前页码,per_page=每一页数据量,max_per_page=每一页最大数据量)
    #当前页码,默认是从request.args["page"],如果当前参数没有值,则默认为1
    # 每一页数据量,默认是100条
    # 因为分页器有提供了一个  request.args["per_page"]给客户端设置每一页数据量,所以再次限定客户端最多能设置的每一页数据量
    # pagination=Student.query.paginate(page=1)
    pagination=Student.query.paginate(per_page=2)
    # print(request.args['page'],'-----------------------------')
    # print('当前页面对象:', pagination)
    # print('前页数据量:', pagination.items)
    # print('还有下一页数据:', pagination.has_next)
    # print('还有上一页数据:', pagination.has_prev)
    # print('当前页页码:', pagination.page)  # 当前页页码 request.args.get("page",1)
    # print('被分页的数据量总数:', pagination.total)
    # print('总页码:', pagination.pages)
    # print('上一页的分页器对象:', pagination.prev())  # 如果没有上一页,则默认为None
    # print('下一页的分页器对象:', pagination.next())  # 如果没有下一页,则默认为None
    # if pagination.next():
    #     print('下一页的数据列表:', pagination.next().items())
    return render_template('list.html',pagination=pagination)

    return 'ok'


if __name__=='__main__':
    # with app.app_context():
    #     db.create_all()
    #     db.drop_all()

    app.run()

9.分组查询和执行SQL语句

一般分组查询会结合聚合函数来一起使用。SQLAlchemy中所有的聚合函数都在func模块中声明的。

from sqlalchemy import func
函数名说明
func.count统计总数
func.avg平均值
func.min最小值
func.max最大值
func.sum

func函数只能针对db.session.query来使用,而不能通过模型.query来使用

#查询男生女生最大年龄:
ret=db.session.query(Student.sex,func.max(Student.age)).group_by(Student.sex).all()
 # 查询当前不同年龄的学生数量
ret = db.session.query(Student.age,func.count(Student.id)).group_by(Student.age).having(Student.age>19).all()
#having能进行过滤的字段只能是进行分组的字段,否则报错;
#查询男生和女生中,年龄最小的是几岁?
ret=db.session.query(Student.sex,func.min(Student.age)).group_by(Student.sex).all()

执行原生SQL语句

#返回结果不是模型对象, 是列表和元祖
#查询多条
ret=db.session.execute('select id,name,IF(sex,'',')from tb_studnet').fetchall()
ret = db.session.execute('select id,name,age,IF(sex,"男","女") from tb_student where sex=1').fetchall()

#查询单条
ret=db.session.execute('select id,name,IF(sex,'','')from tb_student where id=2').fetchone()

#添加/修改/删除
db.session.execute("UPDATE tb_student SET money=(money + %s) WHERE age=%s" % (200,23))
db.session.commit()

#查询出女生和男生中大于18岁的人数
ret=db.session.execute('SELECT id,name,age,IF(sex,"男","女") FROM `tb_student` WHERE age>18 ').fetchall()
ret2=db.session.execute('SELECT IF(sex,"男","女"),count(id) from (SELECT id,name,age,sex FROM `tb_student` WHERE age>18) as stu group by sex').fetchall()
  

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app=Flask(__name__)

class Config():
    DEBUG=True
    JSON_AS_ASCII=False
    SQLALCHEMY_DATABASE_URI='mysql://all:123456@127.0.0.1:3306/students'
    SQLALCHEMY_TRACK_MODIFICATIONS=True
    SQLALCHEMY_ECHO=True

app.config.from_object(Config)
db=SQLAlchemy()
db.init_app(app)

"""创建模型类"""
class Student(db.Model):
    __tablename__ = "tb_student"
    id = db.Column(db.Integer, primary_key=True,comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    age = db.Column(db.Integer, comment="年龄")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    money = db.Column(db.DECIMAL(8,2), nullable=True, comment="钱包")

    def __repr__(self):
        return self.name

class Teacher(db.Model):
    __tablename__ = "tb_teacher"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    option = db.Column(db.Enum("讲师","助教","班主任"), default="讲师", comment="教职")

    def __repr__(self):
        return self.name

class Course(db.Model):
    __tablename__ = "tb_course"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), unique=True, comment="课程名称")
    price = db.Column(db.Numeric(6, 2))

    def __repr__(self):
        return self.name

@app.route('/')
def index():
    from sqlalchemy import func
    '''group_by'''
    #查询男生和女生的最大年龄
    # ret=db.session.query(Student.sex,func.max(Student.age)).group_by(Student.sex).all()
    # print(ret)

    #having是针对分组的结果进行过滤处理,所以having能调用的字段,必须是分组查询结果中的字段,否则报错!!
    # ret=db.session.query(Student.sex,Student.age,func.count(Student.age)).group_by(Student.sex,Student.age).having(Student.age>12).all()
    # print(ret)

    """执行原生SQL语句,返回结果不是模型对象, 是列表和元祖"""
    #查询多条
    # ret = db.session.execute('select id,name,age,IF(sex,"男","女") from tb_student').fetchall()
    # ret = db.session.execute('select id,name,age,IF(sex,"男","女") from tb_student where sex=1').fetchall()
    # print(ret)

    #查询单条
    # ret=db.session.execute('select * from tb_student where id=2').fetchone()
    # print(ret)
    # print(ret.name)

    #添加/修改/删除
    # db.session.execute('UPDATE tb_student SET money=(money + %s) WHERE age = %s' % (200,12))
    # db.session.commit()

    #查询出女生和男生中大于18岁的人数
    ret=db.session.execute('SELECT id,name,age,IF(sex,"男","女") FROM `tb_student` WHERE age>18 ').fetchall()
    ret2=db.session.execute('SELECT IF(sex,"男","女"),count(id) from (SELECT id,name,age,sex FROM `tb_student` WHERE age>18) as stu group by sex').fetchall()
    print(ret2)
    return 'ok'

if __name__=='__main__':
    app.run()
4.一对一关系表
4.1创建
学生表和学生信息表:
from flask import Flask
from flask_sqlalchemy import SQLAMchemy
app=Flask(__name__)

class Config():
  DEBUG=True
  JSON_AS_ASCII=False
  SQLALCHEMY_DATABASE_URI='mysql://all:123456@127.0.0.1:3306/studnets'
  SQLALCHEMY_TRACK_MODIFICATIONS=True
  SQLALCHEMY_ECHO=True
  
app.config.from_object(Config)
db=SQLALchemy()
db.init_app(app)

'''创建模型'''
class Student(db.Model):
  __tablename__='tb_student'
  id=db.Column(db.Integer,primary_key=True,comment='主键ID')
  name=db.Column(db.String(255),comment='姓名')
  age=db.Column(db.Integer,comment='年龄')
  sex=db.Column(db.Boolean,default=False,comment='性别')
  money=db.Column(db.DECIMAL(8,2),bullable=True,comment='工资')
  # 关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中!!!
  # backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
  info=db.relationship('StudnetInfo',backref='own',uselist=False)
  
  def __repr__:
    return self.name

  
class StudentInfo(db.Model):
  __tablename__='tb_student_info'
  id=db.Column(db.Integer,primary_key=True,comment='主键ID')
  #外键
  sid=db.Column(db.Integer,db.ForeignKey(Student.id),comment='学生')
  address=db.Column(db.String(250),nullable=True,comment='家庭地址')
  mobile=db.Column(db.String(15),unique=True,comment='手机号')
  
  def __repr__(self):
    return self.own.name
  
  
@app.route('/')
def index():
  return 'ok'

if __name__=='__main__':
  app.run()  
4.2添加数据(视图函数中)
studnet=Student(
	name='haci',
  age='32',
  sex=True,
  money=67896,
  info=StudentInfo(
  	mobile='18434391584',
    address='山西'
  )
)
db.session.add(student)
db.session.commit()
4.3查询数据
#正向查询
student=Student.query.get(1)
print(student)
print(student.name)
print(student.info.address)

#反向查询(地址--->姓名)
student_info=StudentInfo.query.filter(StudentInfo.address=='山西').first()
print(student_info.own.name)
4.4修改更新
student=Student.query.get(1)
student.age=89
student.info.mobile='12345678908'
db.session.commit()
4.5删除
student=Student.query.get(2)
db.session.delete(student.info)
db.session.delete(student)
db.session.commit()
5.一对多关系表
5.1建表

class Teacher(db.Model):
    __tablename__ = "tb_teacher"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    option = db.Column(db.Enum("讲师","助教","班主任"), default="讲师", comment="教职")
    course_list=db.relationship('Course',uselist=True,backref='teacher',lazy='subquery')

    def __repr__(self):
        return self.name

class Course(db.Model):
    __tablename__ = "tb_course"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), unique=True, comment="课程名称")
    price = db.Column(db.Numeric(6, 2))
    teacher_id=db.Column(db.Integer,db.ForeignKey(Teacher.id),comment='老师')

    def __repr__(self):
        return self.name      
5.2添加数据
#既添加主模型数据,也添加外键模型
 teacher=Teacher(
        name='ww',
        option='讲师',
        course_list=[
            Course(name='python',price=8900),
            Course(name='Linux',price=5000),
            Course(name='Java',price=6789)
        ]
    )
    db.session.add(teacher)
    db.session.commit()

    #反向添加数据
    course=Course(
        name='web',
        price=908,
        teacher=Teacher(
            name='haci',
            option='班主任'
        )

           )
	db.session.add(course)
	db.session.commit()
5.3查询数据
teacher=Teacher.query.filter(Teacher.name=='ww').first()
    print(teacher.name)
    print(teacher.course_list)    
    for i in teacher.course_list:
        print(i.name,i.price)
        
#反向查询
course=Course.query.filter(Course.name=='python').first()
    print(course.price,'================')
    print(course.teacher.name)
5.4更新数据
teacher=Teacher.query.get(1)
teacher.course_list[0].name='C++'
db.session.commit()

#反向查询
course=Course.query.get(3)
course.teacher.name='er'
db.session.commit()
5.4删除数据
teacher=Teacher.query.get(1)
for course in teacher.course_list:
  db.session.delete(course)
  db.session.delete(teacher)
  db.session.commit()
6.多对多关系表
6.1利用tb_table第三张表
1.建表
# db.Table(
# 表名,
# db.Column("字段名",字段类型,外键声明),
# db.Column("字段名",字段类型,外键声明),
# )
achievement=db.Table(
    'tb_achievement',
    db.Column('student_id',db.Integer,db.ForeignKey('tb_student.id')),
    db.Column('course_id',db.Integer,db.ForeignKey('tb_course.id')),
)

class Student(db.Model):
    __tablename__ = "tb_student"
    id = db.Column(db.Integer, primary_key=True,comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    age = db.Column(db.Integer, comment="年龄")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    money = db.Column(db.DECIMAL(8,2), nullable=True, comment="钱包")
    # 关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中!!!
    # backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
    info=db.relationship('StudentInfo',backref='own',uselist=False)

    def __repr__(self):
        return self.name

class Course(db.Model):
    __tablename__ = "tb_course"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), unique=True, comment="课程名称")
    price = db.Column(db.Numeric(6, 2))
    teacher_id=db.Column(db.Integer,db.ForeignKey(Teacher.id),comment='老师')
    student_list=db.relationship('Student',secondary=achievement,backref='course_list',lazy='dynamic')
    def __repr__(self):
        return self.name      
2.添加数据
course1=Course(name='python',price=990,teacher=Teacher(name='ww',option='讲师'))
    course2=Course(name='linux',price=890,teacher=Teacher(name='ll',option='讲师'))
    course3=Course(name='web',price=87,teacher=Teacher(name='ll',option='讲师'))
    student=Student(
        name='xmh',
        age=9,
        money=234456,
        sex=False,
        info=StudentInfo(
                address='中国',
                mobile='18434391584'
            ),
        course_list=[
            course1,
            course2,
            course3,
        ]

    )
    db.session.add(student)
    db.session.commit()
    
#正向添加  
student1=Student(name='hh',age=16,sex=0,info=StudentInfo(address='伊犁',mobile='12345678965'))
    student2=Student(name='xx',age=19,sex=0,info=StudentInfo(address='河北',mobile='15525251185'))
    course=Course(
        name='C++',
        price=998,
        teacher=Teacher(
            name='bb',
            option='班主任',
        ),
        student_list=[
            student1,
            student2
        ]
                  )
    db.session.add(course)
    db.session.commit()    
3.查询
student=Student.query.get(1)
    course_list=student.course_list
    print(course_list,'===========')
    for i in course_list:
        print(i.name,i.price)

#查询学习某课的学生的列表
    course=Course.query.filter(Course.name=='C++').first()
    student_list=course.student_list.all()
    print(student_list)       
4.更新
student=Student.query.filter(Student.name=='hh').first()
student.course_list[0].name='java'
db.session.commit()
6.2新建一个模型
1.建表
class Achievement(db.Model):
    __tablename__='tb_acievement'
    id=db.Column(db.Integer,primary_key=True,comment='主键ID')
    student_id=db.Column(db.Integer,db.ForeignKey('tb_student.id'),comment='学生')
    course_id=db.Column(db.Integer,db.ForeignKey('tb_course.id'),comment='课程')
    score=db.Column(db.DECIMAL(5,2),nullable=True,comment='成绩分数')
    created_time=db.Column(db.DateTime,default=datetime.now(),comment='考试时间')
    def __repr__(self):
        return '%s的成绩为%s' % (self.student.name,self.score)
      
class Student(db.Model):
    __tablename__ = "tb_student"
    id = db.Column(db.Integer, primary_key=True,comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    age = db.Column(db.Integer, comment="年龄")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    money = db.Column(db.DECIMAL(8,2), nullable=True, comment="钱包")
    # 关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中!!!
    # backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
    info=db.relationship('StudentInfo',backref='own',uselist=False)
    achievement_list=db.relationship('Achievement',uselist=True,backref='student',lazy='select')


    def __repr__(self):
        return self.name
      
class Course(db.Model):
    __tablename__ = "tb_course"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), unique=True, comment="课程名称")
    price = db.Column(db.Numeric(6, 2))
    teacher_id=db.Column(db.Integer,db.ForeignKey(Teacher.id),comment='老师')
    achievement_list=db.relationship('Achievement',uselist=True,backref='course',lazy='select')
    def __repr__(self):
        return self.name      
2.添加数据
student1=Student(name='hh',age=90,sex=1,money=90000)
student2=Student(name='xx',age=25,sex=0,money=89076)
course=Course(
    name='python',
    price=90,
    achievement_list=[
        Achievement(
            student=student1,
            score=89,
        ),
        Achievement(
            student=student2,
            score=100,
        )
    ],
    teacher=Teacher(
        name='ww',
        option='讲师'
    )
)
db.session.add(course)
db.session.commit()
3.更新
student=Student.query.filter(Student.name=='hh').first()
student.achievement_list[0].score=85.7
db.session.commit()
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值