Python 后端 学习笔记 ·

「Flask实战」鱼书项目实战五

flask鱼书项目实战


上一篇写到构建完了三个模型,但是和这三个模型直接关联的就是用户系统,所以开始完善用户逻辑。

在创建之前,需要解决一个历史遗留问题,在运行flask程序的时候会报错,因为在创建Base的时候我们是不希望它成为一个数据表的,但是我没没有指定他是一个基类,所以他会说我们没有指定这个表的主键,解决方案

class Base(db.Model):
    # 创建一个基类
    __abstract__ = True
    create_time = Column('create_time', Integer)
    status = Column(SmallInteger, default=1)

为了先显示出注册页面,先传入一个空值

# web/auth.py

@web.route('/register/', methods=['GET', 'POST'])
def register():
    return render_template('auth/register.html', form={'data':{}})

这样访问127.0.0.1:5000/register就可以看到正常的注册页面了。

编写注册验证表单

from wtforms import Form, StringField, IntegerField, PasswordField
from wtforms.validators import Length, NumberRange, DataRequired, Email


class RegisterForm(Form):
    email = StringField(validators=[DataRequired(), Length(8, 128), Email(message='电子邮箱不符合规范')])
    password = PasswordField(validators=[DataRequired(message='密码不可以为空,请输入密码'), Length(6, 32)])
    nickname = StringField(validators=[DataRequired(), Length(2, 10, message='昵称长度在2到10之间')])

数据的赋值

我们可以这样将表单的数据赋值给一个变量

@web.route('/register/', methods=['GET', 'POST'])
def register():
    form = RegisterForm(request.form)
    if request.method == 'POST' and form.validate():
        user = User()
        user.nickname = form.nickname.data
        user.email = form.email

但是这样写显然不是很优雅,并且数据足够多的时候代码冗长。所以采用以下方法
表单验证的结果数据,赋值到User模型里,可以在Base类里编写一个set_attrs函数,统一将属性拷贝赋值。动态赋值。

#models/base.py
def set_attrs(self, attrs_dict):
    for key, value in attrs_dict.items():
        #id不需要被赋值
        if hasattr(self, key) and key != 'id':
            setattr(self, key, value)

属性描述符实现getter与setter

password加密并加入模型中。

#models/sql_user.py
from sqlalchemy import Column, Integer, Float, String, Boolean
from werkzeug.security import generate_password_hash

from app.models.base import Base, db


class User(Base):
    id = Column(Integer, primary_key=True)
    _password = Column('password',)
    nickname = Column(String(24), nullable=False)
    phone_number = Column(String(18), unique=True)
    email = Column(String(50), unique=True, nullable=False)
    confirmed = Column(Boolean, default=False)
    beans = Column(Float, default=0)
    send_counter = Column(Integer, default=0)
    receive_counter = Column(Integer, default=0)
    wx_open_id = Column(String(50))
    wx_name = Column(String(32))

    #属性的getter
    @property
    def password(self):
        return self._password

    #属性的setter
    @password.setter
    def password(self, raw):
        self._password = generate_password_hash(raw)

这样子做了之后,当我们写入password的时候,先调用setter来写入加密的password然后读取密码的时候读取的就是加密后的密码而不是直接以前端传入的明文形式显示

保存信息到数据库

数据库有关操作可以看我以前的文章

@web.route('/register/', methods=['GET', 'POST'])
def register():
    form = RegisterForm(request.form)
    if request.method == 'POST' and form.validate():
        user = User()
        user.set_attrs(form.data)
        #添加信息
        db.session.add(user)
        #提交
        db.session.commit()
    return render_template('auth/register.html', form=form)

这次修改除了将数据库提交代码加上去,还修改了模板中form关键字参数,这样就可以实现提示错误信息(在模板文件中输出error),并且如果输入错误,页面中的表单不会清空内容,用户体验较友好。

自定义验证器

因为邮箱不能相同,所以需要判断,wtforms自带的验证器显然不能满足需求,需要自定义验证器

#forms/auth.py
from wtforms import Form, StringField, IntegerField, PasswordField
from wtforms.validators import Length, NumberRange, DataRequired, Email, ValidationError

from app.models.sql_user import User


class RegisterForm(Form):
    email = StringField(validators=[DataRequired(), Length(8, 64), Email(message='电子邮箱不符合规范')])
    password = PasswordField(validators=[DataRequired(message='密码不可以为空,请输入密码'), Length(6, 32)])
    nickname = StringField(validators=[DataRequired(), Length(2, 10, message='昵称长度在2到10之间')])

    #field.data相当于form.email,存放表单的数据
    #validata_value wtform会自动根据value来进行表单验证,所以不需要在email中导入自定义验证器
    def validate_email(self, field):
        #通过一次查询来判断是否有同名邮箱,有则抛出异常
        #使用first的原因是,不管返回几条只要存在一条相同的,就抛出异常
        if User.query.filter_by(email=field.data).first():
            raise ValidationError('电子邮件已被注册')

同样的nickname也不能相同,所以通用添加自定义验证器

def validate_nickname(self, field):
    if User.query.filter_by(nickname=field.data).first():
        raise ValidationError('用户名已被注册')

注册成功后重定向到登陆界面

@web.route('/register/', methods=['GET', 'POST'])
def register():
    form = RegisterForm(request.form)
    if request.method == 'POST' and form.validate():
        user = User()
        user.set_attrs(form.data)
        db.session.add(user)
        db.session.commit()
        #别忘了导入redirect
        redirect(url_for('web.login'))
    return render_template('auth/register.html', form=form)

编写login

@web.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm(request.form)
    if request.method == 'POST' and form.validate():
        #具体业务逻辑先不编写
        pass
    return render_template('auth/login.html', form=form)

同样的这里需要login的验证器LoginForm
forms下的auth.py下创建验证器

#forms/auth.py
class LoginForm(Form):
    email = StringField(validators=[DataRequired(), Length(8, 64), Email(message='电子邮箱不符合规范')])
    password = PasswordField(validators=[DataRequired(message='密码不可以为空,请输入密码'), Length(6, 32)])

参与评论