一、Flask使用mysql链接池
Mysql连接池的使用,参考[Python自学] day-12 (Mysql、事务、索引、ORM)
1.Flask使用settings.py中的Config类作为配置
参考:[Python自学] Flask框架 (1) (Flask介绍、配置、Session、路由、请求和响应、Jinjia2模板语言、视图装饰器) 中的第三章:Flask的配置文件
目录结构:
settings.py中的代码实现:
from DBUtils.PooledDB import PooledDB, SharedDBConnection import pymysql class Config(object): # 在配置文件中添加一个Mysql链接池 POOL = PooledDB( creator=pymysql, # 使用链接数据库的模块 maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数 mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建 maxcached=5, # 链接池中最多闲置的链接,0和None不限制 maxshared=8, # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。 blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] 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='123456', database='pooldb', charset='utf8' )
让Flask使用该类作为配置:
app.config.from_object("settings.Config")
2.在视图函数中使用链接池
from flask import Blueprint, render_template, request, session, redirect from settings import Config import pymysql # 定义一个蓝图对象 lg = Blueprint('lg', __name__, url_prefix='/login') # 使用蓝图来调用装饰器(而不是使用app) @lg.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'GET': return render_template('login.html') username = request.form.get('user') password = request.form.get('pwd') # 一般存在数据库中的密码都是经过md5或其他算法加密的 # 从数据库链接池中获取一个链接 conn = Config.POOL.connection() cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 执行SQL获取用户信息 cursor.execute("select id,nickname from userinfo where user=%s and pwd = %s", (username, password)) # 获取结果中一条数据 data = cursor.fetchone() cursor.close() conn.close() # 归还链接到链接池 # 如果没有数据,则说明用户或密码错误 if not data: return render_template('login.html', error='用户名密码错误') # 如果获取到数据,则写session session['user_info'] = {id: data['id'], 'nickname': data['nickname']} # 跳转到/home页面 return redirect('/home')
注意,在一个视图函数中使用连接池链接数据库,并操作数据库。如果每个视图函数都这样使用,代码显得很冗余。所以我们应该将数据库操作部分的代码进行封装。然后将其放入utils/db_helper.py(我们自己实现的工具)中。例如:
import pymysql from settings import Config def fetch_one(sql, args): conn = Config.POOL.connection() cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 执行SQL获取用户信息 cursor.execute(sql, args) # 获取结果中一条数据 data = cursor.fetchone() cursor.close() conn.close() # 归还链接到链接池 return data
然后再在视图函数中使用:
@lg.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'GET': return render_template('login.html') username = request.form.get('user') password = request.form.get('pwd') # 一般存在数据库中的密码都是经过md5或其他算法加密的 # 链接数据库并获取用户信息 data = db_helper.fetch_one("select id,nickname from userinfo where user=%s and pwd = %s", (username, password)) # 如果没有数据,则说明用户或密码错误 if not data: return render_template('login.html', error='用户名密码错误') # 如果获取到数据,则写session session['user_info'] = {id: data['id'], 'nickname': data['nickname']} # 跳转到/home页面 return redirect('/home')
当然,除了封装成许多个函数,例如fetch_one、fetch_all、insert等。我们也可以将其封装成一个类。
3.强调注意
今后在使用数据库的时候,按一下步骤来做:
1)必须使用数据库连接池
2)进行必要的封装(函数或类)
二、wtforms
参考博客:https://www.cnblogs.com/wupeiqi/articles/8202357.html
1.安装wtforms
pip install wtforms
2.简单使用wtforms
视图函数代码:
from flask import Blueprint, render_template, request from wtforms import Form from wtforms.fields import simple from wtforms import widgets, validators # 定义一个蓝图对象 lg = Blueprint('lg', __name__, url_prefix='/login') class LoginForm(Form): name = simple.StringField( validators=[ validators.DataRequired(message='用户名不能为空'), # 验证用户名是否为空 validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d') # 用户名是否满足长度要求 ], widget=widgets.TextInput(), # 可以指定这个控件的样式,例如TextArea等 render_kw={'placeholder': '请输入用户名'} ) pwd = simple.PasswordField( validators=[ validators.DataRequired(message='密码不能为空'), # 验证密码是否为空 validators.Length(min=8, message='用户名长度必须大于%(min)'), # 密码是否满足长度要求 # validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[$@$!%*?&])[A-Za-zd$@$!%*?&]{8,}", # message="密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符") ], widget=widgets.PasswordInput(), # 可以指定这个控件的样式,例如TextArea等 render_kw={'placeholder': '请输入密码'} ) # 使用蓝图来调用装饰器(而不是使用app) @lg.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'GET': form = LoginForm()
# 这里返回的form中,form.name是一个html标签,例如<input type='text' /> return render_template('login.html', form=form) form = LoginForm(formdata=request.form) # 将post请求的数据拷贝给LoginForm对象form,form.data来获取 if form.validate(): # 如果输入的数据格式验证通过 # 如果格式验证成功 print(form.data) # 从post中获取的数据 #TODO 在这里做数据库比对 return '成功' else: # 如果格式验证失败,返回form,然后在form中获取error,form.errors return render_template('login.html', form=form)
对应的模板代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Login</title> </head> <body> <form method="post" action="/login/login" novalidate> <!-- 注意这里的form.name.errors[0],按理说应该是form.errors.name.0,但是当errors中name不存在时,则name.0会出错--> <!-- 我们将其反过来,先获取form.name,然后在获取其中的errors --> <p>{{ form.name }} {{ form.name.errors[0] }}</p> <p>{{ form.pwd }} {{ form.pwd.errors[0] }}</p> <p><input type="submit" value="提交"/></p> </form> </body> </html>
3.使用wtform实现注册表单
后台代码:
from flask import Flask, render_template, request, redirect from wtforms import Form from wtforms.fields import core from wtforms.fields import html5 from wtforms.fields import simple from wtforms import validators from wtforms import widgets app = Flask(__name__, template_folder='templates') app.debug = True class RegisterForm(Form): name = simple.StringField( label='用户名', validators=[ validators.DataRequired() ], widget=widgets.TextInput(), render_kw={'class': 'form-control'}, default='alex' ) pwd = simple.PasswordField( label='密码', validators=[ validators.DataRequired(message='密码不能为空.') ], widget=widgets.PasswordInput(), render_kw={'class': 'form-control'} ) pwd_confirm = simple.PasswordField( label='重复密码', validators=[ validators.DataRequired(message='重复密码不能为空.'), validators.EqualTo('pwd', message="两次密码输入不一致") ], widget=widgets.PasswordInput(), render_kw={'class': 'form-control'} ) email = html5.EmailField( label='邮箱', validators=[ validators.DataRequired(message='邮箱不能为空.'), validators.Email(message='邮箱格式错误') ], widget=widgets.TextInput(input_type='email'), render_kw={'class': 'form-control'} ) gender = core.RadioField( label='性别', choices=( (1, '男'), (2, '女'), ), coerce=int ) city = core.SelectField( label='城市', choices=( ('bj', '北京'), ('sh', '上海'), ) ) hobby = core.SelectMultipleField( label='爱好', choices=( (1, '篮球'), (2, '足球'), ), coerce=int ) favor = core.SelectMultipleField( label='喜好', choices=( (1, '篮球'), (2, '足球'), ), widget=widgets.ListWidget(prefix_label=False), option_widget=widgets.CheckboxInput(), coerce=int, default=[1, 2] ) def __init__(self, *args, **kwargs): super(RegisterForm, self).__init__(*args, **kwargs) self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球')) def validate_pwd_confirm(self, field): """ 自定义pwd_confirm字段规则,例:与pwd字段是否一致 :param field: :return: """ # 最开始初始化时,self.data中已经有所有的值 if field.data != self.data['pwd']: # raise validators.ValidationError("密码不一致") # 继续后续验证 raise validators.StopValidation("密码不一致") # 不再继续后续验证 @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'GET': form = RegisterForm(data={'gender': 1}) return render_template('register.html', form=form) else: form = RegisterForm(formdata=request.form) if form.validate(): print('用户提交数据通过格式验证,提交的值为:', form.data) else: print(form.errors) return render_template('register.html', form=form) if __name__ == '__main__': app.run()
html模板代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>用户注册</h1> <form method="post" novalidate style="padding:0 50px"> {% for item in form %} <p>{{item.label}}: {{item}} {{item.errors[0] }}</p> {% endfor %} <input type="submit" value="提交"> </form> </body> </html>
4.实时从数据库中取表单选项
从第3.节,我们看到有很多单选、多选、复选框等。里面的选项都是我们直接在代码中写死的。在实际项目中,这些选项应该是从数据库实时读取的。
例如:
class RegisterForm(Form): city = core.SelectField( label='城市', choices=( ('bj', '北京'), ('sh', '上海'), ) )
city是RegisterForm类的一个静态属性(特别注意,是静态属性)。我们将其中的choices参数替换为从数据库中读取值:
choices=helper.fatch_all('select id,name form city_tb',[],type=None)
但是,由于city字段是静态字段,只有在这个类创建的时候被赋值一次(即只会读取一次数据库),如果数据库的数据进行了修改,页面上的单选下拉框中显示的city是不会改变的(除非重启Web服务器)。
所以,我们应该让city的choices在每次RegisterForm实例化时都重新读取数据库中的数据:
class RegisterForm(Form): # 静态属性city,只在类创建的时候赋值一次(读取一次数据库) city = core.SelectField( label='城市', choices=( ('bj', '北京'), ('sh', '上海'), ) ) # 每次实例化RegisterForm时,都会重新给choices赋值(读一次数据库) def __init__(self,*args,**kwargs): super(RegisterForm,self).__init__(*args,**kwargs) self.city.choices=helper.fetch_all('select id,name from city_tb',[],type=None)
###