docx.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  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 = request.args.get("page", 1, type=int)
  103. return __load_docx_page(page, WriteBlogForm(True))
  104. @docx.route('/archive')
  105. def archive_page():
  106. page = request.args.get("page", 1, type=int)
  107. archive = request.args.get("archive", 1, type=int)
  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. page_list=page_list,
  122. form=None)
  123. def __load_article_page(blog_id: int, form: WriteCommentForm,
  124. view: Optional[UpdateBlogForm] = None,
  125. archive: Optional[UpdateBlogArchiveForm] = None):
  126. article = BlogArticle(blog_id)
  127. if article is None:
  128. app.HBlogFlask.print_user_opt_fail_log(f"Load article with error id({blog_id})")
  129. abort(404)
  130. return
  131. app.HBlogFlask.print_load_page_log(f"article (id: {blog_id})")
  132. if view is None:
  133. view = UpdateBlogForm(article)
  134. if archive is None:
  135. archive = UpdateBlogArchiveForm(article)
  136. add_blog_click(article.id)
  137. return render_template("docx/article.html",
  138. article=article,
  139. cache_str=f":{article.id}",
  140. archive_list=article.archive,
  141. form=form,
  142. view=view,
  143. archive=archive,
  144. can_update=current_user.check_role("WriteBlog"),
  145. show_delete=current_user.check_role("DeleteComment"),
  146. show_email=current_user.check_role("ReadUserInfo"))
  147. @docx.route('/article')
  148. def article_page():
  149. blog_id = request.args.get("blog", 1, type=int)
  150. return __load_article_page(blog_id, WriteCommentForm())
  151. @docx.route('/article/download')
  152. def article_down_page():
  153. blog_id = request.args.get("blog", 1, type=int)
  154. article = BlogArticle(blog_id)
  155. if article is None:
  156. app.HBlogFlask.print_user_opt_fail_log(f"Download article with error id({blog_id})")
  157. abort(404)
  158. return
  159. response = make_response(article.content)
  160. response.headers["Content-Disposition"] = f"attachment;filename={article.title.encode().decode('latin-1')}.md"
  161. app.HBlogFlask.print_load_page_log(f"download article (id: {blog_id})")
  162. return response
  163. @docx.route('/article/create', methods=["POST"])
  164. @login_required
  165. @app.form_required(WriteBlogForm,
  166. "write blog",
  167. lambda form: __load_docx_page(request.args.get("page", 1, type=int), form))
  168. @app.role_required("WriteBlog", "write blog")
  169. def create_docx_page():
  170. form: WriteBlogForm = g.form
  171. title = form.title.data
  172. subtitle = form.subtitle.data
  173. archive = []
  174. if -1 not in form.archive.data:
  175. for i in form.archive.data:
  176. i = Archive(i)
  177. if i is not None:
  178. archive.append(i)
  179. if BlogArticle.create(title, subtitle, form.content.data, archive, current_user):
  180. app.HBlogFlask.print_sys_opt_success_log("write blog")
  181. flash(f"博客 {title} 发表成功")
  182. else:
  183. app.HBlogFlask.print_sys_opt_fail_log("write blog")
  184. flash(f"博客 {title} 发表失败")
  185. return redirect(url_for("docx.docx_page", page=1))
  186. @docx.route('/article/update', methods=["POST"])
  187. @login_required
  188. @app.form_required(UpdateBlogForm, "update blog",
  189. lambda form: __load_article_page(form.id.data, WriteCommentForm(), form))
  190. @app.role_required("WriteBlog", "write blog")
  191. def update_docx_page():
  192. form: UpdateBlogForm = g.form
  193. if BlogArticle(form.blog_id.data).update(form.content.data):
  194. app.HBlogFlask.print_sys_opt_success_log("update blog")
  195. flash("博文更新成功")
  196. else:
  197. app.HBlogFlask.print_sys_opt_fail_log("update blog")
  198. flash("博文更新失败")
  199. return redirect(url_for("docx.article_page", blog=form.blog_id.data))
  200. @docx.route("/article/delete")
  201. @login_required
  202. @app.role_required("DeleteBlog", "delete blog")
  203. def delete_blog_page():
  204. blog_id = request.args.get("blog", None, type=int)
  205. if not blog_id:
  206. return abort(400)
  207. if BlogArticle(blog_id).delete():
  208. app.HBlogFlask.print_sys_opt_success_log("delete blog")
  209. flash("博文删除成功")
  210. else:
  211. app.HBlogFlask.print_sys_opt_fail_log("delete blog")
  212. flash("博文删除失败")
  213. return redirect(url_for("docx.docx_page", page=1))
  214. @docx.route("/article/set/top")
  215. @login_required
  216. @app.role_required("WriteBlog", "set blog top")
  217. def set_blog_top_page():
  218. blog_id = request.args.get("blog", None, type=int)
  219. top = request.args.get("top", 0, type=int) != 0
  220. if not blog_id:
  221. return abort(400)
  222. blog = BlogArticle(blog_id)
  223. blog.top = top
  224. if top == blog.top:
  225. app.HBlogFlask.print_sys_opt_success_log(f"set blog top ({top})")
  226. flash(f"博文{'取消' if not top else ''}置顶成功")
  227. else:
  228. app.HBlogFlask.print_sys_opt_fail_log(f"set blog top ({top})")
  229. flash(f"博文{'取消' if not top else ''}置顶失败")
  230. return redirect(url_for("docx.article_page", blog=blog_id))
  231. @docx.route("/article/set/archive", methods=["POST"])
  232. @login_required
  233. @app.form_required(UpdateBlogArchiveForm, "update archive",
  234. lambda form: __load_article_page(form.id.data, WriteCommentForm(), UpdateBlogForm(), form))
  235. @app.role_required("WriteBlog", "update archive")
  236. def update_archive_page():
  237. form: UpdateBlogArchiveForm = g.form
  238. article = BlogArticle(form.blog_id.data)
  239. add = request.args.get("add", 0, type=int) != 0
  240. for i in form.archive.data:
  241. if add:
  242. article.add_to_archive(i)
  243. else:
  244. article.sub_from_archive(i)
  245. flash("归档设定完成")
  246. return redirect(url_for("docx.article_page", blog=form.blog_id.data))
  247. @docx.route('/comment/create', methods=["POST"])
  248. @login_required
  249. @app.form_required(WriteCommentForm, "write comment",
  250. lambda form: __load_article_page(request.args.get("blog", 1, type=int), form))
  251. @app.role_required("WriteComment", "write comment")
  252. def comment_page():
  253. blog_id = request.args.get("blog", 1, type=int)
  254. form: WriteCommentForm = g.form
  255. content = form.content.data
  256. if Comment.create(BlogArticle(blog_id), current_user, content):
  257. app.HBlogFlask.print_user_opt_success_log("comment")
  258. flash("评论成功")
  259. else:
  260. app.HBlogFlask.print_user_opt_error_log("comment")
  261. flash("评论失败")
  262. return redirect(url_for("docx.article_page", blog=blog_id))
  263. @docx.route("/comment/delete")
  264. @login_required
  265. @app.role_required("DeleteComment", "delete comment")
  266. def delete_comment_page():
  267. comment_id = request.args.get("comment", 1, type=int)
  268. comment = Comment(comment_id)
  269. blog_id = comment.blog.id
  270. if blog_id == -1:
  271. abort(404)
  272. if comment.delete():
  273. app.HBlogFlask.print_sys_opt_success_log("delete comment")
  274. flash("博文评论删除成功")
  275. else:
  276. app.HBlogFlask.print_sys_opt_fail_log("delete comment")
  277. flash("博文评论删除失败")
  278. return redirect(url_for("docx.article_page", blog=blog_id))
  279. @docx.context_processor
  280. @app.cache.cached(timeout=conf["CACHE_EXPIRE"], key_prefix="inject_base:docx")
  281. def inject_base():
  282. """ docx 默认模板变量 """
  283. return {"top_nav": ["", "", "active", "", "", ""]}