Browse Source

Enhanced photo album features

Support album descriptions and layout options, and add photo
descriptions
pull/2/head
Noah Petherbridge 5 years ago
parent
commit
1c32d08ab8
9 changed files with 321 additions and 42 deletions
  1. +105
    -10
      rophako/model/photo.py
  2. +81
    -6
      rophako/modules/photo/__init__.py
  3. +37
    -12
      rophako/modules/photo/templates/photos/album.html
  4. +4
    -0
      rophako/modules/photo/templates/photos/edit.html
  5. +31
    -0
      rophako/modules/photo/templates/photos/edit_album.html
  6. +33
    -0
      rophako/modules/photo/templates/photos/edit_captions.html
  7. +7
    -5
      rophako/modules/photo/templates/photos/upload.html
  8. +11
    -9
      rophako/modules/photo/templates/photos/view.html
  9. +12
    -0
      rophako/www/smoke/style.css

+ 105
- 10
rophako/model/photo.py View File

@@ -28,11 +28,25 @@ def list_albums():
index = get_index()
result = []

# Missing settings?
if not "settings" in index:
index["settings"] = dict()

for album in index["album-order"]:
if not album in index["settings"]:
# Need to initialize its settings.
index["settings"][album] = dict(
format="classic",
description="",
)
write_index(index)

cover = index["covers"][album]
pic = index["albums"][album][cover]["thumb"]
result.append(dict(
name=album,
format=index["settings"][album]["format"],
description=index["settings"][album]["description"],
cover=pic,
data=index["albums"][album],
))
@@ -40,6 +54,68 @@ def list_albums():
return result


def get_album(name):
"""Get details about an album."""
index = get_index()
result = []

if not name in index["albums"]:
return None

album = index["albums"][name]
cover = index["covers"][name]

return dict(
name=name,
format=index["settings"][name]["format"],
description=index["settings"][name]["description"],
cover=album[cover]["thumb"],
)


def rename_album(old_name, new_name):
"""Rename an existing photo album.

Returns True on success, False if the new name conflicts with another
album's name."""
old_name = sanitize_name(old_name)
newname = sanitize_name(new_name)
index = get_index()

# New name is unique?
if new_name in index["albums"]:
logger.error("Can't rename album: new name already exists!")
return False

def transfer_key(obj, old_key, new_key):
# Reusable function to do a simple move on a dict key.
obj[new_key] = obj[old_key]
del obj[old_key]

# Simple moves.
transfer_key(index["albums"], old_name, new_name)
transfer_key(index["covers"], old_name, new_name)
transfer_key(index["photo-order"], old_name, new_name)
transfer_key(index["settings"], old_name, new_name)

# Update the photo -> album maps.
for photo in index["map"]:
if index["map"][photo] == old_name:
index["map"][photo] = new_name

# Fix the album ordering.
new_order = list()
for name in index["album-order"]:
if name == old_name:
name = new_name
new_order.append(name)
index["album-order"] = new_order

# And save.
write_index(index)
return True


def list_photos(album):
"""List the photos in an album."""
album = sanitize_name(album)
@@ -125,18 +201,18 @@ def get_image_dimensions(pic):
return img.size


def update_photo(album, key, data):
"""Update photo meta-data in the album."""
index = get_index()
# def update_photo(album, key, data):
# """Update photo meta-data in the album."""
# index = get_index()

if not album in index["albums"]:
index["albums"][album] = {}
if not key in index["albums"][album]:
index["albums"][album][key] = {}
# if not album in index["albums"]:
# index["albums"][album] = {}
# if not key in index["albums"][album]:
# index["albums"][album][key] = {}

# Update!
index["albums"][album][key].update(data)
write_index(index)
# # Update!
# index["albums"][album][key].update(data)
# write_index(index)


def crop_photo(key, x, y, length):
@@ -201,6 +277,18 @@ def edit_photo(key, data):
write_index(index)


def edit_album(album, data):
"""Update an album's settings (description, format, etc.)"""
album = sanitize_name(album)
index = get_index()
if not album in index["albums"]:
logger.error("Failed to edit album: not found!")
return

index["settings"][album].update(data)
write_index(index)


def rotate_photo(key, rotate):
"""Rotate a photo 90 degrees to the left or right."""
photo = get_photo(key)
@@ -404,6 +492,7 @@ def process_photo(form, filename):
# What album are the photos going to?
album = form.get("album", "")
new_album = form.get("new-album", None)
new_desc = form.get("new-description", None)
if album == "" and new_album:
album = new_album

@@ -425,12 +514,18 @@ def process_photo(form, filename):
# Update the photo data.
if not album in index["albums"]:
index["albums"][album] = {}
if not album in index["settings"]:
index["settings"][album] = {
"format": "classic",
"description": new_desc,
}

index["albums"][album][key] = dict(
ip=request.remote_addr,
author=g.info["session"]["uid"],
uploaded=int(time.time()),
caption=form.get("caption", ""),
description=form.get("description", ""),
**sizes
)



+ 81
- 6
rophako/modules/photo/__init__.py View File

@@ -6,7 +6,8 @@ from flask import Blueprint, g, request, redirect, url_for, session, flash

import rophako.model.user as User
import rophako.model.photo as Photo
from rophako.utils import template, pretty_time, login_required, ajax_response
from rophako.utils import (template, pretty_time, render_markdown,
login_required, ajax_response)
from rophako.plugin import load_plugin
from rophako.log import logger
from config import *
@@ -40,8 +41,15 @@ def album_index(name):
flash("That album doesn't exist.")
return redirect(url_for(".albums"))

g.info["album"] = name
g.info["photos"] = photos
g.info["album"] = name
g.info["album_info"] = Photo.get_album(name)
g.info["markdown"] = render_markdown(g.info["album_info"]["description"])
g.info["photos"] = photos

# Render Markdown descriptions for photos.
for photo in g.info["photos"]:
photo["data"]["markdown"] = render_markdown(photo["data"].get("description", ""))

return template("photos/album.html")


@@ -61,6 +69,7 @@ def view_photo(key):
g.info["photo"] = photo
g.info["photo"]["key"] = key
g.info["photo"]["pretty_time"] = pretty_time(PHOTO_TIME_FORMAT, photo["uploaded"])
g.info["photo"]["markdown"] = render_markdown(photo.get("description", ""))
return template("photos/view.html")


@@ -196,9 +205,10 @@ def edit(key):
return redirect(url_for(".albums"))

if request.method == "POST":
caption = request.form.get("caption", "")
rotate = request.form.get("rotate", "")
Photo.edit_photo(key, dict(caption=caption))
caption = request.form.get("caption", "")
description = request.form.get("description", "")
rotate = request.form.get("rotate", "")
Photo.edit_photo(key, dict(caption=caption, description=description))

# Rotating the photo?
if rotate in ["left", "right", "180"]:
@@ -234,6 +244,43 @@ def delete(key):
return template("photos/delete.html")


@mod.route("/edit_album/<album>", methods=["GET", "POST"])
@login_required
def edit_album(album):
photos = Photo.list_photos(album)
if photos is None:
flash("That album doesn't exist.")
return redirect(url_for(".albums"))

if request.method == "POST":
# Collect the form details.
new_name = request.form["name"]
description = request.form["description"]
layout = request.form["format"]

# Renaming the album?
if new_name != album:
ok = Photo.rename_album(album, new_name)
if not ok:
flash("Failed to rename album: already exists?")
return redirect(url_for(".edit_album", album=album))
album = new_name

# Update album settings.
Photo.edit_album(album, dict(
description=description,
format=layout,
))

return redirect(url_for(".albums"))

g.info["album"] = album
g.info["album_info"] = Photo.get_album(album)
g.info["photos"] = photos

return template("photos/edit_album.html")


@mod.route("/arrange_albums", methods=["GET", "POST"])
@login_required
def arrange_albums():
@@ -253,6 +300,34 @@ def arrange_albums():
return template("photos/arrange_albums.html")


@mod.route("/edit_captions/<album>", methods=["GET", "POST"])
@login_required
def bulk_captions(album):
"""Bulk edit captions and titles in an album."""
photos = Photo.list_photos(album)
if photos is None:
flash("That album doesn't exist.")
return redirect(url_for(".albums"))

if request.method == "POST":
# Do it.
for photo in photos:
caption_key = "{}:caption".format(photo["key"])
desc_key = "{}:description".format(photo["key"])
if caption_key in request.form and desc_key in request.form:
caption = request.form[caption_key]
description = request.form[desc_key]
Photo.edit_photo(photo['key'], dict(caption=caption, description=description))

flash("The photos have been updated.")
return redirect(url_for(".albums"))

g.info["album"] = album
g.info["photos"] = photos

return template("photos/edit_captions.html")


@mod.route("/delete_album/<album>", methods=["GET", "POST"])
@login_required
def delete_album(album):


+ 37
- 12
rophako/modules/photo/templates/photos/album.html View File

@@ -4,22 +4,45 @@

<h1>Album: {{ album }}</h1>

<ul class="photo-grid">
{% if markdown %}
{{ markdown|safe }}<p>
{% endif %}

{% if album_info["format"] == "vertical" %}
{% for photo in photos %}
<li class="portrait">
<div class="dummy"></div>
<div class="photo-grid-item">
<a href="{{ url_for('photo.view_photo', key=photo['key']) }}">
<img src="{{ app['photo_url'] }}/{{ photo['data']['thumb'] }}" width="100%" height="100%">
<span class="name">{{ photo["data"]["caption"] }}</span>
</a>
</div>
</li>
{% set data = photo["data"] %}
{% if data["caption"] %}
<h2>{{ data["caption"] }}</h2>
{% endif %}

<a href="{{ url_for('photo.view_photo', key=photo['key']) }}">
<img src="{{ app['photo_url'] }}/{{ data['large'] }}" class="portrait">
</a><p>

{% if data["description"] %}
<div class="photo-description">{{ data["markdown"]|safe }}</div>
{% endif %}

{% if loop.index < photos|length %}<hr>{% endif %}
{% endfor %}
{% else %}{# classic layout #}
<ul class="photo-grid">

{% for photo in photos %}
<li class="portrait">
<div class="dummy"></div>
<div class="photo-grid-item">
<a href="{{ url_for('photo.view_photo', key=photo['key']) }}">
<img src="{{ app['photo_url'] }}/{{ photo['data']['thumb'] }}" width="100%" height="100%">
<span class="name">{{ photo["data"]["caption"] }}</span>
</a>
</div>
</li>
{% endfor %}

</ul>
<div class="clear"></div>
</ul>
<div class="clear"></div>
{% endif %}

{% if session["login"] %}
<h1>Administrative Options</h1>
@@ -27,6 +50,8 @@
<ul>
<li><a href="{{ url_for('photo.upload') }}">Upload a Photo</a></li>
{% if photos|length > 0 %}
<li><a href="{{ url_for('photo.edit_album', album=album) }}">Edit Album Settings</a></li>
<li><a href="{{ url_for('photo.bulk_captions', album=album) }}">Edit Image Titles/Descriptions</a></li>
<li><a href="{{ url_for('photo.arrange_photos', album=album) }}">Rearrange Photos</a></li>
<li><a href="{{ url_for('photo.delete_album', album=album) }}">Delete Album</a></li>
{% endif %}


+ 4
- 0
rophako/modules/photo/templates/photos/edit.html View File

@@ -12,6 +12,10 @@
<strong>Photo Caption:</strong><br>
<input type="text" size="40" name="caption" value="{{ photo['caption'] }}"><p>

<strong>Description:</strong><br>
<textarea cols="50" rows="6" name="description">{{ photo['description'] }}</textarea><br>
<small>Use <a href="/markdown">Markdown</a> syntax.</small><p>

Rotate:
<label>
<input type="radio" name="rotate" value="" checked> Leave alone


+ 31
- 0
rophako/modules/photo/templates/photos/edit_album.html View File

@@ -0,0 +1,31 @@
{% extends "layout.html" %}
{% block title %}Edit Album{% endblock %}
{% block content %}

<h1>Edit Album: {{ album }}</h1>

<form id="album-editor" action="{{ url_for('photo.edit_album', album=album) }}" method="POST">
<input type="hidden" name="token" value="{{ csrf_token() }}">

<strong>Album Title:</strong><br>
<input type="text" size="40" name="name" value="{{ album }}"><p>

<strong>Description:</strong><br>
<textarea cols="50" rows="6" name="description">{{ album_info["description"] }}</textarea><br>
<small>Use <a href="/markdown" target="_blank">Markdown</a> syntax.</small><p>

<strong>Display Format:</strong><br>
<label>
<input type="radio" name="format" value="classic"{% if album_info["format"] == "classic" %} checked{% endif %}>
<strong>Classic:</strong> Display a grid of thumbnails that must be clicked to view full size images.
</label><br>
<label>
<input type="radio" name="format" value="vertical"{% if album_info["format"] == "vertical" %} checked{% endif %}>
<strong>Vertical:</strong> Display all full size photos in one vertical view.
</label><p>

<button type="submit">Save Changes</button>

</form>

{% endblock %}

+ 33
- 0
rophako/modules/photo/templates/photos/edit_captions.html View File

@@ -0,0 +1,33 @@
{% extends "layout.html" %}
{% block title %}Edit Captions{% endblock %}
{% block content %}

<h1>Edit Captions in {{ album }}</h1>

All captions use <a href="/markdown">Markdown</a> syntax.<p>

<form id="caption-editor" action="{{ url_for('photo.bulk_captions', album=album) }}" method="POST">
<input type="hidden" name="token" value="{{ csrf_token() }}">

<table width="100%" border="0" cellspacing="4" cellpadding="4">
{% for photo in photos %}
<tr>
<td width="100" align="center" valign="top">
<img src="{{ app['photo_url'] }}/{{ photo['data']['avatar'] }}" alt="Photo">
</td>
<td align="left" valign="top">
<strong>Caption:</strong><br>
<input type="text" size="40" name="{{ photo['key'] }}:caption" value="{{ photo['data']['caption'] }}"><p>

<strong>Description:</strong><br>
<textarea cols="50" rows="6" name="{{ photo['key'] }}:description">{{ photo['data']['description'] }}</textarea>
</td>
</tr>
{% endfor %}
</table><p>

<button type="submit">Save Changes</button>

</form>

{% endblock %}

+ 7
- 5
rophako/modules/photo/templates/photos/upload.html View File

@@ -50,15 +50,17 @@ somewhere else on the Internet.
{% endfor %}
</optgroup>
<option value="">Create a new album</option>
</select><p>
</select>

<blockquote id="create-album">
<strong>New album:</strong><br>
<input type="text" size="20" id="new-album" name="new-album">
</blockquote>
<input type="text" size="20" id="new-album" name="new-album"><p>

<strong>Caption:</strong><br>
<input type="text" size="40" name="caption">
<strong>Album Description:</strong><br>
<textarea cols="50" rows="6" name="new-description"></textarea><br>
<small>Shows up at the top of the album.
Use <a href="/markdown" target="_blank">Markdown</a> formatting.</small>
</blockquote>
</fieldset>
<p>



+ 11
- 9
rophako/modules/photo/templates/photos/view.html View File

@@ -22,15 +22,17 @@

{{ nav_links() }}

<div class="center">
<a href="{{ url_for('photo.view_photo', key=photo['next']) }}">
<img src="{{ app['photo_url'] }}/{{ photo['large'] }}" class="portrait">
</a><br>
<strong>{{ photo["caption"] }}</strong>
</div>
<p>

Uploaded by {{ author["name"] }} on {{ photo["pretty_time"] }}.
{% if photo["caption"] %}
<h2>{{ photo["caption"] }}</h2>
{% endif %}
<a href="{{ url_for('photo.view_photo', key=photo['next']) }}">
<img src="{{ app['photo_url'] }}/{{ photo['large'] }}" class="portrait">
</a><p>
{% if photo["markdown"] %}
<div class="photo-description">{{ photo["markdown"]|safe }}</div>
{% endif %}

<em>Uploaded by {{ author["name"] }} on {{ photo["pretty_time"] }}.</em>

{{ nav_links() }}



+ 12
- 0
rophako/www/smoke/style.css View File

@@ -219,6 +219,18 @@ ul.photo-grid li .dummy {
box-shadow: 0px 0px 4px #FF4444;
}

/* Photo description blocks */
.photo-description {
display: block;
border: 1px solid #000000;
box-shadow: 0px 0px 4px #000000;
padding: 10px;
margin: 20px 0px;
background-color: #646464;
color: #FFFFFF;
width: 790px;
}

/* Blog titles when shown on index view */
a.blog-title-index:link, a.blog-title-index:visited {
font-size: 32pt;


Loading…
Cancel
Save