Improve normalizers, use less None; Add author search box.
It was getting difficult to remember which of the normalizers use None and which don't. So let's try to be a little more consistent and just use empty sets, etc, so the caller can rely on receiving a set instead of having to check for None. Also renamed search parameter authors->author to be more in line with the singular form of extension.
This commit is contained in:
parent
088a79ffff
commit
0e3ae11610
5 changed files with 132 additions and 78 deletions
|
@ -366,11 +366,10 @@ def sql_listify(items):
|
||||||
|
|
||||||
def truthystring(s):
|
def truthystring(s):
|
||||||
'''
|
'''
|
||||||
Convert strings to True, False, or None based on the options presented
|
If s is already a boolean, int, or None, return a boolean or None.
|
||||||
|
If s is a string, return True, False, or None based on the options presented
|
||||||
in constants.TRUTHYSTRING_TRUE, constants.TRUTHYSTRING_NONE, or False
|
in constants.TRUTHYSTRING_TRUE, constants.TRUTHYSTRING_NONE, or False
|
||||||
for all else.
|
for all else. Case insensitive.
|
||||||
|
|
||||||
Case insensitive.
|
|
||||||
'''
|
'''
|
||||||
if s is None:
|
if s is None:
|
||||||
return None
|
return None
|
||||||
|
@ -378,6 +377,9 @@ def truthystring(s):
|
||||||
if isinstance(s, (bool, int)):
|
if isinstance(s, (bool, int)):
|
||||||
return bool(s)
|
return bool(s)
|
||||||
|
|
||||||
|
if not isinstance(s, str):
|
||||||
|
raise TypeError('Unsupported type %s' % type(s))
|
||||||
|
|
||||||
s = s.lower()
|
s = s.lower()
|
||||||
if s in constants.TRUTHYSTRING_TRUE:
|
if s in constants.TRUTHYSTRING_TRUE:
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -314,7 +314,7 @@ class PDBPhotoMixin:
|
||||||
bytes=None,
|
bytes=None,
|
||||||
duration=None,
|
duration=None,
|
||||||
|
|
||||||
authors=None,
|
author=None,
|
||||||
created=None,
|
created=None,
|
||||||
extension=None,
|
extension=None,
|
||||||
extension_not=None,
|
extension_not=None,
|
||||||
|
@ -341,7 +341,7 @@ class PDBPhotoMixin:
|
||||||
for lower bound.
|
for lower bound.
|
||||||
|
|
||||||
TAGS AND FILTERS
|
TAGS AND FILTERS
|
||||||
authors:
|
author:
|
||||||
A list of User objects, or usernames, or user ids.
|
A list of User objects, or usernames, or user ids.
|
||||||
|
|
||||||
created:
|
created:
|
||||||
|
@ -381,6 +381,8 @@ class PDBPhotoMixin:
|
||||||
mimetype:
|
mimetype:
|
||||||
A string or list of strings of acceptable mimetypes.
|
A string or list of strings of acceptable mimetypes.
|
||||||
'image', 'video', ...
|
'image', 'video', ...
|
||||||
|
Note we are only interested in the simple "video", "audio" etc.
|
||||||
|
For exact mimetypes you might as well use an extension search.
|
||||||
|
|
||||||
tag_musts:
|
tag_musts:
|
||||||
A list of tag names or Tag objects.
|
A list of tag names or Tag objects.
|
||||||
|
@ -436,14 +438,14 @@ class PDBPhotoMixin:
|
||||||
searchhelpers.minmax('bytes', bytes, minimums, maximums, warning_bag=warning_bag)
|
searchhelpers.minmax('bytes', bytes, minimums, maximums, warning_bag=warning_bag)
|
||||||
searchhelpers.minmax('duration', duration, minimums, maximums, warning_bag=warning_bag)
|
searchhelpers.minmax('duration', duration, minimums, maximums, warning_bag=warning_bag)
|
||||||
|
|
||||||
authors = searchhelpers.normalize_authors(authors, photodb=self, warning_bag=warning_bag)
|
author = searchhelpers.normalize_author(author, photodb=self, warning_bag=warning_bag)
|
||||||
extension = searchhelpers.normalize_extensions(extension)
|
extension = searchhelpers.normalize_extension(extension)
|
||||||
extension_not = searchhelpers.normalize_extensions(extension_not)
|
extension_not = searchhelpers.normalize_extension(extension_not)
|
||||||
filename = searchhelpers.normalize_filename(filename)
|
filename = searchhelpers.normalize_filename(filename)
|
||||||
has_tags = searchhelpers.normalize_has_tags(has_tags)
|
has_tags = searchhelpers.normalize_has_tags(has_tags)
|
||||||
has_thumbnail = searchhelpers.normalize_has_thumbnail(has_thumbnail)
|
has_thumbnail = searchhelpers.normalize_has_thumbnail(has_thumbnail)
|
||||||
is_searchhidden = searchhelpers.normalize_is_searchhidden(is_searchhidden)
|
is_searchhidden = searchhelpers.normalize_is_searchhidden(is_searchhidden)
|
||||||
mimetype = searchhelpers.normalize_extensions(mimetype)
|
mimetype = searchhelpers.normalize_extension(mimetype)
|
||||||
|
|
||||||
if has_tags is False:
|
if has_tags is False:
|
||||||
tag_musts = None
|
tag_musts = None
|
||||||
|
@ -494,8 +496,8 @@ class PDBPhotoMixin:
|
||||||
# has_tags check is redundant then, so disable it.
|
# has_tags check is redundant then, so disable it.
|
||||||
has_tags = None
|
has_tags = None
|
||||||
|
|
||||||
limit = searchhelpers.normalize_positive_integer(limit, warning_bag=warning_bag)
|
limit = searchhelpers.normalize_limit(limit, warning_bag=warning_bag)
|
||||||
offset = searchhelpers.normalize_positive_integer(offset, warning_bag=warning_bag)
|
offset = searchhelpers.normalize_offset(offset, warning_bag=warning_bag)
|
||||||
orderby = searchhelpers.normalize_orderby(orderby, warning_bag=warning_bag)
|
orderby = searchhelpers.normalize_orderby(orderby, warning_bag=warning_bag)
|
||||||
|
|
||||||
if filename:
|
if filename:
|
||||||
|
@ -523,14 +525,14 @@ class PDBPhotoMixin:
|
||||||
'ratio': ratio,
|
'ratio': ratio,
|
||||||
'bytes': bytes,
|
'bytes': bytes,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'authors': authors,
|
'author': list(author) or None,
|
||||||
'created': created,
|
'created': created,
|
||||||
'extension': extension or None,
|
'extension': list(extension) or None,
|
||||||
'extension_not': extension_not or None,
|
'extension_not': list(extension_not) or None,
|
||||||
'filename': filename or None,
|
'filename': filename or None,
|
||||||
'has_tags': has_tags,
|
'has_tags': has_tags,
|
||||||
'has_thumbnail': has_thumbnail,
|
'has_thumbnail': has_thumbnail,
|
||||||
'mimetype': mimetype or None,
|
'mimetype': list(mimetype) or None,
|
||||||
'tag_musts': tag_musts or None,
|
'tag_musts': tag_musts or None,
|
||||||
'tag_mays': tag_mays or None,
|
'tag_mays': tag_mays or None,
|
||||||
'tag_forbids': tag_forbids or None,
|
'tag_forbids': tag_forbids or None,
|
||||||
|
@ -552,8 +554,9 @@ class PDBPhotoMixin:
|
||||||
wheres = []
|
wheres = []
|
||||||
bindings = []
|
bindings = []
|
||||||
|
|
||||||
if authors:
|
if author:
|
||||||
wheres.append('author_id IN %s' % helpers.sql_listify(authors))
|
author_ids = [user.id for user in author]
|
||||||
|
wheres.append('author_id IN %s' % helpers.sql_listify(author_ids))
|
||||||
|
|
||||||
if extension:
|
if extension:
|
||||||
if '*' in extension:
|
if '*' in extension:
|
||||||
|
@ -654,7 +657,7 @@ class PDBPhotoMixin:
|
||||||
if not success:
|
if not success:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if offset is not None and offset > 0:
|
if offset > 0:
|
||||||
offset -= 1
|
offset -= 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
@ -122,24 +122,23 @@ def minmax(key, value, minimums, maximums, warning_bag=None):
|
||||||
if high is not None:
|
if high is not None:
|
||||||
maximums[key] = high
|
maximums[key] = high
|
||||||
|
|
||||||
def normalize_authors(authors, photodb, warning_bag=None):
|
def normalize_author(authors, photodb, warning_bag=None):
|
||||||
'''
|
'''
|
||||||
Either:
|
Either:
|
||||||
- A string, where the usernames are separated by commas
|
- A string, where the usernames are separated by commas
|
||||||
- An iterable containing
|
- An iterable containing
|
||||||
- Usernames
|
- Usernames as strings
|
||||||
- User IDs
|
|
||||||
- User objects
|
- User objects
|
||||||
|
|
||||||
Returns: A set of user IDs.
|
Returns: A set of user IDs.
|
||||||
'''
|
'''
|
||||||
if not authors:
|
if authors is None:
|
||||||
return None
|
authors = []
|
||||||
|
|
||||||
if isinstance(authors, str):
|
if isinstance(authors, str):
|
||||||
authors = helpers.comma_space_split(authors)
|
authors = helpers.comma_space_split(authors)
|
||||||
|
|
||||||
user_ids = set()
|
users = set()
|
||||||
for requested_author in authors:
|
for requested_author in authors:
|
||||||
if isinstance(requested_author, objects.User):
|
if isinstance(requested_author, objects.User):
|
||||||
if requested_author.photodb == photodb:
|
if requested_author.photodb == photodb:
|
||||||
|
@ -155,14 +154,18 @@ def normalize_authors(authors, photodb, warning_bag=None):
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
user_ids.add(user.id)
|
users.add(user)
|
||||||
|
|
||||||
if len(user_ids) == 0:
|
return users
|
||||||
return None
|
|
||||||
|
|
||||||
return user_ids
|
def normalize_extension(extensions):
|
||||||
|
'''
|
||||||
|
Either:
|
||||||
|
- A string, where extensions are separated by commas or spaces.
|
||||||
|
- An iterable containing extensions as strings.
|
||||||
|
|
||||||
def normalize_extensions(extensions):
|
Returns: A set of strings with no leading dots.
|
||||||
|
'''
|
||||||
if extensions is None:
|
if extensions is None:
|
||||||
extensions = set()
|
extensions = set()
|
||||||
|
|
||||||
|
@ -175,44 +178,97 @@ def normalize_extensions(extensions):
|
||||||
return extensions
|
return extensions
|
||||||
|
|
||||||
def normalize_filename(filename_terms):
|
def normalize_filename(filename_terms):
|
||||||
if not filename_terms:
|
'''
|
||||||
return None
|
Either:
|
||||||
|
- A string.
|
||||||
|
- An iterable containing search terms as strings.
|
||||||
|
|
||||||
|
Returns: A string where terms are separated by spaces.
|
||||||
|
'''
|
||||||
|
if filename_terms is None:
|
||||||
|
filename_terms = ''
|
||||||
|
|
||||||
if not isinstance(filename_terms, str):
|
if not isinstance(filename_terms, str):
|
||||||
filename_terms = ' '.join(filename_terms)
|
filename_terms = ' '.join(filename_terms)
|
||||||
|
|
||||||
filename_terms = filename_terms.strip()
|
filename_terms = filename_terms.strip()
|
||||||
|
|
||||||
if not filename_terms:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return filename_terms
|
return filename_terms
|
||||||
|
|
||||||
def normalize_has_tags(has_tags):
|
def normalize_has_tags(has_tags):
|
||||||
if not has_tags:
|
'''
|
||||||
return None
|
See etiquette.helpers.truthystring.
|
||||||
|
'''
|
||||||
if isinstance(has_tags, str):
|
return helpers.truthystring(has_tags)
|
||||||
return helpers.truthystring(has_tags)
|
|
||||||
|
|
||||||
if isinstance(has_tags, int):
|
|
||||||
return bool(has_tags)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def normalize_has_thumbnail(has_thumbnail):
|
def normalize_has_thumbnail(has_thumbnail):
|
||||||
|
'''
|
||||||
|
See etiquette.helpers.truthystring.
|
||||||
|
'''
|
||||||
return helpers.truthystring(has_thumbnail)
|
return helpers.truthystring(has_thumbnail)
|
||||||
|
|
||||||
def normalize_is_searchhidden(is_searchhidden):
|
def normalize_is_searchhidden(is_searchhidden):
|
||||||
|
'''
|
||||||
|
See etiquette.helpers.truthystring.
|
||||||
|
'''
|
||||||
return helpers.truthystring(is_searchhidden)
|
return helpers.truthystring(is_searchhidden)
|
||||||
|
|
||||||
|
def _limit_offset(number, warning_bag):
|
||||||
|
if number is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
number = normalize_positive_integer(number)
|
||||||
|
except ValueError as exc:
|
||||||
|
if warning_bag:
|
||||||
|
warning_bag.add(exc)
|
||||||
|
number = 0
|
||||||
|
return number
|
||||||
|
|
||||||
def normalize_limit(limit, warning_bag=None):
|
def normalize_limit(limit, warning_bag=None):
|
||||||
return normalize_positive_integer(limit, warning_bag)
|
'''
|
||||||
|
Either:
|
||||||
|
- None to indicate unlimited.
|
||||||
|
- A non-negative number as an int, float, or string.
|
||||||
|
|
||||||
|
Returns: None or a positive integer.
|
||||||
|
'''
|
||||||
|
return _limit_offset(limit, warning_bag)
|
||||||
|
|
||||||
|
def normalize_mimetype(mimetype, warning_bag=None):
|
||||||
|
'''
|
||||||
|
Either:
|
||||||
|
- A string, where mimetypes are separated by commas or spaces.
|
||||||
|
- An iterable containing mimetypes as strings.
|
||||||
|
|
||||||
|
Returns: A set of strings.
|
||||||
|
'''
|
||||||
|
return normalize_extensions(mimetype, warning_bag)
|
||||||
|
|
||||||
def normalize_offset(offset, warning_bag=None):
|
def normalize_offset(offset, warning_bag=None):
|
||||||
return normalize_positive_integer(limit, warning_bag)
|
'''
|
||||||
|
Either:
|
||||||
|
- None.
|
||||||
|
- A non-negative number as an int, float, or string.
|
||||||
|
|
||||||
|
Returns: None or a positive integer.
|
||||||
|
'''
|
||||||
|
if offset is None:
|
||||||
|
return 0
|
||||||
|
return _limit_offset(offset, warning_bag)
|
||||||
|
|
||||||
def normalize_orderby(orderby, warning_bag=None):
|
def normalize_orderby(orderby, warning_bag=None):
|
||||||
|
'''
|
||||||
|
Either:
|
||||||
|
- A string of orderbys separated by commas, where a single orderby consists
|
||||||
|
of 'column' or 'column-direction' or 'column direction'.
|
||||||
|
- A list of such orderby strings.
|
||||||
|
- A list of tuples of (column, direction)
|
||||||
|
|
||||||
|
With no direction, direction is implied desc.
|
||||||
|
|
||||||
|
Returns: A list of tuples of (column, direction)
|
||||||
|
'''
|
||||||
if orderby is None:
|
if orderby is None:
|
||||||
orderby = []
|
orderby = []
|
||||||
|
|
||||||
|
@ -222,11 +278,14 @@ def normalize_orderby(orderby, warning_bag=None):
|
||||||
|
|
||||||
final_orderby = []
|
final_orderby = []
|
||||||
for requested_order in orderby:
|
for requested_order in orderby:
|
||||||
requested_order = requested_order.lower().strip()
|
if isinstance(requested_order, str):
|
||||||
if not requested_order:
|
requested_order = requested_order.strip().lower()
|
||||||
continue
|
if not requested_order:
|
||||||
|
continue
|
||||||
|
split_order = requested_order.split()
|
||||||
|
else:
|
||||||
|
split_order = tuple(x.strip().lower() for x in requested_order)
|
||||||
|
|
||||||
split_order = requested_order.split(' ')
|
|
||||||
if len(split_order) == 2:
|
if len(split_order) == 2:
|
||||||
(column, direction) = split_order
|
(column, direction) = split_order
|
||||||
|
|
||||||
|
@ -235,7 +294,7 @@ def normalize_orderby(orderby, warning_bag=None):
|
||||||
direction = 'desc'
|
direction = 'desc'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
message = constants.WARNING_ORDERBY_INVALID.format(requested=requested_order)
|
message = constants.WARNING_ORDERBY_INVALID.format(request=requested_order)
|
||||||
if warning_bag:
|
if warning_bag:
|
||||||
warning_bag.add(message)
|
warning_bag.add(message)
|
||||||
else:
|
else:
|
||||||
|
@ -269,36 +328,19 @@ def normalize_orderby(orderby, warning_bag=None):
|
||||||
|
|
||||||
return final_orderby
|
return final_orderby
|
||||||
|
|
||||||
def normalize_positive_integer(number, warning_bag=None):
|
def normalize_positive_integer(number):
|
||||||
if number is None:
|
if number is None:
|
||||||
return None
|
|
||||||
|
|
||||||
if not number:
|
|
||||||
number = 0
|
number = 0
|
||||||
|
|
||||||
elif isinstance(number, str):
|
elif isinstance(number, str):
|
||||||
number = number.strip()
|
# Convert to float, then int, just in case they type '-4.5'
|
||||||
try:
|
# because int('-4.5') does not work.
|
||||||
number = int(number)
|
number = float(number)
|
||||||
except ValueError as exc:
|
|
||||||
if warning_bag:
|
|
||||||
warning_bag.add(exc)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
elif isinstance(number, float):
|
number = int(number)
|
||||||
number = int(number)
|
|
||||||
|
|
||||||
if not isinstance(number, int):
|
|
||||||
message = 'Invalid number "%s"' % number
|
|
||||||
if warning_bag:
|
|
||||||
warning_bag.add(message)
|
|
||||||
number = None
|
|
||||||
else:
|
|
||||||
raise ValueError(message)
|
|
||||||
|
|
||||||
if number < 0:
|
if number < 0:
|
||||||
raise ValueError('Invalid number %d' % number)
|
raise ValueError('%d must be >= 0.' % number)
|
||||||
|
|
||||||
return number
|
return number
|
||||||
|
|
||||||
|
|
|
@ -254,7 +254,7 @@ def get_search_core():
|
||||||
|
|
||||||
offset = request.args.get('offset')
|
offset = request.args.get('offset')
|
||||||
|
|
||||||
authors = request.args.get('author')
|
author = request.args.get('author')
|
||||||
|
|
||||||
orderby = request.args.get('orderby')
|
orderby = request.args.get('orderby')
|
||||||
area = request.args.get('area')
|
area = request.args.get('area')
|
||||||
|
@ -275,7 +275,7 @@ def get_search_core():
|
||||||
'bytes': bytes,
|
'bytes': bytes,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
|
|
||||||
'authors': authors,
|
'author': author,
|
||||||
'created': created,
|
'created': created,
|
||||||
'extension': extension,
|
'extension': extension,
|
||||||
'extension_not': extension_not,
|
'extension_not': extension_not,
|
||||||
|
@ -308,6 +308,9 @@ def get_search_core():
|
||||||
search_kwargs['extension_not'] = join_helper(search_kwargs['extension_not'])
|
search_kwargs['extension_not'] = join_helper(search_kwargs['extension_not'])
|
||||||
search_kwargs['mimetype'] = join_helper(search_kwargs['mimetype'])
|
search_kwargs['mimetype'] = join_helper(search_kwargs['mimetype'])
|
||||||
|
|
||||||
|
author_helper = lambda users: ', '.join(user.username for user in users) if users else None
|
||||||
|
search_kwargs['author'] = author_helper(search_kwargs['author'])
|
||||||
|
|
||||||
tagname_helper = lambda tags: [tag.qualified_name() for tag in tags] if tags else None
|
tagname_helper = lambda tags: [tag.qualified_name() for tag in tags] if tags else None
|
||||||
search_kwargs['tag_musts'] = tagname_helper(search_kwargs['tag_musts'])
|
search_kwargs['tag_musts'] = tagname_helper(search_kwargs['tag_musts'])
|
||||||
search_kwargs['tag_mays'] = tagname_helper(search_kwargs['tag_mays'])
|
search_kwargs['tag_mays'] = tagname_helper(search_kwargs['tag_mays'])
|
||||||
|
|
|
@ -244,8 +244,12 @@ form
|
||||||
name="extension" placeholder="Extension(s)">
|
name="extension" placeholder="Extension(s)">
|
||||||
|
|
||||||
<input type="text" class="basic_param"
|
<input type="text" class="basic_param"
|
||||||
value="{%if search_kwargs['extension_not']%}{{search_kwargs['extension_not']}}{%endif%}"
|
value="{%if search_kwargs['extension_not']%}{{search_kwargs['extension_not']}}{%endif%}"
|
||||||
name="extension_not" placeholder="Forbid extension(s)">
|
name="extension_not" placeholder="Forbid extension(s)">
|
||||||
|
|
||||||
|
<input type="text" class="basic_param"
|
||||||
|
value="{%if search_kwargs['author']%}{{search_kwargs['author']}}{%endif%}"
|
||||||
|
name="author" placeholder="Author">
|
||||||
|
|
||||||
<select name="limit" class="basic_param">
|
<select name="limit" class="basic_param">
|
||||||
{% set limit_options = [20, 50, 100] %}
|
{% set limit_options = [20, 50, 100] %}
|
||||||
|
|
Loading…
Reference in a new issue