Просмотр исходного кода

feat: 新增博文修改功能

SongZihuan 3 лет назад
Родитель
Сommit
30ce518b85
5 измененных файлов с 138 добавлено и 30 удалено
  1. 44 6
      app/docx.py
  2. 7 0
      object/blog.py
  3. 15 4
      sql/blog.py
  4. 8 5
      sql/mysql.py
  5. 64 15
      templates/docx/article.html

+ 44 - 6
app/docx.py

@@ -1,8 +1,9 @@
 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, ValidationError
+from wtforms import HiddenField, TextAreaField, StringField, SelectMultipleField, SubmitField, ValidationError
 from wtforms.validators import DataRequired, Length
+from typing import Optional
 
 import app
 from sql.base import DBBit
@@ -13,7 +14,11 @@ from object.archive import load_archive_by_id, Archive
 docx = Blueprint("docx", __name__)
 
 
-class WriteBlogForm(FlaskForm):
+class EditorMD(FlaskForm):
+    context = TextAreaField("博客内容", validators=[DataRequired(message="必须输入博客文章")])
+
+
+class WriteBlogForm(EditorMD):
     title = StringField("标题", description="博文主标题",
                         validators=[
                             DataRequired(message="必须填写标题"),
@@ -21,7 +26,6 @@ class WriteBlogForm(FlaskForm):
     subtitle = StringField("副标题", description="博文副标题",
                            validators=[Length(-1, 20, message="副标题长度20个字符以内")])
     archive = SelectMultipleField("归档", coerce=int)
-    context = TextAreaField("博客内容", validators=[DataRequired()])
     submit = SubmitField("提交博客")
 
     def __init__(self, **kwargs):
@@ -45,6 +49,17 @@ class WriteBlogForm(FlaskForm):
                     raise ValidationError("错误的归档被指定")
 
 
+class UpdateBlogForm(EditorMD):
+    blog_id = HiddenField("ID", validators=[DataRequired()])
+    submit = SubmitField("更新博客")
+
+    def __init__(self, blog: Optional[BlogArticle] = None, **kwargs):
+        super().__init__(**kwargs)
+        if blog is not None:
+            self.blog_id.data = blog.blog_id
+            self.context.data = blog.context
+
+
 class WriteCommentForm(FlaskForm):
     context = TextAreaField("", description="评论正文",
                             validators=[DataRequired(message="请输入评论的内容"),
@@ -97,17 +112,21 @@ def archive_page():
                            form=None)
 
 
-def __load_article_page(blog_id: int, form: WriteCommentForm):
+def __load_article_page(blog_id: int, form: WriteCommentForm, view: Optional[UpdateBlogForm] = None):
     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})")
         abort(404)
         return
     app.HBlogFlask.print_load_page_log(f"article (id: {blog_id})")
+    if view is None:
+        view = UpdateBlogForm(article)
     return render_template("docx/article.html",
                            article=article,
                            archive_list=article.archive,
                            form=form,
+                           view=view,
+                           can_update=current_user.check_role("WriteBlog"),
                            show_delete=current_user.check_role("DeleteComment"),
                            show_email=current_user.check_role("ReadUserInfo"))
 
@@ -157,11 +176,29 @@ def create_docx_page():
     return redirect(url_for("docx.docx_page", page=1))
 
 
+@docx.route('/article/update', methods=["POST"])
+@login_required
+@app.form_required(UpdateBlogForm, "update blog",
+                   lambda form: __load_article_page(int(request.args.get("blog", 1)), WriteCommentForm(), form))
+@app.role_required("WriteBlog", "write blog")
+def update_docx_page():
+    form: UpdateBlogForm = g.form
+    if BlogArticle(form.blog_id.data, None, None, None, None).update(form.context.data):
+        app.HBlogFlask.print_sys_opt_success_log("update blog")
+        flash("博文更新成功")
+    else:
+        app.HBlogFlask.print_sys_opt_fail_log("update blog")
+        flash("博文更新失败")
+    return redirect(url_for("docx.docx_page", page=1))
+
+
 @docx.route("/article/delete")
 @login_required
 @app.role_required("DeleteBlog", "delete blog")
 def delete_blog_page():
-    blog_id = int(request.args.get("blog", 1))
+    blog_id = int(request.args.get("blog", -1))
+    if blog_id == -1:
+        return abort(400)
     if BlogArticle(blog_id, None, None, None, None).delete():
         app.HBlogFlask.print_sys_opt_success_log("delete blog")
         flash("博文删除成功")
@@ -173,7 +210,8 @@ def delete_blog_page():
 
 @docx.route('/comment/create', methods=["POST"])
 @login_required
-@app.form_required(WriteCommentForm, "write comment", __load_article_page)
+@app.form_required(WriteCommentForm, "write comment",
+                   lambda form: __load_article_page(int(request.args.get("blog", 1)), form))
 @app.role_required("WriteComment", "write comment")
 def comment_page():
     blog_id = int(request.args.get("blog", 1))

+ 7 - 0
object/blog.py

@@ -6,6 +6,7 @@ from sql.blog import (get_blog_list,
                       get_archive_blog_count,
                       get_blog_list_not_top,
                       read_blog,
+                      update_blog,
                       create_blog,
                       delete_blog,
                       get_user_user_count)
@@ -73,3 +74,9 @@ class BlogArticle:
 
     def delete(self):
         return delete_blog(self.blog_id)
+
+    def update(self, context: str):
+        if update_blog(self.blog_id, context):
+            self.context = context
+            return True
+        return False

+ 15 - 4
sql/blog.py

@@ -3,8 +3,9 @@ from typing import Optional, List
 import object.archive
 
 
-def create_blog(auth_id: int, title: str, subtitle: str, context: str, archive_list: List[object.archive.Archive]) -> bool:
-    """写入新的blog"""
+def create_blog(auth_id: int, title: str, subtitle: str, context: str,
+                archive_list: List[object.archive.Archive]) -> bool:
+    """ 写入新的blog """
     cur = db.insert(table="blog", columns=["Auth", "Title", "SubTitle", "Context"],
                     values=f"{auth_id}, '{title}', '{subtitle}', '{context}'")
     if cur is None or cur.rowcount == 0:
@@ -18,8 +19,18 @@ def create_blog(auth_id: int, title: str, subtitle: str, context: str, archive_l
     return True
 
 
+def update_blog(blog_id: int, context: str) -> bool:
+    """ 更新博客文章 """
+    cur = db.update(table="blog",
+                    kw={"UpdateTime": "CURRENT_TIMESTAMP()", "Context": f"'{context}'"},
+                    where=f"ID={blog_id}")
+    if cur is None or cur.rowcount != 1:
+        return False
+    return True
+
+
 def read_blog(blog_id: int) -> list:
-    """读取blog内容"""
+    """ 读取blog内容 """
     cur = db.search(columns=["Auth", "Title", "SubTitle", "Context", "UpdateTime", "Top"],
                     table="blog",
                     where=f"ID={blog_id}")
@@ -42,7 +53,7 @@ def delete_blog(blog_id: int):
 
 
 def get_blog_list(limit: Optional[int] = None, offset: Optional[int] = None) -> list:
-    """获得 blog 列表"""
+    """ 获得 blog 列表 """
     cur = db.search(columns=["ID", "Title", "SubTitle", "UpdateTime", "Top"], table="blog_with_top",
                     limit=limit,
                     offset=offset)

+ 8 - 5
sql/mysql.py

@@ -1,6 +1,6 @@
+import pymysql.cursors
 import pymysql
 import threading
-import traceback
 from sql.base import Database, DBException, DBCloseException
 from typing import Optional, Union, List, Tuple, Dict
 
@@ -52,7 +52,7 @@ class MysqlDB(Database):
                offset: Optional[int] = None,
                order_by: Optional[List[Tuple[str, str]]] = None,
                group_by: Optional[List[str]] = None,
-               for_update: bool = False):
+               for_update: bool = False) -> Union[None, pymysql.cursors.Cursor]:
         if type(where) is list and len(where) > 0:
             where: str = " WHERE " + " AND ".join(f"({w})" for w in where)
         elif type(where) is str and len(where) > 0:
@@ -90,7 +90,8 @@ class MysqlDB(Database):
                              f"FROM {table} "
                              f"{where} {group_by} {order_by} {limit} {offset} {for_update};")
 
-    def insert(self, table: str, columns: list, values: Union[str, List[str]], not_commit: bool = False):
+    def insert(self, table: str, columns: list, values: Union[str, List[str]],
+               not_commit: bool = False) -> Union[None, pymysql.cursors.Cursor]:
         columns: str = ", ".join(columns)
         if type(values) is str:
             values: str = f"({values})"
@@ -98,7 +99,8 @@ class MysqlDB(Database):
             values: str = ", ".join(f"{v}" for v in values)
         return self.__done(f"INSERT INTO {table}({columns}) VALUES {values};", not_commit=not_commit)
 
-    def delete(self, table: str, where: Union[str, List[str]] = None, not_commit: bool = False):
+    def delete(self, table: str, where: Union[str, List[str]] = None,
+               not_commit: bool = False) -> Union[None, pymysql.cursors.Cursor]:
         if type(where) is list and len(where) > 0:
             where: str = " AND ".join(f"({w})" for w in where)
         elif type(where) is not str or len(where) == 0:  # 必须指定条件
@@ -106,7 +108,8 @@ class MysqlDB(Database):
 
         return self.__done(f"DELETE FROM {table} WHERE {where};", not_commit=not_commit)
 
-    def update(self, table: str, kw: "Dict[str:str]", where: Union[str, List[str]] = None, not_commit: bool = False):
+    def update(self, table: str, kw: "Dict[str:str]", where: Union[str, List[str]] = None,
+               not_commit: bool = False) -> Union[None, pymysql.cursors.Cursor]:
         if len(kw) == 0:
             return None
 

+ 64 - 15
templates/docx/article.html

@@ -21,16 +21,47 @@
                     <a href="{{ url_for('docx.article_down_page', blog=article.blog_id) }}"> 下载 </a>
                     <hr>
 
-                    <div id="markdown-view">
-                        {# 不要插入label #}
-                        <textarea style="display:none;"> {{ article.context }} </textarea>
-                    </div>
+                    <form method="post" action="{{ url_for('docx.update_docx_page') }}">
+                        {% if can_update %}
+                            {{ view.hidden_tag() }}
+                            {{ view.blog_id() }}
+                        {% endif %}
+                        <div id="markdown-view">
+                            {{ view.context(class="form-control mb-2", style="display:none;") }}
+                        </div>
+                        {% for error in view.context.errors %}
+                            <small class="text-danger form-text"> {{ error }} </small>
+                        {% endfor %}
+
+                        {% if can_update %}
+                            <div id="UpdateModal" class="modal fade" role="dialog" aria-hidden="true">
+                                <div class="modal-dialog">
+                                    <div class="modal-content text-left">
+                                        <div class="modal-header">
+                                            <h4 class="modal-title"> 确认更新博文吗? </h4>
+                                        </div>
+                                        <div class="modal-body">
+                                            <p> 是否确认更新博文?请注意校对文本。 </p>
+                                        </div>
+                                        <div class="modal-footer">
+                                            {{ view.submit(class="btn btn-danger", value="确认") }}
+                                            <button type="button" class="btn btn-outline-dark" data-dismiss="modal">取消</button>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+
+                            <div class="text-right">
+                                <button type="button" class="btn btn-primary mb-2" data-toggle="modal" data-target="#UpdateModal"> 更新博文 </button>
+                            </div>
+                        {% endif %}
+                    </form>
                 </article>
             </div>
         {% endif %}
 
         {% if current_user.check_role("ReadComment") %}
-            <div class="row">
+            <div id="CommentList" class="row">
                 <article class="col-12">
                     <h1 class="mt-3"> 评论 </h1>
 
@@ -119,16 +150,34 @@
     <script src="{{ url_for('static', filename='editor.md/lib/marked.min.js') }}"></script>
     <script src="{{ url_for('static', filename='editor.md/lib/prettify.min.js') }}"></script>
     <script type="text/javascript">
-        $(function() {
-            let editor = editormd.markdownToHTML("markdown-view", {
-                path: "{{ url_for('static',filename='editor.md/lib/') }}",
-                saveHTMLToTextarea: true,
-                emoji: true,
-                taskList: true,
-                tex: true,
-                flowChart: true,
-                sequenceDiagram: true,
+        {% if can_update %}
+            $(function() {
+                let editor = editormd("markdown-view", {
+                    height: document.documentElement.clientHeight * 0.5,
+                    path: "{{ url_for('static',filename='editor.md/lib/') }}",
+                    placeholder: "请写下你的日志...",
+                    saveHTMLToTextarea: true,
+                    emoji: true,
+                    taskList: true,
+                    tex: true,
+                    flowChart: true,
+                    sequenceDiagram: true,
+                    onfullscreen() {document.getElementById("CommentList").style.visibility = 'hidden';},
+                    onfullscreenExit() {document.getElementById("CommentList").style.visibility = 'visible';},
+                });
             });
-        });
+        {% else %}
+            $(function() {
+                let editor = editormd.markdownToHTML("markdown-view", {
+                    path: "{{ url_for('static',filename='editor.md/lib/') }}",
+                    saveHTMLToTextarea: true,
+                    emoji: true,
+                    taskList: true,
+                    tex: true,
+                    flowChart: true,
+                    sequenceDiagram: true,
+                });
+            });
+        {% endif %}
     </script>
 {% endblock %}