Blog 项目源码:/JmilkFan/JmilkFan-s-Blog
目录
目录前文列表扩展阅读添加账户管理蓝图新建控制器(蓝图)新建表单新建蓝图 main 的视图函数新建模板 页面效果前文列表
用 Flask 来写个轻博客 (1) — 创建项目
用 Flask 来写个轻博客 (2) — Hello World!
用 Flask 来写个轻博客 (3) — (M)VC_连接 MySQL 和 SQLAlchemy
用 Flask 来写个轻博客 (4) — (M)VC_创建数据模型和表
用 Flask 来写个轻博客 (5) — (M)VC_SQLAlchemy 的 CRUD 详解
用 Flask 来写个轻博客 (6) — (M)VC_models 的关系(one to many)
用 Flask 来写个轻博客 (7) — (M)VC_models 的关系(many to many)
用 Flask 来写个轻博客 (8) — (M)VC_Alembic 管理数据库结构的升级和降级
用 Flask 来写个轻博客 (9) — M(V)C_Jinja 语法基础快速概览
用 Flask 来写个轻博客 (10) — M(V)C_Jinja 常用过滤器与 Flask 特殊变量及方法
用 Flask 来写个轻博客 (11) — M(V)C_创建视图函数
用 Flask 来写个轻博客 (12) — M(V)C_编写和继承 Jinja 模板
用 Flask 来写个轻博客 (13) — M(V)C_WTForms 服务端表单检验
用 Flask 来写个轻博客 (14) — M(V)C_实现项目首页的模板
用 Flask 来写个轻博客 (15) — M(V)C_实现博文页面评论表单
用 Flask 来写个轻博客 (16) — MV(C)_Flask Blueprint 蓝图
用 Flask 来写个轻博客 (17) — MV(C)_应用蓝图来重构项目
用 Flask 来写个轻博客 (18) — 使用工厂模式来生成应用对象
用 Flask 来写个轻博客 (19) — 以 Bcrypt 密文存储账户信息与实现用户登陆表单
用 Flask 来写个轻博客 (20) — 实现注册表单与应用 reCAPTCHA 来实现验证码
扩展阅读
flash 消息闪现
添加账户管理蓝图
网站的账户管理功能一般包括最基本的登录/注册/权限, 在本篇中, 我们主要实现前两个功能模块.
在当前的项目中, 对 URL:http://localhost:5000/
的路由在 jmilkfansblog.__init__:create_app() 被定义且重定向到蓝图 blog 的视图函数 home() 中. 如果一个 URL/
只被一个视图函数处理的话, 这样做是没有问题的, 但现在我们需要在/
下添加新的视图函数来为项目提供账户管理功能, 因此我们需要添加一个新的 controller, 并把该控制器注册到 app 对象中.
新建控制器(蓝图)
jmilkfansblog/controller/main.pyfrom os import pathfrom uuid import uuid4from flask import flash, url_for, redirect, render_template, Blueprintfrom jmilkfansblog.forms import LoginForm, RegisterFormfrom jmilkfansblog.models import db, Usermain_blueprint = Blueprint('main',__name__,template_folder=path.join(path.pardir, 'templates', 'main'))@main_blueprint.route('/')def index():return redirect(url_for('blog.home'))
NOTE: 当 HTTP Request method 为 GET 的时候, 路由函数index()
将会被优先调用.
jmilkfansblog/__init__.py
from flask import Flask, redirect, url_forfrom jmilkfansblog.models import dbfrom jmilkfansblog.controllers import blog, mainfrom jmilkfansblog.extensions import bcryptdef create_app(object_name):"""Create the app instance via `Factory Method`"""app = Flask(__name__)# Set the config for app instanceapp.config.from_object(object_name)# Will be load the SQLALCHEMY_DATABASE_URL from config.py to db objectdb.init_app(app)# Init the Flask-Bcrypt via app objectbcrypt.init_app(app)# Register the Blueprint into app objectapp.register_blueprint(blog.blog_blueprint)app.register_blueprint(main.main_blueprint)return app
NOTE 1: 这样的话, app 对象就拥有了两个蓝图, 其中 blog 提供博客内容的管理和展示功能, main 提供了网站的账户管理功能.
NOTE 2: 因为在 URL/
下包含了博客首页和用户登录两个视图, 所以在两个蓝图中都必须含有对/
进行处理的视图函数.
新建表单
表单主要用于输入登录和注册信息.
jmilkfansblog/forms.py
class LoginForm(Form):"""Login Form"""username = StringField('Usermame', [DataRequired(), Length(max=255)])password = PasswordField('Password', [DataRequired()])def validate(self):"""Validator for check the account information."""check_validata = super(LoginForm, self).validate()# If validator no passif not check_validata:return False# Check the user whether exist.user = User.query.filter_by(username=self.username.data).first()if not user:self.username.errors.append('Invalid username or password.')return False# Check the password whether right.if not user.check_password(self.password.data):self.username.errors.append('Invalid username or password.')return Falsereturn Trueclass RegisterForm(Form):"""Register Form."""username = StringField('Username', [DataRequired(), Length(max=255)])password = PasswordField('Password', [DataRequired(), Length(min=8)])comfirm = PasswordField('Confirm Password', [DataRequired(), EqualTo('password')])recaptcha = RecaptchaField()def validate(self):check_validate = super(RegisterForm, self).validate()# If validator no passif not check_validate:return False# Check the user whether exist.user = User.query.filter_by(username=self.username.data).first()if user:self.username.errors.append('User with that name already exists.')return Falsereturn True
NOTE 1: 在 class Form 中提供了检验函数validate()
, 该函数会在表单对象调用validate_on_submit()
的时候被调用, 所以在 LoginForm 和 RegisterForm 子类中都重载了Form.validate()
并扩展了个性化的检验需求. EG. LoginForm 的 validate() 还需要检验用户是否存在以及用户密码是否正确.
NOTE 2:validate()
应该返回 boolean 类型.
NOTE 3: 我们会在注册页面上使用验证码, 所以 RegisterForm 中定义了类属性recaptcha = RecaptchaField()
.
新建蓝图 main 的视图函数
在创建了表单之后, 我们需要新建为登录和注册模板提供数据对象的视图函数, 并且将表单对象应用到其中.
jmilkfansblog/controller/main.py
@main_blueprint.route('/login', methods=['GET', 'POST'])def login():"""View function for login."""# Will be check the account whether rigjt.form = LoginForm()if form.validate_on_submit():flash("You have been logged in.", category="success")return redirect(url_for('blog.home'))return render_template('login.html',form=form)@main_blueprint.route('/logout', methods=['GET', 'POST'])def logout():"""View function for logout."""flash("You have been logged out.", category="success")return redirect(url_for('blog.home'))@main_blueprint.route('/register', methods=['GET', 'POST'])def register():"""View function for Register."""# Will be check the username whether exist.form = RegisterForm()if form.validate_on_submit():new_user = User(id=str(uuid4()),username=form.username.data,password=form.password.data)db.session.add(new_user)mit()flash('Your user has been created, please login.',category="success")return redirect(url_for('main.login'))return render_template('register.html',form=form)
NOTE 1: 当用户注册成功之后会将用户信息写入到数据库中, 然后直接跳转到登录页面.
NOTE 2: 在登录成功之后会返回[(‘success’, ‘You have been logged in.’)]的信息, 这是由flash()
来实现的. 详见扩展阅读.
新建模板
最后我们在创建登录和注册页面的模板.
jmilkfansblog/template/base.html: 首先我们需要在 base 模板中加入对flash()
和 reCAPTCHA 的支持.
...{% block captcha %} {% endblock %}</head> <body><div class="container"><div class="jumbotron"><!-- Replace the route function to URL: `/` --><h1><a href="{{ url_for('blog.home')}} ">JmilkFan's Blog</a></h1><p>Welcome to the blog!</p></div>{% with messages = get_flashed_messages(with_categories=true) %}{% if messages %}{% for category, message in messages %}<div class="button" class="close" data-dismiss="alert" aria-label="Close"><button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>{{ messages }}</div>{% endfor%}{% endif %}{% endwith %}{% block body %}body_content{% endblock %}...
NOTE 1:get_flashed_messages(with_categories=true)
逐一取出并消费掉之前在应用中通过flash()
传入的信息列表,类似实现一个队列。
NOTE 2: 一般来说flash()
的消息是全局的, 只要在任意页面中定义了get_flashed_messages()
就会被获取.
jmilkfansblog/template/login.html
{% extends "base.html" %}{% block body %}<div class="col-lg-3"><!-- Set the form --><form method="POST" action="{{ url_for('main.login')}}">{{ form.hidden_tag() }}<div>{{ form.username.label }}{% if form.username.errors %}{% for e in form.username.errors %}<p class="help-block">{{ e }}</p>{% endfor %}{% endif %}{{ form.username(class_="form-control") }}</div><div class="form-group">{{ form.password.label }}{% if form.password.errors %}{% for e in form.password.errors %}<p class="help-block">{{ e }}</p>{% endfor %}{% endif %}{{ form.password(class_='form-control') }}</div><input class="btn btn-primary" type="submit" value="Login"></form></div>{% endblock %}
register.html
{% extends "base.html" %}{% block title %} Register{% endblock %} {% block captcha %}<script src='/recaptcha/api.js'></script>{% endblock %}{% block body %} <div class="col-lg-3"><!-- Set the form --> <form method="POST" action="{{ url_for('main.register') }}">{{ form.hidden_tag() }}<div> {{ form.username.label }} {{ form.username(class_="form-control") }} </div><div class="form-group"> {{ form.password.label }} {{ form.password(class_='form-control') }} </div><div class="form-group">{{ firm.label }}{{ firm(class_='form-control') }} </div><input class="btn btn-primary" type="submit" value="Register"> <div class="g-recaptcha" data-sitekey="<Your public key>"></div></form></div>{% endblock %}
NOTE 1: 在模板 register 中需要按照 reCAPTCHA 官档给出的方法将<script src='/recaptcha/api.js'></script>
和<div class="g-recaptcha" data-sitekey="<Your public key>"></div>
应用到该模板中, 验证码才会生效.
NOTE 2: 因为要访问 google 所以在测试是部署的时候都需要使用 VPN 会反向代理来完成.
页面效果
注册页面:
reCHPTCHA 验证码:
通过验证:
成功注册并跳转到登录页面:
登录失败: