fix(autotag): the orphan cleanup keeps a system tag a saved view filters on
Feed views store tag ids. A system tag no channel carried for a while was deleted and, once a channel earned it again, recreated under a new id — the view's filter then pointed at nothing, the feed dropped the unknown id, and the view showed the whole feed under its old name. The cleanup now skips tags a saved feed view references (test seen red without it).
This commit is contained in:
@@ -12,7 +12,7 @@ from sqlalchemy import and_, exists, func, select
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app import logs, progress, sysconfig
|
from app import logs, progress, sysconfig
|
||||||
from app.models import Channel, ChannelTag, Tag, Video
|
from app.models import Channel, ChannelTag, SavedView, Tag, Video
|
||||||
|
|
||||||
log = logs.get_logger(__name__)
|
log = logs.get_logger(__name__)
|
||||||
|
|
||||||
@@ -289,12 +289,22 @@ def run_autotag_all(db: Session, only_missing: bool = False) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def _cleanup_orphan_system_tags(db: Session) -> int:
|
def _cleanup_orphan_system_tags(db: Session) -> int:
|
||||||
"""Delete system tags that no longer link to any channel (e.g. after a mapping change)."""
|
"""Delete system tags that no longer link to any channel (e.g. after a mapping change) — but not
|
||||||
|
one a saved feed view filters on. Views hold tag IDS, and a deleted tag comes back under a new id
|
||||||
|
when a channel earns it again, so the view's filter would point at nothing; the feed drops an
|
||||||
|
unknown id, and the view showed the whole feed under its old name (E32 S2)."""
|
||||||
|
in_views = {
|
||||||
|
t
|
||||||
|
for (filters,) in db.execute(select(SavedView.filters).where(SavedView.source == "feed"))
|
||||||
|
for t in ((filters or {}).get("tags") or [])
|
||||||
|
if isinstance(t, int)
|
||||||
|
}
|
||||||
orphans = (
|
orphans = (
|
||||||
db.execute(
|
db.execute(
|
||||||
select(Tag).where(
|
select(Tag).where(
|
||||||
Tag.user_id.is_(None),
|
Tag.user_id.is_(None),
|
||||||
~exists().where(ChannelTag.tag_id == Tag.id),
|
~exists().where(ChannelTag.tag_id == Tag.id),
|
||||||
|
Tag.id.notin_(in_views),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.scalars()
|
.scalars()
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
"""sync/autotag's orphan cleanup (E32 S2: the module was 32% covered).
|
||||||
|
|
||||||
|
A system tag no channel carries any more is deleted — unless a saved feed view filters on it. The
|
||||||
|
views hold tag ids; a deleted tag returns under a NEW id when a channel earns it again, and the feed
|
||||||
|
drops an unknown id, so the view silently showed the whole feed under its old name.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
from app.models import Channel, ChannelTag, SavedView, Tag
|
||||||
|
from app.sync import autotag
|
||||||
|
|
||||||
|
|
||||||
|
def test_an_orphan_system_tag_goes_but_one_a_saved_view_filters_on_stays(db, make_user):
|
||||||
|
user = make_user()
|
||||||
|
used = Tag(user_id=None, name="Gaming", category="topic")
|
||||||
|
orphan = Tag(user_id=None, name="Cooking", category="topic")
|
||||||
|
in_view = Tag(user_id=None, name="Music", category="topic")
|
||||||
|
own = Tag(user_id=user.id, name="Mine", category="other")
|
||||||
|
ch = Channel(id="UC_tags", title="T")
|
||||||
|
db.add_all([used, orphan, in_view, own, ch])
|
||||||
|
db.flush()
|
||||||
|
db.add(ChannelTag(channel_id=ch.id, tag_id=used.id, user_id=None, source="auto"))
|
||||||
|
db.add(
|
||||||
|
SavedView(
|
||||||
|
user_id=user.id, source="feed", name="Music", filters={"tags": [in_view.id], "q": ""}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# A Plex view's filters are another vocabulary; they must not break the scan.
|
||||||
|
db.add(SavedView(user_id=user.id, source="plex", name="P", filters={"genres": ["Drama"]}))
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
assert autotag._cleanup_orphan_system_tags(db) == 1
|
||||||
|
db.expire_all()
|
||||||
|
names = set(db.execute(select(Tag.name)).scalars())
|
||||||
|
assert names == {"Gaming", "Music", "Mine"} # only the unreferenced system orphan went
|
||||||
Reference in New Issue
Block a user