diff --git a/server/entertainment_decider/models/__init__.py b/server/entertainment_decider/models/__init__.py index 42ae5c9..f7f3ed6 100644 --- a/server/entertainment_decider/models/__init__.py +++ b/server/entertainment_decider/models/__init__.py @@ -13,6 +13,7 @@ from .entities import ( MediaUriMapping, Tag, Tagable, + TagKey, are_multiple_considered, db, get_all_considered, diff --git a/server/entertainment_decider/models/entities.py b/server/entertainment_decider/models/entities.py index b9bda3d..50a3bc9 100644 --- a/server/entertainment_decider/models/entities.py +++ b/server/entertainment_decider/models/entities.py @@ -192,6 +192,8 @@ class Tag(db.Entity, Tagable, TagProto["Tag"]): use_for_preferences: bool = orm.Required(bool, default=True) + tag_keys: Iterable[TagKey] = orm.Set(lambda: TagKey) + super_tag_list: Iterable[Tag] = orm.Set(lambda: Tag, reverse="sub_tag_list") sub_tag_list: Iterable[Tag] = orm.Set(lambda: Tag, reverse="super_tag_list") @@ -207,6 +209,78 @@ class Tag(db.Entity, Tagable, TagProto["Tag"]): return self.super_tag_list +class TagKey(db.Entity): + + num_id: int = orm.PrimaryKey(int, auto=True) + tag_key: str = orm.Required(str, unique=True) + """Format: [/][/] + + These IDs are distinctive of URLs. + Multiple sub-kinds can be used if required. + They should not contain unnecceray information + like names, titles, descriptions, dates + (or only when part of the one unique ID). + + Domains must be used in reverse domain name notation + to allow for efficient prefix searches. + Domains must not start or end with a ".", + all domains are meant to be absolute. + + Internal identifiers should use "." as domain and should omit the first "/" + to avoid collisions with TLDs, + e.g. ".automatic/collection/". + + Identifiers from extractors which want to avoid collisions + because of multiple ones supporting the same site + should choose a domain for their own product + and use the format: /[/][/] + """ + tag: Tag = orm.Required(Tag) + + @classmethod + def get_by_prefix(cls, tag_key_prefix: str) -> Set[Tag]: + key_set = orm.select( + key for key in cls if key.tag_key.startswith(tag_key_prefix) + ) + return {key.tag for key in key_set} + + @classmethod + def get_or_create_tag( + cls, + tag_key: str, + title: Optional[str] = None, + notes: Optional[str] = None, + use_for_preferences: bool = False, + super_tags: Iterable[str] = [], + ) -> Tag: + tag = cls.get_tag(tag_key) + if tag is not None: + if title is not None and not tag.title: + tag.title = title + if notes is not None and not tag.notes: + tag.notes = notes + if tag is None: + tag = Tag( + title=title, + notes=notes, + use_for_preferences=use_for_preferences, + ) + cls( + tag_key=tag_key, + tag=tag, + ) + for super_tag_key in super_tags: + super_tag = cls.get_tag(tag_key=super_tag_key) + if super_tag is not None: + tag.super_tag_list.add(super_tag) + return tag + + @classmethod + def get_tag(cls, tag_key: str) -> Optional[Tag]: + tag: Tag = orm.select(key.tag for key in cls if key.tag_key == tag_key).first() + return tag if tag is not None else None + + ## Element <-> Collection Linking