Skip to content

Commit 2b6067d

Browse files
committed
toggle favorite tag via badge
1 parent fd738d9 commit 2b6067d

File tree

7 files changed

+102
-92
lines changed

7 files changed

+102
-92
lines changed

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

Lines changed: 35 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
TagBoxTypes,
2424
TextField,
2525
)
26-
from .joins import TagField, TagSubtag
26+
from .joins import TagSubtag, TagField
2727
from .models import Entry, Preferences, Tag, TagAlias
2828
from ...constants import PREFS, TS_FOLDER_NAME, TAG_ARCHIVED, TAG_FAVORITE
2929

@@ -118,25 +118,44 @@ def open_library(self, library_dir: Path | str) -> None:
118118

119119
def delete_item(self, item):
120120
logger.info("deleting item", item=item)
121-
with Session(self.engine) as session, session.begin():
121+
with Session(self.engine) as session:
122122
session.delete(item)
123123
session.commit()
124124

125-
def remove_field_tag(self, field: TagBoxField, tag_id: int):
126-
with Session(self.engine) as session, session.begin():
127-
# remove instance of TagField matching combination of `field` and `tag_id`
128-
session.delete(
129-
session.scalar(
125+
def remove_field_tag(self, entry: Entry, tag_id: int, field_type: TagBoxTypes):
126+
with Session(self.engine) as session:
127+
# find field matching entry and field_type
128+
field = session.scalars(
129+
select(TagBoxField).where(
130+
and_(
131+
TagBoxField.entry_id == entry.id,
132+
TagBoxField.type == field_type,
133+
)
134+
)
135+
).first()
136+
137+
if not field:
138+
logger.error("no field found", entry=entry, field=field)
139+
return False
140+
141+
try:
142+
# find the record in `TagField` table and delete it
143+
tag_field = session.scalars(
130144
select(TagField).where(
131145
and_(
132-
TagField.field_id == field.id,
133146
TagField.tag_id == tag_id,
147+
TagField.field_id == field.id,
134148
)
135149
)
136-
)
137-
)
138-
139-
session.commit()
150+
).first()
151+
if tag_field:
152+
session.delete(tag_field)
153+
session.commit()
154+
return True
155+
except IntegrityError as e:
156+
logger.exception(e)
157+
session.rollback()
158+
return False
140159

141160
def get_entry(self, entry_id: int) -> Entry | None:
142161
"""Load entry without joins."""
@@ -153,7 +172,7 @@ def entries(self) -> list[Entry]:
153172
"""Load all entries with joins.
154173
Debugging purposes only.
155174
"""
156-
with Session(self.engine) as session, session.begin():
175+
with Session(self.engine) as session:
157176
stmt = (
158177
select(Entry)
159178
.outerjoin(Entry.text_fields)
@@ -469,7 +488,7 @@ def remove_field(
469488
field: Field,
470489
entry_ids: list[int],
471490
) -> None:
472-
with Session(self.engine) as session, session.begin():
491+
with Session(self.engine) as session:
473492
fields = session.scalars(
474493
select(field.__class__).where(
475494
and_(
@@ -489,7 +508,7 @@ def update_field(
489508
entry_ids: list[int],
490509
mode: Literal["replace", "append", "remove"],
491510
):
492-
with Session(self.engine) as session, session.begin():
511+
with Session(self.engine) as session:
493512
fields = session.scalars(
494513
select(field.__class__).where(
495514
and_(
@@ -571,7 +590,7 @@ def add_tag(self, tag: Tag, subtag_ids: list[int] | None = None) -> Tag | None:
571590
return None
572591

573592
def add_field_tag(self, entry: Entry, tag: Tag, field_type: TagBoxTypes) -> bool:
574-
with Session(self.engine) as session, session.begin():
593+
with Session(self.engine) as session:
575594
# find field matching entry and field_type
576595
field = session.scalars(
577596
select(TagBoxField).where(
@@ -596,48 +615,6 @@ def add_field_tag(self, entry: Entry, tag: Tag, field_type: TagBoxTypes) -> bool
596615
session.rollback()
597616
return False
598617

599-
def add_tag_to_entry_meta_tags(self, tag: int | Tag, entry_id: int) -> None:
600-
if isinstance(tag, Tag):
601-
tag = tag.id
602-
603-
with Session(self.engine) as session, session.begin():
604-
meta_tag_box = session.scalars(
605-
select(TagBoxField).where(
606-
and_(
607-
TagBoxField.entry_id == entry_id,
608-
TagBoxField.type == TagBoxTypes.meta_tag_box,
609-
)
610-
)
611-
).one()
612-
tag = session.scalars(select(Tag).where(Tag.id == tag)).one()
613-
614-
meta_tag_box.tags.add(tag)
615-
616-
def remove_tag_from_entry_meta_tags(self, tag: int | Tag, entry_id: int) -> None:
617-
if isinstance(tag, Tag):
618-
tag = tag.id
619-
620-
with Session(self.engine) as session, session.begin():
621-
meta_tag_box = session.scalars(
622-
select(TagBoxField).where(
623-
and_(
624-
TagBoxField.entry_id == entry_id,
625-
TagBoxField.type == TagBoxTypes.meta_tag_box,
626-
)
627-
)
628-
).one()
629-
tag = session.scalars(select(Tag).where(Tag.id == tag)).one()
630-
631-
meta_tag_box.tags.remove(tag)
632-
633-
def entry_archived_favorited_status(self, entry: int | Entry) -> tuple[bool, bool]:
634-
if isinstance(entry, Entry):
635-
entry = entry.id
636-
with Session(self.engine) as session, session.begin():
637-
entry_ = session.scalars(select(Entry).where(Entry.id == entry)).one()
638-
639-
return (entry_.archived, entry_.favorited)
640-
641618
def save_library_backup_to_disk(self, *args, **kwargs):
642619
logger.error("save_library_backup_to_disk to be implemented")
643620

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def tags(self) -> set[Tag]:
129129
return tag_set
130130

131131
@property
132-
def favorited(self) -> bool:
132+
def is_favorited(self) -> bool:
133133
for tag_box_field in self.tag_box_fields:
134134
for tag in tag_box_field.tags:
135135
if tag.name == "Favorite":

tagstudio/src/qt/ts_qt.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,7 @@ def _init_thumb_grid(self):
937937

938938
for _ in range(self.filter.page_size):
939939
item_thumb = ItemThumb(
940-
None, self.lib, self.preview_panel, (self.thumb_size, self.thumb_size)
940+
None, self.lib, self, (self.thumb_size, self.thumb_size)
941941
)
942942
layout.addWidget(item_thumb)
943943
self.item_thumbs.append(item_thumb)

tagstudio/src/qt/widgets/item_thumb.py

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import time
55
import typing
66
from pathlib import Path
7-
from typing import Optional
7+
from typing import Optional, TYPE_CHECKING
88

99
import structlog
1010
from PIL import Image, ImageQt
@@ -26,19 +26,15 @@
2626
TAG_FAVORITE,
2727
TAG_ARCHIVED,
2828
)
29-
from src.core.library import ItemType, Entry
29+
from src.core.library import ItemType, Entry, Library
3030
from src.core.library.alchemy.fields import TagBoxTypes
3131
from src.qt.flowlayout import FlowWidget
3232
from src.qt.helpers.file_opener import FileOpenerHelper
3333
from src.qt.widgets.thumb_renderer import ThumbRenderer
3434
from src.qt.widgets.thumb_button import ThumbButton
3535

36-
if typing.TYPE_CHECKING:
37-
from src.qt.widgets.preview_panel import PreviewPanel
38-
39-
ERROR = "[ERROR]"
40-
WARNING = "[WARNING]"
41-
INFO = "[INFO]"
36+
if TYPE_CHECKING:
37+
from src.qt.ts_qt import QtDriver
4238

4339
logger = structlog.get_logger(__name__)
4440

@@ -86,16 +82,16 @@ class ItemThumb(FlowWidget):
8682

8783
def __init__(
8884
self,
89-
mode,
90-
library,
91-
panel: "PreviewPanel",
85+
mode: ItemType,
86+
library: Library,
87+
driver: "QtDriver",
9288
thumb_size: tuple[int, int],
9389
):
9490
"""Modes: entry, collation, tag_group"""
9591
super().__init__()
9692
self.lib = library
97-
self.panel = panel
9893
self.mode = mode
94+
self.driver = driver
9995
self.item_id: int = -1
10096
self.is_favorite: bool = False
10197
self.is_archived: bool = False
@@ -451,27 +447,41 @@ def leaveEvent(self, event: QEvent) -> None:
451447
return super().leaveEvent(event)
452448

453449
def on_archived_check(self, toggle_value: bool):
454-
# if self.mode == ItemType.ENTRY:
455450
self.is_archived = toggle_value
456-
self.toggle_item_tag(toggle_value, TAG_ARCHIVED)
451+
for idx in self.driver.selected:
452+
entry = self.driver.frame_content[idx]
453+
self.toggle_item_tag(
454+
entry, toggle_value, TAG_ARCHIVED, TagBoxTypes.meta_tag_box
455+
)
457456

458457
def on_favorite_check(self, toggle_value: bool):
459-
# if self.mode == ItemType.ENTRY:
460458
self.is_favorite = toggle_value
461-
self.toggle_item_tag(toggle_value, TAG_FAVORITE)
459+
for idx in self.driver.selected:
460+
entry = self.driver.frame_content[idx]
461+
self.toggle_item_tag(
462+
entry, toggle_value, TAG_FAVORITE, TagBoxTypes.meta_tag_box
463+
)
462464

463-
def toggle_item_tag(self, toggle_value: bool, tag_id: int):
464-
entry = self.panel.driver.frame_content[self.item_id]
465+
def toggle_item_tag(
466+
self, entry: Entry, toggle_value: bool, tag_id: int, field_type: TagBoxTypes
467+
):
468+
logger.info(
469+
"toggle_item_tag",
470+
entry_id=entry.id,
471+
toggle_value=toggle_value,
472+
tag_id=tag_id,
473+
field_type=field_type,
474+
)
465475

466476
tag = self.lib.get_tag(tag_id)
467477

468478
if toggle_value:
469479
self.favorite_badge.setHidden(False)
470-
self.lib.add_field_tag(entry, tag, TagBoxTypes.meta_tag_box)
480+
self.lib.add_field_tag(entry, tag, field_type)
471481
else:
472-
self.lib.remove_field_tag(entry, tag, TagBoxTypes.meta_tag_box)
482+
self.lib.remove_field_tag(entry, tag.id, field_type)
473483

474-
if self.panel.is_open:
475-
self.panel.update_widgets()
484+
if self.driver.preview_panel.is_open:
485+
self.driver.preview_panel.update_widgets()
476486

477-
self.panel.driver.update_badges()
487+
self.driver.update_badges()

tagstudio/src/qt/widgets/preview_panel.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -837,7 +837,7 @@ def write_container(
837837
window_title=f"Edit {field.name}",
838838
save_callback=(
839839
lambda content: (
840-
self.update_field(field, content),
840+
self.update_field(field, content), # type: ignore
841841
self.update_widgets(),
842842
)
843843
),
@@ -999,15 +999,14 @@ def remove_field(self, field):
999999
"Tried to remove field from Entry that never had it", entry=entry
10001000
)
10011001

1002-
def update_field(self, field, content):
1002+
def update_field(self, field, content) -> None:
10031003
"""Remove a field from all selected Entries, given a field object."""
10041004
field = dict(field)
10051005
for grid_idx in self.selected:
10061006
entry = self.driver.frame_content[grid_idx]
10071007
try:
1008-
logger.info(field)
10091008
index = entry.fields.index(field)
1010-
self.lib.update_entry_field(entry.id, index, content, "replace")
1009+
self.lib.update_field(index, content, [entry.id], "replace")
10111010
except ValueError:
10121011
logger.exception(
10131012
"Tried to update field from Entry that never had it", entry=entry

tagstudio/src/qt/widgets/tag_box.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from src.core.constants import TAG_FAVORITE, TAG_ARCHIVED
1414
from src.core.library import Entry, Tag
1515
from src.core.library.alchemy.enums import FilterState
16-
from src.core.library.alchemy.fields import TagBoxField
16+
from src.core.library.alchemy.fields import TagBoxField, TagBoxTypes
1717
from src.qt.flowlayout import FlowLayout
1818
from src.qt.widgets.fields import FieldWidget
1919
from src.qt.widgets.tag import TagWidget
@@ -170,11 +170,9 @@ def remove_tag(self, tag_id: int):
170170

171171
for grid_idx in self.driver.selected:
172172
entry = self.driver.frame_content[grid_idx]
173-
# TODO - remove tag from correct field
174173
assert entry.fields
175-
tag_field = entry.fields[0]
176-
177-
self.driver.lib.remove_field_tag(tag_field, tag_id)
174+
# TODO - remove tag from correct field
175+
self.driver.lib.remove_field_tag(entry, tag_id, TagBoxTypes.tag_box)
178176

179177
self.updated.emit()
180178

tagstudio/tests/qt/test_item_thumb.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import pytest
2+
3+
from src.core.library import ItemType
4+
from src.qt.widgets.item_thumb import ItemThumb
5+
6+
7+
@pytest.mark.parametrize("toggle_value", (True, False))
8+
def test_toggle_favorite(qtbot, qt_driver, toggle_value):
9+
# panel = PreviewPanel(qt_driver.lib, qt_driver)
10+
11+
entry = qt_driver.lib.entries[0]
12+
13+
qt_driver.frame_content = [entry]
14+
qt_driver.selected = [0]
15+
16+
thumb = ItemThumb(ItemType.ENTRY, qt_driver.lib, qt_driver, (100, 100))
17+
18+
qtbot.addWidget(thumb)
19+
20+
thumb.on_favorite_check(toggle_value)
21+
22+
# reload entry
23+
entry = qt_driver.lib.entries[0]
24+
25+
# check entry has favorite tag in meta tags field
26+
assert entry.is_favorited == toggle_value

0 commit comments

Comments
 (0)