feat: modules dependencies
This commit is contained in:
		@@ -12,8 +12,16 @@ if TYPE_CHECKING:
 | 
				
			|||||||
project_module = Table(
 | 
					project_module = Table(
 | 
				
			||||||
    'project_module',
 | 
					    'project_module',
 | 
				
			||||||
    BaseModel.metadata,
 | 
					    BaseModel.metadata,
 | 
				
			||||||
    Column('project_id', ForeignKey('projects.id')),
 | 
					    Column('project_id', ForeignKey('projects.id'), primary_key=True),
 | 
				
			||||||
    Column('module_id', ForeignKey('modules.id')),
 | 
					    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)
 | 
					    icon_name: Mapped[Optional[str]] = mapped_column(unique=True, nullable=False)
 | 
				
			||||||
    is_deleted: Mapped[bool] = mapped_column(default=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(
 | 
					    projects: Mapped[list['Project']] = relationship(
 | 
				
			||||||
        'Project',
 | 
					        'Project',
 | 
				
			||||||
        uselist=True,
 | 
					        uselist=True,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,7 @@ class ModuleSchema(BaseSchema):
 | 
				
			|||||||
    label: str
 | 
					    label: str
 | 
				
			||||||
    icon_name: Optional[str] = None
 | 
					    icon_name: Optional[str] = None
 | 
				
			||||||
    is_deleted: bool
 | 
					    is_deleted: bool
 | 
				
			||||||
 | 
					    depends_on: list["ModuleSchema"] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ProjectSchema(ProjectGeneralInfoSchema):
 | 
					class ProjectSchema(ProjectGeneralInfoSchema):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from sqlalchemy import select, update, func, delete
 | 
					from sqlalchemy import select, update, func, delete
 | 
				
			||||||
from sqlalchemy.orm import selectinload
 | 
					from sqlalchemy.orm import selectinload, immediateload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from card_attributes import CardAttributesCommandHandler
 | 
					from card_attributes import CardAttributesCommandHandler
 | 
				
			||||||
from models import Project, Board, Module
 | 
					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)
 | 
					            .outerjoin(board_count_sub, Project.id == board_count_sub.c.project_id)
 | 
				
			||||||
            .options(
 | 
					            .options(
 | 
				
			||||||
                selectinload(Project.attributes),
 | 
					                selectinload(Project.attributes),
 | 
				
			||||||
                selectinload(Project.modules),
 | 
					                selectinload(Project.modules)
 | 
				
			||||||
 | 
					                .selectinload(Module.depends_on),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .where(Project.is_deleted == False)
 | 
					            .where(Project.is_deleted == False)
 | 
				
			||||||
            .order_by(Project.id)
 | 
					            .order_by(Project.id)
 | 
				
			||||||
@@ -90,22 +91,48 @@ class ProjectService(BaseService):
 | 
				
			|||||||
        stmt = (
 | 
					        stmt = (
 | 
				
			||||||
            select(Module)
 | 
					            select(Module)
 | 
				
			||||||
            .where(Module.is_deleted == False)
 | 
					            .where(Module.is_deleted == False)
 | 
				
			||||||
 | 
					            .options(
 | 
				
			||||||
 | 
					                selectinload(Module.depends_on),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        modules = await self.session.scalars(stmt)
 | 
					        modules = await self.session.scalars(stmt)
 | 
				
			||||||
        return GetAllModulesResponse(modules=modules.all())
 | 
					        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:
 | 
					    async def update_project_modules(self, request: UpdateModulesRequest) -> UpdateModulesResponse:
 | 
				
			||||||
        project: Optional[Project] = await self.session.get(Project, request.project_id)
 | 
					        project: Optional[Project] = await self.session.get(Project, request.project_id)
 | 
				
			||||||
        if not project:
 | 
					        if not project:
 | 
				
			||||||
            return UpdateModulesResponse(ok=False, message=f"Проект с ID {request.project_id} не найден")
 | 
					            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 = (
 | 
					        modules_stmt = (
 | 
				
			||||||
            select(Module)
 | 
					            select(Module)
 | 
				
			||||||
            .where(Module.id.in_(request.module_ids))
 | 
					            .where(Module.id.in_(request.module_ids))
 | 
				
			||||||
 | 
					            .options(load_dependencies)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        modules = (await self.session.scalars(modules_stmt)).all()
 | 
					        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()
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return UpdateModulesResponse(ok=True, message="Модули успешно обновлены")
 | 
					        return UpdateModulesResponse(ok=True, message="Модули успешно обновлены")
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user