diff --git a/models/module.py b/models/module.py index 9850e18..0a0f063 100644 --- a/models/module.py +++ b/models/module.py @@ -12,8 +12,16 @@ if TYPE_CHECKING: project_module = Table( 'project_module', BaseModel.metadata, - Column('project_id', ForeignKey('projects.id')), - Column('module_id', ForeignKey('modules.id')), + Column('project_id', ForeignKey('projects.id'), primary_key=True), + Column('module_id', ForeignKey('modules.id'), primary_key=True), +) + + +module_dependencies = Table( + 'module_dependencies', + BaseModel.metadata, + Column('module_id', ForeignKey('modules.id'), primary_key=True), + Column('depends_on_id', ForeignKey('modules.id'), primary_key=True), ) @@ -26,6 +34,24 @@ class Module(BaseModel): icon_name: Mapped[Optional[str]] = mapped_column(unique=True, nullable=False) is_deleted: Mapped[bool] = mapped_column(default=False) + depends_on: Mapped[list['Module']] = relationship( + 'Module', + secondary=module_dependencies, + primaryjoin='Module.id == module_dependencies.c.module_id', + secondaryjoin='Module.id == module_dependencies.c.depends_on_id', + back_populates='depended_on_by', + lazy='immediate', + ) + + depended_on_by: Mapped[list['Module']] = relationship( + 'Module', + secondary='module_dependencies', + primaryjoin='Module.id == module_dependencies.c.depends_on_id', + secondaryjoin='Module.id == module_dependencies.c.module_id', + back_populates='depends_on', + lazy='noload', + ) + projects: Mapped[list['Project']] = relationship( 'Project', uselist=True, diff --git a/schemas/project.py b/schemas/project.py index c769ec4..d1d89ac 100644 --- a/schemas/project.py +++ b/schemas/project.py @@ -22,6 +22,7 @@ class ModuleSchema(BaseSchema): label: str icon_name: Optional[str] = None is_deleted: bool + depends_on: list["ModuleSchema"] = [] class ProjectSchema(ProjectGeneralInfoSchema): diff --git a/services/project.py b/services/project.py index e740433..e9d65e6 100644 --- a/services/project.py +++ b/services/project.py @@ -1,7 +1,7 @@ from datetime import datetime from sqlalchemy import select, update, func, delete -from sqlalchemy.orm import selectinload +from sqlalchemy.orm import selectinload, immediateload from card_attributes import CardAttributesCommandHandler from models import Project, Board, Module @@ -27,7 +27,8 @@ class ProjectService(BaseService): .outerjoin(board_count_sub, Project.id == board_count_sub.c.project_id) .options( selectinload(Project.attributes), - selectinload(Project.modules), + selectinload(Project.modules) + .selectinload(Module.depends_on), ) .where(Project.is_deleted == False) .order_by(Project.id) @@ -90,22 +91,48 @@ class ProjectService(BaseService): stmt = ( select(Module) .where(Module.is_deleted == False) + .options( + selectinload(Module.depends_on), + ) ) modules = await self.session.scalars(stmt) return GetAllModulesResponse(modules=modules.all()) + def get_module_with_dependencies(self, module: Module, visited_ids: set[int]) -> set[Module]: + result_modules = {module} + + for dependency in module.depends_on: + if dependency.id not in visited_ids: + visited_ids.add(dependency.id) + result_modules.update( + self.get_module_with_dependencies(dependency, visited_ids) + ) + + return result_modules + async def update_project_modules(self, request: UpdateModulesRequest) -> UpdateModulesResponse: project: Optional[Project] = await self.session.get(Project, request.project_id) if not project: return UpdateModulesResponse(ok=False, message=f"Проект с ID {request.project_id} не найден") + load_dependencies = immediateload(Module.depends_on) + for i in range(5): + load_dependencies = load_dependencies.immediateload(Module.depends_on) + modules_stmt = ( select(Module) .where(Module.id.in_(request.module_ids)) + .options(load_dependencies) ) modules = (await self.session.scalars(modules_stmt)).all() - project.modules = modules + result_modules = set() + visited_module_ids = set(request.module_ids) + for module in modules: + result_modules.update( + self.get_module_with_dependencies(module, visited_module_ids) + ) + project.modules = list(result_modules) await self.session.commit() return UpdateModulesResponse(ok=True, message="Модули успешно обновлены")