diff --git a/ArtNet/artnet_manager.py b/ArtNet/artnet_manager.py
index d76bb5e..f2220f5 100644
--- a/ArtNet/artnet_manager.py
+++ b/ArtNet/artnet_manager.py
@@ -14,19 +14,22 @@ from ArtNet.gui.importer_window import ImporterWindow
 from ArtNet.gui.browse_window import BrowseWindow
 from ArtNet.gui.dialogs.db_connection_dialog.db_dialog import DBDialog
 from ArtNet.web.link_generator import LinkGenerator
+from ArtNet.singleton_manager import SingletonManager
 
 
 class ArtNetManager:
     LOG_FOLDER = "log"
 
     def __init__(self, config_location: str = "."):
+        SingletonManager.set_instance(self)
+
         if not os.path.isdir(ArtNetManager.LOG_FOLDER):
             os.mkdir(ArtNetManager.LOG_FOLDER)
         file_name = "artnet-" + str(datetime.datetime.now().strftime("%Y-%m-%d")) + ".log"
         logging.basicConfig(filename=os.path.join(ArtNetManager.LOG_FOLDER, file_name), encoding='utf-8',
                             level=logging.DEBUG)
         logFormatter = logging.Formatter(fmt="%(asctime)s.%(msecs)03d [%(threadName)-12.12s] [%(levelname)-5.5s]  "
-                                         "%(message)s",
+                                             "%(message)s",
                                          datefmt="%Y-%m-%d %H:%M:%S")
 
         logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
@@ -182,7 +185,7 @@ class ArtNetManager:
             if tag in already_applied_tags:  # tag is already applied
                 continue
 
-            result = self.db_connection.get_tag_impliers(tag)
+            result = self.db_connection.get_tag_impliers_by_name(tag)
             if len(result) != 0:  # tag is implied by some other tag
                 skip = False
                 for implier_tag_id in result:
@@ -389,10 +392,12 @@ class ArtNetManager:
         s = self.all_images[self.curr_image_index].split(os.path.sep)
         if image_db_result is not None:
             image_title = image_db_result["title"] if isinstance(image_db_result["title"], str) and \
-                                                      len(image_db_result["title"]) > 0 else self.import_window.UNKNOWN_TITLE
+                                                      len(image_db_result[
+                                                              "title"]) > 0 else self.import_window.UNKNOWN_TITLE
             image_author = self.db_connection.get_authors_of_art_by_ID(image_db_result["ID"])
             image_link = image_db_result["link"]
             image_description = image_db_result["description"]
+            collections = self.db_connection.get_collections_of_art_by_ID(image_db_result["ID"])
             art_ID = image_db_result["ID"]
             if image_author is None or len(image_author) == 0:
                 image_author = [(self.all_images[self.curr_image_index].split(os.path.sep)[0], "(Not in Database)")]
@@ -401,13 +406,16 @@ class ArtNetManager:
             image_author = [(s[0], "(Not in Database)")]
             image_title = s[-1] + " (Not in Database)"
             image_link = "(Unknown)"
+            collections = []
             image_description = None
 
         logging.info(f"Displaying #{self.curr_image_index} \"{self.all_images[self.curr_image_index]}\"")
         self.curr_active_window.display_image(image_title, image_author,
-                                         os.path.join(self.config.data["file_root"], self.all_images[self.curr_image_index]),
-                                         self.all_images[self.curr_image_index],
-                                         art_ID, image_link, file_name=s[-1], description=image_description)
+                                              os.path.join(self.config.data["file_root"],
+                                                           self.all_images[self.curr_image_index]),
+                                              self.all_images[self.curr_image_index],
+                                              art_ID, image_link, file_name=s[-1], description=image_description,
+                                              collections=collections)
         self.curr_active_window.set_tag_list(
             [self.db_connection.get_tag_by_ID(x)[0][1].strip() for x in self.db_connection.get_art_tags_by_ID(art_ID)])
 
@@ -431,7 +439,7 @@ class ArtNetManager:
         """
         if len(path) == 0:
             exit(0)
-        logging.info("Changing root to", path)
+        logging.info(f"Changing root to {path}")
         self.config.data["file_root"] = path
         self.config.update_config()
         self.__file_reader = FileReader(self.config.data["file_root"])
@@ -442,6 +450,12 @@ class ArtNetManager:
         return self.config.data["db"]
 
     def change_db_connection(self, host: str, port: int, database: str, user: str, password: str):
+        try:
+            self.db_connection = self.create_db_connection(host, port, database, user, password)
+        except ValueError as e:
+            logging.error(f"Error encountered during change_db_connection(): {e}")
+            raise e
+
         self.config.data["db"]["user"] = user
         self.config.data["db"]["password"] = password
         self.config.data["db"]["host"] = host
@@ -449,5 +463,3 @@ class ArtNetManager:
         self.config.data["db"]["port"] = str(port)
 
         self.config.update_config()
-
-        self.db_connection = self.create_db_connection(host, port, database, user, password)
diff --git a/ArtNet/db/db_adapter.py b/ArtNet/db/db_adapter.py
index 3d35413..ecd9401 100644
--- a/ArtNet/db/db_adapter.py
+++ b/ArtNet/db/db_adapter.py
@@ -1,10 +1,11 @@
 import logging
+from typing import List, Tuple
+
 import psycopg2
 from psycopg2.errorcodes import UNIQUE_VIOLATION
 
 
 class DBAdapter:
-
     EXPECTED_TABLES = ['art', 'artist', 'tag', 'topic', 'presence', 'artist_to_topic', 'art_to_presence',
                        'art_to_tag', 'tag_alias', 'tag_implication', 'tag_category', 'art_collection',
                        'art_to_art_collection']
@@ -30,8 +31,13 @@ class DBAdapter:
         Check if all the expected tables are present in the connected database.
         :return:
         """
-        self.db_cursor.execute("select relname from pg_class where relkind='r' and relname !~ '^(pg_|sql_)';")
-        present_tables = self.db_cursor.fetchall()
+        try:
+            self.db_cursor.execute("select relname from pg_class where relkind='r' and relname !~ '^(pg_|sql_)';")
+            present_tables = self.db_cursor.fetchall()
+        except psycopg2.DatabaseError as e:
+            logging.error(f"Database error occured on check_tables(): {e}")
+            self.db.rollback()
+            return False
         unknown_tables = []
         missing = [x for x in DBAdapter.EXPECTED_TABLES]
         for table in present_tables:
@@ -46,11 +52,13 @@ class DBAdapter:
             return False
 
         if len(unknown_tables) > 0:
-            logging.error("The following tables are unknown and not expected inside the database: {0}".format(unknown_tables))
+            logging.error(
+                "The following tables are unknown and not expected inside the database: {0}".format(unknown_tables))
 
         return True
 
-    def save_image(self, ID: str, title: str, authors: list, path: str, tags: list, link: str, md5_hash: str, desc: str):
+    def save_image(self, ID: str, title: str, authors: list, path: str, tags: list, link: str, md5_hash: str,
+                   desc: str, collections: list):
         """
         Updates or saves the given image data to the DB
         :param ID:
@@ -61,30 +69,34 @@ class DBAdapter:
         :param link:
         :param md5_hash: md5 hash as a hex digest
         :param desc: image description or None for empty
+        :param collections: list of collections included by the image
         :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))
-        d = {"title": title, "path": path, "id": ID, "link": link, "hash": md5_hash, "desc": desc}
+                      .format(ID, title, authors, path, 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:
                 self.db_cursor.execute(
                     "INSERT INTO art (path, title, link, md5_hash, description) "
                     "VALUES (%(path)s, %(title)s, %(link)s, %(hash)s, %(desc)s)",
                     d)
+                self.db.commit()
                 ID = self.get_art_by_path(path)["ID"]
                 d["id"] = ID
             elif self.get_tag_by_ID(ID) is None:
                 self.db_cursor.execute(
                     "INSERT INTO art (path, title, link, md5_hash, description) "
                     "VALUES (%(path)s, %(title)s, %(link)s, %(hash)s, %(desc)s)", d)
+                self.db.commit()
         else:
             self.db_cursor.execute("UPDATE art SET path = %(path)s, title = %(title)s, link = %(link)s, " +
                                    "md5_hash = %(hash)s, description = %(desc)s WHERE id = %(id)s", d)
+            self.db.commit()
 
         if ID is None:
             ID = self.get_art_by_path(path)["ID"]
-            assert(ID != None)  # was checked before, should never fail
+            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)]
         for tag in tags:
@@ -93,10 +105,12 @@ class DBAdapter:
             d = {"id": ID, "tag": self.get_tag_ID(tag)}
             try:
                 self.db_cursor.execute("INSERT INTO art_to_tag (art_id, tag_ID) VALUES (%(id)s, %(tag)s)", d)
+                self.db.commit()
             except psycopg2.Error as e:
-                if e.pgcode == UNIQUE_VIOLATION:
+                if e.pgcode == UNIQUE_VIOLATION:  # tag was already set
                     logging.debug(e)
                     logging.info("Skipping Unique Violation ...")
+                    self.db.rollback()
                 else:
                     raise e
 
@@ -111,6 +125,7 @@ class DBAdapter:
             d = {"id": ID, "author_name": author[0], "author_domain": author[1]}
             self.db_cursor.execute("INSERT INTO art_to_presence (presence_name, presence_domain, art_ID) " +
                                    "VALUES (%(author_name)s, %(author_domain)s, %(id)s)", d)
+            self.db.commit()
         for old_author_name, old_author_domain in old_authors:
             if (old_author_name, old_author_domain) not in authors:  # need to remove old author
                 self.remove_artist_from_image(art_ID=ID, presence_name=old_author_name,
@@ -130,16 +145,15 @@ class DBAdapter:
         art_ID = image_data["ID"]
         logging.debug(f"Deleting image #{art_ID} {image_data['title']}")
         tags = self.get_art_tags_by_ID(art_ID)
-        for tag_ID in tags:
-            pass
-            #self.remove_tag_from_image(art_ID=art_ID, tag_ID=tag_ID)
+        # for tag_ID in tags:
+        #    pass
+        # self.remove_tag_from_image(art_ID=art_ID, tag_ID=tag_ID)
         authors = self.get_authors_of_art_by_ID(art_ID)
-        for presence_name, presence_domain in authors:
-            pass
-            #self.remove_artist_from_image(art_ID=art_ID, presence_name=presence_name, presence_domain=presence_domain)
-
-        #self.db_cursor.execute("DELETE FROM art WHERE md5_hash = %(hash)s", {"hash": hash})
+        # for presence_name, presence_domain in authors:
+        #    pass
+        # self.remove_artist_from_image(art_ID=art_ID, presence_name=presence_name, presence_domain=presence_domain)
 
+        # self.db_cursor.execute("DELETE FROM art WHERE md5_hash = %(hash)s", {"hash": hash})
 
     def remove_artist_from_image(self, art_ID, presence_name, presence_domain):
         """
@@ -340,6 +354,29 @@ class DBAdapter:
         """
         self.db_cursor.execute("SELECT id, name FROM artist")
 
+    def get_art_by_ID(self, ID: int):
+        """
+        Queries the database for the art via its ID and returns it if available.
+        """
+        d = {"ID": ID}
+        self.db_cursor.execute("SELECT id, path, title, link, md5_hash, description FROM art WHERE id = %(ID)s", d)
+
+        result = self.db_cursor.fetchall()
+        if len(result) == 0:
+            return None
+        else:
+            result = result[0]
+            image_data = {
+                "ID": result[0],
+                "path": result[1],
+                "title": result[2],
+                "link": result[3],
+                "md5_hash": result[4],
+                "description": result[5],
+            }
+            return image_data
+
+
     def get_art_by_hash(self, file_hash: str) -> dict:
         """
         Queries the database for the art via its hash and returns it if available.
@@ -349,7 +386,8 @@ class DBAdapter:
         :return:
         """
         d = {"hash": file_hash}
-        self.db_cursor.execute("SELECT id, path, title, link, md5_hash, description FROM art where md5_hash = %(hash)s", d)
+        self.db_cursor.execute("SELECT id, path, title, link, md5_hash, description FROM art where md5_hash = %(hash)s",
+                               d)
 
         result = self.db_cursor.fetchall()
 
@@ -449,6 +487,36 @@ class DBAdapter:
             new_rows.append(row[0])
         return new_rows
 
+    def search_art(self, tags: list, presences: list, resolve_tags: bool = True):
+        """
+        Search with the tags and given presences for fitting art.
+        :param tags: List[int] list of tag ids that are to be present in the art. See resolve_tags for implication.
+        :param presences: List[Tuple(str, str)] list of presences to be present in the art.
+        :param resolve_tags: flag if tag implications should be resolved fully.
+        """
+        d = {"tags": tags, "presences": presences}
+        l = list()
+        for i in range(len(presences)):  # converting for psycopg2
+            name, domain = presences[i]
+            casted = [name, domain]
+            l.append(casted)
+        d["presences"] = l
+
+        search_query = "SELECT art.id FROM art " \
+                       "INNER JOIN art_to_tag ON art_to_tag.art_id = art.id " \
+                       "INNER JOIN tag ON art_to_tag.tag_id = tag.id " \
+                       "INNER JOIN art_to_presence as AtP ON art.id = AtP.art_id " \
+                       "GROUP BY art.id " \
+                       "HAVING array_agg(tag.name) @> %(tags)s::varchar[] AND " \
+                       "array_agg(array[AtP.presence_name, AtP.presence_domain]) @> %(presences)s::varchar[]"
+
+        # 1. Join all related tables
+        # 2. Group them over art.id
+        # 3. check which rows contain the searched tags or presences in the value arrays
+        self.db_cursor.execute(search_query, d)
+        result = self.db_cursor.fetchall()
+        return result
+
     def search_fuzzy_presence(self, name: str, domain: str, all_if_empty: bool = False):
         """
         Search a list of presences fitting the (name, domain) fuzzy.
@@ -460,7 +528,7 @@ class DBAdapter:
         if all_if_empty and (name is None or len(name) == 0) and (domain is None or len(domain) == 0):
             self.db_cursor.execute("SELECT name, domain, artist_id FROM presence")
         else:
-            d = {"name":"%"+name+"%", "domain": "%"+domain+"%"}
+            d = {"name": "%" + name + "%", "domain": "%" + domain + "%"}
             self.db_cursor.execute("SELECT name, domain, artist_id FROM presence WHERE LOWER(name) LIKE "
                                    "LOWER(%(name)s) AND domain LIKE %(domain)s", d)
         rows = self.db_cursor.fetchall()
@@ -502,7 +570,7 @@ class DBAdapter:
         :param search:
         :return:
         """
-        d = {"search": "%"+search+"%"}
+        d = {"search": "%" + search + "%"}
         self.db_cursor.execute("SELECT name FROM tag_category WHERE LOWER(name) LIKE LOWER(%(search)s)", d)
 
         rows = []
@@ -519,10 +587,11 @@ class DBAdapter:
         :return:
         """
         if all_if_empty and len(name) == 0:
-            self.db_cursor.execute("SELECT name, description, category_id FROM tag")
+            self.db_cursor.execute("SELECT name, description, category_id, ID FROM tag")
         else:
-            d = {"name": "%"+name+"%"}
-            self.db_cursor.execute("SELECT name, description, category_id FROM tag WHERE LOWER(name) LIKE LOWER(%(name)s)", d)
+            d = {"name": "%" + name + "%"}
+            self.db_cursor.execute(
+                "SELECT name, description, category_id, ID FROM tag WHERE LOWER(name) LIKE LOWER(%(name)s)", d)
 
         rows = self.db_cursor.fetchall()
         new_rows = []
@@ -553,7 +622,7 @@ class DBAdapter:
         elif all_if_empty and ID is None and len(name) == 0:
             self.db_cursor.execute("SELECT id, name FROM artist")
         else:
-            d = {"name": "%"+name+"%"}
+            d = {"name": "%" + name + "%"}
             self.db_cursor.execute("SELECT id, name FROM artist WHERE LOWER(name) LIKE LOWER(%(name)s)", d)
 
         return self.db_cursor.fetchall()
@@ -600,7 +669,10 @@ class DBAdapter:
             "category_id": category_id,
         }
 
-        self.db_cursor.execute("UPDATE tag SET description = %(description)s, category_id = %(category_id)s, name = %(name)s WHERE tag_ID = %(id)s", d)
+        self.db_cursor.execute(
+            "UPDATE tag SET description = %(description)s, category_id = %(category_id)s, name = %(name)s "
+            "WHERE ID = %(id)s",
+            d)
 
         if aliases is not None:
             old_aliases = self.get_tag_aliases_by_ID(tag_id)
@@ -684,7 +756,6 @@ class DBAdapter:
                                d)
         self.db.commit()
 
-
     def add_implication_by_name(self, name: str, implicant: str):
         """
         Add the implication to the database
@@ -725,7 +796,7 @@ class DBAdapter:
         :return:
         """
         d = {"tag": tag}
-        self.db_cursor.execute("DELETE FROM tag WHERE tag_id = %(tag)s", d)
+        self.db_cursor.execute("DELETE FROM tag WHERE id = %(tag)s", d)
         self.db.commit()
 
     def remove_tag_by_name(self, name: str):
@@ -743,11 +814,15 @@ class DBAdapter:
         :return:
         """
         d = {"name": name}
-        self.db_cursor.execute("SELECT tag_ID FROM tag where LOWER(name) = LOWER(%(name)s)", d)
+        try:
+            self.db_cursor.execute("SELECT ID FROM tag where LOWER(name) = LOWER(%(name)s)", d)
 
-        rows = []
-        for row in self.db_cursor.fetchall():
-            rows.append(row[0])
+            rows = []
+            for row in self.db_cursor.fetchall():
+                rows.append(row[0])
+        except psycopg2.DatabaseError as e:
+
+            return None
 
         if len(rows) > 1:
             raise Exception("Found multiple tags by the same name!")
@@ -765,7 +840,8 @@ class DBAdapter:
         :return: Returns the tag's name, description, category, tag ID
         """
         d = {"name": name}
-        self.db_cursor.execute("SELECT name, description, category_id, tag_ID FROM tag where LOWER(name) = LOWER(%(name)s)", d)
+        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():
@@ -789,13 +865,13 @@ class DBAdapter:
         :return: Returns the tag's ID, name, description, category
         """
         d = {"ID": ID}
-        self.db_cursor.execute("SELECT tag_ID, name, description, category_id FROM tag where tag_ID = %(ID)s", d)
+        self.db_cursor.execute("SELECT ID, name, description, category_id FROM tag where ID = %(ID)s", d)
 
         return self.db_cursor.fetchall()
 
     def get_tag_aliases_by_name(self, name: str) -> list:
         """
-        Search for the tag's aliases and the tag's aliases
+        Search for the tag's aliases and the alias's aliases
         :param name:
         :return: List of tag Names that are aliases of this one
         """
@@ -803,7 +879,7 @@ class DBAdapter:
         result = []
         for alias in aliases:
             tag_data = self.get_tag_by_ID(alias)
-            if len(tag_data) > 0:
+            if len(tag_data) > 0:  # tag exists
                 result.append(tag_data[0][1].strip())
         return result
 
@@ -848,7 +924,8 @@ class DBAdapter:
                     collected_tags.append(found)
                     to_search.append(found)
 
-        collected_tags.remove(root_ID)
+        if root_ID in collected_tags:
+            collected_tags.remove(root_ID)
 
         result = []
         for tag_ID in collected_tags:
@@ -857,6 +934,31 @@ class DBAdapter:
                 result.append(tag_data[0][1].strip())
         return result
 
+    def get_all_tag_impliers_by_ID(self, ID: int) -> list:
+        """
+        Search for all tags that imply this one or imply a tag that implies this one.
+        :param ID:
+        :return:
+        """
+        collected_tags = self.get_tag_impliers_by_ID(ID)
+        to_search = self.get_tag_impliers_by_ID(ID)
+        collected_tags.append(ID)
+
+        while len(to_search) != 0:
+            curr_tag = to_search.pop()
+            found_impliers = self.get_tag_impliers_by_ID(curr_tag)
+            for found in found_impliers:
+                if found in collected_tags:
+                    continue
+                else:
+                    collected_tags.append(found)
+                    to_search.append(found)
+
+        if ID in collected_tags:
+            collected_tags.remove(ID)
+
+        return collected_tags
+
     def get_tag_implications_by_ID(self, ID: int) -> list:
         """
         Search for the tag's implications
@@ -908,20 +1010,29 @@ class DBAdapter:
 
         return r
 
-    def get_tag_impliers(self, name: str) -> list:
+    def get_tag_impliers_by_ID(self, ID: int):
+        """
+        Search for tags that imply this one.
+        :param ID:
+        :return:
+        """
+        d = {"ID": ID}
+        self.db_cursor.execute("SELECT root_tag FROM Tag_Implication WHERE implicate = %(ID)s", d)
+
+        rows = self.db_cursor.fetchall()
+        return rows
+
+    def get_tag_impliers_by_name(self, name: str) -> list:
         """
         Search for tags that imply this one.
         :param name:
         :return:
         """
         d = {"ID": self.get_tag_ID(name)}
-        self.db_cursor.execute("SELECT root_tag, implicate FROM Tag_Implication WHERE implicate = %(ID)s", d)
+        self.db_cursor.execute("SELECT root_tag FROM Tag_Implication WHERE implicate = %(ID)s", d)
 
         rows = self.db_cursor.fetchall()
-        r = []
-        for row in rows:
-            r.append(row[0])
-        return r
+        return rows
 
     def __get_tag_aliases_no_recurse(self, tag_id: int) -> list:
         """
@@ -946,3 +1057,140 @@ class DBAdapter:
                 raise Exception("Something went terribly wrong!")
 
         return r
+
+    def create_collection(self, collection_name: str, description: str) -> int:
+        """
+        Create the collection with name and description as given.
+        """
+        d = {"name": collection_name, "description": description}
+
+        try:
+            self.db_cursor.execute("INSERT INTO art_collection (name, description) VALUES (%(name)s, %(description)s) "
+                                   "RETURNING id", d)
+            self.db.commit()
+        except psycopg2.DatabaseError as e:
+            logging.error(f"Encountered database error on create_collection() with collection_name: {collection_name} "
+                          f"description: {description}. Rolling it back now ...")
+            logging.error(e)
+            self.db.rollback()
+            return None
+
+        return self.db_cursor.fetchall()[0]
+
+    def delete_collection(self, collection_id: int):
+        """
+        Delete the collection given by the id.
+        """
+        d = {"ID": collection_id}
+
+        try:
+            self.db_cursor.execute("DELETE FROM art_collection WHERE ID = %(ID)s", d)
+            self.db.commit()
+        except psycopg2.DatabaseError as e:
+            logging.error(f"Encountered database error on delete_collection() ID: {collection_id}. "
+                          f"Rolling it back now ...")
+            logging.error(e)
+            self.db.rollback()
+
+    def get_collections_of_art_by_ID(self, art_id: int):
+        """
+        Queries the database for collections that include the given art_id
+
+        :return: [(ID, name, description)]
+        """
+        d = {"art_id": art_id}
+
+        self.db_cursor.execute("SELECT art_collection.id, art_collection.name, art_collection.description "
+                               "FROM art_collection "
+                               "INNER JOIN art_to_art_collection as atac ON art_collection.id = atac.collection_id "
+                               "WHERE atac.art_id = %(art_id)s", d)
+
+        return self.db_cursor.fetchall()
+
+    def get_collection(self, collection_id: int):
+        """
+        Returns the collection given by the id.
+        """
+        d = {"ID": collection_id}
+
+        self.db_cursor.execute("SELECT collection.id, collection.name, collection.description, array_agg(art.id), "
+                               "array_agg(art.title), array_agg(art.path), array_agg(atac.ranking) "
+                               "FROM art_collection as collection "
+                               "LEFT OUTER JOIN art_to_art_collection as atac ON atac.collection_id = collection.id "
+                               "LEFT OUTER JOIN art ON atac.art_id = art.id "
+                               "WHERE collection.ID = %(ID)s "
+                               "GROUP BY collection.id", d)
+        r = self.db_cursor.fetchall()
+        if len(r) > 0:
+            r = r[0]
+            collection = dict()
+            collection["id"] = r[0]
+            collection["name"] = r[1]
+            collection["description"] = r[2]
+            collection["entries"] = []
+            for i in range(len(r[3])):
+                art_data = {"id": r[3][i], "title": r[4][i], "path": r[5][i], "ranking": r[6][i]}
+                if art_data["id"] is None:  # skipping non-existing art entries
+                    continue  # might happen once when the collection has no entries
+                collection["entries"].append(art_data)
+
+        return collection
+
+    def search_collections_by_name(self, name: str) -> List[Tuple[int, str]]:
+        """
+        Returns the collection by the given name
+        """
+        d = {"name": name}
+
+        try:
+            self.db_cursor.execute("SELECT art_collection.id, art_collection.name "
+                                   "FROM art_collection "
+                                   "WHERE art_collection.name = %(name)s", d)
+        except psycopg2.DatabaseError as e:
+            logging.error(f"Encountered database error on get_collection_by_name() name: {name}."
+                          f"Rolling it back now ...")
+            logging.error(e)
+            self.db.rollback()
+            return None
+
+        return self.db_cursor.fetchall()
+
+    def search_collections(self, collection_name: str = None, presence_name: str = None,
+                           presence_domain: str = None, art_name: str = None):
+        """
+        Search for collections that contain the given parameters.
+
+        Search is always fuzzy.
+        :param collection_name:
+        :param presence_name:
+        :param presence_domain:
+        :param art_name: Search for collections containing art with this title
+        :result: List[collection_id, collection_name, collection_desc, List[art_id]]
+        """
+        d = {"collection_name": "%"+collection_name+"%", "presence_name": "%"+presence_name+"%",
+             "presence_domain": "%"+presence_domain+"%", "art_name": "%"+art_name+"%"}
+
+        try:
+            self.db_cursor.execute("SELECT "
+                                   "(art_collection.id) as collection_id, "
+                                   "(art_collection.name) as collection_name, "
+                                   "(art_collection.description) as collection_desc, "
+                                   "array_agg(art.id) as art_ID, array_agg(art.title) as art_titles, "
+                                   "array_agg(art_to_presence.presence_name) as presence_names, "
+                                   "array_agg(art_to_presence.presence_domain) as presence_domains "
+                                   "FROM art_collection "
+                                   "LEFT OUTER JOIN art_to_art_collection On art_to_art_collection.collection_id = art_collection.id "
+                                   "LEFT OUTER JOIN art ON art_to_art_collection.art_id = art.id "
+                                   "LEFT OUTER JOIN art_to_presence ON art_to_presence.art_ID = art.id "
+                                   "WHERE LOWER(art_collection.name) LIKE LOWER(%(collection_name)s) "
+                                   "GROUP BY art_collection.id "
+                                   "HAVING LOWER(array_to_string(array_agg(art_to_presence.presence_name), '|', '')) LIKE LOWER(%(presence_name)s) "
+                                   "AND LOWER(array_to_string(array_agg(art_to_presence.presence_domain), '|', '')) LIKE LOWER(%(presence_domain)s) "
+                                   "AND LOWER(array_to_string(array_agg(art.title), '|', '')) LIKE LOWER(%(art_name)s);",
+                                   d)
+        except psycopg2.DatabaseError as e:
+            logging.error(f"Encountered database error on search_collections(): {e}")
+            self.db.rollback()
+            raise e
+
+        return self.db_cursor.fetchall()
diff --git a/ArtNet/file/file_reader.py b/ArtNet/file/file_reader.py
index 61a1185..e49eec6 100644
--- a/ArtNet/file/file_reader.py
+++ b/ArtNet/file/file_reader.py
@@ -32,6 +32,8 @@ class FileReader:
         while len(dirs) != 0:
             curr_dir = dirs.pop()
             curr_full_dir = os.path.join(root, curr_dir)
+            if not os.path.isdir(curr_full_dir):
+                continue
             l += [os.path.join(curr_dir, f) for f in os.listdir(curr_full_dir) if os.path.isfile(os.path.join(curr_full_dir, f))]
             for d in [x for x in os.listdir(curr_full_dir) if os.path.isdir(os.path.join(curr_full_dir, x))]:
                 dirs.append(os.path.join(curr_dir, d))
diff --git a/ArtNet/gui/browse_window.py b/ArtNet/gui/browse_window.py
index dfdc39f..7b08841 100644
--- a/ArtNet/gui/browse_window.py
+++ b/ArtNet/gui/browse_window.py
@@ -41,7 +41,7 @@ class BrowseWindow(ArtnetMainWindow):
         self.ui.image_info_button.clicked.connect(self.on_image_info_clicked)
 
     def display_image(self, image_title: str, image_authors: list, full_path: str, relative_path: str, art_ID: int,
-                      link: str, file_name: str, description: str):
+                      link: str, file_name: str, description: str, collections: list):
         """
         Display the described image in the GraphicsView
         """
diff --git a/ArtNet/gui/dialogs/artist_modify_dialog/artist_modify_dialog.py b/ArtNet/gui/dialogs/artist_modify_dialog/artist_modify_dialog.py
index 3ed320f..baa3738 100644
--- a/ArtNet/gui/dialogs/artist_modify_dialog/artist_modify_dialog.py
+++ b/ArtNet/gui/dialogs/artist_modify_dialog/artist_modify_dialog.py
@@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
 class Ui_Artist_Mod_Dialog(object):
     def setupUi(self, Artist_Mod_Dialog):
         Artist_Mod_Dialog.setObjectName("Artist_Mod_Dialog")
-        Artist_Mod_Dialog.resize(311, 205)
+        Artist_Mod_Dialog.resize(311, 253)
         self.verticalLayout = QtWidgets.QVBoxLayout(Artist_Mod_Dialog)
         self.verticalLayout.setObjectName("verticalLayout")
         self.dialog_frame = QtWidgets.QFrame(Artist_Mod_Dialog)
@@ -49,6 +49,10 @@ class Ui_Artist_Mod_Dialog(object):
         sizePolicy.setVerticalStretch(0)
         sizePolicy.setHeightForWidth(self.id_label.sizePolicy().hasHeightForWidth())
         self.id_label.setSizePolicy(sizePolicy)
+        font = QtGui.QFont()
+        font.setBold(True)
+        font.setWeight(75)
+        self.id_label.setFont(font)
         self.id_label.setObjectName("id_label")
         self.verticalLayout_5.addWidget(self.id_label)
         self.id_layout = QtWidgets.QVBoxLayout()
@@ -65,6 +69,10 @@ class Ui_Artist_Mod_Dialog(object):
         sizePolicy.setVerticalStretch(0)
         sizePolicy.setHeightForWidth(self.description_label.sizePolicy().hasHeightForWidth())
         self.description_label.setSizePolicy(sizePolicy)
+        font = QtGui.QFont()
+        font.setBold(True)
+        font.setWeight(75)
+        self.description_label.setFont(font)
         self.description_label.setObjectName("description_label")
         self.verticalLayout_5.addWidget(self.description_label)
         self.description_line = QtWidgets.QLineEdit(self.frame)
@@ -87,8 +95,8 @@ class Ui_Artist_Mod_Dialog(object):
         _translate = QtCore.QCoreApplication.translate
         Artist_Mod_Dialog.setWindowTitle(_translate("Artist_Mod_Dialog", "Dialog"))
         self.dialog_topic.setText(_translate("Artist_Mod_Dialog", "Artist Topic"))
-        self.id_label.setText(_translate("Artist_Mod_Dialog", "Artist ID:"))
-        self.description_label.setText(_translate("Artist_Mod_Dialog", "Artist Description:"))
+        self.id_label.setText(_translate("Artist_Mod_Dialog", "Artist ID"))
+        self.description_label.setText(_translate("Artist_Mod_Dialog", "Artist Name"))
 
 
 if __name__ == "__main__":
diff --git a/ArtNet/gui/dialogs/artist_modify_dialog/artist_modify_dialog.ui b/ArtNet/gui/dialogs/artist_modify_dialog/artist_modify_dialog.ui
index 85cf331..4f77729 100644
--- a/ArtNet/gui/dialogs/artist_modify_dialog/artist_modify_dialog.ui
+++ b/ArtNet/gui/dialogs/artist_modify_dialog/artist_modify_dialog.ui
@@ -7,7 +7,7 @@
     0
     0
     311
-    205
+    253
    
   
   
@@ -63,8 +63,14 @@
              0
             
            
+           
+            
+             75
+             true
+            
+           
            
-            Artist ID:
+            Artist ID
            
           
          
@@ -90,8 +96,14 @@
              0
             
            
+           
+            
+             75
+             true
+            
+           
            
-            Artist Description:
+            Artist Name
            
           
          
diff --git a/ArtNet/gui/dialogs/image_select_dialog/image_sel_dialog.py b/ArtNet/gui/dialogs/image_select_dialog/image_sel_dialog.py
new file mode 100644
index 0000000..05aefa1
--- /dev/null
+++ b/ArtNet/gui/dialogs/image_select_dialog/image_sel_dialog.py
@@ -0,0 +1,247 @@
+import logging
+import os.path
+
+from PyQt5 import QtWidgets
+from PyQt5.QtCore import Qt, QSize, QModelIndex
+from PyQt5.QtGui import QStandardItem, QStandardItemModel, QIcon
+
+from ArtNet.gui.dialogs.image_select_dialog.image_select_dialog import Ui_Dialog
+from ArtNet.db.db_adapter import DBAdapter
+from ArtNet.singleton_manager import SingletonManager
+
+
+class ImageSelectDialog(QtWidgets.QDialog):
+
+    def __init__(self, db_connection, parent=None):
+        super().__init__(parent)
+        self.parent = parent
+        self.__config = SingletonManager.get_manager().config.data
+        self.__file_root = self.__config["file_root"]
+        self.db_connection: DBAdapter = db_connection
+
+        self.data: dict = None  # data to be returned by this dialog to the parent
+
+        self.__tag_search_result = list()
+        self.__presence_search_result = list()
+        self.__art_list_dict = dict()
+        self.curr_selected_tags = list()
+        self.curr_selected_presences = list()
+
+        self.setWindowTitle("Select Art")
+
+        self.ui = Ui_Dialog()
+        self.ui.setupUi(self)
+
+        self.ui.search_tag_line.textChanged.connect(self.on_tag_search_changed)
+        self.ui.search_presence_name_line.textChanged.connect(self.on_presence_search_changed)
+        self.ui.search_presence_domain_line.textChanged.connect(self.on_presence_search_changed)
+
+        self.ui.art_selection_list.hide()
+        self.ui.art_selection_list.doubleClicked.connect(self.on_art_doubleclicked)
+
+        self.on_presence_search_changed("")
+        self.on_tag_search_changed("")
+
+    def set_tag_search_result_list(self, tags: list):
+        """
+        Set the tag search result list given the tags from the search
+        """
+        item_model = QStandardItemModel(self.ui.search_tag_list)
+
+        self.__tag_search_result = tags
+
+        for tag in tags:
+            item = QStandardItem(tag)
+            flags = Qt.ItemIsEnabled
+
+            item.setData(Qt.Unchecked, Qt.CheckStateRole)
+            flags |= Qt.ItemIsUserCheckable
+            if self.curr_selected_tags is not None and tag in self.curr_selected_tags:
+                # already selected, implied or aliased tags
+                item.setCheckState(Qt.Checked)
+            item.setFlags(flags)
+            item_model.appendRow(item)
+
+        item_model.itemChanged.connect(self.on_tag_search_item_changed)
+        self.ui.search_tag_list.setModel(item_model)
+
+    def refresh_selected_tags_list(self):
+        """
+        Refreshes the selected tags list according to self.curr_selected_tags
+        """
+        item_model = QStandardItemModel(self.ui.selected_tag_list)
+
+        for tag in self.curr_selected_tags:
+            item = QStandardItem(tag)
+            flags = Qt.ItemIsEnabled
+
+            item.setData(Qt.Unchecked, Qt.CheckStateRole)
+            flags |= Qt.ItemIsUserCheckable
+            item.setCheckState(Qt.Checked)
+            item.setFlags(flags)
+            item_model.appendRow(item)
+
+        item_model.itemChanged.connect(self.on_tag_selected_item_changed)
+        self.ui.selected_tag_list.setModel(item_model)
+
+        self.refresh_image_selection()
+
+    def refresh_selected_presences_list(self):
+        """
+        Refreshes the selected presences list according to self.curr_selected_presences
+        """
+        item_model = QStandardItemModel(self.ui.selected_presence_list)
+
+        for name, domain in self.curr_selected_presences:
+            item = QStandardItem(f"{name}:{domain}")
+            flags = Qt.ItemIsEnabled
+
+            item.setData(Qt.Unchecked, Qt.CheckStateRole)
+            flags |= Qt.ItemIsUserCheckable
+            item.setCheckState(Qt.Checked)
+            item.setFlags(flags)
+            item_model.appendRow(item)
+
+        item_model.itemChanged.connect(self.on_presence_selected_item_changed)
+        self.ui.selected_presence_list.setModel(item_model)
+
+        self.refresh_image_selection()
+
+    def refresh_image_selection(self):
+        """
+        Refreshes the images available for selection by using self.curr_selected_presences and self.curr_selected_tags
+        """
+        logging.debug(f"Refreshing info selection with tags {self.curr_selected_tags} "
+                      f"and presences {self.curr_selected_presences}")
+        arts = self.db_connection.search_art(tags=self.curr_selected_tags, resolve_tags=True,
+                                             presences=self.curr_selected_presences)
+        #print("Result:", arts)
+
+        self.ui.art_placeholder_label.hide()
+        self.ui.art_selection_list.show()
+
+        self.ui.art_selection_list.setViewMode(QtWidgets.QListView.IconMode)
+
+        self.__art_list_dict = dict()
+        item_model = QStandardItemModel(self.ui.art_selection_list)
+        items = list()
+        if len(arts) > 100:
+            arts = arts[:100]
+        for art in arts:
+            art_data = self.db_connection.get_art_by_ID(art)
+            full_path = os.path.join(self.__file_root, art_data["path"])
+            if not os.path.isfile(full_path):
+                continue
+
+            icon = QIcon()
+            icon.addFile(full_path)
+            flags = Qt.ItemIsEnabled
+
+            item = QStandardItem(
+                art_data["title"] if art_data["title"] is not None else os.path.basename(art_data["path"]))
+            item.setData(icon, role=Qt.DecorationRole)
+            item.setData(art_data["ID"], role=Qt.UserRole)  # allows the item to be identified again
+
+            presences = self.db_connection.get_authors_of_art_by_ID(art_data["ID"])
+
+            p = ""
+            for presence in presences:
+                p += f"{presence[0]}:{presence[1]}|"
+            p = p[:-1]
+
+            item.setData(f"{art_data['title']} \nby \n{p} \n\nDescription:\n{art_data['description']}", role=Qt.ToolTipRole)  # tooltip for the item
+
+            item.setFlags(flags)
+            items.append(item)
+            self.__art_list_dict[art_data["ID"]] = art_data
+
+        #print("Adding to last row", items)
+        item_model.appendColumn(items)
+
+        self.ui.art_selection_list.setIconSize(QSize(150, 150))
+        self.ui.art_selection_list.setModel(item_model)
+
+    def set_presence_search_result_list(self, presences: list):
+        """
+        Set the presence search result list as given by presences.
+        """
+        item_model = QStandardItemModel(self.ui.search_presence_list)
+
+        self.__presence_search_result = presences
+        for name, domain in presences:
+            item = QStandardItem(f"{name}:{domain}")
+            flags = Qt.ItemIsEnabled
+
+            item.setData(Qt.Unchecked, Qt.CheckStateRole)
+            flags |= Qt.ItemIsUserCheckable
+
+            item.setFlags(flags)
+            item_model.appendRow(item)
+
+        item_model.itemChanged.connect(self.on_presence_search_item_changed)
+        self.ui.search_presence_list.setModel(item_model)
+
+    def on_art_doubleclicked(self, index: QModelIndex):
+        art_id = index.model().itemData(index)[Qt.UserRole]
+        print(f"Double-clicked: {self.__art_list_dict[art_id]}")
+        self.data = self.__art_list_dict[art_id]
+        self.done(QtWidgets.QDialog.Accepted)
+
+    def on_tag_search_changed(self, v: str):
+        result = self.db_connection.search_fuzzy_tag(v, all_if_empty=True)
+
+        self.set_tag_search_result_list([r[0] for r in result])
+
+    def on_presence_search_changed(self, v: str):
+        presence_name = self.ui.search_presence_name_line.text()
+        presence_domain = self.ui.search_presence_domain_line.text()
+
+        result = self.db_connection.search_fuzzy_presence(name=presence_name, domain=presence_domain, all_if_empty=True)
+
+        presences = list()
+        for name, domain in result:
+            presences.append((name, domain))
+        self.set_presence_search_result_list(presences)
+
+    def on_tag_search_item_changed(self, item: QStandardItem):
+        logging.info(f"Tag search item {item.text()} has changed!")
+        if item.checkState() == Qt.Checked:
+            self.curr_selected_tags.append(item.text())
+        if item.checkState() == Qt.Unchecked:
+            self.curr_selected_tags.remove(item.text())
+
+        self.refresh_selected_tags_list()
+
+    def on_tag_selected_item_changed(self, item: QStandardItem):
+        logging.info(f"Tag select item {item.text()} has changed!")
+        self.curr_selected_tags.remove(item.text())
+
+        self.refresh_selected_tags_list()
+        self.set_tag_search_result_list(self.__tag_search_result)
+
+    def on_presence_search_item_changed(self, item: QStandardItem):
+        logging.info(f"Presence search item {item.text()} has changed!")
+        name, domain = item.text().split(":")
+        if item.checkState() == Qt.Checked:
+            self.curr_selected_presences.append((name, domain))
+        elif item.checkState() == Qt.Unchecked:
+            self.curr_selected_presences.remove((name, domain))
+
+        self.refresh_selected_presences_list()
+
+    def on_presence_selected_item_changed(self, item: QStandardItem):
+        logging.info(f"Presence select item {item.text()} has changed!")
+        name, domain = item.text().split(":")
+        self.curr_selected_presences.remove((name, domain))
+
+        self.set_presence_search_result_list(self.__presence_search_result)
+        self.refresh_selected_presences_list()
+
+    def exec_(self) -> dict:
+        if super(ImageSelectDialog, self).exec_() == QtWidgets.QDialog.Rejected:
+            return None
+
+        if self.data is None:
+            self.done(QtWidgets.QDialog.Rejected)
+
+        return self.data
diff --git a/ArtNet/gui/importer_window.py b/ArtNet/gui/importer_window.py
index 59176cb..610f8fc 100644
--- a/ArtNet/gui/importer_window.py
+++ b/ArtNet/gui/importer_window.py
@@ -20,6 +20,8 @@ from ArtNet.gui.dockers.presence.presence_dock import PresenceDocker
 from ArtNet.gui.dialogs.category_modify_dialog.category_mod_dialog import CategoryModDialog
 from ArtNet.gui.dialogs.tag_import_dialog.tag_imp_dialog import TagImportDialog
 from ArtNet.gui.dialogs.link_input_dialog.link_dialog import LinkInputDialog
+from ArtNet.gui.dialogs.collection_select_dialog.collection_sel_dialog import CollectionSelectDialog
+from ArtNet.gui.dialogs.collection_modify_dialog.collection_dialog import CollectionModDialog
 
 from ArtNet.web.link_generator import LinkGenerator
 
@@ -28,6 +30,7 @@ class ImporterWindow(ArtnetMainWindow):
     UNKNOWN_TITLE = "-Title Unknown-"
     UNKNOWN_PRESENCE = "(Not in Database)"
     UNKNOWN_LINK = "No Source Available"
+    NO_COLLECTION = "No Collections"
 
     def __init__(self, main):
         super().__init__(main)
@@ -52,6 +55,7 @@ class ImporterWindow(ArtnetMainWindow):
         self.curr_tags: list = list()
         self.curr_implied_tags: list = list()
         self.curr_tag_aliases: list = list()
+        self.curr_collections: list = list()
 
         self.__data_changed: bool = False
 
@@ -154,6 +158,7 @@ class ImporterWindow(ArtnetMainWindow):
             "link": self.curr_link,
             "md5_hash": self.main.get_md5_of_image(self.curr_art_path),
             "description": new_description,
+            "collections": self.curr_collections,
         }
 
         for presence in self.curr_presences:
@@ -166,10 +171,19 @@ class ImporterWindow(ArtnetMainWindow):
                 msg.exec_()
                 return False
 
+        for coll_id, coll_name in self.curr_collections:
+            msg = QtWidgets.QMessageBox()
+            msg.setWindowTitle("Could not save collection!")
+            msg.setInformativeText("The saving and proper selection of collections is not supported as of now!")
+            msg.setIcon(QtWidgets.QMessageBox.Warning)
+            msg.exec_()
+            break
+
         self.main.db_connection.save_image(ID=image_data["ID"], title=image_data["title"],
                                            authors=image_data["authors"], path=image_data["path"],
                                            tags=image_data["tags"], link=image_data["link"],
-                                           md5_hash=image_data["md5_hash"], desc=image_data["description"])
+                                           md5_hash=image_data["md5_hash"], desc=image_data["description"],
+                                           collections=image_data["collections"])
 
         self.set_temporary_status_message("Saved {0} ({1}) to ArtNet DB!"
                                           .format(image_data["title"], image_data["ID"]), 5000)
@@ -318,6 +332,30 @@ class ImporterWindow(ArtnetMainWindow):
 
         self.data_changed = True
 
+    def get_current_collections(self) -> list:
+        """
+        Get the collections currently associated with the current art
+        """
+        return self.curr_collections
+
+    def set_current_collections(self, collections: list):
+        """
+        Set the collections associated with the current art
+        :param collections: List[Tuple[coll_id, coll_name]]
+        """
+        self.curr_collections = collections
+        if len(collections) == 0:
+            self.ui.collections_label.setText(ImporterWindow.NO_COLLECTION)
+        else:
+            s = ""
+            for coll_id, coll_name, coll_desc in collections:
+                s += "{1}".format("", f"#{coll_id}:{coll_name}")
+                # currently not linking to anything
+                s += "|"
+            s = s[:-1]
+            self.ui.collections_label.setText(s)
+        self.data_changed = True
+
     def get_categories(self, search: str, all_if_empty: bool = False):
         """
         Fuzzy Query for categories in the database.
@@ -332,6 +370,35 @@ class ImporterWindow(ArtnetMainWindow):
 
         return self.main.db_connection.search_fuzzy_categories(search)
 
+    def create_collection(self, collection_name: str, description: str = None) -> int:
+        """
+        Create a new collection with the given name and description
+
+        Returns the assigned ID of the collection
+        """
+        return self.main.db_connection.create_collection(collection_name, description)
+
+    def get_collection(self, collection_id: int):
+        """
+        Fetch the collection given by the id. If not present None is returned.
+        """
+        return self.main.db_connection.get_collection(collection_id)
+
+    def delete_collection(self, collection_id: int):
+        """
+        Delete the collection given by the id from the database.
+        """
+        self.main.db_connection.delete_collection(collection_id)
+
+    def search_collections(self, collection_name: str = None, presence_name: str = None,
+                           presence_domain: str = None, art_name: str = None):
+        """
+        Search for collections that fit the given parameters. All parameters are searched fuzzy.
+        """
+        return self.main.db_connection.search_collections(collection_name=collection_name,
+                                                          presence_name=presence_name, presence_domain=presence_domain,
+                                                          art_name=art_name)
+
     def get_image_link_from_label(self) -> str:
         """
         Gets the image link from the label if it is a valid link.
@@ -432,17 +499,18 @@ class ImporterWindow(ArtnetMainWindow):
         :param tags:
         :return:
         """
+        tags.sort()
         item_model = QStandardItemModel(self.ui.search_result_list)
 
         for tag in tags:
             item = QStandardItem(tag)
             flags = Qt.ItemIsEnabled
 
-            if tag not in self.curr_imply_tags + self.curr_tag_aliases and tag not in self.curr_tags:
+            if tag not in self.curr_implied_tags:
                 # new tag and not implied yet
                 item.setData(Qt.Unchecked, Qt.CheckStateRole)
                 flags |= Qt.ItemIsUserCheckable
-            if self.curr_tags is not None and tag in (self.curr_tags + self.curr_imply_tags + self.curr_tag_aliases):
+            if self.curr_tags is not None and tag in (self.curr_tags + self.curr_implied_tags + self.curr_tag_aliases):
                 # already selected, implied or aliased tags
                 item.setCheckState(Qt.Checked)
             item.setFlags(flags)
@@ -463,21 +531,37 @@ class ImporterWindow(ArtnetMainWindow):
         Also updates the tag implication list if no_implication is False
         :param tags:
         :param set_checked:
-        :param no_implication: bool indicating if the implication list should also be updated
+        :param no_implication: not used
         :return:
         """
+        tags.sort()
         self.curr_tags = tags
         item_model = QStandardItemModel(self.ui.tag_list)
 
+        implied_tags = []
+        for x in self.curr_tags:
+            # collect all implied tags into a list
+            implied_tags += self.main.db_connection.get_all_tag_implications_by_name(x)
+
+        self.set_implied_list(implied_tags)
+
         for tag in tags:
             item = QStandardItem(tag)
-            item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
-            item.setData(Qt.Unchecked, Qt.CheckStateRole)
 
-            item_model.appendRow(item)
+            flags = Qt.ItemIsEnabled
+            if tag not in self.curr_implied_tags:
+                # new tag and not implied yet
+                item.setData(Qt.Unchecked, Qt.CheckStateRole)
+                flags |= Qt.ItemIsUserCheckable
 
             if set_checked:
-                item.setCheckState(Qt.Checked)
+                if tag not in self.curr_implied_tags:
+                    item.setCheckState(Qt.Checked)
+                else:
+                    item.setCheckState(Qt.Unchecked)
+
+            item.setFlags(flags)
+            item_model.appendRow(item)
 
         item_model.itemChanged.connect(self.on_tag_item_changed)
         self.ui.tag_list.setModel(item_model)
@@ -490,7 +574,7 @@ class ImporterWindow(ArtnetMainWindow):
         :param tags:
         :return:
         """
-        self.curr_imply_tags = tags
+        self.curr_implied_tags = tags
         item_model = QStandardItemModel(self.ui.implied_tag_list)
         done = []
         for tag in tags:
@@ -506,7 +590,7 @@ class ImporterWindow(ArtnetMainWindow):
         self.data_changed = True
 
     def display_image(self, image_title: str, image_authors: list, full_path: str, relative_path: str, art_ID: int,
-                      link: str, file_name: str, description: str):
+                      link: str, file_name: str, description: str, collections: list):
         """
         Display an image in the central widget
         :param image_authors:
@@ -517,6 +601,7 @@ class ImporterWindow(ArtnetMainWindow):
         :param link:
         :param file_name:
         :param description:
+        :param collections:
         :return:
         """
         self.curr_art_id = art_ID
@@ -524,6 +609,8 @@ class ImporterWindow(ArtnetMainWindow):
         self.curr_image_title = image_title
         self.curr_file_name = os.path.basename(full_path)
         self.curr_link = link
+
+        self.set_current_collections(collections)
         self.set_current_presences(image_authors)
 
         file_ending = relative_path.split(".")[-1]
@@ -856,6 +943,11 @@ class ImporterWindow(ArtnetMainWindow):
                                            category=tag_data["category"])
         self.on_tag_search_change()
 
+        implied_tags = []  # refresh implied tags, the edit/addition might have changed smth
+        for x in self.curr_tags:
+            # collect all implied tags into a list
+            implied_tags += self.main.db_connection.get_all_tag_implications_by_name(x)
+
     def on_tag_deletion_clicked(self):
         logging.info("Clicked Tag Deletion!")
         dialog = TagSelectDialog(self, delete_tag=True)
@@ -946,17 +1038,38 @@ class ImporterWindow(ArtnetMainWindow):
     def on_tag_search_item_changed(self, item: QStandardItem):
         if item.checkState() == Qt.Checked:
             self.curr_tags.append(item.text())
+
+            aliases = self.main.db_connection.get_tag_aliases_by_name(item.text())
+            for alias in aliases:
+                self.curr_tags.append(alias)
+            implications = self.main.db_connection.get_all_tag_implications_by_name(item.text())
+            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())
+                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)
             else:
-                return
+                raise Exception("Something went terribly wrong!")
+
         self.set_tag_list(self.curr_tags)
+        self.on_tag_search_change()
 
     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:
+                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())
                 self.set_tag_list(self.curr_tags)
                 self.on_tag_search_change()
@@ -967,7 +1080,7 @@ class ImporterWindow(ArtnetMainWindow):
         tags = self.main.db_connection.search_fuzzy_tag(self.ui.tag_search_bar.text(), all_if_empty=True)
 
         result = []
-        for tag_name, tag_desc, tag_category in tags:
+        for tag_name, tag_desc, tag_category, tag_id in tags:
             result.append(tag_name)
 
         self.set_tag_search_result_list(result)
diff --git a/ArtNet/gui/windows/artnet_mainwindow.py b/ArtNet/gui/windows/artnet_mainwindow.py
index e6deb05..5ae69fc 100644
--- a/ArtNet/gui/windows/artnet_mainwindow.py
+++ b/ArtNet/gui/windows/artnet_mainwindow.py
@@ -29,7 +29,7 @@ class ArtnetMainWindow(QtWidgets.QMainWindow):
             self.setting_up_data = False
 
     def display_image(self, image_title: str, image_authors: list, full_path: str, relative_path: str, art_ID: int,
-                      link: str, file_name: str, description: str):
+                      link: str, file_name: str, description: str, collections: list):
         """
         Display an image in the central widget
         :param image_authors:
diff --git a/ArtNet/singleton_manager.py b/ArtNet/singleton_manager.py
new file mode 100644
index 0000000..8f2b24b
--- /dev/null
+++ b/ArtNet/singleton_manager.py
@@ -0,0 +1,13 @@
+
+class SingletonManager:
+
+    __instance = None
+
+    @staticmethod
+    def set_instance(instance):
+        SingletonManager.__instance = instance
+
+    @staticmethod
+    def get_manager():
+        if SingletonManager.__instance is not None:
+            return SingletonManager.__instance
diff --git a/DB b/DB
index 7a0b0df..4d9059b 160000
--- a/DB
+++ b/DB
@@ -1 +1 @@
-Subproject commit 7a0b0dfc88e46f1ec31842a34c18d408f23f041d
+Subproject commit 4d9059b55790d9e758c129fbbf3c706266e999a4
diff --git a/__main__.py b/__main__.py
index b5e47ea..3cf14d6 100644
--- a/__main__.py
+++ b/__main__.py
@@ -1,13 +1,9 @@
 from ArtNet.artnet_manager import ArtNetManager
 
-# TODO fix DB bug
-# TODO 1. Open known image
-# TODO 2. edit a tag on the current image & save the edit
-# TODO 3. attempt to save the current image
+# TODO features:
+# TODO 2. Add creation/editing/deletion of Topics (Needs Dialogs)
+# TODO 3. add changing/adding Topics to Artists (See dockers/topic/artist_topic_docker.py)
 
-# TODO fix bugs:
-# TODO 1. import tags on #237 failed to catch all tags
-# TODO 2. pressing prev. Unknown from ~#238 returns wrong unknowns! (always #136, #132, #127, then correct)
 
 if __name__ == "__main__":
     am = ArtNetManager()