diff --git a/modules/archive/models.py b/modules/archive/models.py index 0549d16a..a6604918 100644 --- a/modules/archive/models.py +++ b/modules/archive/models.py @@ -5,7 +5,7 @@ from typing import Iterable, List, Optional import pytz from sqlalchemy import Column, Integer, ForeignKey, BigInteger, Index -from sqlalchemy.orm import relationship, Session +from sqlalchemy.orm import relationship, Session, joinedload from wrolpi import dates from wrolpi.collections import Collection @@ -36,10 +36,15 @@ class Archive(Base, ModelHelper): id = Column(Integer, primary_key=True) collection_id = Column(Integer, ForeignKey('collection.id', ondelete='CASCADE')) - collection: Collection = relationship('Collection', primaryjoin='Archive.collection_id==Collection.id', - lazy='joined') + collection: Collection = relationship('Collection', primaryjoin='Archive.collection_id==Collection.id') file_group_id = Column(BigInteger, ForeignKey('file_group.id', ondelete='CASCADE'), unique=True, nullable=False) - file_group: FileGroup = relationship('FileGroup', lazy='joined') + file_group: FileGroup = relationship('FileGroup') + + @staticmethod + def json_options() -> tuple: + """Loader options for a query whose Archives will be serialized: `__json__` reads the FileGroup and its Tags, + and the domain (the Collection's name).""" + return joinedload(Archive.file_group).selectinload(FileGroup.tag_files), joinedload(Archive.collection) @serializer def __json__(self) -> dict: diff --git a/modules/docs/lib.py b/modules/docs/lib.py index 72d51de6..d28db9fe 100644 --- a/modules/docs/lib.py +++ b/modules/docs/lib.py @@ -340,7 +340,7 @@ def _doc_response(doc) -> dict: def _get_doc(session, file_group_id: int): from .models import Doc - doc = session.query(Doc).filter_by(file_group_id=file_group_id).one_or_none() + doc = session.query(Doc).options(*Doc.json_options()).filter_by(file_group_id=file_group_id).one_or_none() if not doc: raise ValidationError(f'Doc with file_group_id {file_group_id} not found') return doc @@ -375,7 +375,7 @@ def _search_docs(session: Session, search_str=None, author=None, subject=None, l # the much smaller a/b/c columns. match = fts.translate_websearch(search_str, None if deep else fts.ABC_COLUMNS) if search_str else None - query = session.query(FileGroup).join(Doc, Doc.file_group_id == FileGroup.id) + query = session.query(FileGroup).options(*FileGroup.json_options()).join(Doc, Doc.file_group_id == FileGroup.id) fts_sq = None if match is not None: @@ -441,7 +441,8 @@ def _search_docs(session: Session, search_str=None, author=None, subject=None, l browse_sql = _doc_browse_sql(order_by if order_by in DOC_BROWSE_ORDERS else 'id') if browse_sql: fg_ids = [i for i, in session.execute(text(browse_sql), dict(limit=limit, offset=offset)).fetchall()] - by_id = {fg.id: fg for fg in session.query(FileGroup).filter(FileGroup.id.in_(fg_ids))} if fg_ids else {} + by_id = {fg.id: fg for fg in session.query(FileGroup).options(*FileGroup.json_options()) + .filter(FileGroup.id.in_(fg_ids))} if fg_ids else {} file_groups = [by_id[i] for i in fg_ids] else: file_groups = query.offset(offset).limit(limit).all() diff --git a/modules/docs/models.py b/modules/docs/models.py index fbbc9fa2..ba2e0f62 100644 --- a/modules/docs/models.py +++ b/modules/docs/models.py @@ -2,7 +2,7 @@ import pathlib from typing import Optional from sqlalchemy import Column, Integer, BigInteger, ForeignKey, String, Text, Index, or_ -from sqlalchemy.orm import relationship, Session +from sqlalchemy.orm import relationship, Session, joinedload from wrolpi.common import ModelHelper, Base from wrolpi.db import get_db_session, serializer @@ -56,7 +56,7 @@ class Doc(ModelHelper, Base): description = Column(Text) file_group_id = Column(BigInteger, ForeignKey('file_group.id', ondelete='CASCADE'), nullable=False, unique=True) - file_group: FileGroup = relationship('FileGroup', lazy='joined') + file_group: FileGroup = relationship('FileGroup') sections = relationship('DocSection', back_populates='doc', cascade='all, delete-orphan', passive_deletes=True) @@ -65,6 +65,11 @@ class Doc(ModelHelper, Base): path = str(self.file_group.primary_path) if self.file_group else 'None' return f'' + @staticmethod + def json_options() -> tuple: + """Loader options for a query whose Docs will be serialized: `__json__` reads the FileGroup and its Tags.""" + return joinedload(Doc.file_group).selectinload(FileGroup.tag_files), + @serializer def __json__(self) -> dict: d = self.file_group.__json__() diff --git a/modules/videos/channel/lib.py b/modules/videos/channel/lib.py index 86f48db1..f32d31c1 100644 --- a/modules/videos/channel/lib.py +++ b/modules/videos/channel/lib.py @@ -208,6 +208,7 @@ async def search_channels_by_name(session: Session, name: str, limit: int = 5, name_order = (func.lower(func.replace(Collection.name, ' ', '')).asc(), Collection.name.desc()) if order_by_video_count: stmt = session.query(Channel, func.count(Video.id).label('video_count')) \ + .options(*Channel.json_options()) \ .join(Collection) \ .filter(or_( Collection.name.ilike(f'%{name}%'), @@ -220,6 +221,7 @@ async def search_channels_by_name(session: Session, name: str, limit: int = 5, channels = [i[0] for i in stmt] else: stmt = session.query(Channel) \ + .options(*Channel.json_options()) \ .join(Collection) \ .filter(or_( Collection.name.ilike(f'%{name}%'), @@ -319,5 +321,6 @@ async def tag_channel(session: Session, tag_name: str | None, directory: pathlib async def search_channels(session: Session, tag_names: List[str]) -> List[Channel]: """Search Tagged Channels.""" - channels = session.query(Channel).join(Collection).join(Tag).filter(Tag.name.in_(tag_names)).all() + channels = session.query(Channel).options(*Channel.json_options()) \ + .join(Collection).join(Tag).filter(Tag.name.in_(tag_names)).all() return channels diff --git a/modules/videos/models.py b/modules/videos/models.py index 90ae2efa..b8591ef4 100644 --- a/modules/videos/models.py +++ b/modules/videos/models.py @@ -3,7 +3,7 @@ import pathlib from typing import Optional, Dict, List from sqlalchemy import Column, Integer, String, Boolean, Date, ForeignKey, BigInteger, Index, text, JSON -from sqlalchemy.orm import relationship, Session, deferred, undefer +from sqlalchemy.orm import relationship, Session, deferred, undefer, joinedload, selectinload from sqlalchemy.orm.collections import InstrumentedList from modules.videos.errors import UnknownVideo, UnknownChannel @@ -11,6 +11,7 @@ from wrolpi.captions import read_captions, read_captions_with_timestamps from wrolpi.common import Base, ModelHelper, logger, get_media_directory, get_relative_to_media_directory, \ background_task from wrolpi.db import get_db_curs, get_db_session, session_curs, serializer +from wrolpi.collections.models import Collection from wrolpi.downloader import Download from wrolpi.files.lib import split_path_stem_and_suffix from wrolpi.files.worker import file_worker @@ -46,10 +47,9 @@ class Video(ModelHelper, Base): comments_failed = Column(Boolean, default=False) # see `get_missing_videos_comments` channel_id = Column(Integer, ForeignKey('channel.id')) - channel = relationship('Channel', primaryjoin='Video.channel_id==Channel.id', back_populates='videos', - lazy='joined') + channel = relationship('Channel', primaryjoin='Video.channel_id==Channel.id', back_populates='videos') file_group_id = Column(BigInteger, ForeignKey('file_group.id', ondelete='CASCADE'), unique=True, nullable=False) - file_group: FileGroup = relationship('FileGroup', lazy='joined') + file_group: FileGroup = relationship('FileGroup') def __repr__(self): v = None @@ -58,6 +58,13 @@ class Video(ModelHelper, Base): return f'