feat: implement Tool model and related API for Tool management
This commit is contained in:
parent
9778fd2bb4
commit
4c23b9aded
@ -109,6 +109,8 @@ class PermissionConstants(Enum):
|
|||||||
RoleConstants.USER])
|
RoleConstants.USER])
|
||||||
USER_EDIT = Permission(group=Group.USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN])
|
USER_EDIT = Permission(group=Group.USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN])
|
||||||
USER_DELETE = Permission(group=Group.USER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN])
|
USER_DELETE = Permission(group=Group.USER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN])
|
||||||
|
TOOL_CREATE = Permission(group=Group.USER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN,
|
||||||
|
RoleConstants.USER])
|
||||||
|
|
||||||
def get_workspace_application_permission(self):
|
def get_workspace_application_permission(self):
|
||||||
return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate,
|
return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate,
|
||||||
|
|||||||
@ -39,6 +39,7 @@ INSTALLED_APPS = [
|
|||||||
'drf_spectacular',
|
'drf_spectacular',
|
||||||
'drf_spectacular_sidecar',
|
'drf_spectacular_sidecar',
|
||||||
'users.apps.UsersConfig',
|
'users.apps.UsersConfig',
|
||||||
|
'tools.apps.ToolConfig',
|
||||||
'common',
|
'common',
|
||||||
'system_manage'
|
'system_manage'
|
||||||
]
|
]
|
||||||
|
|||||||
@ -30,6 +30,7 @@ SpectacularRedocView.permission_classes = [permissions.AllowAny]
|
|||||||
SpectacularRedocView.authentication_classes = [AnonymousAuthentication]
|
SpectacularRedocView.authentication_classes = [AnonymousAuthentication]
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("api/", include("users.urls")),
|
path("api/", include("users.urls")),
|
||||||
|
path("api/", include("tools.urls"))
|
||||||
]
|
]
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
path('schema/', SpectacularAPIView.as_view(), name='schema'), # schema的配置文件的路由,下面两个ui也是根据这个配置文件来生成的
|
path('schema/', SpectacularAPIView.as_view(), name='schema'), # schema的配置文件的路由,下面两个ui也是根据这个配置文件来生成的
|
||||||
|
|||||||
0
apps/tools/__init__.py
Normal file
0
apps/tools/__init__.py
Normal file
3
apps/tools/admin.py
Normal file
3
apps/tools/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
1
apps/tools/api/__init__.py
Normal file
1
apps/tools/api/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# coding=utf-8
|
||||||
20
apps/tools/api/tool.py
Normal file
20
apps/tools/api/tool.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
from common.mixins.api_mixin import APIMixin
|
||||||
|
from common.result import ResultSerializer
|
||||||
|
from tools.serializers.tool import ToolModelSerializer, ToolCreateRequest
|
||||||
|
|
||||||
|
|
||||||
|
class ToolCreateResponse(ResultSerializer):
|
||||||
|
def get_data(self):
|
||||||
|
return ToolModelSerializer()
|
||||||
|
|
||||||
|
|
||||||
|
class ToolCreateAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return ToolCreateRequest
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return ToolCreateResponse
|
||||||
6
apps/tools/apps.py
Normal file
6
apps/tools/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ToolConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'tools'
|
||||||
41
apps/tools/migrations/0001_initial.py
Normal file
41
apps/tools/migrations/0001_initial.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Generated by Django 5.2 on 2025-04-17 06:03
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid_utils.compat
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Tool',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
|
('name', models.CharField(max_length=64, verbose_name='函数名称')),
|
||||||
|
('desc', models.CharField(max_length=128, verbose_name='描述')),
|
||||||
|
('code', models.CharField(max_length=102400, verbose_name='python代码')),
|
||||||
|
('input_field_list', models.JSONField(default=list, verbose_name='输入字段列表')),
|
||||||
|
('init_field_list', models.JSONField(default=list, verbose_name='启动字段列表')),
|
||||||
|
('icon', models.CharField(default='/ui/favicon.ico', max_length=256, verbose_name='函数库icon')),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('scope', models.CharField(choices=[('SHARED', '共享'), ('WORKSPACE', '工作空间可用')], default='WORKSPACE', max_length=20, verbose_name='可用范围')),
|
||||||
|
('tool_type', models.CharField(choices=[('INTERNAL', '内置'), ('PUBLIC', '公开')], default='PUBLIC', max_length=20, verbose_name='函数类型')),
|
||||||
|
('template_id', models.UUIDField(default=None, null=True, verbose_name='模版id')),
|
||||||
|
('module_id', models.CharField(default='root', max_length=64, null=True, verbose_name='模块id')),
|
||||||
|
('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')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'tool',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
apps/tools/migrations/__init__.py
Normal file
0
apps/tools/migrations/__init__.py
Normal file
1
apps/tools/models/__init__.py
Normal file
1
apps/tools/models/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .tool import *
|
||||||
38
apps/tools/models/tool.py
Normal file
38
apps/tools/models/tool.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import uuid_utils.compat as uuid
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class ToolScope(models.TextChoices):
|
||||||
|
SHARED = "SHARED", '共享'
|
||||||
|
WORKSPACE = "WORKSPACE", "工作空间可用"
|
||||||
|
|
||||||
|
|
||||||
|
class ToolType(models.TextChoices):
|
||||||
|
INTERNAL = "INTERNAL", '内置'
|
||||||
|
PUBLIC = "PUBLIC", "公开"
|
||||||
|
|
||||||
|
|
||||||
|
class Tool(models.Model):
|
||||||
|
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id")
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户id")
|
||||||
|
name = models.CharField(max_length=64, verbose_name="函数名称")
|
||||||
|
desc = models.CharField(max_length=128, verbose_name="描述")
|
||||||
|
code = models.CharField(max_length=102400, verbose_name="python代码")
|
||||||
|
input_field_list = models.JSONField(verbose_name="输入字段列表", default=list)
|
||||||
|
init_field_list = models.JSONField(verbose_name="启动字段列表", default=list)
|
||||||
|
icon = models.CharField(max_length=256, verbose_name="函数库icon", default="/ui/favicon.ico")
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
scope = models.CharField(max_length=20, verbose_name='可用范围', choices=ToolScope.choices,
|
||||||
|
default=ToolScope.WORKSPACE)
|
||||||
|
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.CharField(max_length=64, verbose_name="模块id", null=True, default='root')
|
||||||
|
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)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "tool"
|
||||||
1
apps/tools/serializers/__init__.py
Normal file
1
apps/tools/serializers/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# coding=utf-8
|
||||||
66
apps/tools/serializers/tool.py
Normal file
66
apps/tools/serializers/tool.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
|
||||||
|
import uuid_utils.compat as uuid
|
||||||
|
from django.core import validators
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from tools.models import Tool, ToolScope
|
||||||
|
|
||||||
|
|
||||||
|
class ToolModelSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Tool
|
||||||
|
fields = ['id', 'name', 'icon', 'desc', 'code', 'input_field_list', 'init_field_list', 'init_params',
|
||||||
|
'scope', 'is_active', 'user_id', 'template_id',
|
||||||
|
'create_time', 'update_time']
|
||||||
|
|
||||||
|
|
||||||
|
class ToolInputField(serializers.Serializer):
|
||||||
|
name = serializers.CharField(required=True, label=_('variable name'))
|
||||||
|
is_required = serializers.BooleanField(required=True, label=_('required'))
|
||||||
|
type = serializers.CharField(required=True, label=_('type'), validators=[
|
||||||
|
validators.RegexValidator(regex=re.compile("^string|int|dict|array|float$"),
|
||||||
|
message=_('fields only support string|int|dict|array|float'), code=500)
|
||||||
|
])
|
||||||
|
source = serializers.CharField(required=True, label=_('source'), validators=[
|
||||||
|
validators.RegexValidator(regex=re.compile("^custom|reference$"),
|
||||||
|
message=_('The field only supports custom|reference'), code=500)
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class ToolCreateRequest(serializers.Serializer):
|
||||||
|
name = serializers.CharField(required=True, label=_('tool name'))
|
||||||
|
|
||||||
|
desc = serializers.CharField(required=False, allow_null=True, allow_blank=True,
|
||||||
|
label=_('tool description'))
|
||||||
|
|
||||||
|
code = serializers.CharField(required=True, label=_('tool content'))
|
||||||
|
|
||||||
|
input_field_list = serializers.ListField(child=ToolInputField(), required=True, label=_('input field list'))
|
||||||
|
|
||||||
|
init_field_list = serializers.ListField(required=False, default=list, label=_('init field list'))
|
||||||
|
|
||||||
|
is_active = serializers.BooleanField(required=False, label=_('Is active'))
|
||||||
|
|
||||||
|
|
||||||
|
class ToolSerializer(serializers.Serializer):
|
||||||
|
class Create(serializers.Serializer):
|
||||||
|
user_id = serializers.UUIDField(required=True, label=_('user id'))
|
||||||
|
|
||||||
|
def insert(self, instance, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
ToolCreateRequest(data=instance).is_valid(raise_exception=True)
|
||||||
|
tool = Tool(id=uuid.uuid7(),
|
||||||
|
name=instance.get('name'),
|
||||||
|
desc=instance.get('desc'),
|
||||||
|
code=instance.get('code'),
|
||||||
|
user_id=self.data.get('user_id'),
|
||||||
|
input_field_list=instance.get('input_field_list'),
|
||||||
|
init_field_list=instance.get('init_field_list'),
|
||||||
|
scope=ToolScope.WORKSPACE,
|
||||||
|
is_active=False)
|
||||||
|
tool.save()
|
||||||
|
return ToolModelSerializer(tool).data
|
||||||
3
apps/tools/tests.py
Normal file
3
apps/tools/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
8
apps/tools/urls.py
Normal file
8
apps/tools/urls.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = "tool"
|
||||||
|
urlpatterns = [
|
||||||
|
path('workspace/<str:workspace_id>/tool/create', views.ToolCreateView.as_view()),
|
||||||
|
]
|
||||||
1
apps/tools/views/__init__.py
Normal file
1
apps/tools/views/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .tool import *
|
||||||
28
apps/tools/views/tool.py
Normal file
28
apps/tools/views/tool.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from drf_spectacular.utils import extend_schema
|
||||||
|
from rest_framework.request import Request
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
from common.auth import TokenAuth
|
||||||
|
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.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))
|
||||||
@ -14,6 +14,7 @@ django-db-connection-pool = "1.2.5"
|
|||||||
psycopg = {extras = ["binary"], version = "3.2.6"}
|
psycopg = {extras = ["binary"], version = "3.2.6"}
|
||||||
python-dotenv = "1.1.0"
|
python-dotenv = "1.1.0"
|
||||||
uuid-utils = "0.10.0"
|
uuid-utils = "0.10.0"
|
||||||
|
diskcache = "5.6.3"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user