diff --git a/etiquette/objects.py b/etiquette/objects.py index b28725f..091f0d1 100644 --- a/etiquette/objects.py +++ b/etiquette/objects.py @@ -171,13 +171,13 @@ class GroupableMixin: class Album(ObjectBase, GroupableMixin): - def __init__(self, photodb, row_tuple): + def __init__(self, photodb, db_row): self.photodb = photodb - if isinstance(row_tuple, (list, tuple)): - row_tuple = {constants.SQL_ALBUM_COLUMNS[index]: value for (index, value) in enumerate(row_tuple)} - self.id = row_tuple['id'] - self.title = row_tuple['title'] - self.description = row_tuple['description'] + if isinstance(db_row, (list, tuple)): + db_row = {constants.SQL_ALBUM_COLUMNS[index]: value for (index, value) in enumerate(db_row)} + self.id = db_row['id'] + self.title = db_row['title'] + self.description = db_row['description'] self.name = 'Album %s' % self.id self.group_getter = self.photodb.get_album @@ -285,27 +285,25 @@ class Album(ObjectBase, GroupableMixin): else: return total - def walk_photos(self): yield from self.photos() children = self.walk_children() # The first yield is itself next(children) for child in children: - print(child) yield from child.walk_photos() class Bookmark(ObjectBase): - def __init__(self, photodb, row_tuple): + def __init__(self, photodb, db_row): self.photodb = photodb - if isinstance(row_tuple, (list, tuple)): - row_tuple = {constants.SQL_BOOKMARK_COLUMNS[index]: value for (index, value) in enumerate(row_tuple)} + if isinstance(db_row, (list, tuple)): + db_row = {constants.SQL_BOOKMARK_COLUMNS[index]: value for (index, value) in enumerate(db_row)} - self.id = row_tuple['id'] - self.title = row_tuple['title'] - self.url = row_tuple['url'] - self.author_id = row_tuple['author_id'] + self.id = db_row['id'] + self.title = db_row['title'] + self.url = db_row['url'] + self.author_id = db_row['author_id'] def __repr__(self): return 'Bookmark:{id}'.format(id=self.id) @@ -339,34 +337,34 @@ class Photo(ObjectBase): Photo objects cannot exist without a corresponding PhotoDB object, because Photos are not the actual image data, just the database entry. ''' - def __init__(self, photodb, row_tuple): + def __init__(self, photodb, db_row): self.photodb = photodb - if isinstance(row_tuple, (list, tuple)): - row_tuple = {constants.SQL_PHOTO_COLUMNS[index]: value for (index, value) in enumerate(row_tuple)} + if isinstance(db_row, (list, tuple)): + db_row = {constants.SQL_PHOTO_COLUMNS[index]: value for (index, value) in enumerate(db_row)} - self.real_filepath = helpers.normalize_filepath(row_tuple['filepath'], allowed=':\\/') + self.real_filepath = helpers.normalize_filepath(db_row['filepath'], allowed=':\\/') self.real_path = pathclass.Path(self.real_filepath) - self.id = row_tuple['id'] - self.created = row_tuple['created'] - self.author_id = row_tuple['author_id'] - self.filepath = row_tuple['override_filename'] or self.real_path.absolute_path - self.basename = row_tuple['override_filename'] or self.real_path.basename - self.extension = row_tuple['extension'] - self.tagged_at = row_tuple['tagged_at'] + self.id = db_row['id'] + self.created = db_row['created'] + self.author_id = db_row['author_id'] + self.filepath = db_row['override_filename'] or self.real_path.absolute_path + self.basename = db_row['override_filename'] or self.real_path.basename + self.extension = db_row['extension'] + self.tagged_at = db_row['tagged_at'] if self.extension == '': self.dot_extension = '' else: self.dot_extension = '.' + self.extension - self.area = row_tuple['area'] - self.bytes = row_tuple['bytes'] - self.duration = row_tuple['duration'] - self.width = row_tuple['width'] - self.height = row_tuple['height'] - self.ratio = row_tuple['ratio'] - self.thumbnail = row_tuple['thumbnail'] + self.area = db_row['area'] + self.bytes = db_row['bytes'] + self.duration = db_row['duration'] + self.width = db_row['width'] + self.height = db_row['height'] + self.ratio = db_row['ratio'] + self.thumbnail = db_row['thumbnail'] self.mimetype = helpers.get_mimetype(self.real_filepath) if self.mimetype is None: @@ -736,12 +734,12 @@ class Tag(ObjectBase, GroupableMixin): ''' A Tag, which can be applied to Photos for organization. ''' - def __init__(self, photodb, row_tuple): + def __init__(self, photodb, db_row): self.photodb = photodb - if isinstance(row_tuple, (list, tuple)): - row_tuple = {constants.SQL_TAG_COLUMNS[index]: value for (index, value) in enumerate(row_tuple)} - self.id = row_tuple['id'] - self.name = row_tuple['name'] + if isinstance(db_row, (list, tuple)): + db_row = {constants.SQL_TAG_COLUMNS[index]: value for (index, value) in enumerate(db_row)} + self.id = db_row['id'] + self.name = db_row['name'] self.group_getter = self.photodb.get_tag self._cached_qualified_name = None @@ -913,13 +911,13 @@ class User(ObjectBase): ''' A dear friend of ours. ''' - def __init__(self, photodb, row_tuple): + def __init__(self, photodb, db_row): self.photodb = photodb - if isinstance(row_tuple, (list, tuple)): - row_tuple = {constants.SQL_USER_COLUMNS[index]: value for (index, value) in enumerate(row_tuple)} - self.id = row_tuple['id'] - self.username = row_tuple['username'] - self.created = row_tuple['created'] + if isinstance(db_row, (list, tuple)): + db_row = {constants.SQL_USER_COLUMNS[index]: value for (index, value) in enumerate(db_row)} + self.id = db_row['id'] + self.username = db_row['username'] + self.created = db_row['created'] def __repr__(self): rep = 'User:{id}:{username}'.format(id=self.id, username=self.username) diff --git a/etiquette/photodb.py b/etiquette/photodb.py index ef07b2b..2091c3e 100644 --- a/etiquette/photodb.py +++ b/etiquette/photodb.py @@ -541,14 +541,16 @@ class PDBPhotoMixin: ''' PHOTO PROPERTIES area, width, height, ratio, bytes, duration: - A hyphen_range string representing min and max. Or just a number for lower bound. + A hyphen_range string representing min and max. Or just a number + for lower bound. TAGS AND FILTERS authors: 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. + A hyphen_range string respresenting min and max. Or just a number + for lower bound. extension: A string or list of strings of acceptable file extensions. @@ -558,16 +560,20 @@ class PDBPhotoMixin: Including '*' will forbid all extensions filename: - A string or list of strings which will be split into words. The file's basename - must include every word, NOT case-sensitive. + A string or list of strings in the form of an expression. + Match is CASE-INSENSITIVE. + Examples: + '.pdf AND (programming OR "survival guide")' + '.pdf programming python' (implicitly AND each term) has_tags: If True, require that the Photo has >=1 tag. If False, require that the Photo has no tags. - If None, not considered. + If None, any amount is okay. mimetype: - A string or list of strings of acceptable mimetypes. 'image', 'video', ... + A string or list of strings of acceptable mimetypes. + 'image', 'video', ... tag_musts: A list of tag names or Tag objects. @@ -582,8 +588,11 @@ class PDBPhotoMixin: Photos MUST NOT have ANY tag in the list. tag_expression: - A string like 'family AND (animals OR vacation)' to filter by. + A string or list of strings in the form of an expression. Can NOT be used with the must, may, forbid style search. + Examples: + 'family AND (animals OR vacation)' + 'family vacation outdoors' (implicitly AND each term) QUERY OPTIONS limit: @@ -598,13 +607,15 @@ class PDBPhotoMixin: Descending is assumed if not provided. warning_bag: - Invalid search queries will add a warning to the bag and try their best to continue. - Otherwise they may raise exceptions. + If provided, invalid search queries will add a warning to the bag + and try their best to continue. The generator will yield the bag + back to you as the final object. + Without the bag, exceptions may be raised. give_back_parameters: - If True, the generator's first yield will be a dictionary of all the cleaned up, normalized - parameters. The user may have given us loads of trash, so we should show them the formatting - we want. + If True, the generator's first yield will be a dictionary of all the + cleaned up, normalized parameters. The user may have given us loads + of trash, so we should show them the formatting we want. ''' start_time = time.time() @@ -791,7 +802,6 @@ class PDBPhotoMixin: #print('Failed has_tags=True') continue - if tag_expression: success = expression_tree.evaluate( photo_tags, @@ -819,7 +829,6 @@ class PDBPhotoMixin: if limit is not None and photos_received >= limit: break - photos_received += 1 yield photo @@ -1383,7 +1392,7 @@ class PhotoDB(PDBAlbumMixin, PDBBookmarkMixin, PDBPhotoMixin, PDBTagMixin, PDBUs things = cur.fetchall() for thing in things: - thing = thing_map['class'](self, row_tuple=thing) + thing = thing_map['class'](self, db_row=thing) yield thing diff --git a/etiquette/searchhelpers.py b/etiquette/searchhelpers.py index 4643f74..69bc881 100644 --- a/etiquette/searchhelpers.py +++ b/etiquette/searchhelpers.py @@ -153,7 +153,6 @@ def normalize_filename(filename_terms): filename_terms = ' '.join(filename_terms) filename_terms = filename_terms.strip() - filename_terms = shlex.split(filename_terms) if not filename_terms: return None @@ -280,6 +279,11 @@ def normalize_tag_expression(expression): if not isinstance(expression, str): expression = ' '.join(expression) + expression = expression.strip() + + if not expression: + return None + return expression def normalize_tag_mmf(tags, photodb, warning_bag=None): diff --git a/etiquette_site.py b/etiquette_site.py index bafb436..a9b6201 100644 --- a/etiquette_site.py +++ b/etiquette_site.py @@ -484,16 +484,19 @@ def get_search_core(): # The search has converted many arguments into sets or other types. # Convert them back into something that will display nicely on the search form. join_helper = lambda x: ', '.join(x) if x else None - tagname_helper = lambda tags: [tag.qualified_name() for tag in tags] if tags else None - filename_helper = lambda fn: ' '.join('"%s"' % part if ' ' in part else part for part in fn) if fn else None search_kwargs['extension'] = join_helper(search_kwargs['extension']) search_kwargs['extension_not'] = join_helper(search_kwargs['extension_not']) search_kwargs['mimetype'] = join_helper(search_kwargs['mimetype']) - search_kwargs['filename'] = filename_helper(search_kwargs['filename']) + + 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']) search_kwargs['tag_forbids'] = tagname_helper(search_kwargs['tag_forbids']) + #quoted_helper = lambda text: '"%s"' % text if ' ' in text else text + #filename_helper = lambda fn: ' '.join(quoted_helper(part) for part in fn) if fn else None + #search_kwargs['filename'] = filename_helper(search_kwargs['filename']) + search_results = list(search_generator) warnings = set() photos = [] diff --git a/utilities/database_upgrader.py b/utilities/database_upgrader.py index cb8202b..bbb9f63 100644 --- a/utilities/database_upgrader.py +++ b/utilities/database_upgrader.py @@ -37,7 +37,7 @@ def upgrade_3_to_4(sql): ''' cur = sql.cursor() cur.execute('ALTER TABLE photos ADD COLUMN author_id TEXT') - cur.execute('CREATE INDEX IF NOT EXISTS index_photo_author on photos(author_id)') + cur.execute('CREATE INDEX IF NOT EXISTS index_photo_author ON photos(author_id)') def upgrade_4_to_5(sql): ''' @@ -52,8 +52,8 @@ def upgrade_4_to_5(sql): 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)') + 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): '''