Skip to content

Commit fdfd649

Browse files
DandyDev01VasigaranAndAngelseakrueger
authored
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>
1 parent 8387676 commit fdfd649

File tree

6 files changed

+104
-2
lines changed

6 files changed

+104
-2
lines changed

tagstudio/src/core/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313

1414
TAG_FAVORITE = 1
1515
TAG_ARCHIVED = 0
16+
RESERVED_TAG_IDS = range(0, 999)

tagstudio/src/core/library/alchemy/library.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,39 @@ def update_entry_path(self, entry_id: int | Entry, path: Path) -> None:
657657
session.execute(update_stmt)
658658
session.commit()
659659

660+
def remove_tag(self, tag: Tag):
661+
with Session(self.engine, expire_on_commit=False) as session:
662+
try:
663+
subtags = session.scalars(
664+
select(TagSubtag).where(TagSubtag.parent_id == tag.id)
665+
).all()
666+
667+
tags_query = select(Tag).options(
668+
selectinload(Tag.subtags), selectinload(Tag.aliases)
669+
)
670+
tag = session.scalar(tags_query.where(Tag.id == tag.id))
671+
672+
aliases = session.scalars(select(TagAlias).where(TagAlias.tag_id == tag.id))
673+
674+
for alias in aliases or []:
675+
session.delete(alias)
676+
677+
for subtag in subtags or []:
678+
session.delete(subtag)
679+
session.expunge(subtag)
680+
681+
session.delete(tag)
682+
683+
session.commit()
684+
685+
session.expunge(tag)
686+
return tag
687+
688+
except IntegrityError as e:
689+
logger.exception(e)
690+
session.rollback()
691+
return None
692+
660693
def remove_tag_from_field(self, tag: Tag, field: TagBoxField) -> None:
661694
with Session(self.engine) as session:
662695
field_ = session.scalars(select(TagBoxField).where(TagBoxField.id == field.id)).one()

tagstudio/src/qt/modals/tag_database.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@
99
QFrame,
1010
QHBoxLayout,
1111
QLineEdit,
12+
QMessageBox,
13+
QPushButton,
1214
QScrollArea,
1315
QVBoxLayout,
1416
QWidget,
1517
)
18+
from src.core.constants import RESERVED_TAG_IDS
1619
from src.core.library import Library, Tag
1720
from src.qt.modals.build_tag import BuildTagPanel
1821
from src.qt.widgets.panel import PanelModal, PanelWidget
@@ -59,8 +62,32 @@ def __init__(self, library: Library):
5962
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
6063
self.scroll_area.setWidget(self.scroll_contents)
6164

65+
self.create_tag_button = QPushButton()
66+
self.create_tag_button.setText("Create Tag")
67+
self.create_tag_button.clicked.connect(self.build_tag)
68+
6269
self.root_layout.addWidget(self.search_field)
6370
self.root_layout.addWidget(self.scroll_area)
71+
self.root_layout.addWidget(self.create_tag_button)
72+
self.update_tags()
73+
74+
def build_tag(self):
75+
self.modal = PanelModal(
76+
BuildTagPanel(self.lib),
77+
"New Tag",
78+
"Add Tag",
79+
has_save=True,
80+
)
81+
82+
panel: BuildTagPanel = self.modal.widget
83+
self.modal.saved.connect(
84+
lambda: (
85+
self.lib.add_tag(panel.build_tag(), panel.subtag_ids),
86+
self.modal.hide(),
87+
self.update_tags(),
88+
)
89+
)
90+
self.modal.show()
6491

6592
def on_return(self, text: str):
6693
if text and self.first_tag_id >= 0:
@@ -84,14 +111,41 @@ def update_tags(self, query: str | None = None):
84111
row = QHBoxLayout(container)
85112
row.setContentsMargins(0, 0, 0, 0)
86113
row.setSpacing(3)
87-
tag_widget = TagWidget(tag, has_edit=True, has_remove=False)
114+
115+
if tag.id in RESERVED_TAG_IDS:
116+
tag_widget = TagWidget(tag, has_edit=False, has_remove=False)
117+
else:
118+
tag_widget = TagWidget(tag, has_edit=True, has_remove=True)
119+
88120
tag_widget.on_edit.connect(lambda checked=False, t=tag: self.edit_tag(t))
121+
tag_widget.on_remove.connect(lambda t=tag: self.remove_tag(t))
89122
row.addWidget(tag_widget)
90123
self.scroll_layout.addWidget(container)
91124

92125
self.search_field.setFocus()
93126

127+
def remove_tag(self, tag: Tag):
128+
if tag.id in RESERVED_TAG_IDS:
129+
return
130+
131+
message_box = QMessageBox()
132+
message_box.setWindowTitle("Remove tag")
133+
message_box.setText("Are you sure you want to remove " + tag.name + "?")
134+
message_box.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) # type: ignore
135+
message_box.setIcon(QMessageBox.Question) # type: ignore
136+
137+
result = message_box.exec()
138+
139+
if result != QMessageBox.Ok: # type: ignore
140+
return
141+
142+
self.lib.remove_tag(tag)
143+
self.update_tags()
144+
94145
def edit_tag(self, tag: Tag):
146+
if tag.id in RESERVED_TAG_IDS:
147+
return
148+
95149
build_tag_panel = BuildTagPanel(self.lib, tag=tag)
96150

97151
self.edit_modal = PanelModal(

tagstudio/src/qt/widgets/panel.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ def __init__(
9191
self.root_layout.setStretch(1, 2)
9292
self.root_layout.addWidget(self.button_container)
9393

94+
def closeEvent(self, event): # noqa: N802
95+
self.done_button.click()
96+
event.accept()
97+
9498

9599
class PanelWidget(QWidget):
96100
"""Used for widgets that go in a modal panel, ex. for editing or searching."""

tagstudio/src/qt/widgets/thumb_renderer.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import cv2
1414
import numpy as np
15-
import pillow_jxl # noqa: F401
1615
import rawpy
1716
import structlog
1817
from mutagen import MutagenError, flac, id3, mp4

tagstudio/tests/test_library.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,17 @@ def test_subtags_add(library, generate_tag):
213213
assert tag.subtag_ids
214214

215215

216+
def test_remove_tag(library, generate_tag):
217+
tag = library.add_tag(generate_tag("food", id=123))
218+
219+
assert tag
220+
221+
tag_count = len(library.tags)
222+
223+
library.remove_tag(tag)
224+
assert len(library.tags) == tag_count - 1
225+
226+
216227
@pytest.mark.parametrize("is_exclude", [True, False])
217228
def test_search_filter_extensions(library, is_exclude):
218229
# Given

0 commit comments

Comments
 (0)