feat: 应用、知识库、函数库增加创建者

--story=1016834 --user=王孝刚 应用、知识库、函数增加创建者,可以按创建用户查看资源 #1304 https://www.tapd.cn/57709429/s/1605886
This commit is contained in:
wxg0103 2024-11-05 11:52:56 +08:00 committed by wxg0103
parent 81b8e522e6
commit f318f2da40
14 changed files with 323 additions and 66 deletions

View File

@ -19,8 +19,10 @@ from django.conf import settings
from django.contrib.postgres.fields import ArrayField
from django.core import cache, validators
from django.core import signing
from django.core.paginator import Paginator
from django.db import transaction, models
from django.db.models import QuerySet
from django.db.models import QuerySet, Q
from django.forms import CharField
from django.http import HttpResponse
from django.template import Template, Context
from rest_framework import serializers
@ -44,7 +46,7 @@ from dataset.models import DataSet, Document, Image
from dataset.serializers.common_serializers import list_paragraph, get_embedding_model_by_dataset_id_list
from embedding.models import SearchMode
from function_lib.serializers.function_lib_serializer import FunctionLibSerializer
from setting.models import AuthOperate
from setting.models import AuthOperate, TeamMember, TeamMemberPermission
from setting.models.model_management import Model
from setting.models_provider import get_model_credential
from setting.models_provider.constants.model_provider_constants import ModelProvideConstants
@ -559,17 +561,22 @@ class ApplicationSerializer(serializers.Serializer):
desc = serializers.CharField(required=False, error_messages=ErrMessage.char("应用描述"))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
select_user_id = serializers.UUIDField(required=False, error_messages=ErrMessage.uuid("选择用户id"))
def get_query_set(self):
user_id = self.data.get("user_id")
query_set_dict = {}
query_set = QuerySet(model=get_dynamics_model(
{'temp_application.name': models.CharField(), 'temp_application.desc': models.CharField(),
'temp_application.create_time': models.DateTimeField()}))
'temp_application.create_time': models.DateTimeField(),
'temp_application.user_id': models.CharField(), }))
if "desc" in self.data and self.data.get('desc') is not None:
query_set = query_set.filter(**{'temp_application.desc__icontains': self.data.get("desc")})
if "name" in self.data and self.data.get('name') is not None:
query_set = query_set.filter(**{'temp_application.name__icontains': self.data.get("name")})
if 'select_user_id' in self.data and self.data.get('select_user_id') is not None and self.data.get(
'select_user_id') != 'all':
query_set = query_set.filter(**{'temp_application.user_id__exact': self.data.get('select_user_id')})
query_set = query_set.order_by("-temp_application.create_time")
query_set_dict['default_sql'] = query_set

View File

@ -129,17 +129,21 @@ class DataSetSerializers(serializers.ModelSerializer):
)
user_id = serializers.CharField(required=True)
select_user_id = serializers.CharField(required=False)
def get_query_set(self):
user_id = self.data.get("user_id")
query_set_dict = {}
query_set = QuerySet(model=get_dynamics_model(
{'temp.name': models.CharField(), 'temp.desc': models.CharField(),
"document_temp.char_length": models.IntegerField(), 'temp.create_time': models.DateTimeField()}))
"document_temp.char_length": models.IntegerField(), 'temp.create_time': models.DateTimeField(),
'temp.user_id': models.CharField(), }))
if "desc" in self.data and self.data.get('desc') is not None:
query_set = query_set.filter(**{'temp.desc__icontains': self.data.get("desc")})
if "name" in self.data and self.data.get('name') is not None:
query_set = query_set.filter(**{'temp.name__icontains': self.data.get("name")})
if "select_user_id" in self.data and self.data.get('select_user_id') is not None:
query_set = query_set.filter(**{'temp.user_id__exact': self.data.get("select_user_id")})
query_set = query_set.order_by("-temp.create_time")
query_set_dict['default_sql'] = query_set

View File

@ -221,7 +221,7 @@ class Dataset(APIView):
def get(self, request: Request, current_page, page_size):
d = DataSetSerializers.Query(
data={'name': request.query_params.get('name', None), 'desc': request.query_params.get("desc", None),
'user_id': str(request.user.id)})
'user_id': str(request.user.id), 'select_user_id': request.query_params.get('select_user_id', None)})
d.is_valid()
return result.success(d.page(current_page, page_size))

View File

@ -98,6 +98,7 @@ class FunctionLibSerializer(serializers.Serializer):
is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.char("是否可用"))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
select_user_id = serializers.CharField(required=False, allow_null=True, allow_blank=True)
def get_query_set(self):
query_set = QuerySet(FunctionLib).filter(
@ -108,6 +109,8 @@ class FunctionLibSerializer(serializers.Serializer):
query_set = query_set.filter(desc__contains=self.data.get('desc'))
if self.data.get('is_active') is not None:
query_set = query_set.filter(is_active=self.data.get('is_active'))
if self.data.get('select_user_id') is not None:
query_set = query_set.filter(user_id=self.data.get('select_user_id'))
query_set = query_set.order_by("-create_time")
return query_set

View File

@ -105,5 +105,6 @@ class FunctionLibView(APIView):
FunctionLibSerializer.Query(
data={'name': request.query_params.get('name'),
'desc': request.query_params.get('desc'),
'user_id': request.user.id}).page(
'user_id': request.user.id,
'select_user_id': request.query_params.get('select_user_id')}).page(
current_page, page_size))

View File

@ -17,7 +17,7 @@ from django.core import validators, signing, cache
from django.core.mail import send_mail
from django.core.mail.backends.smtp import EmailBackend
from django.db import transaction
from django.db.models import Q, QuerySet
from django.db.models import Q, QuerySet, Prefetch
from drf_yasg import openapi
from rest_framework import serializers
@ -35,6 +35,7 @@ from common.util.field_message import ErrMessage
from common.util.lock import lock
from dataset.models import DataSet, Document, Paragraph, Problem, ProblemParagraphMapping
from embedding.task import delete_embedding_by_dataset_id_list
from function_lib.models.function import FunctionLib
from setting.models import Team, SystemSetting, SettingType, Model, TeamMember, TeamMemberPermission
from smartdoc.conf import PROJECT_DIR
from users.models.user import User, password_encrypt, get_user_dynamics_permission
@ -495,6 +496,40 @@ class UserSerializer(ApiMixin, serializers.ModelSerializer):
return [{'id': user_model.id, 'username': user_model.username, 'email': user_model.email} for user_model in
QuerySet(User).filter(Q(username=email_or_username) | Q(email=email_or_username))]
def listByType(self, type, user_id):
teamIds = TeamMember.objects.filter(user_id=user_id).values_list('id', flat=True)
targets = TeamMemberPermission.objects.filter(
member_id__in=teamIds,
auth_target_type=type,
operate__contains=['USE']
).values_list('target', flat=True)
prefetch_users = Prefetch('user', queryset=User.objects.only('id', 'username'))
user_list = []
if type == 'DATASET':
user_list = DataSet.objects.filter(
Q(id__in=targets) | Q(user_id=user_id)
).prefetch_related(prefetch_users).distinct('user_id')
elif type == 'APPLICATION':
user_list = Application.objects.filter(
Q(id__in=targets) | Q(user_id=user_id)
).prefetch_related(prefetch_users).distinct('user_id')
elif type == 'FUNCTION':
user_list = FunctionLib.objects.filter(
Q(permission_type='PUBLIC') | Q(user_id=user_id)
).prefetch_related(prefetch_users).distinct('user_id')
other_users = [
{'id': app.user.id, 'username': app.user.username}
for app in user_list if app.user.id != user_id
]
users = [
{'id': 'all', 'username': '全部'},
{'id': user_id, 'username': '我的'}
]
users.extend(other_users)
return users
class UserInstanceSerializer(ApiMixin, serializers.ModelSerializer):
class Meta:

View File

@ -21,4 +21,5 @@ urlpatterns = [
name="user_manage_re_password"),
path("user_manage/<int:current_page>/<int:page_size>", views.UserManage.Page.as_view(),
name="user_manage_re_password"),
path('user/list/<str:type>', views.UserListView.as_view()),
]

View File

@ -301,3 +301,16 @@ class UserManage(APIView):
def put(self, request: Request, user_id):
return result.success(
UserManageSerializer.Operate(data={'id': user_id}).edit(request.data, with_valid=True))
class UserListView(APIView):
authentication_classes = [TokenAuth]
@swagger_auto_schema(operation_summary="通过类型获取用户列表",
operation_id="通过类型获取用户列表",
manual_parameters=UserSerializer.Query.get_request_params_api(),
responses=result.get_api_array_response(UserSerializer.Query.get_response_body_api()),
tags=['用户'])
@has_permissions(PermissionConstants.USER_READ)
def get(self, request: Request, type):
return result.success(UserSerializer().listByType(type, request.user.id))

View File

@ -456,6 +456,13 @@ const putWorkFlowVersion: (
)
}
const getUserList: (type: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
type,
loading
) => {
return get(`/user/list/${type}`, undefined, loading)
}
export default {
getAllAppilcation,
getApplication,
@ -492,5 +499,6 @@ export default {
getWorkFlowVersion,
getWorkFlowVersionDetail,
putWorkFlowVersion,
playDemoText
playDemoText,
getUserList
}

View File

@ -8,9 +8,19 @@
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
</AppAvatar>
</slot>
<auto-tooltip :content="title" style="width: 65%">
{{ title }}
</auto-tooltip>
<div>
<auto-tooltip :content="title" style="width: 65%; height: 22px">
{{ title }}
</auto-tooltip>
<span class="lighter mr-8">
<auto-tooltip
:content="username"
style="display: inline-block; width: 100%; font-size: 12px; height: 20px"
>
创建人: {{ username }}
</auto-tooltip></span
>
</div>
</div>
</slot>
</div>
@ -49,6 +59,7 @@ const props = withDefaults(
* 是否展示icon
*/
showIcon?: boolean
username?: string
}>(),
{ title: '标题', description: '', showIcon: true, border: true }
)
@ -62,13 +73,12 @@ function cardEnter() {
}
function cardLeave() {
show.value = subHovered.value;
show.value = subHovered.value
}
function subHoveredEnter() {
subHovered.value = true
}
</script>
<style lang="scss" scoped>
.card-box {

View File

@ -34,7 +34,7 @@
/** card */
--card-width: 330px;
--card-min-height: 160px;
--card-min-height: 166px;
--card-min-width: 220px;
/** setting */

View File

@ -2,14 +2,24 @@
<div class="application-list-container p-24" style="padding-top: 16px">
<div class="flex-between mb-16">
<h4>{{ $t('views.application.applicationList.title') }}</h4>
<el-input
v-model="searchValue"
@change="searchHandle"
:placeholder="$t('views.application.applicationList.searchBar.placeholder')"
prefix-icon="Search"
class="w-240"
clearable
/>
<div class="flex-between">
<el-select v-model="selectUserId" class="mr-12 w-120" @change="searchHandle">
<el-option
v-for="item in userOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-model="searchValue"
@change="searchHandle"
:placeholder="$t('views.application.applicationList.searchBar.placeholder')"
prefix-icon="Search"
class="w-240"
clearable
/>
</div>
</div>
<div v-loading.fullscreen.lock="paginationConfig.current_page === 1 && loading">
<InfiniteScroll
@ -39,6 +49,7 @@
>
<CardBox
:title="item.name"
:username="item.username"
:description="item.desc"
class="application-card cursor"
@click="router.push({ path: `/application/${item.id}/${item.type}/overview` })"
@ -63,8 +74,10 @@
/>
</template>
<div class="status-tag">
<el-tag type="warning" v-if="isWorkFlow(item.type)">高级编排</el-tag>
<el-tag class="blue-tag" v-else>简单配置</el-tag>
<el-tag type="warning" v-if="isWorkFlow(item.type)" style="height: 22px"
>高级编排</el-tag
>
<el-tag class="blue-tag" v-else style="height: 22px">简单配置</el-tag>
</div>
<template #footer>
@ -149,6 +162,14 @@ const paginationConfig = reactive({
page_size: 20,
total: 0
})
interface UserOption {
label: string
value: string
}
const userOptions = ref<UserOption[]>([])
const selectUserId = ref('all')
const searchValue = ref('')
@ -175,7 +196,7 @@ function openCreateDialog() {
} else {
MsgConfirm(`提示`, '社区版最多支持 5 个应用,如需拥有更多应用,请升级为专业版。', {
cancelButtonText: '确定',
confirmButtonText: '购买专业版',
confirmButtonText: '购买专业版'
})
.then(() => {
window.open('https://maxkb.cn/pricing.html', '_blank')
@ -193,6 +214,9 @@ function openCreateDialog() {
}
function searchHandle() {
if (user.userInfo) {
localStorage.setItem(user.userInfo.id + 'application', selectUserId.value)
}
paginationConfig.total = 0
paginationConfig.current_page = 1
applicationList.value = []
@ -226,16 +250,45 @@ function deleteApplication(row: any) {
}
function getList() {
applicationApi
.getApplication(paginationConfig, searchValue.value && { name: searchValue.value }, loading)
.then((res) => {
applicationList.value = [...applicationList.value, ...res.data.records]
paginationConfig.total = res.data.total
const params = {
...(searchValue.value && { name: searchValue.value }),
...(selectUserId.value &&
selectUserId.value !== 'all' && { select_user_id: selectUserId.value })
}
applicationApi.getApplication(paginationConfig, params, loading).then((res) => {
res.data.records.forEach((item: any) => {
if (user.userInfo && item.user_id === user.userInfo.id) {
item.username = user.userInfo.username
} else {
item.username = userOptions.value.find((v) => v.value === item.user_id)?.label
}
})
applicationList.value = [...applicationList.value, ...res.data.records]
paginationConfig.total = res.data.total
})
}
function getUserList() {
applicationApi.getUserList('APPLICATION', loading).then((res) => {
if (res.data) {
userOptions.value = res.data.map((item: any) => {
return {
label: item.username,
value: item.id
}
})
if (user.userInfo) {
const selectUserIdValue = localStorage.getItem(user.userInfo.id + 'application')
if (selectUserIdValue && userOptions.value.find((v) => v.value === selectUserIdValue)) {
selectUserId.value = selectUserIdValue
}
}
getList()
}
})
}
onMounted(() => {
getList()
getUserList()
})
</script>
<style lang="scss" scoped>
@ -243,7 +296,7 @@ onMounted(() => {
.status-tag {
position: absolute;
right: 16px;
top: 13px;
top: 3px;
}
}
.dropdown-custom-switch {

View File

@ -2,14 +2,24 @@
<div class="dataset-list-container p-24" style="padding-top: 16px">
<div class="flex-between mb-16">
<h4>知识库</h4>
<el-input
v-model="searchValue"
@change="searchHandle"
placeholder="按名称搜索"
prefix-icon="Search"
class="w-240"
clearable
/>
<div class="flex-between">
<el-select v-model="selectUserId" class="mr-12 w-120" @change="searchHandle">
<el-option
v-for="item in userOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-model="searchValue"
@change="searchHandle"
:placeholder="$t('views.application.applicationList.searchBar.placeholder')"
prefix-icon="Search"
class="w-240"
clearable
/>
</div>
</div>
<div v-loading.fullscreen.lock="paginationConfig.current_page === 1 && loading">
<InfiniteScroll
@ -28,6 +38,7 @@
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb-16">
<CardBox
:title="item.name"
:username="item.username"
:description="item.desc"
class="cursor"
@click="router.push({ path: `/dataset/${item.id}/document` })"
@ -46,8 +57,14 @@
</AppAvatar>
</template>
<div class="delete-button">
<el-tag class="blue-tag" v-if="item.type === '0'">通用型</el-tag>
<el-tag class="purple-tag" v-else-if="item.type === '1'" type="warning"
<el-tag class="blue-tag" v-if="item.type === '0'" style="height: 22px"
>通用型</el-tag
>
<el-tag
class="purple-tag"
v-else-if="item.type === '1'"
type="warning"
style="height: 22px"
>Web 站点</el-tag
>
</div>
@ -120,6 +137,7 @@ import { useRouter } from 'vue-router'
import { numberFormat } from '@/utils/utils'
import { ValidType, ValidCount } from '@/enums/common'
import useStore from '@/stores'
import applicationApi from '@/api/application'
const { user, common } = useStore()
const router = useRouter()
@ -136,6 +154,15 @@ const paginationConfig = reactive({
const searchValue = ref('')
interface UserOption {
label: string
value: string
}
const userOptions = ref<UserOption[]>([])
const selectUserId = ref('all')
function openCreateDialog() {
if (user.isEnterprise()) {
CreateDatasetDialogRef.value.open()
@ -174,6 +201,9 @@ function syncDataset(row: any) {
}
function searchHandle() {
if (user.userInfo) {
localStorage.setItem(user.userInfo.id + 'dataset', selectUserId.value)
}
paginationConfig.current_page = 1
datasetList.value = []
getList()
@ -204,16 +234,46 @@ function deleteDataset(row: any) {
}
function getList() {
datasetApi
.getDataset(paginationConfig, searchValue.value && { name: searchValue.value }, loading)
.then((res) => {
paginationConfig.total = res.data.total
datasetList.value = [...datasetList.value, ...res.data.records]
const params = {
...(searchValue.value && { name: searchValue.value }),
...(selectUserId.value &&
selectUserId.value !== 'all' && { select_user_id: selectUserId.value })
}
datasetApi.getDataset(paginationConfig, params, loading).then((res) => {
res.data.records.forEach((item: any) => {
if (user.userInfo && item.user_id === user.userInfo.id) {
item.username = user.userInfo.username
} else {
item.username = userOptions.value.find((v) => v.value === item.user_id)?.label
}
})
paginationConfig.total = res.data.total
datasetList.value = [...datasetList.value, ...res.data.records]
})
}
function getUserList() {
applicationApi.getUserList('DATASET', loading).then((res) => {
if (res.data) {
userOptions.value = res.data.map((item: any) => {
return {
label: item.username,
value: item.id
}
})
if (user.userInfo) {
const selectUserIdValue = localStorage.getItem(user.userInfo.id + 'dataset')
if (selectUserIdValue && userOptions.value.find((v) => v.value === selectUserIdValue)) {
selectUserId.value = selectUserIdValue
}
}
getList()
}
})
}
onMounted(() => {
getList()
getUserList()
})
</script>
<style lang="scss" scoped>
@ -221,7 +281,7 @@ onMounted(() => {
.delete-button {
position: absolute;
right: 12px;
top: 13px;
top: 3px;
height: auto;
}
.footer-content {

View File

@ -2,14 +2,24 @@
<div class="function-lib-list-container p-24" style="padding-top: 16px">
<div class="flex-between mb-16">
<h4>函数库</h4>
<el-input
v-model="searchValue"
@change="searchHandle"
placeholder="按函数名称搜索"
prefix-icon="Search"
class="w-240"
clearable
/>
<div class="flex-between">
<el-select v-model="selectUserId" class="mr-12 w-120" @change="searchHandle">
<el-option
v-for="item in userOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-model="searchValue"
@change="searchHandle"
placeholder="按函数名称搜索"
prefix-icon="Search"
class="w-240"
clearable
/>
</div>
</div>
<div
v-loading.fullscreen.lock="
@ -41,6 +51,7 @@
<CardBox
:title="item.name"
:description="item.desc"
:username="item.username"
class="function-lib-card"
@click="openCreateDialog(item)"
:class="item.permission_type === 'PUBLIC' && !canEdit(item) ? '' : 'cursor'"
@ -51,8 +62,16 @@
</AppAvatar>
</template>
<div class="status-button">
<el-tag class="info-tag" v-if="item.permission_type === 'PUBLIC'">公用</el-tag>
<el-tag class="danger-tag" v-else-if="item.permission_type === 'PRIVATE'"
<el-tag
class="info-tag"
v-if="item.permission_type === 'PUBLIC'"
style="height: 22px"
>公用</el-tag
>
<el-tag
class="danger-tag"
v-else-if="item.permission_type === 'PRIVATE'"
style="height: 22px"
>私有</el-tag
>
</div>
@ -100,6 +119,7 @@ import functionLibApi from '@/api/function-lib'
import FunctionFormDrawer from './component/FunctionFormDrawer.vue'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
import useStore from '@/stores'
import applicationApi from '@/api/application'
const { user } = useStore()
const loading = ref(false)
@ -118,6 +138,15 @@ const searchValue = ref('')
const title = ref('')
const changeStateloading = ref(false)
interface UserOption {
label: string
value: string
}
const userOptions = ref<UserOption[]>([])
const selectUserId = ref('all')
const canEdit = (row: any) => {
return user.userInfo?.id === row?.user_id
}
@ -134,6 +163,9 @@ function openCreateDialog(data?: any) {
}
function searchHandle() {
if (user.userInfo) {
localStorage.setItem(user.userInfo.id + 'function', selectUserId.value)
}
paginationConfig.total = 0
paginationConfig.current_page = 1
functionLibList.value = []
@ -196,12 +228,22 @@ function copyFunctionLib(row: any) {
}
function getList() {
functionLibApi
.getFunctionLib(paginationConfig, searchValue.value && { name: searchValue.value }, loading)
.then((res: any) => {
functionLibList.value = [...functionLibList.value, ...res.data.records]
paginationConfig.total = res.data.total
const params = {
...(searchValue.value && { name: searchValue.value }),
...(selectUserId.value &&
selectUserId.value !== 'all' && { select_user_id: selectUserId.value })
}
functionLibApi.getFunctionLib(paginationConfig, params, loading).then((res: any) => {
res.data.records.forEach((item: any) => {
if (user.userInfo && item.user_id === user.userInfo.id) {
item.username = user.userInfo.username
} else {
item.username = userOptions.value.find((v) => v.value === item.user_id)?.label
}
})
functionLibList.value = [...functionLibList.value, ...res.data.records]
paginationConfig.total = res.data.total
})
}
function refresh(data: any) {
@ -216,8 +258,28 @@ function refresh(data: any) {
}
}
function getUserList() {
applicationApi.getUserList('FUNCTION', loading).then((res) => {
if (res.data) {
userOptions.value = res.data.map((item: any) => {
return {
label: item.username,
value: item.id
}
})
if (user.userInfo) {
const selectUserIdValue = localStorage.getItem(user.userInfo.id + 'function')
if (selectUserIdValue && userOptions.value.find((v) => v.value === selectUserIdValue)) {
selectUserId.value = selectUserIdValue
}
}
getList()
}
})
}
onMounted(() => {
getList()
getUserList()
})
</script>
<style lang="scss" scoped>
@ -225,7 +287,7 @@ onMounted(() => {
.status-button {
position: absolute;
right: 12px;
top: 13px;
top: 3px;
height: auto;
}
}