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',
|
||||
'associated_directory',
|
||||
]
|
||||
SQL_BOOKMARK_COLUMNS = [
|
||||
'id',
|
||||
'title',
|
||||
'url',
|
||||
'author_id',
|
||||
]
|
||||
SQL_PHOTO_COLUMNS = [
|
||||
'id',
|
||||
'filepath',
|
||||
|
@ -82,6 +88,7 @@ SQL_USER_COLUMNS = [
|
|||
|
||||
_sql_dictify = lambda columns: {key:index for (index, key) in enumerate(columns)}
|
||||
SQL_ALBUM = _sql_dictify(SQL_ALBUM_COLUMNS)
|
||||
SQL_BOOKMARK = _sql_dictify(SQL_BOOKMARK_COLUMNS)
|
||||
SQL_ALBUMPHOTO = _sql_dictify(SQL_ALBUMPHOTO_COLUMNS)
|
||||
SQL_LASTID = _sql_dictify(SQL_LASTID_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,
|
||||
)
|
||||
site.jinja_env.add_extension('jinja2.ext.do')
|
||||
site.jinja_env.trim_blocks = True
|
||||
site.jinja_env.lstrip_blocks = True
|
||||
site.debug = True
|
||||
|
||||
P = phototagger.PhotoDB()
|
||||
|
@ -83,6 +85,12 @@ def P_tag(tagname):
|
|||
except exceptions.NoSuchTag as 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):
|
||||
'''
|
||||
Range-enabled file sending.
|
||||
|
@ -279,12 +287,14 @@ def get_album_zip(albumid):
|
|||
|
||||
recursive = request.args.get('recursive', True)
|
||||
recursive = helpers.truthystring(recursive)
|
||||
|
||||
arcnames = helpers.album_zip_filenames(album, recursive=recursive)
|
||||
|
||||
streamed_zip = zipstream.ZipFile()
|
||||
for (real_filepath, arcname) in arcnames.items():
|
||||
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)
|
||||
for (inner_album, directory) in directories.items():
|
||||
text = []
|
||||
|
@ -337,7 +347,8 @@ def get_albums_json():
|
|||
@session_manager.give_token
|
||||
def get_bookmarks():
|
||||
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>')
|
||||
|
@ -584,6 +595,26 @@ def get_thumbnail(photoid):
|
|||
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>.json', methods=['POST'])
|
||||
@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('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):
|
||||
'''
|
||||
Given the filename of a phototagger database, apply all of the needed
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
class NoSuchAlbum(Exception):
|
||||
pass
|
||||
|
||||
class NoSuchBookmark(Exception):
|
||||
pass
|
||||
|
||||
class NoSuchGroup(Exception):
|
||||
pass
|
||||
|
||||
|
|
|
@ -55,3 +55,11 @@ def tag(t):
|
|||
'qualified_name': t.qualified_name(),
|
||||
}
|
||||
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
|
||||
cur.execute('INSERT INTO tag_group_rel VALUES(?, ?)', [self.id, member.id])
|
||||
if commit:
|
||||
self.photodb.log.debug('Commiting - add to group')
|
||||
self.photodb.log.debug('Committing - add to group')
|
||||
self.photodb.commit()
|
||||
|
||||
def children(self):
|
||||
|
@ -266,6 +266,7 @@ class Album(ObjectBase, GroupableMixin):
|
|||
[self.id, photo.id]
|
||||
)
|
||||
if commit:
|
||||
self.photodb.log.debug('Committing - remove photo from album')
|
||||
self.photodb.commit()
|
||||
|
||||
def walk_photos(self):
|
||||
|
@ -277,6 +278,44 @@ class Album(ObjectBase, GroupableMixin):
|
|||
print(child)
|
||||
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):
|
||||
'''
|
||||
A PhotoDB entry containing information about an image file.
|
||||
|
@ -528,6 +567,8 @@ class Photo(ObjectBase):
|
|||
self.ratio = None
|
||||
self.duration = None
|
||||
|
||||
self.photodb.log.debug('Reloading metadata for {photo:r}'.format(photo=self))
|
||||
|
||||
if self.mimetype == 'image':
|
||||
try:
|
||||
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
|
||||
# happens after the out-of-date check occurs, so no chance of accidentally
|
||||
# overwriting it.
|
||||
DATABASE_VERSION = 4
|
||||
DATABASE_VERSION = 5
|
||||
DB_INIT = '''
|
||||
PRAGMA count_changes = OFF;
|
||||
PRAGMA cache_size = 10000;
|
||||
|
@ -39,6 +39,12 @@ CREATE TABLE IF NOT EXISTS albums(
|
|||
description TEXT,
|
||||
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(
|
||||
id TEXT,
|
||||
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_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
|
||||
CREATE INDEX IF NOT EXISTS index_photo_id on photos(id);
|
||||
CREATE INDEX IF NOT EXISTS index_photo_path on photos(filepath COLLATE NOCASE);
|
||||
|
@ -379,6 +389,48 @@ class PDBAlbumMixin:
|
|||
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:
|
||||
def get_photo(self, photoid):
|
||||
return self.get_thing_by_id('photo', photoid)
|
||||
|
@ -452,15 +504,7 @@ class PDBPhotoMixin:
|
|||
exc.photo = existing
|
||||
raise exc
|
||||
|
||||
if isinstance(author, objects.User):
|
||||
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
|
||||
author_id = self.get_user_id_or_none(author)
|
||||
|
||||
extension = os.path.splitext(filename)[1]
|
||||
extension = extension.replace('.', '')
|
||||
|
@ -503,11 +547,11 @@ class PDBPhotoMixin:
|
|||
photo.add_tag(tag, commit=False)
|
||||
|
||||
if commit:
|
||||
self.log.debug('Commiting - new_photo')
|
||||
self.log.debug('Committing - new_photo')
|
||||
self.commit()
|
||||
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.
|
||||
'''
|
||||
|
@ -515,14 +559,20 @@ class PDBPhotoMixin:
|
|||
for photo in photos:
|
||||
if os.path.exists(photo.real_filepath):
|
||||
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()
|
||||
for album in albums:
|
||||
if album.children() or album.photos():
|
||||
continue
|
||||
album.delete()
|
||||
album.delete(commit=False)
|
||||
if commit:
|
||||
self.log.debug('Committing - purge empty albums')
|
||||
self.sql.commit()
|
||||
|
||||
def search(
|
||||
self,
|
||||
|
@ -559,7 +609,7 @@ class PDBPhotoMixin:
|
|||
|
||||
TAGS AND FILTERS
|
||||
authors:
|
||||
A list of User object or users IDs.
|
||||
A list of User objects, or usernames, or user ids.
|
||||
|
||||
created:
|
||||
A hyphen_range string respresenting min and max. Or just a number for lower bound.
|
||||
|
@ -813,7 +863,7 @@ class PDBPhotoMixin:
|
|||
photos_received += 1
|
||||
yield photo
|
||||
|
||||
if warning_bag.warnings:
|
||||
if warning_bag and warning_bag.warnings:
|
||||
yield warning_bag
|
||||
|
||||
end_time = time.time()
|
||||
|
@ -894,7 +944,7 @@ class PDBTagMixin:
|
|||
cur = self.sql.cursor()
|
||||
cur.execute('INSERT INTO tags VALUES(?, ?)', [tagid, tagname])
|
||||
if commit:
|
||||
self.log.debug('Commiting - new_tag')
|
||||
self.log.debug('Committing - new_tag')
|
||||
self.commit()
|
||||
tag = objects.Tag(self, [tagid, tagname])
|
||||
return tag
|
||||
|
@ -955,6 +1005,23 @@ class PDBUserMixin:
|
|||
else:
|
||||
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):
|
||||
cur = self.sql.cursor()
|
||||
cur.execute('SELECT * FROM users WHERE id == ?', [user_id])
|
||||
|
@ -1018,7 +1085,7 @@ class PDBUserMixin:
|
|||
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:
|
||||
|
||||
|
@ -1189,7 +1256,7 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin, PDBUserMixin):
|
|||
current_album.add_photo(photo, commit=False)
|
||||
|
||||
if commit:
|
||||
self.log.debug('Commiting - digest')
|
||||
self.log.debug('Committing - digest')
|
||||
self.commit()
|
||||
return album
|
||||
|
||||
|
@ -1318,7 +1385,7 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin, PDBUserMixin):
|
|||
ID is actually used.
|
||||
'''
|
||||
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)
|
||||
|
||||
cur = self.sql.cursor()
|
||||
|
@ -1377,6 +1444,12 @@ _THING_CLASSES = {
|
|||
'exception': exceptions.NoSuchAlbum,
|
||||
'table': 'albums',
|
||||
},
|
||||
'bookmark':
|
||||
{
|
||||
'class': objects.Bookmark,
|
||||
'exception': exceptions.NoSuchBookmark,
|
||||
'table': 'bookmarks',
|
||||
},
|
||||
'photo':
|
||||
{
|
||||
'class': objects.Photo,
|
||||
|
|
|
@ -26,6 +26,17 @@ def build_query(orderby):
|
|||
query += ' ORDER BY %s' % orderby
|
||||
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):
|
||||
'''
|
||||
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
|
||||
|
||||
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:
|
||||
return None
|
||||
|
||||
|
@ -84,7 +103,7 @@ def normalize_authors(authors, photodb, warning_bag=None):
|
|||
requested_author = requested_author.username
|
||||
|
||||
try:
|
||||
user = photodb.get_user(username=requested_author)
|
||||
user = get_user(photodb, requested_author)
|
||||
except exceptions.NoSuchUser:
|
||||
if warning_bag:
|
||||
warning_bag.add(constants.WARNING_NO_SUCH_USER.format(username=requested_author))
|
||||
|
|
|
@ -138,16 +138,6 @@ li
|
|||
right: 8px;
|
||||
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
|
||||
{
|
||||
position: absolute;
|
||||
|
@ -156,8 +146,15 @@ li
|
|||
}
|
||||
.photo_card_grid_filename
|
||||
{
|
||||
position: absolute;
|
||||
max-height: 30px;
|
||||
overflow: hidden;
|
||||
word-break:break-word;
|
||||
}
|
||||
.photo_card_grid_filename:hover
|
||||
{
|
||||
max-height: 100%;
|
||||
}
|
||||
.photo_card_grid_tags
|
||||
{
|
||||
position: absolute;
|
||||
|
|
|
@ -36,7 +36,7 @@ p
|
|||
{% if sub_albums %}
|
||||
<h3>Sub-albums</h3>
|
||||
<ul>
|
||||
{% for sub_album in sub_albums %}
|
||||
{% for sub_album in sub_albums|sort(attribute='title') %}
|
||||
<li><a href="/album/{{sub_album.id}}">
|
||||
{% if sub_album.title %}
|
||||
{{sub_album.title}}
|
||||
|
|
|
@ -8,6 +8,26 @@
|
|||
<script src="/static/common.js"></script>
|
||||
|
||||
<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>
|
||||
</head>
|
||||
|
||||
|
@ -15,7 +35,20 @@
|
|||
<body>
|
||||
{{header.make_header(session=session)}}
|
||||
<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>
|
||||
</body>
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
{% if photo.duration %}
|
||||
{{photo.duration_string()}},
|
||||
{% endif %}
|
||||
{{photo.bytestring()}}
|
||||
<a target="_blank" href="/file/{{photo.id}}.{{photo.extension}}">{{photo.bytestring()}}</a>
|
||||
</span>
|
||||
<span class="photo_card_grid_tags">
|
||||
{% set tags = photo.tags() %}
|
||||
|
|
|
@ -12,9 +12,10 @@
|
|||
|
||||
|
||||
<body>
|
||||
<div id="content_body">
|
||||
{{header.make_header(session=session)}}
|
||||
<div id="content_body">
|
||||
<p>test</p>
|
||||
</div>
|
||||
</div>
|
||||
</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