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.
 
 
 
 
 

373 lines
11 KiB

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