Flask+Bootstrap实战二
本次的实战是仿照视频制作一个简易的博客网站
项目教程地址:
Youtobe源地址:Bootstrap Flask
Bilibili搬运:哔哩哔哩
一.创建数据库完成用户注册任务
Flask-SQLAlChemy创建用户模型
首先安装flask-sqlalchemy库,之后需要创建一个config文件进行数据库的配置
config.py文件内容如下,他的意思就是首先会去环境变量中获取数据库的url,如果没有的话会新建一个app.db
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
# Database configuration
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
新创建一个models.py文件,文件内容如下
from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True)
password = db.Column(db.String(20), unique=True)
email = db.Column(db.String(120), unique=True)
def __repr__(self):
return '<User %r>' % self.username
之后,需要修改app.py,使config创建的用户模型能够被读取到
主要是需要加上这两行代码
from config import Config,以及app.config.from_object(Config);db = SQLAlchemy(app)
注意:创建数据库要在配置之前,否则会报错
RuntimeError: Either 'SQLALCHEMY_DATABASE_URI' or 'SQLALCHEMY_BINDS'
must be set.
新建一个数据库
进入flask shell,导入from models import db,之后新建一个db.create_all(),即可新建一个app.db文件
from models import db
>>> db.create_all()
>>> from models import User
>>> user = User(username = "jack", password = "123456", email = "jack@qq.com")
>>> db.session.add(user)
>>> db.session.commit()
>>> User.query.all()
[<User 'jack'>]
>>>
按照上面依次输入即可
二.使用Flask WTF创建用户注册表单
在这里可以查看WTF支持的示例,使用quick_form宏
首先需要修改navbar.html
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Flask App</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="{% if request.endpoint == 'index' %}active{% endif %}">
<a href="{{url_for('index')}}">Home</a>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
需要新建一个路由
@app.route('/register')
def register():
return render_template("register.html")
之后需要新建一个register.html
{% block_app content %}
<h1>Register Now</h1>
{% endblock %}
使用flask-wtf定义forms
新建一个forms.py代码如下
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, Email, EqualTo
class RegisterForm(FlaskForm):
username = StringField("Username", validators=[DataRequired(), Length(min=6, max=20)])
password = PasswordField("Password", validators=[DataRequired(), Length(min=6, max=20)])
email = StringField("Email", validators=[DataRequired(), Email()])
conform = StringField("Repeat Password", validators=[DataRequired(), EqualTo("password")])
submit = SubmitField("Register")
在app.py中导入forms.py文件
做如下修改
@app.route('/register')
def register():
form = RegisterForm()
return render_template("register.html", form=form)
在config.py文件中修改key
import os
basedir = os.path.abspath(os.path.dirname(file))
class Config(object):
SECRET_KEY = os.environ.get('DATABASE_URL') or 'A VERY LONG SECRET_KEY'
# Database configuration
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
修改register.html
{% extends "base.html" %}
{% block content %}
<h1>Register Now</h1>
<br>
<div class="row">
<div class="col-md-8">
{% import bootstrap/wtf.html as wtf %}
{{ wtf.quick_form(form) }}
</div>
</div>
{% endblock %}
最后效果展示
这里就是这样正常处理完之后,会出现一个小错误
这里需要再回到app.py中进行修改如下。有两处修改,一是使register函数可以处理GET和POST请求,一是检查用户输入是否通过表单验证
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if form.validate_on_submit():
pass
return render_template("register.html", form=form)
然后就可以了
添加一个reCAPTURE
这个很酷,我们试着来操作一下,wtf教程文档点击这里
但是由于谷歌这里的原因,用梯子试了等一下不行
这个就先跳过了
三.将register登录的信息存储到数据库中
使用Bcrypt进行用户数据加密
对app.py文件进行修改后如下
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if form.validate_on_submit():
username = form.username.data
email = form.email.data
password = bcrypt.generate_password_hash(form.password.data)
print(username, password, email)
即可打印出加密后的密码的哈希字符串
将用户注册信息写入数据库
循环导入报错
修改app.py中register的路由,改成这样
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if form.validate_on_submit():
username = form.username.data
email = form.email.data
password = bcrypt.generate_password_hash(form.password.data)
user = User(username=username, email=email, password=password)
db.session.add(user)
db.session.commit()
flash("Congratulation, registeration success!", category="success")
之后在app.py中添加from models import User,就会报错,会出现循环导入
这个错误是由于循环导入引起的。循环导入发生在两个或多个模块相互导入对方,导致 Python 无法正确解析依赖关系。在你的情况下,app.py 导入了 models.py,而 models.py 又试图导入 app.py,这就导致了循环导入。
首先搞清楚,就是在app.py中,因为操作数据库会不可避免地用到app = Flask(__name__)和db = SQLAlchemy(app),而在models.py中from app import db之后进行用户模型定义。最后,我们又企图在app.py中利用建好的用户模型进行注册信息的写入user = User(username=username, email=email, password=password),才会发生循环导入
目录结构修改
可以看到的是,这个目录结构设置的就比较巧妙,首先是新建了一个app目录,在__init__.py文件中使用app = Flask(_name_)和db = SQLAlchemy(app)新建了app,这样做的目的就是在models.py中进行app导入时,可以不用修改路径直接导入,因为__init__.py文件的特殊性,也不用from app.__init__ import app,所以可以直接导入。另外就是routes导入User的时候也不用修改路径,直接from models import User
另外就是在原先的app.py位置新建一个run.py用于启动
那整个的顺序就是变成了__init__.py来新建的app.py,之后models.py调用一次之后,由routes.py再进行调用,这样就避免了循环导入。最后再由run.py进行直接启动
最后一步,因为routes.py导入html需要和templates文件夹在同一模板下面,所以需要将templates文件夹整个cut到app文件夹下面,最后整个的结构如图所示
验证用户名和密码是否存在于数据库中
我们需要在wtf建立的用户注册表单中进行修改,就是在forms.py文件中
这里修改为from models import User,为什么不能是from app.routes import User呢?一旦这么写就又会发生循环导入
在你的情况下,forms.py 导入了 app.routes,而 app.routes 又试图导入 forms.py,这就导致了循环导入。
之后可以看到
写Flash弹窗消息
这个按照视频里的写法有一些小bug,就是在每次注册完成后反而并不显示flash的弹窗消息,会直接重定向到index界面,而再次打开register注册界面才会出现那个绿色的弹出,而且必须得刷新一下才能消失。就是不符合我们正常的操作习惯,他应该是先显示弹窗2秒之后,消失再重定向到index界面
咨询了一下GPT,这么写。首先绿色弹窗的样式都不用动,我们新建一个register_success.html文件,代码如下
点击查看代码
{% extends "bootstrap/base.html" %}
{% block content %}
<div class = "container">
<div class="row">
<div class="col-md-6">
{% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
</div>
</div>
<script>
// Wait for 2 seconds and then redirect to the index page
setTimeout(function() {
window.location.href = "{{ url_for('index') }}";
}, 2000);
</script>
{% endblock %}
里面script的部分就是控制的延时函数
之后在routes.py中写入一个新的路由用来重定向
@app.route('/register_success')
def register_success():
return render_template("register_success.html")
这样原先的register路由就会变成
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if form.validate_on_submit():
username = form.username.data
email = form.email.data
password = bcrypt.generate_password_hash(form.password.data)
user = User(username=username, email=email, password=password)
db.session.add(user)
db.session.commit()
flash("Congratulation, registeration success!", category="success")
return redirect(url_for("register_success"))
return render_template("register.html", form=form)
最后的效果如下
浙公网安备 33010602011771号