Add database upgrader script
This commit is contained in:
parent
45a8a8ccc5
commit
2d4b07c10f
7 changed files with 127 additions and 26 deletions
|
@ -1,7 +1,7 @@
|
||||||
import string
|
import string
|
||||||
|
|
||||||
# Errors and warnings
|
# Errors and warnings
|
||||||
ERROR_DATABASE_OUTOFDATE = 'Database is out-of-date. {current} should be {new}'
|
ERROR_DATABASE_OUTOFDATE = 'Database is out-of-date. {current} should be {new}. Please use etiquette_upgrader.py'
|
||||||
ERROR_INVALID_ACTION = 'Invalid action'
|
ERROR_INVALID_ACTION = 'Invalid action'
|
||||||
ERROR_NO_SUCH_TAG = 'Doesn\'t exist'
|
ERROR_NO_SUCH_TAG = 'Doesn\'t exist'
|
||||||
ERROR_NO_TAG_GIVEN = 'No tag name supplied'
|
ERROR_NO_TAG_GIVEN = 'No tag name supplied'
|
||||||
|
|
|
@ -235,6 +235,11 @@ def get_albums_json():
|
||||||
return make_json_response(albums)
|
return make_json_response(albums)
|
||||||
|
|
||||||
|
|
||||||
|
@site.route('/bookmarks')
|
||||||
|
def get_bookmarks():
|
||||||
|
return flask.render_template('bookmarks.html')
|
||||||
|
|
||||||
|
|
||||||
@site.route('/file/<photoid>')
|
@site.route('/file/<photoid>')
|
||||||
def get_file(photoid):
|
def get_file(photoid):
|
||||||
requested_photoid = photoid
|
requested_photoid = photoid
|
||||||
|
@ -327,7 +332,6 @@ def get_search_core():
|
||||||
orderby = request.args.get('orderby', None)
|
orderby = request.args.get('orderby', None)
|
||||||
if orderby:
|
if orderby:
|
||||||
orderby = orderby.replace('-', ' ')
|
orderby = orderby.replace('-', ' ')
|
||||||
orderby = orderby.replace('_', ' ')
|
|
||||||
orderby = orderby.split(',')
|
orderby = orderby.split(',')
|
||||||
else:
|
else:
|
||||||
orderby = None
|
orderby = None
|
||||||
|
|
60
etiquette/etiquette_upgrader.py
Normal file
60
etiquette/etiquette_upgrader.py
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import phototagger
|
||||||
|
|
||||||
|
def upgrade_1_to_2(sql):
|
||||||
|
'''
|
||||||
|
In this version, a column `tagged_at` was added to the Photos table, to keep
|
||||||
|
track of the last time the photo's tags were edited (added or removed).
|
||||||
|
'''
|
||||||
|
cur = sql.cursor()
|
||||||
|
cur.execute('ALTER TABLE photos ADD COLUMN tagged_at INT')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_all(database_filename):
|
||||||
|
'''
|
||||||
|
Given the filename of a phototagger database, apply all of the needed
|
||||||
|
upgrade_x_to_y functions in order.
|
||||||
|
'''
|
||||||
|
if not os.path.isfile(database_filename):
|
||||||
|
raise FileNotFoundError(database_filename)
|
||||||
|
|
||||||
|
sql = sqlite3.connect(database_filename)
|
||||||
|
cur = sql.cursor()
|
||||||
|
|
||||||
|
cur.execute('PRAGMA user_version')
|
||||||
|
current_version = cur.fetchone()[0]
|
||||||
|
needed_version = phototagger.DATABASE_VERSION
|
||||||
|
|
||||||
|
if current_version == needed_version:
|
||||||
|
print('Already up-to-date.')
|
||||||
|
return
|
||||||
|
|
||||||
|
for version_number in range(current_version + 1, needed_version + 1):
|
||||||
|
print('Upgrading from %d to %d' % (current_version, version_number))
|
||||||
|
upgrade_function = 'upgrade_%d_to_%d' % (current_version, version_number)
|
||||||
|
upgrade_function = eval(upgrade_function)
|
||||||
|
upgrade_function(sql)
|
||||||
|
sql.cursor().execute('PRAGMA user_version = 2')
|
||||||
|
sql.commit()
|
||||||
|
current_version = version_number
|
||||||
|
print('Upgrades finished.')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_all_argparse(args):
|
||||||
|
return upgrade_all(database_filename=args.database_filename)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument('database_filename')
|
||||||
|
parser.set_defaults(func=upgrade_all_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv[1:])
|
|
@ -70,6 +70,7 @@ SQL_PHOTO_COLUMNS = [
|
||||||
'bytes',
|
'bytes',
|
||||||
'created',
|
'created',
|
||||||
'thumbnail',
|
'thumbnail',
|
||||||
|
'tagged_at',
|
||||||
]
|
]
|
||||||
SQL_TAG_COLUMNS = [
|
SQL_TAG_COLUMNS = [
|
||||||
'id',
|
'id',
|
||||||
|
@ -101,7 +102,10 @@ SQL_SYN = {key:index for (index, key) in enumerate(SQL_SYN_COLUMNS)}
|
||||||
SQL_TAG = {key:index for (index, key) in enumerate(SQL_TAG_COLUMNS)}
|
SQL_TAG = {key:index for (index, key) in enumerate(SQL_TAG_COLUMNS)}
|
||||||
SQL_TAGGROUP = {key:index for (index, key) in enumerate(SQL_TAGGROUP_COLUMNS)}
|
SQL_TAGGROUP = {key:index for (index, key) in enumerate(SQL_TAGGROUP_COLUMNS)}
|
||||||
|
|
||||||
DATABASE_VERSION = 1
|
# 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 = 2
|
||||||
DB_INIT = '''
|
DB_INIT = '''
|
||||||
PRAGMA count_changes = OFF;
|
PRAGMA count_changes = OFF;
|
||||||
PRAGMA cache_size = 10000;
|
PRAGMA cache_size = 10000;
|
||||||
|
@ -124,7 +128,8 @@ CREATE TABLE IF NOT EXISTS photos(
|
||||||
duration INT,
|
duration INT,
|
||||||
bytes INT,
|
bytes INT,
|
||||||
created INT,
|
created INT,
|
||||||
thumbnail TEXT
|
thumbnail TEXT,
|
||||||
|
tagged_at INT
|
||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS tags(
|
CREATE TABLE IF NOT EXISTS tags(
|
||||||
id TEXT,
|
id TEXT,
|
||||||
|
@ -180,6 +185,18 @@ CREATE INDEX IF NOT EXISTS index_grouprel_parentid on tag_group_rel(parentid);
|
||||||
CREATE INDEX IF NOT EXISTS index_grouprel_memberid on tag_group_rel(memberid);
|
CREATE INDEX IF NOT EXISTS index_grouprel_memberid on tag_group_rel(memberid);
|
||||||
'''.format(user_version=DATABASE_VERSION)
|
'''.format(user_version=DATABASE_VERSION)
|
||||||
|
|
||||||
|
ALLOWED_ORDERBY_COLUMNS = [
|
||||||
|
'extension',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'ratio',
|
||||||
|
'area',
|
||||||
|
'duration',
|
||||||
|
'bytes',
|
||||||
|
'created',
|
||||||
|
'tagged_at',
|
||||||
|
'random',
|
||||||
|
]
|
||||||
|
|
||||||
def _helper_extension(ext):
|
def _helper_extension(ext):
|
||||||
'''
|
'''
|
||||||
|
@ -240,18 +257,7 @@ def _helper_orderby(orderby):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
#print(column, sorter)
|
#print(column, sorter)
|
||||||
sortable = column in [
|
if column not in ALLOWED_ORDERBY_COLUMNS:
|
||||||
'extension',
|
|
||||||
'width',
|
|
||||||
'height',
|
|
||||||
'ratio',
|
|
||||||
'area',
|
|
||||||
'duration',
|
|
||||||
'bytes',
|
|
||||||
'created',
|
|
||||||
'random',
|
|
||||||
]
|
|
||||||
if not sortable:
|
|
||||||
warnings.warn(constants.WARNING_ORDERBY_BADCOL.format(column=column))
|
warnings.warn(constants.WARNING_ORDERBY_BADCOL.format(column=column))
|
||||||
return None
|
return None
|
||||||
if column == 'random':
|
if column == 'random':
|
||||||
|
@ -1666,6 +1672,7 @@ class Photo(ObjectBase):
|
||||||
self.id = row_tuple[SQL_PHOTO['id']]
|
self.id = row_tuple[SQL_PHOTO['id']]
|
||||||
self.real_filepath = row_tuple[SQL_PHOTO['filepath']]
|
self.real_filepath = row_tuple[SQL_PHOTO['filepath']]
|
||||||
self.real_filepath = normalize_filepath(self.real_filepath)
|
self.real_filepath = normalize_filepath(self.real_filepath)
|
||||||
|
self.real_path = pathclass.Path(self.real_filepath)
|
||||||
self.filepath = row_tuple[SQL_PHOTO['override_filename']] or self.real_filepath
|
self.filepath = row_tuple[SQL_PHOTO['override_filename']] or self.real_filepath
|
||||||
self.basename = row_tuple[SQL_PHOTO['override_filename']] or os.path.basename(self.real_filepath)
|
self.basename = row_tuple[SQL_PHOTO['override_filename']] or os.path.basename(self.real_filepath)
|
||||||
self.extension = row_tuple[SQL_PHOTO['extension']]
|
self.extension = row_tuple[SQL_PHOTO['extension']]
|
||||||
|
@ -1677,7 +1684,7 @@ class Photo(ObjectBase):
|
||||||
self.duration = row_tuple[SQL_PHOTO['duration']]
|
self.duration = row_tuple[SQL_PHOTO['duration']]
|
||||||
self.created = row_tuple[SQL_PHOTO['created']]
|
self.created = row_tuple[SQL_PHOTO['created']]
|
||||||
self.thumbnail = row_tuple[SQL_PHOTO['thumbnail']]
|
self.thumbnail = row_tuple[SQL_PHOTO['thumbnail']]
|
||||||
self.real_path = pathclass.Path(self.real_filepath)
|
self.tagged_at = row_tuple[SQL_PHOTO['tagged_at']]
|
||||||
|
|
||||||
def __reinit__(self):
|
def __reinit__(self):
|
||||||
'''
|
'''
|
||||||
|
@ -1709,9 +1716,10 @@ class Photo(ObjectBase):
|
||||||
log.debug('Preferring new {tag:s} over {par:s}'.format(tag=tag, par=parent))
|
log.debug('Preferring new {tag:s} over {par:s}'.format(tag=tag, par=parent))
|
||||||
self.remove_tag(parent)
|
self.remove_tag(parent)
|
||||||
|
|
||||||
|
|
||||||
log.debug('Applying tag {tag:s} to photo {pho:s}'.format(tag=tag, pho=self))
|
log.debug('Applying tag {tag:s} to photo {pho:s}'.format(tag=tag, pho=self))
|
||||||
|
now = int(getnow())
|
||||||
self.photodb.cur.execute('INSERT INTO photo_tag_rel VALUES(?, ?)', [self.id, tag.id])
|
self.photodb.cur.execute('INSERT INTO photo_tag_rel VALUES(?, ?)', [self.id, tag.id])
|
||||||
|
self.photodb.cur.execute('UPDATE photos SET tagged_at = ? WHERE id == ?', [now, self.id])
|
||||||
if commit:
|
if commit:
|
||||||
log.debug('Committing - add photo tag')
|
log.debug('Committing - add photo tag')
|
||||||
self.photodb.commit()
|
self.photodb.commit()
|
||||||
|
@ -1913,6 +1921,8 @@ class Photo(ObjectBase):
|
||||||
'DELETE FROM photo_tag_rel WHERE photoid == ? AND tagid == ?',
|
'DELETE FROM photo_tag_rel WHERE photoid == ? AND tagid == ?',
|
||||||
[self.id, tag.id]
|
[self.id, tag.id]
|
||||||
)
|
)
|
||||||
|
now = int(getnow())
|
||||||
|
self.photodb.cur.execute('UPDATE photos SET tagged_at = ? WHERE id == ?', [now, self.id])
|
||||||
if commit:
|
if commit:
|
||||||
log.debug('Committing - remove photo tag')
|
log.debug('Committing - remove photo tag')
|
||||||
self.photodb.commit()
|
self.photodb.commit()
|
||||||
|
|
25
etiquette/templates/bookmarks.html
Normal file
25
etiquette/templates/bookmarks.html
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<!DOCTYPE html5>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{% import "header.html" as header %}
|
||||||
|
<title>Bookmarks</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" href="/static/common.css">
|
||||||
|
<script src="/static/common.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{{header.make_header()}}
|
||||||
|
<div id="content_body">
|
||||||
|
<a href="/search?has_tags=no&orderby=random-desc&mimetype=image">Needs tagging</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
</script>
|
||||||
|
</html>
|
|
@ -33,6 +33,7 @@ a:hover
|
||||||
<a href="/search">Search</a>
|
<a href="/search">Search</a>
|
||||||
<a href="/tags">Browse tags</a>
|
<a href="/tags">Browse tags</a>
|
||||||
<a href="/albums">Browse albums</a>
|
<a href="/albums">Browse albums</a>
|
||||||
|
<a href="/bookmarks">Bookmarks</a>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,7 @@ form
|
||||||
<option value="ratio" {%if selected_column=="ratio"%}selected{%endif%} >Aspect Ratio</option>
|
<option value="ratio" {%if selected_column=="ratio"%}selected{%endif%} >Aspect Ratio</option>
|
||||||
<option value="bytes" {%if selected_column=="bytes"%}selected{%endif%} >File size</option>
|
<option value="bytes" {%if selected_column=="bytes"%}selected{%endif%} >File size</option>
|
||||||
<option value="duration" {%if selected_column=="duration"%}selected{%endif%} >Duration</option>
|
<option value="duration" {%if selected_column=="duration"%}selected{%endif%} >Duration</option>
|
||||||
|
<option value="tagged_at" {%if selected_column=="tagged_at"%}selected{%endif%}>Recently tagged</option>
|
||||||
<option value="random" {%if selected_column=="random"%}selected{%endif%} >Random</option>
|
<option value="random" {%if selected_column=="random"%}selected{%endif%} >Random</option>
|
||||||
</select>
|
</select>
|
||||||
<select>
|
<select>
|
||||||
|
|
Loading…
Reference in a new issue