Day 24 实作 user_bp (2)

前言

今天要继续 user_bp,今天会把验证的部分处理掉。

/register

理论上我们现在应该要写一个资料库的函式来处理新增使用者,但我们之前在写 manage.py 的时候已经处理过了,所以就把旧的拿来用就好。

我们直接进入 HTML 的部分。

register.html

{% extends "base.html" %}

{% block title %}Register{% endblock %}

{% block content %}
<form action="/register" method="post">
    {{ form.csrf_token }}
    {{ form.username }}
    {{ form.password }}
    {{ form.repeat_password }}
    {{ form.email }}
    {{ form.submit }}
</form>
{% endblock %}

同样地,他也是直接继承 base.html。接着我们又使用了等等会从 render_template 送进来的 form,他当然就是 RegisterForm,所以我们乖乖把他的每一个栏位都填上去,当然也不要忘了 csrf_token

於是我们很快来到了路径本身,基本上该引入的新东西昨天都引入了,额外需要的只有 RegisterForm 和在 app/database/ 里面的 add_user

views.py

@user_bp.route("/register", methods=["GET", "POST"])
def register_page():
    if current_user.is_active:
        flash("You have logined.", category="info")
        return redirect(url_for("user.dashboard_page"))
    else:
        form = RegisterForm()
        if request.method == "GET":
            return render_template("register.html", form=form)
        if request.method == "POST":
            if form.validate_on_submit():
                username = form.username.data
                password = form.password.data
                email = form.email.data
                if add_user(username, password, email):
                    flash("Register successfully.", category="success")
                    return redirect(url_for("user.login_page"))
                else:
                    flash("The username or the email has been used.", category="alert")
                    return redirect(url_for("user.register_page"))
            else:
                for _, errors in form.errors.items():
                    for error in errors:
                        flash(error, category="alert")
                return redirect(url_for("user.register_page"))

跟昨天类似,我们先判断使用者有没有登入,如果没有的话就处理这个页面该处理的东西。接下来当表单验证通过後,就继续处理,把表单资料的使用者名称、密码、电子邮件抓出来,然後送给资料库处理,处理过後如果传回 False,那就代表这个使用者名称或是电子邮件用过了,就 flash 出错误讯息;如果通过的话,那就重新导向到登入页面给使用者登入,不过当然也可以在注册结束後就直接用 login_user 把它登入。

/setting

接下来要进入使用者设定的页面,但进入路径之前,我们必须先写一点资料库的东西。

def user_to_dict(user_objects: list):
    li = []
    for user in user_objects:
        d = dict()
        d["id"] = user.id
        d["username"] = user.username
        d["email"] = user.email
        d["is_admin"] = user.is_admin
        d["introduction"] = user.introduction
        d["register_time"] = user.register_time.strftime("%Y-%m-%d %H:%M:%S")
        li.append(d)
    return li

def render_user_data(user_id):
    if user := Users.query.filter_by(id=user_id).first():
        return user_to_dict([user])[0]
    else:
        return False

def update_user_data(user_id, password=None, email=None, is_admin=None):
    filter = Users.query.filter_by(id=user_id)
    if filter.first():
        data = {}
        if password:
            data["password"] = generate_password_hash(password)
        if email:
            data["email"] = email
        if is_admin != None:
            data["is_admin"] = is_admin
        try:
            filter.update(data)
            db.session.commit()
            return True
        except:
            return "Username or email is used."
    else:
        return "The user does not exist."

里面的 user_to_dict 是一个辅助的函式,基本上就是把使用者资料从 Users 物件换成 dict。render_user_data 则是透过 user_id 抓出使用者,接着再用刚刚的函式把他转成 dict 然後传回去。update_user_data 是用来更新使用者资讯的,逻辑上来说,我们会看它传入了甚麽东西,然後一个一个把它更新,如果遇到 password 的话就要先 generate_password_hash,最後如果 update 失败的话就代表使用者名称或是电子邮件已经被使用 (unique constraint) (也有可能不是,但此处方便起见就先当是,可以自己写一个 check 比较好),那就回传一条错误讯息。

这边比较要注意的是他更新的方法,我们可以注意到我们更新的东西是 query (但被 filter_by 过),而不是一个物件或是一个 list 的物件,如果再把它变成物件之後 (.all()),那就会错误。他更新需要使用 update 这个函式,他的参数是一个 dict,所以也可以直接把 kwargs 丢进去 (如果有使用的话)。

接下来要看到 HTML 的部分。

user_setting.html

{% extends "base.html" %}

{% block title %}Setting{% endblock %}

{% block content %}
<form action="/setting" method="post">
    {{ form.csrf_token }}
    {{ form.password }}
    {{ form.email }}
    {{ form.submit }}
</form>
{% endblock %}

一样很简单,这个 form 当然就是 UserSettingForm,可以去复习一下他的栏位。

最後就进入路径了,废话不多说直接来看。

views.py

@user_bp.route("/setting", methods=["GET", "POST"])
@login_required
def user_setting_page():
    data = render_user_data(current_user.id)
    form = UserSettingForm(email=data["email"])
    if request.method == "GET":
        return render_template("user_setting.html", form=form)
    if request.method == "POST":
        if form.validate_on_submit():
            password = form.password.data
            email = form.email.data
            if (
                msg := update_user_data(current_user.id, password=password, email=email)
            ) == True:
                flash("OK.", category="success")
            else:
                flash(msg, category="alert")
        else:
            for _, errors in form.errors.items():
                for error in errors:
                    flash(error, category="alert")
        return redirect(url_for("user.setting_page"))

在最一开始我们先抓出使用者的资料,接着我们建立 form,但这次我们加入了 email=data["email"],用这个方法就可以直接改掉 email 栏位的值,这样就可以让 email 直接显示在前端,使用者如果不改的话就直接案送出即可。

後面基本上就跟之前类似,比较不同的是这次我们会直接把 update_user_data 的回传值当成错误讯息然後 flash 出去。

base.html

在今天的最後,我们要回到 base.html,然後修改他的 nav。最一开始在写的时候我们只是放了注解在那边,但是没有判断到底甚麽时候要用甚麽 nav,今天我们就要改掉这个部分。


<nav>
        {% if current_user.is_anonymous %}
        <span><a href="/login">Login</a></span>
        <span><a href="/register">Register</a></span>
        {% else %}
        <span><a href="/dashboard">Dashboard</a></span>
        <span><a href="/posts">All posts</a></span>
        <span><a href="/post/add">Add post</a></span>
        <span><a href="/setting">Setting</a></span>
        {% if current_user.is_admin %}
        <span><a href="/admin_dashboard/posts">Admin Dashboard for posts</a></span>
        <span><a href="/admin_dashboard/comments">Admin Dashboard for comments</a></span>
        <span><a href="/manage_user">Manage User</a></span>
        {% endif %}
        {% endif %}
</nav>

我们直接使用了 current_user 这个变数,他不需要传入,他本来就在,也跟我们在 flask 里面用到的相同。一开始我们先判断这个使用者是不是匿名,如果是的话那就只显示 /login/register,如果不是的话,就显示 dashboard 等等,然後就再判断他是不是管理员,如果是的话就再多显示一些管理员的选项。

做完这些之後,就可以再打开网页,然後看看他是不是顺利判断使用者的角色。


<<:  Day 09: 【番外篇】关於写 Code 这件事 (待改进中... )

>>:  Day 09: Creational patterns - Prototype

Day 14 - 安装与执行 YOLO

Day 14 - 安装与执行 YOLO 在 介绍影像辨识的处理流程 - Day 10 有提到 YOL...

Youtube Data API 教学 - 看透你的频道你的心 channels.list

「鲑鱼均,因为一场鲑鱼之乱被主管称为鲑鱼世代,广义来说以年龄和脸蛋分类的话这应该算是一种 KNN 的...

惨 ...

EP 14: The MenuItem of ListView binds Command in itself Model

Hello, 各位 iT邦帮忙 的粉丝们大家好~~~ 本篇是 Re: 从零开始用 Xamarin 技...

DAY11 - [JS] 经典的ToDoList

今日文章目录 ToDoList 需求 事前准备 参考资料 ToDoList 需求 Q: 需要有哪些...