From 24feb63e5e60ec402e2e9f309abf662b4d4db4f9 Mon Sep 17 00:00:00 2001 From: Peery Date: Mon, 2 Jan 2023 23:35:03 +0100 Subject: [PATCH] Fixed Crashes related to recent DB Changes Fixed a few bugs/crashes that were caused by code still expecting tag name lists being returned by the DB API instead of the new dictionaries. I should really automate tests or do something to catch these better in the future ... --- ArtNet/artnet_manager.py | 43 +++++++++++-------- ArtNet/db/db_adapter.py | 62 +++++++++++++++++---------- ArtNet/gui/importer_window.py | 81 ++++++++++++++++++++++++++--------- 3 files changed, 124 insertions(+), 62 deletions(-) diff --git a/ArtNet/artnet_manager.py b/ArtNet/artnet_manager.py index 6759478..7f5ba98 100644 --- a/ArtNet/artnet_manager.py +++ b/ArtNet/artnet_manager.py @@ -165,7 +165,7 @@ class ArtNetManager: already_applied_tags = self.db_connection.get_art_tags_by_ID(art_ID) for i in range(len(already_applied_tags)): # converting the list to List[str] - already_applied_tags[i] = self.db_connection.get_tag_by_ID(already_applied_tags[i])[0][1].strip() + already_applied_tags[i] = self.db_connection.get_tag_by_ID(already_applied_tags[i]) importable_tags = [] importable_artists = [] @@ -173,23 +173,29 @@ class ArtNetManager: for i in tags.values(): scraped_tags += i - for tag in scraped_tags: - result = self.db_connection.get_tag_by_name(tag) - if len(result) == 0: # tag does not exist yet - if tag in tags['artists']: - importable_artists.append(tag) + for tag_name in scraped_tags: + result = self.db_connection.get_tag_by_name(tag_name) + if result is None: # tag does not exist yet + if tag_name in tags['artists']: + importable_artists.append(tag_name) continue - importable_tags.append(tag) + importable_tags.append(tag_name) continue - if tag in already_applied_tags: # tag is already applied + is_in_applied_tags = False + for t in already_applied_tags: + if t["name"] == tag_name: + is_in_applied_tags = True + if is_in_applied_tags: # tag is already applied continue - result = self.db_connection.get_tag_impliers_by_name(tag) + result = self.db_connection.get_tag_impliers_by_name(tag_name) if len(result) != 0: # tag is implied by some other tag skip = False for implier_tag_id in result: - implier_tag_id, implier_tag_name, _, _ = self.db_connection.get_tag_by_ID(implier_tag_id)[0] + data = self.db_connection.get_tag_by_ID(implier_tag_id) + implier_tag_id = data["id"] + implier_tag_name = data["name"] if implier_tag_name.strip() in scraped_tags: skip = True @@ -197,14 +203,14 @@ class ArtNetManager: if skip: # skipping the current tag as it is implied by another tag continue - result = self.db_connection.get_tag_aliases_by_name(tag) + result = self.db_connection.get_tag_aliases_by_name(tag_name) if len(result) != 0: # tag is already tagged by an alias continue - if tag in tags['artists']: - importable_artists.append(tag) + if tag_name in tags['artists']: + importable_artists.append(tag_name) continue - importable_tags.append(tag) # tag must be known and not tagged yet + importable_tags.append(tag_name) # tag must be known and not tagged yet return importable_tags, importable_artists @@ -216,7 +222,7 @@ class ArtNetManager: """ for tag in tags: - if len(self.db_connection.get_tag_by_name(tag)) == 0: # tag doesn't exist yet + if self.db_connection.get_tag_by_name(tag) is None: # tag doesn't exist yet result = self.import_window.force_edit_tag_dialog(name=tag) if result is None: # tag creation was aborted @@ -224,8 +230,8 @@ class ArtNetManager: tag = result['name'] # overwrite with possibly new tag name - tag = self.db_connection.get_tag_by_name(tag)[0][0] - self.import_window.curr_tags.append(tag) + tag_data = self.db_connection.get_tag_by_name(tag) + self.import_window.curr_tags.append(tag_data) self.import_window.data_changed = True def get_md5_of_image(self, path: str): @@ -416,8 +422,7 @@ class ArtNetManager: self.all_images[self.curr_image_index], art_ID, image_link, file_name=s[-1], description=image_description, collections=collections) - tmp = [self.db_connection.get_tag_by_ID(x) for x in self.db_connection.get_art_tags_by_ID(art_ID)] - tags = [{"id": int(t[0][0]), "name": t[0][1].strip(), "description": t[0][2].strip(), "category": t[0][3]} for t in tmp] + tags = [self.db_connection.get_tag_by_ID(x) for x in self.db_connection.get_art_tags_by_ID(art_ID)] self.curr_active_window.set_tag_list(tags) self.curr_active_window.data_changed = False diff --git a/ArtNet/db/db_adapter.py b/ArtNet/db/db_adapter.py index 60ca3f0..4e87a18 100644 --- a/ArtNet/db/db_adapter.py +++ b/ArtNet/db/db_adapter.py @@ -73,7 +73,7 @@ class DBAdapter: :return: """ logging.debug("Saving Image {0}:{1} authors: {2} path: {3} tags: {4} link: {5} hash:{6} desc:{7}" - .format(ID, title, authors, path, tags, link, md5_hash, desc)) + .format(ID, title, authors, path, [t["name"] for t in tags], link, md5_hash, desc)) d = {"title": title, "path": path, "id": ID, "link": link, "hash": md5_hash, "desc": desc} # TODO implement try/except for database errors and rollback! if self.get_art_by_path(path) is None: if ID is None: @@ -98,11 +98,11 @@ class DBAdapter: ID = self.get_art_by_path(path)["ID"] assert (ID != None) # was checked before, should never fail - old_tags = [self.get_tag_by_ID(x)[0][1].strip() for x in self.get_art_tags_by_ID(ID)] + old_tags = [self.get_tag_by_ID(x)["name"].strip() for x in self.get_art_tags_by_ID(ID)] for tag in tags: if tag in old_tags: # already present continue - d = {"id": ID, "tag": self.get_tag_ID(tag)} + d = {"id": ID, "tag": tag["id"]} try: self.db_cursor.execute("INSERT INTO art_to_tag (art_id, tag_ID) VALUES (%(id)s, %(tag)s)", d) self.db.commit() @@ -115,7 +115,11 @@ class DBAdapter: raise e for old_tag in old_tags: - if old_tag not in tags: # need to remove old tag + is_in_tags = False + for t in tags: + if t["name"] == old_tag: + is_in_tags = True + if not is_in_tags: # need to remove old tag self.remove_tag_from_image(art_ID=ID, tag_ID=self.get_tag_ID(old_tag)) old_authors = self.get_authors_of_art_by_ID(ID) @@ -831,7 +835,7 @@ class DBAdapter: return rows[0] - def get_tag_by_name(self, name: str) -> list: + def get_tag_by_name(self, name: str) -> dict: """ Search the tag in the DB via its name. @@ -843,20 +847,21 @@ class DBAdapter: self.db_cursor.execute( "SELECT name, description, category_id, ID FROM tag where LOWER(name) = LOWER(%(name)s)", d) - rows = [] - for row in self.db_cursor.fetchall(): - new_row = [] - for value in row: - if value is None: - new_row.append("") - elif type(value) == str: - new_row.append(value.strip()) - else: - new_row.append(value) - rows.append(new_row) - return rows + new_row = [] + row = self.db_cursor.fetchall() + if len(row) == 0: + return None + row = row[0] + for value in row: + if value is None: + new_row.append("") + elif type(value) == str: + new_row.append(value.strip()) + else: + new_row.append(value) + return {"name": new_row[0], "description": new_row[1], "category": new_row[2], "id": new_row[3]} - def get_tag_by_ID(self, ID: int) -> list: + def get_tag_by_ID(self, ID: int) -> dict: """ Search the tag in the DB via its ID. @@ -867,9 +872,21 @@ class DBAdapter: d = {"ID": ID} self.db_cursor.execute("SELECT ID, name, description, category_id FROM tag where ID = %(ID)s", d) - return self.db_cursor.fetchall() + new_row = [] + rows = self.db_cursor.fetchall() + if len(rows) != 1: + raise Exception("Something went terribly wrong!") + row = rows[0] + for value in row: + if value is None: + new_row.append("") + elif type(value) == str: + new_row.append(value.strip()) + else: + new_row.append(value) + return {"name": new_row[1], "description": new_row[2], "category": new_row[3], "id": new_row[0]} - def get_tag_aliases_by_name(self, name: str) -> list: + def get_tag_aliases_by_name(self, name: str) -> List[dict]: """ Search for the tag's aliases and the alias's aliases :param name: @@ -880,7 +897,7 @@ class DBAdapter: for alias in aliases: tag_data = self.get_tag_by_ID(alias) if len(tag_data) > 0: # tag exists - result.append(tag_data[0][1].strip()) + result.append(tag_data) return result def get_tag_aliases_by_ID(self, tag_ID: int) -> list: @@ -931,8 +948,7 @@ class DBAdapter: for tag_ID in collected_tags: tag_data = self.get_tag_by_ID(tag_ID) if len(tag_data) != 0: - result.append({"name": tag_data[0][1].strip(), "id": int(tag_data[0][0]), - "description": tag_data[0][2].strip(), "category": int(tag_data[0][3])}) + result.append(tag_data) return result def get_all_tag_impliers_by_ID(self, ID: int) -> list: diff --git a/ArtNet/gui/importer_window.py b/ArtNet/gui/importer_window.py index f6a8a1f..2355dbc 100644 --- a/ArtNet/gui/importer_window.py +++ b/ArtNet/gui/importer_window.py @@ -1,7 +1,10 @@ +from typing import List + import validators import os import logging import re +import json from PyQt5 import QtWidgets from PyQt5.QtCore import Qt, QSize, QUrl @@ -493,7 +496,7 @@ class ImporterWindow(ArtnetMainWindow): """ return self.main.db_connection.search_fuzzy_tag(name, all_if_empty=True) - def set_tag_search_result_list(self, tags: list): + def set_tag_search_result_list(self, tags: List[dict]): """ Set the tags in the search result list to tags :param tags: @@ -510,9 +513,21 @@ class ImporterWindow(ArtnetMainWindow): if tag_name not in self.curr_implied_tags: # new tag and not implied yet item.setData(Qt.Unchecked, Qt.CheckStateRole) - item.setToolTip(f"Tag ID: {tag['id']}\n\n"+tag["description"]+f"\nCategory: {tag['category']}") + if 'id' in tag.keys() and 'description' in tag.keys() and 'category' in tag.keys(): + s = f"Tag ID: {tag['id']}\n\n" + if len(tag["description"]) != 0: + s += tag["description"]+"\n\n" + s += f"Category: {tag['category']}" + item.setToolTip(s) + item.setData(json.dumps(tag), Qt.UserRole) flags |= Qt.ItemIsUserCheckable - if self.curr_tags is not None and tag_name in (self.curr_tags + self.curr_implied_tags + self.curr_tag_aliases): + + is_in_tags = False + for i in range(len((self.curr_tags + self.curr_implied_tags + self.curr_tag_aliases))): + if item.text() == (self.curr_tags + self.curr_implied_tags + self.curr_tag_aliases)[i]["name"]: + is_in_tags = True + + if self.curr_tags is not None and is_in_tags: # already selected, implied or aliased tags item.setCheckState(Qt.Checked) item.setFlags(flags) @@ -555,7 +570,12 @@ class ImporterWindow(ArtnetMainWindow): if tag_name not in self.curr_implied_tags: # new tag and not implied yet item.setData(Qt.Unchecked, Qt.CheckStateRole) - item.setToolTip(f"Tag ID: {tag['id']}\n\n" + tag["description"] + f"\nCategory: {tag['category']}") + if 'id' in tag.keys() and 'description' in tag.keys() and 'category' in tag.keys(): + s = f"Tag ID: {tag['id']}\n\n" + if len(tag["description"]) != 0: + s += tag["description"] + "\n\n" + s += f"Category: {tag['category']}" + item.setToolTip(s) flags |= Qt.ItemIsUserCheckable if set_checked: @@ -588,7 +608,12 @@ class ImporterWindow(ArtnetMainWindow): else: done.append(tag_name) item = QStandardItem(tag_name) - item.setToolTip(f"Tag ID: {tag['id']}\n\n"+tag["description"]+f"\nCategory: {tag['category']}") + if 'id' in tag.keys() and 'description' in tag.keys() and 'category' in tag.keys(): + s = f"Tag ID: {tag['id']}\n\n" + if len(tag["description"]) != 0: + s += tag["description"] + "\n\n" + s += f"Category: {tag['category']}" + item.setToolTip(s) item_model.appendRow(item) self.ui.implied_tag_list.setModel(item_model) @@ -764,15 +789,15 @@ class ImporterWindow(ArtnetMainWindow): if self.ui.search_result_list.model().rowCount() == 1: # only 1 search result left model_index = self.ui.search_result_list.model().index(0, 0) - item_data = self.ui.search_result_list.model().itemData(model_index) + item_data = json.loads(self.ui.search_result_list.model().itemData(model_index)[Qt.UserRole]) - if item_data[0] not in self.curr_tags: # add/remove new selected tag to the lists - self.curr_tags.append(item_data[0]) + if item_data not in self.curr_tags: # add/remove new selected tag to the lists + self.curr_tags.append(item_data) else: - self.curr_tags.remove(item_data[0]) + self.curr_tags.remove(item_data) self.set_tag_list(self.curr_tags) # update relevant lists - self.set_tag_search_result_list([item_data[0]]) + self.set_tag_search_result_list([item_data]) logging.debug(item_data) def on_movie_player_state_changed(self, state: int): @@ -823,8 +848,8 @@ class ImporterWindow(ArtnetMainWindow): i = 0 while i < len(tags): # workaround for an issue with altering lists during iteration r = self.main.db_connection.get_tag_by_name(tags[i]) - if len(r) > 0: - self.curr_tags.append(tags[i]) + if r is not None: + self.curr_tags.append(r) self.data_changed = True tags.remove(tags[i]) continue @@ -995,9 +1020,10 @@ class ImporterWindow(ArtnetMainWindow): "No tag is allowed without a category!") return None - if len(self.get_tag(tag_data['name'])) > 0: + if self.get_tag(tag_data['name']) is not None: QtWidgets.QMessageBox.information(self, "Tag already exists", - "The Tag \"{0}\" you wanted to create already exists! Skipping...") + f"The Tag \"{tag_data['name']}\" you wanted to create already exists! Skipping...") + return None else: self.main.db_connection.create_tag(name=tag_data["name"], description=tag_data["description"], aliases=tag_data["aliases"], implications=tag_data["implications"], @@ -1043,7 +1069,8 @@ class ImporterWindow(ArtnetMainWindow): def on_tag_search_item_changed(self, item: QStandardItem): if item.checkState() == Qt.Checked: - self.curr_tags.append(item.text()) + tag_data = self.main.db_connection.get_tag_by_name(item.text()) + self.curr_tags.append(tag_data) aliases = self.main.db_connection.get_tag_aliases_by_name(item.text()) for alias in aliases: @@ -1052,15 +1079,20 @@ class ImporterWindow(ArtnetMainWindow): for implication in implications: self.curr_tags.append(implication) if item.checkState() == Qt.Unchecked: - if item.text() in self.curr_tags: - self.curr_tags.remove(item.text()) + is_in_tags = False + for i in range(len(self.curr_tags)): + if item.text() == self.curr_tags[i]["name"]: + tags_index = i + is_in_tags = True + if is_in_tags: + self.curr_tags.pop(tags_index) aliases = self.main.db_connection.get_tag_aliases_by_name(item.text()) for alias in aliases: if alias in self.curr_tags: self.curr_tags.remove(alias) implications = self.main.db_connection.get_all_tag_implications_by_name(item.text()) for implication in implications: - self.curr_tags.remove(implication) + self.curr_tags.remove({"name": implication}) else: raise Exception("Something went terribly wrong!") @@ -1070,13 +1102,22 @@ class ImporterWindow(ArtnetMainWindow): def on_tag_item_changed(self, item: QStandardItem): logging.debug("Item {0} has changed!".format(item.text())) if item.checkState() == Qt.Unchecked: - if item.text() in self.curr_tags: + is_in_tags = False + for i in range(len(self.curr_tags)): + if item.text() == self.curr_tags[i]["name"]: + tags_index = i + is_in_tags = True + if is_in_tags: aliases = self.main.db_connection.get_tag_aliases_by_name(item.text()) if len(aliases) > 0: # tag has aliases, might need to also remove them for alias in aliases: self.curr_tags.remove(alias) - self.curr_tags.remove(item.text()) + for i in range(len(self.curr_tags)): + if item.text() == self.curr_tags[i]["name"]: + tags_index = i + + self.curr_tags.pop(tags_index) self.set_tag_list(self.curr_tags) self.on_tag_search_change() else: