Explorar el Código

feat: 表单都启用验证功能

SongZihuan hace 3 años
padre
commit
ff5e89fcec

+ 27 - 18
app/archive.py

@@ -1,8 +1,8 @@
 from flask import Blueprint, render_template, redirect, url_for, flash, g
 from flask_login import login_required, current_user
 from flask_wtf import FlaskForm
-from wtforms import StringField, SubmitField
-from wtforms.validators import DataRequired
+from wtforms import StringField, TextAreaField, SubmitField
+from wtforms.validators import DataRequired, Length, ValidationError
 
 import app
 from object.archive import Archive
@@ -11,40 +11,49 @@ archive = Blueprint("archive", __name__)
 
 
 class CreateArchiveForm(FlaskForm):
-    name = StringField("名字", validators=[DataRequired()])
-    describe = StringField("描述", validators=[DataRequired()])
+    name = StringField("名称", description="归档名称",
+                       validators=[DataRequired(message="必须填写归档名称"),
+                                   Length(1, 10, message="归档名称长度1-10个字符")])
+    describe = TextAreaField("描述", description="归档描述",
+                             validators=[Length(-1, 25, message="归档描述长度25个字符以内")])
     submit = SubmitField("创建归档")
 
+    def validate_name(self, field):
+        name = field.data
+        archive_list = Archive.get_archive_list()
+        for i in archive_list:
+            if name == i[1]:
+                raise ValidationError("归档已经存在")
 
-@archive.route('/')
-def archive_page():
+
+def __load_archive_page(form: CreateArchiveForm):
     archive_list = Archive.get_archive_list()
     app.HBlogFlask.print_load_page_log("archive list")
     return render_template("archive/archive.html",
                            archive_list=archive_list,
-                           form=CreateArchiveForm(),
+                           form=form,
                            show_delete=current_user.check_role("DeleteBlog"))
 
 
+@archive.route('/')
+def archive_page():
+    return __load_archive_page(CreateArchiveForm())
+
+
 @archive.route("create", methods=["POST"])
 @login_required
-@app.form_required(CreateArchiveForm, "create archive")
+@app.form_required(CreateArchiveForm, "create archive", __load_archive_page)
 @app.role_required("WriteBlog", "create archive")
 def create_archive_page():
     form: CreateArchiveForm = g.form
     name = form.name.data
     describe = form.describe.data
-    if len(name) > 10:
-        flash("归档名太长了")
-    elif len(describe) > 30:
-        flash("归档描述太长了")
+    if Archive(name, describe, None).create():
+        app.HBlogFlask.print_sys_opt_success_log(f"Create archive {name}")
+        flash(f"创建归档 {name} 成功")
     else:
-        if Archive(name, describe, None).create():
-            app.HBlogFlask.print_sys_opt_success_log(f"Create archive {name}")
-            flash(f"创建归档 {name} 成功")
-        else:
-            app.HBlogFlask.print_sys_opt_fail_log(f"Create archive {name}")
-            flash(f"创建归档 {name} 失败")
+        app.HBlogFlask.print_sys_opt_fail_log(f"Create archive {name}")
+        flash(f"创建归档 {name} 失败")
     return redirect(url_for("archive.archive_page"))
 
 

+ 96 - 73
app/auth.py

@@ -1,8 +1,15 @@
 from flask import Blueprint, render_template, redirect, flash, url_for, request, abort, current_app, g
 from flask_login import login_required, login_user, current_user, logout_user
 from flask_wtf import FlaskForm
-from wtforms import StringField, PasswordField, BooleanField, SelectMultipleField, SelectField, SubmitField, ValidationError
-from wtforms.validators import DataRequired
+from wtforms import (EmailField,
+                     StringField,
+                     PasswordField,
+                     BooleanField,
+                     SelectMultipleField,
+                     SelectField,
+                     SubmitField,
+                     ValidationError)
+from wtforms.validators import DataRequired, Length, Regexp, EqualTo
 
 import app
 from object.user import User, load_user_by_email
@@ -11,17 +18,43 @@ from send_email import send_msg
 auth = Blueprint("auth", __name__)
 
 
-class LoginForm(FlaskForm):
-    email = StringField("邮箱", validators=[DataRequired()])
-    passwd = PasswordField("密码", validators=[DataRequired()])
+class AuthField(FlaskForm):
+    @staticmethod
+    def email_field(name: str, description: str):
+        return EmailField(name, description=description,
+                          validators=[
+                              DataRequired(f"必须填写{name}"),
+                              Length(1, 20, message=f"{name}长度1-20个字符"),
+                              Regexp(r"^[a-zA-Z0-9_\.]+@[a-zA-Z0-9_]+(\.[a-zA-Z0-9_\.]+)+$",
+                                     message=f"{name}不满足正则表达式")])
+
+    @staticmethod
+    def passwd_field(name: str, description: str):
+        return PasswordField(name, description=description,
+                             validators=[
+                                 DataRequired(f"必须填写{name}"),
+                                 Length(8, 32, message=f"{name}长度为8-32位")])
+
+    @staticmethod
+    def passwd_again_field(name: str, description: str, passwd: str = "passwd"):
+        return PasswordField(f"重复{name}", description=description,
+                             validators=[
+                                 DataRequired(message=f"必须再次填写{name}"),
+                                 EqualTo(passwd, message=f"两次输入的{name}不相同")])
+
+
+class EmailPasswd(AuthField):
+    email = AuthField.email_field("邮箱", "用户邮箱")
+    passwd = AuthField.passwd_field("密码", "用户密码")
+
+
+class LoginForm(EmailPasswd):
     remember = BooleanField("记住我")
     submit = SubmitField("登录")
 
 
-class RegisterForm(FlaskForm):
-    email = StringField("邮箱", validators=[DataRequired()])
-    passwd = PasswordField("密码", validators=[DataRequired()])
-    passwd_again = PasswordField("重复密码", validators=[DataRequired()])
+class RegisterForm(EmailPasswd):
+    passwd_again = AuthField.passwd_again_field("密码", "用户密码")
     submit = SubmitField("注册")
 
     def validate_email(self, field):
@@ -29,45 +62,70 @@ class RegisterForm(FlaskForm):
             raise ValidationError("邮箱已被注册")
 
 
-class ChangePasswdForm(FlaskForm):
-    old_passwd = PasswordField("旧密码", validators=[DataRequired()])
-    passwd = PasswordField("新密码", validators=[DataRequired()])
-    passwd_again = PasswordField("重复密码", validators=[DataRequired()])
+class ChangePasswdForm(AuthField):
+    old_passwd = AuthField.passwd_field("旧密码", "用户原密码")
+    passwd = AuthField.passwd_field("新密码", "用户新密码")
+    passwd_again = AuthField.passwd_again_field("新密码", "用户新密码")
     submit = SubmitField("修改密码")
 
+    def validate_passwd(self, field):
+        if field.data == self.old_passwd.data:
+            raise ValidationError("新旧密码不能相同")
 
-class DeleteUserForm(FlaskForm):
-    email = StringField("邮箱", validators=[DataRequired()])
+
+class DeleteUserForm(AuthField):
+    email = AuthField.email_field("邮箱", "用户邮箱")
     submit = SubmitField("删除用户")
 
+    def __init__(self):
+        super(DeleteUserForm, self).__init__()
+        self.email_user = None
+
     def validate_email(self, field):
-        if load_user_by_email(field.data) is None:
+        self.email_user = load_user_by_email(field.data)
+        if self.email_user is None:
             raise ValidationError("邮箱用户不存在")
 
 
-class CreateRoleForm(FlaskForm):
+class CreateRoleForm(AuthField):
     name = StringField("角色名称", validators=[DataRequired()])
     authority = SelectMultipleField("权限", coerce=str, choices=User.RoleAuthorize)
     submit = SubmitField("创建角色")
 
 
-class DeleteRoleForm(FlaskForm):
+class RoleForm(AuthField):
     name = SelectField("角色名称", validators=[DataRequired()], coerce=int)
-    submit = SubmitField("删除角色")
 
     def __init__(self):
-        super(DeleteRoleForm, self).__init__()
-        self.name.choices = [(i[0], i[1]) for i in User.get_role_list()]
+        super(RoleForm, self).__init__()
+        self.name_res = []
+        self.name_choices = []
+        for i in User.get_role_list():
+            self.name_res.append(i[0])
+            self.name_choices.append((i[0], i[1]))
+        self.name.choices = self.name_choices
 
+    def validate_name(self, field):
+        if field.data not in self.name_res:
+            raise ValidationError("角色不存在")
 
-class SetRoleForm(FlaskForm):
-    email = StringField("邮箱", validators=[DataRequired()])
-    name = SelectField("角色名称", validators=[DataRequired()], coerce=int)
+
+class DeleteRoleForm(RoleForm):
+    submit = SubmitField("删除角色")
+
+
+class SetRoleForm(RoleForm):
+    email = AuthField.email_field("邮箱", "用户邮箱")
     submit = SubmitField("设置角色")
 
     def __init__(self):
         super(SetRoleForm, self).__init__()
-        self.name.choices = [(i[0], i[1]) for i in User.get_role_list()]
+        self.email_user = None
+
+    def validate_email(self, field):
+        self.email_user = load_user_by_email(field.data)
+        if self.email_user is None:
+            raise ValidationError("邮箱用户不存在")
 
 
 @auth.route('/yours')
@@ -110,17 +168,6 @@ def register_page():
 
     form = RegisterForm()
     if form.validate_on_submit():
-        email = form.email.data
-        passwd = form.passwd.data
-        if len(email) > 20:
-            flash("邮箱太长了")
-            return redirect(url_for("auth.register_page"))
-        elif not 8 < len(passwd) < 32:
-            flash("请输入8-12位密码")
-            return redirect(url_for("auth.register_page"))
-        elif passwd != form.passwd_again.data:
-            flash("两次输入的密码不一致")
-            return redirect(url_for("auth.register_page"))
         token = User.creat_token(form.email.data, form.passwd.data)
         register_url = url_for("auth.confirm_page", token=token, _external=True)
         hblog: app.Hblog = current_app
@@ -172,19 +219,7 @@ def logout_page():
 def change_passwd_page():
     form = ChangePasswdForm()
     if form.validate_on_submit():
-        passwd = form.passwd.data
-        if not 8 < passwd < 32:
-            flash("请输入8-32位密码")
-            return redirect(url_for("auth.change_passwd_page"))
-        elif passwd != form.passwd_again.data:
-            flash("两次输入的密码不一致")
-            return redirect(url_for("auth.change_passwd_page"))
-        elif not current_user.check_passwd(form.old_passwd.data):
-            app.HBlogFlask.print_user_opt_fail_log("change passwd (old passwd error)")
-            flash("旧密码错误")
-            return redirect(url_for("auth.change_passwd_page"))
-
-        if current_user.change_passwd(passwd):
+        if current_user.change_passwd(form.passwd.data):
             app.HBlogFlask.print_user_opt_success_log(f"change passwd")
             flash("密码修改成功")
         else:
@@ -201,12 +236,7 @@ def change_passwd_page():
 def delete_user_page():
     form = DeleteUserForm()
     if form.validate_on_submit():
-        user = load_user_by_email(form.email.data)
-        if user is None:
-            app.HBlogFlask.print_sys_opt_fail_log(f"delete user {form.email.data}")
-            abort(404)
-            return
-
+        user = form.email_user
         if user.delete():
             app.HBlogFlask.print_sys_opt_success_log(f"{current_user.email} delete user {form.email.data} success")
             flash("用户删除成功")
@@ -236,15 +266,12 @@ def role_page():
 def role_create_page():
     form: CreateRoleForm = g.form
     name = form.name.data
-    if len(name) > 10:
-        flash("角色名字太长")
+    if User.create_role(name, form.authority.data):
+        app.HBlogFlask.print_sys_opt_success_log(f"Create role success: {name}")
+        flash("角色创建成功")
     else:
-        if User.create_role(name, form.authority.data):
-            app.HBlogFlask.print_sys_opt_success_log(f"Create role success: {name}")
-            flash("角色创建成功")
-        else:
-            app.HBlogFlask.print_sys_opt_success_log(f"Create role fail: {name}")
-            flash("角色创建失败")
+        app.HBlogFlask.print_sys_opt_success_log(f"Create role fail: {name}")
+        flash("角色创建失败")
     return redirect(url_for("auth.role_page"))
 
 
@@ -269,17 +296,13 @@ def role_delete_page():
 @app.role_required("ConfigureSystem", "assign user a role")
 def role_set_page():
     form: SetRoleForm = g.form
-    user = load_user_by_email(form.email.data)
-    if user is not None:
-        if user.set_user_role(form.name.data):
-            app.HBlogFlask.print_sys_opt_success_log(f"Role assign {form.email.data} -> {form.name.data}")
-            flash("角色设置成功")
-        else:
-            app.HBlogFlask.print_sys_opt_fail_log(f"Role assign {form.email.data} -> {form.name.data}")
-            flash("角色设置失败")
+    user = form.email_user
+    if user.set_user_role(form.name.data):
+        app.HBlogFlask.print_sys_opt_success_log(f"Role assign {form.email.data} -> {form.name.data}")
+        flash("角色设置成功")
     else:
-        app.HBlogFlask.print_sys_opt_fail_log(f"Role assign (bad email) {form.email.data} -> {form.name.data}")
-        flash("邮箱未注册")
+        app.HBlogFlask.print_sys_opt_fail_log(f"Role assign {form.email.data} -> {form.name.data}")
+        flash("角色设置失败")
     return redirect(url_for("auth.role_page"))
 
 

+ 48 - 27
app/docx.py

@@ -1,8 +1,8 @@
-from flask import Blueprint, render_template, abort, redirect, url_for, flash, make_response, g
+from flask import Blueprint, render_template, abort, redirect, url_for, flash, make_response, g, request
 from flask_wtf import FlaskForm
 from flask_login import login_required, current_user
-from wtforms import TextAreaField, StringField, SelectMultipleField, SubmitField
-from wtforms.validators import DataRequired
+from wtforms import TextAreaField, StringField, SelectMultipleField, SubmitField, ValidationError
+from wtforms.validators import DataRequired, Length
 
 import app
 from sql.base import DBBit
@@ -14,8 +14,12 @@ docx = Blueprint("docx", __name__)
 
 
 class WriteBlogForm(FlaskForm):
-    title = StringField("标题", validators=[DataRequired()])
-    subtitle = StringField("副标题", validators=[DataRequired()])
+    title = StringField("标题", description="博文主标题",
+                        validators=[
+                            DataRequired(message="必须填写标题"),
+                            Length(1, 20, message="标题长度1-20个字符")])
+    subtitle = StringField("副标题", description="博文副标题",
+                           validators=[Length(-1, 20, message="副标题长度20个字符以内")])
     archive = SelectMultipleField("归档", coerce=int)
     context = TextAreaField("博客内容", validators=[DataRequired()])
     submit = SubmitField("提交博客")
@@ -24,16 +28,31 @@ class WriteBlogForm(FlaskForm):
         super().__init__(**kwargs)
         self.context.data = "# Blog Title\n## Blog subtitle\nHello, World"
         archive = Archive.get_archive_list()
-        self.archive.choices = [(-1, "None")] + [(i[0], f"{i[1]} ({i[3]})") for i in archive]
+        self.archive_res = []
+        self.archive_choices = [(-1, "None")]
+        for i in archive:
+            self.archive_res.append(i[0])
+            self.archive_choices.append((i[0], f"{i[1]} ({i[3]})"))
+        self.archive.choices = self.archive_choices
+
+    def validate_archive(self, field):
+        if -1 in field.data:
+            if len(field.data) != 1:
+                raise ValidationError("归档指定错误(none归档不能和其他归档同时被指定)")
+        else:
+            for i in field.data:
+                if i not in self.archive_res:
+                    raise ValidationError("错误的归档被指定")
 
 
 class WriteCommentForm(FlaskForm):
-    context = TextAreaField(validators=[DataRequired()])
+    context = TextAreaField("", description="评论正文",
+                            validators=[DataRequired(message="请输入评论的内容"),
+                                        Length(1, 100, message="请输入1-100个字的评论")])
     submit = SubmitField("评论")
 
 
-@docx.route('/<int:page>')
-def docx_page(page: int = 1):
+def __load_docx_page(page: int, form: WriteBlogForm):
     if page < 1:
         app.HBlogFlask.print_user_opt_fail_log(f"Load docx list with error page({page})")
         abort(404)
@@ -44,13 +63,19 @@ def docx_page(page: int = 1):
     page_list = app.HBlogFlask.get_page("docx.docx_page", page, max_page)
     app.HBlogFlask.print_load_page_log(f"docx list (page: {page})")
     return render_template("docx/docx.html",
+                           page=page,
                            blog_list=blog_list,
                            is_top=DBBit.BIT_1,
                            page_list=page_list,
-                           form=WriteBlogForm(),
+                           form=form,
                            show_delete=current_user.check_role("DeleteBlog"))
 
 
+@docx.route('/<int:page>')
+def docx_page(page: int = 1):
+    return __load_docx_page(page, WriteBlogForm())
+
+
 @docx.route('/<int:archive>/<int:page>')
 def archive_page(archive: int, page: int = 1):
     if page < 1:
@@ -69,8 +94,7 @@ def archive_page(archive: int, page: int = 1):
                            form=None)
 
 
-@docx.route('/article/<int:blog_id>')
-def article_page(blog_id: int):
+def __load_article_page(blog_id: int, form: WriteCommentForm):
     article = load_blog_by_id(blog_id)
     if article is None:
         app.HBlogFlask.print_user_opt_fail_log(f"Load article with error id({blog_id})")
@@ -80,11 +104,16 @@ def article_page(blog_id: int):
     return render_template("docx/article.html",
                            article=article,
                            archive_list=article.archive,
-                           form=WriteCommentForm(),
+                           form=form,
                            show_delete=current_user.check_role("DeleteComment"),
                            show_email=current_user.check_role("ReadUserInfo"))
 
 
+@docx.route('/article/<int:blog_id>')
+def article_page(blog_id: int):
+    return __load_article_page(blog_id, WriteCommentForm())
+
+
 @docx.route('/down/<int:blog_id>')
 def article_down_page(blog_id: int):
     article = load_blog_by_id(blog_id)
@@ -99,38 +128,30 @@ def article_down_page(blog_id: int):
     return response
 
 
-@docx.route('/comment/<int:blog>', methods=["POST"])
+@docx.route('/comment/<int:blog_id>', methods=["POST"])
 @login_required
-@app.form_required(WriteCommentForm, "write comment")
+@app.form_required(WriteCommentForm, "write comment", __load_article_page)
 @app.role_required("WriteComment", "write comment")
-def comment_page(blog: int):
+def comment_page(blog_id: int):
     form: WriteCommentForm = g.form
     context = form.context.data
-    if Comment(None, blog, current_user, context).create():
+    if Comment(None, blog_id, current_user, context).create():
         app.HBlogFlask.print_user_opt_success_log("comment")
         flash("评论成功")
     else:
         app.HBlogFlask.print_user_opt_error_log("comment")
         flash("评论失败")
-    return redirect(url_for("docx.article_page", blog_id=blog))
+    return redirect(url_for("docx.article_page", blog_id=blog_id))
 
 
 @docx.route('/create-docx', methods=["POST"])
 @login_required
-@app.form_required(WriteBlogForm, "write blog")
+@app.form_required(WriteBlogForm, "write blog", lambda form: __load_docx_page(int(request.args.get("page", 1)), form))
 @app.role_required("WriteBlog", "write blog")
 def create_docx_page():
     form: WriteBlogForm = g.form
     title = form.title.data
-    if len(title) > 10:
-        flash("标题太长了")
-        abort(400)
-
     subtitle = form.subtitle.data
-    if len(subtitle) > 10:
-        flash("副标题太长了")
-        abort(400)
-
     archive = []
     if -1 not in form.archive.data:
         for i in form.archive.data:

+ 15 - 7
app/msg.py

@@ -1,8 +1,8 @@
-from flask import Blueprint, render_template, abort, redirect, url_for, flash, g
+from flask import Blueprint, render_template, abort, redirect, url_for, flash, g, request
 from flask_wtf import FlaskForm
 from flask_login import login_required, current_user
 from wtforms import TextAreaField, BooleanField, SubmitField
-from wtforms.validators import DataRequired
+from wtforms.validators import DataRequired, Length
 
 import app
 from sql.base import DBBit
@@ -15,13 +15,15 @@ class WriteForm(FlaskForm):
     """
     写新内容表单
     """
-    context = TextAreaField(validators=[DataRequired()])
+    context = TextAreaField("", description="留言正文",
+                            validators=[
+                                DataRequired("请输入留言的内容"),
+                                Length(1, 100, message="留言长度1-100个字符")])
     secret = BooleanField("私密留言")
     submit = SubmitField("留言")
 
 
-@msg.route('/<int:page>')
-def msg_page(page: int = 1):
+def __load_msg_page(page: int, form: WriteForm):
     if page < 1:
         app.HBlogFlask.print_user_opt_fail_log(f"Load msg list with error page({page})")
         abort(404)
@@ -34,16 +36,22 @@ def msg_page(page: int = 1):
     app.HBlogFlask.print_load_page_log(f"msg (page: {page})")
     return render_template("msg/msg.html",
                            msg_list=msg_list,
+                           page=page,
                            page_list=page_list,
-                           form=WriteForm(),
+                           form=form,
                            is_secret=DBBit.BIT_1,
                            show_delete=current_user.check_role("DeleteMsg"),
                            show_email=current_user.check_role("ReadUserInfo"))
 
 
+@msg.route('/<int:page>')
+def msg_page(page: int = 1):
+    return __load_msg_page(page, WriteForm())
+
+
 @msg.route('/write', methods=["POST"])
 @login_required
-@app.form_required(WriteForm, "write msg")
+@app.form_required(WriteForm, "write msg", lambda form: __load_msg_page(int(request.args.get("page", 1)), form))
 @app.role_required("WriteMsg", "write msg")
 def write_msg_page():
     form: WriteForm = g.form

+ 9 - 3
app/oss.py

@@ -2,7 +2,7 @@ from flask import Blueprint, redirect, render_template, abort, flash, url_for, r
 from flask_login import login_required
 from flask_wtf import FlaskForm
 from wtforms import FileField, StringField, SubmitField
-from wtforms.validators import DataRequired
+from wtforms.validators import DataRequired, Length
 
 from aliyun import aliyun
 import app
@@ -11,10 +11,16 @@ oss = Blueprint("oss", __name__)
 
 
 class UploadForm(FlaskForm):
-    file = FileField("选择文件", validators=[DataRequired()])
-    path = StringField("存储路径")
+    file = FileField("选择文件", description="待上传文件",
+                     validators=[DataRequired(message="必须选择文件")])
+    path = StringField("存储文件夹", description="文件路径(不含文件名)",
+                       validators=[Length(-1, 30, message="文件路径长度为30个字符以内")])
     submit = SubmitField("上传")
 
+    def __init__(self):
+        super(UploadForm, self).__init__()
+        self.path.data = "hblog/"
+
 
 @oss.before_request
 def check_aliyun():

+ 6 - 4
app/tool.py

@@ -1,8 +1,8 @@
 from functools import wraps
-from flask import abort, g
+from flask import abort, g, redirect, url_for
 from flask_login import current_user
 from flask_wtf import FlaskForm
-from typing import ClassVar
+from typing import ClassVar, Optional, Callable
 import app
 
 
@@ -18,14 +18,16 @@ def role_required(role: str, opt: str):
     return required
 
 
-def form_required(form: ClassVar[FlaskForm], opt: str):
+def form_required(form: ClassVar[FlaskForm], opt: str, callback: Optional[Callable] = None, **kw):
     def required(func):
         @wraps(func)
         def new_func(*args, **kwargs):
             f = form()
             if not f.validate_on_submit():
                 app.HBlogFlask.print_form_error_log(opt)
-                return abort(404)
+                if callback is None:
+                    return abort(404)
+                return callback(form=f, **kw, **kwargs)
             g.form = f
             return func(*args, **kwargs)
         return new_func

+ 3 - 8
templates/archive/archive.html

@@ -15,14 +15,9 @@
                     <div class="create">
                         <form action="{{ url_for('archive.create_archive_page') }}" method="post">
                             {{ form.hidden_tag() }}
-                            <div class="form-group text-left">
-                                {{ form.name.label }}
-                                {{ form.name(class="form-control") }}
-                            </div>
-
-                            <div class="form-group text-left">
-                                {{ form.describe.label }}
-                                {{ form.describe(class="form-control") }}
+                            <div class="text-left">
+                                {{ macro.render_field(form.name) }}
+                                {{ macro.render_field(form.describe) }}
                             </div>
 
                             <div id="CreateModal" class="modal fade" role="dialog" aria-hidden="true">

+ 1 - 5
templates/auth/delete.html

@@ -13,11 +13,7 @@
             <div class="col-12 col-lg-6 offset-lg-3">
                 <form method="post" action="{{ url_for("auth.delete_user_page") }}" class="delete-form">
                     {{ DeleteUserForm.hidden_tag() }}
-
-                    <div class="form-group">
-                        {{ DeleteUserForm.email.label }}
-                        {{ DeleteUserForm.email(class="form-control") }}
-                    </div>
+                    {{ macro.render_field(DeleteUserForm.email) }}
 
                     <div id="DeleteModal" class="modal fade" role="dialog" aria-hidden="true">
                         <div class="modal-dialog">

+ 2 - 11
templates/auth/login.html

@@ -13,17 +13,8 @@
         <div class="col-12 col-lg-6 offset-lg-3">
             <form method="post" action="{{ url_for("auth.login_page") }}" class="login-form">
                 {{ form.hidden_tag() }}
-
-                <div class="form-group">
-                    {{ form.email.label }}
-                    {{ form.email(class="form-control") }}
-                </div>
-
-                <div class="form-group">
-                    {{ form.passwd.label }}
-                    {{ form.passwd(class="form-control") }}
-                </div>
-
+                {{ macro.render_field(form.email) }}
+                {{ macro.render_field(form.passwd) }}
                 <div class="text-right">
                     <a class="btn btn-outline-secondary mr-2" href="{{ url_for("auth.register_page") }}"> 前往注册 </a>
                     {{ form.submit(class='btn btn-success mr-2') }}

+ 3 - 15
templates/auth/passwd.html

@@ -13,21 +13,9 @@
         <div class="col-12 col-lg-6 offset-lg-3">
             <form method="post" action="{{ url_for("auth.change_passwd_page") }}" class="passwd-form">
                 {{ ChangePasswdForm.hidden_tag() }}
-
-                <div class="form-group">
-                    {{ ChangePasswdForm.old_passwd.label }}
-                    {{ ChangePasswdForm.old_passwd(class="form-control") }}
-                </div>
-
-                <div class="form-group">
-                    {{ ChangePasswdForm.passwd.label }}
-                    {{ ChangePasswdForm.passwd(class="form-control") }}
-                </div>
-
-                <div class="form-group">
-                    {{ ChangePasswdForm.passwd_again.label }}
-                    {{ ChangePasswdForm.passwd_again(class="form-control") }}
-                </div>
+                {{ macro.render_field(ChangePasswdForm.old_passwd) }}
+                {{ macro.render_field(ChangePasswdForm.passwd) }}
+                {{ macro.render_field(ChangePasswdForm.passwd_again) }}
 
                 <div class="text-right">
                     {{ ChangePasswdForm.submit(class='btn btn-outline-danger') }}

+ 3 - 16
templates/auth/register.html

@@ -13,22 +13,9 @@
         <div class="col-12 col-lg-6 offset-lg-3">
             <form method="post" action="{{ url_for("auth.register_page") }}" class="register-form">
                 {{ RegisterForm.hidden_tag() }}
-
-                <div class="form-group">
-                    {{ RegisterForm.email.label }}
-                    {{ RegisterForm.email(class="form-control") }}
-                </div>
-
-                <div class="form-group">
-                    {{ RegisterForm.passwd.label }}
-                    {{ RegisterForm.passwd(class="form-control") }}
-                </div>
-
-                <div class="form-group">
-                    {{ RegisterForm.passwd_again.label }}
-                    {{ RegisterForm.passwd_again(class="form-control") }}
-                </div>
-
+                {{ macro.render_field(RegisterForm.email) }}
+                {{ macro.render_field(RegisterForm.passwd) }}
+                {{ macro.render_field(RegisterForm.passwd_again) }}
                 <div class="text-right">
                     {{ RegisterForm.submit(class='btn btn-success mr-2') }}
                     <a class="btn btn-outline-secondary" href="{{ url_for("auth.login_page") }}"> 前往登录 </a>

+ 5 - 25
templates/auth/role.html

@@ -28,16 +28,8 @@
                         <div class="tab-pane fade active" id="create">
                             <form method="post" action="{{ url_for('auth.role_create_page') }}" class="role-form">
                                 {{ CreateRoleForm.hidden_tag() }}
-
-                                <div class="form-group">
-                                    {{ CreateRoleForm.name.label }}
-                                    {{ CreateRoleForm.name(class="form-control") }}
-                                </div>
-
-                                <div class="form-group">
-                                    {{ CreateRoleForm.authority.label }}
-                                    {{ CreateRoleForm.authority(class="form-control") }}
-                                </div>
+                                {{ macro.render_field(CreateRoleForm.name) }}
+                                {{ macro.render_field(CreateRoleForm.authority) }}
 
                                 <div id="CreateModal" class="modal fade" role="dialog" aria-hidden="true">
                                     <div class="modal-dialog">
@@ -67,11 +59,7 @@
                         <div class="tab-pane fade" id="drop">
                             <form method="post" action="{{ url_for('auth.role_delete_page') }}" class="role-form">
                                 {{ DeleteRoleForm.hidden_tag() }}
-
-                                <div class="form-group">
-                                    {{ DeleteRoleForm.name.label }}
-                                    {{ DeleteRoleForm.name(class="form-control") }}
-                                </div>
+                                {{ macro.render_field(DeleteRoleForm.name) }}
 
                                 <div id="DeleteModal" class="modal fade" role="dialog" aria-hidden="true">
                                     <div class="modal-dialog">
@@ -101,16 +89,8 @@
                         <div class="tab-pane fade" id="set">
                             <form method="post" action="{{ url_for('auth.role_set_page') }}" class="role-form">
                                 {{ SetRoleForm.hidden_tag() }}
-
-                                <div class="form-group">
-                                    {{ SetRoleForm.email.label }}
-                                    {{ SetRoleForm.email(class="form-control") }}
-                                </div>
-
-                                <div class="form-group">
-                                    {{ SetRoleForm.name.label }}
-                                    {{ SetRoleForm.name(class="form-control") }}
-                                </div>
+                                {{ macro.render_field(SetRoleForm.email) }}
+                                {{ macro.render_field(SetRoleForm.name) }}
 
                                 <div id="SetModal" class="modal fade" role="dialog" aria-hidden="true">
                                     <div class="modal-dialog">

+ 2 - 0
templates/base.html

@@ -1,3 +1,5 @@
+{% import "macro.html" as macro %}
+
 <!DOCTYPE html>
 <html lang="zh">
 <head>

+ 4 - 3
templates/docx/article.html

@@ -1,5 +1,4 @@
 {% extends "base.html" %}
-{% import "macro.html" as macro %}
 
 {% block title %} 文档 {% endblock %}
 
@@ -36,10 +35,12 @@
                     <h1 class="mt-3"> 评论 </h1>
 
                     <section class="col-12 text-right">
-                        <form action="{{ url_for('docx.comment_page', blog=article.blog_id) }}" method="post">
+                        <form action="{{ url_for('docx.comment_page', blog_id=article.blog_id) }}" method="post">
                             {{ form.hidden_tag() }}
                             {{ form.context(class="form-control mb-2", rows="3") }}
-
+                            {% for error in form.context.errors %}
+                                <small class="text-danger form-text text-left"> {{ error }} </small>
+                            {% endfor %}
 
                             <div id="CommentModal" class="modal fade" role="dialog" aria-hidden="true">
                                 <div class="modal-dialog">

+ 7 - 17
templates/docx/docx.html

@@ -1,5 +1,4 @@
 {% extends "base.html" %}
-{% import "macro.html" as macro %}
 
 {% block title %} 博客 {% endblock %}
 
@@ -16,26 +15,17 @@
             <div class="row">
                 <div class="col-12">
                     <div class="markdown">
-                        <form action="{{ url_for('docx.create_docx_page') }}" method="post">
+                        <form action="{{ url_for('docx.create_docx_page', page=page) }}" method="post">
                             {{ form.hidden_tag() }}
-                            <div class="form-group">
-                                {{ form.title.label }}
-                                {{ form.title(class="form-control") }}
-                            </div>
-
-                            <div class="form-group">
-                                {{ form.subtitle.label }}
-                                {{ form.subtitle(class="form-control") }}
-                            </div>
-
-                            <div class="form-group">
-                                {{ form.archive.label }}
-                                {{ form.archive(class="form-control") }}
-                            </div>
-
+                            {{ macro.render_field(form.title) }}
+                            {{ macro.render_field(form.subtitle) }}
+                            {{ macro.render_field(form.archive) }}
                             <div id="editor">
                                 {{ form.context(class="form-control mb-2", style="display:none;") }}
                             </div>
+                            {% for error in form.context.errors %}
+                                <small class="text-danger form-text"> {{ error }} </small>
+                            {% endfor %}
 
                             <div id="CreateModal" class="modal fade" role="dialog" aria-hidden="true">
                                 <div class="modal-dialog">

+ 10 - 0
templates/macro.html

@@ -7,3 +7,13 @@
         {% endif %}
     {% endfor %}
 {% endmacro %}
+
+{% macro render_field(field) %}
+    <div class="form-group">
+        {{ field.label }}
+        {{ field(class="form-control", **kwargs) | safe }}
+        {% for error in field.errors %}
+            <small class="text-danger form-text"> {{ error }} </small>
+        {% endfor %}
+    </div>
+{% endmacro %}

+ 4 - 2
templates/msg/msg.html

@@ -1,5 +1,4 @@
 {% extends "base.html" %}
-{% import "macro.html" as macro %}
 
 {% block title %} 留言 {% endblock %}
 
@@ -12,9 +11,12 @@
     <section id="base" class="container mt-3">
         <div class="row">
             <section class="col-12 text-right">
-                <form class="writer clearfix" action="{{ url_for('msg.write_msg_page') }}" method="post">
+                <form class="writer clearfix" action="{{ url_for('msg.write_msg_page', page=page) }}" method="post">
                     {{ form.hidden_tag() }}
                     {{ form.context(class="form-control mb-2", rows="5") }}
+                    {% for error in form.context.errors %}
+                        <small class="text-danger form-text text-left"> {{ error }} </small>
+                    {% endfor %}
                     {{ form.secret() }} {{ form.secret.label }}
 
                     <div id="MsgModal" class="modal fade" role="dialog" aria-hidden="true">

+ 4 - 5
templates/oss/upload.html

@@ -13,14 +13,13 @@
         <div class="col-12 col-lg-6 offset-lg-3">
             <form method="post" action="#" class="upload-form" enctype="multipart/form-data">
                 {{ UploadForm.hidden_tag() }}
-
-                <div class="form-group pb-2">
-                    {{ UploadForm.path.label }}
-                    {{ UploadForm.path(class="form-control", value="hblog/") }}
-                </div>
+                {{ macro.render_field(UploadForm.path) }}
 
                 <div class="form-group pb-2">
                     {{ UploadForm.file(class="form-control") }}
+                    {% for error in UploadForm.file.errors %}
+                        <small class="text-danger form-text"> {{ error }} </small>
+                    {% endfor %}
                 </div>
 
                 <div class="text-right">