A Python content management system designed for kirsle.net featuring a blog, comments and photo albums. https://rophako.kirsle.net/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

301 lines
9.1KB

  1. # -*- coding: utf-8 -*-
  2. """Endpoints for the web blog."""
  3. from flask import Blueprint, g, request, redirect, url_for, session, flash
  4. import re
  5. import datetime
  6. import calendar
  7. import rophako.model.user as User
  8. import rophako.model.blog as Blog
  9. from rophako.utils import template, pretty_time, admin_required
  10. from rophako.log import logger
  11. from config import *
  12. mod = Blueprint("blog", __name__, url_prefix="/blog")
  13. @mod.route("/")
  14. def index():
  15. return template("blog/index.html")
  16. @mod.route("/category/<category>")
  17. def category(category):
  18. g.info["url_category"] = category
  19. return template("blog/index.html")
  20. @mod.route("/entry/<fid>")
  21. def entry(fid):
  22. """Endpoint to view a specific blog entry."""
  23. # Resolve the friendly ID to a real ID.
  24. post_id = Blog.resolve_id(fid)
  25. if not post_id:
  26. flash("That blog post wasn't found.")
  27. return redirect(url_for(".index"))
  28. # Look up the post.
  29. post = Blog.get_entry(post_id)
  30. post["post_id"] = post_id
  31. # Get the author's information.
  32. post["profile"] = User.get_user(uid=post["author"])
  33. # Pretty-print the time.
  34. post["pretty_time"] = pretty_time(BLOG_TIME_FORMAT, post["time"])
  35. # TODO: count the comments for this post
  36. post["comment_count"] = 0
  37. g.info["post"] = post
  38. return template("blog/entry.html")
  39. @mod.route("/entry")
  40. @mod.route("/index")
  41. def dummy():
  42. return redirect(url_for(".index"))
  43. @mod.route("/update", methods=["GET", "POST"])
  44. @admin_required
  45. def update():
  46. """Post/edit a blog entry."""
  47. # Get our available avatars.
  48. g.info["avatars"] = Blog.list_avatars()
  49. # Default vars.
  50. g.info.update(dict(
  51. post_id="",
  52. fid="",
  53. author=g.info["session"]["uid"],
  54. subject="",
  55. body="",
  56. avatar="",
  57. categories="",
  58. privacy=BLOG_DEFAULT_PRIVACY,
  59. emoticons=True,
  60. comments=BLOG_ALLOW_COMMENTS,
  61. month="",
  62. day="",
  63. year="",
  64. hour="",
  65. min="",
  66. sec="",
  67. preview=False,
  68. ))
  69. # Editing an existing post?
  70. post_id = request.args.get("id", None)
  71. if post_id:
  72. post_id = Blog.resolve_id(post_id)
  73. if post_id:
  74. logger.info("Editing existing blog post {}".format(post_id))
  75. post = Blog.get_entry(post_id)
  76. g.info["post_id"] = post_id
  77. g.info["post"] = post
  78. # Copy fields.
  79. for field in ["author", "fid", "subject", "body", "avatar",
  80. "categories", "privacy", "emoticons", "comments"]:
  81. g.info[field] = post[field]
  82. # Dissect the time.
  83. date = datetime.datetime.fromtimestamp(post["time"])
  84. g.info.update(dict(
  85. month="{:02d}".format(date.month),
  86. day="{:02d}".format(date.day),
  87. year=date.year,
  88. hour="{:02d}".format(date.hour),
  89. min="{:02d}".format(date.minute),
  90. sec="{:02d}".format(date.second),
  91. ))
  92. # Are we SUBMITTING the form?
  93. if request.method == "POST":
  94. action = request.form.get("action")
  95. # Get all the fields from the posted params.
  96. g.info["post_id"] = request.form.get("id")
  97. for field in ["fid", "subject", "body", "avatar", "categories", "privacy"]:
  98. g.info[field] = request.form.get(field)
  99. for boolean in ["emoticons", "comments"]:
  100. print "BOOL:", boolean, request.form.get(boolean)
  101. g.info[boolean] = True if request.form.get(boolean, None) == "true" else False
  102. print g.info[boolean]
  103. for number in ["author", "month", "day", "year", "hour", "min", "sec"]:
  104. g.info[number] = int(request.form.get(number, 0))
  105. # What action are they doing?
  106. if action == "preview":
  107. g.info["preview"] = True
  108. elif action == "publish":
  109. # Publishing! Validate inputs first.
  110. invalid = False
  111. if len(g.info["body"]) == 0:
  112. invalid = True
  113. flash("You must enter a body for your blog post.")
  114. if len(g.info["subject"]) == 0:
  115. invalid = True
  116. flash("You must enter a subject for your blog post.")
  117. # Make sure the times are valid.
  118. date = None
  119. try:
  120. date = datetime.datetime(
  121. g.info["year"],
  122. g.info["month"],
  123. g.info["day"],
  124. g.info["hour"],
  125. g.info["min"],
  126. g.info["sec"],
  127. )
  128. except ValueError, e:
  129. invalid = True
  130. flash("Invalid date/time: " + str(e))
  131. # Format the categories.
  132. tags = []
  133. for tag in g.info["categories"].split(","):
  134. tags.append(tag.strip())
  135. # Okay to update?
  136. if invalid is False:
  137. # Convert the date into a Unix time stamp.
  138. epoch = float(date.strftime("%s"))
  139. new_id, new_fid = Blog.post_entry(
  140. post_id = g.info["post_id"],
  141. epoch = epoch,
  142. author = g.info["author"],
  143. subject = g.info["subject"],
  144. fid = g.info["fid"],
  145. avatar = g.info["avatar"],
  146. categories = tags,
  147. privacy = g.info["privacy"],
  148. ip = request.remote_addr,
  149. emoticons = g.info["emoticons"],
  150. comments = g.info["comments"],
  151. body = g.info["body"],
  152. )
  153. return redirect(url_for(".entry", fid=new_fid))
  154. if type(g.info["categories"]) is list:
  155. g.info["categories"] = ", ".join(g.info["categories"])
  156. return template("blog/update.html")
  157. @mod.route("/delete", methods=["GET", "POST"])
  158. def delete():
  159. """Delete a blog post."""
  160. post_id = request.args.get("id")
  161. # Resolve the post ID.
  162. post_id = Blog.resolve_id(post_id)
  163. if not post_id:
  164. flash("That blog post wasn't found.")
  165. return redirect(url_for(".index"))
  166. if request.method == "POST":
  167. confirm = request.form.get("confirm")
  168. if confirm == "true":
  169. Blog.delete_entry(post_id)
  170. flash("The blog entry has been deleted.")
  171. return redirect(url_for(".index"))
  172. # Get the entry's subject.
  173. post = Blog.get_entry(post_id)
  174. g.info["subject"] = post["subject"]
  175. g.info["post_id"] = post_id
  176. return template("blog/delete.html")
  177. def partial_index():
  178. """Partial template for including the index view of the blog."""
  179. # Get the blog index.
  180. index = Blog.get_index()
  181. pool = {} # The set of blog posts to show.
  182. category = g.info.get("url_category", None)
  183. # Are we narrowing by category?
  184. if category:
  185. # Narrow down the index to just those that match the category.
  186. for post_id, data in index.iteritems():
  187. if not category in data["categories"]:
  188. continue
  189. pool[post_id] = data
  190. # No such category?
  191. if len(pool) == 0:
  192. flash("There are no posts with that category.")
  193. return redirect(url_for(".index"))
  194. else:
  195. pool = index
  196. # Separate the sticky posts from the normal ones.
  197. sticky, normal = set(), set()
  198. for post_id, data in pool.iteritems():
  199. if data["sticky"]:
  200. sticky.add(post_id)
  201. else:
  202. normal.add(post_id)
  203. # Sort the blog IDs by published time.
  204. posts = []
  205. posts.extend(sorted(sticky, key=lambda x: pool[x]["time"], reverse=True))
  206. posts.extend(sorted(normal, key=lambda x: pool[x]["time"], reverse=True))
  207. # Handle pagination.
  208. offset = request.args.get("skip", 0)
  209. try: offset = int(offset)
  210. except: offset = 0
  211. # Handle the offsets, and get those for the "older" and "earlier" posts.
  212. # "earlier" posts count down (towards index 0), "older" counts up.
  213. g.info["offset"] = offset
  214. g.info["earlier"] = offset - BLOG_ENTRIES_PER_PAGE if offset > 0 else 0
  215. g.info["older"] = offset + BLOG_ENTRIES_PER_PAGE
  216. if g.info["earlier"] < 0:
  217. g.info["earlier"] = 0
  218. if g.info["older"] < 0 or g.info["older"] > len(posts):
  219. g.info["older"] = 0
  220. g.info["count"] = 0
  221. # Can we go to other pages?
  222. g.info["can_earlier"] = True if offset > 0 else False
  223. g.info["can_older"] = False if g.info["older"] == 0 else True
  224. # Load the selected posts.
  225. selected = []
  226. stop = offset + BLOG_ENTRIES_PER_PAGE
  227. if stop > len(posts): stop = len(posts)
  228. for i in range(offset, stop):
  229. post_id = posts[i]
  230. post = Blog.get_entry(post_id)
  231. post["post_id"] = post_id
  232. # Get the author's information.
  233. post["profile"] = User.get_user(uid=post["author"])
  234. post["pretty_time"] = pretty_time(BLOG_TIME_FORMAT, post["time"])
  235. # TODO: count the comments for this post
  236. post["comment_count"] = 0
  237. selected.append(post)
  238. g.info["count"] += 1
  239. g.info["category"] = category
  240. g.info["posts"] = selected
  241. return template("blog/index.inc.html")