docx.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. from flask import Blueprint, render_template, abort, redirect, url_for, flash, make_response, g, request
  2. from flask_wtf import FlaskForm
  3. from flask_login import login_required, current_user
  4. from wtforms import HiddenField, TextAreaField, StringField, SelectMultipleField, SubmitField, ValidationError
  5. from wtforms.validators import DataRequired, Length
  6. from typing import Optional
  7. import app
  8. from sql.base import DBBit
  9. from sql.statistics import add_blog_click, add_archive_click
  10. from object.blog import BlogArticle
  11. from object.comment import Comment
  12. from object.archive import Archive
  13. from configure import conf
  14. docx = Blueprint("docx", __name__)
  15. class EditorMD(FlaskForm):
  16. content = TextAreaField("博客内容", validators=[DataRequired(message="必须输入博客文章")])
  17. class WriteBlogForm(EditorMD):
  18. title = StringField("标题", description="博文主标题",
  19. validators=[
  20. DataRequired(message="必须填写标题"),
  21. Length(1, 20, message="标题长度1-20个字符")])
  22. subtitle = StringField("副标题", description="博文副标题",
  23. validators=[Length(-1, 20, message="副标题长度20个字符以内")])
  24. archive = SelectMultipleField("归档", coerce=int)
  25. submit = SubmitField("提交博客")
  26. def __init__(self, default: bool = False, **kwargs):
  27. super().__init__(**kwargs)
  28. if default:
  29. self.content.data = "# Blog Title\n## Blog subtitle\nHello, World"
  30. archive = Archive.get_archive_list()
  31. self.archive_res = []
  32. self.archive_choices = [(-1, "None")]
  33. for i in archive:
  34. self.archive_res.append(i.id)
  35. self.archive_choices.append((i.id, f"{i.name} ({i.count})"))
  36. self.archive.choices = self.archive_choices
  37. def validate_archive(self, field):
  38. if -1 in field.data:
  39. if len(field.data) != 1:
  40. raise ValidationError("归档指定错误(none归档不能和其他归档同时被指定)")
  41. else:
  42. for i in field.data:
  43. if i not in self.archive_res:
  44. raise ValidationError("错误的归档被指定")
  45. class UpdateBlogForm(EditorMD):
  46. blog_id = HiddenField("ID", validators=[DataRequired()])
  47. submit = SubmitField("更新博客")
  48. def __init__(self, blog: Optional[BlogArticle] = None, **kwargs):
  49. super().__init__(**kwargs)
  50. if blog is not None:
  51. self.blog_id.data = blog.id
  52. self.content.data = blog.content
  53. class UpdateBlogArchiveForm(FlaskForm):
  54. blog_id = HiddenField("ID", validators=[DataRequired()])
  55. archive = SelectMultipleField("归档", coerce=int)
  56. add = SubmitField("加入归档")
  57. sub = SubmitField("去除归档")
  58. def __init__(self, blog: Optional[BlogArticle] = None, **kwargs):
  59. super().__init__(**kwargs)
  60. archive = Archive.get_archive_list()
  61. self.archive_res = []
  62. self.archive_choices = []
  63. for i in archive:
  64. archive_id = i.id
  65. self.archive_res.append(archive_id)
  66. self.archive_choices.append((archive_id, f"{i.name} ({i.count})"))
  67. self.archive.choices = self.archive_choices
  68. if blog is not None:
  69. self.archive_data = []
  70. self.archive.data = self.archive_data
  71. for a in blog.archive:
  72. a: Archive
  73. self.archive_data.append(a)
  74. self.blog_id.data = blog.id
  75. def validate_archive(self, field):
  76. for i in field.data:
  77. if i not in self.archive_res:
  78. raise ValidationError("错误的归档被指定")
  79. class WriteCommentForm(FlaskForm):
  80. content = TextAreaField("", description="评论正文",
  81. validators=[DataRequired(message="请输入评论的内容"),
  82. Length(1, 100, message="请输入1-100个字的评论")])
  83. submit = SubmitField("评论")
  84. def __load_docx_page(page: int, form: WriteBlogForm):
  85. if page < 1:
  86. app.HBlogFlask.print_user_opt_fail_log(f"Load docx list with error page({page})")
  87. abort(404)
  88. return
  89. blog_list = BlogArticle.get_blog_list(limit=20, offset=(page - 1) * 20)
  90. max_page = app.HBlogFlask.get_max_page(BlogArticle.get_blog_count(), 20)
  91. page_list = app.HBlogFlask.get_page("docx.docx_page", page, max_page)
  92. app.HBlogFlask.print_load_page_log(f"docx list (page: {page})")
  93. return render_template("docx/docx.html",
  94. page=page,
  95. cache_str=f":{page}",
  96. blog_list=blog_list,
  97. page_list=page_list,
  98. form=form,
  99. show_delete=current_user.check_role("DeleteBlog"))
  100. @docx.route('/')
  101. def docx_page():
  102. page = int(request.args.get("page", 1))
  103. return __load_docx_page(page, WriteBlogForm(True))
  104. @docx.route('/archive')
  105. def archive_page():
  106. page = int(request.args.get("page", 1))
  107. archive = int(request.args.get("archive", 1))
  108. if page < 1:
  109. app.HBlogFlask.print_user_opt_fail_log(f"Load archive-docx list with error page({page}) archive: {archive}")
  110. abort(404)
  111. return
  112. blog_list = BlogArticle.get_blog_list(archive_id=archive, limit=20, offset=(page - 1) * 20)
  113. max_page = app.HBlogFlask.get_max_page(BlogArticle.get_blog_count(archive_id=archive), 20)
  114. page_list = app.HBlogFlask.get_page("docx.archive_page", page, max_page)
  115. add_archive_click(archive)
  116. app.HBlogFlask.print_load_page_log(f"archive-docx list (archive-id: {archive} page: {page})")
  117. return render_template("docx/docx.html",
  118. page=page,
  119. cache_str=f":{page}",
  120. blog_list=blog_list,
  121. is_top=DBBit.BIT_1,
  122. page_list=page_list,
  123. form=None)
  124. def __load_article_page(blog_id: int, form: WriteCommentForm,
  125. view: Optional[UpdateBlogForm] = None,
  126. archive: Optional[UpdateBlogArchiveForm] = None):
  127. article = BlogArticle(blog_id)
  128. if article is None:
  129. app.HBlogFlask.print_user_opt_fail_log(f"Load article with error id({blog_id})")
  130. abort(404)
  131. return
  132. app.HBlogFlask.print_load_page_log(f"article (id: {blog_id})")
  133. if view is None:
  134. view = UpdateBlogForm(article)
  135. if archive is None:
  136. archive = UpdateBlogArchiveForm(article)
  137. add_blog_click(article.id)
  138. return render_template("docx/article.html",
  139. article=article,
  140. cache_str=f":{article.id}",
  141. archive_list=article.archive,
  142. form=form,
  143. view=view,
  144. archive=archive,
  145. can_update=current_user.check_role("WriteBlog"),
  146. show_delete=current_user.check_role("DeleteComment"),
  147. show_email=current_user.check_role("ReadUserInfo"))
  148. @docx.route('/article')
  149. def article_page():
  150. blog_id = int(request.args.get("blog", 1))
  151. return __load_article_page(blog_id, WriteCommentForm())
  152. @docx.route('/article/download')
  153. def article_down_page():
  154. blog_id = int(request.args.get("blog", 1))
  155. article = BlogArticle(blog_id)
  156. if article is None:
  157. app.HBlogFlask.print_user_opt_fail_log(f"Download article with error id({blog_id})")
  158. abort(404)
  159. return
  160. response = make_response(article.content)
  161. response.headers["Content-Disposition"] = f"attachment;filename={article.title.encode().decode('latin-1')}.md"
  162. app.HBlogFlask.print_load_page_log(f"download article (id: {blog_id})")
  163. return response
  164. @docx.route('/article/create', methods=["POST"])
  165. @login_required
  166. @app.form_required(WriteBlogForm, "write blog", lambda form: __load_docx_page(int(request.args.get("page", 1)), form))
  167. @app.role_required("WriteBlog", "write blog")
  168. def create_docx_page():
  169. form: WriteBlogForm = g.form
  170. title = form.title.data
  171. subtitle = form.subtitle.data
  172. archive = []
  173. if -1 not in form.archive.data:
  174. for i in form.archive.data:
  175. i = Archive(i)
  176. if i is not None:
  177. archive.append(i)
  178. if BlogArticle.create(title, subtitle, form.content.data, archive, current_user):
  179. app.HBlogFlask.print_sys_opt_success_log("write blog")
  180. flash(f"博客 {title} 发表成功")
  181. else:
  182. app.HBlogFlask.print_sys_opt_fail_log("write blog")
  183. flash(f"博客 {title} 发表失败")
  184. return redirect(url_for("docx.docx_page", page=1))
  185. @docx.route('/article/update', methods=["POST"])
  186. @login_required
  187. @app.form_required(UpdateBlogForm, "update blog",
  188. lambda form: __load_article_page(form.id.data, WriteCommentForm(), form))
  189. @app.role_required("WriteBlog", "write blog")
  190. def update_docx_page():
  191. form: UpdateBlogForm = g.form
  192. if BlogArticle(form.blog_id.data).update(form.content.data):
  193. app.HBlogFlask.print_sys_opt_success_log("update blog")
  194. flash("博文更新成功")
  195. else:
  196. app.HBlogFlask.print_sys_opt_fail_log("update blog")
  197. flash("博文更新失败")
  198. return redirect(url_for("docx.article_page", blog=form.blog_id.data))
  199. @docx.route("/article/delete")
  200. @login_required
  201. @app.role_required("DeleteBlog", "delete blog")
  202. def delete_blog_page():
  203. blog_id = int(request.args.get("blog", -1))
  204. if blog_id == -1:
  205. return abort(400)
  206. if BlogArticle(blog_id).delete():
  207. app.HBlogFlask.print_sys_opt_success_log("delete blog")
  208. flash("博文删除成功")
  209. else:
  210. app.HBlogFlask.print_sys_opt_fail_log("delete blog")
  211. flash("博文删除失败")
  212. return redirect(url_for("docx.docx_page", page=1))
  213. @docx.route("/article/set/top")
  214. @login_required
  215. @app.role_required("WriteBlog", "set blog top")
  216. def set_blog_top_page():
  217. blog_id = int(request.args.get("blog", -1))
  218. top = request.args.get("top", '0') != '0'
  219. if blog_id == -1:
  220. return abort(400)
  221. blog = BlogArticle(blog_id)
  222. blog.top = top
  223. if top == blog.top:
  224. app.HBlogFlask.print_sys_opt_success_log(f"set blog top ({top})")
  225. flash(f"博文{'取消' if not top else ''}置顶成功")
  226. else:
  227. app.HBlogFlask.print_sys_opt_fail_log(f"set blog top ({top})")
  228. flash(f"博文{'取消' if not top else ''}置顶失败")
  229. return redirect(url_for("docx.article_page", blog=blog_id))
  230. @docx.route("/article/set/archive", methods=["POST"])
  231. @login_required
  232. @app.form_required(UpdateBlogArchiveForm, "update archive",
  233. lambda form: __load_article_page(form.id.data, WriteCommentForm(), UpdateBlogForm(), form))
  234. @app.role_required("WriteBlog", "update archive")
  235. def update_archive_page():
  236. form: UpdateBlogArchiveForm = g.form
  237. article = BlogArticle(form.blog_id.data)
  238. add = request.args.get("add", '0') != '0'
  239. for i in form.archive.data:
  240. if add:
  241. article.add_to_archive(i)
  242. else:
  243. article.sub_from_archive(i)
  244. flash("归档设定完成")
  245. return redirect(url_for("docx.article_page", blog=form.blog_id.data))
  246. @docx.route('/comment/create', methods=["POST"])
  247. @login_required
  248. @app.form_required(WriteCommentForm, "write comment",
  249. lambda form: __load_article_page(int(request.args.get("blog", 1)), form))
  250. @app.role_required("WriteComment", "write comment")
  251. def comment_page():
  252. blog_id = int(request.args.get("blog", 1))
  253. form: WriteCommentForm = g.form
  254. content = form.content.data
  255. if Comment.create(BlogArticle(blog_id), current_user, content):
  256. app.HBlogFlask.print_user_opt_success_log("comment")
  257. flash("评论成功")
  258. else:
  259. app.HBlogFlask.print_user_opt_error_log("comment")
  260. flash("评论失败")
  261. return redirect(url_for("docx.article_page", blog=blog_id))
  262. @docx.route("/comment/delete")
  263. @login_required
  264. @app.role_required("DeleteComment", "delete comment")
  265. def delete_comment_page():
  266. comment_id = int(request.args.get("comment", 1))
  267. if Comment(comment_id).delete():
  268. app.HBlogFlask.print_sys_opt_success_log("delete comment")
  269. flash("博文评论成功")
  270. else:
  271. app.HBlogFlask.print_sys_opt_fail_log("delete comment")
  272. flash("博文评论失败")
  273. return redirect(url_for("docx.docx_page", page=1))
  274. @docx.context_processor
  275. @app.cache.cached(timeout=conf["CACHE_EXPIRE"], key_prefix="inject_base:docx")
  276. def inject_base():
  277. """ docx 默认模板变量 """
  278. return {"top_nav": ["", "", "active", "", "", ""]}