mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-18 00:00:00 +00:00
can add and modify property (#6297)
* can add and modify property * clear extend property code
This commit is contained in:
@@ -1,67 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import toaster from '../toast';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import Loading from '../loading';
|
||||
|
||||
import '../../css/apply-folder-properties.css';
|
||||
|
||||
const propTypes = {
|
||||
toggle: PropTypes.func,
|
||||
repoID: PropTypes.string,
|
||||
path: PropTypes.string
|
||||
};
|
||||
|
||||
class ConfirmApplyFolderPropertiesDialog extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
submitting: false
|
||||
};
|
||||
}
|
||||
|
||||
submit = () => {
|
||||
const { repoID, path } = this.props;
|
||||
this.setState({ submitting: true });
|
||||
seafileAPI.applyFolderExtendedProperties(repoID, path).then(() => {
|
||||
toaster.success(gettext('Successfully applied the properties.'));
|
||||
this.props.toggle();
|
||||
}).catch(error => {
|
||||
let errorMsg = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMsg);
|
||||
this.setState({ submitting: false });
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { submitting } = this.state;
|
||||
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.props.toggle} className="apply-properties-dialog">
|
||||
<ModalHeader toggle={this.props.toggle}>
|
||||
{gettext('Apply properties')}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<p>
|
||||
{gettext('Are you sure you want to apply the properties to all the files inside the folder?')}
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color='secondary' onClick={this.props.toggle} disabled={submitting}>{gettext('Cancel')}</Button>
|
||||
<Button color='primary' className='flex-shrink-0 apply-properties' disabled={submitting} onClick={this.submit}>
|
||||
{submitting ? (<Loading />) : (<>{gettext('Submit')}</>)}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ConfirmApplyFolderPropertiesDialog.propTypes = propTypes;
|
||||
|
||||
export default ConfirmApplyFolderPropertiesDialog;
|
@@ -2,18 +2,19 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
|
||||
import isHotkey from 'is-hotkey';
|
||||
import { zIndexes, DIALOG_MAX_HEIGHT, EXTRA_ATTRIBUTES_COLUMN_TYPE } from '../../../constants';
|
||||
import { zIndexes, DIALOG_MAX_HEIGHT } from '../../../constants';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { getSelectColumnOptions, getValidColumns } from '../../../utils/extra-attributes';
|
||||
import { getValidColumns } from '../../../utils/extra-attributes';
|
||||
import Column from './column';
|
||||
import Loading from '../../loading';
|
||||
import toaster from '../../toast';
|
||||
import metadataAPI from '../../../metadata/api';
|
||||
|
||||
import './index.css';
|
||||
|
||||
class ExtraAttributesDialog extends Component {
|
||||
|
||||
class ExtraMetadataAttributesDialog extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -71,64 +72,43 @@ class ExtraAttributesDialog extends Component {
|
||||
}, 1);
|
||||
};
|
||||
|
||||
getFormatUpdateData = (update = {}) => {
|
||||
const { columns } = this.state;
|
||||
const updateData = {};
|
||||
for (let key in update) {
|
||||
const column = columns.find(column => column.key === key);
|
||||
if (column && column.editable) {
|
||||
const { type, name } = column;
|
||||
const value = update[key];
|
||||
if (type === EXTRA_ATTRIBUTES_COLUMN_TYPE.SINGLE_SELECT) {
|
||||
const options = getSelectColumnOptions(column);
|
||||
const option = options.find(item => item.id === value);
|
||||
updateData[name] = option ? option.name : '';
|
||||
} else {
|
||||
updateData[column.name] = update[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return updateData;
|
||||
};
|
||||
|
||||
getData = () => {
|
||||
const { repoID, filePath } = this.props;
|
||||
seafileAPI.getFileExtendedProperties(repoID, filePath).then(res => {
|
||||
const { repoID, filePath, direntType } = this.props;
|
||||
|
||||
let dirName = Utils.getDirName(filePath);
|
||||
let fileName = Utils.getFileName(filePath);
|
||||
let parentDir = direntType === 'file' ? dirName : dirName.slice(0, dirName.length - fileName.length - 1);
|
||||
|
||||
if (!parentDir.startsWith('/')) {
|
||||
parentDir = '/' + parentDir;
|
||||
}
|
||||
|
||||
metadataAPI.getMetadataRecordInfo(repoID, parentDir, fileName).then(res => {
|
||||
const { row, metadata, editable_columns } = res.data;
|
||||
this.isExist = Boolean(row._id);
|
||||
this.setState({ row: row, columns: getValidColumns(metadata, editable_columns, this.isEmptyFile), isLoading: false, errorMsg: '' });
|
||||
}).catch(error => {
|
||||
const errorMsg =Utils.getErrorMsg(error);
|
||||
const errorMsg = Utils.getErrorMsg(error);
|
||||
this.setState({ isLoading: false, errorMsg });
|
||||
});
|
||||
};
|
||||
|
||||
createData = (data) => {
|
||||
const { repoID, filePath } = this.props;
|
||||
seafileAPI.newFileExtendedProperties(repoID, filePath, data).then(res => {
|
||||
this.isExist = true;
|
||||
const { row } = res.data;
|
||||
this.setState({ row: row, isLoading: false, errorMsg: '' });
|
||||
}).catch(error => {
|
||||
const errorMsg =Utils.getErrorMsg(error);
|
||||
toaster.danger(gettext(errorMsg));
|
||||
});
|
||||
};
|
||||
|
||||
updateData = (update, column) => {
|
||||
const newRow = { ...this.state.row, ...update };
|
||||
this.setState({ row: newRow }, () => {
|
||||
const data = this.getFormatUpdateData(update);
|
||||
const { repoID, filePath } = this.props;
|
||||
|
||||
let newValue = update[column.key];
|
||||
let recordID = this.state.row._id;
|
||||
if (this.isExist) {
|
||||
seafileAPI.updateFileExtendedProperties(repoID, filePath, data).then(res => {
|
||||
metadataAPI.updateMetadataRecord(repoID, recordID, column.name, newValue).then(res => {
|
||||
this.setState({ update: {}, row: res.data.row });
|
||||
}).catch(error => {
|
||||
const errorMsg = Utils.getErrorMsg(error);
|
||||
toaster.danger(gettext(errorMsg));
|
||||
});
|
||||
} else {
|
||||
this.createData(data);
|
||||
// this.createData(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -244,7 +224,7 @@ class ExtraAttributesDialog extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
ExtraAttributesDialog.propTypes = {
|
||||
ExtraMetadataAttributesDialog.propTypes = {
|
||||
repoID: PropTypes.string,
|
||||
filePath: PropTypes.string,
|
||||
direntType: PropTypes.string,
|
||||
@@ -252,4 +232,4 @@ ExtraAttributesDialog.propTypes = {
|
||||
onToggle: PropTypes.func,
|
||||
};
|
||||
|
||||
export default ExtraAttributesDialog;
|
||||
export default ExtraMetadataAttributesDialog;
|
@@ -3,12 +3,11 @@ import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import Icon from '../icon';
|
||||
import { gettext, canSetExProps } from '../../utils/constants';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import EditFileTagPopover from '../popover/edit-filetag-popover';
|
||||
import ExtraAttributesDialog from '../dialog/extra-attributes-dialog';
|
||||
import FileTagList from '../file-tag-list';
|
||||
import ConfirmApplyFolderPropertiesDialog from '../dialog/confirm-apply-folder-properties-dialog';
|
||||
import ExtraMetadataAttributesDialog from '../dialog/extra-metadata-attributes-dialog';
|
||||
|
||||
const propTypes = {
|
||||
repoInfo: PropTypes.object.isRequired,
|
||||
@@ -28,8 +27,7 @@ class DetailListView extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
isEditFileTagShow: false,
|
||||
isShowExtraProperties: false,
|
||||
isShowApplyProperties: false
|
||||
isShowMetadataExtraProperties: false,
|
||||
};
|
||||
this.tagListTitleID = `detail-list-view-tags-${uuidv4()}`;
|
||||
}
|
||||
@@ -65,12 +63,8 @@ class DetailListView extends React.Component {
|
||||
return Utils.joinPath(path, dirent.name);
|
||||
};
|
||||
|
||||
toggleExtraPropertiesDialog = () => {
|
||||
this.setState({ isShowExtraProperties: !this.state.isShowExtraProperties });
|
||||
};
|
||||
|
||||
toggleApplyPropertiesDialog = () => {
|
||||
this.setState({ isShowApplyProperties: !this.state.isShowApplyProperties });
|
||||
toggleExtraMetadataPropertiesDialog = () => {
|
||||
this.setState({ isShowMetadataExtraProperties: !this.state.isShowMetadataExtraProperties });
|
||||
};
|
||||
|
||||
renderTags = () => {
|
||||
@@ -85,23 +79,12 @@ class DetailListView extends React.Component {
|
||||
<tbody>
|
||||
<tr><th>{gettext('Location')}</th><td>{position}</td></tr>
|
||||
<tr><th>{gettext('Last Update')}</th><td>{moment(direntDetail.mtime).format('YYYY-MM-DD')}</td></tr>
|
||||
{direntDetail.permission === 'rw' && canSetExProps && (
|
||||
{direntDetail.permission === 'rw' && window.app.pageOptions.enableMetadataManagement && (
|
||||
<Fragment>
|
||||
<tr className="file-extra-attributes">
|
||||
<th colSpan={2}>
|
||||
<div className="edit-file-extra-attributes-btn" onClick={this.toggleExtraPropertiesDialog}>
|
||||
{gettext('Edit extra properties')}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<tr className="file-extra-attributes">
|
||||
<th colSpan={2}>
|
||||
<div
|
||||
className="edit-file-extra-attributes-btn text-truncate"
|
||||
onClick={this.toggleApplyPropertiesDialog}
|
||||
title={gettext('Apply properties to files inside the folder')}
|
||||
>
|
||||
{gettext('Apply properties to files inside the folder')}
|
||||
<div className="edit-file-extra-attributes-btn" onClick={this.toggleExtraMetadataPropertiesDialog}>
|
||||
{gettext('Edit metadata properties')}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
@@ -127,11 +110,11 @@ class DetailListView extends React.Component {
|
||||
<span onClick={this.onEditFileTagToggle} id={this.tagListTitleID}><Icon symbol='tag' /></span>
|
||||
</td>
|
||||
</tr>
|
||||
{direntDetail.permission === 'rw' && canSetExProps && (
|
||||
{direntDetail.permission === 'rw' && window.app.pageOptions.enableMetadataManagement && (
|
||||
<tr className="file-extra-attributes">
|
||||
<th colSpan={2}>
|
||||
<div className="edit-file-extra-attributes-btn" onClick={this.toggleExtraPropertiesDialog}>
|
||||
{gettext('Edit extra properties')}
|
||||
<div className="edit-file-extra-attributes-btn" onClick={this.toggleExtraMetadataPropertiesDialog}>
|
||||
{gettext('Edit metadata properties')}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
@@ -160,20 +143,13 @@ class DetailListView extends React.Component {
|
||||
isEditFileTagShow={this.state.isEditFileTagShow}
|
||||
/>
|
||||
}
|
||||
{this.state.isShowExtraProperties && (
|
||||
<ExtraAttributesDialog
|
||||
{this.state.isShowMetadataExtraProperties && (
|
||||
<ExtraMetadataAttributesDialog
|
||||
repoID={this.props.repoID}
|
||||
filePath={direntPath}
|
||||
direntType={direntType}
|
||||
direntDetail={direntDetail}
|
||||
onToggle={this.toggleExtraPropertiesDialog}
|
||||
/>
|
||||
)}
|
||||
{this.state.isShowApplyProperties && (
|
||||
<ConfirmApplyFolderPropertiesDialog
|
||||
toggle={this.toggleApplyPropertiesDialog}
|
||||
repoID={this.props.repoID}
|
||||
path={direntPath}
|
||||
onToggle={this.toggleExtraMetadataPropertiesDialog}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
|
@@ -77,24 +77,35 @@ class MetadataManagerAPI {
|
||||
return this.req.post(url, data);
|
||||
}
|
||||
|
||||
updateMetadataRecord = (repoID, recordID, creator, createTime, modifier, modifyTime, parentDir, name) => {
|
||||
addMetadataColumn(repoID, column_name) {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/columns/';
|
||||
let data = {
|
||||
'column_name': column_name
|
||||
};
|
||||
return this.req.post(url, data);
|
||||
}
|
||||
|
||||
getMetadataRecordInfo(repoID, parentDir, name) {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/record/';
|
||||
let params = {};
|
||||
if (parentDir) {
|
||||
params['parent_dir'] = parentDir;
|
||||
}
|
||||
if (name) {
|
||||
params['name'] = name;
|
||||
}
|
||||
return this.req.get(url, {params: params});
|
||||
}
|
||||
|
||||
updateMetadataRecord = (repoID, recordID, columnName, newValue) => {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/records/' + recordID + '/';
|
||||
const data = {
|
||||
'creator': creator,
|
||||
'create_time': createTime,
|
||||
'modifier': modifier,
|
||||
'modify_time': modifyTime,
|
||||
'current_dir': parentDir,
|
||||
'name': name,
|
||||
'column_name': columnName,
|
||||
'value': newValue,
|
||||
};
|
||||
return this.req.put(url, data);
|
||||
};
|
||||
|
||||
deleteMetadataRecord = (repoID, recordID) => {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/records/' + recordID + '/';
|
||||
return this.req.delete(url);
|
||||
};
|
||||
|
||||
listUserInfo = (userIds) => {
|
||||
const url = this.server + '/api/v2.1/user-list/';
|
||||
const params = { user_id_list: userIds };
|
||||
|
@@ -97,8 +97,6 @@ export const enablePDFThumbnail = window.app.pageOptions.enablePDFThumbnail;
|
||||
export const enableOnlyoffice = window.app.pageOptions.enableOnlyoffice || false;
|
||||
export const onlyofficeConverterExtensions = window.app.pageOptions.onlyofficeConverterExtensions || [];
|
||||
|
||||
export const canSetExProps = window.app.pageOptions.canSetExProps || false;
|
||||
|
||||
// seafile_ai
|
||||
export const enableSeafileAI = window.app.pageOptions.enableSeafileAI || false;
|
||||
|
||||
|
@@ -1,64 +0,0 @@
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
LEDGER_COLUMNS = [
|
||||
{'column_key': '0000', 'column_name': 'Repo ID', 'column_type': 'text', 'column_data': None},
|
||||
{'column_key': 'GqGh', 'column_name': 'File', 'column_type': 'text', 'column_data': {'enable_fill_default_value': False, 'enable_check_format': False, 'format_specification_value': None, 'default_value': '', 'format_check_type': 'chinese_id_card'}},
|
||||
{'column_key': 'l76s', 'column_name': 'UUID', 'column_type': 'text', 'column_data': {'enable_fill_default_value': False, 'enable_check_format': False, 'format_specification_value': None, 'default_value': '', 'format_check_type': 'chinese_id_card'}},
|
||||
{'column_key': '1fUd', 'column_name': 'Path', 'column_type': 'text', 'column_data': {'enable_fill_default_value': False, 'enable_check_format': False, 'format_specification_value': None, 'default_value': '', 'format_check_type': 'chinese_id_card'}},
|
||||
{'column_key': 'IFzK', 'column_name': '文件大分类', 'column_type': 'single-select', 'column_data': {'enable_fill_default_value': False, 'default_value': None, 'options': []}},
|
||||
{'column_key': 'qc3L', 'column_name': '文件中分类', 'column_type': 'single-select', 'column_data': {'enable_fill_default_value': False, 'default_value': None, 'options': []}},
|
||||
{'column_key': 'k93T', 'column_name': '文件小分类', 'column_type': 'single-select', 'column_data': {'enable_fill_default_value': False, 'default_value': None, 'options': []}},
|
||||
{'column_key': 'sysV', 'column_name': '文件负责人', 'column_type': 'text', 'column_data': {'enable_fill_default_value': False, 'enable_check_format': False, 'format_specification_value': None, 'default_value': '', 'format_check_type': 'chinese_id_card'}},
|
||||
{'column_key': 'TZw3', 'column_name': '密级', 'column_type': 'single-select', 'column_data': {'enable_fill_default_value': False, 'default_value': None, 'options': []}},
|
||||
{'column_key': 'uFNa', 'column_name': '保密期限', 'column_type': 'number', 'column_data': {'format': 'number', 'precision': 2, 'enable_precision': False, 'enable_fill_default_value': False, 'enable_check_format': False, 'decimal': 'dot', 'thousands': 'no', 'format_min_value': 0, 'format_max_value': 1000}},
|
||||
{'column_key': 'BeVA', 'column_name': '创建日期', 'column_type': 'date', 'column_data': {'format': 'YYYY-MM-DD HH:mm', 'enable_fill_default_value': False, 'default_value': '', 'default_date_type': 'specific_date'}},
|
||||
{'column_key': 'ngbE', 'column_name': '废弃日期', 'column_type': 'formula', 'column_data': {'format': 'YYYY-MM-DD', 'formula': "dateAdd({创建日期}, {保密期限}, 'days')", 'operated_columns': ['BeVA', 'uFNa'], 'result_type': 'date'}}
|
||||
]
|
||||
|
||||
DTABLE_WEB_SERVER = ''
|
||||
SEATABLE_EXTENDED_PROPS_BASE_API_TOKEN = ''
|
||||
EXTENDED_PROPS_TABLE_NAME = ''
|
||||
|
||||
# auth
|
||||
url = f"{DTABLE_WEB_SERVER.strip('/')}/api/v2.1/dtable/app-access-token/?from=dtable_web"
|
||||
resp = requests.get(url, headers={'Authorization': f'Token {SEATABLE_EXTENDED_PROPS_BASE_API_TOKEN}'})
|
||||
dtable_uuid = resp.json()['dtable_uuid']
|
||||
access_token = resp.json()['access_token']
|
||||
dtable_server_url = resp.json()['dtable_server']
|
||||
headers = {'Authorization': f'Token {access_token}'}
|
||||
|
||||
# query metadata
|
||||
url = f"{dtable_server_url.strip('/')}/api/v1/dtables/{dtable_uuid}/metadata/?from=dtable_web"
|
||||
resp = requests.get(url, headers=headers)
|
||||
metadata = resp.json()['metadata']
|
||||
existed_table = None
|
||||
for table in metadata['tables']:
|
||||
if table['name'] == EXTENDED_PROPS_TABLE_NAME:
|
||||
existed_table = table
|
||||
break
|
||||
|
||||
# check table or add table
|
||||
if existed_table:
|
||||
logging.info('table %s exists', EXTENDED_PROPS_TABLE_NAME)
|
||||
for col in LEDGER_COLUMNS:
|
||||
target_col = None
|
||||
for table_col in existed_table['columns']:
|
||||
if col['column_name'] == table_col['name']:
|
||||
target_col = table_col
|
||||
break
|
||||
if not target_col:
|
||||
logging.error('Column %s not found', col['column_name'])
|
||||
exit(1)
|
||||
if target_col['type'] != col['column_type']:
|
||||
logging.error('Column %s type should be %s', col['column_name'], col['column_type'])
|
||||
exit(1)
|
||||
else:
|
||||
# add table
|
||||
url = f"{dtable_server_url.strip('/')}/api/v1/dtables/{dtable_uuid}/tables/?from=dtable_web"
|
||||
data = {
|
||||
'table_name': EXTENDED_PROPS_TABLE_NAME,
|
||||
'columns': LEDGER_COLUMNS
|
||||
}
|
||||
resp = requests.post(url, headers=headers, json=data)
|
@@ -1,539 +0,0 @@
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import stat
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from threading import Lock
|
||||
from uuid import uuid4
|
||||
|
||||
import requests
|
||||
from rest_framework import status
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from seaserv import seafile_api
|
||||
|
||||
from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.api2.utils import api_error
|
||||
from seahub.base.templatetags.seahub_tags import email2nickname
|
||||
from seahub.settings import DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN, \
|
||||
EX_PROPS_TABLE, EX_EDITABLE_COLUMNS
|
||||
from seahub.tags.models import FileUUIDMap
|
||||
from seahub.utils import normalize_file_path, EMPTY_SHA1
|
||||
from seahub.utils.repo import parse_repo_perm
|
||||
from seahub.utils.seatable_api import SeaTableAPI
|
||||
from seahub.views import check_folder_permission
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QueryException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def check_table(seatable_api: SeaTableAPI):
|
||||
"""check EX_PROPS_TABLE is invalid or not
|
||||
|
||||
:return: error_msg -> str or None
|
||||
"""
|
||||
table = seatable_api.get_table_by_name(EX_PROPS_TABLE)
|
||||
if not table:
|
||||
return 'Table %s not found' % EX_PROPS_TABLE
|
||||
return None
|
||||
|
||||
|
||||
class ExtendedPropertiesView(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def post(self, request, repo_id):
|
||||
if not all((DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN, EX_PROPS_TABLE)):
|
||||
return api_error(status.HTTP_403_FORBIDDEN, 'Feature not enabled')
|
||||
# arguments check
|
||||
path = request.data.get('path')
|
||||
if not path:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'path invalid')
|
||||
path = normalize_file_path(path)
|
||||
parent_dir = os.path.dirname(path)
|
||||
dirent_name = os.path.basename(path)
|
||||
props_data_str = request.data.get('props_data')
|
||||
if not props_data_str or not isinstance(props_data_str, str):
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'props_data invalid')
|
||||
|
||||
try:
|
||||
props_data = json.loads(props_data_str)
|
||||
except:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'props_data invalid')
|
||||
|
||||
# resource check
|
||||
repo = seafile_api.get_repo(repo_id)
|
||||
if not repo:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'Library not found')
|
||||
dirent = seafile_api.get_dirent_by_path(repo_id, path)
|
||||
if not dirent:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'File or folder %s not found' % path)
|
||||
if not stat.S_ISDIR(dirent.mode) and dirent.obj_id == EMPTY_SHA1:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'File or folder %s is empty' % path)
|
||||
|
||||
# permission check
|
||||
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web:
|
||||
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||
|
||||
# check base
|
||||
try:
|
||||
seatable_api = SeaTableAPI(SEATABLE_EX_PROPS_BASE_API_TOKEN, DTABLE_WEB_SERVER)
|
||||
except:
|
||||
logger.error('server: %s token: %s seatable-api fail', DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN)
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'Props table invalid')
|
||||
## props table
|
||||
try:
|
||||
error_msg = check_table(seatable_api)
|
||||
except Exception as e:
|
||||
logger.exception('check ex-props table %s error: %s', EX_PROPS_TABLE, e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
if error_msg:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'Props table invalid: %s' % error_msg)
|
||||
## check existed props row
|
||||
file_uuid = None
|
||||
if not stat.S_ISDIR(dirent.mode):
|
||||
file_uuid = FileUUIDMap.objects.get_or_create_fileuuidmap(repo_id, parent_dir, dirent_name, False).uuid.hex
|
||||
sql = f"SELECT COUNT(1) as `count` FROM `{EX_PROPS_TABLE}` WHERE `Repo ID`='{repo_id}' AND `Path`='{path}'"
|
||||
result = seatable_api.query(sql)
|
||||
count = result['results'][0]['count']
|
||||
if count > 0:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'The props of the file exists')
|
||||
## append props row
|
||||
props_data = {column_name: value for column_name, value in props_data.items() if column_name in EX_EDITABLE_COLUMNS}
|
||||
props_data.update({
|
||||
'Repo ID': repo_id,
|
||||
'File': dirent_name,
|
||||
'Path': path,
|
||||
'UUID': file_uuid,
|
||||
'创建日期': str(datetime.fromtimestamp(dirent.mtime)),
|
||||
'文件负责人': email2nickname(request.user.username)
|
||||
})
|
||||
try:
|
||||
seatable_api.append_row(EX_PROPS_TABLE, props_data)
|
||||
except Exception as e:
|
||||
logger.error('update props table error: %s', e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
## query
|
||||
sql = f"SELECT * FROM {EX_PROPS_TABLE} WHERE `Repo ID`='{repo_id}' AND `Path`='{path}'"
|
||||
try:
|
||||
result = seatable_api.query(sql)
|
||||
except Exception as e:
|
||||
logger.exception('query sql: %s error: %s', sql, e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
rows = result.get('results')
|
||||
row = rows[0] if rows else {}
|
||||
return Response({
|
||||
'row': row,
|
||||
'metadata': result['metadata'],
|
||||
'editable_columns': EX_EDITABLE_COLUMNS
|
||||
})
|
||||
|
||||
def get(self, request, repo_id):
|
||||
if not all((DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN, EX_PROPS_TABLE)):
|
||||
return api_error(status.HTTP_403_FORBIDDEN, 'Feature not enabled')
|
||||
# arguments check
|
||||
path = request.GET.get('path')
|
||||
if not path:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'path invalid')
|
||||
path = normalize_file_path(path)
|
||||
parent_dir = os.path.dirname(path)
|
||||
|
||||
# resource check
|
||||
repo = seafile_api.get_repo(repo_id)
|
||||
if not repo:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'Library not found')
|
||||
dirent = seafile_api.get_dirent_by_path(repo_id, path)
|
||||
if not dirent:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'File or folder %s not found' % path)
|
||||
|
||||
# permission check
|
||||
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web:
|
||||
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||
|
||||
# check base
|
||||
try:
|
||||
seatable_api = SeaTableAPI(SEATABLE_EX_PROPS_BASE_API_TOKEN, DTABLE_WEB_SERVER)
|
||||
except:
|
||||
logger.error('server: %s token: %s seatable-api fail', DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN)
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'Props table invalid')
|
||||
## props table
|
||||
try:
|
||||
error_msg = check_table(seatable_api)
|
||||
except Exception as e:
|
||||
logger.exception('check ex-props table %s error: %s', EX_PROPS_TABLE, e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
if error_msg:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'Props table invalid: %s' % error_msg)
|
||||
## query
|
||||
dirent_name = os.path.basename(path)
|
||||
file_uuid = None
|
||||
if not stat.S_ISDIR(dirent.mode):
|
||||
file_uuid = FileUUIDMap.objects.get_or_create_fileuuidmap(repo_id, parent_dir, dirent_name, False).uuid.hex
|
||||
sql = f"SELECT * FROM `{EX_PROPS_TABLE}` WHERE `Repo ID`='{repo_id}' AND `Path`='{path}'"
|
||||
try:
|
||||
result = seatable_api.query(sql)
|
||||
except Exception as e:
|
||||
logger.exception('query sql: %s error: %s', sql, e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
rows = result.get('results')
|
||||
if rows:
|
||||
row = rows[0]
|
||||
else:
|
||||
row = {
|
||||
'Repo ID': repo_id,
|
||||
'File': dirent_name,
|
||||
'Path': path,
|
||||
'UUID': file_uuid
|
||||
}
|
||||
for name in ['Repo ID', 'File', 'Path', 'UUID']:
|
||||
for column in result['metadata']:
|
||||
if name == column['name']:
|
||||
row[column['key']] = row[name]
|
||||
row.pop(name, None)
|
||||
break
|
||||
return Response({
|
||||
'row': row,
|
||||
'metadata': result['metadata'],
|
||||
'editable_columns': EX_EDITABLE_COLUMNS
|
||||
})
|
||||
|
||||
def put(self, request, repo_id):
|
||||
if not all((DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN, EX_PROPS_TABLE)):
|
||||
return api_error(status.HTTP_403_FORBIDDEN, 'Feature not enabled')
|
||||
# arguments check
|
||||
path = request.data.get('path')
|
||||
if not path:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'path invalid')
|
||||
path = normalize_file_path(path)
|
||||
parent_dir = os.path.dirname(path)
|
||||
props_data_str = request.data.get('props_data')
|
||||
if not props_data_str or not isinstance(props_data_str, str):
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'props_data invalid')
|
||||
|
||||
try:
|
||||
props_data = json.loads(props_data_str)
|
||||
except:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'props_data invalid')
|
||||
|
||||
# resource check
|
||||
repo = seafile_api.get_repo(repo_id)
|
||||
if not repo:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'Library not found')
|
||||
dirent = seafile_api.get_dirent_by_path(repo_id, path)
|
||||
if not dirent:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'File or folder %s not found' % path)
|
||||
|
||||
# permission check
|
||||
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web:
|
||||
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||
|
||||
# check base
|
||||
try:
|
||||
seatable_api = SeaTableAPI(SEATABLE_EX_PROPS_BASE_API_TOKEN, DTABLE_WEB_SERVER)
|
||||
except:
|
||||
logger.error('server: %s token: %s seatable-api fail', DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN)
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'Props table invalid')
|
||||
## props table
|
||||
try:
|
||||
error_msg = check_table(seatable_api)
|
||||
except Exception as e:
|
||||
logger.exception('check ex-props table %s error: %s', EX_PROPS_TABLE, e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
if error_msg:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'Props table invalid: %s' % error_msg)
|
||||
## check existed props row
|
||||
sql = f"SELECT * FROM `{EX_PROPS_TABLE}` WHERE `Repo ID`='{repo_id}' AND `Path`='{path}'"
|
||||
result = seatable_api.query(sql)
|
||||
results = result['results']
|
||||
if not results:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'The props of the file or folder not found')
|
||||
row_id = results[0]['_id']
|
||||
## update props row
|
||||
props_data = {col_name: value for col_name, value in props_data.items() if col_name in EX_EDITABLE_COLUMNS}
|
||||
try:
|
||||
seatable_api.update_row(EX_PROPS_TABLE, row_id, props_data)
|
||||
except Exception as e:
|
||||
logger.error('update props table error: %s', e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
## query
|
||||
sql = f"SELECT * FROM `{EX_PROPS_TABLE}` WHERE `Repo ID`='{repo_id}' AND `Path`='{path}'"
|
||||
try:
|
||||
result = seatable_api.query(sql)
|
||||
except Exception as e:
|
||||
logger.exception('query sql: %s error: %s', sql, e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
rows = result.get('results')
|
||||
row = rows[0] if rows else {}
|
||||
return Response({
|
||||
'row': row,
|
||||
'metadata': result['metadata'],
|
||||
'editable_columns': EX_EDITABLE_COLUMNS
|
||||
})
|
||||
|
||||
def delete(self, request, repo_id):
|
||||
if not all((DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN, EX_PROPS_TABLE)):
|
||||
return api_error(status.HTTP_403_FORBIDDEN, 'Feature not enabled')
|
||||
# arguments check
|
||||
path = request.GET.get('path')
|
||||
if not path:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'path invalid')
|
||||
path = normalize_file_path(path)
|
||||
parent_dir = os.path.dirname(path)
|
||||
|
||||
# resource check
|
||||
repo = seafile_api.get_repo(repo_id)
|
||||
if not repo:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'Library not found')
|
||||
dirent = seafile_api.get_dirent_by_path(repo_id, path)
|
||||
if not dirent:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'File or folder %s not found' % path)
|
||||
|
||||
# permission check
|
||||
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web:
|
||||
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||
|
||||
# check base
|
||||
try:
|
||||
seatable_api = SeaTableAPI(SEATABLE_EX_PROPS_BASE_API_TOKEN, DTABLE_WEB_SERVER)
|
||||
except:
|
||||
logger.error('server: %s token: %s seatable-api fail', DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN)
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'Props table invalid')
|
||||
## props table
|
||||
try:
|
||||
error_msg = check_table(seatable_api)
|
||||
except Exception as e:
|
||||
logger.exception('check ex-props table %s error: %s', EX_PROPS_TABLE, e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
if error_msg:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'Props table invalid: %s' % error_msg)
|
||||
sql = f"DELETE FROM `{EX_PROPS_TABLE}` WHERE `Repo ID`='{repo_id}' AND `Path`='{path}'"
|
||||
try:
|
||||
seatable_api.query(sql)
|
||||
except Exception as e:
|
||||
logger.exception('delete props record error: %s', e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
return Response({'success': True})
|
||||
|
||||
|
||||
class ApplyFolderExtendedPropertiesView(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
list_max = 1000
|
||||
step = 500
|
||||
|
||||
def md5_repo_id_parent_path(self, repo_id, parent_path):
|
||||
parent_path = parent_path.rstrip('/') if parent_path != '/' else '/'
|
||||
return hashlib.md5((repo_id + parent_path).encode('utf-8')).hexdigest()
|
||||
|
||||
def query_fileuuids_map(self, repo_id, file_paths):
|
||||
"""
|
||||
:return: {file_path: fileuuid}
|
||||
"""
|
||||
file_path_2_uuid_map = {}
|
||||
no_uuid_file_paths = []
|
||||
|
||||
# query uuids
|
||||
for i in range(0, len(file_paths), self.step):
|
||||
parent_path_2_filenames_map = defaultdict(list)
|
||||
for file_path in file_paths[i: i+self.step]:
|
||||
parent_path, filename = os.path.split(file_path)
|
||||
parent_path_2_filenames_map[parent_path].append(filename)
|
||||
for parent_path, filenames in parent_path_2_filenames_map.items():
|
||||
md5 = self.md5_repo_id_parent_path(repo_id, parent_path)
|
||||
results = FileUUIDMap.objects.filter(repo_id=repo_id, repo_id_parent_path_md5=md5, filename__in=filenames)
|
||||
for uuid_item in results:
|
||||
file_path_2_uuid_map[os.path.join(parent_path, uuid_item.filename)] = uuid_item.uuid.hex
|
||||
## some filename no uuids
|
||||
for filename in filenames:
|
||||
cur_file_path = os.path.join(parent_path, filename)
|
||||
if cur_file_path not in file_path_2_uuid_map:
|
||||
no_uuid_file_paths.append({'file_path': cur_file_path, 'uuid': uuid4().hex, 'repo_id_parent_path_md5': md5})
|
||||
# create uuids
|
||||
for i in range(0, len(no_uuid_file_paths), self.step):
|
||||
uuid_objs = []
|
||||
for j in range(i, min(i+self.step, len(no_uuid_file_paths))):
|
||||
no_uuid_file_path = no_uuid_file_paths[j]
|
||||
kwargs = {
|
||||
'uuid': no_uuid_file_path['uuid'],
|
||||
'repo_id': repo_id,
|
||||
'repo_id_parent_path_md5': no_uuid_file_path['repo_id_parent_path_md5'],
|
||||
'parent_path': os.path.dirname(no_uuid_file_path['file_path']),
|
||||
'filename': os.path.basename(no_uuid_file_path['file_path']),
|
||||
'is_dir': 0
|
||||
}
|
||||
uuid_objs.append(FileUUIDMap(**kwargs))
|
||||
FileUUIDMap.objects.bulk_create(uuid_objs)
|
||||
for j in range(i, min(i+self.step, len(no_uuid_file_paths))):
|
||||
file_path_2_uuid_map[no_uuid_file_paths[j]['file_path']] = no_uuid_file_paths[j]['uuid']
|
||||
|
||||
return file_path_2_uuid_map
|
||||
|
||||
def query_path_2_row_id_map(self, repo_id, query_list, seatable_api: SeaTableAPI):
|
||||
"""
|
||||
:return: path_2_row_id_map -> {path: row_id}
|
||||
"""
|
||||
path_2_row_id_map = {}
|
||||
for i in range(0, len(query_list), self.step):
|
||||
paths_str = ', '.join(map(lambda x: f"'{x['path']}'", query_list[i: i+self.step]))
|
||||
sql = f"SELECT `_id`, `Path` FROM `{EX_PROPS_TABLE}` WHERE `Repo ID`='{repo_id}' AND `Path` IN ({paths_str})"
|
||||
resp_json = seatable_api.query(sql, convert=True)
|
||||
rows = resp_json['results']
|
||||
path_2_row_id_map.update({row['Path']: row['_id'] for row in rows})
|
||||
return path_2_row_id_map
|
||||
|
||||
def query_ex_props_by_path(self, repo_id, path, seatable_api: SeaTableAPI):
|
||||
columns_str = ', '.join(map(lambda x: f"`{x}`", EX_EDITABLE_COLUMNS))
|
||||
sql = f"SELECT {columns_str} FROM `{EX_PROPS_TABLE}` WHERE `Repo ID` = '{repo_id}' AND `Path` = '{path}'"
|
||||
resp_json = seatable_api.query(sql, convert=True)
|
||||
if not resp_json['results']:
|
||||
return None
|
||||
row = resp_json['results'][0]
|
||||
return row
|
||||
|
||||
def update_ex_props(self, update_list, ex_props, seatable_api: SeaTableAPI):
|
||||
for i in range(0, len(update_list), self.step):
|
||||
updates = []
|
||||
for j in range(i, min(len(update_list), i+self.step)):
|
||||
updates.append({
|
||||
'row_id': update_list[j]['row_id'],
|
||||
'row': ex_props
|
||||
})
|
||||
seatable_api.update_rows_by_dtable_db(EX_PROPS_TABLE, updates)
|
||||
|
||||
def insert_ex_props(self, repo_id, insert_list, ex_props, context, seatable_api: SeaTableAPI):
|
||||
for i in range(0, len(insert_list), self.step):
|
||||
rows = []
|
||||
for j in range(i, min(len(insert_list), i+self.step)):
|
||||
row = {
|
||||
'Repo ID': repo_id,
|
||||
'File': os.path.basename(insert_list[j]['path']),
|
||||
'UUID': insert_list[j].get('fileuuid'),
|
||||
'Path': insert_list[j]['path'],
|
||||
'创建日期': str(datetime.fromtimestamp(insert_list[j]['mtime'])),
|
||||
'文件负责人': context['文件负责人']
|
||||
}
|
||||
row.update(ex_props)
|
||||
rows.append(row)
|
||||
seatable_api.batch_append_rows(EX_PROPS_TABLE, rows)
|
||||
|
||||
def apply_folder(self, repo_id, folder_path, context, seatable_api: SeaTableAPI, folder_props):
|
||||
stack = [folder_path]
|
||||
|
||||
query_list = [] # [{path, type}]
|
||||
file_query_list = [] # [path]
|
||||
|
||||
update_list = [] # [{}]
|
||||
insert_list = [] # [{}]
|
||||
|
||||
# query folder props
|
||||
while stack:
|
||||
current_path = stack.pop()
|
||||
dirents = seafile_api.list_dir_by_path(repo_id, current_path)
|
||||
if not dirents:
|
||||
continue
|
||||
for dirent in dirents:
|
||||
dirent_path = os.path.join(current_path, dirent.obj_name)
|
||||
if stat.S_ISDIR(dirent.mode):
|
||||
query_list.append({'path': dirent_path, 'type': 'dir', 'mtime': dirent.mtime})
|
||||
stack.append(dirent_path)
|
||||
else:
|
||||
if dirent.obj_id == EMPTY_SHA1:
|
||||
continue
|
||||
query_list.append({'path': dirent_path, 'type': 'file', 'mtime': dirent.mtime})
|
||||
file_query_list.append(dirent_path)
|
||||
# query ex-props
|
||||
if len(query_list) >= self.list_max:
|
||||
file_path_2_uuid_map = self.query_fileuuids_map(repo_id, file_query_list)
|
||||
path_2_row_id_map = self.query_path_2_row_id_map(repo_id, query_list, seatable_api)
|
||||
for query_item in query_list:
|
||||
if query_item['path'] in path_2_row_id_map:
|
||||
query_item['row_id'] = path_2_row_id_map.get(query_item['path'])
|
||||
update_list.append(query_item)
|
||||
else:
|
||||
if query_item['type'] == 'file':
|
||||
query_item['fileuuid'] = file_path_2_uuid_map.get(query_item['path'])
|
||||
insert_list.append(query_item)
|
||||
query_list = file_query_list = []
|
||||
# update ex-props
|
||||
if len(update_list) >= self.list_max:
|
||||
self.update_ex_props(update_list, folder_props, seatable_api)
|
||||
update_list = []
|
||||
# insert ex-props
|
||||
if len(insert_list) >= self.list_max:
|
||||
self.insert_ex_props(repo_id, insert_list, folder_props, context, seatable_api)
|
||||
insert_list = []
|
||||
|
||||
# handle query/update/insert left
|
||||
file_path_2_uuid_map = self.query_fileuuids_map(repo_id, file_query_list)
|
||||
path_2_row_id_map = self.query_path_2_row_id_map(repo_id, query_list, seatable_api)
|
||||
for query_item in query_list:
|
||||
if query_item['path'] in path_2_row_id_map:
|
||||
query_item['row_id'] = path_2_row_id_map.get(query_item['path'])
|
||||
update_list.append(query_item)
|
||||
else:
|
||||
if query_item['type'] == 'file':
|
||||
query_item['fileuuid'] = file_path_2_uuid_map.get(query_item['path'])
|
||||
insert_list.append(query_item)
|
||||
self.update_ex_props(update_list, folder_props, seatable_api)
|
||||
self.insert_ex_props(repo_id, insert_list, folder_props, context, seatable_api)
|
||||
|
||||
def post(self, request, repo_id):
|
||||
if not all((DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN, EX_PROPS_TABLE)):
|
||||
return api_error(status.HTTP_403_FORBIDDEN, 'Feature not enabled')
|
||||
# arguments check
|
||||
path = request.data.get('path')
|
||||
path = normalize_file_path(path)
|
||||
if not path:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'path invalid')
|
||||
parent_dir = os.path.dirname(path)
|
||||
|
||||
dirent = seafile_api.get_dirent_by_path(repo_id, path)
|
||||
if not dirent:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'Folder %s not found' % path)
|
||||
if not stat.S_ISDIR(dirent.mode):
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, '%s is not a folder' % path)
|
||||
|
||||
# permission check
|
||||
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web:
|
||||
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||
|
||||
# request props from seatable
|
||||
try:
|
||||
seatable_api = SeaTableAPI(SEATABLE_EX_PROPS_BASE_API_TOKEN, DTABLE_WEB_SERVER)
|
||||
except:
|
||||
logger.error('server: %s token: %s seatable-api fail', DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN)
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'Props base invalid')
|
||||
|
||||
try:
|
||||
folder_props = self.query_ex_props_by_path(repo_id, path, seatable_api)
|
||||
if not folder_props:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'The folder is not be set extended properties')
|
||||
except Exception as e:
|
||||
logger.exception('query repo: %s folder: %s ex-props error: %s', repo_id, path, e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
# apply props
|
||||
context = {'文件负责人': email2nickname(request.user.username)}
|
||||
try:
|
||||
self.apply_folder(repo_id, path, context, seatable_api, folder_props)
|
||||
except QueryException as e:
|
||||
logger.exception('apply folder: %s ex-props query dtable-db error: %s', path, e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
except Exception as e:
|
||||
logger.exception('apply folder: %s ex-props error: %s', path, e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
return Response({'success': True})
|
@@ -9,7 +9,7 @@ from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.repo_metadata.models import RepoMetadata
|
||||
from seahub.views import check_folder_permission
|
||||
from seahub.repo_metadata.utils import add_init_metadata_task
|
||||
from seahub.repo_metadata.utils import add_init_metadata_task, gen_unique_id
|
||||
from seahub.repo_metadata.metadata_server_api import MetadataServerAPI, list_metadata_records
|
||||
|
||||
from seaserv import seafile_api
|
||||
@@ -132,7 +132,7 @@ class MetadataManage(APIView):
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
return Response({'success': True})
|
||||
|
||||
|
||||
class MetadataRecords(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated, )
|
||||
@@ -210,3 +210,207 @@ class MetadataRecords(APIView):
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
return Response(results)
|
||||
|
||||
|
||||
class MetadataRecordInfo(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def get(self, request, repo_id):
|
||||
parent_dir = request.GET.get('parent_dir')
|
||||
name = request.GET.get('name')
|
||||
if not parent_dir:
|
||||
error_msg = 'parent_dir invalid'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if not name:
|
||||
error_msg = 'name invalid'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
record = RepoMetadata.objects.filter(repo_id=repo_id).first()
|
||||
if not record or not record.enabled:
|
||||
error_msg = f'The metadata module is disabled for repo {repo_id}.'
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
permission = check_folder_permission(request, repo_id, '/')
|
||||
if permission != 'rw':
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
repo = seafile_api.get_repo(repo_id)
|
||||
if not repo:
|
||||
error_msg = 'Library %s not found.' % repo_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
metadata_server_api = MetadataServerAPI(repo_id, request.user.username)
|
||||
|
||||
from seafevents.repo_metadata.metadata_server_api import METADATA_TABLE
|
||||
|
||||
sql = f'SELECT * FROM `{METADATA_TABLE.name}` WHERE \
|
||||
`{METADATA_TABLE.columns.parent_dir.name}`=? AND `{METADATA_TABLE.columns.file_name.name}`=?;'
|
||||
parameters = [parent_dir, name]
|
||||
|
||||
try:
|
||||
query_result = metadata_server_api.query_rows(sql, parameters)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
sys_columns = [
|
||||
METADATA_TABLE.columns.id.key,
|
||||
METADATA_TABLE.columns.file_creator.key,
|
||||
METADATA_TABLE.columns.file_ctime.key,
|
||||
METADATA_TABLE.columns.file_modifier.key,
|
||||
METADATA_TABLE.columns.file_mtime.key,
|
||||
METADATA_TABLE.columns.parent_dir.key,
|
||||
METADATA_TABLE.columns.file_name.key,
|
||||
METADATA_TABLE.columns.is_dir.key,
|
||||
]
|
||||
|
||||
rows = query_result.get('results')
|
||||
|
||||
if not rows:
|
||||
error_msg = 'Record not found'
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
metadata = query_result.get('metadata')
|
||||
editable_columns = []
|
||||
name_to_key = {}
|
||||
for col in metadata:
|
||||
col_key = col.get('key')
|
||||
col_name = col.get('name')
|
||||
name_to_key[col_name] = col_key
|
||||
if col_key in sys_columns:
|
||||
continue
|
||||
editable_columns.append(col.get('name'))
|
||||
|
||||
row = {name_to_key[name]: value for name, value in rows[0].items()}
|
||||
query_result['row'] = row
|
||||
query_result['editable_columns'] = editable_columns
|
||||
|
||||
query_result.pop('results', None)
|
||||
|
||||
return Response(query_result)
|
||||
|
||||
|
||||
class MetadataRecord(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def put(self, request, repo_id, record_id):
|
||||
column_name = request.data.get('column_name')
|
||||
new_value = request.data.get('value')
|
||||
|
||||
if not column_name:
|
||||
error_msg = 'column_name invalid'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if not new_value:
|
||||
error_msg = 'value invalid'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
record = RepoMetadata.objects.filter(repo_id=repo_id).first()
|
||||
if not record or not record.enabled:
|
||||
error_msg = f'The metadata module is disabled for repo {repo_id}.'
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
permission = check_folder_permission(request, repo_id, '/')
|
||||
if permission != 'rw':
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
repo = seafile_api.get_repo(repo_id)
|
||||
if not repo:
|
||||
error_msg = 'Library %s not found.' % repo_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
metadata_server_api = MetadataServerAPI(repo_id, request.user.username)
|
||||
|
||||
from seafevents.repo_metadata.metadata_server_api import METADATA_TABLE
|
||||
|
||||
sys_column_names = [
|
||||
METADATA_TABLE.columns.id.name,
|
||||
METADATA_TABLE.columns.file_creator.name,
|
||||
METADATA_TABLE.columns.file_ctime.name,
|
||||
METADATA_TABLE.columns.file_modifier.name,
|
||||
METADATA_TABLE.columns.file_mtime.name,
|
||||
METADATA_TABLE.columns.parent_dir.name,
|
||||
METADATA_TABLE.columns.file_name.name,
|
||||
METADATA_TABLE.columns.is_dir.name,
|
||||
]
|
||||
|
||||
if column_name in sys_column_names:
|
||||
error_msg = 'column_name invalid'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
row = {
|
||||
METADATA_TABLE.columns.id.name: record_id,
|
||||
column_name: new_value
|
||||
}
|
||||
|
||||
try:
|
||||
metadata_server_api.update_rows(METADATA_TABLE.id, [row])
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
return Response({'success': True})
|
||||
|
||||
|
||||
class MetadataColumns(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def post(self, request, repo_id):
|
||||
column_name = request.data.get('column_name')
|
||||
column_type = request.data.get('column_type', 'text')
|
||||
if not column_name:
|
||||
error_msg = 'column_name invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
record = RepoMetadata.objects.filter(repo_id=repo_id).first()
|
||||
if not record or not record.enabled:
|
||||
error_msg = f'The metadata module is disabled for repo {repo_id}.'
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
repo = seafile_api.get_repo(repo_id)
|
||||
if not repo:
|
||||
error_msg = 'Library %s not found.' % repo_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
permission = check_folder_permission(request, repo_id, '/')
|
||||
if permission != 'rw':
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
metadata_server_api = MetadataServerAPI(repo_id, request.user.username)
|
||||
|
||||
from seafevents.repo_metadata.metadata_server_api import METADATA_TABLE, MetadataColumn
|
||||
columns = metadata_server_api.list_columns(METADATA_TABLE.id).get('columns')
|
||||
column_keys = set()
|
||||
column_names = set()
|
||||
|
||||
for column in columns:
|
||||
column_keys.add(column.get('key'))
|
||||
column_names.add(column.get('name'))
|
||||
|
||||
if column_name in column_names:
|
||||
error_msg = 'column_name duplicated.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
column_key = gen_unique_id(column_keys)
|
||||
column = MetadataColumn(column_key, column_name, column_type)
|
||||
|
||||
try:
|
||||
metadata_server_api.add_column(METADATA_TABLE.id, column.to_dict())
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
return Response({'success': True})
|
||||
|
@@ -136,3 +136,11 @@ class MetadataServerAPI:
|
||||
url = f'{METADATA_SERVER_URL}/api/v1/base/{self.base_id}/query'
|
||||
response = requests.post(url, json=post_data, headers=self.headers, timeout=self.timeout)
|
||||
return parse_response(response)
|
||||
|
||||
def list_columns(self, table_id):
|
||||
url = f'{METADATA_SERVER_URL}/api/v1/base/{self.base_id}/columns'
|
||||
data = {
|
||||
'table_id': table_id
|
||||
}
|
||||
response = requests.get(url, json=data, headers=self.headers, timeout=self.timeout)
|
||||
return parse_response(response)
|
||||
|
@@ -2,6 +2,7 @@ import jwt
|
||||
import time
|
||||
import requests
|
||||
import json
|
||||
import random
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from seahub.settings import SECRET_KEY, SEAFEVENTS_SERVER_URL
|
||||
@@ -14,3 +15,18 @@ def add_init_metadata_task(params):
|
||||
url = urljoin(SEAFEVENTS_SERVER_URL, '/add-init-metadata-task')
|
||||
resp = requests.get(url, params=params, headers=headers)
|
||||
return json.loads(resp.content)['task_id']
|
||||
|
||||
|
||||
def generator_base64_code(length=4):
|
||||
possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123456789'
|
||||
ids = random.sample(possible, length)
|
||||
return ''.join(ids)
|
||||
|
||||
|
||||
def gen_unique_id(id_set, length=4):
|
||||
_id = generator_base64_code(length)
|
||||
|
||||
while True:
|
||||
if _id not in id_set:
|
||||
return _id
|
||||
_id = generator_base64_code(length)
|
||||
|
@@ -886,13 +886,6 @@ if os.environ.get('SEAFILE_DOCS', None):
|
||||
LOGO_WIDTH = ''
|
||||
ENABLE_WIKI = True
|
||||
|
||||
#######################
|
||||
# extended properties #
|
||||
#######################
|
||||
SEATABLE_EX_PROPS_BASE_API_TOKEN = ''
|
||||
EX_PROPS_TABLE = ''
|
||||
EX_EDITABLE_COLUMNS = []
|
||||
|
||||
##############################
|
||||
# metadata server properties #
|
||||
##############################
|
||||
|
@@ -150,7 +150,6 @@
|
||||
onlyofficeConverterExtensions: {% if onlyofficeConverterExtensions %} {{onlyofficeConverterExtensions|safe}} {% else %} null {% endif %},
|
||||
enableSeadoc: {% if enable_seadoc %} true {% else %} false {% endif %},
|
||||
enableSeafileAI: {% if enable_seafile_ai %} true {% else %} false {% endif %},
|
||||
canSetExProps: {% if can_set_ex_props %} true {% else %} false {% endif %},
|
||||
enableSeaTableIntegration: {% if enable_seatable_integration %} true {% else %} false {% endif %},
|
||||
isOrgContext: {% if org is not None %} true {% else %} false {% endif %},
|
||||
enableMetadataManagement: {% if enable_metadata_management %} true {% else %} false {% endif %},
|
||||
|
@@ -120,8 +120,6 @@ from seahub.ocm_via_webdav.ocm_api import OCMProviderView
|
||||
from seahub.api2.endpoints.repo_share_links import RepoShareLinks, RepoShareLink
|
||||
from seahub.api2.endpoints.repo_upload_links import RepoUploadLinks, RepoUploadLink
|
||||
|
||||
from seahub.api2.endpoints.extended_properties import ExtendedPropertiesView, ApplyFolderExtendedPropertiesView
|
||||
|
||||
# Admin
|
||||
from seahub.api2.endpoints.admin.abuse_reports import AdminAbuseReportsView, AdminAbuseReportView
|
||||
from seahub.api2.endpoints.admin.revision_tag import AdminTaggedItemsView
|
||||
@@ -206,7 +204,7 @@ from seahub.ai.apis import LibrarySdocIndexes, Search, LibrarySdocIndex, TaskSta
|
||||
from seahub.wiki2.views import wiki_view
|
||||
from seahub.api2.endpoints.wiki2 import Wikis2View, Wiki2View, Wiki2ConfigView, Wiki2PagesView, Wiki2PageView, Wiki2DuplicatePageView
|
||||
from seahub.api2.endpoints.subscription import SubscriptionView, SubscriptionPlansView, SubscriptionLogsView
|
||||
from seahub.api2.endpoints.metadata_manage import MetadataRecords, MetadataManage
|
||||
from seahub.api2.endpoints.metadata_manage import MetadataRecords, MetadataManage, MetadataRecord, MetadataColumns, MetadataRecordInfo
|
||||
from seahub.api2.endpoints.user_list import UserListView
|
||||
|
||||
|
||||
@@ -444,10 +442,6 @@ urlpatterns = [
|
||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/file/participant/$', FileParticipantView.as_view(), name='api-v2.1-file-participant'),
|
||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/related-users/$', RepoRelatedUsersView.as_view(), name='api-v2.1-related-user'),
|
||||
|
||||
## user:file:extended-props
|
||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/extended-properties/$', ExtendedPropertiesView.as_view(), name='api-v2.1-extended-properties'),
|
||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/apply-folder-extended-properties/$', ApplyFolderExtendedPropertiesView.as_view(), name='api-v2.1-apply-folder-extended-properties'),
|
||||
|
||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/auto-delete/$', RepoAutoDeleteView.as_view(), name='api-v2.1-repo-auto-delete'),
|
||||
|
||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/share-links/$', RepoShareLinks.as_view(), name='api-v2.1-repo-share-links'),
|
||||
@@ -1036,4 +1030,8 @@ if settings.ENABLE_METADATA_MANAGEMENT:
|
||||
urlpatterns += [
|
||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/$', MetadataManage.as_view(), name='api-v2.1-metadata'),
|
||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/records/$', MetadataRecords.as_view(), name='api-v2.1-metadata-records'),
|
||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/records/(?P<record_id>[A-Za-z0-9_-]+)/$', MetadataRecord.as_view(), name='api-v2.1-metadata-record'),
|
||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/record/$', MetadataRecordInfo.as_view(), name='api-v2.1-metadata-record-info'),
|
||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/columns/$', MetadataColumns.as_view(), name='api-v2.1-metadata-columns'),
|
||||
|
||||
]
|
||||
|
@@ -1,146 +0,0 @@
|
||||
import requests
|
||||
|
||||
|
||||
class ColumnTypes:
|
||||
COLLABORATOR = 'collaborator'
|
||||
NUMBER = 'number'
|
||||
DATE = 'date'
|
||||
GEOLOCATION = 'geolocation'
|
||||
CREATOR = 'creator'
|
||||
LAST_MODIFIER = 'last-modifier'
|
||||
TEXT = 'text'
|
||||
IMAGE = 'image'
|
||||
LONG_TEXT = 'long-text'
|
||||
CHECKBOX = 'checkbox'
|
||||
SINGLE_SELECT = 'single-select'
|
||||
MULTIPLE_SELECT = 'multiple-select'
|
||||
URL = 'url'
|
||||
DURATION = 'duration'
|
||||
FILE = 'file'
|
||||
EMAIL = 'email'
|
||||
RATE = 'rate'
|
||||
FORMULA = 'formula'
|
||||
LINK_FORMULA = 'link-formula'
|
||||
AUTO_NUMBER = 'auto-number'
|
||||
LINK = 'link'
|
||||
CTIME = 'ctime'
|
||||
MTIME = 'mtime'
|
||||
BUTTON = 'button'
|
||||
DIGITAL_SIGN = 'digital-sign'
|
||||
|
||||
|
||||
def parse_response(response):
|
||||
if response.status_code >= 400:
|
||||
raise ConnectionError(response.status_code, response.text)
|
||||
else:
|
||||
try:
|
||||
return response.json()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class SeaTableAPI:
|
||||
|
||||
def __init__(self, api_token, server_url):
|
||||
self.api_token = api_token
|
||||
self.server_url = server_url
|
||||
self.dtable_uuid = None
|
||||
self.access_token = None
|
||||
self.dtable_server_url = None
|
||||
self.dtable_db_url = None
|
||||
self.headers = None
|
||||
self.auth()
|
||||
|
||||
def auth(self):
|
||||
url = f"{self.server_url.strip('/')}/api/v2.1/dtable/app-access-token/?from=dtable_web"
|
||||
resp = requests.get(url, headers={'Authorization': f'Token {self.api_token}'})
|
||||
self.dtable_uuid = resp.json()['dtable_uuid']
|
||||
self.access_token = resp.json()['access_token']
|
||||
self.dtable_server_url = resp.json()['dtable_server']
|
||||
self.dtable_db_url = resp.json()['dtable_db']
|
||||
self.headers = {'Authorization': f'Token {self.access_token}'}
|
||||
|
||||
def get_metadata(self):
|
||||
url = f"{self.dtable_server_url.strip('/')}/api/v1/dtables/{self.dtable_uuid}/metadata/?from=dtable_web"
|
||||
resp = requests.get(url, headers=self.headers)
|
||||
return parse_response(resp)['metadata']
|
||||
|
||||
def query(self, sql, convert=None, server_only=None, parameters=None):
|
||||
url = f"{self.dtable_db_url.strip('/')}/api/v1/query/{self.dtable_uuid}/?from=dtable_web"
|
||||
data = {'sql': sql}
|
||||
if convert is not None:
|
||||
data['convert_keys'] = convert
|
||||
if server_only is not None:
|
||||
data['server_only'] = server_only
|
||||
if parameters:
|
||||
data['parameters'] = parameters
|
||||
resp = requests.post(url, json=data, headers=self.headers)
|
||||
return parse_response(resp)
|
||||
|
||||
def add_table(self, table_name, columns=None):
|
||||
url = f"{self.dtable_server_url.strip('/')}/api/v1/dtables/{self.dtable_uuid}/tables/?from=dtable_web"
|
||||
data = {'table_name': table_name}
|
||||
if columns:
|
||||
data['columns'] = columns
|
||||
resp = requests.post(url, headers=self.headers, json=data)
|
||||
return parse_response(resp)
|
||||
|
||||
def insert_column(self, table_name, column):
|
||||
url = f"{self.dtable_server_url.strip('/')}/api/v1/dtables/{self.dtable_uuid}/columns/?from=dtable_web"
|
||||
data = {'table_name': table_name}
|
||||
data.update(column)
|
||||
resp = requests.post(url, headers=self.headers, json=data)
|
||||
return parse_response(resp)
|
||||
|
||||
def append_row(self, table_name, row):
|
||||
url = f"{self.dtable_server_url.strip('/')}/api/v1/dtables/{self.dtable_uuid}/rows/?from=dtable_web"
|
||||
data = {
|
||||
'table_name': table_name,
|
||||
'row': row
|
||||
}
|
||||
resp = requests.post(url, headers=self.headers, json=data)
|
||||
return parse_response(resp)
|
||||
|
||||
def update_row(self, table_name, row_id, row):
|
||||
url = f"{self.dtable_server_url.strip('/')}/api/v1/dtables/{self.dtable_uuid}/rows/?from=dtable_web"
|
||||
data = {
|
||||
'table_name': table_name,
|
||||
'row': row,
|
||||
"row_id": row_id
|
||||
}
|
||||
resp = requests.put(url, headers=self.headers, json=data)
|
||||
return parse_response(resp)
|
||||
|
||||
def batch_append_rows(self, table_name, rows):
|
||||
url = f"{self.dtable_server_url.strip('/')}/api/v1/dtables/{self.dtable_uuid}/batch-append-rows/?from=dtable_web"
|
||||
data = {
|
||||
'table_name': table_name,
|
||||
'rows': rows
|
||||
}
|
||||
resp = requests.post(url, headers=self.headers, json=data)
|
||||
return parse_response(resp)
|
||||
|
||||
def get_table_by_name(self, table_name):
|
||||
metadata = self.get_metadata()
|
||||
for table in metadata['tables']:
|
||||
if table['name'] == table_name:
|
||||
return table
|
||||
return None
|
||||
|
||||
def update_rows_by_dtable_db(self, table_name, updates):
|
||||
url = f"{self.dtable_db_url.strip('/')}/api/v1/update-rows/{self.dtable_uuid}/?from=dtable_web"
|
||||
data = {
|
||||
'table_name': table_name,
|
||||
'updates': updates
|
||||
}
|
||||
resp = requests.put(url, headers=self.headers, json=data)
|
||||
return parse_response(resp)
|
||||
|
||||
def insert_rows_by_dtable_db(self, table_name, rows):
|
||||
url = f"{self.dtable_db_url.strip('/')}/api/v1/insert-rows/{self.dtable_uuid}/?from=dtable_web"
|
||||
data = {
|
||||
'table_name': table_name,
|
||||
'rows': rows
|
||||
}
|
||||
resp = requests.post(url, headers=self.headers, json=data)
|
||||
return parse_response(resp)
|
@@ -58,7 +58,7 @@ from seahub.settings import AVATAR_FILE_STORAGE, ENABLE_REPO_SNAPSHOT_LABEL, \
|
||||
UPLOAD_LINK_EXPIRE_DAYS_MIN, UPLOAD_LINK_EXPIRE_DAYS_MAX, UPLOAD_LINK_EXPIRE_DAYS_DEFAULT, \
|
||||
SEAFILE_COLLAB_SERVER, ENABLE_RESET_ENCRYPTED_REPO_PASSWORD, \
|
||||
ADDITIONAL_SHARE_DIALOG_NOTE, ADDITIONAL_APP_BOTTOM_LINKS, ADDITIONAL_ABOUT_DIALOG_LINKS, \
|
||||
DTABLE_WEB_SERVER, EX_PROPS_TABLE, SEATABLE_EX_PROPS_BASE_API_TOKEN, EX_EDITABLE_COLUMNS
|
||||
DTABLE_WEB_SERVER
|
||||
|
||||
from seahub.ocm.settings import ENABLE_OCM, OCM_REMOTE_SERVERS
|
||||
from seahub.ocm_via_webdav.settings import ENABLE_OCM_VIA_WEBDAV
|
||||
@@ -1114,6 +1114,5 @@ def react_fake_view(request, **kwargs):
|
||||
'group_import_members_extra_msg': GROUP_IMPORT_MEMBERS_EXTRA_MSG,
|
||||
'request_from_onlyoffice_desktop_editor': ONLYOFFICE_DESKTOP_EDITOR_HTTP_USER_AGENT in request.headers.get('user-agent', ''),
|
||||
'enable_sso_to_thirdpart_website': settings.ENABLE_SSO_TO_THIRDPART_WEBSITE,
|
||||
'can_set_ex_props': DTABLE_WEB_SERVER and SEATABLE_EX_PROPS_BASE_API_TOKEN and EX_PROPS_TABLE and EX_EDITABLE_COLUMNS,
|
||||
'enable_metadata_management': settings.ENABLE_METADATA_MANAGEMENT
|
||||
})
|
||||
|
Reference in New Issue
Block a user