From 345e588ef63891bf853a723e4db9334a3752e688 Mon Sep 17 00:00:00 2001 From: shaohuzhang1 <80892890+shaohuzhang1@users.noreply.github.com> Date: Wed, 28 May 2025 18:57:09 +0800 Subject: [PATCH] feat: application list (#3160) --- apps/application/api/application_api.py | 79 ++++++++++++++++- apps/application/serializers/application.py | 84 ++++++++++++++++++- apps/application/sql/list_application.sql | 31 +++++++ apps/application/sql/list_application_ee.sql | 39 +++++++++ apps/application/urls.py | 2 + apps/application/views/application.py | 38 ++++++++- apps/common/constants/permission_constants.py | 8 +- 7 files changed, 273 insertions(+), 8 deletions(-) create mode 100644 apps/application/sql/list_application.sql create mode 100644 apps/application/sql/list_application_ee.sql diff --git a/apps/application/api/application_api.py b/apps/application/api/application_api.py index b4d1f83a4..d0e1fdfa8 100644 --- a/apps/application/api/application_api.py +++ b/apps/application/api/application_api.py @@ -11,9 +11,10 @@ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from rest_framework import serializers -from application.serializers.application import ApplicationCreateSerializer +from application.serializers.application import ApplicationCreateSerializer, ApplicationListResponse, \ + ApplicationQueryRequest from common.mixins.api_mixin import APIMixin -from common.result import ResultSerializer +from common.result import ResultSerializer, ResultPageSerializer class ApplicationCreateRequest(ApplicationCreateSerializer.SimplateRequest): @@ -25,6 +26,80 @@ class ApplicationCreateResponse(ResultSerializer): return ApplicationCreateSerializer.ApplicationResponse() +class ApplicationListResult(ResultSerializer): + def get_data(self): + return ApplicationListResponse(many=True) + + +class ApplicationPageResult(ResultPageSerializer): + def get_data(self): + return ApplicationListResponse(many=True) + + +class ApplicationQueryAPI(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name="workspace_id", + description="工作空间id", + type=OpenApiTypes.STR, + location='path', + required=True, + ), + OpenApiParameter( + name="current_page", + description=_("Current page"), + type=OpenApiTypes.INT, + location='path', + required=True, + ), + OpenApiParameter( + name="page_size", + description=_("Page size"), + type=OpenApiTypes.INT, + location='path', + required=True, + ), + OpenApiParameter( + name="folder_id", + description=_("folder id"), + type=OpenApiTypes.STR, + location='query', + required=False, + ), + OpenApiParameter( + name="name", + description=_("Application Name"), + type=OpenApiTypes.STR, + location='query', + required=False, + ), + OpenApiParameter( + name="desc", + description=_("Application Description"), + type=OpenApiTypes.STR, + location='query', + required=False, + ), + OpenApiParameter( + name="user_id", + description=_("User ID"), + type=OpenApiTypes.STR, + location='query', + required=False, + ) + ] + + @staticmethod + def get_response(): + return ApplicationListResult + + @staticmethod + def get_page_response(): + return ApplicationPageResult + + class ApplicationCreateAPI(APIMixin): @staticmethod def get_parameters(): diff --git a/apps/application/serializers/application.py b/apps/application/serializers/application.py index abb5a24f9..fe979b83d 100644 --- a/apps/application/serializers/application.py +++ b/apps/application/serializers/application.py @@ -7,6 +7,7 @@ @desc: """ import hashlib +import os import re from typing import Dict @@ -17,10 +18,15 @@ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from application.models.application import Application, ApplicationTypeChoices, ApplicationKnowledgeMapping +from application.models.application import Application, ApplicationTypeChoices, ApplicationKnowledgeMapping, \ + ApplicationFolder from application.models.application_access_token import ApplicationAccessToken +from common.database_model_manage.database_model_manage import DatabaseModelManage +from common.db.search import native_search, native_page_search from common.exception.app_exception import AppApiException +from common.utils.common import get_file_content from knowledge.models import Knowledge +from maxkb.conf import PROJECT_DIR from models_provider.models import Model @@ -227,11 +233,85 @@ class ApplicationCreateSerializer(serializers.Serializer): ) +class ApplicationQueryRequest(serializers.Serializer): + folder_id = serializers.CharField(required=False, label=_("folder id")) + name = serializers.CharField(required=False, label=_('Application Name')) + desc = serializers.CharField(required=False, label=_("Application Description")) + user_id = serializers.UUIDField(required=False, label=_("User ID")) + + +class ApplicationListResponse(serializers.Serializer): + id = serializers.CharField(required=True, label=_("Primary key id"), help_text=_("Primary key id")) + name = serializers.CharField(required=True, label=_("Application Name"), help_text=_("Application Name")) + desc = serializers.CharField(required=True, label=_("Application Description"), + help_text=_("Application Description")) + is_publish = serializers.BooleanField(required=True, label=_("Model id"), help_text=_("Model id")) + type = serializers.CharField(required=True, label=_("Application type"), help_text=_("Application type")) + resource_type = serializers.CharField(required=True, label=_("Resource type"), help_text=_("Resource type")) + user_id = serializers.CharField(required=True, label=_('Affiliation user'), help_text=_("Affiliation user")) + create_time = serializers.CharField(required=True, label=_('Creation time'), help_text=_("Creation time")) + update_time = serializers.CharField(required=True, label=_('Modification time'), help_text=_("Modification time")) + + +class Query(serializers.Serializer): + workspace_id = serializers.CharField(required=False, label=_('workspace id')) + + def get_query_set(self, instance: Dict): + folder_query_set = QuerySet(ApplicationFolder) + application_query_set = QuerySet(Application) + workspace_id = self.data.get('workspace_id') + user_id = instance.get('user_id') + desc = instance.get('desc') + name = instance.get('name') + if workspace_id is not None: + folder_query_set = folder_query_set.filter(workspace_id=workspace_id) + application_query_set = application_query_set.filter(workspace_id=workspace_id) + if user_id is not None: + folder_query_set = folder_query_set.filter(user_id=user_id) + application_query_set = application_query_set.filter(user_id=user_id) + folder_id = instance.get('folder_id') + if folder_id is not None: + folder_query_set = folder_query_set.filter(parent=folder_id) + application_query_set = application_query_set.filter(folder_id=folder_id) + if name is not None: + folder_query_set = folder_query_set.filter(name__contains=name) + application_query_set = application_query_set.filter(name__contains=name) + if desc is not None: + folder_query_set = folder_query_set.filter(desc__contains=desc) + application_query_set = application_query_set.filter(desc__contains=desc) + application_query_set = application_query_set.order_by("-update_time") + return { + 'folder_query_set': folder_query_set, + 'application_query_set': application_query_set + } + + @staticmethod + def is_x_pack_ee(): + workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") + role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model") + return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None + + def list(self, instance: Dict): + self.is_valid(raise_exception=True) + ApplicationQueryRequest(data=instance).is_valid(raise_exception=True) + return native_search(self.get_query_set(instance), select_string=get_file_content( + os.path.join(PROJECT_DIR, "apps", "application", 'sql', + 'list_application_ee.sql' if self.is_x_pack_ee() else 'list_application.sql'))) + + def page(self, current_page: int, page_size: int, instance: Dict): + self.is_valid(raise_exception=True) + ApplicationQueryRequest(data=instance).is_valid(raise_exception=True) + return native_page_search(current_page, page_size, self.get_query_set(instance), get_file_content( + os.path.join(PROJECT_DIR, "apps", "application", 'sql', + 'list_application_ee.sql' if self.is_x_pack_ee() else 'list_application.sql')), + ) + + class ApplicationSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) user_id = serializers.UUIDField(required=True, label=_("User ID")) - def insert(self, instance: Dict, with_valid=True): + def insert(self, instance: Dict): application_type = instance.get('type') if 'WORK_FLOW' == application_type: return self.insert_workflow(instance) diff --git a/apps/application/sql/list_application.sql b/apps/application/sql/list_application.sql new file mode 100644 index 000000000..edc889120 --- /dev/null +++ b/apps/application/sql/list_application.sql @@ -0,0 +1,31 @@ +select * +from (select "id"::text, + "name", + "desc", + "is_publish", + "type", + 'application' as "resource_type", + "workspace_id", + "folder_id", + "user_id", + "create_time", + "update_time" + from application + where id in (select target + from workspace_user_resource_permission + where auth_target_type = 'APPLICATION' + and 'VIEW' = any (permission_list)) + UNION + select "id", + "name", + "desc", + true as "is_publish", + 'folder' as "type", + 'folder' as "resource_type", + "workspace_id", + "parent_id" as "folder_id", + "user_id", + "create_time", + "update_time" + from application_folder ${folder_query_set}) temp +${application_query_set} \ No newline at end of file diff --git a/apps/application/sql/list_application_ee.sql b/apps/application/sql/list_application_ee.sql new file mode 100644 index 000000000..234338bca --- /dev/null +++ b/apps/application/sql/list_application_ee.sql @@ -0,0 +1,39 @@ +select * +from (select "id"::text, + "name", + "desc", + "is_publish", + "type", + 'application' as "resource_type", + "workspace_id", + "folder_id", + "user_id", + "create_time", + "update_time" + from application + where id in (select target + from workspace_user_resource_permission + where auth_target_type = 'APPLICATION' + and case + when auth_type = 'ROLE' then + 'APPLICATION_READ' in (select permission_id + from role_permission + where role_id in (select role_id + from user_role_relation)) + else + 'VIEW' = any (permission_list) + end) + UNION + select "id", + "name", + "desc", + true as "is_publish", + 'folder' as "type", + 'folder' as "resource_type", + "workspace_id", + "parent_id" as "folder_id", + "user_id", + "create_time", + "update_time" + from application_folder ${folder_query_set}) temp +${application_query_set} \ No newline at end of file diff --git a/apps/application/urls.py b/apps/application/urls.py index a7b0c8ef2..dfe44cf08 100644 --- a/apps/application/urls.py +++ b/apps/application/urls.py @@ -6,5 +6,7 @@ app_name = 'application' urlpatterns = [ path('workspace//application', views.Application.as_view(), name='application'), + path('workspace//application//', + views.Application.Page.as_view(), name='application_page'), path('workspace//application//application_key', views.ApplicationKey.as_view())] diff --git a/apps/application/views/application.py b/apps/application/views/application.py index a381d6b52..9ab96aa93 100644 --- a/apps/application/views/application.py +++ b/apps/application/views/application.py @@ -11,10 +11,12 @@ from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.views import APIView -from application.api.application_api import ApplicationCreateAPI -from application.serializers.application import ApplicationSerializer +from application.api.application_api import ApplicationCreateAPI, ApplicationQueryAPI +from application.serializers.application import ApplicationSerializer, Query from common import result from common.auth import TokenAuth +from common.auth.authentication import has_permissions +from common.constants.permission_constants import PermissionConstants class Application(APIView): @@ -30,6 +32,38 @@ class Application(APIView): responses=ApplicationCreateAPI.get_response(), tags=[_('Application')] # type: ignore ) + @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_permission()) def post(self, request: Request, workspace_id: str): return result.success( ApplicationSerializer(data={'workspace_id': workspace_id, 'user_id': request.user.id}).insert(request.data)) + + @extend_schema( + methods=['GET'], + description=_('Get the application list'), + summary=_('Get the application list'), + operation_id=_('Get the application list'), # type: ignore + parameters=ApplicationQueryAPI.get_parameters(), + responses=ApplicationQueryAPI.get_response(), + tags=[_('Application')] # type: ignore + ) + @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_permission()) + def get(self, request: Request, workspace_id: str): + return result.success(Query(data={'workspace_id': workspace_id, 'user_id': request.user.id}).list(request.data)) + + class Page(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['GET'], + description=_('Get the application list by page'), + summary=_('Get the application list by page'), + operation_id=_('Get the application list by page'), # type: ignore + parameters=ApplicationQueryAPI.get_parameters(), + responses=ApplicationQueryAPI.get_page_response(), + tags=[_('Application')] # type: ignore + ) + @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_permission()) + def get(self, request: Request, workspace_id: str, current_page: int, page_size: int): + return result.success( + Query(data={'workspace_id': workspace_id, 'user_id': request.user.id}).page(current_page, page_size, + request.query_params)) diff --git a/apps/common/constants/permission_constants.py b/apps/common/constants/permission_constants.py index b6fc3e8c7..01dcc97d3 100644 --- a/apps/common/constants/permission_constants.py +++ b/apps/common/constants/permission_constants.py @@ -58,6 +58,7 @@ class SystemGroup(Enum): SYSTEM_SETTING = "SYSTEM_SETTING" OPERATION_LOG = "OPERATION_LOG" OTHER = "OTHER" + APPLICATION = "APPLICATION" class WorkspaceGroup(Enum): @@ -514,8 +515,11 @@ class PermissionConstants(Enum): group=Group.LOGIN_AUTH, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SYSTEM_SETTING] ) - - + APPLICATION_READ = Permission(group=Group.APPLICATION, operate=Operate.READ, + role_list=[RoleConstants.ADMIN, RoleConstants.USER], + parent_group=[SystemGroup.APPLICATION], + resource_permission_group_list=[ResourcePermissionGroup.VIEW], + ) def get_workspace_application_permission(self): return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate,