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.

369 lines
11KB

  1. # -*- coding: utf-8 -*-
  2. """Endpoints for the photo albums."""
  3. from flask import Blueprint, g, request, redirect, url_for, flash
  4. import rophako.model.user as User
  5. import rophako.model.photo as Photo
  6. from rophako.utils import (template, pretty_time, render_markdown,
  7. login_required, ajax_response)
  8. from rophako.plugin import load_plugin
  9. from rophako.settings import Config
  10. mod = Blueprint("photo", __name__, url_prefix="/photos")
  11. load_plugin("rophako.modules.comment")
  12. @mod.route("/")
  13. def index():
  14. return redirect(url_for(".albums"))
  15. @mod.route("/albums")
  16. def albums():
  17. """View the index of the photo albums."""
  18. albums = Photo.list_albums()
  19. # If there's only one album, jump directly to that one.
  20. if len(albums) == 1:
  21. return redirect(url_for(".album_index", name=albums[0]["name"]))
  22. g.info["albums"] = albums
  23. return template("photos/albums.html")
  24. @mod.route("/album/<name>")
  25. def album_index(name):
  26. """View the photos inside an album."""
  27. photos = Photo.list_photos(name)
  28. if photos is None:
  29. flash("That album doesn't exist.")
  30. return redirect(url_for(".albums"))
  31. g.info["album"] = name
  32. g.info["album_info"] = Photo.get_album(name)
  33. g.info["markdown"] = render_markdown(g.info["album_info"]["description"])
  34. g.info["photos"] = photos
  35. # Render Markdown descriptions for photos.
  36. for photo in g.info["photos"]:
  37. photo["data"]["markdown"] = render_markdown(photo["data"].get("description", ""))
  38. return template("photos/album.html")
  39. @mod.route("/view/<key>")
  40. def view_photo(key):
  41. """View a specific photo."""
  42. photo = Photo.get_photo(key)
  43. if photo is None:
  44. flash("That photo wasn't found!")
  45. return redirect(url_for(".albums"))
  46. # Get the author info.
  47. author = User.get_user(uid=photo["author"])
  48. if author:
  49. g.info["author"] = author
  50. g.info["photo"] = photo
  51. g.info["photo"]["key"] = key
  52. g.info["photo"]["pretty_time"] = pretty_time(Config.photo.time_format, photo["uploaded"])
  53. g.info["photo"]["markdown"] = render_markdown(photo.get("description", ""))
  54. return template("photos/view.html")
  55. @mod.route("/upload", methods=["GET", "POST"])
  56. @login_required
  57. def upload():
  58. """Upload a photo."""
  59. if request.method == "POST":
  60. # We're posting the upload.
  61. # Is this an ajax post or a direct post?
  62. is_ajax = request.form.get("__ajax", "false") == "true"
  63. # Album name.
  64. album = request.form.get("album") or request.form.get("new-album")
  65. # What source is the pic from?
  66. result = None
  67. location = request.form.get("location")
  68. if location == "pc":
  69. # An upload from the PC.
  70. result = Photo.upload_from_pc(request)
  71. elif location == "www":
  72. # An upload from the Internet.
  73. result = Photo.upload_from_www(request.form)
  74. else:
  75. flash("Stop messing around.")
  76. return redirect(url_for(".upload"))
  77. # How'd it go?
  78. if result["success"] is not True:
  79. if is_ajax:
  80. return ajax_response(False, result["error"])
  81. else:
  82. flash("The upload has failed: {}".format(result["error"]))
  83. return redirect(url_for(".upload"))
  84. # Good!
  85. if is_ajax:
  86. # Was it a multiple upload?
  87. if result.get("multi"):
  88. return ajax_response(True, url_for(".album_index", name=album))
  89. else:
  90. return ajax_response(True, url_for(".crop", photo=result["photo"]))
  91. else:
  92. if result["multi"]:
  93. return redirect(url_for(".album_index", name=album))
  94. else:
  95. return redirect(url_for(".crop", photo=result["photo"]))
  96. # Get the list of available albums.
  97. g.info["album_list"] = [
  98. "My Photos", # the default
  99. ]
  100. g.info["selected"] = Config.photo.default_album
  101. albums = Photo.list_albums()
  102. if len(albums):
  103. g.info["album_list"] = [ x["name"] for x in albums ]
  104. g.info["selected"] = albums[0]
  105. return template("photos/upload.html")
  106. @mod.route("/crop/<photo>", methods=["GET", "POST"])
  107. @login_required
  108. def crop(photo):
  109. pic = Photo.get_photo(photo)
  110. if not pic:
  111. flash("The photo you want to crop wasn't found!")
  112. return redirect(url_for(".albums"))
  113. # Saving?
  114. if request.method == "POST":
  115. try:
  116. x = int(request.form.get("x", 0))
  117. y = int(request.form.get("y", 0))
  118. length = int(request.form.get("length", 0))
  119. except:
  120. flash("Error with form inputs.")
  121. return redirect(url_for(".crop", photo=photo))
  122. # Re-crop the photo!
  123. Photo.crop_photo(photo, x, y, length)
  124. flash("The photo has been cropped!")
  125. return redirect(url_for(".albums")) # TODO go to photo
  126. # Get the photo's true size.
  127. true_width, true_height = Photo.get_image_dimensions(pic)
  128. g.info["true_width"] = true_width
  129. g.info["true_height"] = true_height
  130. g.info["photo"] = photo
  131. g.info["preview"] = pic["large"]
  132. return template("photos/crop.html")
  133. @mod.route("/set_cover/<album>/<key>")
  134. @login_required
  135. def set_cover(album, key):
  136. """Set the pic as the album cover."""
  137. pic = Photo.get_photo(key)
  138. if not pic:
  139. flash("The photo you want to crop wasn't found!")
  140. return redirect(url_for(".albums"))
  141. Photo.set_album_cover(album, key)
  142. flash("Album cover has been set.")
  143. return redirect(url_for(".albums"))
  144. @mod.route("/set_profile/<key>")
  145. @login_required
  146. def set_profile(key):
  147. """Set the pic as your profile picture."""
  148. pic = Photo.get_photo(key)
  149. if not pic:
  150. flash("The photo wasn't found!")
  151. return redirect(url_for(".albums"))
  152. uid = g.info["session"]["uid"]
  153. User.update_user(uid, dict(picture=key))
  154. flash("Your profile picture has been updated.")
  155. return redirect(url_for(".view_photo", key=key))
  156. @mod.route("/edit/<key>", methods=["GET", "POST"])
  157. @login_required
  158. def edit(key):
  159. """Edit a photo."""
  160. pic = Photo.get_photo(key)
  161. if not pic:
  162. flash("The photo wasn't found!")
  163. return redirect(url_for(".albums"))
  164. if request.method == "POST":
  165. caption = request.form.get("caption", "")
  166. description = request.form.get("description", "")
  167. rotate = request.form.get("rotate", "")
  168. Photo.edit_photo(key, dict(caption=caption, description=description))
  169. # Rotating the photo?
  170. if rotate in ["left", "right", "180"]:
  171. Photo.rotate_photo(key, rotate)
  172. flash("The photo has been updated.")
  173. return redirect(url_for(".view_photo", key=key))
  174. g.info["key"] = key
  175. g.info["photo"] = pic
  176. return template("photos/edit.html")
  177. @mod.route("/delete/<key>", methods=["GET", "POST"])
  178. @login_required
  179. def delete(key):
  180. """Delete a photo."""
  181. pic = Photo.get_photo(key)
  182. if not pic:
  183. flash("The photo wasn't found!")
  184. return redirect(url_for(".albums"))
  185. if request.method == "POST":
  186. # Do it.
  187. Photo.delete_photo(key)
  188. flash("The photo has been deleted.")
  189. return redirect(url_for(".albums"))
  190. g.info["key"] = key
  191. g.info["photo"] = pic
  192. return template("photos/delete.html")
  193. @mod.route("/edit_album/<album>", methods=["GET", "POST"])
  194. @login_required
  195. def edit_album(album):
  196. photos = Photo.list_photos(album)
  197. if photos is None:
  198. flash("That album doesn't exist.")
  199. return redirect(url_for(".albums"))
  200. if request.method == "POST":
  201. # Collect the form details.
  202. new_name = request.form["name"]
  203. description = request.form["description"]
  204. layout = request.form["format"]
  205. # Renaming the album?
  206. if new_name != album:
  207. ok = Photo.rename_album(album, new_name)
  208. if not ok:
  209. flash("Failed to rename album: already exists?")
  210. return redirect(url_for(".edit_album", album=album))
  211. album = new_name
  212. # Update album settings.
  213. Photo.edit_album(album, dict(
  214. description=description,
  215. format=layout,
  216. ))
  217. return redirect(url_for(".albums"))
  218. g.info["album"] = album
  219. g.info["album_info"] = Photo.get_album(album)
  220. g.info["photos"] = photos
  221. return template("photos/edit_album.html")
  222. @mod.route("/arrange_albums", methods=["GET", "POST"])
  223. @login_required
  224. def arrange_albums():
  225. """Rearrange the photo album order."""
  226. albums = Photo.list_albums()
  227. if len(albums) == 0:
  228. flash("There are no albums yet.")
  229. return redirect(url_for(".albums"))
  230. if request.method == "POST":
  231. order = request.form.get("order", "").split(";")
  232. Photo.order_albums(order)
  233. flash("The albums have been rearranged!")
  234. return redirect(url_for(".albums"))
  235. g.info["albums"] = albums
  236. return template("photos/arrange_albums.html")
  237. @mod.route("/edit_captions/<album>", methods=["GET", "POST"])
  238. @login_required
  239. def bulk_captions(album):
  240. """Bulk edit captions and titles in an album."""
  241. photos = Photo.list_photos(album)
  242. if photos is None:
  243. flash("That album doesn't exist.")
  244. return redirect(url_for(".albums"))
  245. if request.method == "POST":
  246. # Do it.
  247. for photo in photos:
  248. caption_key = "{}:caption".format(photo["key"])
  249. desc_key = "{}:description".format(photo["key"])
  250. if caption_key in request.form and desc_key in request.form:
  251. caption = request.form[caption_key]
  252. description = request.form[desc_key]
  253. Photo.edit_photo(photo['key'], dict(caption=caption, description=description))
  254. flash("The photos have been updated.")
  255. return redirect(url_for(".albums"))
  256. g.info["album"] = album
  257. g.info["photos"] = photos
  258. return template("photos/edit_captions.html")
  259. @mod.route("/delete_album/<album>", methods=["GET", "POST"])
  260. @login_required
  261. def delete_album(album):
  262. """Delete an entire album."""
  263. photos = Photo.list_photos(album)
  264. if photos is None:
  265. flash("That album doesn't exist.")
  266. return redirect(url_for(".albums"))
  267. if request.method == "POST":
  268. # Do it.
  269. for photo in photos:
  270. Photo.delete_photo(photo["key"])
  271. flash("The album has been deleted.")
  272. return redirect(url_for(".albums"))
  273. g.info["album"] = album
  274. return template("photos/delete_album.html")
  275. @mod.route("/arrange_photos/<album>", methods=["GET", "POST"])
  276. @login_required
  277. def arrange_photos(album):
  278. """Rearrange the photos in an album."""
  279. photos = Photo.list_photos(album)
  280. if photos is None:
  281. flash("That album doesn't exist.")
  282. return redirect(url_for(".albums"))
  283. if request.method == "POST":
  284. order = request.form.get("order", "").split(";")
  285. Photo.order_photos(album, order)
  286. flash("The albums have been rearranged!")
  287. return redirect(url_for(".album_index", name=album))
  288. g.info["album"] = album
  289. g.info["photos"] = photos
  290. return template("photos/arrange_photos.html")