checkpoint
Add Bookmark class; Add user.html; Add more commit loggers; Fix warning_bag attributeerror when it was None
This commit is contained in:
parent
109d5feef1
commit
8b05a26ff7
14 changed files with 300 additions and 40 deletions
|
@ -37,6 +37,12 @@ SQL_ALBUM_COLUMNS = [
|
||||||
'description',
|
'description',
|
||||||
'associated_directory',
|
'associated_directory',
|
||||||
]
|
]
|
||||||
|
SQL_BOOKMARK_COLUMNS = [
|
||||||
|
'id',
|
||||||
|
'title',
|
||||||
|
'url',
|
||||||
|
'author_id',
|
||||||
|
]
|
||||||
SQL_PHOTO_COLUMNS = [
|
SQL_PHOTO_COLUMNS = [
|
||||||
'id',
|
'id',
|
||||||
'filepath',
|
'filepath',
|
||||||
|
@ -82,6 +88,7 @@ SQL_USER_COLUMNS = [
|
||||||
|
|
||||||
_sql_dictify = lambda columns: {key:index for (index, key) in enumerate(columns)}
|
_sql_dictify = lambda columns: {key:index for (index, key) in enumerate(columns)}
|
||||||
SQL_ALBUM = _sql_dictify(SQL_ALBUM_COLUMNS)
|
SQL_ALBUM = _sql_dictify(SQL_ALBUM_COLUMNS)
|
||||||
|
SQL_BOOKMARK = _sql_dictify(SQL_BOOKMARK_COLUMNS)
|
||||||
SQL_ALBUMPHOTO = _sql_dictify(SQL_ALBUMPHOTO_COLUMNS)
|
SQL_ALBUMPHOTO = _sql_dictify(SQL_ALBUMPHOTO_COLUMNS)
|
||||||
SQL_LASTID = _sql_dictify(SQL_LASTID_COLUMNS)
|
SQL_LASTID = _sql_dictify(SQL_LASTID_COLUMNS)
|
||||||
SQL_PHOTO = _sql_dictify(SQL_PHOTO_COLUMNS)
|
SQL_PHOTO = _sql_dictify(SQL_PHOTO_COLUMNS)
|
||||||
|
|
33
etiquette.py
33
etiquette.py
|
@ -24,6 +24,8 @@ site.config.update(
|
||||||
TEMPLATES_AUTO_RELOAD=True,
|
TEMPLATES_AUTO_RELOAD=True,
|
||||||
)
|
)
|
||||||
site.jinja_env.add_extension('jinja2.ext.do')
|
site.jinja_env.add_extension('jinja2.ext.do')
|
||||||
|
site.jinja_env.trim_blocks = True
|
||||||
|
site.jinja_env.lstrip_blocks = True
|
||||||
site.debug = True
|
site.debug = True
|
||||||
|
|
||||||
P = phototagger.PhotoDB()
|
P = phototagger.PhotoDB()
|
||||||
|
@ -83,6 +85,12 @@ def P_tag(tagname):
|
||||||
except exceptions.NoSuchTag as e:
|
except exceptions.NoSuchTag as e:
|
||||||
flask.abort(404, 'That tag doesnt exist: %s' % e)
|
flask.abort(404, 'That tag doesnt exist: %s' % e)
|
||||||
|
|
||||||
|
def P_user(username):
|
||||||
|
try:
|
||||||
|
return P.get_user(username=username)
|
||||||
|
except exceptions.NoSuchUser as e:
|
||||||
|
flask.abort(404, 'That user doesnt exist: %s' % e)
|
||||||
|
|
||||||
def send_file(filepath):
|
def send_file(filepath):
|
||||||
'''
|
'''
|
||||||
Range-enabled file sending.
|
Range-enabled file sending.
|
||||||
|
@ -279,12 +287,14 @@ def get_album_zip(albumid):
|
||||||
|
|
||||||
recursive = request.args.get('recursive', True)
|
recursive = request.args.get('recursive', True)
|
||||||
recursive = helpers.truthystring(recursive)
|
recursive = helpers.truthystring(recursive)
|
||||||
|
|
||||||
arcnames = helpers.album_zip_filenames(album, recursive=recursive)
|
arcnames = helpers.album_zip_filenames(album, recursive=recursive)
|
||||||
|
|
||||||
streamed_zip = zipstream.ZipFile()
|
streamed_zip = zipstream.ZipFile()
|
||||||
for (real_filepath, arcname) in arcnames.items():
|
for (real_filepath, arcname) in arcnames.items():
|
||||||
streamed_zip.write(real_filepath, arcname=arcname)
|
streamed_zip.write(real_filepath, arcname=arcname)
|
||||||
|
|
||||||
|
# Add the album metadata as an {id}.txt file within each directory.
|
||||||
directories = helpers.album_zip_directories(album, recursive=recursive)
|
directories = helpers.album_zip_directories(album, recursive=recursive)
|
||||||
for (inner_album, directory) in directories.items():
|
for (inner_album, directory) in directories.items():
|
||||||
text = []
|
text = []
|
||||||
|
@ -337,7 +347,8 @@ def get_albums_json():
|
||||||
@session_manager.give_token
|
@session_manager.give_token
|
||||||
def get_bookmarks():
|
def get_bookmarks():
|
||||||
session = session_manager.get(request)
|
session = session_manager.get(request)
|
||||||
return flask.render_template('bookmarks.html', session=session)
|
bookmarks = list(P.get_bookmarks())
|
||||||
|
return flask.render_template('bookmarks.html', bookmarks=bookmarks, session=session)
|
||||||
|
|
||||||
|
|
||||||
@site.route('/file/<photoid>')
|
@site.route('/file/<photoid>')
|
||||||
|
@ -584,6 +595,26 @@ def get_thumbnail(photoid):
|
||||||
return send_file(path)
|
return send_file(path)
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_core(username):
|
||||||
|
user = P_user(username)
|
||||||
|
return user
|
||||||
|
|
||||||
|
@site.route('/user/<username>', methods=['GET'])
|
||||||
|
@session_manager.give_token
|
||||||
|
def get_user_html(username):
|
||||||
|
user = get_user_core(username)
|
||||||
|
session = session_manager.get(request)
|
||||||
|
return flask.render_template('user.html', user=user, session=session)
|
||||||
|
|
||||||
|
@site.route('/user/<username>.json', methods=['GET'])
|
||||||
|
@session_manager.give_token
|
||||||
|
def get_user_json(username):
|
||||||
|
user = get_user_core(username)
|
||||||
|
user = jsonify.user(user)
|
||||||
|
user = jsonify.make_json_response(user)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
@site.route('/album/<albumid>', methods=['POST'])
|
@site.route('/album/<albumid>', methods=['POST'])
|
||||||
@site.route('/album/<albumid>.json', methods=['POST'])
|
@site.route('/album/<albumid>.json', methods=['POST'])
|
||||||
@session_manager.give_token
|
@session_manager.give_token
|
||||||
|
|
|
@ -39,6 +39,22 @@ def upgrade_3_to_4(sql):
|
||||||
cur.execute('ALTER TABLE photos ADD COLUMN author_id TEXT')
|
cur.execute('ALTER TABLE photos ADD COLUMN author_id TEXT')
|
||||||
cur.execute('CREATE INDEX IF NOT EXISTS index_photo_author on photos(author_id)')
|
cur.execute('CREATE INDEX IF NOT EXISTS index_photo_author on photos(author_id)')
|
||||||
|
|
||||||
|
def upgrade_4_to_5(sql):
|
||||||
|
'''
|
||||||
|
Add table `bookmarks` and its indices.
|
||||||
|
'''
|
||||||
|
cur = sql.cursor()
|
||||||
|
cur.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS bookmarks(
|
||||||
|
id TEXT,
|
||||||
|
title TEXT,
|
||||||
|
url TEXT,
|
||||||
|
author_id TEXT
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
cur.execute('CREATE INDEX IF NOT EXISTS index_bookmark_id on bookmarks(id)')
|
||||||
|
cur.execute('CREATE INDEX IF NOT EXISTS index_bookmark_author on bookmarks(author_id)')
|
||||||
|
|
||||||
def upgrade_all(database_filename):
|
def upgrade_all(database_filename):
|
||||||
'''
|
'''
|
||||||
Given the filename of a phototagger database, apply all of the needed
|
Given the filename of a phototagger database, apply all of the needed
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
class NoSuchAlbum(Exception):
|
class NoSuchAlbum(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class NoSuchBookmark(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class NoSuchGroup(Exception):
|
class NoSuchGroup(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -55,3 +55,11 @@ def tag(t):
|
||||||
'qualified_name': t.qualified_name(),
|
'qualified_name': t.qualified_name(),
|
||||||
}
|
}
|
||||||
return j
|
return j
|
||||||
|
|
||||||
|
def user(u):
|
||||||
|
j = {
|
||||||
|
'id': u.id,
|
||||||
|
'username': u.username,
|
||||||
|
'created': u.created,
|
||||||
|
}
|
||||||
|
return j
|
||||||
|
|
43
objects.py
43
objects.py
|
@ -61,7 +61,7 @@ class GroupableMixin:
|
||||||
self.photodb._cached_frozen_children = None
|
self.photodb._cached_frozen_children = None
|
||||||
cur.execute('INSERT INTO tag_group_rel VALUES(?, ?)', [self.id, member.id])
|
cur.execute('INSERT INTO tag_group_rel VALUES(?, ?)', [self.id, member.id])
|
||||||
if commit:
|
if commit:
|
||||||
self.photodb.log.debug('Commiting - add to group')
|
self.photodb.log.debug('Committing - add to group')
|
||||||
self.photodb.commit()
|
self.photodb.commit()
|
||||||
|
|
||||||
def children(self):
|
def children(self):
|
||||||
|
@ -266,6 +266,7 @@ class Album(ObjectBase, GroupableMixin):
|
||||||
[self.id, photo.id]
|
[self.id, photo.id]
|
||||||
)
|
)
|
||||||
if commit:
|
if commit:
|
||||||
|
self.photodb.log.debug('Committing - remove photo from album')
|
||||||
self.photodb.commit()
|
self.photodb.commit()
|
||||||
|
|
||||||
def walk_photos(self):
|
def walk_photos(self):
|
||||||
|
@ -277,6 +278,44 @@ class Album(ObjectBase, GroupableMixin):
|
||||||
print(child)
|
print(child)
|
||||||
yield from child.walk_photos()
|
yield from child.walk_photos()
|
||||||
|
|
||||||
|
|
||||||
|
class Bookmark(ObjectBase):
|
||||||
|
def __init__(self, photodb, row_tuple):
|
||||||
|
self.photodb = photodb
|
||||||
|
if isinstance(row_tuple, (list, tuple)):
|
||||||
|
row_tuple = {constants.SQL_BOOKMARK_COLUMNS[index]: value for (index, value) in enumerate(row_tuple)}
|
||||||
|
|
||||||
|
self.id = row_tuple['id']
|
||||||
|
self.title = row_tuple['title']
|
||||||
|
self.url = row_tuple['url']
|
||||||
|
self.author_id = row_tuple['author_id']
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'Bookmark:{id}'.format(id=self.id)
|
||||||
|
|
||||||
|
def delete(self, *, commit=True):
|
||||||
|
cur = self.photodb.sql.cursor()
|
||||||
|
cur.execute('DELETE FROM bookmarks WHERE id == ?', [self.id])
|
||||||
|
if commit:
|
||||||
|
self.photodb.sql.commit()
|
||||||
|
|
||||||
|
def edit(self, title=None, url=None, *, commit=True):
|
||||||
|
if title is None and url is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if title is not None:
|
||||||
|
self.title = title
|
||||||
|
|
||||||
|
if url is not None:
|
||||||
|
self.url = url
|
||||||
|
|
||||||
|
cur = self.photodb.sql.cursor()
|
||||||
|
cur.execute('UPDATE bookmarks SET title = ?, url = ? WHERE id == ?', [self.title, self.url, self.id])
|
||||||
|
if commit:
|
||||||
|
self.photodb.log.debug('Committing - edit bookmark')
|
||||||
|
self.photodb.sql.commit()
|
||||||
|
|
||||||
|
|
||||||
class Photo(ObjectBase):
|
class Photo(ObjectBase):
|
||||||
'''
|
'''
|
||||||
A PhotoDB entry containing information about an image file.
|
A PhotoDB entry containing information about an image file.
|
||||||
|
@ -528,6 +567,8 @@ class Photo(ObjectBase):
|
||||||
self.ratio = None
|
self.ratio = None
|
||||||
self.duration = None
|
self.duration = None
|
||||||
|
|
||||||
|
self.photodb.log.debug('Reloading metadata for {photo:r}'.format(photo=self))
|
||||||
|
|
||||||
if self.mimetype == 'image':
|
if self.mimetype == 'image':
|
||||||
try:
|
try:
|
||||||
image = PIL.Image.open(self.real_filepath)
|
image = PIL.Image.open(self.real_filepath)
|
||||||
|
|
115
phototagger.py
115
phototagger.py
|
@ -28,7 +28,7 @@ logging.getLogger('PIL.PngImagePlugin').setLevel(logging.WARNING)
|
||||||
# Note: Setting user_version pragma in init sequence is safe because it only
|
# Note: Setting user_version pragma in init sequence is safe because it only
|
||||||
# happens after the out-of-date check occurs, so no chance of accidentally
|
# happens after the out-of-date check occurs, so no chance of accidentally
|
||||||
# overwriting it.
|
# overwriting it.
|
||||||
DATABASE_VERSION = 4
|
DATABASE_VERSION = 5
|
||||||
DB_INIT = '''
|
DB_INIT = '''
|
||||||
PRAGMA count_changes = OFF;
|
PRAGMA count_changes = OFF;
|
||||||
PRAGMA cache_size = 10000;
|
PRAGMA cache_size = 10000;
|
||||||
|
@ -39,6 +39,12 @@ CREATE TABLE IF NOT EXISTS albums(
|
||||||
description TEXT,
|
description TEXT,
|
||||||
associated_directory TEXT COLLATE NOCASE
|
associated_directory TEXT COLLATE NOCASE
|
||||||
);
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS bookmarks(
|
||||||
|
id TEXT,
|
||||||
|
title TEXT,
|
||||||
|
url TEXT,
|
||||||
|
author_id TEXT
|
||||||
|
);
|
||||||
CREATE TABLE IF NOT EXISTS photos(
|
CREATE TABLE IF NOT EXISTS photos(
|
||||||
id TEXT,
|
id TEXT,
|
||||||
filepath TEXT COLLATE NOCASE,
|
filepath TEXT COLLATE NOCASE,
|
||||||
|
@ -91,6 +97,10 @@ CREATE INDEX IF NOT EXISTS index_album_id on albums(id);
|
||||||
CREATE INDEX IF NOT EXISTS index_albumrel_albumid on album_photo_rel(albumid);
|
CREATE INDEX IF NOT EXISTS index_albumrel_albumid on album_photo_rel(albumid);
|
||||||
CREATE INDEX IF NOT EXISTS index_albumrel_photoid on album_photo_rel(photoid);
|
CREATE INDEX IF NOT EXISTS index_albumrel_photoid on album_photo_rel(photoid);
|
||||||
|
|
||||||
|
-- Bookmark
|
||||||
|
CREATE INDEX IF NOT EXISTS index_bookmark_id on bookmarks(id);
|
||||||
|
CREATE INDEX IF NOT EXISTS index_bookmark_author on bookmarks(author_id);
|
||||||
|
|
||||||
-- Photo
|
-- Photo
|
||||||
CREATE INDEX IF NOT EXISTS index_photo_id on photos(id);
|
CREATE INDEX IF NOT EXISTS index_photo_id on photos(id);
|
||||||
CREATE INDEX IF NOT EXISTS index_photo_path on photos(filepath COLLATE NOCASE);
|
CREATE INDEX IF NOT EXISTS index_photo_path on photos(filepath COLLATE NOCASE);
|
||||||
|
@ -379,6 +389,48 @@ class PDBAlbumMixin:
|
||||||
return album
|
return album
|
||||||
|
|
||||||
|
|
||||||
|
class PDBBookmarkMixin:
|
||||||
|
def get_bookmark(self, id):
|
||||||
|
cur = self.sql.cursor()
|
||||||
|
cur.execute('SELECT * FROM bookmarks WHERE id == ?', [id])
|
||||||
|
fetch = cur.fetchone()
|
||||||
|
if fetch is None:
|
||||||
|
raise exceptions.NoSuchBookmark(id)
|
||||||
|
bookmark = objects.Bookmark(self, fetch)
|
||||||
|
return bookmark
|
||||||
|
|
||||||
|
def get_bookmarks(self):
|
||||||
|
yield from self.get_things(thing_type='bookmark')
|
||||||
|
|
||||||
|
def new_bookmark(self, url, title=None, *, author=None, commit=True):
|
||||||
|
if not url:
|
||||||
|
raise ValueError('Must provide a URL')
|
||||||
|
|
||||||
|
bookmark_id = self.generate_id('bookmarks')
|
||||||
|
title = title or None
|
||||||
|
author_id = self.get_user_id_or_none(author)
|
||||||
|
|
||||||
|
# To do: NORMALIZATION AND VALIDATION
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'author_id': author_id,
|
||||||
|
'id': bookmark_id,
|
||||||
|
'title': title,
|
||||||
|
'url': url,
|
||||||
|
}
|
||||||
|
|
||||||
|
(qmarks, bindings) = helpers.binding_filler(constants.SQL_BOOKMARK_COLUMNS, data)
|
||||||
|
query = 'INSERT INTO bookmarks VALUES(%s)' % qmarks
|
||||||
|
cur = self.sql.cursor()
|
||||||
|
cur.execute(query, bindings)
|
||||||
|
|
||||||
|
bookmark = objects.Bookmark(self, data)
|
||||||
|
if commit:
|
||||||
|
self.log.debug('Committing - new Bookmark')
|
||||||
|
self.sql.commit()
|
||||||
|
return bookmark
|
||||||
|
|
||||||
|
|
||||||
class PDBPhotoMixin:
|
class PDBPhotoMixin:
|
||||||
def get_photo(self, photoid):
|
def get_photo(self, photoid):
|
||||||
return self.get_thing_by_id('photo', photoid)
|
return self.get_thing_by_id('photo', photoid)
|
||||||
|
@ -452,15 +504,7 @@ class PDBPhotoMixin:
|
||||||
exc.photo = existing
|
exc.photo = existing
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
if isinstance(author, objects.User):
|
author_id = self.get_user_id_or_none(author)
|
||||||
if author.photodb != self:
|
|
||||||
raise ValueError('That user does not belong to this photodb')
|
|
||||||
author_id = author.id
|
|
||||||
elif author is not None:
|
|
||||||
# Just to confirm
|
|
||||||
author_id = self.get_user(id=author).id
|
|
||||||
else:
|
|
||||||
author_id = None
|
|
||||||
|
|
||||||
extension = os.path.splitext(filename)[1]
|
extension = os.path.splitext(filename)[1]
|
||||||
extension = extension.replace('.', '')
|
extension = extension.replace('.', '')
|
||||||
|
@ -503,11 +547,11 @@ class PDBPhotoMixin:
|
||||||
photo.add_tag(tag, commit=False)
|
photo.add_tag(tag, commit=False)
|
||||||
|
|
||||||
if commit:
|
if commit:
|
||||||
self.log.debug('Commiting - new_photo')
|
self.log.debug('Committing - new_photo')
|
||||||
self.commit()
|
self.commit()
|
||||||
return photo
|
return photo
|
||||||
|
|
||||||
def purge_deleted_files(self):
|
def purge_deleted_files(self, *, commit=True):
|
||||||
'''
|
'''
|
||||||
Remove Photo entries if their corresponding file is no longer found.
|
Remove Photo entries if their corresponding file is no longer found.
|
||||||
'''
|
'''
|
||||||
|
@ -515,14 +559,20 @@ class PDBPhotoMixin:
|
||||||
for photo in photos:
|
for photo in photos:
|
||||||
if os.path.exists(photo.real_filepath):
|
if os.path.exists(photo.real_filepath):
|
||||||
continue
|
continue
|
||||||
photo.delete()
|
photo.delete(commit=False)
|
||||||
|
if commit:
|
||||||
|
self.log.debug('Committing - purge deleted photos')
|
||||||
|
self.sql.commit()
|
||||||
|
|
||||||
def purge_empty_albums(self):
|
def purge_empty_albums(self, *, commit=True):
|
||||||
albums = self.get_albums()
|
albums = self.get_albums()
|
||||||
for album in albums:
|
for album in albums:
|
||||||
if album.children() or album.photos():
|
if album.children() or album.photos():
|
||||||
continue
|
continue
|
||||||
album.delete()
|
album.delete(commit=False)
|
||||||
|
if commit:
|
||||||
|
self.log.debug('Committing - purge empty albums')
|
||||||
|
self.sql.commit()
|
||||||
|
|
||||||
def search(
|
def search(
|
||||||
self,
|
self,
|
||||||
|
@ -559,7 +609,7 @@ class PDBPhotoMixin:
|
||||||
|
|
||||||
TAGS AND FILTERS
|
TAGS AND FILTERS
|
||||||
authors:
|
authors:
|
||||||
A list of User object or users IDs.
|
A list of User objects, or usernames, or user ids.
|
||||||
|
|
||||||
created:
|
created:
|
||||||
A hyphen_range string respresenting min and max. Or just a number for lower bound.
|
A hyphen_range string respresenting min and max. Or just a number for lower bound.
|
||||||
|
@ -813,7 +863,7 @@ class PDBPhotoMixin:
|
||||||
photos_received += 1
|
photos_received += 1
|
||||||
yield photo
|
yield photo
|
||||||
|
|
||||||
if warning_bag.warnings:
|
if warning_bag and warning_bag.warnings:
|
||||||
yield warning_bag
|
yield warning_bag
|
||||||
|
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
|
@ -894,7 +944,7 @@ class PDBTagMixin:
|
||||||
cur = self.sql.cursor()
|
cur = self.sql.cursor()
|
||||||
cur.execute('INSERT INTO tags VALUES(?, ?)', [tagid, tagname])
|
cur.execute('INSERT INTO tags VALUES(?, ?)', [tagid, tagname])
|
||||||
if commit:
|
if commit:
|
||||||
self.log.debug('Commiting - new_tag')
|
self.log.debug('Committing - new_tag')
|
||||||
self.commit()
|
self.commit()
|
||||||
tag = objects.Tag(self, [tagid, tagname])
|
tag = objects.Tag(self, [tagid, tagname])
|
||||||
return tag
|
return tag
|
||||||
|
@ -955,6 +1005,23 @@ class PDBUserMixin:
|
||||||
else:
|
else:
|
||||||
raise exceptions.NoSuchUser(username)
|
raise exceptions.NoSuchUser(username)
|
||||||
|
|
||||||
|
def get_user_id_or_none(self, user):
|
||||||
|
'''
|
||||||
|
For methods that create photos, albums, etc., we sometimes associate
|
||||||
|
them with an author but sometimes not. This method hides validation
|
||||||
|
that those methods would otherwise have to duplicate.
|
||||||
|
'''
|
||||||
|
if isinstance(user, objects.User):
|
||||||
|
if user.photodb != self:
|
||||||
|
raise ValueError('That user does not belong to this photodb')
|
||||||
|
author_id = user.id
|
||||||
|
elif user is not None:
|
||||||
|
# Confirm that this string is an ID and not junk.
|
||||||
|
author_id = self.get_user(id=user).id
|
||||||
|
else:
|
||||||
|
author_id = None
|
||||||
|
return author_id
|
||||||
|
|
||||||
def login(self, user_id, password):
|
def login(self, user_id, password):
|
||||||
cur = self.sql.cursor()
|
cur = self.sql.cursor()
|
||||||
cur.execute('SELECT * FROM users WHERE id == ?', [user_id])
|
cur.execute('SELECT * FROM users WHERE id == ?', [user_id])
|
||||||
|
@ -1018,7 +1085,7 @@ class PDBUserMixin:
|
||||||
return objects.User(self, data)
|
return objects.User(self, data)
|
||||||
|
|
||||||
|
|
||||||
class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin, PDBUserMixin):
|
class PhotoDB(PDBAlbumMixin, PDBBookmarkMixin, PDBPhotoMixin, PDBTagMixin, PDBUserMixin):
|
||||||
'''
|
'''
|
||||||
This class represents an SQLite3 database containing the following tables:
|
This class represents an SQLite3 database containing the following tables:
|
||||||
|
|
||||||
|
@ -1189,7 +1256,7 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin, PDBUserMixin):
|
||||||
current_album.add_photo(photo, commit=False)
|
current_album.add_photo(photo, commit=False)
|
||||||
|
|
||||||
if commit:
|
if commit:
|
||||||
self.log.debug('Commiting - digest')
|
self.log.debug('Committing - digest')
|
||||||
self.commit()
|
self.commit()
|
||||||
return album
|
return album
|
||||||
|
|
||||||
|
@ -1318,7 +1385,7 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin, PDBUserMixin):
|
||||||
ID is actually used.
|
ID is actually used.
|
||||||
'''
|
'''
|
||||||
table = table.lower()
|
table = table.lower()
|
||||||
if table not in ['photos', 'tags', 'groups']:
|
if table not in ['photos', 'tags', 'groups', 'bookmarks']:
|
||||||
raise ValueError('Invalid table requested: %s.', table)
|
raise ValueError('Invalid table requested: %s.', table)
|
||||||
|
|
||||||
cur = self.sql.cursor()
|
cur = self.sql.cursor()
|
||||||
|
@ -1377,6 +1444,12 @@ _THING_CLASSES = {
|
||||||
'exception': exceptions.NoSuchAlbum,
|
'exception': exceptions.NoSuchAlbum,
|
||||||
'table': 'albums',
|
'table': 'albums',
|
||||||
},
|
},
|
||||||
|
'bookmark':
|
||||||
|
{
|
||||||
|
'class': objects.Bookmark,
|
||||||
|
'exception': exceptions.NoSuchBookmark,
|
||||||
|
'table': 'bookmarks',
|
||||||
|
},
|
||||||
'photo':
|
'photo':
|
||||||
{
|
{
|
||||||
'class': objects.Photo,
|
'class': objects.Photo,
|
||||||
|
|
|
@ -26,6 +26,17 @@ def build_query(orderby):
|
||||||
query += ' ORDER BY %s' % orderby
|
query += ' ORDER BY %s' % orderby
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
def get_user(photodb, username_or_id):
|
||||||
|
try:
|
||||||
|
user = photodb.get_user(username=username_or_id)
|
||||||
|
except exceptions.NoSuchUser:
|
||||||
|
try:
|
||||||
|
user = photodb.get_user(id=username_or_id)
|
||||||
|
except exceptions.NoSuchUser:
|
||||||
|
raise
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
def minmax(key, value, minimums, maximums, warning_bag=None):
|
def minmax(key, value, minimums, maximums, warning_bag=None):
|
||||||
'''
|
'''
|
||||||
Dissects a hyphenated range string and inserts the correct k:v pair into
|
Dissects a hyphenated range string and inserts the correct k:v pair into
|
||||||
|
@ -69,6 +80,14 @@ def minmax(key, value, minimums, maximums, warning_bag=None):
|
||||||
maximums[key] = high
|
maximums[key] = high
|
||||||
|
|
||||||
def normalize_authors(authors, photodb, warning_bag=None):
|
def normalize_authors(authors, photodb, warning_bag=None):
|
||||||
|
'''
|
||||||
|
Either:
|
||||||
|
- A string, where the usernames are separated by commas
|
||||||
|
- An iterable containing usernames
|
||||||
|
- An iterable containing User objects.
|
||||||
|
|
||||||
|
Returns: A set of user IDs.
|
||||||
|
'''
|
||||||
if not authors:
|
if not authors:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -84,7 +103,7 @@ def normalize_authors(authors, photodb, warning_bag=None):
|
||||||
requested_author = requested_author.username
|
requested_author = requested_author.username
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = photodb.get_user(username=requested_author)
|
user = get_user(photodb, requested_author)
|
||||||
except exceptions.NoSuchUser:
|
except exceptions.NoSuchUser:
|
||||||
if warning_bag:
|
if warning_bag:
|
||||||
warning_bag.add(constants.WARNING_NO_SUCH_USER.format(username=requested_author))
|
warning_bag.add(constants.WARNING_NO_SUCH_USER.format(username=requested_author))
|
||||||
|
|
|
@ -138,16 +138,6 @@ li
|
||||||
right: 8px;
|
right: 8px;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
.photo_card_grid_info a
|
|
||||||
{
|
|
||||||
position: absolute;
|
|
||||||
max-height: 30px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.photo_card_grid_info a:hover
|
|
||||||
{
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
.photo_card_grid_file_metadata
|
.photo_card_grid_file_metadata
|
||||||
{
|
{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -156,8 +146,15 @@ li
|
||||||
}
|
}
|
||||||
.photo_card_grid_filename
|
.photo_card_grid_filename
|
||||||
{
|
{
|
||||||
|
position: absolute;
|
||||||
|
max-height: 30px;
|
||||||
|
overflow: hidden;
|
||||||
word-break:break-word;
|
word-break:break-word;
|
||||||
}
|
}
|
||||||
|
.photo_card_grid_filename:hover
|
||||||
|
{
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
.photo_card_grid_tags
|
.photo_card_grid_tags
|
||||||
{
|
{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -36,7 +36,7 @@ p
|
||||||
{% if sub_albums %}
|
{% if sub_albums %}
|
||||||
<h3>Sub-albums</h3>
|
<h3>Sub-albums</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{% for sub_album in sub_albums %}
|
{% for sub_album in sub_albums|sort(attribute='title') %}
|
||||||
<li><a href="/album/{{sub_album.id}}">
|
<li><a href="/album/{{sub_album.id}}">
|
||||||
{% if sub_album.title %}
|
{% if sub_album.title %}
|
||||||
{{sub_album.title}}
|
{{sub_album.title}}
|
||||||
|
|
|
@ -8,6 +8,26 @@
|
||||||
<script src="/static/common.js"></script>
|
<script src="/static/common.js"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
#bookmarks
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.bookmark_card
|
||||||
|
{
|
||||||
|
background-color: #ffffd4;
|
||||||
|
display: inline-flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px;
|
||||||
|
margin: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.bookmark_card .bookmark_url
|
||||||
|
{
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -15,7 +35,20 @@
|
||||||
<body>
|
<body>
|
||||||
{{header.make_header(session=session)}}
|
{{header.make_header(session=session)}}
|
||||||
<div id="content_body">
|
<div id="content_body">
|
||||||
<a href="/search?has_tags=no&orderby=random-desc&mimetype=image">Needs tagging</a>
|
<div id="bookmarks">
|
||||||
|
{% for bookmark in bookmarks %}
|
||||||
|
<div class="bookmark_card">
|
||||||
|
<a href="{{bookmark.url}}" class="bookmark_title">
|
||||||
|
{% if bookmark.title %}
|
||||||
|
{{bookmark.title}}
|
||||||
|
{% else %}
|
||||||
|
{{bookmark.id}}
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
<a href="{{bookmark.url}}" class="bookmark_url">{{bookmark.url}}</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
{% if photo.duration %}
|
{% if photo.duration %}
|
||||||
{{photo.duration_string()}},
|
{{photo.duration_string()}},
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{photo.bytestring()}}
|
<a target="_blank" href="/file/{{photo.id}}.{{photo.extension}}">{{photo.bytestring()}}</a>
|
||||||
</span>
|
</span>
|
||||||
<span class="photo_card_grid_tags">
|
<span class="photo_card_grid_tags">
|
||||||
{% set tags = photo.tags() %}
|
{% set tags = photo.tags() %}
|
||||||
|
|
|
@ -12,9 +12,10 @@
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="content_body">
|
{{header.make_header(session=session)}}
|
||||||
<p>test</p>
|
<div id="content_body">
|
||||||
</div>
|
<p>test</p>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
|
31
templates/user.html
Normal file
31
templates/user.html
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<!DOCTYPE html5>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{% import "header.html" as header %}
|
||||||
|
<title>User {{user.username}}</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" href="/static/common.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#content_body
|
||||||
|
{
|
||||||
|
/* overriding common.css here */
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{{header.make_header(session=session)}}
|
||||||
|
<div id="content_body">
|
||||||
|
<h2>{{user.username}}</h2>
|
||||||
|
<p>ID: {{user.id}}</p>
|
||||||
|
<p><a href="/search?author={{user.username}}">Photos by {{user.username}}</a></p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
</script>
|
||||||
|
</html>
|
Loading…
Reference in a new issue