diff --git a/apps/common/constants/permission_constants.py b/apps/common/constants/permission_constants.py index ab6a54a4456..d67240dc272 100644 --- a/apps/common/constants/permission_constants.py +++ b/apps/common/constants/permission_constants.py @@ -133,6 +133,12 @@ class PermissionConstants(Enum): TOOL_CREATE = Permission(group=Group.TOOL, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER]) + TOOL_EDIT = Permission(group=Group.TOOL, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, + RoleConstants.USER]) + TOOL_READ = Permission(group=Group.TOOL, operate=Operate.READ, role_list=[RoleConstants.ADMIN, + RoleConstants.USER]) + TOOL_DELETE = Permission(group=Group.TOOL, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, + RoleConstants.USER]) def get_workspace_application_permission(self): return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate, diff --git a/apps/tools/api/tool.py b/apps/tools/api/tool.py index 39c3e23a2d1..a82e79ac848 100644 --- a/apps/tools/api/tool.py +++ b/apps/tools/api/tool.py @@ -1,4 +1,6 @@ # coding=utf-8 +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer @@ -11,6 +13,18 @@ def get_data(self): class ToolCreateAPI(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name="workspace_id", + description="工作空间id", + type=OpenApiTypes.STR, + location='path', + required=True, + ) + ] + @staticmethod def get_request(): return ToolCreateRequest @@ -18,3 +32,39 @@ def get_request(): @staticmethod def get_response(): return ToolCreateResponse + + +class ToolReadAPI(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name="workspace_id", + description="工作空间id", + type=OpenApiTypes.STR, + location='path', + required=True, + ), + OpenApiParameter( + name="tool_id", + description="工具id", + type=OpenApiTypes.STR, + location='path', + required=True, + ) + ] + + @staticmethod + def get_response(): + return ToolCreateResponse + + +class ToolEditAPI(ToolReadAPI): + + @staticmethod + def get_request(): + return ToolCreateRequest + + +class ToolDeleteAPI(ToolReadAPI): + pass diff --git a/apps/tools/migrations/0001_initial.py b/apps/tools/migrations/0001_initial.py index 84874d3097d..83dce27c9b8 100644 --- a/apps/tools/migrations/0001_initial.py +++ b/apps/tools/migrations/0001_initial.py @@ -66,13 +66,13 @@ class Migration(migrations.Migration): models.CharField(choices=[('INTERNAL', '内置'), ('PUBLIC', '公开')], default='PUBLIC', max_length=20, verbose_name='函数类型')), ('template_id', models.UUIDField(default=None, null=True, verbose_name='模版id')), - ('workspace_id', models.CharField(default='default', max_length=64, verbose_name='工作空间id')), + ('workspace_id', models.CharField(default='default', max_length=64, verbose_name='工作空间id', db_index=True)), ('init_params', models.CharField(max_length=102400, null=True, verbose_name='初始化参数')), ('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, null=True, verbose_name='修改时间')), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='用户id')), - ('module_id', + ('module', models.ForeignKey(default='root', on_delete=django.db.models.deletion.CASCADE, to='tools.toolmodule', verbose_name='模块id')), ], diff --git a/apps/tools/models/tool.py b/apps/tools/models/tool.py index 045b4d5fd44..89869229741 100644 --- a/apps/tools/models/tool.py +++ b/apps/tools/models/tool.py @@ -30,8 +30,8 @@ class Tool(models.Model): tool_type = models.CharField(max_length=20, verbose_name='函数类型', choices=ToolType.choices, default=ToolType.PUBLIC) template_id = models.UUIDField(max_length=128, verbose_name="模版id", null=True, default=None) - module_id = models.ForeignKey(ToolModule, on_delete=models.CASCADE, verbose_name="模块id", default='root') - workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default") + module = models.ForeignKey(ToolModule, on_delete=models.CASCADE, verbose_name="模块id", default='root') + workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) init_params = models.CharField(max_length=102400, verbose_name="初始化参数", null=True) create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True, null=True) update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True, null=True) diff --git a/apps/tools/serializers/tool.py b/apps/tools/serializers/tool.py index d7f4c3ddb7c..4960ac74e8f 100644 --- a/apps/tools/serializers/tool.py +++ b/apps/tools/serializers/tool.py @@ -3,6 +3,7 @@ import uuid_utils.compat as uuid from django.core import validators +from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers @@ -30,6 +31,17 @@ class ToolInputField(serializers.Serializer): ]) +class InitField(serializers.Serializer): + field = serializers.CharField(required=True, label=_('field name')) + label = serializers.CharField(required=True, label=_('field label')) + required = serializers.BooleanField(required=True, label=_('required')) + input_type = serializers.CharField(required=True, label=_('input type')) + default_value = serializers.CharField(required=False, allow_null=True, allow_blank=True) + show_default_value = serializers.BooleanField(required=False, default=False) + props_info = serializers.DictField(required=False, default=dict) + attrs = serializers.DictField(required=False, default=dict) + + class ToolCreateRequest(serializers.Serializer): name = serializers.CharField(required=True, label=_('tool name')) @@ -38,9 +50,10 @@ class ToolCreateRequest(serializers.Serializer): code = serializers.CharField(required=True, label=_('tool content')) - input_field_list = serializers.ListField(child=ToolInputField(), required=False, label=_('input field list')) + input_field_list = serializers.ListField(child=ToolInputField(), required=False, default=list, + label=_('input field list')) - init_field_list = serializers.ListField(required=False, default=list, label=_('init field list')) + init_field_list = serializers.ListField(child=InitField(), required=False, default=list, label=_('init field list')) is_active = serializers.BooleanField(required=False, label=_('Is active')) @@ -50,6 +63,7 @@ class ToolCreateRequest(serializers.Serializer): class ToolSerializer(serializers.Serializer): class Create(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) + workspace_id = serializers.UUIDField(required=True, label=_('workspace id')) def insert(self, instance, with_valid=True): if with_valid: @@ -66,3 +80,32 @@ def insert(self, instance, with_valid=True): is_active=False) tool.save() return ToolModelSerializer(tool).data + + class Operate(serializers.Serializer): + id = serializers.UUIDField(required=True, label=_('tool id')) + workspace_id = serializers.CharField(required=True, label=_('workspace id')) + + def edit(self, instance, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + ToolCreateRequest(data=instance).is_valid(raise_exception=True) + if not QuerySet(Tool).filter(id=self.data.get('id')).exists(): + raise serializers.ValidationError(_('Tool not found')) + + edit_field_list = ['name', 'desc', 'code', 'icon', 'input_field_list', 'init_field_list', 'init_params', + 'is_active'] + edit_dict = {field: instance.get(field) for field in edit_field_list if ( + field in instance and instance.get(field) is not None)} + + QuerySet(Tool).filter(id=self.data.get('id')).update(**edit_dict) + + return self.one() + + def delete(self): + self.is_valid(raise_exception=True) + QuerySet(Tool).filter(id=self.data.get('id')).delete() + + def one(self): + self.is_valid(raise_exception=True) + tool = QuerySet(Tool).filter(id=self.data.get('id')).first() + return ToolModelSerializer(tool).data diff --git a/apps/tools/urls.py b/apps/tools/urls.py index caea5a0ffa2..8f05a5e7a71 100644 --- a/apps/tools/urls.py +++ b/apps/tools/urls.py @@ -4,5 +4,6 @@ app_name = "tool" urlpatterns = [ - path('workspace//tool/create', views.ToolCreateView.as_view()), + path('workspace//tool', views.ToolView.Create.as_view()), + path('workspace//tool/', views.ToolView.Operate.as_view()), ] diff --git a/apps/tools/views/tool.py b/apps/tools/views/tool.py index 397d7713514..f29d520d13b 100644 --- a/apps/tools/views/tool.py +++ b/apps/tools/views/tool.py @@ -7,22 +7,62 @@ from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants from common.result import result -from tools.api.tool import ToolCreateAPI +from tools.api.tool import ToolCreateAPI, ToolEditAPI, ToolReadAPI, ToolDeleteAPI from tools.serializers.tool import ToolSerializer -class ToolCreateView(APIView): - authentication_classes = [TokenAuth] - - @extend_schema(methods=['POST'], - description=_('Create tool'), - operation_id=_('Create tool'), - request=ToolCreateAPI.get_request(), - responses=ToolCreateAPI.get_response(), - tags=[_('Tool')]) - @has_permissions(PermissionConstants.TOOL_CREATE) - # @log(menu='Tool', operate="Create tool", - # get_operation_object=lambda r, k: r.data.get('name')) - def post(self, request: Request, workspace_id: str): - print(workspace_id) - return result.success(ToolSerializer.Create(data={'user_id': request.user.id}).insert(request.data)) +class ToolView(APIView): + class Create(APIView): + authentication_classes = [TokenAuth] + + @extend_schema(methods=['POST'], + description=_('Create tool'), + operation_id=_('Create tool'), + parameters=ToolCreateAPI.get_parameters(), + request=ToolCreateAPI.get_request(), + responses=ToolCreateAPI.get_response(), + tags=[_('Tool')]) + @has_permissions(PermissionConstants.TOOL_CREATE.get_workspace_permission()) + def post(self, request: Request, workspace_id: str): + return result.success(ToolSerializer.Create( + data={'user_id': request.user.id, 'workspace_id': workspace_id} + ).insert(request.data)) + + class Operate(APIView): + authentication_classes = [TokenAuth] + + @extend_schema(methods=['PUT'], + description=_('Update tool'), + operation_id=_('Update tool'), + parameters=ToolEditAPI.get_parameters(), + request=ToolEditAPI.get_request(), + responses=ToolEditAPI.get_response(), + tags=[_('Tool')]) + @has_permissions(PermissionConstants.TOOL_EDIT.get_workspace_permission()) + def put(self, request: Request, workspace_id: str, tool_id: str): + return result.success(ToolSerializer.Operate( + data={'id': tool_id, 'workspace_id': workspace_id} + ).edit(request.data)) + + @extend_schema(methods=['GET'], + description=_('Update tool'), + operation_id=_('Update tool'), + parameters=ToolReadAPI.get_parameters(), + responses=ToolReadAPI.get_response(), + tags=[_('Tool')]) + @has_permissions(PermissionConstants.TOOL_READ.get_workspace_permission()) + def get(self, request: Request, workspace_id: str, tool_id: str): + return result.success(ToolSerializer.Operate( + data={'id': tool_id, 'workspace_id': workspace_id} + ).one()) + + @extend_schema(methods=['DELETE'], + description=_('Delete tool'), + operation_id=_('Delete tool'), + parameters=ToolDeleteAPI.get_parameters(), + tags=[_('Tool')]) + @has_permissions(PermissionConstants.TOOL_DELETE.get_workspace_permission()) + def delete(self, request: Request, workspace_id: str, tool_id: str): + return result.success(ToolSerializer.Operate( + data={'id': tool_id, 'workspace_id': workspace_id} + ).delete())