diff --git a/backend/app/admin/crud/crud_data_rule.py b/backend/app/admin/crud/crud_data_rule.py index 2240c16d..f71f0698 100644 --- a/backend/app/admin/crud/crud_data_rule.py +++ b/backend/app/admin/crud/crud_data_rule.py @@ -2,9 +2,8 @@ # -*- coding: utf-8 -*- from typing import Sequence -from sqlalchemy import Select, and_, desc, select +from sqlalchemy import Select from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import noload from sqlalchemy_crud_plus import CRUDPlus from backend.app.admin.model import DataRule @@ -31,16 +30,12 @@ class CRUDDataRule(CRUDPlus[DataRule]): :param name: 规则名称 :return: """ - stmt = select(self.model).options(noload(self.model.scopes)).order_by(desc(self.model.created_time)) + filters = {} - filters = [] if name is not None: - filters.append(self.model.name.like(f'%{name}%')) + filters['name__like'] = f'%{name}%' - if filters: - stmt = stmt.where(and_(*filters)) - - return stmt + return await self.select_order('id', load_strategies={'scopes': 'noload'}, **filters) async def get_by_name(self, db: AsyncSession, name: str) -> DataRule | None: """ diff --git a/backend/app/admin/crud/crud_data_scope.py b/backend/app/admin/crud/crud_data_scope.py index ad5b8be1..9e7be362 100644 --- a/backend/app/admin/crud/crud_data_scope.py +++ b/backend/app/admin/crud/crud_data_scope.py @@ -2,9 +2,8 @@ # -*- coding: utf-8 -*- from typing import Sequence -from sqlalchemy import Select, and_, desc, select +from sqlalchemy import Select, select from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import noload, selectinload from sqlalchemy_crud_plus import CRUDPlus from backend.app.admin.model import DataRule, DataScope @@ -42,9 +41,7 @@ class CRUDDataScope(CRUDPlus[DataScope]): :param pk: 范围 ID :return: """ - stmt = select(self.model).options(selectinload(self.model.rules)).where(self.model.id == pk) - data_scope = await db.execute(stmt) - return data_scope.scalars().first() + return await self.select_model(db, pk, load_strategies=['rules']) async def get_all(self, db: AsyncSession) -> Sequence[DataScope]: """ @@ -63,22 +60,14 @@ class CRUDDataScope(CRUDPlus[DataScope]): :param status: 范围状态 :return: """ - stmt = ( - select(self.model) - .options(noload(self.model.rules), noload(self.model.roles)) - .order_by(desc(self.model.created_time)) - ) + filters = {} - filters = [] if name is not None: - filters.append(self.model.name.like(f'%{name}%')) + filters['name__like'] = f'%{name}%' if status is not None: - filters.append(self.model.status == status) + filters['status'] = status - if filters: - stmt = stmt.where(and_(*filters)) - - return stmt + return await self.select_order('id', load_strategies={'rules': 'noload', 'roles': 'noload'}, **filters) async def create(self, db: AsyncSession, obj: CreateDataScopeParam) -> None: """ diff --git a/backend/app/admin/crud/crud_dept.py b/backend/app/admin/crud/crud_dept.py index 5ca334c0..c719b977 100644 --- a/backend/app/admin/crud/crud_dept.py +++ b/backend/app/admin/crud/crud_dept.py @@ -3,9 +3,7 @@ from typing import Sequence from fastapi import Request -from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import selectinload from sqlalchemy_crud_plus import CRUDPlus from backend.app.admin.model import Dept @@ -56,16 +54,19 @@ class CRUDDept(CRUDPlus[Dept]): :param status: 部门状态 :return: """ - filters = {'del_flag__eq': 0} + filters = {'del_flag': 0} + if name is not None: - filters.update(name__like=f'%{name}%') + filters['name__like'] = f'%{name}%' if leader is not None: - filters.update(leader__like=f'%{leader}%') + filters['leader__like'] = f'%{leader}%' if phone is not None: - filters.update(phone__startswith=phone) + filters['phone__startswith'] = phone if status is not None: - filters.update(status=status) - return await self.select_models_order(db, 'sort', None, await filter_data_permission(db, request), **filters) + filters['status'] = status + + data_filtered = await filter_data_permission(db, request) + return await self.select_models_order(db, 'sort', 'desc', data_filtered, **filters) async def create(self, db: AsyncSession, obj: CreateDeptParam) -> None: """ @@ -106,9 +107,7 @@ class CRUDDept(CRUDPlus[Dept]): :param dept_id: 部门 ID :return: """ - stmt = select(self.model).options(selectinload(self.model.users)).where(self.model.id == dept_id) - result = await db.execute(stmt) - return result.scalars().first() + return await self.select_model(db, dept_id, load_strategies=['users']) async def get_children(self, db: AsyncSession, dept_id: int) -> Sequence[Dept | None]: """ @@ -118,9 +117,7 @@ class CRUDDept(CRUDPlus[Dept]): :param dept_id: 部门 ID :return: """ - stmt = select(self.model).where(self.model.parent_id == dept_id, self.model.del_flag == 0) - result = await db.execute(stmt) - return result.scalars().all() + return await self.select_models(db, parent_id=dept_id, del_flag=0) dept_dao: CRUDDept = CRUDDept(Dept) diff --git a/backend/app/admin/crud/crud_login_log.py b/backend/app/admin/crud/crud_login_log.py index b3e74e1e..6c726e16 100644 --- a/backend/app/admin/crud/crud_login_log.py +++ b/backend/app/admin/crud/crud_login_log.py @@ -21,12 +21,14 @@ class CRUDLoginLog(CRUDPlus[LoginLog]): :return: """ filters = {} + if username is not None: - filters.update(username__like=f'%{username}%') + filters['username__like'] = f'%{username}%' if status is not None: - filters.update(status=status) + filters['status'] = status if ip is not None: - filters.update(ip__like=f'%{ip}%') + filters['ip__like'] = f'%{ip}%' + return await self.select_order('created_time', 'desc', **filters) async def create(self, db: AsyncSession, obj: CreateLoginLogParam) -> None: diff --git a/backend/app/admin/crud/crud_menu.py b/backend/app/admin/crud/crud_menu.py index 647b3cc1..199fb8fd 100644 --- a/backend/app/admin/crud/crud_menu.py +++ b/backend/app/admin/crud/crud_menu.py @@ -2,9 +2,7 @@ # -*- coding: utf-8 -*- from typing import Sequence -from sqlalchemy import and_, asc, select from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import selectinload from sqlalchemy_crud_plus import CRUDPlus from backend.app.admin.model import Menu @@ -44,10 +42,12 @@ class CRUDMenu(CRUDPlus[Menu]): :return: """ filters = {} + if title is not None: - filters.update(title__like=f'%{title}%') + filters['title__like'] = f'%{title}%' if status is not None: - filters.update(status=status) + filters['status'] = status + return await self.select_models_order(db, 'sort', **filters) async def get_sidebar(self, db: AsyncSession, superuser: bool, menu_ids: list[int | None]) -> Sequence[Menu]: @@ -59,13 +59,12 @@ class CRUDMenu(CRUDPlus[Menu]): :param menu_ids: 菜单 ID 列表 :return: """ - stmt = select(self.model).order_by(asc(self.model.sort)) - filters = [self.model.type.in_([0, 1, 3, 4])] + filters = {'type__in': [0, 1, 3, 4]} + if not superuser: - filters.append(self.model.id.in_(menu_ids)) - stmt = stmt.where(and_(*filters)) - menu = await db.execute(stmt) - return menu.scalars().all() + filters['id__in'] = menu_ids + + return await self.select_models_order(db, 'sort', 'asc', **filters) async def create(self, db: AsyncSession, obj: CreateMenuParam) -> None: """ @@ -106,9 +105,7 @@ class CRUDMenu(CRUDPlus[Menu]): :param menu_id: 菜单 ID :return: """ - stmt = select(self.model).options(selectinload(self.model.children)).where(self.model.id == menu_id) - result = await db.execute(stmt) - menu = result.scalars().first() + menu = await self.select_model(db, menu_id, load_strategies=['children']) return menu.children diff --git a/backend/app/admin/crud/crud_opera_log.py b/backend/app/admin/crud/crud_opera_log.py index f07f26f7..c21a625a 100644 --- a/backend/app/admin/crud/crud_opera_log.py +++ b/backend/app/admin/crud/crud_opera_log.py @@ -21,12 +21,14 @@ class CRUDOperaLogDao(CRUDPlus[OperaLog]): :return: """ filters = {} + if username is not None: - filters.update(username__like=f'%{username}%') + filters['username__like'] = f'%{username}%' if status is not None: - filters.update(status=status) + filters['status__eq'] = status if ip is not None: - filters.update(ip__like=f'%{ip}%') + filters['ip__like'] = f'%{ip}%' + return await self.select_order('created_time', 'desc', **filters) async def create(self, db: AsyncSession, obj: CreateOperaLogParam) -> None: diff --git a/backend/app/admin/crud/crud_role.py b/backend/app/admin/crud/crud_role.py index 69e41dd3..7721a4a3 100644 --- a/backend/app/admin/crud/crud_role.py +++ b/backend/app/admin/crud/crud_role.py @@ -2,9 +2,8 @@ # -*- coding: utf-8 -*- from typing import Sequence -from sqlalchemy import Select, and_, desc, select +from sqlalchemy import Select, select from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import noload, selectinload from sqlalchemy_crud_plus import CRUDPlus from backend.app.admin.model import DataScope, Menu, Role @@ -37,13 +36,7 @@ class CRUDRole(CRUDPlus[Role]): :param role_id: 角色 ID :return: """ - stmt = ( - select(self.model) - .options(selectinload(self.model.menus), selectinload(self.model.scopes)) - .where(self.model.id == role_id) - ) - role = await db.execute(stmt) - return role.scalars().first() + return await self.select_model(db, role_id, load_strategies=['menus', 'scopes']) async def get_all(self, db: AsyncSession) -> Sequence[Role]: """ @@ -62,22 +55,23 @@ class CRUDRole(CRUDPlus[Role]): :param status: 角色状态 :return: """ - stmt = ( - select(self.model) - .options(noload(self.model.users), noload(self.model.menus), noload(self.model.scopes)) - .order_by(desc(self.model.created_time)) - ) - filters = [] + filters = {} + if name is not None: - filters.append(self.model.name.like(f'%{name}%')) + filters['name__like'] = f'%{name}%' if status is not None: - filters.append(self.model.status == status) + filters['status'] = status - if filters: - stmt = stmt.where(and_(*filters)) - - return stmt + return await self.select_order( + 'id', + load_strategies={ + 'users': 'noload', + 'menus': 'noload', + 'scopes': 'noload', + }, + **filters, + ) async def get_by_name(self, db: AsyncSession, name: str) -> Role | None: """ diff --git a/backend/app/admin/crud/crud_user.py b/backend/app/admin/crud/crud_user.py index 6f9f9b98..d91a43f1 100644 --- a/backend/app/admin/crud/crud_user.py +++ b/backend/app/admin/crud/crud_user.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import bcrypt -from sqlalchemy import and_, desc, select +from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import noload, selectinload from sqlalchemy.sql import Select @@ -171,30 +171,27 @@ class CRUDUser(CRUDPlus[User]): :param status: 用户状态 :return: """ - stmt = ( - select(self.model) - .options( + filters = {} + + if dept: + filters['dept_id'] = dept + if username: + filters['username__like'] = f'%{username}%' + if phone: + filters['phone_like'] = f'%{phone}%' + if status is not None: + filters['status'] = status + + return await self.select_order( + 'id', + 'desc', + load_options=[ selectinload(self.model.dept).options(noload(Dept.parent), noload(Dept.children), noload(Dept.users)), selectinload(self.model.roles).options(noload(Role.users), noload(Role.menus), noload(Role.scopes)), - ) - .order_by(desc(self.model.join_time)) + ], + **filters, ) - filters = [] - if dept: - filters.append(self.model.dept_id == dept) - if username: - filters.append(self.model.username.like(f'%{username}%')) - if phone: - filters.append(self.model.phone.like(f'%{phone}%')) - if status is not None: - filters.append(self.model.status == status) - - if filters: - stmt = stmt.where(and_(*filters)) - - return stmt - async def get_super(self, db: AsyncSession, user_id: int) -> bool: """ 获取用户是否为超级管理员 @@ -294,22 +291,19 @@ class CRUDUser(CRUDPlus[User]): :param username: 用户名 :return: """ - stmt = select(self.model).options( - selectinload(self.model.dept), - selectinload(self.model.roles).options(selectinload(Role.menus), selectinload(Role.scopes)), - ) + filters = {} - filters = [] if user_id: - filters.append(self.model.id == user_id) + filters['id'] = user_id if username: - filters.append(self.model.username == username) + filters['username'] = username - if filters: - stmt = stmt.where(and_(*filters)) - - user = await db.execute(stmt) - return user.scalars().first() + return await self.select_model_by_column( + db, + load_options=[selectinload(self.model.roles).options(selectinload(Role.menus), selectinload(Role.scopes))], + load_strategies=['dept'], + **filters, + ) user_dao: CRUDUser = CRUDUser(User) diff --git a/backend/plugin/config/crud/crud_config.py b/backend/plugin/config/crud/crud_config.py index 65600898..da096293 100644 --- a/backend/plugin/config/crud/crud_config.py +++ b/backend/plugin/config/crud/crud_config.py @@ -64,10 +64,12 @@ class CRUDConfig(CRUDPlus[Config]): :return: """ filters = {'type__not_in': settings.CONFIG_BUILT_IN_TYPES} + if name is not None: - filters.update(name__like=f'%{name}%') + filters['name__like'] = f'%{name}%' if type is not None: - filters.update(type__like=f'%{type}%') + filters['type__like'] = f'%{type}%' + return await self.select_order('created_time', 'desc', **filters) async def create(self, db: AsyncSession, obj: CreateConfigParam) -> None: diff --git a/backend/plugin/dict/crud/crud_dict_data.py b/backend/plugin/dict/crud/crud_dict_data.py index fbe69463..53307dbe 100644 --- a/backend/plugin/dict/crud/crud_dict_data.py +++ b/backend/plugin/dict/crud/crud_dict_data.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from sqlalchemy import Select, and_, desc, select +from sqlalchemy import Select from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import noload, selectinload from sqlalchemy_crud_plus import CRUDPlus from backend.plugin.dict.model import DictData @@ -31,20 +30,16 @@ class CRUDDictData(CRUDPlus[DictData]): :param status: 字典状态 :return: """ - stmt = select(self.model).options(noload(self.model.type)).order_by(desc(self.model.sort)) + filters = {} - filters = [] if label is not None: - filters.append(self.model.label.like(f'%{label}%')) + filters['label__like'] = f'%{label}%' if value is not None: - filters.append(self.model.value.like(f'%{value}%')) + filters['value__like'] = f'%{value}%' if status is not None: - filters.append(self.model.status == status) + filters['status'] = status - if filters: - stmt = stmt.where(and_(*filters)) - - return stmt + return await self.select_order('id', 'desc', *filters) async def get_by_label(self, db: AsyncSession, label: str) -> DictData | None: """ @@ -95,9 +90,7 @@ class CRUDDictData(CRUDPlus[DictData]): :param pk: 字典数据 ID :return: """ - stmt = select(self.model).options(selectinload(self.model.type)).where(self.model.id == pk) - dict_data = await db.execute(stmt) - return dict_data.scalars().first() + return await self.select_model(db, pk, load_strategies=['type']) dict_data_dao: CRUDDictData = CRUDDictData(DictData) diff --git a/backend/plugin/dict/crud/crud_dict_type.py b/backend/plugin/dict/crud/crud_dict_type.py index 6eaf0b35..96823d3f 100644 --- a/backend/plugin/dict/crud/crud_dict_type.py +++ b/backend/plugin/dict/crud/crud_dict_type.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from sqlalchemy import Select, and_, desc, select +from sqlalchemy import Select from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import noload from sqlalchemy_crud_plus import CRUDPlus from backend.plugin.dict.model import DictType @@ -31,20 +30,16 @@ class CRUDDictType(CRUDPlus[DictType]): :param status: 字典状态 :return: """ - stmt = select(self.model).options(noload(self.model.datas)).order_by(desc(self.model.created_time)) + filters = {} - filters = [] if name is not None: - filters.append(self.model.name.like(f'%{name}%')) + filters['name__like'] = f'%{name}%' if code is not None: - filters.append(self.model.code.like(f'%{code}%')) + filters['code__like'] = f'%{code}%' if status is not None: - filters.append(self.model.status == status) + filters['status'] = status - if filters: - stmt = stmt.where(and_(*filters)) - - return stmt + return await self.select_order('id', 'desc', **filters) async def get_by_code(self, db: AsyncSession, code: str) -> DictType | None: """ diff --git a/pyproject.toml b/pyproject.toml index 5b20bc47..68d9a73f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ dependencies = [ "python-socketio>=5.12.0", "redis[hiredis]>=5.2.0", "rtoml>=0.12.0", - "sqlalchemy-crud-plus>=1.8.0", + "sqlalchemy-crud-plus>=1.10.0", "sqlalchemy[asyncio]>=2.0.40", "user-agents==2.2.0", ] diff --git a/requirements.txt b/requirements.txt index 67737b83..d7c30458 100644 --- a/requirements.txt +++ b/requirements.txt @@ -252,7 +252,7 @@ sqlalchemy==2.0.40 # alembic # fastapi-best-architecture # sqlalchemy-crud-plus -sqlalchemy-crud-plus==1.8.0 +sqlalchemy-crud-plus==1.10.0 # via fastapi-best-architecture starlette==0.46.1 # via diff --git a/uv.lock b/uv.lock index 5c29cae5..aed80c89 100644 --- a/uv.lock +++ b/uv.lock @@ -674,7 +674,7 @@ requires-dist = [ { name = "redis", extras = ["hiredis"], specifier = ">=5.2.0" }, { name = "rtoml", specifier = ">=0.12.0" }, { name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0.40" }, - { name = "sqlalchemy-crud-plus", specifier = ">=1.8.0" }, + { name = "sqlalchemy-crud-plus", specifier = ">=1.10.0" }, { name = "user-agents", specifier = "==2.2.0" }, ] @@ -1891,15 +1891,15 @@ asyncio = [ [[package]] name = "sqlalchemy-crud-plus" -version = "1.8.0" -source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://mirrors.aliyun.com/pypi/packages/dc/56/57a19b9a55910f73c80e802c42e9374f83752e6bfb5ad46829cf99e07758/sqlalchemy_crud_plus-1.8.0.tar.gz", hash = "sha256:cda7fc71a07887ac6fbbc423c0e061dfb912063b4ae207eccba31e08dd87aabb" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/47/fa0059f2ba24a0e8a6ded068460e23063a0b62b93ce1f3aa23fb7ae002fd/sqlalchemy_crud_plus-1.10.0.tar.gz", hash = "sha256:ce81cb0aac7f1e5cd1d6be794712d8f2a93f127c0936f4c3a2c79f3039eb2f62", size = 52871 } wheels = [ - { url = "https://mirrors.aliyun.com/pypi/packages/f4/25/7bb0ecc055dee08e18682f67af26671c33974722f477dd74c1cc1400c6f8/sqlalchemy_crud_plus-1.8.0-py3-none-any.whl", hash = "sha256:15ac0c6ce83df3b89585f05df25a8b5e35baf846d3a20fd8305fb1ff58aef65b" }, + { url = "https://files.pythonhosted.org/packages/a2/72/a7553fb70a41559d74654d9111644e8326d5d420588b59f39de872a3da8f/sqlalchemy_crud_plus-1.10.0-py3-none-any.whl", hash = "sha256:44db51256b57aa00757e48ade1e8be5ce6b6bd334fdd6d1fed5a22e97c6b0a6b", size = 10517 }, ] [[package]]