Give Photos a searchhidden
property.
By default, photos with searchhidden do not appear in the search results. This allows a small number of representative images from a large album to appear in the results, while the rest can be found on the album's page. The same effect could be achieved with a tag and forbid search, but tag searching has much higher cost and it would be more difficult to implement as a default behavior without requiring lots of special checks whenever listing tags etc.
This commit is contained in:
parent
d88db08693
commit
5f6d21fdee
8 changed files with 135 additions and 7 deletions
|
@ -21,7 +21,7 @@ FILENAME_BADCHARS = '\\/:*?<>|"'
|
||||||
# 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 = 8
|
DATABASE_VERSION = 9
|
||||||
DB_INIT = '''
|
DB_INIT = '''
|
||||||
PRAGMA count_changes = OFF;
|
PRAGMA count_changes = OFF;
|
||||||
PRAGMA cache_size = 10000;
|
PRAGMA cache_size = 10000;
|
||||||
|
@ -93,7 +93,8 @@ CREATE TABLE IF NOT EXISTS photos(
|
||||||
created INT,
|
created INT,
|
||||||
thumbnail TEXT,
|
thumbnail TEXT,
|
||||||
tagged_at INT,
|
tagged_at INT,
|
||||||
author_id TEXT
|
author_id TEXT,
|
||||||
|
searchhidden INT
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS index_photos_id on photos(id);
|
CREATE INDEX IF NOT EXISTS index_photos_id on photos(id);
|
||||||
CREATE INDEX IF NOT EXISTS index_photos_filepath on photos(filepath COLLATE NOCASE);
|
CREATE INDEX IF NOT EXISTS index_photos_filepath on photos(filepath COLLATE NOCASE);
|
||||||
|
@ -102,6 +103,7 @@ CREATE INDEX IF NOT EXISTS index_photos_override_filename on
|
||||||
CREATE INDEX IF NOT EXISTS index_photos_created on photos(created);
|
CREATE INDEX IF NOT EXISTS index_photos_created on photos(created);
|
||||||
CREATE INDEX IF NOT EXISTS index_photos_extension on photos(extension);
|
CREATE INDEX IF NOT EXISTS index_photos_extension on photos(extension);
|
||||||
CREATE INDEX IF NOT EXISTS index_photos_author_id on photos(author_id);
|
CREATE INDEX IF NOT EXISTS index_photos_author_id on photos(author_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS index_photos_searchhidden on photos(searchhidden);
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
CREATE TABLE IF NOT EXISTS tag_group_rel(
|
CREATE TABLE IF NOT EXISTS tag_group_rel(
|
||||||
parentid TEXT,
|
parentid TEXT,
|
||||||
|
|
|
@ -53,6 +53,7 @@ def photo(p, include_albums=True, include_tags=True):
|
||||||
'created': p.created,
|
'created': p.created,
|
||||||
'filename': p.basename,
|
'filename': p.basename,
|
||||||
'mimetype': p.mimetype,
|
'mimetype': p.mimetype,
|
||||||
|
'searchhidden': bool(p.searchhidden),
|
||||||
}
|
}
|
||||||
if include_albums:
|
if include_albums:
|
||||||
j['albums'] = [album(a, minimal=True) for a in p.get_containing_albums()]
|
j['albums'] = [album(a, minimal=True) for a in p.get_containing_albums()]
|
||||||
|
|
|
@ -554,6 +554,7 @@ class Photo(ObjectBase):
|
||||||
self.height = db_row['height']
|
self.height = db_row['height']
|
||||||
self.ratio = db_row['ratio']
|
self.ratio = db_row['ratio']
|
||||||
self.thumbnail = db_row['thumbnail']
|
self.thumbnail = db_row['thumbnail']
|
||||||
|
self.searchhidden = db_row['searchhidden']
|
||||||
|
|
||||||
if self.duration and self.bytes is not None:
|
if self.duration and self.bytes is not None:
|
||||||
self.bitrate = (self.bytes / 128) / self.duration
|
self.bitrate = (self.bytes / 128) / self.duration
|
||||||
|
@ -1023,6 +1024,21 @@ class Photo(ObjectBase):
|
||||||
|
|
||||||
self.__reinit__()
|
self.__reinit__()
|
||||||
|
|
||||||
|
@decorators.required_feature('photo.edit')
|
||||||
|
@decorators.transaction
|
||||||
|
def set_searchhidden(self, searchhidden, *, commit=True):
|
||||||
|
data = {
|
||||||
|
'id': self.id,
|
||||||
|
'searchhidden': bool(searchhidden),
|
||||||
|
}
|
||||||
|
self.photodb.sql_update(table='photos', pairs=data, where_key='id')
|
||||||
|
|
||||||
|
self.searchhidden = searchhidden
|
||||||
|
|
||||||
|
if commit:
|
||||||
|
self.photodb.log.debug('Committing - set override filename')
|
||||||
|
self.photodb.commit()
|
||||||
|
|
||||||
@decorators.required_feature('photo.edit')
|
@decorators.required_feature('photo.edit')
|
||||||
@decorators.transaction
|
@decorators.transaction
|
||||||
def set_override_filename(self, new_filename, *, commit=True):
|
def set_override_filename(self, new_filename, *, commit=True):
|
||||||
|
|
|
@ -274,6 +274,7 @@ class PDBPhotoMixin:
|
||||||
'created': created,
|
'created': created,
|
||||||
'tagged_at': None,
|
'tagged_at': None,
|
||||||
'author_id': author_id,
|
'author_id': author_id,
|
||||||
|
'searchhidden': False,
|
||||||
# These will be filled in during the metadata stage.
|
# These will be filled in during the metadata stage.
|
||||||
'bytes': None,
|
'bytes': None,
|
||||||
'width': None,
|
'width': None,
|
||||||
|
@ -349,6 +350,7 @@ class PDBPhotoMixin:
|
||||||
filename=None,
|
filename=None,
|
||||||
has_tags=None,
|
has_tags=None,
|
||||||
has_thumbnail=None,
|
has_thumbnail=None,
|
||||||
|
is_searchhidden=False,
|
||||||
mimetype=None,
|
mimetype=None,
|
||||||
tag_musts=None,
|
tag_musts=None,
|
||||||
tag_mays=None,
|
tag_mays=None,
|
||||||
|
@ -398,6 +400,13 @@ class PDBPhotoMixin:
|
||||||
Require a thumbnail?
|
Require a thumbnail?
|
||||||
If None, anything is okay.
|
If None, anything is okay.
|
||||||
|
|
||||||
|
is_searchhidden:
|
||||||
|
Find photos that are marked as searchhidden?
|
||||||
|
If True, find *only* searchhidden photos.
|
||||||
|
If False, find *only* nonhidden photos.
|
||||||
|
If None, either is okay.
|
||||||
|
Default False.
|
||||||
|
|
||||||
mimetype:
|
mimetype:
|
||||||
A string or list of strings of acceptable mimetypes.
|
A string or list of strings of acceptable mimetypes.
|
||||||
'image', 'video', ...
|
'image', 'video', ...
|
||||||
|
@ -487,6 +496,7 @@ class PDBPhotoMixin:
|
||||||
|
|
||||||
limit = searchhelpers.normalize_limit(limit, warning_bag=warning_bag)
|
limit = searchhelpers.normalize_limit(limit, warning_bag=warning_bag)
|
||||||
has_thumbnail = searchhelpers.normalize_has_thumbnail(has_thumbnail)
|
has_thumbnail = searchhelpers.normalize_has_thumbnail(has_thumbnail)
|
||||||
|
is_searchhidden = searchhelpers.normalize_is_searchhidden(is_searchhidden)
|
||||||
|
|
||||||
offset = searchhelpers.normalize_offset(offset)
|
offset = searchhelpers.normalize_offset(offset)
|
||||||
if offset is None:
|
if offset is None:
|
||||||
|
@ -506,6 +516,7 @@ class PDBPhotoMixin:
|
||||||
|
|
||||||
notnulls = set()
|
notnulls = set()
|
||||||
yesnulls = set()
|
yesnulls = set()
|
||||||
|
wheres = []
|
||||||
if extension or mimetype:
|
if extension or mimetype:
|
||||||
notnulls.add('extension')
|
notnulls.add('extension')
|
||||||
if width or height or ratio or area:
|
if width or height or ratio or area:
|
||||||
|
@ -520,6 +531,11 @@ class PDBPhotoMixin:
|
||||||
elif has_thumbnail is False:
|
elif has_thumbnail is False:
|
||||||
yesnulls.add('thumbnail')
|
yesnulls.add('thumbnail')
|
||||||
|
|
||||||
|
if is_searchhidden is True:
|
||||||
|
wheres.append('searchhidden == 1')
|
||||||
|
elif is_searchhidden is False:
|
||||||
|
wheres.append('searchhidden == 0')
|
||||||
|
|
||||||
if orderby is None:
|
if orderby is None:
|
||||||
giveback_orderby = None
|
giveback_orderby = None
|
||||||
else:
|
else:
|
||||||
|
@ -611,6 +627,7 @@ class PDBPhotoMixin:
|
||||||
notnulls=notnulls,
|
notnulls=notnulls,
|
||||||
yesnulls=yesnulls,
|
yesnulls=yesnulls,
|
||||||
orderby=orderby,
|
orderby=orderby,
|
||||||
|
wheres=wheres,
|
||||||
)
|
)
|
||||||
print(query[:200])
|
print(query[:200])
|
||||||
generator = helpers.select_generator(self.sql, query)
|
generator = helpers.select_generator(self.sql, query)
|
||||||
|
|
|
@ -20,6 +20,7 @@ def build_query(
|
||||||
notnulls=None,
|
notnulls=None,
|
||||||
yesnulls=None,
|
yesnulls=None,
|
||||||
orderby=None,
|
orderby=None,
|
||||||
|
wheres=None,
|
||||||
):
|
):
|
||||||
|
|
||||||
if notnulls is None:
|
if notnulls is None:
|
||||||
|
@ -28,8 +29,12 @@ def build_query(
|
||||||
if yesnulls is None:
|
if yesnulls is None:
|
||||||
yesnulls = set()
|
yesnulls = set()
|
||||||
|
|
||||||
query = ['SELECT * FROM photos']
|
if wheres is None:
|
||||||
wheres = set()
|
wheres = set()
|
||||||
|
else:
|
||||||
|
wheres = set(wheres)
|
||||||
|
|
||||||
|
query = ['SELECT * FROM photos']
|
||||||
|
|
||||||
if author_ids:
|
if author_ids:
|
||||||
notnulls.add('author_id')
|
notnulls.add('author_id')
|
||||||
|
@ -275,6 +280,9 @@ def normalize_has_tags(has_tags):
|
||||||
def normalize_has_thumbnail(has_thumbnail):
|
def normalize_has_thumbnail(has_thumbnail):
|
||||||
return helpers.truthystring(has_thumbnail)
|
return helpers.truthystring(has_thumbnail)
|
||||||
|
|
||||||
|
def normalize_is_searchhidden(is_searchhidden):
|
||||||
|
return helpers.truthystring(is_searchhidden)
|
||||||
|
|
||||||
def normalize_limit(limit, warning_bag=None):
|
def normalize_limit(limit, warning_bag=None):
|
||||||
if not limit and limit != 0:
|
if not limit and limit != 0:
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -165,6 +165,32 @@ def post_batch_photos_refresh_metadata():
|
||||||
response = post_photo_refresh_metadata_core(photo_ids=request.form['photo_ids'])
|
response = post_photo_refresh_metadata_core(photo_ids=request.form['photo_ids'])
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@decorators.catch_etiquette_exception
|
||||||
|
def post_photo_searchhidden_core(photo_ids, searchhidden):
|
||||||
|
if isinstance(photo_ids, str):
|
||||||
|
photo_ids = etiquette.helpers.comma_space_split(photo_ids)
|
||||||
|
|
||||||
|
photos = [common.P_photo(photo_id, response_type='json') for photo_id in photo_ids]
|
||||||
|
|
||||||
|
for photo in photos:
|
||||||
|
photo.set_searchhidden(searchhidden, commit=False)
|
||||||
|
|
||||||
|
common.P.commit()
|
||||||
|
|
||||||
|
return jsonify.make_json_response({})
|
||||||
|
|
||||||
|
@site.route('/batch/photos/set_searchhidden', methods=['POST'])
|
||||||
|
@decorators.required_fields(['photo_ids'], forbid_whitespace=True)
|
||||||
|
def post_batch_photos_set_searchhidden():
|
||||||
|
response = post_photo_searchhidden_core(photo_ids=request.form['photo_ids'], searchhidden=True)
|
||||||
|
return response
|
||||||
|
|
||||||
|
@site.route('/batch/photos/unset_searchhidden', methods=['POST'])
|
||||||
|
@decorators.required_fields(['photo_ids'], forbid_whitespace=True)
|
||||||
|
def post_batch_photos_unset_searchhidden():
|
||||||
|
response = post_photo_searchhidden_core(photo_ids=request.form['photo_ids'], searchhidden=False)
|
||||||
|
return response
|
||||||
|
|
||||||
# Clipboard ########################################################################################
|
# Clipboard ########################################################################################
|
||||||
|
|
||||||
@site.route('/clipboard')
|
@site.route('/clipboard')
|
||||||
|
@ -214,6 +240,7 @@ def get_search_core():
|
||||||
extension = request.args.get('extension')
|
extension = request.args.get('extension')
|
||||||
extension_not = request.args.get('extension_not')
|
extension_not = request.args.get('extension_not')
|
||||||
mimetype = request.args.get('mimetype')
|
mimetype = request.args.get('mimetype')
|
||||||
|
is_searchhidden = request.args.get('is_searchhidden', False)
|
||||||
|
|
||||||
limit = request.args.get('limit')
|
limit = request.args.get('limit')
|
||||||
# This is being pre-processed because the site enforces a maximum value
|
# This is being pre-processed because the site enforces a maximum value
|
||||||
|
@ -255,6 +282,7 @@ def get_search_core():
|
||||||
'filename': filename_terms,
|
'filename': filename_terms,
|
||||||
'has_tags': has_tags,
|
'has_tags': has_tags,
|
||||||
'has_thumbnail': has_thumbnail,
|
'has_thumbnail': has_thumbnail,
|
||||||
|
'is_searchhidden': is_searchhidden,
|
||||||
'mimetype': mimetype,
|
'mimetype': mimetype,
|
||||||
'tag_musts': tag_musts,
|
'tag_musts': tag_musts,
|
||||||
'tag_mays': tag_mays,
|
'tag_mays': tag_mays,
|
||||||
|
|
|
@ -41,11 +41,12 @@ body
|
||||||
grid-area: right;
|
grid-area: right;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 1fr 1fr 1fr 1fr;
|
grid-template-rows: 75px 75px 75px 75px auto;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"add_tag_area"
|
"add_tag_area"
|
||||||
"remove_tag_area"
|
"remove_tag_area"
|
||||||
"refresh_metadata_area"
|
"refresh_metadata_area"
|
||||||
|
"searchhidden_area"
|
||||||
"message_area";
|
"message_area";
|
||||||
|
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
@ -65,11 +66,15 @@ body
|
||||||
grid-area: refresh_metadata_area;
|
grid-area: refresh_metadata_area;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
#searchhidden_area
|
||||||
|
{
|
||||||
|
grid-area: searchhidden_area;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
#message_area
|
#message_area
|
||||||
{
|
{
|
||||||
grid-area: message_area;
|
grid-area: message_area;
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
max-height: 300px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
@ -88,13 +93,23 @@ body
|
||||||
<input type="text" id="add_tag_textbox">
|
<input type="text" id="add_tag_textbox">
|
||||||
<button class="add_tag_button green_button" id="add_tag_button" onclick="submit_add_tag(add_remove_callback);">Add tag</button>
|
<button class="add_tag_button green_button" id="add_tag_button" onclick="submit_add_tag(add_remove_callback);">Add tag</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="remove_tag_area">
|
<div id="remove_tag_area">
|
||||||
<input type="text" id="remove_tag_textbox">
|
<input type="text" id="remove_tag_textbox">
|
||||||
<button class="red_button" id="remove_tag_button" onclick="submit_remove_tag(add_remove_callback);">Remove tag</button>
|
<button class="red_button" id="remove_tag_button" onclick="submit_remove_tag(add_remove_callback);">Remove tag</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="refresh_metadata_area">
|
<div id="refresh_metadata_area">
|
||||||
<button class="green_button" id="refresh_metadata_button" onclick="submit_refresh_metadata(refresh_metadata_callback);">Refresh metadata</button>
|
<button class="green_button" id="refresh_metadata_button" onclick="submit_refresh_metadata(refresh_metadata_callback);">Refresh metadata</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="searchhidden_area">
|
||||||
|
<span>
|
||||||
|
<button class="yellow_button" id="set_searchhidden_button" onclick="submit_set_searchhidden(searchhidden_callback)">Searchhide</button>
|
||||||
|
<button class="yellow_button" id="unset_searchhidden_button" onclick="submit_unset_searchhidden(searchhidden_callback)">Unhide</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="message_area">
|
<div id="message_area">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -276,5 +291,38 @@ function refresh_metadata_callback(response)
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function searchhidden_callback(response)
|
||||||
|
{
|
||||||
|
response = response["data"];
|
||||||
|
var message_area = document.getElementById("message_area");
|
||||||
|
var message_positivity;
|
||||||
|
var message_text;
|
||||||
|
if ("error_type" in response)
|
||||||
|
{
|
||||||
|
message_positivity = "message_negative";
|
||||||
|
message_text = response["error_message"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message_positivity = "message_positive";
|
||||||
|
message_text = "Success."
|
||||||
|
}
|
||||||
|
create_message_bubble(message_area, message_positivity, message_text, 8000);
|
||||||
|
}
|
||||||
|
function submit_set_searchhidden()
|
||||||
|
{
|
||||||
|
var url = "/batch/photos/set_searchhidden";
|
||||||
|
var data = new FormData();
|
||||||
|
data.append("photo_ids", Array.from(photo_clipboard).join(","));
|
||||||
|
post(url, data, searchhidden_callback);
|
||||||
|
}
|
||||||
|
function submit_unset_searchhidden()
|
||||||
|
{
|
||||||
|
var url = "/batch/photos/unset_searchhidden";
|
||||||
|
var data = new FormData();
|
||||||
|
data.append("photo_ids", Array.from(photo_clipboard).join(","));
|
||||||
|
post(url, data, searchhidden_callback);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import etiquette.photodb
|
import etiquette
|
||||||
|
|
||||||
def upgrade_1_to_2(sql):
|
def upgrade_1_to_2(sql):
|
||||||
'''
|
'''
|
||||||
|
@ -146,6 +146,14 @@ def upgrade_7_to_8(sql):
|
||||||
cur = sql.cursor()
|
cur = sql.cursor()
|
||||||
cur.execute('ALTER TABLE tags ADD COLUMN description TEXT')
|
cur.execute('ALTER TABLE tags ADD COLUMN description TEXT')
|
||||||
|
|
||||||
|
def upgrade_8_to_9(sql):
|
||||||
|
'''
|
||||||
|
Give the Photos table a searchhidden field.
|
||||||
|
'''
|
||||||
|
cur = sql.cursor()
|
||||||
|
cur.execute('ALTER TABLE photos ADD COLUMN searchhidden INT')
|
||||||
|
cur.execute('UPDATE photos SET searchhidden = 0')
|
||||||
|
cur.execute('CREATE INDEX index_photos_searchhidden on photos(searchhidden)')
|
||||||
|
|
||||||
def upgrade_all(database_filename):
|
def upgrade_all(database_filename):
|
||||||
'''
|
'''
|
||||||
|
@ -160,7 +168,7 @@ def upgrade_all(database_filename):
|
||||||
|
|
||||||
cur.execute('PRAGMA user_version')
|
cur.execute('PRAGMA user_version')
|
||||||
current_version = cur.fetchone()[0]
|
current_version = cur.fetchone()[0]
|
||||||
needed_version = etiquette.photodb.DATABASE_VERSION
|
needed_version = etiquette.constants.DATABASE_VERSION
|
||||||
|
|
||||||
if current_version == needed_version:
|
if current_version == needed_version:
|
||||||
print('Already up-to-date with version %d.' % needed_version)
|
print('Already up-to-date with version %d.' % needed_version)
|
||||||
|
|
Loading…
Reference in a new issue