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.

370 line
11KB

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