上篇中 我们通过Flask蓝图简单做了一个增删改查的学生管理系统,在数据和页面校验上都存在很大问题。接下来我们完善一下。
代码存储:GitHub
一、基于DBUtils实现数据库连接池
1:原理浅析
连接池:是创建和管理连接的缓存池。简单的说:随时准备着。有一些国外学者喜欢称之曰“备胎”。
优点:
一:减少连接创建时间
二:受控的资源使用
连接池能够使性能最大化,同时还能将资源利用控制在一定的水平之下,如果超过该水平,应用程序将崩溃而不仅仅是变慢。
原理与操作
(1)建立数据库连接池对象(服务器启动)。
(2)按照事先指定的参数创建初始数量的数据库连接(即:空闲连接数)。
(3)对于一个数据库访问请求,直接从连接池中得到一个连接。如果数据库连接池对象中没有空闲的连接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接。
(4)存取数据库。
(5)关闭数据库,释放所有数据库连接(此时的关闭数据库连接,并非真正关闭,而是将其放入空闲队列中。如实际空闲连接数大于初始空闲连接数则释放连接)。
(6)释放数据库连接池对象(服务器停止、维护期间,释放数据库连接池对象,并释放所有连接)
2:连接练习
1:首先安装DBUtils 和 PyMySQL
from flask import Flask import pymysql import time from DBUtils.PooledDB import PooledDB, SharedDBConnection # 创建共享连接池 POOL = PooledDB( creator=pymysql, # 使用链接数据库的模块 maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数 mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建 maxcached=5, # 链接池中最多闲置的链接,0和None不限制 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制,连接使用次数过多会有缓存,有时需要使用最新的 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] maxshared=3, # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。 blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错 ping=0, # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always host='127.0.0.1', port=3306, user='root', password='', database='yk2012', charset='utf8', ) # 记忆方法 # 模块 最大 初始化 # 闲置 最新 开始化 # 阻塞 检查 基本化 def get_conn(): """ 连接数据库 :return: conn, cursor """ conn = POOL.connection() # 数据库连接 cursor = conn.cursor(pymysql.cursors.DictCursor) # 数据库指针 return conn, cursor def reset_conn(conn, cursor): """ :param conn: 数据库连接 :param cursor: 数据库指针 :return: Null """ cursor.close() conn.close() def fetch_all(sql, args): """ :param sql: sql语句 :param args: sql语句的参数 :return: 查询结果 """ conn, cursor = get_conn() cursor.execute(sql, args) result = cursor.fetchall() reset_conn(conn, cursor) return result app=Flask(__name__) @app.route('/') def index(): res=fetch_all("select * from student",()) print(res) return "执行成功" if __name__ == '__main__': app.run(debug=True)
总结:配置项记忆方法
# 模块 最大 初始化
# 闲置 最新 开始化
# 阻塞 检查 基本化
二、数据准备
1:创建相应的数据库和数据表
首先,我们创建一个数据库(StudentManage_WTF)。
再创建一个用户表(User){ID:自增,UserName:用户名, Password:密码}
再创建一个学生表(Student){ID:自增, StudentName: 姓名,Age:年龄, Gender:性别}
#创建数据库 create database StudentManage_WTF; #创建用户表 create table User( ID int primary key auto_increment, UserName nvarchar(20) not null, Password nvarchar(200) not null ); #初始化管理员 insert into User (UserName,Password) Values('Aaron','1'); #创建学生信息表 create table Student( ID int primary key auto_increment, StudentName nvarchar(20) not null, Age int, Gender int );
三、代码结构和MYSQLHelper工具类
1:代码结构
2:帮助类MYSQLHelper
from flask import Flask import pymysql import time from DBUtils.PooledDB import PooledDB, SharedDBConnection class MYSQLHelper(object): def __init__(self, host, port, dbuser, password, database): self.pool = PooledDB( creator=pymysql, # 使用链接数据库的模块 maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数 mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建 maxcached=5, # 链接池中最多闲置的链接,0和None不限制 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制,连接使用次数过多会有缓存,有时需要使用最新的 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] maxshared=3, # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。 blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错 ping=0, # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always host=host, port=int(port), user= dbuser, password=password, database= database, charset='utf8', ) def get_conn(self): """ 连接数据库 :return: conn, cursor """ conn = self.pool.connection() # 数据库连接 cursor = conn.cursor(pymysql.cursors.DictCursor) # 数据库指针 return conn, cursor def reset_conn(self,conn, cursor): """ :param conn: 数据库连接 :param cursor: 数据库指针 :return: Null """ cursor.close() conn.close() def fetch_all(self,sql, args): """ :param sql: sql语句 :param args: sql语句的参数 :return: 查询结果 """ conn, cursor = self.get_conn() cursor.execute(sql, args) result = cursor.fetchall() self.reset_conn(conn, cursor) return result def insert_one(self,sql,args): conn,cursor=self.get_conn() res=cursor.execute(sql,args) conn.commit() self.reset_conn() return res def update(self,sql,args): conn,cursor=self.get_conn() result = cursor.execute(sql,args) conn.commit() self.reset_conn() return result
四、WTForm实现登录校验
1:登录页面
a:使用数据库中User表判断是否有权限。
b:使用WTForm框架做校验。
#1:安装WTForms!ps:别安装错了,不是WTForm from wtforms.fields import simple,core from wtforms import Form,validators,widgets class LoginForm(Form): username =simple.StringField( label="用户名", validators=[ validators.data_required(message="用户名不能为空") ], widget=widgets.TextInput(), render_kw={"class":"my_username"} ) password =simple.StringField( label="密码", validators=[ validators.data_required(message="密码不能为空!") ], widget=widgets.PasswordInput() ) submit=simple.SubmitField( label="提交" )
{% extends "index.html" %} {% block css %} <style> label[for='submit'] { display: none } </style> {% endblock %} {% block content %} <h2>学生登录</h2> <form action="/s_login" method="post" novalidate> {% for filed in loginForm %} <p>{{ filed.label }} {{ filed }}{{ filed.errors.0 }}</p> {% endfor %} </form> {% endblock %}
from flask import Blueprint from flask import render_template, request, session, redirect from StudentManage_WTF.Tools.Helper.DBHelper.MYSQLHelper import MYSQLHelper from StudentManage_WTF.UserLogin.ModelForm.LoginModel import LoginForm # 每个蓝图都可以为自己独立出一套template模板文件夹,如果不写则共享项目目录中的templates sl = Blueprint("sl", __name__, template_folder="templates") @sl.route("/s_login", methods=("GET", "POSt")) def userLogin(): if request.method == "GET": loginForm = LoginForm() return render_template("s_login.html", loginForm=loginForm) else: lf = LoginForm(request.form) if lf.validate(): username = request.form.get("username") password = request.form.get("password") # 获取sql帮助类对象 sqlhelper = MYSQLHelper("127.0.0.1", 3306, "root", "", "StudentManage_WTF") # 拼接sql语句 sql = "select id,username from user where username='%s' and password='%s'" % (username, password) res = sqlhelper.fetch_all(sql, ()) print(res) if res: session["user"] = res return redirect("/s_view") else: session["user"] = None lf.username.errors.append("用户名或密码错误!") return render_template("s_login.html", loginForm=lf) else: return render_template("s_login.html", loginForm=lf)
五、学生信息的维护
1:学生列表页面
from flask import Blueprint from flask import render_template from StudentManage_WTF.Tools.Helper.DBHelper.MYSQLHelper import MYSQLHelper # 每个蓝图都可以为自己独立出一套template模板文件夹,如果不写则共享项目目录中的templates sv = Blueprint("sv", __name__, template_folder="templates") @sv.route("/s_view") def studentView(): # 获取sql帮助类对象 sqlhelper = MYSQLHelper("127.0.0.1", 3306, "root", "", "StudentManage_WTF") # 拼接sql语句 sql = "select * from Student where 1=1" studentList = sqlhelper.fetch_all(sql, ()) return render_template("s_view.html", studentList=studentList)
{% extends "index.html" %} {% block content %} <h2>学生列表</h2> <table border="3xp"> <thead> <tr> <td>编号</td> <td>学生姓名</td> <td>年龄</td> <td>性别</td> <td>操作</td> </tr> </thead> <tbody> {% for student in studentList %} <tr> <td>{{ student.ID }}</td> <td>{{ student["StudentName"] }}</td> <td>{{ student.get("Age") }}</td> <td>{{ student.Gender }}</td> <td><a href="/s_update/{{ student.ID }}">修改</a> | <a href="/s_del?id={{ student.ID }}">删除</a></td> </tr> {% endfor %} </tbody> </table> <a href="/s_add">新增</a> {% endblock %}
2:学生管理页面
from flask import Blueprint from flask import render_template, request, redirect from StudentManage_WTF.student_oper.ModelForm.StudentModel import StudentForm from StudentManage_WTF.configer.MYSQLSetting import getMYSQLHelper # 每个蓝图都可以为自己独立出一套template模板文件夹,如果不写则共享项目目录中的templates sa = Blueprint("sa", __name__, template_folder="templates") sqlhelper = getMYSQLHelper() @sa.route("/s_add", methods=('GET', 'POST')) def studentAdd(): if request.method == "GET": stu = StudentForm() return render_template("s_add_modify.html", curTitle="新增", curAction="/s_add", stu=stu) else: stu = StudentForm(request.form) if stu.validate(): # 获取sql帮助类对象 # 拼接sql语句 sql = "insert into student (studentname,age,gender) values('%s',%s,%s);" % ( stu.StudentName.data, stu.Age.data, stu.Gender.data) res = sqlhelper.insert_one(sql, ()) return redirect("/s_view") else: return render_template("s_add_modify.html", curTitle="新增", curAction="/s_add", stu=stu) @sa.route("/s_update/<int:id>", methods=('GET', 'POST')) def studentUpdate(id): if request.method == "GET": sql = "select * from student where ID='%s'" % (id) curStu = sqlhelper.fetch_all(sql, ()) if curStu: curStu = StudentForm(**curStu[0]) # 神来之笔 return render_template("s_add_modify.html", curTitle="编辑", curAction="/s_update/" + str(id), stu=curStu) else: stu = StudentForm(request.form) if stu.validate(): sql = "update student set studentname='%s',age='%s',gender='%s' where ID='%s'" % (stu.StudentName.data, stu.Age.data, stu.Gender.data, id) res = sqlhelper.update(sql, ()) return redirect("/s_view") else: return render_template("s_add_modify.html", curTitle="编辑", curAction="/s_update/" + str(id), stu=stu) @sa.route("/s_del", methods=('GET', 'POST')) def studentDel(): id = request.args.get('id') sql = "delete from student where ID='%s'" % (id) curStu = sqlhelper.update(sql, ()) return redirect("/s_view")
from wtforms.fields import simple, core from wtforms import widgets, validators, Form class StudentForm(Form): ID = simple.StringField( widget=widgets.HiddenInput ), StudentName = simple.StringField( label="姓名", validators=[ validators.data_required(message="学生姓名不能为空!"), validators.Length(min=2, max=20, message="学生姓名长度必须大于2位,小于20位") ] ) Age = core.StringField( label="年龄", validators=[ validators.data_required(message="学生年龄不能为空!"), validators.Regexp(regex="^d+$", message="学生年龄必须为数字") ] ) Gender = core.RadioField( label="性别", coerce=int, # 保存到数据中的值为int类型 choices=( (0, '女'), (1, '男') ), default=1 ) submit = simple.SubmitField( label="提交" )
{% extends "index.html" %} {% block css %} <style> label[for='submit'] { display: none } </style> {% endblock %} {% block content %} <h2>学生{{ curTitle }}</h2> <form action="{{ curAction }}" method="post" novalidate> {% for filed in stu %} <p>{{ filed.label }} {{ filed }}{{ filed.errors.0 }}</p> {% endfor %} </form> {% endblock %}