Skip to content

Commit

Permalink
toggle favorite tag via badge
Browse files Browse the repository at this point in the history
  • Loading branch information
yedpodtrzitko committed Aug 19, 2024
1 parent fd738d9 commit b2ed7e3
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 81 deletions.
93 changes: 35 additions & 58 deletions tagstudio/src/core/library/alchemy/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
TagBoxTypes,
TextField,
)
from .joins import TagField, TagSubtag
from .joins import TagSubtag, TagField
from .models import Entry, Preferences, Tag, TagAlias
from ...constants import PREFS, TS_FOLDER_NAME, TAG_ARCHIVED, TAG_FAVORITE

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

def delete_item(self, item):
logger.info("deleting item", item=item)
with Session(self.engine) as session, session.begin():
with Session(self.engine) as session:
session.delete(item)
session.commit()

def remove_field_tag(self, field: TagBoxField, tag_id: int):
with Session(self.engine) as session, session.begin():
# remove instance of TagField matching combination of `field` and `tag_id`
session.delete(
session.scalar(
def remove_field_tag(self, entry: Entry, tag: Tag, field: TagBoxTypes):
with Session(self.engine) as session:
# find field matching entry and field_type
field = session.scalars(

Check failure on line 128 in tagstudio/src/core/library/alchemy/library.py

View workflow job for this annotation

GitHub Actions / Run MyPy

[mypy] reported by reviewdog 🐶 Incompatible types in assignment (expression has type "TagBoxField", variable has type "TagBoxTypes") [assignment] Raw Output: /home/runner/work/TagStudio/TagStudio/tagstudio/src/core/library/alchemy/library.py:128:21: error: Incompatible types in assignment (expression has type "TagBoxField", variable has type "TagBoxTypes") [assignment]
select(TagBoxField).where(
and_(
TagBoxField.entry_id == entry.id,
TagBoxField.type == field,
)
)
).first()

if not field:
logger.error("no field found", entry=entry, field=field)
return False

try:
# find the record in `TagField` table and delete it
tag_field = session.scalars(
select(TagField).where(
and_(
TagField.tag_id == tag.id,
TagField.field_id == field.id,

Check failure on line 147 in tagstudio/src/core/library/alchemy/library.py

View workflow job for this annotation

GitHub Actions / Run MyPy

[mypy] reported by reviewdog 🐶 "TagBoxTypes" has no attribute "id" [attr-defined] Raw Output: /home/runner/work/TagStudio/TagStudio/tagstudio/src/core/library/alchemy/library.py:147:50: error: "TagBoxTypes" has no attribute "id" [attr-defined]
TagField.tag_id == tag_id,
)
)
)
)

session.commit()
).first()
if tag_field:
session.delete(tag_field)
session.commit()
return True
except IntegrityError as e:
logger.exception(e)
session.rollback()
return False

def get_entry(self, entry_id: int) -> Entry | None:
"""Load entry without joins."""
Expand All @@ -153,7 +172,7 @@ def entries(self) -> list[Entry]:
"""Load all entries with joins.
Debugging purposes only.
"""
with Session(self.engine) as session, session.begin():
with Session(self.engine) as session:
stmt = (
select(Entry)
.outerjoin(Entry.text_fields)
Expand Down Expand Up @@ -469,7 +488,7 @@ def remove_field(
field: Field,
entry_ids: list[int],
) -> None:
with Session(self.engine) as session, session.begin():
with Session(self.engine) as session:
fields = session.scalars(
select(field.__class__).where(
and_(
Expand All @@ -489,7 +508,7 @@ def update_field(
entry_ids: list[int],
mode: Literal["replace", "append", "remove"],
):
with Session(self.engine) as session, session.begin():
with Session(self.engine) as session:
fields = session.scalars(
select(field.__class__).where(
and_(
Expand Down Expand Up @@ -571,7 +590,7 @@ def add_tag(self, tag: Tag, subtag_ids: list[int] | None = None) -> Tag | None:
return None

def add_field_tag(self, entry: Entry, tag: Tag, field_type: TagBoxTypes) -> bool:
with Session(self.engine) as session, session.begin():
with Session(self.engine) as session:
# find field matching entry and field_type
field = session.scalars(
select(TagBoxField).where(
Expand All @@ -596,48 +615,6 @@ def add_field_tag(self, entry: Entry, tag: Tag, field_type: TagBoxTypes) -> bool
session.rollback()
return False

def add_tag_to_entry_meta_tags(self, tag: int | Tag, entry_id: int) -> None:
if isinstance(tag, Tag):
tag = tag.id

with Session(self.engine) as session, session.begin():
meta_tag_box = session.scalars(
select(TagBoxField).where(
and_(
TagBoxField.entry_id == entry_id,
TagBoxField.type == TagBoxTypes.meta_tag_box,
)
)
).one()
tag = session.scalars(select(Tag).where(Tag.id == tag)).one()

meta_tag_box.tags.add(tag)

def remove_tag_from_entry_meta_tags(self, tag: int | Tag, entry_id: int) -> None:
if isinstance(tag, Tag):
tag = tag.id

with Session(self.engine) as session, session.begin():
meta_tag_box = session.scalars(
select(TagBoxField).where(
and_(
TagBoxField.entry_id == entry_id,
TagBoxField.type == TagBoxTypes.meta_tag_box,
)
)
).one()
tag = session.scalars(select(Tag).where(Tag.id == tag)).one()

meta_tag_box.tags.remove(tag)

def entry_archived_favorited_status(self, entry: int | Entry) -> tuple[bool, bool]:
if isinstance(entry, Entry):
entry = entry.id
with Session(self.engine) as session, session.begin():
entry_ = session.scalars(select(Entry).where(Entry.id == entry)).one()

return (entry_.archived, entry_.favorited)

def save_library_backup_to_disk(self, *args, **kwargs):
logger.error("save_library_backup_to_disk to be implemented")

Expand Down
2 changes: 1 addition & 1 deletion tagstudio/src/core/library/alchemy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def tags(self) -> set[Tag]:
return tag_set

@property
def favorited(self) -> bool:
def is_favorited(self) -> bool:
for tag_box_field in self.tag_box_fields:
for tag in tag_box_field.tags:
if tag.name == "Favorite":
Expand Down
2 changes: 1 addition & 1 deletion tagstudio/src/qt/ts_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -937,7 +937,7 @@ def _init_thumb_grid(self):

for _ in range(self.filter.page_size):
item_thumb = ItemThumb(
None, self.lib, self.preview_panel, (self.thumb_size, self.thumb_size)
None, self.lib, self, (self.thumb_size, self.thumb_size)
)
layout.addWidget(item_thumb)
self.item_thumbs.append(item_thumb)
Expand Down
49 changes: 28 additions & 21 deletions tagstudio/src/qt/widgets/item_thumb.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import time
import typing
from pathlib import Path
from typing import Optional
from typing import Optional, TYPE_CHECKING

import structlog
from PIL import Image, ImageQt
Expand All @@ -26,19 +26,15 @@
TAG_FAVORITE,
TAG_ARCHIVED,
)
from src.core.library import ItemType, Entry
from src.core.library import ItemType, Entry, Library
from src.core.library.alchemy.fields import TagBoxTypes
from src.qt.flowlayout import FlowWidget
from src.qt.helpers.file_opener import FileOpenerHelper
from src.qt.widgets.thumb_renderer import ThumbRenderer
from src.qt.widgets.thumb_button import ThumbButton

if typing.TYPE_CHECKING:
from src.qt.widgets.preview_panel import PreviewPanel

ERROR = "[ERROR]"
WARNING = "[WARNING]"
INFO = "[INFO]"
if TYPE_CHECKING:
from src.qt.ts_qt import QtDriver

logger = structlog.get_logger(__name__)

Expand Down Expand Up @@ -86,16 +82,16 @@ class ItemThumb(FlowWidget):

def __init__(
self,
mode,
library,
panel: "PreviewPanel",
mode: ItemType,
library: Library,
driver: "QtDriver",
thumb_size: tuple[int, int],
):
"""Modes: entry, collation, tag_group"""
super().__init__()
self.lib = library
self.panel = panel
self.mode = mode
self.driver = driver
self.item_id: int = -1
self.is_favorite: bool = False
self.is_archived: bool = False
Expand Down Expand Up @@ -456,22 +452,33 @@ def on_archived_check(self, toggle_value: bool):
self.toggle_item_tag(toggle_value, TAG_ARCHIVED)

Check failure on line 452 in tagstudio/src/qt/widgets/item_thumb.py

View workflow job for this annotation

GitHub Actions / Run MyPy

[mypy] reported by reviewdog 🐶 Missing positional arguments "tag_id", "field_type" in call to "toggle_item_tag" of "ItemThumb" [call-arg] Raw Output: /home/runner/work/TagStudio/TagStudio/tagstudio/src/qt/widgets/item_thumb.py:452:9: error: Missing positional arguments "tag_id", "field_type" in call to "toggle_item_tag" of "ItemThumb" [call-arg]

Check failure on line 452 in tagstudio/src/qt/widgets/item_thumb.py

View workflow job for this annotation

GitHub Actions / Run MyPy

[mypy] reported by reviewdog 🐶 Argument 1 to "toggle_item_tag" of "ItemThumb" has incompatible type "bool"; expected "Entry" [arg-type] Raw Output: /home/runner/work/TagStudio/TagStudio/tagstudio/src/qt/widgets/item_thumb.py:452:30: error: Argument 1 to "toggle_item_tag" of "ItemThumb" has incompatible type "bool"; expected "Entry" [arg-type]

Check failure on line 452 in tagstudio/src/qt/widgets/item_thumb.py

View workflow job for this annotation

GitHub Actions / Run MyPy

[mypy] reported by reviewdog 🐶 Argument 2 to "toggle_item_tag" of "ItemThumb" has incompatible type "int"; expected "bool" [arg-type] Raw Output: /home/runner/work/TagStudio/TagStudio/tagstudio/src/qt/widgets/item_thumb.py:452:44: error: Argument 2 to "toggle_item_tag" of "ItemThumb" has incompatible type "int"; expected "bool" [arg-type]

def on_favorite_check(self, toggle_value: bool):
# if self.mode == ItemType.ENTRY:
self.is_favorite = toggle_value
self.toggle_item_tag(toggle_value, TAG_FAVORITE)
for idx in self.driver.selected:
entry = self.driver.frame_content[idx]
self.toggle_item_tag(
entry, toggle_value, TAG_FAVORITE, TagBoxTypes.meta_tag_box
)

def toggle_item_tag(self, toggle_value: bool, tag_id: int):
entry = self.panel.driver.frame_content[self.item_id]
def toggle_item_tag(
self, entry: Entry, toggle_value: bool, tag_id: int, field_type: TagBoxTypes
):
logger.info(
"toggle_item_tag",
entry_id=entry.id,
toggle_value=toggle_value,
tag_id=tag_id,
field_type=field_type,
)

tag = self.lib.get_tag(tag_id)

if toggle_value:
self.favorite_badge.setHidden(False)
self.lib.add_field_tag(entry, tag, TagBoxTypes.meta_tag_box)
self.lib.add_field_tag(entry, tag, field_type)
else:
self.lib.remove_field_tag(entry, tag, TagBoxTypes.meta_tag_box)
self.lib.remove_field_tag(entry, tag, field_type)

if self.panel.is_open:
self.panel.update_widgets()
if self.driver.preview_panel.is_open:
self.driver.preview_panel.update_widgets()

self.panel.driver.update_badges()
self.driver.update_badges()
26 changes: 26 additions & 0 deletions tagstudio/tests/qt/test_item_thumb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import pytest

from src.core.library import ItemType
from src.qt.widgets.item_thumb import ItemThumb


@pytest.mark.parametrize("toggle_value", (True, False))
def test_toggle_favorite(qtbot, qt_driver, toggle_value):
# panel = PreviewPanel(qt_driver.lib, qt_driver)

entry = qt_driver.lib.entries[0]

qt_driver.frame_content = [entry]
qt_driver.selected = [0]

thumb = ItemThumb(ItemType.ENTRY, qt_driver.lib, qt_driver, (100, 100))

qtbot.addWidget(thumb)

thumb.on_favorite_check(toggle_value)

# reload entry
entry = qt_driver.lib.entries[0]

# check entry has favorite tag in meta tags field
assert entry.is_favorited == toggle_value

0 comments on commit b2ed7e3

Please sign in to comment.