Flask 极致细节:3. 一个案例,简单的用户界面

Flask 极致细节:3. 一个案例,简单的用户界面

提示:在第一个案例中,我们将逐渐规范化我们的代码,以及flask项目的结构(什么文档应该放在哪里)。此案例,我们仅做一个简单的用户名注册,登录的界面。
这里,我们一如既往地分享所有代码,并附有非常详细的注解。

喜欢的朋友点个赞哦:)
代码链接:https://pan.baidu.com/s/1Q_OpztbqA7r0GTOyRtaS8w
密码:a2f9


文章目录

  • Flask 极致细节:3. 一个案例,简单的用户界面
    • 1. 项目结构
    • 2. 蓝图
    • 3. 主界面
    • 注册界面
    • 删除/修改用户信息


1. 项目结构

--项目名
  |---static (静态)
  |---templates (模板)
    |---user
      |---login.html
      |---register.html
      |---show.html
    |---base.html
  |---apps
    |---goods
      |---__init__.py
    |---order
      |---__init__.py
    |---users
      |---model.py
      |---view.py
      |---__init__.py
    |---__init__.py
  |---app.py (运行/启动)
  |---venv1 (虚拟环境)
  |---requirements.txt (所有安装包以及版本)
  |---config.py (参数配置文件)
  |---readme.md (说明文档)

在之前的案例中,我们会把所有的后台代码都写在app.py文件中。但在实际项目中,由于项目体量增大,app.py文件将变得非常杂乱,难以维护。因此,我们把app.py作为系统的总启动“钥匙”,把子模块拆分出来,分别放到各自的文件中。
比如,我们这个项目可以拆分成“产品”,“订单”,“用户”。那么,我们新建一个apps文件夹。
templates文件夹下,也是一样的道理。

关于代码的运行环境,我们可以创建虚拟环境:virtualenv [venv]。接下来进入虚拟环境:.\[venv]\Scripts\activate。如是自己创建的新虚拟环境,还需要安装依赖:pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple。相关的配置以及解释请参见前一篇博文:Flask 极致细节:0. VS Code 手动新建Flask项目。

2. 蓝图

如下为一个最小的Flask项目

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run(port=5678)

当然,项目大了,我们就不能这么写了。让我们打开代码,就能很明显地看到蓝图的意义。

app.py

from flask import Flask
from apps import create_app

app = create_app()

if __name__ == '__main__':
    app.run(port=8080,host="0.0.0.0")

现在的app文件很简洁,让我们打开create_app函数。注意,这个函数存在于users文件夹下的__init__初始化文件中。
原因在于,当我们建立一个包(package),虽然看起来和新建一个文件夹没有区别(实际上也没什么区别),但会多生成一个__init__.py文件,或者你自己可以新建一个。在打开这个包,或者引用这个包的时候,系统会自动先运行这个__init__文件。

from flask import Flask
import settings
from apps.users.view import user_bp

def create_app():
    app = Flask(__name__,template_folder="../templates",static_folder="../static")       # app是一个核心对象。因为很多东西都会和这个app建立联系,所以这里我们把它独立出来。
    app.config.from_object(settings)
    # 蓝图的导入。整个项目结构就像是树的分支一样。
    app.register_blueprint(user_bp)
    print(app.url_map)
    return app

在这里,我们实例化了一个Flask类,并且定义了template以及static文件夹的位置。注意,这里需要重新定义,因为默认的两个文件夹设立于实例化此Flask类的路径基础上的template以及static文件夹。而我们这个例子中,两个文件夹在上一个路径。

def __init__(
        self,
        import_name: str,
        static_url_path: t.Optional[str] = None,
        static_folder: t.Optional[t.Union[str, os.PathLike]] = "static",
        static_host: t.Optional[str] = None,
        host_matching: bool = False,
        subdomain_matching: bool = False,
        template_folder: t.Optional[str] = "templates",
        instance_path: t.Optional[str] = None,
        instance_relative_config: bool = False,
        root_path: t.Optional[str] = None,
    ):

然后,我们就在这里导入新建好的蓝图:

from apps.users.view import user_bp
...
app.register_blueprint(user_bp)

从文件结构中,我们已经看到,项目被分为三个子模块:“产品”,“订单”,“用户”。
这里距离“用户”。在apps/users/路径下,我们新建一个view.py文件,然后实例化一个蓝图类。
其实很简单,即,在子模块实例化好一个蓝图类,然后在“更高阶”的文件中用register_blueprint进行调用。

from flask import Blueprint,request,redirect
from flask.templating import render_template
from apps.users.model import UserCls

# 蓝图就像是app的某一个模块的版本,app就像是最终的那个开关,但我们一个项目中会有很多模块。
# 比如,这里列举了users, goods, order。对于每一个模块,我们可以建立一个蓝图。
# 蓝图的使用和app本身非常类似。
user_bp = Blueprint('user',__name__)

3. 主界面

让我们从直接运行代码,然后从结果解读过程。当我们在terminal中直接运行python app.py (注意,先进入虚拟环境),点击链接,你会看到如下界面:

Flask 极致细节:3. 一个案例,简单的用户界面_第1张图片

通过上面的描述,我们应该很容易找到对应的代码位置:

apps/users/view.py:

@user_bp.route('/',endpoint="usermainpage",methods = ['GET','POST'])
def user_center():
    return render_template('user/show.html',users=users)

templates/user/show.html:

{% extends 'base.html' %}
{% block middle %}

    <a href="/register">用户注册a><br>
    <a href="/login">用户登录a><br>
    <a href="/logout">用户注销a>

    <h1>用户信息h1>
    <span> 当前用户人数是:{{ users |length }} span>
    <table border="1" cellspacing="0" width="60%" >
        {%for user in users %}
            <tr>
                <td> {{ loop.index }} td>
                <td> {{ user.username }} td>
                <td> {{ user.password }} td>
                <td> {{ user.phone }} td>
                <td> <a href=""> 修改 a> 
                     <a href="javascript:;" onclick="del('{{ user.username }}')"> 删除 a> td>
                
            tr>
        {% endfor %}
    table>
{% endblock %}

{% block myjs %}
    <script>
        function del(username){
            console.log(username)
            // location 地址栏对象。.href是将我们地址栏port后面的值换成了下面的值,而这个格式就是GET
            location.href = '/del?username='+username   
        }
    script>
    
{% endblock %}

细节1:view.py中,render_template函数的一个参数:user/show.html,之所以不需要写全路径,是因为render_template会自动寻找template文件夹下的文件。

细节2:render_template导入参数users,这个我们之前的文章中讲过。

细节3:show.html很明显是继承了一个模板:{% extends 'base.html' %}。关于模板的继承,我们在上一章节中有描述。

细节4:show.html中,首先我们设置了三个超链接,分别对应进入registerlogin以及logout路由。相关的代码,我们就需要先去view.py中对应的三个路由模块。

注册界面

当我们点击用户注册按钮,系统进入register路由,位于view.py中,代码如下:

@user_bp.route('/register',methods=['GET','POST'])
def register():
    if request.method == "POST":
        # 获取post提交的数据
        username = request.form.get('username')
        password = request.form.get('password')
        phone = request.form.get('phone')
        repassword = request.form.get('repassword')
        if password == repassword:
            #创建User对象
            for user in users:
                if user.username == username:
                    return render_template("user/register.html",msg = "用户名已存在")
            user = UserCls(username,password,phone)
            users.append(user)
            return redirect('/')
        else:
            return render_template("user/register.html",msg = "密码输入不一致,请校对")
    return render_template("user/register.html")

这段代码的意思是,如果没有接收到POST返回,那么系统就会进入user/register.html页面。
但是,如果有接收到POST返回(说明用户在register页面点击了用户注册按钮),系统会接收到从POST返回的数据,
包括usernamepasswordphonerepassword。如果输入的两边密码不一致,那么页面还是在register。并且页面会显示密码输入不一致,请校对
如果在系统中已经有了此用户,那么页面还是在register。并且页面会显示用户名已存在
如果注册成功,那么页面将跳转回主界面。

注册页面的html代码如下:

{% extends 'base.html' %}

{% block title %}
    用户注册
{% endblock %}

{% block middle %}
    <p style="color: red;"> {{ msg }} p>
    <form action="/register" method="post">
        <p><input type="text" name="username" placeholder="用户名">p>
        <p><input type="password" name="password" placeholder="密码">p>
        <p><input type="password" name="repassword" placeholder="确认密码">p>
        <p><input type="number" name="phone" placeholder="手机号码">p>
        <p><input type="submit" value="用户注册">p>
    form>
    <form action="/" method="post">
        <p><button>返回主页button>p>      
    form>
{% endblock %}

Flask 极致细节:3. 一个案例,简单的用户界面_第2张图片

在注册成功后,主界面会显示其相关信息。

删除/修改用户信息

当我们注册了新用户后,主界面上会显示其信息,如下图:

Flask 极致细节:3. 一个案例,简单的用户界面_第3张图片

我们发现,用户信息每一行分别有一个修改以及删除按钮。这两个按钮是在show.html中编写:

<h1>用户信息h1>
<span> 当前用户人数是:{{ users |length }} span>
<table border="1" cellspacing="0" width="60%" >
    {%for user in users %}
        <tr>
            <td> {{ loop.index }} td>
            <td> {{ user.username }} td>
            <td> {{ user.password }} td>
            <td> {{ user.phone }} td>
            <td> <a href="javascript:;" onclick="update('{{ user.username }}')"> 修改 a> 
                    <a href="javascript:;" onclick="del('{{ user.username }}')"> 删除 a> td>
            
        tr>
    {% endfor %}
table>

两个onclick函数的定义如下:

{% block myjs %}
    <script>
        function del(username){
            console.log(username)
            // location 地址栏对象。.href是将我们地址栏port后面的值换成了下面的值,而这个格式就是GET
            location.href = '/del?username='+username   
        }
        // 修改
        function update(username){
            location.href = '/update?username='+username
        } 
    </script>
    <!-- 相当于通过GET的方式将username传出去 -->
{% endblock %}

也就是说,当我们点击了修改或者删除按钮后,对应的onclick函数被调用,location地址栏返回地址,通过GET的方式将username返回。

view.py删除功能相关的代码如下:

@user_bp.route('/del')
def del_user():
    # 你传递过来的用户名,在show.html中,当我们点击了删除按钮后,系统发动onclick事件,通过js代码将username通过GET方式传出来。
    username = request.args.get('username')
    # 根据你的username找到列表中的user对象
    for user in users:
        if user.username == username:
            # 删除这个user
            users.remove(user)
            # 最终返回删除成功的结果
            return redirect('/')
    else:
        return "删除失败"

也就是说,系统返回带有username的链接,del路由提取username的值,然后和所有users进行比对,如果找到了,那么我们就移除。

view.py修改功能相关的代码如下:

@user_bp.route('/update', methods=['GET','POST'], endpoint='update')
def update_user():
    if request.method == "POST":
        # 当我们在修改界面点击了更新按钮后,系统会以POST方式返回
        realname = request.form.get('realname')
        username = request.form.get('username')
        password = request.form.get('password')
        phone = request.form.get('phone')
        for user in users:
            # 如果我们找到需要修改的user,我们把刚才我们输入的参数赋给user,然后页面切换到主页。
            if user.username == realname:
                user.phone = phone
                user.username = username
                return redirect('/')
        return render_template('user/update.html', user=user, msg='出了一些问题')
    elif request.method == "GET":
        # 当我们在界面点击了修改按钮后,show.html对应的onclick函数会返回一个链接,可以通过GET的方式传回。
        username = request.args.get('username')
        for user in users:
            if user.username == username:
                # 当系统找到这个需要修改的用户后,系统进入修改界面,并传输user。
                return render_template('user/update.html', user=user)
    return render_template('user/update.html')

其他的几个模块大家就自己看代码了,总体上来说,这篇博文以及代码距离真正意义上的项目代码又更近了一步。

你可能感兴趣的:(flask,flask,python,后端)