flask基础

Flask基础

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/](http://docs.jinkan.org/docs/flask/)

**Flask常用第三方扩展包:**

- Flask-SQLalchemy:操作数据库,ORM;
- Flask-script:终端脚本工具,脚手架;
- Flask-migrate:管理迁移数据库;
- Flask-Session:Session存储方式指定;
- Flask-WTF:表单;
- Flask-Mail:邮件;
- Flask-Bable:提供国际化和本地化支持,翻译;
- Flask-Login:认证用户状态;
- Flask-OpenID:认证, OAuth;
- Flask-RESTful:开发REST API的工具;
- Flask JSON-RPC:  开发rpc远程服务[过程]调用
- Flask-Bootstrap:集成前端Twitter Bootstrap框架
- Flask-Moment:本地化日期和时间
- Flask-Admin:简单而可扩展的管理接口的框架

可以通过  https://pypi.org/search/?c=Framework+%3A%3A+Flask 查看更多flask官方推荐的扩展

虚拟环境

# 创建虚拟环境flask
mkvirtualenv flask -p python3
# 安装flask
pip install flask==0.12.5
# 创建项目
1,进入虚拟环境
workon flask
2,进入目录创建文件夹(项目)
mkdir
3,指定虚拟环境解释器

第一个程序main.py

from flask import Flask            # 导入Flask类
app = Flask(__name__)

class Config():                    # 项目配置
   DEBUG = True                   # 开启调试模式
app.config.from_object(Config)     # 加载项目配置


@app.route(rule = '/')             # 声名路由
def index(mobile):                 # 视图函数
   return 'Hello World %s' % mobile

if __name__ == '__main__':         # 启动程序
   app.run(host="0.0.0.0", port=5000) # 指定IP,端口

路由

# flask的路由是通过给视图添加装饰器的方式进行编写的。当然也可以分离到另一个文件中。
# flask的视图函数,flask中默认允许通过return返回html格式数据给客户端。

# 基本使用
@app.route(rule = '/')             # 声名路由
def index(mobile):                 # 视图函数
   return 'Hello World %s' % mobile

不限定路由参数数据类型

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

@app.route('/user/<user_id>')
def user_info(user_id):
   return 'hello %s' % user_id

限定路由参数数据类型

# 通过路由转换器限定路由参数的类型 werkzeug.routing.py
# 系统提供的一些数据类型
DEFAULT_CONVERTERS = {
   "default": UnicodeConverter,
   "string": UnicodeConverter,
   "any": AnyConverter,
   "path": PathConverter,
   "int": IntegerConverter,
   "float": FloatConverter,
   "uuid": UUIDConverter,
}

系统提供的路由参数转换器

@app.route('/user/<参数类型:user_id>')

@app.route('/user/<int:user_id>')
def user_info(user_id):
   return 'hello %s' % user_id

自定义路由参数转换器

from werkzeug.routing import BaseConverter    # 导入转换器基类

# 方式一
class MobileCinverter(BaseConverter):
   def __init__(self,map):
       self.regex = '1[3-9]\d{9}'                    # 匹配规则
       super().__init__(map)                         # 总路由类
app.url_map.converters['mobile'] = MobileCinverter    # mobile就是自定义参数类型

@app.route('/user/<mobile:mobile>')
def user_info(mobile):
   return 'hello %s' % mobile

# 方式二
class MobileCinverter(BaseConverter):
   def __init__(self,map,*args):
       super().__init__(map)                         # 总路由类
       self.regex = args[0]                          # 拿到路由中的匹配规则
app.url_map.converters['mobile'] = MobileCinverter    # mobile就是自定义参数类型

@app.route("/user/<mobile('1[3-9]\d{9}'):mobile>")    # 匹配规则写到路由中
def user_info(mobile):
   return 'hello %s' % mobile

限定路由请求方法

@app.route(rule="/user", methods=["post","put","get","delete","patch"])

请求

from flask import Flask,request

app = Flask(__name__)

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

@app.route(rule='/')
def index():
   data = request.args       # 获取查询字符串
   data = request.data       # 获取请求体数据
   data = request.form       # 获取表单提交的数据
       # 如果表单中有多选框通过request.form.getlist()获取
   data = request.json       # 获取提交过来的json数据
   data = request.files      # 获取提交过来的文件数据
   data = request.headers    # 获取请求头数据
   data = request.url        # 获取请求路由 http://127.0.0.1:5000/data
   data = request.path       # 获取请求路径  /data
   
   # 数据类型的转换
   """
  ImmutableMultiDict的实例对象 提供了四个常用的方法给我们获取数据,
  1. get 获取指定参数的第一个值
  2. getlist 获取指定参数的多个值
  3. to_dict 把所有参数转换成字典[request对象的部分属性不支持]
  4. to_list 把所有参数转换成列表[request对象的部分属性不支持]
  """
   print(data)
   return 'ok'


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

钩子

from flask import Flask,make_response,request

app = Flask(__name__)

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


@app.before_first_request        # 发起请求第一个执行,只执行一次
def before_first_request():
   print("0,----before_first_request----")

@app.before_request              # 访问视图就会执行,再试图执行前执行
def before_request():
   print("1,----before_request----")

@app.after_request               # 访问视图就会执行,再试图执行后执行
def after_request(response):
   print("2,----after_request----")
   """一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作"""
   response.headers["Content-Type"] = "application/json"
   response.headers["Company"] = "python oldboy..."
   return response              # 必须返回response参数

@app.teardown_request
def teardown_request(exc):
   print("9----teardown_request----")
   print("如果有异常错误,则立马执行.没有报错,最后执行")
   # 在项目关闭了DEBUG模式以后,则异常信息就会被传递到exc中,我们可以记录异常信息到日志文件中
   print(exc)       # 报错信息

   
# 视图
@app.route(rule='/')
def index():
   print("-----------视图函数执行了---------------")
   return "<h1>hello world!</h1>"

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

响应

# Response(response="内容", status="http响应状态码",headers=自定义响应头,mimetype="数据格式")

# 状态码的响应
@app.route("/rep")
def index7():
   response.status_code = 201 # 自定义响应状态码
   return response    

1,响应html文本

from flask import make_response

@app.route("/")
def index():
   return "<img src='http://flask.pocoo.org/static/logo.png'>"    # [默认支持]响应html文本
   return make_response("<h1>hello user</h1>")                    # 等同于上面的一段

2, 返回JSON数据

from flask import Flask, request, jsonify
# jsonify 就是json里面的jsonify

@app.route("/")
def index():
   # 也可以响应json格式代码
   data = [
      {"id":1,"username":"liulaoshi","age":18},
      {"id":2,"username":"liulaoshi","age":17},
      {"id":3,"username":"liulaoshi","age":16},
      {"id":4,"username":"liulaoshi","age":15},
  ]
   return jsonify(data)

3, 返回图片

from flask import Flask,request,make_response,Response,jsonify

app = Flask(__name__)

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

   # 返回图片信息
   with open('./1.zip',"rb") as f:
       content=f.read()
   # 判断权限,身份...
   return Response(response=content,mimetype="application/zip")

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

4, 重定向

4.1 外网跳转
from flask import redirect
@app.route("/user")
def user():
   # 页面跳转 redirect函数就是response对象的页面跳转的封装
   return redirect("http://www.baidu.com")
4.2 内网跳转
from flask import url_for

@app.route("/")
def index():
   data = [
      {"id":1,"username":"liulaoshi","age":18},},   # 内容响应
  ]
   return jsonify(data)

# 使用url_for可以实现视图方法之间的内部跳转
@app.route("/login")
def login():
   return redirect( url_for("index") )
4.2.1 带参数跳转
# 路由传递参数
@app.route('/user/<user_id>')
def user_info(user_id):
   return 'hello %d' % user_id

# 重定向
@app.route('/demo4')
def demo4():
   return redirect( url_for(endpoint="user",user_id=100) ) # endpoint:路径 后面跟参数

http会话控制

cookie

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

from flask import Flask,make_response,request

app = Flask(__name__)

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

@app.route(rule='/')
def set_cookie():
   response = make_response('ok')
   response.set_cookie('key','value',20)
   return response

@app.route(rule='/get_cookie')
def get_cookie():
   return request.cookies.get('key')

@app.route("/del_cookie")
def del_cookie():
   response = make_response("ok")
   response.set_cookie("key","",0)
   return response


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

session

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

flask中的session需要加密,所以使用session之前必须配置SECRET_KEY选项,否则报错.
session的有效期默认是会话期,会话结束了,session就废弃了。
"""
from flask import Flask, make_response, request,session

app = Flask(__name__)

class Config():
   SECRET_KEY = "kjdas/.的>发4.4'!"           # 盐必须加
   DEBUG = True
app.config.from_object( Config )

@app.route(rule='/set_session')
def set_session():
   """支持py数据类型"""
   session["key"] = "value"
   session["info"] = {
       "age":11,
       "sex":True,
  }
   return "ok"

@app.route(rule='/get_session')
def get_cookie():
   return request.session.get('key')

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


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

session储存空间

flask-session
# 允许设置session到指定存储的空间中

# 安装
pip install flask-Session
redis
# 配置
from flask import Flask,session
from flask_redis import FlaskRedis
from flask_session import Session

app = Flask(__name__)
redis = FlaskRedis()
session_store = Session()

class Config(object):
   DEBUG = True                           # 开启调试模式
   SECRET_KEY = "*(%#4sxcz(^(#$#8423"     # 密钥
   SESSION_TYPE="redis"                   # session存储方式为redis
   SESSION_REDIS = redis                  # session保存数据到redis时启用的链接对象
   SESSION_PERMANENT = False              # 为True,则关闭浏览器session就失效
   SESSION_USE_SIGNER = False             # 是否对发送到浏览器上session的cookie值进行加密
   SESSION_KEY_PREFIX = "session:"        # 保存到redis的session数的名称前缀
   REDIS_URL = "redis://localhost:6379/1" # redis的链接配置
app.config.from_object(Config)
redis.init_app(app)
session_store.init_app(app)

# 使用
@app.route(rule='/set_session')
def set_session():
   session['username'] = 'bajie'
   return 'ok'

@app.route(rule='/get_session')
def get_session():
   print(session['username'])
   return 'ok'


# redis其他使用
# 视图
@app.route("/set_redis")
def set_redis():
   redis.set("username","xiaohuihui")
   redis.hset("brother","zhangfei","17")
   return "ok"

@app.route("/get_redis")
def get_redis():
   user = redis.get("username").decode()
   print(user)

   brother = redis.hgetall("brother")
   print(brother)
   print(brother["zhangfei".encode()].decode())
   return "ok"
SQLAlchemy
from flask import Flask,session
from flask_session import Session
from flask_sqlalchemy import SQLAlchemy

session_store = Session()
db = SQLAlchemy()
app = Flask(__name__)

class Config(object):
   DEBUG = True
   # 动态追踪修改设置,如未设置只会提示警告
   SQLALCHEMY_TRACK_MODIFICATIONS = True
   # 查询时会显示原始SQL语句
   SQLALCHEMY_ECHO = True
   # 密钥
   SECRET_KEY = "*(%#4sxcz(^(#$#8423"
   # 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
   SQLALCHEMY_DATABASE_URI = "mysql://root:1@127.0.0.1:3306/flask_test?charset=utf8mb4"
   # 数据库保存session
   SESSION_TYPE = 'sqlalchemy'              # session类型为sqlalchemy
   SESSION_SQLALCHEMY = db                  # SQLAlchemy对象
   SESSION_SQLALCHEMY_TABLE = 'tb_session'  # session要保存的表名称
   SESSION_PERMANENT = True                 # 如果设置为True,则关闭浏览器session就失效。
   SESSION_USE_SIGNER = False               # 是否对发送到浏览器上session的cookie值进行加密
   SESSION_KEY_PREFIX = 'session:'          # 保存到session中的值的前缀

db.init_app(app)
app.config.from_object( Config )
session_store.init_app(app)

# 视图
@app.route("/set_session")
def index():
   session["username"] = "bajie"
   return "Ok"

@app.route("/get_session")
def get_session():
   return session["username"]

if __name__ == '__main__':
   with app.app_context():    # 用于创建session表!!!!
       db.create_all()
   app.run()

 

 

异常捕获

主动抛出HTTP异常

abort(500)

捕获异常

@app.errorhandler(500)                 # 按照状态码捕获
def internal_server_error(e):
   return '服务器搬家了'

@app.errorhandler(ZeroDivisionError)    # 按照异常类型捕获
def zero_division_error(e):
   return '除数不能为0'

context 执行上下文

    在程序中可以理解为在代码执行到某一行时,根据之前代码所做的操作以及下文即将要执行的逻辑,可以决定在当前时刻下可以使用到的变量,或者可以完成的事情。
   Flask中有两种上下文,请求上下文(request context)和应用上下文(application context)
   
请求上下文:保存了客户端和服务器交互的数据,一般来自于客户端。
应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如路由列表,程序名、数据库连接、应用信息等

请求上下文(request context)

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

应用上下文(application context)

# current_app
- current_app.name              # 获取当前app的名称
   - current_app.config            # 获取当前项目的所有配置信息
   - current_app.url_map           # 获取当前项目的所有路由信息
   
# g变量(只能在视图当中进行)
- g.name = "root"             # 设置全局变量
   - obj = g.name               # 获取全局变量

 

Flask-Script 扩展

# 作用:可以让我们通过终端来控制flask项目的运行,类似于django的manage.py

# 安装:pip3 install flask-script

基本使用

from flask import Flask,g

app = Flask(__name__)

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


from flask_script import Manager
manage = Manager(app)

# 视图
@app.route(rule='/')
def index():
   return "<h1>hello world!</h1>"

if __name__ == '__main__':
   manage.run()
   
# 终端启动
python3 main.py runserver
# 可以指定IP,port
python3 main.py runserver -h127.0.0.1 -p8888

Flask-Script 为当前应用程序添加脚本命令

from flask import Flask

app = Flask(__name__)

class Config():
   DEBUG = True

app.config.from_object( Config )


from flask_script import Manager,Command,Option
manage = Manager(app)  # 注册运行app的命令

import os
class BluePrintCommand(Command):   # Command 命令基类
   option_list = [
       Option('--name','-n',help='蓝图')
  ]

   def run(self,name=None):
       if name is None:
           print('蓝图不能为空')
       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 = [

              ]""")

manage.add_command('blue',BluePrintCommand)    # 注册子应用命令blue

# 视图
@app.route(rule='/')
def index():
   return "<h1>hello world!</h1>"

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

# 终端启动
python3 main.py blue -n=文件夹名称

Jinja2模板引擎

渲染模版函数

- Flask提供的 render_template 函数封装了该模板引擎
- render_template 函数的第一个参数是模板的文件名,后面的参数都是键值对,表示模板中变量对应的真实值。

1 万能点

# (索引,key,属性,方法(不加括号,只能进行无参数方法),类,对象)

2 if 语句

{% if 条件 %}
{% else if 条件 %}
{% else %}
{% endif %}

3 for 语句

{for i in lst}       # 如果lst为空,或者后台没有给lst数据,那么就展示empty下面的内容
<h1>i</h1>
{% empty %}  
<span>哥,啥也木有啊</span>
{% endfor %}

### loop 循环信息对象

loop.index             # 当前循环次数,从1开始
loop.index0            # 当前循环次数,从0开始
loop.first             # 当前循环是不是第一次循环(布尔值)
loop.last              # 当前循环是不是最后一次循环(布尔值)

4 过滤器

# html,语法:{{ value|filter_name:参数}}
{{ value|upper}}                              # value英文变大写
{{ value|safe }}                              # 若果是标签格式,具有标签效果
{{ value|reverse}}                            # value英文变大写
{{ value|truncate(4) }}                       # 从第四个字符截断...

{{ value|default:"默认值"}}                    # 当value没有传值时,拿默认值填充
{{ value|length}}                             # value的长度
{{ value|filesizeformat}}                     # 数字可转换为MB单位
{{ value|slice:"0:4"}}                        # value切片
{{ value|date:"Y-m-d H:i:s" }}                # value时间格式化

{{ value|cut:' ' }}                           # 移除(字符)空格  
{{ value|join:'-' }}                          # 字符串拼接

4.1 自定义过滤器

# 自定义过滤器
@app.template_filter("mobile")
def do_mobile(data,string):
   return data[:3]+string+data[7:]

# 注册过滤器
app.add_template_filter(do_mobile, "mobile")

5 模板继承

# 模板(母版.html页面)

划分子板可能要添加内容的区域
{% block content%}
要添加内容的区域
{% endblock %}

# 继承模板
{% extends '母版.html页面'%}

{% block content%}
子板要修改的内容
{% endblock %}

# 总结:
{% block css%}  等同样适用

csrf 攻击

from flask import Flask,render_template
app = Flask(__name__,template_folder='templates')# 应用使用视图,需要告诉Flask去哪找templates模板

class Config():
   DEBUG = False
   SECRET_KEY = 'ASLKFM.,//;,.FS;DKMF'          # 用于加密生成的 csrf_token 的值
app.config.from_object( Config )

from flask.ext.wtf import CSRFProtect
CSRFProtect(app)                                 # csrf 防护体系作用于app

# 视图
@app.route(rule='/',methods=['POST'])
def index():
   return render_template("index.html")         # 返回模板页面

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


# 模板
<form method="post" action="/">
   <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
   <input type="submit" value="登录">
</form>

数据库操作

    flask默认提供模型操作,但是并没有提供ORM,所以一般开发的时候我们会采用flask-SQLAlchemy模块来实现ORM操作。
SQLAlchemy是一个关系型数据库框架,它提供了高层的 ORM 和底层的原生数据库的操作。flask-sqlalchemy 是一个简化了 SQLAlchemy 操作的flask扩展。

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

# 安装
安装 flask-sqlalchemy【清华源】
pip install flask-sqlalchemy -i https://pypi.tuna.tsinghua.edu.cn/simple
如果连接的是 mysql 数据库,需要安装 mysqldb 驱动
sudo dpkg --configure -a
   apt-get install libmysqlclient-dev python3-dev
   pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple
       
       
# 配置
class Config(object):
   # 调试模式
   DEBUG = True
   # 显示多字节
   
   # 动态追踪修改设置,如未设置只会提示警告
   SQLALCHEMY_TRACK_MODIFICATIONS = True
   # 查询时会显示原始SQL语句
   SQLALCHEMY_ECHO = True
   # 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
   SQLALCHEMY_DATABASE_URI = "mysql://root:1@127.0.0.1:3306/flask_test?charset=utf8mb4"
app.config.from_object( Config )

# 创建模型类
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
   
# 注册表
with app.app_context():
   db.create_all()

单表基本操作

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

 

# 添加一条数据
student1 = Student(name="小明", sex=True, age=17, email="123456@qq.com", money=100)
db.session.add(student1)
db.session.commit()
# 添加一条数据
st1 = Student(name='wang',email='wang@163.com',age=22)
st2 = Student(name='zhang',email='zhang@189.com',age=22)
st3 = Student(name='chen',email='chen@126.com',age=22)
db.session.add_all([st1,st2,st3])
db.session.commit()

# 方法1
student = Student.query.first()
db.session.delete(student)
db.session.commit()

# 方法2【事务中使用,就是乐观锁】
ret = Student.query.filter(Student.name=='sun').delete()
db.session.commit()

# 方法1
student = Student.query.first()
student.name = 'dong'
db.session.commit()

# 方法2【事务中使用,就是乐观锁】
ret = Student.query.filter(Student.name == 'liu').update({'money': 1000})
db.session.commit()

# 方法3【批量操作, 实现类似django里面F函数的效果】
ret = Student.query.filter(Student.age == 22).update({Student.money: Student.money+'200'})
db.session.commit()



# filter
# 单条件查找
student = Student.query.first(Student.age == 18).first()
student = Student.query.first(Student.age >= 18).first()
# 多条件查找
from sqlalchemy import and_, or_ , not_, in_
student = Student.query.first(and_(Student.age > 18,Student.name=='liu')).all()
   student = Student.query.first(or_(Student.age > 18,Student.name=='liu')).all()
   student = Student.query.first(not_(Student.name=='liu')).all()
   student = Student.query.first(Student.age.in_([22,18])).all()
# 模糊查询
student = Student.query.first(Student.name.like('%xiao%')).all()  # 包含 contains("xiao")
student = Student.query.first(Student.name.like('xiao%')).all()   # 以..开头 startwiths("xiao")
student = Student.query.first(Student.name.like('%xiao')).all()   # 以..结尾 startwiths("xiao")
student = Student.query.first(Student.name.like('_')).all()       # 值长度为1个字符的
# filter_by
student = Student.query.first(Student.age=18).first()
   
# order_by
student = Student.query.order_by(Student.age.asc()).all()      # 正序
student = Student.query.order_by(Student.age.desc()).all()     # 倒序
obj = Student.query.filter(or_(Student.name == '周鹏飞' ,Student.age>= 20)).
       order_by(db.desc(Student.age)).all()                       # 查找结果后再排序
   
# 聚合查询
   ret = Student.query.filter(Student.age > 18).count()           # 统计数量
   ret = Student.query.order_by(Student.age.asc()).limit(1).all()
   # 对学生的钱包进行从大到小排名,第3-第5名的学生
   student_list = Student.query.order_by(Student.money.desc()).offset(2).limit(3).all()
   
   # 分页器 paginate
   paginate(page=当前页码,per_page=每一页数据量,max_per_page每一页最大数据量)
   paginations = Student.query.first(Student.sex==True).paginate(100)   # 默认100条数据
   
   paginations.items        # 获取当前页数据
   paginations.has_next     # 是否有下一页
   paginations.has_prev     # 是否有上一页
   paginations.next_num     # 下一页页页码
   paginations.page         # 当前页页码
   paginations.pages        # 总页码
   paginations.total        # 总页码

多表基本操作

一对一

from flask import Flask
app = Flask(__name__,template_folder='templates')

class Config(object):
   DEBUG = True
   # 动态追踪修改设置,如未设置只会提示警告
   SQLALCHEMY_TRACK_MODIFICATIONS = True
   # 查询时会显示原始SQL语句
   # SQLALCHEMY_ECHO = True
   # 密钥
   SECRET_KEY = "*(%#4sxcz(^(#$#8423"
   # 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
   SQLALCHEMY_DATABASE_URI = "mysql://root:1@127.0.0.1:3306/flask_test?charset=utf8mb4"
app.config.from_object( Config )

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

class Student(db.Model):
   __tablename__ = 'tb_student'
   id = db.Column(db.Integer, primary_key=True, comment='主键字段')
   name = db.Column(db.String(5), comment='姓名')
   age = db.Column(db.Integer, comment='年龄')
   # 关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中!!!
   # backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性 uselist,一对一加上
   info = db.relationship('StudentInfo', backref='student',uselist=False)
   def __repr__(self):
       return self.name

class StudentInfo(db.Model):
   __tablename__ = 'tb_student_info'
   id = db.Column(db.Integer, primary_key=True, comment='主键字段')
   addr = db.Column(db.String(255), nullable=True, comment="家庭住址")
   mobile = db.Column(db.String(15), unique=True, comment="紧急联系电话")
   sid = db.Column(db.Integer,db.ForeignKey(Student.id),comment="关联学生ID字段")
# 视图
from sqlalchemy import or_
@app.route(rule='/')
def index():
   return 'ok'

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

   app.run()
# 正向
@app.route(rule='/')
def index():
   student = Student(
       name='baji6',
       age = 20,
       info= StudentInfo(
           mobile="13312388617",
           addr = "北京市昌平区白沙路103号"
      )
  )
   db.session.add(student)
   db.session.commit()
   return 'ok'

# 反向
student_info = StudentInfo(
       mobile="13232382678",
       addr="北京市昌平区白沙路103号",
       student=Student(
           name="yaga",
           age=19
      ),
  )
   db.session.add(student_info)
   db.session.commit()
obj = Student.query.get(2)
db.session.delete(obj)
db.session.commit()
# 正向修改
obj = Student.query.get(1)
obj.name = "baji"
db.session.commit()

# 反向修改
obj = StudentInfo.query.get(1)
obj.student.name = "bajie"
db.session.commit()
# 正向查询
obj = Student.query.get(1)

print(obj.name)       # 查询本字段
print(obj.info.addr)  # 查询关联字段

# 反向查询
obj = StudentInfo.query.get(1)

print(obj.student.name)       # 查询关联字段
print(obj.addr)               # 查询本字段

一对多

class Student(db.Model):
   __tablename__ = 'tb_student'
   id = db.Column(db.Integer, primary_key=True, comment='主键字段')
   name = db.Column(db.String(5), comment='姓名')
   age = db.Column(db.Integer, comment='年龄')
   sex = db.Column(db.Boolean, default=False, comment="性别")
   addr = db.Column(db.String(255), nullable=True, comment="家庭住址")
   mobile = db.Column(db.String(15), unique=True, comment="紧急联系电话")
   course_list = db.relationship("Course", uselist=True, backref="course", 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))
   sid= db.Column(db.Integer,db.ForeignKey(Student.id), comment="学生")
   
   def __repr__(self):
       return self.name
obj = Student(
   name="baji1",
   age=22,
   sex=True,
   addr="中国",
   mobile="15034485577",
   course_list = [
       Course(name="吃",price="10"),
       Course(name="喝",price="20"),
       Course(name="票",price="30"),
       Course(name="赌",price="100"),
  ]
)
db.session.add(obj)
db.session.commit()
obj = Student.query.filter(Student.name == 'baji1').first()
db.session.delete(obj)
dn.session.commit()
obj = Student.query.filter(Student.name == 'baji1').first()
obj.name = "bajie"
obj.course_list.name = "piao"
db.session.commit()
# 查找baji1学了哪些课程
obj = Student.query.filter(Student.name == "baji1").first()
for i in obj.course_list:
   print(i.price , i.name)
   
# 查看谁报了吃的课程
obj = Course.query.filter(Course.name=='吃').all()
for i in obj:
   print(i.course.name)

多对多

class Student(db.Model):
   __tablename__ = 'tb_student'
   id = db.Column(db.Integer, primary_key=True, comment='主键字段')
   name = db.Column(db.String(5), comment='姓名')
   age = db.Column(db.Integer, comment='年龄')
   achievement_list = db.relationship("StudentCourse",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))
   achievement_list = db.relationship("StudentCourse", uselist=True, backref="course", lazy="select")

   def __repr__(self):
       return self.name

class StudentCourse(db.Model):
   __tablename__ = 'tb_student_course'
   id = db.Column(db.Integer, primary_key=True, comment='主键字段')
   student_id = db.Column(db.Integer, db.ForeignKey(Student.id))
   course_id = db.Column(db.Integer, db.ForeignKey(Course.id))

   def __repr__(self):
       return 1
    student = Student(
       name='baji1',
       age= 18,
       achievement_list=[
           StudentCourse(course = Course.query.filter(Course.name == '吃').first()),
           StudentCourse(course = Course.query.filter(Course.name == '赌').first()),
      ]
  )
   db.session.add(student)
   db.session.commit()

数据库迁移

- 在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。
- 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。
- 在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
- 为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。

# 安装
pip3 install flask-migrate
   
   
# 模块推荐
文档: https://faker.readthedocs.io/en/master/locales/zh_CN.html
批量生成测试数据: https://github.com/joke2k/faker

配置

# 初始化SQLAlchemy
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy() # 初始化数据库操作对象
db.init_app(app)  # 初始化数据库链接
# 实例化一个给app增加命令的对象
manage = Manager(app)    # 在终端可以使用指令来操作程序
# 第一个参数是Flask的实例,第二个参数是Sqlalchemy数据库实例
migrete = Migrate(app,db)
# 自定义命令 db,使用扩展的迁移类
manage.add_command('db', MigrateCommand)

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

使用

python3 main.py db init

init                             # 创建一个新的迁移存储库(文件)
migrate -m 'initial migration'   # 数据库迁移版本初始化
upgrade                          # 更新数据库版本
downgrade                        # 降低数据库版本

history                          # 查看数据库版本号
downgrade 版本号            # 回滚指定版本

蓝图Blueprint

模块化开发:MVT,MMVT
   
   简单来说,Blueprint 是一个存储视图方法的容器,这些操作在这个Blueprint 被注册到一个应用之后就可以被调用,Flask 可以通过Blueprint来组织URL以及处理请求。

Flask使用Blueprint让应用实现模块化,在Flask中,Blueprint具有如下属性:
- 一个项目可以具有多个Blueprint
- 可以将一个Blueprint注册到任何一个未使用的URL下比如 /”、“/sample”或者子域名
- 在一个应用中,一个模块可以注册多次
- Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的
- 在一个应用初始化时,就应该要注册需要使用的Blueprint

蓝图的基本使用

# 创建应用包文件

### __init__.py

from flask import Blueprint
# 1,创建蓝图目录,初始化
users_blue = Blueprint("uesrs",__name__)
# 3,注册视图,相当于将views.py 全部引入过来
from .views import *

### views.py

from . import users_blue     # 2,创建视图
@users_blue.route('/')  
def home():
   return 'users.home'

### main.py

from flask import Flask
app = Flask(__name__)
# 4,注册蓝图
from users import users_blue
app.register_blueprint(users_blue,url_prefix = '/users')   # 路由前缀

if __name__ == '__main__':   # 运行程序
   app.run()

自动加载配置

from importlib import import_module

def load_config(config_path):
   # import_module 根据字符串进行导包,能够识别模块中的变量成员
   # config_path == application.settings.dev
   module = import_module(config_path)   # dev 包/模块
   # print(module.Config)               # dev模块下的Config类
   config_file_name = config_path.split(".")[-1]   # dev
   
   if config_file_name == "settings":
       return module.InitConfig           # 加载settings配置文件
   # settings.什么加载什么,前提是dev和pord的配置类都叫Config
   return module.Config

# 在调用处记得加载
   app.config.from_object(Config)      # 使用配置文件

加载终端命令

from importlib import import_module
from flask_script import Command
import inspect

def load_command(manager,command_path):
   """自动加载终端命令"""
   module = import_module(command_path)
   # 当前模块所有类(包括引入的)
   # ('BluePrintCommand', <class 'application.utils.commands.BluePrintCommand'> )
   class_list = inspect.getmembers(module,inspect.isclass)
   for class_name,class_obj in class_list:
       if issubclass(class_obj,Command) and class_name != "Command":
           manager.add_command(class_obj.name,class_obj)      # 生成所有终端命令

注册蓝图及总路由分发

def path(rule,view_func):
   """注册子路由"""
   return {"rule":rule,"view_func":view_func}

def include(blue_url_prefix,blue_path):
   """总路由分发"""
   return {"blue_url_prefix":blue_url_prefix,"blue_path":blue_path}

def load_blueprint(app):
   """自动加载蓝图对象"""
   app_url_list = import_module(app.config.get('URL_PATH')).urlpatterns  # 获取总路由列表
   for blueprint_path in app.config.get("INSTALLED_APPS"):
       blueprint_name = blueprint_path.split('.')[-1]              # 蓝图名字
       for app_url in app_url_list:                                # 匹配总路由,后面加前缀用
           if app_url.get('blue_path') == blueprint_name + '.urls':# 自由定义前缀
               blue_url_prefix = app_url.get('blue_url_prefix')
               break
       # bule_obj = Blueprint(home, __name__)
       bule_obj = Blueprint(blueprint_name,blueprint_path)         # 蓝图对象
       blue_urls_module = import_module(blueprint_path + ".urls")
       for item in blue_urls_module.urlpatterns:                   # 加载的当前蓝图中所有的路由信息
           bule_obj.add_url_rule(**item)                           # 注册路由
       app.register_blueprint(bule_obj,url_prefix=blue_url_prefix) # 注册蓝图

加载蓝图的模型/数据库迁移

# 1,models.py中创建模型类
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
   
# 2,在加载蓝图对象中加载模型
import_module(blueprint_path + ".models")                   # 加载当前蓝图下的模型
   
# 3,在应用中挂载迁移命令
migrate = Migrate()                            # 数据迁移初始化
migrate.init_app(app,db)                       # 注册数据库迁移文件
manager.add_command('db',MigrateCommand)

 

 

 

 

 

 

posted @ 2020-11-26 10:04  bajie_new  阅读(149)  评论(0编辑  收藏  举报