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):
|
||||
'''
|
||||
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
|
||||
for all else.
|
||||
|
||||
Case insensitive.
|
||||
for all else. Case insensitive.
|
||||
'''
|
||||
if s is None:
|
||||
return None
|
||||
|
@ -378,6 +377,9 @@ def truthystring(s):
|
|||
if isinstance(s, (bool, int)):
|
||||
return bool(s)
|
||||
|
||||
if not isinstance(s, str):
|
||||
raise TypeError('Unsupported type %s' % type(s))
|
||||
|
||||
s = s.lower()
|
||||
if s in constants.TRUTHYSTRING_TRUE:
|
||||
return True
|
||||
|
|
|
@ -314,7 +314,7 @@ class PDBPhotoMixin:
|
|||
bytes=None,
|
||||
duration=None,
|
||||
|
||||
authors=None,
|
||||
author=None,
|
||||
created=None,
|
||||
extension=None,
|
||||
extension_not=None,
|
||||
|
@ -341,7 +341,7 @@ class PDBPhotoMixin:
|
|||
for lower bound.
|
||||
|
||||
TAGS AND FILTERS
|
||||
authors:
|
||||
author:
|
||||
A list of User objects, or usernames, or user ids.
|
||||
|
||||
created:
|
||||
|
@ -381,6 +381,8 @@ class PDBPhotoMixin:
|
|||
mimetype:
|
||||
A string or list of strings of acceptable mimetypes.
|
||||
'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:
|
||||
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('duration', duration, minimums, maximums, warning_bag=warning_bag)
|
||||
|
||||
authors = searchhelpers.normalize_authors(authors, photodb=self, warning_bag=warning_bag)
|
||||
extension = searchhelpers.normalize_extensions(extension)
|
||||
extension_not = searchhelpers.normalize_extensions(extension_not)
|
||||
author = searchhelpers.normalize_author(author, photodb=self, warning_bag=warning_bag)
|
||||
extension = searchhelpers.normalize_extension(extension)
|
||||
extension_not = searchhelpers.normalize_extension(extension_not)
|
||||
filename = searchhelpers.normalize_filename(filename)
|
||||
has_tags = searchhelpers.normalize_has_tags(has_tags)
|
||||
has_thumbnail = searchhelpers.normalize_has_thumbnail(has_thumbnail)
|
||||
is_searchhidden = searchhelpers.normalize_is_searchhidden(is_searchhidden)
|
||||
mimetype = searchhelpers.normalize_extensions(mimetype)
|
||||
mimetype = searchhelpers.normalize_extension(mimetype)
|
||||
|
||||
if has_tags is False:
|
||||
tag_musts = None
|
||||
|
@ -494,8 +496,8 @@ class PDBPhotoMixin:
|
|||
# has_tags check is redundant then, so disable it.
|
||||
has_tags = None
|
||||
|
||||
limit = searchhelpers.normalize_positive_integer(limit, warning_bag=warning_bag)
|
||||
offset = searchhelpers.normalize_positive_integer(offset, warning_bag=warning_bag)
|
||||
limit = searchhelpers.normalize_limit(limit, warning_bag=warning_bag)
|
||||
offset = searchhelpers.normalize_offset(offset, warning_bag=warning_bag)
|
||||
orderby = searchhelpers.normalize_orderby(orderby, warning_bag=warning_bag)
|
||||
|
||||
if filename:
|
||||
|
@ -523,14 +525,14 @@ class PDBPhotoMixin:
|
|||
'ratio': ratio,
|
||||
'bytes': bytes,
|
||||
'duration': duration,
|
||||
'authors': authors,
|
||||
'author': list(author) or None,
|
||||
'created': created,
|
||||
'extension': extension or None,
|
||||
'extension_not': extension_not or None,
|
||||
'extension': list(extension) or None,
|
||||
'extension_not': list(extension_not) or None,
|
||||
'filename': filename or None,
|
||||
'has_tags': has_tags,
|
||||
'has_thumbnail': has_thumbnail,
|
||||
'mimetype': mimetype or None,
|
||||
'mimetype': list(mimetype) or None,
|
||||
'tag_musts': tag_musts or None,
|
||||
'tag_mays': tag_mays or None,
|
||||
'tag_forbids': tag_forbids or None,
|
||||
|
@ -552,8 +554,9 @@ class PDBPhotoMixin:
|
|||
wheres = []
|
||||
bindings = []
|
||||
|
||||
if authors:
|
||||
wheres.append('author_id IN %s' % helpers.sql_listify(authors))
|
||||
if author:
|
||||
author_ids = [user.id for user in author]
|
||||
wheres.append('author_id IN %s' % helpers.sql_listify(author_ids))
|
||||
|
||||
if extension:
|
||||
if '*' in extension:
|
||||
|
@ -654,7 +657,7 @@ class PDBPhotoMixin:
|
|||
if not success:
|
||||
continue
|
||||
|
||||
if offset is not None and offset > 0:
|
||||
if offset > 0:
|
||||
offset -= 1
|
||||
continue
|
||||
|
||||
|
|
|
@ -122,24 +122,23 @@ def minmax(key, value, minimums, maximums, warning_bag=None):
|
|||
if high is not None:
|
||||
maximums[key] = high
|
||||
|
||||
def normalize_authors(authors, photodb, warning_bag=None):
|
||||
def normalize_author(authors, photodb, warning_bag=None):
|
||||
'''
|
||||
Either:
|
||||
- A string, where the usernames are separated by commas
|
||||
- An iterable containing
|
||||
- Usernames
|
||||
- User IDs
|
||||
- Usernames as strings
|
||||
- User objects
|
||||
|
||||
Returns: A set of user IDs.
|
||||
'''
|
||||
if not authors:
|
||||
return None
|
||||
if authors is None:
|
||||
authors = []
|
||||
|
||||
if isinstance(authors, str):
|
||||
authors = helpers.comma_space_split(authors)
|
||||
|
||||
user_ids = set()
|
||||
users = set()
|
||||
for requested_author in authors:
|
||||
if isinstance(requested_author, objects.User):
|
||||
if requested_author.photodb == photodb:
|
||||
|
@ -155,14 +154,18 @@ def normalize_authors(authors, photodb, warning_bag=None):
|
|||
else:
|
||||
raise
|
||||
else:
|
||||
user_ids.add(user.id)
|
||||
users.add(user)
|
||||
|
||||
if len(user_ids) == 0:
|
||||
return None
|
||||
return users
|
||||
|
||||
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:
|
||||
extensions = set()
|
||||
|
||||
|
@ -175,44 +178,97 @@ def normalize_extensions(extensions):
|
|||
return extensions
|
||||
|
||||
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):
|
||||
filename_terms = ' '.join(filename_terms)
|
||||
|
||||
filename_terms = filename_terms.strip()
|
||||
|
||||
if not filename_terms:
|
||||
return None
|
||||
|
||||
return filename_terms
|
||||
|
||||
def normalize_has_tags(has_tags):
|
||||
if not has_tags:
|
||||
return None
|
||||
|
||||
if isinstance(has_tags, str):
|
||||
'''
|
||||
See etiquette.helpers.truthystring.
|
||||
'''
|
||||
return helpers.truthystring(has_tags)
|
||||
|
||||
if isinstance(has_tags, int):
|
||||
return bool(has_tags)
|
||||
|
||||
return None
|
||||
|
||||
def normalize_has_thumbnail(has_thumbnail):
|
||||
'''
|
||||
See etiquette.helpers.truthystring.
|
||||
'''
|
||||
return helpers.truthystring(has_thumbnail)
|
||||
|
||||
def normalize_is_searchhidden(is_searchhidden):
|
||||
'''
|
||||
See etiquette.helpers.truthystring.
|
||||
'''
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
'''
|
||||
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:
|
||||
orderby = []
|
||||
|
||||
|
@ -222,11 +278,14 @@ def normalize_orderby(orderby, warning_bag=None):
|
|||
|
||||
final_orderby = []
|
||||
for requested_order in orderby:
|
||||
requested_order = requested_order.lower().strip()
|
||||
if isinstance(requested_order, str):
|
||||
requested_order = requested_order.strip().lower()
|
||||
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:
|
||||
(column, direction) = split_order
|
||||
|
||||
|
@ -235,7 +294,7 @@ def normalize_orderby(orderby, warning_bag=None):
|
|||
direction = 'desc'
|
||||
|
||||
else:
|
||||
message = constants.WARNING_ORDERBY_INVALID.format(requested=requested_order)
|
||||
message = constants.WARNING_ORDERBY_INVALID.format(request=requested_order)
|
||||
if warning_bag:
|
||||
warning_bag.add(message)
|
||||
else:
|
||||
|
@ -269,36 +328,19 @@ def normalize_orderby(orderby, warning_bag=None):
|
|||
|
||||
return final_orderby
|
||||
|
||||
def normalize_positive_integer(number, warning_bag=None):
|
||||
def normalize_positive_integer(number):
|
||||
if number is None:
|
||||
return None
|
||||
|
||||
if not number:
|
||||
number = 0
|
||||
|
||||
elif isinstance(number, str):
|
||||
number = number.strip()
|
||||
try:
|
||||
number = int(number)
|
||||
except ValueError as exc:
|
||||
if warning_bag:
|
||||
warning_bag.add(exc)
|
||||
else:
|
||||
raise
|
||||
# Convert to float, then int, just in case they type '-4.5'
|
||||
# because int('-4.5') does not work.
|
||||
number = float(number)
|
||||
|
||||
elif isinstance(number, float):
|
||||
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:
|
||||
raise ValueError('Invalid number %d' % number)
|
||||
raise ValueError('%d must be >= 0.' % number)
|
||||
|
||||
return number
|
||||
|
||||
|
|
|
@ -254,7 +254,7 @@ def get_search_core():
|
|||
|
||||
offset = request.args.get('offset')
|
||||
|
||||
authors = request.args.get('author')
|
||||
author = request.args.get('author')
|
||||
|
||||
orderby = request.args.get('orderby')
|
||||
area = request.args.get('area')
|
||||
|
@ -275,7 +275,7 @@ def get_search_core():
|
|||
'bytes': bytes,
|
||||
'duration': duration,
|
||||
|
||||
'authors': authors,
|
||||
'author': author,
|
||||
'created': created,
|
||||
'extension': extension,
|
||||
'extension_not': extension_not,
|
||||
|
@ -308,6 +308,9 @@ def get_search_core():
|
|||
search_kwargs['extension_not'] = join_helper(search_kwargs['extension_not'])
|
||||
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
|
||||
search_kwargs['tag_musts'] = tagname_helper(search_kwargs['tag_musts'])
|
||||
search_kwargs['tag_mays'] = tagname_helper(search_kwargs['tag_mays'])
|
||||
|
|
|
@ -247,6 +247,10 @@ form
|
|||
value="{%if search_kwargs['extension_not']%}{{search_kwargs['extension_not']}}{%endif%}"
|
||||
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">
|
||||
{% set limit_options = [20, 50, 100] %}
|
||||
{% if search_kwargs['limit'] not in limit_options %}
|
||||
|
|
Loading…
Reference in a new issue