feat: Import and Export function lib
This commit is contained in:
parent
a1fca58864
commit
f45855c34b
@ -7,15 +7,21 @@
|
|||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
|
import pickle
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
|
from django.db import transaction
|
||||||
from django.db.models import QuerySet, Q
|
from django.db.models import QuerySet, Q
|
||||||
from rest_framework import serializers
|
from django.http import HttpResponse
|
||||||
|
from rest_framework import serializers, status
|
||||||
|
|
||||||
from common.db.search import page_search
|
from common.db.search import page_search
|
||||||
from common.exception.app_exception import AppApiException
|
from common.exception.app_exception import AppApiException
|
||||||
|
from common.field.common import UploadedFileField
|
||||||
|
from common.response import result
|
||||||
from common.util.field_message import ErrMessage
|
from common.util.field_message import ErrMessage
|
||||||
from common.util.function_code import FunctionExecutor
|
from common.util.function_code import FunctionExecutor
|
||||||
from function_lib.models.function import FunctionLib
|
from function_lib.models.function import FunctionLib
|
||||||
@ -24,6 +30,11 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
function_executor = FunctionExecutor(CONFIG.get('SANDBOX'))
|
function_executor = FunctionExecutor(CONFIG.get('SANDBOX'))
|
||||||
|
|
||||||
|
class FlibInstance:
|
||||||
|
def __init__(self, function_lib: dict, version: str):
|
||||||
|
self.function_lib = function_lib
|
||||||
|
self.version = version
|
||||||
|
|
||||||
|
|
||||||
class FunctionLibModelSerializer(serializers.ModelSerializer):
|
class FunctionLibModelSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -227,3 +238,43 @@ class FunctionLibSerializer(serializers.Serializer):
|
|||||||
raise AppApiException(500, _('Function does not exist'))
|
raise AppApiException(500, _('Function does not exist'))
|
||||||
function_lib = QuerySet(FunctionLib).filter(id=self.data.get('id')).first()
|
function_lib = QuerySet(FunctionLib).filter(id=self.data.get('id')).first()
|
||||||
return FunctionLibModelSerializer(function_lib).data
|
return FunctionLibModelSerializer(function_lib).data
|
||||||
|
|
||||||
|
def export(self, with_valid=True):
|
||||||
|
try:
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid()
|
||||||
|
id = self.data.get('id')
|
||||||
|
function_lib = QuerySet(FunctionLib).filter(id=id).first()
|
||||||
|
application_dict = FunctionLibModelSerializer(function_lib).data
|
||||||
|
mk_instance = FlibInstance(application_dict, 'v1')
|
||||||
|
application_pickle = pickle.dumps(mk_instance)
|
||||||
|
response = HttpResponse(content_type='text/plain', content=application_pickle)
|
||||||
|
response['Content-Disposition'] = f'attachment; filename="{function_lib.name}.flib"'
|
||||||
|
return response
|
||||||
|
except Exception as e:
|
||||||
|
return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
|
class Import(serializers.Serializer):
|
||||||
|
file = UploadedFileField(required=True, error_messages=ErrMessage.image(_("file")))
|
||||||
|
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID")))
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def import_(self, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid()
|
||||||
|
user_id = self.data.get('user_id')
|
||||||
|
flib_instance_bytes = self.data.get('file').read()
|
||||||
|
try:
|
||||||
|
flib_instance = pickle.loads(flib_instance_bytes)
|
||||||
|
except Exception as e:
|
||||||
|
raise AppApiException(1001, _("Unsupported file format"))
|
||||||
|
function_lib = flib_instance.function_lib
|
||||||
|
function_lib_model = FunctionLib(id=uuid.uuid1(), name=function_lib.get('name'),
|
||||||
|
desc=function_lib.get('desc'),
|
||||||
|
code=function_lib.get('code'),
|
||||||
|
user_id=user_id,
|
||||||
|
input_field_list=function_lib.get('input_field_list'),
|
||||||
|
permission_type=function_lib.get('permission_type'),
|
||||||
|
is_active=function_lib.get('is_active'))
|
||||||
|
function_lib_model.save()
|
||||||
|
return True
|
||||||
@ -194,3 +194,24 @@ class FunctionLibApi(ApiMixin):
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Export(ApiMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request_params_api():
|
||||||
|
return [openapi.Parameter(name='id',
|
||||||
|
in_=openapi.IN_PATH,
|
||||||
|
type=openapi.TYPE_STRING,
|
||||||
|
required=True,
|
||||||
|
description=_('ID')),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
class Import(ApiMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request_params_api():
|
||||||
|
return [openapi.Parameter(name='file',
|
||||||
|
in_=openapi.IN_FORM,
|
||||||
|
type=openapi.TYPE_FILE,
|
||||||
|
required=True,
|
||||||
|
description=_('Upload image files'))
|
||||||
|
]
|
||||||
@ -6,6 +6,8 @@ app_name = "function_lib"
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('function_lib', views.FunctionLibView.as_view()),
|
path('function_lib', views.FunctionLibView.as_view()),
|
||||||
path('function_lib/debug', views.FunctionLibView.Debug.as_view()),
|
path('function_lib/debug', views.FunctionLibView.Debug.as_view()),
|
||||||
|
path('function_lib/<str:id>/export', views.FunctionLibView.Export.as_view()),
|
||||||
|
path('function_lib/import', views.FunctionLibView.Import.as_view()),
|
||||||
path('function_lib/pylint', views.PyLintView.as_view()),
|
path('function_lib/pylint', views.PyLintView.as_view()),
|
||||||
path('function_lib/<str:function_lib_id>', views.FunctionLibView.Operate.as_view()),
|
path('function_lib/<str:function_lib_id>', views.FunctionLibView.Operate.as_view()),
|
||||||
path("function_lib/<int:current_page>/<int:page_size>", views.FunctionLibView.Page.as_view(),
|
path("function_lib/<int:current_page>/<int:page_size>", views.FunctionLibView.Page.as_view(),
|
||||||
|
|||||||
@ -8,11 +8,12 @@
|
|||||||
"""
|
"""
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.parsers import MultiPartParser
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from common.auth import TokenAuth, has_permissions
|
from common.auth import TokenAuth, has_permissions
|
||||||
from common.constants.permission_constants import RoleConstants
|
from common.constants.permission_constants import RoleConstants, Permission, Group, Operate
|
||||||
from common.response import result
|
from common.response import result
|
||||||
from function_lib.serializers.function_lib_serializer import FunctionLibSerializer
|
from function_lib.serializers.function_lib_serializer import FunctionLibSerializer
|
||||||
from function_lib.swagger_api.function_lib_api import FunctionLibApi
|
from function_lib.swagger_api.function_lib_api import FunctionLibApi
|
||||||
@ -109,3 +110,30 @@ class FunctionLibView(APIView):
|
|||||||
'user_id': request.user.id,
|
'user_id': request.user.id,
|
||||||
'select_user_id': request.query_params.get('select_user_id')}).page(
|
'select_user_id': request.query_params.get('select_user_id')}).page(
|
||||||
current_page, page_size))
|
current_page, page_size))
|
||||||
|
|
||||||
|
class Import(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
parser_classes = [MultiPartParser]
|
||||||
|
|
||||||
|
@action(methods="POST", detail=False)
|
||||||
|
@swagger_auto_schema(operation_summary=_("Import function"), operation_id=_("Import function"),
|
||||||
|
manual_parameters=FunctionLibApi.Import.get_request_params_api(),
|
||||||
|
tags=[_("function")]
|
||||||
|
)
|
||||||
|
@has_permissions(RoleConstants.ADMIN, RoleConstants.USER)
|
||||||
|
def post(self, request: Request):
|
||||||
|
return result.success(FunctionLibSerializer.Import(
|
||||||
|
data={'user_id': request.user.id, 'file': request.FILES.get('file')}).import_())
|
||||||
|
|
||||||
|
class Export(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
@action(methods="GET", detail=False)
|
||||||
|
@swagger_auto_schema(operation_summary=_("Export function"), operation_id=_("Export function"),
|
||||||
|
manual_parameters=FunctionLibApi.Export.get_request_params_api(),
|
||||||
|
tags=[_("function")]
|
||||||
|
)
|
||||||
|
@has_permissions(RoleConstants.ADMIN, RoleConstants.USER)
|
||||||
|
def get(self, request: Request, id: str):
|
||||||
|
return FunctionLibSerializer.Operate(
|
||||||
|
data={'id': id, 'user_id': request.user.id}).export()
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { Result } from '@/request/Result'
|
import { Result } from '@/request/Result'
|
||||||
import { get, post, del, put } from '@/request/index'
|
import { get, post, del, put, exportFile } from '@/request/index'
|
||||||
import type { pageRequest } from '@/api/type/common'
|
import type { pageRequest } from '@/api/type/common'
|
||||||
import type { functionLibData } from '@/api/type/function-lib'
|
import type { functionLibData } from '@/api/type/function-lib'
|
||||||
import { type Ref } from 'vue'
|
import { type Ref } from 'vue'
|
||||||
@ -99,6 +99,25 @@ const pylint: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
|||||||
return post(`${prefix}/pylint`, { code }, {}, loading)
|
return post(`${prefix}/pylint`, { code }, {}, loading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const exportFunctionLib = (
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
loading?: Ref<boolean>
|
||||||
|
) => {
|
||||||
|
return exportFile(
|
||||||
|
name + '.flib',
|
||||||
|
`${prefix}/${id}/export`,
|
||||||
|
undefined,
|
||||||
|
loading
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const importFunctionLib: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||||
|
data,
|
||||||
|
loading
|
||||||
|
) => {
|
||||||
|
return post(`${prefix}/import`, data, undefined, loading)
|
||||||
|
}
|
||||||
export default {
|
export default {
|
||||||
getFunctionLib,
|
getFunctionLib,
|
||||||
postFunctionLib,
|
postFunctionLib,
|
||||||
@ -107,5 +126,7 @@ export default {
|
|||||||
getAllFunctionLib,
|
getAllFunctionLib,
|
||||||
delFunctionLib,
|
delFunctionLib,
|
||||||
getFunctionLibById,
|
getFunctionLibById,
|
||||||
|
exportFunctionLib,
|
||||||
|
importFunctionLib,
|
||||||
pylint
|
pylint
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ export default {
|
|||||||
createFunction: 'Create Function',
|
createFunction: 'Create Function',
|
||||||
editFunction: 'Edit Function',
|
editFunction: 'Edit Function',
|
||||||
copyFunction: 'Copy Function',
|
copyFunction: 'Copy Function',
|
||||||
|
importFunction: 'Import Function',
|
||||||
searchBar: {
|
searchBar: {
|
||||||
placeholder: 'Search by function name'
|
placeholder: 'Search by function name'
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,6 +3,7 @@ export default {
|
|||||||
createFunction: '创建函数',
|
createFunction: '创建函数',
|
||||||
editFunction: '编辑函数',
|
editFunction: '编辑函数',
|
||||||
copyFunction: '复制函数',
|
copyFunction: '复制函数',
|
||||||
|
importFunction: '导入函数',
|
||||||
searchBar: {
|
searchBar: {
|
||||||
placeholder: '按函数名称搜索'
|
placeholder: '按函数名称搜索'
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,6 +3,7 @@ export default {
|
|||||||
createFunction: '建立函數',
|
createFunction: '建立函數',
|
||||||
editFunction: '編輯函數',
|
editFunction: '編輯函數',
|
||||||
copyFunction: '複製函數',
|
copyFunction: '複製函數',
|
||||||
|
importFunction: '匯入函數',
|
||||||
searchBar: {
|
searchBar: {
|
||||||
placeholder: '按函數名稱搜尋'
|
placeholder: '按函數名稱搜尋'
|
||||||
},
|
},
|
||||||
|
|||||||
@ -42,7 +42,29 @@
|
|||||||
>
|
>
|
||||||
<el-row :gutter="15">
|
<el-row :gutter="15">
|
||||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-16">
|
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-16">
|
||||||
<CardAdd :title="$t('views.functionLib.createFunction')" @click="openCreateDialog()" />
|
<el-card shadow="hover" class="application-card-add" style="--el-card-padding: 8px">
|
||||||
|
<div class="card-add-button flex align-center cursor p-8" @click="openCreateDialog">
|
||||||
|
<AppIcon iconName="app-add-application" class="mr-8"></AppIcon>
|
||||||
|
{{ $t('views.functionLib.createFunction') }}
|
||||||
|
</div>
|
||||||
|
<el-divider style="margin: 8px 0" />
|
||||||
|
<el-upload
|
||||||
|
ref="elUploadRef"
|
||||||
|
:file-list="[]"
|
||||||
|
action="#"
|
||||||
|
multiple
|
||||||
|
:auto-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
:limit="1"
|
||||||
|
:on-change="(file: any, fileList: any) => importFunctionLib(file)"
|
||||||
|
class="card-add-button"
|
||||||
|
>
|
||||||
|
<div class="flex align-center cursor p-8">
|
||||||
|
<AppIcon iconName="app-import" class="mr-8"></AppIcon>
|
||||||
|
{{ $t('views.functionLib.importFunction') }}
|
||||||
|
</div>
|
||||||
|
</el-upload>
|
||||||
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col
|
<el-col
|
||||||
:xs="24"
|
:xs="24"
|
||||||
@ -98,6 +120,12 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-divider direction="vertical" />
|
<el-divider direction="vertical" />
|
||||||
|
<el-tooltip effect="dark" :content="$t('common.export')" placement="top">
|
||||||
|
<el-button text @click.stop="exportFunctionLib(item)">
|
||||||
|
<AppIcon iconName="app-export"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
|
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
|
||||||
<el-button
|
<el-button
|
||||||
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
|
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
|
||||||
@ -131,7 +159,7 @@ import { ref, onMounted, reactive } from 'vue'
|
|||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
import functionLibApi from '@/api/function-lib'
|
import functionLibApi from '@/api/function-lib'
|
||||||
import FunctionFormDrawer from './component/FunctionFormDrawer.vue'
|
import FunctionFormDrawer from './component/FunctionFormDrawer.vue'
|
||||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
import { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
import applicationApi from '@/api/application'
|
import applicationApi from '@/api/application'
|
||||||
import { t } from '@/locales'
|
import { t } from '@/locales'
|
||||||
@ -161,6 +189,7 @@ interface UserOption {
|
|||||||
const userOptions = ref<UserOption[]>([])
|
const userOptions = ref<UserOption[]>([])
|
||||||
|
|
||||||
const selectUserId = ref('all')
|
const selectUserId = ref('all')
|
||||||
|
const elUploadRef = ref<any>()
|
||||||
|
|
||||||
const canEdit = (row: any) => {
|
const canEdit = (row: any) => {
|
||||||
return user.userInfo?.id === row?.user_id
|
return user.userInfo?.id === row?.user_id
|
||||||
@ -242,6 +271,40 @@ function copyFunctionLib(row: any) {
|
|||||||
FunctionFormDrawerRef.value.open(obj)
|
FunctionFormDrawerRef.value.open(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exportFunctionLib(row: any) {
|
||||||
|
functionLibApi.exportFunctionLib(row.id, row.name, loading)
|
||||||
|
.catch((e: any) => {
|
||||||
|
if (e.response.status !== 403) {
|
||||||
|
e.response.data.text().then((res: string) => {
|
||||||
|
MsgError(`${t('views.application.tip.ExportError')}:${JSON.parse(res).message}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function importFunctionLib(file: any) {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file.raw, file.name)
|
||||||
|
elUploadRef.value.clearFiles()
|
||||||
|
functionLibApi
|
||||||
|
.importFunctionLib(formData, loading)
|
||||||
|
.then(async (res: any) => {
|
||||||
|
if (res?.data) {
|
||||||
|
searchHandle()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e: any) => {
|
||||||
|
if (e.code === 400) {
|
||||||
|
MsgConfirm(t('common.tip'), t('views.application.tip.professionalMessage'), {
|
||||||
|
cancelButtonText: t('common.confirm'),
|
||||||
|
confirmButtonText: t('common.professional')
|
||||||
|
}).then(() => {
|
||||||
|
window.open('https://maxkb.cn/pricing.html', '_blank')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function getList() {
|
function getList() {
|
||||||
const params = {
|
const params = {
|
||||||
...(searchValue.value && { name: searchValue.value }),
|
...(searchValue.value && { name: searchValue.value }),
|
||||||
@ -303,6 +366,42 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.application-card-add {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
min-height: var(--card-min-height);
|
||||||
|
border: 1px dashed var(--el-border-color);
|
||||||
|
background: var(--el-disabled-bg-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: 1px solid var(--el-card-bg-color);
|
||||||
|
background-color: var(--el-card-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-add-button {
|
||||||
|
&:hover {
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--app-text-color-light-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-upload) {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.application-card {
|
||||||
|
.status-tag {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.function-lib-list-container {
|
.function-lib-list-container {
|
||||||
.status-button {
|
.status-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user