上一篇寫到構建完了三個模型,但是和這三個模型直接關聯的就是使用者系統,所以開始完善使用者邏輯。
在創建之前,需要解決一個歷史遺留問題,在運行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)])