今天要进入 user_bp
,但因为他路径太多太复杂,所以我们必须分段处理,而今天要处理的是验证的部分。
在开始写路径之前,我们要先稍微处理一下 login_manager
,接下来这个档案要放在 app/
里面。
user_helper.py
from flask_login import LoginManager, UserMixin
from .database.models import Users
class User(UserMixin):
pass
login_manager = LoginManager()
@login_manager.user_loader
def load(user_id):
user_id = int(user_id)
user = Users.query.filter_by(id=user_id).first()
if user:
sessionUser = User()
sessionUser.id = user.id
sessionUser.is_admin = user.is_admin
return sessionUser
else:
return None
在这里面我们看到一个新的 login_manager
,我们要用这个来做事,所以在 __init__.py
的那个就可以删掉了,直接 from .user_helper import login_manager
。
在这边我们做了很多事情,有点复杂,所以我不会完整解释。我们先从最前面定义 User
开始。他继承自从 Flask-Login 来的 UserMixin
,功用是当一个 Flask-Login 需要用的 user,他会在下面的 load
被用到。
接着就看到下面的 load
,他加上了一个 login_manager.user_loader
的装饰器。在这个函式里面,我们去资料库找到这个使用者,然後把它包装一下变成刚刚定义的 User
的形式,这样 Flask-Login 才看得懂,而如果这个使用者不存在的话,那就回传 None
,这是官方文件指示的。这个函式基本上是在我们登入後,可能会想要找这个 user
是谁,那就需要这个 callback function 了,他是一个很核心的函式,没有他基本上就动不了。
这个档案不会这麽快就结束,之後我们还会修改他,可以敬请期待。
在开始写路径之前,我们要先加入一些函式。
def login_auth(username, password):
if user := Users.query.filter_by(username=username).first():
if user.check_password(password):
sessionUser = User()
sessionUser.id = user.id
return sessionUser
return False
他要放在 app/database/helper.py
里面,是用来验证登入有没有成功的工具。
在这边就要说一下 Flask-SQLAlchemy 要怎麽抓资料。我们先看 Users.query
,前面的部分是这个 table 的 model,後面则是一个 query
物件,他也可以写成 db.session.query(Users)
,但我喜欢前者的写法。接下来他有一个 filter_by
,他就是一个过滤器,username=username
代表他要过滤出 username
栏位的值等於我想要的 username
的整个物件。例如说我现在要使用者名称是 siriuskoan
的人,那就写成 filter_by(username="siriuskoan")
即可。这样还没结束,我们可以继续加更多 filter_by
,就这样一直连着下去。直到最後,我们要把那些物件抓出来,这时候就可以使用 all()
或是 first()
,此处使用者名称不会重复,所以直接用後者没有问题。
check_password
则是我们之前在写资料库的时候就写好的东西,如果通过的话,就会建立一个刚刚写好的 User
(继承 UserMixin
),然後设定好 id
并且送回来,等等在写路径的时候就会用到他的这个回传。
接下来加入 HTML,一样直接继承自 base.html
。
login.html
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<form action="/login" method="post">
{{ form.csrf_token }}
{{ form.username }}
{{ form.password }}
{{ form.submit }}
</form>
{% endblock %}
它里面有一个 form
,等等我们会用 render_template
传入,他就是我们的 LoginForm
。後面三个很明显就是我们在 forms.py
写好的栏位,比较特别的是第一个。在好几天前说 Flask-WTF 的时候有提到他可以防止 CSRF,就是利用这个 csrf_token
,如果没有他的话,Flask-WTF 会喷错误,除非在设定加上 WTF_CSRF_ENABLED = False
,像是测试的设定那个样子。
最後就可以进入路径本身了,我们会用到很多函式,之前提过的 flask 函式就不会再说了,请自行引入。
views.py
@user_bp.route("/login", methods=["GET", "POST"])
def login_page():
if current_user.is_active:
flash("You have logined.", category="info")
return redirect(url_for("user.dashboard_page"))
else:
form = LoginForm()
if request.method == "GET":
return render_template("login.html", form=form)
if request.method == "POST":
if form.validate_on_submit():
username = form.username.data
password = form.password.data
if user := login_auth(username, password):
login_user(user)
flash(f"Login as {username}!", category="success")
return redirect(url_for("user.dashboard_page"))
else:
flash("Wrong username or password.", category="alert")
return redirect(url_for("user.login_page"))
else:
for field, errors in form.errors.items():
for error in errors:
flash(error, category="alert")
return redirect(url_for("user.login_page"))
在开始解释之前,应该会注意到有很多之前没看过的东西,像是 current_user
、login_user
,他们都要从 flask_login 引入 (from flask_login import current_user, login_user
)。current_user
就是现在的使用者,如果没登入的话就会是匿名使用者是,他跟 current_app
有点像,就是可以在任意地方抓到现在的东西。而 login_user
就是让使用者登入用的,等等看到後面会比较清楚。
可以看到在路径的最一开始我们先做的一个判断,使用了 current_user.is_active
,他代表这个使用者是否 active,基本上就是是否有登入。其他还有一些东西也可以判断,像是 current_user.is_anonymous
就可以看他是不是匿名使用者。有兴趣的话可以打开 UserMixin
的原始码,会看到他有一些 property 可以用,而在他下面会有一个叫作 AnonymousUserMixin
的物件,如果在没有登入的状况下看 current_user
就会看到他,他的 is_anonymous
就是 True
。如果这个判断是成立的话,我们就 flash
一个小讯息,然後把它重新导向到 dashboard;如果不成立 (他还没登入),那我们会先宣告一个 form
,他是 LoginForm
的实体,然後在 request.method == "GET"
的时候把它传出去,也就是刚刚 HTML 看到的 form
。
基本上来说 GET 没什麽好说的,精彩的在 POST。一开始我们先判断 form.validate_on_submit()
,他就是我们之前的一堆验证,如果不过的话就会有错误讯息出来,所以我们先看看没过的情况,我们要从 form.errors.items()
把错误捞出来,他会包含 field
和 errors
两个部分,前者代表错误发生的栏位,後者代表错误讯息,而这个错误讯息可能很多条,所以他是一个 list,要 for 把他一条一条抓出来然後 flash
出去。
接着回到验证过的情况,我们就从表单里面取出资料,收进 username
和 password
里面,接着用刚刚写好的 login_auth
来看看有没有通过,如果是正确的帐号密码,那就会收到他生出来的使用者,接着用 login_user
把它登入,然後再 然後再 flash
一些东西,最後重新导向到 dashboard,如果验证失败的话,那就会绕回来让使用者重新验证。
刚刚看完复杂的登入页面,现在来看看简单的登出页面。
@user_bp.route("/logout", methods=["GET"])
def logout_page():
logout_user()
return redirect(url_for("main.index_page"))
就这样,非常简短。其中有个 logout
没有看过,他也是要从 Flask-Login 引入的函式,功能就是登出使用者。他不需要参数,反正他会把 session 清乾净,然後我们只需要重新导向到首页就好。
Flask-Login How it Works
Flask实作 ext 11 Flask-Login 登入状态管理
<<: Day 18:专案03 - PTT 八卦版爬虫03 | 文章标题、作者、时间
>>: Day08 Flutter 和 Native 通讯的原理 02
Vivy几个着名事件中,与IoT最为相关的事件即是 研究员达也因为无法忘怀自己小时候的护理师AI葛蕾...
接续之前做容器,希望可以用code连到另台电脑的container,做了ssh的一些测试 先做了一个...
「目前为止,所有建议无疑将帮助你设计出更好的软件,这些软件是由具有明确边界、职责、依赖关系受控的元...
UI Flow 的全名是 User Interface Flow,望文生意,也就是使用者点某个按钮会...
之前写了一个很简单的 Program Loader, 现在就来真正的实作它,让它能够把编译好的程序放...