本篇博客介绍Flask中的表单工具FlaskForm的使用,并搭建一个简易的管理员后台。
1.相关库安装
pip install flask_wtf
2.相关代码
在myblog文件下建立forms.py用于存放表单类,在home目录下新建admin.py用于存放管理员相关的视图函数,在templates目录下新建admin文件夹,并在该文件夹下建立login.html、edit.html、add.html,分别为登录界面、博客管理界面和新建博客界面。项目目录变更如下:
![](https://img2020.cnblogs.com/blog/1709480/202009/1709480-20200929111832798-1415170605.png)
在forms.py中添加登录表单和新建博客表单:
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField, TextAreaField, SelectField
from wtforms.validators import DataRequired, Length
from myblog.models import Category
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(1, 20)])
password = PasswordField('Password', validators=[DataRequired(), Length(1, 128)])
remember = BooleanField('Remember me')
submit = SubmitField('Log in')
class AddForm(FlaskForm):
title = StringField('Title', validators=[DataRequired(), Length(1, 20)])
body = TextAreaField('Body', validators=[DataRequired()])
category = SelectField('Category', coerce=int, default=1)
submit = SubmitField()
def __init__(self, *args, **kwargs):
super(AddForm, self).__init__(*args, **kwargs)
self.category.choices = [(category.id, category.name)
for category in Category.query.order_by(Category.name).all()]
由于表单提交会涉及跨域访问问题CSRF,所以需要在config.py中添加一个 SECRET_KEY 字段:
class Config(object):
SQLALCHEMY_DATABASE_URI = 'mysql+cymysql://root:root@localhost:3306/myflask?charset=utf8'
SQLALCHEMY_TRACK_MODIFICATIONS = True
SECRET_KEY = "you will never known it."
存放视图函数的admin.py:
from flask import request, Blueprint, render_template, redirect, url_for, flash
from myblog.models import Article, Category, Comment, Admin
from myblog.forms import LoginForm, AddForm
from myblog.extensions import db
admin_bp = Blueprint('admin', __name__)
@admin_bp.route('/login', methods=['POST', 'GET'])
def login():
form = LoginForm()
if form.validate_on_submit():
username = form.username.data
password = form.password.data
remember = form.remember.data
admin = Admin.query.first()
if username == admin.username and admin.validate_password(password):
flash('登录成功', category='info')
return redirect(url_for('admin.edit'))
else:
flash('登录失败', category='warning')
return render_template('admin/login.html', form=form)
@admin_bp.route('/edit')
def edit():
page = request.args.get('page', 1, type=int)
articles = Article.query.order_by(Article.timestamp.desc()).paginate(page, 10, False)
return render_template('admin/edit.html', articles=articles)
@admin_bp.route('/delete/<int:article_id>')
def delete(article_id):
article = Article.query.filter(Article.id == article_id).first()
db.session.delete(article)
db.session.commit()
flash('删除成功', category='info')
return redirect(url_for('admin.edit'))
@admin_bp.route('/add', methods=['POST', 'GET'])
def add():
form = AddForm()
if form.validate_on_submit():
title = form.title.data
body = form.body.data
category = form.category.data
article = Article(title=title, body=body, category_id=category)
db.session.add(article)
db.session.commit()
flash('新建博客成功', category='info')
return redirect(url_for('admin.edit'))
return render_template('admin/add.html', form=form)
初始化代码__init__.py中注册admin蓝本:
from flask import Flask
from myblog.home.blog import blog_bp
from myblog.home.admin import admin_bp
from myblog.extensions import db, migrate, bootstrap
from myblog.config import Config
def create_app():
app = Flask(__name__)
app.config.from_object(Config)
register_blueprints(app)
register_extensions(app)
return app
def register_blueprints(app):
app.register_blueprint(blog_bp)
app.register_blueprint(admin_bp, url_prefix='/admin')
def register_extensions(app):
db.init_app(app)
db.create_all(app=app)
migrate.init_app(app, db)
bootstrap.init_app(app)
登录页面login.html:
{% extends 'base.html' %}
{% block title %}
Login
{% endblock %}
{% block content %}
<br />
{% from 'bootstrap/form.html' import render_form %}
{{ render_form(form) }}
{% endblock %}
管理后台页面edit.html:
{% extends 'base.html' %}
{% block title %}
Admin
{% endblock %}
{% block content %}
<div class="col-sm-8">
<br />
<a class="btn btn-info" href="{{ url_for('admin.add') }}">新建博客</a>
<br />
<br />
<ul class="list-group">
{% for article in articles.items %}
<li class="list-group-item">
<h4 style="display:block;float:left;padding-top:2px">
{{ article.title }}
</h4>
<div style="display:block;float: right;">
<a class="btn btn-primary" href="{{ url_for('blog.article', article_id=article.id) }}">查看</a>
<a class="btn btn-danger" href="{{ url_for('admin.delete', article_id=article.id) }}">删除</a>
</div>
</li>
{% endfor %}
</ul>
<nav aria-label="Page navigation example" class="m-4">
<ul class="pagination justify-content-center">
<li class="page-item {% if not articles.has_prev %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('admin.edit', page=articles.prev_num) }}">上一页</a>
</li>
{% for page in articles.iter_pages(1,1,3,2) %}
{% if page %}
<li class="page-item {%if page==articles.page%}active{%endif%}">
<a class="page-link" href="{{ url_for('admin.edit',page=page) }}">{{page}}</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#">…</a>
</li>
{% endif %}
{% endfor %}
<li class="page-item {% if not articles.has_next %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('admin.edit',page=articles.next_num) }}">下一页</a>
</li>
</ul>
</nav>
</div>
{% endblock %}
新建博客页面add.html:
{% extends 'base.html' %}
{% block title %}
Add
{% endblock %}
{% block content %}
<br />
{% from 'bootstrap/form.html' import render_form %}
{{ render_form(form) }}
{% endblock %}
将之前base.html中的登录链接地址修改为admin.login,并添加展示flash消息的代码:
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% block styles %}
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}"> {{ bootstrap.load_css() }}
{% endblock %}
<title>{% block title %}{% endblock %}</title>
{% endblock %}
</head>
<body>
{% for message in get_flashed_messages() %}
<div class="alert alert-primary" role="alert">
{{ message }}
</div>
{% endfor %}
{% block nav %}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="#">我的博客</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item {% if request.endpoint == 'blog.index' %} active {% endif %}">
<a class="nav-link" href="{{ url_for('blog.index') }}">首页<span class="sr-only">(current)</span></a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item {% if request.endpoint == 'blog.index' %} active {% endif %}">
<a class="nav-link" href="{{ url_for('admin.login') }}">登录</a>
</li>
</ul>
</div>
</div>
</nav>
{% endblock nav %}
<div class="container">
{% block content %}{% endblock%}
</div>
{% block scripts %}
{{ bootstrap.load_js() }}
{% endblock %}
</body>
</html>
3.页面展示
登录页面:
![](https://img2020.cnblogs.com/blog/1709480/202009/1709480-20200929112327991-1454666611.png)
管理后台页面:
![](https://img2020.cnblogs.com/blog/1709480/202009/1709480-20200929112250345-311029437.png)
新建博客页面:
![](https://img2020.cnblogs.com/blog/1709480/202009/1709480-20200929112148912-1070694256.png)