feat: delete and create tags from tag database panel (#569)

* [feat] can now add a new tag from the tag library panel

* [feat] can remove tags from the tag library panel
[fix] library panel updates tag list when a new tag is create

* [test] added test for library->remove_tag

* [fix] type error

* removed redundant lambda

Co-authored-by: VasigaranAndAngel <72515046+VasigaranAndAngel@users.noreply.github.com>

* fix: tags with a reserved id could be edited or removed, now they cannot.

* fix: when a tag is removed or edited the preivew panel will update to reflect the changes

Co-authored-by: Sean Krueger <skrueger2270@gmail.com>

* fix: mypy check

* fix: aliases and subtags not being removed from DB when tag they reference was removed.

* feat: added a confirmation message box when removing tags.

* fix: mypy

---------

Co-authored-by: VasigaranAndAngel <72515046+VasigaranAndAngel@users.noreply.github.com>
Co-authored-by: Sean Krueger <skrueger2270@gmail.com>
This commit is contained in:
DandyDev01
2024-12-20 16:22:23 -07:00
committed by GitHub
parent 8387676d79
commit fdfd6490bd
6 changed files with 104 additions and 2 deletions

View File

@@ -13,3 +13,4 @@ FONT_SAMPLE_SIZES: list[int] = [10, 15, 20]
TAG_FAVORITE = 1
TAG_ARCHIVED = 0
RESERVED_TAG_IDS = range(0, 999)

View File

@@ -657,6 +657,39 @@ class Library:
session.execute(update_stmt)
session.commit()
def remove_tag(self, tag: Tag):
with Session(self.engine, expire_on_commit=False) as session:
try:
subtags = session.scalars(
select(TagSubtag).where(TagSubtag.parent_id == tag.id)
).all()
tags_query = select(Tag).options(
selectinload(Tag.subtags), selectinload(Tag.aliases)
)
tag = session.scalar(tags_query.where(Tag.id == tag.id))
aliases = session.scalars(select(TagAlias).where(TagAlias.tag_id == tag.id))
for alias in aliases or []:
session.delete(alias)
for subtag in subtags or []:
session.delete(subtag)
session.expunge(subtag)
session.delete(tag)
session.commit()
session.expunge(tag)
return tag
except IntegrityError as e:
logger.exception(e)
session.rollback()
return None
def remove_tag_from_field(self, tag: Tag, field: TagBoxField) -> None:
with Session(self.engine) as session:
field_ = session.scalars(select(TagBoxField).where(TagBoxField.id == field.id)).one()

View File

@@ -9,10 +9,13 @@ from PySide6.QtWidgets import (
QFrame,
QHBoxLayout,
QLineEdit,
QMessageBox,
QPushButton,
QScrollArea,
QVBoxLayout,
QWidget,
)
from src.core.constants import RESERVED_TAG_IDS
from src.core.library import Library, Tag
from src.qt.modals.build_tag import BuildTagPanel
from src.qt.widgets.panel import PanelModal, PanelWidget
@@ -59,8 +62,32 @@ class TagDatabasePanel(PanelWidget):
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
self.scroll_area.setWidget(self.scroll_contents)
self.create_tag_button = QPushButton()
self.create_tag_button.setText("Create Tag")
self.create_tag_button.clicked.connect(self.build_tag)
self.root_layout.addWidget(self.search_field)
self.root_layout.addWidget(self.scroll_area)
self.root_layout.addWidget(self.create_tag_button)
self.update_tags()
def build_tag(self):
self.modal = PanelModal(
BuildTagPanel(self.lib),
"New Tag",
"Add Tag",
has_save=True,
)
panel: BuildTagPanel = self.modal.widget
self.modal.saved.connect(
lambda: (
self.lib.add_tag(panel.build_tag(), panel.subtag_ids),
self.modal.hide(),
self.update_tags(),
)
)
self.modal.show()
def on_return(self, text: str):
if text and self.first_tag_id >= 0:
@@ -84,14 +111,41 @@ class TagDatabasePanel(PanelWidget):
row = QHBoxLayout(container)
row.setContentsMargins(0, 0, 0, 0)
row.setSpacing(3)
tag_widget = TagWidget(tag, has_edit=True, has_remove=False)
if tag.id in RESERVED_TAG_IDS:
tag_widget = TagWidget(tag, has_edit=False, has_remove=False)
else:
tag_widget = TagWidget(tag, has_edit=True, has_remove=True)
tag_widget.on_edit.connect(lambda checked=False, t=tag: self.edit_tag(t))
tag_widget.on_remove.connect(lambda t=tag: self.remove_tag(t))
row.addWidget(tag_widget)
self.scroll_layout.addWidget(container)
self.search_field.setFocus()
def remove_tag(self, tag: Tag):
if tag.id in RESERVED_TAG_IDS:
return
message_box = QMessageBox()
message_box.setWindowTitle("Remove tag")
message_box.setText("Are you sure you want to remove " + tag.name + "?")
message_box.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) # type: ignore
message_box.setIcon(QMessageBox.Question) # type: ignore
result = message_box.exec()
if result != QMessageBox.Ok: # type: ignore
return
self.lib.remove_tag(tag)
self.update_tags()
def edit_tag(self, tag: Tag):
if tag.id in RESERVED_TAG_IDS:
return
build_tag_panel = BuildTagPanel(self.lib, tag=tag)
self.edit_modal = PanelModal(

View File

@@ -91,6 +91,10 @@ class PanelModal(QWidget):
self.root_layout.setStretch(1, 2)
self.root_layout.addWidget(self.button_container)
def closeEvent(self, event): # noqa: N802
self.done_button.click()
event.accept()
class PanelWidget(QWidget):
"""Used for widgets that go in a modal panel, ex. for editing or searching."""

View File

@@ -12,7 +12,6 @@ from pathlib import Path
import cv2
import numpy as np
import pillow_jxl # noqa: F401
import rawpy
import structlog
from mutagen import MutagenError, flac, id3, mp4

View File

@@ -213,6 +213,17 @@ def test_subtags_add(library, generate_tag):
assert tag.subtag_ids
def test_remove_tag(library, generate_tag):
tag = library.add_tag(generate_tag("food", id=123))
assert tag
tag_count = len(library.tags)
library.remove_tag(tag)
assert len(library.tags) == tag_count - 1
@pytest.mark.parametrize("is_exclude", [True, False])
def test_search_filter_extensions(library, is_exclude):
# Given