Skip to content

Commit 0d166e6

Browse files
authored
feat(parity): backend for aliases and parent tags (#596)
* backend for aliases and parents * resolve merge conflics
1 parent f6a1ca6 commit 0d166e6

File tree

12 files changed

+687
-105
lines changed

12 files changed

+687
-105
lines changed

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

Lines changed: 82 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from sqlalchemy.exc import IntegrityError
2525
from sqlalchemy.orm import (
2626
Session,
27+
aliased,
2728
contains_eager,
2829
make_transient,
2930
selectinload,
@@ -417,13 +418,18 @@ def search_library(
417418
statement = select(Entry)
418419

419420
if search.tag:
421+
SubtagAlias = aliased(Tag) # noqa: N806
420422
statement = (
421423
statement.join(Entry.tag_box_fields)
422424
.join(TagBoxField.tags)
425+
.outerjoin(Tag.aliases)
426+
.outerjoin(SubtagAlias, Tag.subtags)
423427
.where(
424428
or_(
425429
Tag.name.ilike(search.tag),
426430
Tag.shorthand.ilike(search.tag),
431+
TagAlias.name.ilike(search.tag),
432+
SubtagAlias.name.ilike(search.tag),
427433
)
428434
)
429435
)
@@ -752,18 +758,23 @@ def add_entry_field_type(
752758
)
753759
return True
754760

755-
def add_tag(self, tag: Tag, subtag_ids: list[int] | None = None) -> Tag | None:
761+
def add_tag(
762+
self,
763+
tag: Tag,
764+
subtag_ids: set[int] | None = None,
765+
alias_names: set[str] | None = None,
766+
alias_ids: set[int] | None = None,
767+
) -> Tag | None:
756768
with Session(self.engine, expire_on_commit=False) as session:
757769
try:
758770
session.add(tag)
759771
session.flush()
760772

761-
for subtag_id in subtag_ids or []:
762-
subtag = TagSubtag(
763-
parent_id=tag.id,
764-
child_id=subtag_id,
765-
)
766-
session.add(subtag)
773+
if subtag_ids is not None:
774+
self.update_subtags(tag, subtag_ids, session)
775+
776+
if alias_ids is not None and alias_names is not None:
777+
self.update_aliases(tag, alias_ids, alias_names, session)
767778

768779
session.commit()
769780

@@ -847,75 +858,101 @@ def save_library_backup_to_disk(self) -> Path:
847858

848859
def get_tag(self, tag_id: int) -> Tag:
849860
with Session(self.engine) as session:
850-
tags_query = select(Tag).options(selectinload(Tag.subtags))
861+
tags_query = select(Tag).options(selectinload(Tag.subtags), selectinload(Tag.aliases))
851862
tag = session.scalar(tags_query.where(Tag.id == tag_id))
852863

853864
session.expunge(tag)
854865
for subtag in tag.subtags:
855866
session.expunge(subtag)
856867

868+
for alias in tag.aliases:
869+
session.expunge(alias)
870+
857871
return tag
858872

873+
def get_alias(self, tag_id: int, alias_id: int) -> TagAlias:
874+
with Session(self.engine) as session:
875+
alias_query = select(TagAlias).where(TagAlias.id == alias_id, TagAlias.tag_id == tag_id)
876+
alias = session.scalar(alias_query.where(TagAlias.id == alias_id))
877+
878+
return alias
879+
859880
def add_subtag(self, base_id: int, new_tag_id: int) -> bool:
881+
if base_id == new_tag_id:
882+
return False
883+
860884
# open session and save as parent tag
861885
with Session(self.engine) as session:
862-
tag = TagSubtag(
886+
subtag = TagSubtag(
863887
parent_id=base_id,
864888
child_id=new_tag_id,
865889
)
866890

867891
try:
868-
session.add(tag)
892+
session.add(subtag)
869893
session.commit()
870894
return True
871895
except IntegrityError:
872896
session.rollback()
873897
logger.exception("IntegrityError")
874898
return False
875899

876-
def update_tag(self, tag: Tag, subtag_ids: list[int]) -> None:
900+
def remove_subtag(self, base_id: int, remove_tag_id: int) -> bool:
901+
with Session(self.engine) as session:
902+
p_id = base_id
903+
r_id = remove_tag_id
904+
remove = session.query(TagSubtag).filter_by(parent_id=p_id, child_id=r_id).one()
905+
session.delete(remove)
906+
session.commit()
907+
908+
return True
909+
910+
def update_tag(
911+
self,
912+
tag: Tag,
913+
subtag_ids: set[int] | None = None,
914+
alias_names: set[str] | None = None,
915+
alias_ids: set[int] | None = None,
916+
) -> None:
877917
"""Edit a Tag in the Library."""
878-
# TODO - maybe merge this with add_tag?
918+
self.add_tag(tag, subtag_ids, alias_names, alias_ids)
879919

880-
if tag.shorthand:
881-
tag.shorthand = slugify(tag.shorthand)
920+
def update_aliases(self, tag, alias_ids, alias_names, session):
921+
prev_aliases = session.scalars(select(TagAlias).where(TagAlias.tag_id == tag.id)).all()
882922

883-
if tag.aliases:
884-
# TODO
885-
...
923+
for alias in prev_aliases:
924+
if alias.id not in alias_ids or alias.name not in alias_names:
925+
session.delete(alias)
926+
else:
927+
alias_ids.remove(alias.id)
928+
alias_names.remove(alias.name)
886929

887-
# save the tag
888-
with Session(self.engine) as session:
889-
try:
890-
# update the existing tag
891-
session.add(tag)
892-
session.flush()
930+
for alias_name in alias_names:
931+
alias = TagAlias(alias_name, tag.id)
932+
session.add(alias)
893933

894-
# load all tag's subtag to know which to remove
895-
prev_subtags = session.scalars(
896-
select(TagSubtag).where(TagSubtag.parent_id == tag.id)
897-
).all()
934+
def update_subtags(self, tag, subtag_ids, session):
935+
if tag.id in subtag_ids:
936+
subtag_ids.remove(tag.id)
898937

899-
for subtag in prev_subtags:
900-
if subtag.child_id not in subtag_ids:
901-
session.delete(subtag)
902-
else:
903-
# no change, remove from list
904-
subtag_ids.remove(subtag.child_id)
938+
# load all tag's subtag to know which to remove
939+
prev_subtags = session.scalars(select(TagSubtag).where(TagSubtag.parent_id == tag.id)).all()
905940

906-
# create remaining items
907-
for subtag_id in subtag_ids:
908-
# add new subtag
909-
subtag = TagSubtag(
910-
parent_id=tag.id,
911-
child_id=subtag_id,
912-
)
913-
session.add(subtag)
941+
for subtag in prev_subtags:
942+
if subtag.child_id not in subtag_ids:
943+
session.delete(subtag)
944+
else:
945+
# no change, remove from list
946+
subtag_ids.remove(subtag.child_id)
914947

915-
session.commit()
916-
except IntegrityError:
917-
session.rollback()
918-
logger.exception("IntegrityError")
948+
# create remaining items
949+
for subtag_id in subtag_ids:
950+
# add new subtag
951+
subtag = TagSubtag(
952+
parent_id=tag.id,
953+
child_id=subtag_id,
954+
)
955+
session.add(subtag)
919956

920957
def prefs(self, key: LibraryPrefs) -> Any:
921958
# load given item from Preferences table

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from pathlib import Path
2-
from typing import Optional
32

43
from sqlalchemy import JSON, ForeignKey, Integer, event
54
from sqlalchemy.orm import Mapped, mapped_column, relationship
@@ -29,11 +28,11 @@ class TagAlias(Base):
2928
tag_id: Mapped[int] = mapped_column(ForeignKey("tags.id"))
3029
tag: Mapped["Tag"] = relationship(back_populates="aliases")
3130

32-
def __init__(self, name: str, tag: Optional["Tag"] = None):
31+
def __init__(self, name: str, tag_id: int | None = None):
3332
self.name = name
3433

35-
if tag:
36-
self.tag = tag
34+
if tag_id is not None:
35+
self.tag_id = tag_id
3736

3837
super().__init__()
3938

@@ -73,6 +72,10 @@ def subtag_ids(self) -> list[int]:
7372
def alias_strings(self) -> list[str]:
7473
return [alias.name for alias in self.aliases]
7574

75+
@property
76+
def alias_ids(self) -> list[int]:
77+
return [tag.id for tag in self.aliases]
78+
7679
def __init__(
7780
self,
7881
name: str,

tagstudio/src/qt/flowlayout.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,7 @@ def _do_layout(self, rect: QRect, test_only: bool) -> float:
140140
x = next_x
141141
line_height = max(line_height, item.sizeHint().height())
142142

143+
if len(self._item_list) == 0:
144+
return 0
145+
143146
return y + line_height - rect.y() * ((len(self._item_list)) / len(self._item_list))

0 commit comments

Comments
 (0)