1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-02 07:27:04 +00:00

Sync apply folder ex props (#5666)

* init sync apply folder ex-props

* clear worker-map

* opt apply and remove useless code

* use new sync-apply-folder-ex-props api

* opt try exception
This commit is contained in:
Alex Happy
2023-10-07 14:55:16 +08:00
committed by GitHub
parent ce43942cdd
commit 1976ebd0c6
6 changed files with 270 additions and 144 deletions

View File

@@ -20,54 +20,16 @@ class ConfirmApplyFolderPropertiesDialog extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
submitting: true submitting: false
}; };
this.timer = null;
} }
componentDidMount() {
const { repoID, path } = this.props;
seafileAPI.queryFolderItemsExtendedPropertiesStatus(repoID, path).then(res => {
if (res.data.is_finished) {
this.timer && clearInterval(this.timer);
this.setState({ submitting: false });
} else {
this.queryStatus();
}
}).catch(error => {
//
});
}
componentWillUnmount() {
this.timer && clearInterval(this.timer);
}
queryStatus = () =>{
const { repoID, path } = this.props;
this.timer = setInterval(() => {
seafileAPI.queryFolderItemsExtendedPropertiesStatus(repoID, path).then(res => {
if (res.data.is_finished === true) {
clearInterval(this.timer);
this.timer = null;
toaster.success(gettext('Applied folder properties'));
this.props.toggle();
}
}).catch(error => {
clearInterval(this.timer);
this.timer = null;
let errorMsg = Utils.getErrorMsg(error);
toaster.danger(errorMsg);
this.setState({ submitting: false });
});
}, 1000);
};
submit = () => { submit = () => {
const { repoID, path } = this.props; const { repoID, path } = this.props;
this.setState({ submitting: true }); this.setState({ submitting: true });
seafileAPI.setFolderItemsExtendedProperties(repoID, path).then(() => { seafileAPI.applyFolderExtendedProperties(repoID, path).then(() => {
this.queryStatus(); toaster.success('Applied folder properties');
this.props.toggle();
}).catch(error => { }).catch(error => {
let errorMsg = Utils.getErrorMsg(error); let errorMsg = Utils.getErrorMsg(error);
toaster.danger(errorMsg); toaster.danger(errorMsg);
@@ -89,7 +51,7 @@ class ConfirmApplyFolderPropertiesDialog extends React.Component {
</p> </p>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button color='secondary' onClick={this.props.toggle}>{gettext('Cancel')}</Button> <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}> <Button color='primary' className='flex-shrink-0 apply-properties' disabled={submitting} onClick={this.submit}>
{submitting ? (<Loading />) : (<>{gettext('Submit')}</>)} {submitting ? (<Loading />) : (<>{gettext('Submit')}</>)}
</Button> </Button>

View File

@@ -1254,7 +1254,7 @@ class LibContentView extends React.Component {
this.addDirent(newFileName, 'file', res.data.size); this.addDirent(newFileName, 'file', res.data.size);
this.setState({isConvertLoading: false}); this.setState({isConvertLoading: false});
let message = gettext('Successfully converted file.') let message = gettext('Successfully converted file.');
toaster.success(message); toaster.success(message);
}).catch((error) => { }).catch((error) => {

View File

@@ -1,8 +1,12 @@
import hashlib
import json import json
import logging import logging
import os import os
import stat import stat
from collections import defaultdict
from datetime import datetime from datetime import datetime
from threading import Lock
from uuid import uuid4
import requests import requests
from rest_framework import status from rest_framework import status
@@ -18,7 +22,7 @@ from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error from seahub.api2.utils import api_error
from seahub.base.templatetags.seahub_tags import email2nickname from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.settings import DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN, \ from seahub.settings import DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN, \
EX_PROPS_TABLE, EX_EDITABLE_COLUMNS, SEAF_EVENTS_IO_SERVER_URL EX_PROPS_TABLE, EX_EDITABLE_COLUMNS
from seahub.tags.models import FileUUIDMap from seahub.tags.models import FileUUIDMap
from seahub.utils import normalize_file_path, EMPTY_SHA1 from seahub.utils import normalize_file_path, EMPTY_SHA1
from seahub.utils.repo import parse_repo_perm from seahub.utils.repo import parse_repo_perm
@@ -28,27 +32,8 @@ from seahub.views import check_folder_permission
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def request_can_set_ex_props(repo_id, path): class QueryException(Exception):
url = SEAF_EVENTS_IO_SERVER_URL.strip('/') + '/can-set-ex-props' pass
resp = requests.post(url, json={'repo_id': repo_id, 'path': path})
return resp.json()
def add_set_folder_ex_props_task(repo_id, path, username):
url = SEAF_EVENTS_IO_SERVER_URL.strip('/') + '/set-folder-items-ex-props'
context = {
'repo_id': repo_id,
'path': path,
'文件负责人': email2nickname(username)
}
resp = requests.post(url, json=context)
return resp.json()
def query_set_ex_props_status(repo_id, path):
url = SEAF_EVENTS_IO_SERVER_URL.strip('/') + '/query-set-ex-props-status'
resp = requests.get(url, params={'repo_id': repo_id, 'path': path})
return resp.json()
def check_table(seatable_api: SeaTableAPI): def check_table(seatable_api: SeaTableAPI):
@@ -100,13 +85,9 @@ class ExtendedPropertiesView(APIView):
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web: 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.') return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
resp_json = request_can_set_ex_props(repo_id, path) can_set, error_type = ApplyFolderExtendedPropertiesView.can_set_file_or_folder(repo_id, path)
if not resp_json.get('can_set', False): if not can_set:
if resp_json.get('error_type') == 'higher_being_set': return api_error(status.HTTP_400_BAD_REQUEST, 'Another task is running')
error_msg = 'Another task is running'
else:
error_msg = 'Please try again later'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# check base # check base
try: try:
@@ -261,13 +242,9 @@ class ExtendedPropertiesView(APIView):
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web: 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.') return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
resp_json = request_can_set_ex_props(repo_id, path) can_set, error_type = ApplyFolderExtendedPropertiesView.can_set_file_or_folder(repo_id, path)
if not resp_json.get('can_set', False): if not can_set:
if resp_json.get('error_type') == 'higher_being_set': return api_error(status.HTTP_400_BAD_REQUEST, 'Another task is running')
error_msg = 'Another task is running'
else:
error_msg = 'Please try again later'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# check base # check base
try: try:
@@ -358,19 +335,206 @@ class ExtendedPropertiesView(APIView):
return Response({'success': True}) return Response({'success': True})
class FolderItemsExtendedPropertiesView(APIView): class ApplyFolderExtendedPropertiesView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication) authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,) throttle_classes = (UserRateThrottle,)
apply_lock = Lock()
worker_map = defaultdict(list)
list_max = 1000
step = 500
@classmethod
def can_set_file_or_folder(cls, repo_id, path):
"""
:return: can_apply -> bool, error_type -> string or None
"""
paths = cls.worker_map.get(repo_id, [])
for cur_path in paths:
if path.startswith(cur_path):
return False, 'higer_folder_applying'
return True, None
@classmethod
def can_apply_folder(cls, repo_id, path):
"""
:return: can_apply -> bool, error_type -> string or None
"""
for cur_repo_id, paths in cls.worker_map.items():
if repo_id != cur_repo_id:
continue
for cur_path in paths:
if cur_path.startswith(path):
return False, 'sub_folder_applying'
if path.startswith(cur_path):
return False, 'higer_folder_applying'
return True, None
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): def post(self, request, repo_id):
if not all((DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN, EX_PROPS_TABLE)): 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') return api_error(status.HTTP_403_FORBIDDEN, 'Feature not enabled')
# arguments check # arguments check
path = request.data.get('path') path = request.data.get('path')
path = normalize_file_path(path)
if not path: if not path:
return api_error(status.HTTP_400_BAD_REQUEST, 'path invalid') return api_error(status.HTTP_400_BAD_REQUEST, 'path invalid')
path = normalize_file_path(path)
parent_dir = os.path.dirname(path) parent_dir = os.path.dirname(path)
dirent = seafile_api.get_dirent_by_path(repo_id, path) dirent = seafile_api.get_dirent_by_path(repo_id, path)
@@ -383,64 +547,42 @@ class FolderItemsExtendedPropertiesView(APIView):
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web: 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.') return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
# with lock check repo and path can apply props
with self.apply_lock:
can_apply, _ = self.can_apply_folder(repo_id, path)
if not can_apply:
return api_error(status.HTTP_400_BAD_REQUEST, 'Another task is running')
# with lock add repo and path to apply task map
self.worker_map[repo_id].append(path)
# request props from seatable # request props from seatable
try: try:
seatable_api = SeaTableAPI(SEATABLE_EX_PROPS_BASE_API_TOKEN, DTABLE_WEB_SERVER) seatable_api = SeaTableAPI(SEATABLE_EX_PROPS_BASE_API_TOKEN, DTABLE_WEB_SERVER)
except: except:
logger.error('server: %s token: %s seatable-api fail', DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN) 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') return api_error(status.HTTP_400_BAD_REQUEST, 'Props base invalid')
sql = f"SELECT * FROM `{EX_PROPS_TABLE}` WHERE `Repo ID`='{repo_id}' AND `Path`='{path}'" 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')
# apply props
context = {'文件负责人': email2nickname(request.user.username)}
try: try:
result = seatable_api.query(sql) self.apply_folder(repo_id, path, context, seatable_api, folder_props)
except Exception as e: except QueryException as e:
logger.exception('query sql: %s error: %s', sql, 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') return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
rows = result.get('results') except Exception as e:
if not rows: logger.exception('apply folder: %s ex-props error: %s', path, e)
return api_error(status.HTTP_400_BAD_REQUEST, '%s not set extended properties' % path) return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
finally:
resp_json = add_set_folder_ex_props_task(repo_id, path, request.user.username) # remove path from worker
with self.apply_lock:
error_type = resp_json.get('error_type') for repo_id, paths in self.worker_map.items():
if error_type == 'higher_being_set': if repo_id != repo_id:
error_msg = 'Another task is running' continue
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) self.worker_map[repo_id] = [cur_path for cur_path in paths if cur_path != path]
elif error_type == 'server_busy': if not self.worker_map[repo_id]:
error_msg = 'Server is busy' del self.worker_map[repo_id]
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
elif error_type == 'sub_folder_setting':
error_msg = 'Another task is running'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
return Response({'success': True}) return Response({'success': True})
class FolderItemsPropertiesStatusQueryView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
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)
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.')
resp_json = query_set_ex_props_status(repo_id, path)
return Response(resp_json)

View File

@@ -867,11 +867,6 @@ if os.environ.get('SEAFILE_DOCS', None):
LOGO_WIDTH = '' LOGO_WIDTH = ''
ENABLE_WIKI = True ENABLE_WIKI = True
####################
# events io server #
####################
SEAF_EVENTS_IO_SERVER_URL = 'http://127.0.0.1:6066'
####################### #######################
# extended properties # # extended properties #
####################### #######################

View File

@@ -119,8 +119,7 @@ from seahub.ocm_via_webdav.ocm_api import OCMProviderView
from seahub.api2.endpoints.repo_share_links import RepoShareLinks, RepoShareLink from seahub.api2.endpoints.repo_share_links import RepoShareLinks, RepoShareLink
from seahub.api2.endpoints.repo_upload_links import RepoUploadLinks, RepoUploadLink from seahub.api2.endpoints.repo_upload_links import RepoUploadLinks, RepoUploadLink
from seahub.api2.endpoints.extended_properties import ExtendedPropertiesView, FolderItemsExtendedPropertiesView, \ from seahub.api2.endpoints.extended_properties import ExtendedPropertiesView, ApplyFolderExtendedPropertiesView
FolderItemsPropertiesStatusQueryView
# Admin # Admin
from seahub.api2.endpoints.admin.abuse_reports import AdminAbuseReportsView, AdminAbuseReportView from seahub.api2.endpoints.admin.abuse_reports import AdminAbuseReportsView, AdminAbuseReportView
@@ -429,8 +428,7 @@ urlpatterns = [
## user:file:extended-props ## 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})/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})/folder-items-extended-properties/$', FolderItemsExtendedPropertiesView.as_view(), name='api-v2.1-folder-items-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})/folder-items-extended-properties/status-query/$', FolderItemsPropertiesStatusQueryView.as_view(), name='api-v2.1-folder-items-extended-properties-status-query'),
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})/auto-delete/$', RepoAutoDeleteView.as_view(), name='api-v2.1-repo-auto-delete'),

View File

@@ -65,13 +65,15 @@ class SeaTableAPI:
resp = requests.get(url, headers=self.headers) resp = requests.get(url, headers=self.headers)
return parse_response(resp)['metadata'] return parse_response(resp)['metadata']
def query(self, sql, convert=None, server_only=None): 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" url = f"{self.dtable_db_url.strip('/')}/api/v1/query/{self.dtable_uuid}/?from=dtable_web"
data = {'sql': sql} data = {'sql': sql}
if convert is not None: if convert is not None:
data['convert_keys'] = convert data['convert_keys'] = convert
if server_only is not None: if server_only is not None:
data['server_only'] = server_only data['server_only'] = server_only
if parameters:
data['parameters'] = parameters
resp = requests.post(url, json=data, headers=self.headers) resp = requests.post(url, json=data, headers=self.headers)
return parse_response(resp) return parse_response(resp)
@@ -109,9 +111,36 @@ class SeaTableAPI:
resp = requests.put(url, headers=self.headers, json=data) resp = requests.put(url, headers=self.headers, json=data)
return parse_response(resp) 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): def get_table_by_name(self, table_name):
metadata = self.get_metadata() metadata = self.get_metadata()
for table in metadata['tables']: for table in metadata['tables']:
if table['name'] == table_name: if table['name'] == table_name:
return table return table
return None 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)