提示:在第一个案例中,我们将逐渐规范化我们的代码,以及flask
项目的结构(什么文档应该放在哪里)。此案例,我们仅做一个简单的用户名注册,登录的界面。
这里,我们一如既往地分享所有代码,并附有非常详细的注解。
喜欢的朋友点个赞哦:)
代码链接:https://pan.baidu.com/s/1Q_OpztbqA7r0GTOyRtaS8w
密码:a2f9
--项目名
|---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项目。
如下为一个最小的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__)
让我们从直接运行代码,然后从结果解读过程。当我们在terminal中直接运行python app.py
(注意,先进入虚拟环境),点击链接,你会看到如下界面:
通过上面的描述,我们应该很容易找到对应的代码位置:
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
中,首先我们设置了三个超链接,分别对应进入register
,login
以及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返回的数据,
包括username
,password
,phone
,repassword
。如果输入的两边密码不一致,那么页面还是在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 %}
在注册成功后,主界面会显示其相关信息。
当我们注册了新用户后,主界面上会显示其信息,如下图:
我们发现,用户信息每一行分别有一个修改
以及删除
按钮。这两个按钮是在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')
其他的几个模块大家就自己看代码了,总体上来说,这篇博文以及代码距离真正意义上的项目代码又更近了一步。