Your Setup:
- SeaTable Edition: Enterprise
- SeaTable Version: 5.0.8
You can find your SeaTable Server version athttps://your-server-url/server-info
Describe the Problem/Error/Question:
Please describe your issue as precise as possible. If helpful, you can also provide:
- API endpoints you used: https://{server}/api/v2.1/dtable/app-asset/
I am currently trying to delete a file column including the files on the file server using a Python script.
Unfortunately, Seatable is not yet able to do this natively.
The attached script works great in the cloud version.
Unfortunately, I get an error message in the OnPremise version that it cannot delete the file, although I use the same script.
The file is still on the server.
Can somebody tell me what I am doing wrong?
import os
import requests
import traceback
from seatable_api import Base, context
from urllib.parse import urlparse
###############################################################################
# KONFIGURATION
###############################################################################
FILE_COLUMN_NAME = "Datei" # <-- Hier ggf. anpassen, falls die Spalte anders heißt
DELETE_ENDPOINT = "/api/v2.1/dtable/app-asset/" # SeaTable-Endpunkt für Dateilöschungen
###############################################################################
# VERBINDUNG ZU SEATABLE AUFBAUEN
###############################################################################
server_url = context.server_url.rstrip('/')
api_token = context.api_token
current_row = context.current_row
current_table = context.current_table
print("=== DEBUG: Verbindungsinformationen ===")
print(f"Server URL: {server_url}")
print(f"API Token: {api_token}")
print(f"Aktuelle Tabellen-ID: {current_table}")
print(f"Aktuelle Zeile: {current_row}")
print("========================================\n")
# Initiiere und authentifiziere die SeaTable-Base
base = Base(api_token, server_url)
base.auth()
###############################################################################
# FUNKTION: Relativen Pfad robust ermitteln
###############################################################################
def get_relative_path_from_url(file_url):
"""
Ermittelt den für SeaTable relevanten relativen Pfad aus einer Dateiverknüpfungs-URL.
Versucht zuerst, nach '/files/' zu suchen und schneidet alle Pfadsegmente davor ab.
Falls das nicht klappt, wird ein Fallback genutzt (z. B. ab Index 7).
Passen Sie diese Logik an Ihre individuelle URL-Struktur an.
"""
try:
parsed_url = urlparse(file_url)
full_path = parsed_url.path # z. B. "/workspace/.../files/.../datei.pdf"
# Versuche, den Teil nach "/files/" herauszufiltern
path_parts = full_path.split('/files/')
if len(path_parts) == 2:
relative_path = "files/" + path_parts[1]
print(f"[INFO] Ermittelter Pfad (über /files/): {relative_path}")
return relative_path
else:
# Fallback: ab Segment 7
fallback_parts = full_path.split('/')[7:]
relative_path = '/'.join(fallback_parts)
print(f"[INFO] Fallback-Pfad (ab Index 7): {relative_path}")
return relative_path
except Exception as e:
print("[ERROR] Fehler beim Ermitteln des relativen Pfads aus der URL:")
print(f" {file_url}")
print(" Exception:", e)
return None
###############################################################################
# FUNKTION: Datei physisch von SeaTable löschen (erweiterte Fehlerbehandlung)
###############################################################################
def delete_file_from_seatable_via_requests(server, token, relative_path):
"""
Löscht eine Datei über die SeaTable-API per DELETE-Request.
Gibt True zurück, wenn
- die Datei erfolgreich gelöscht wurde (HTTP 200) ODER
- die Datei nicht existiert (HTTP 404).
Gibt False zurück, wenn ein anderer Statuscode kommt (z. B. 403, 500)
oder wenn ein Request-Fehler auftritt.
"""
# Zusammenbau der endgültigen DELETE-URL
delete_url = f"{server}{DELETE_ENDPOINT}?path={relative_path}"
# Auth-Header vorbereiten
headers = {
"Authorization": f"Token {token}",
"accept": "application/json"
}
print("[INFO] Starte Löschen der Datei via DELETE-Request:")
print(f" URL: {delete_url}")
print(f" Headers: {headers}")
try:
response = requests.delete(delete_url, headers=headers)
# Auswertung der häufigsten Statuscodes
if response.status_code == 200:
print(f"[SUCCESS] Datei '{relative_path}' erfolgreich vom SeaTable-Server gelöscht.")
return True
elif response.status_code == 404:
print(f"[WARNING] Datei '{relative_path}' war nicht vorhanden (404) - gilt als gelöscht.")
return True
elif response.status_code == 403:
print(f"[ERROR] Keine Berechtigung, Datei '{relative_path}' zu löschen (403).")
return False
else:
print(f"[ERROR] Unerwarteter Status {response.status_code} beim Löschen der Datei:")
print(" ", response.text)
return False
except requests.exceptions.RequestException as re:
print("[ERROR] Netzwerk-/Request-Fehler beim Löschen der Datei:")
print(" ", str(re))
print(" Stacktrace:", traceback.format_exc())
return False
except Exception as e:
print("[ERROR] Fehler in delete_file_from_seatable_via_requests:")
print(" ", str(e))
print(" Stacktrace:", traceback.format_exc())
return False
###############################################################################
# HAUPT-FUNKTION: Prozess ausführen
###############################################################################
def process_deletion():
"""
1. Alle Dateien in der Spalte FILE_COLUMN_NAME ermitteln (falls mehrere).
2. Für jede Datei den Pfad bestimmen und aus SeaTable löschen.
3. Nur die Dateien, die erfolgreich gelöscht werden konnten, entfernen wir
aus der Liste (Teil-Löschung). Dateien mit Fehlschlag bleiben bestehen.
4. Anschließend wird die Spalte upgedatet, sodass nur die unerfolgreich
gelöschten Dateien (wenn überhaupt) noch angezeigt werden.
"""
try:
# 1) Dateien aus der Zeile holen
file_data = current_row.get(FILE_COLUMN_NAME)
if not file_data:
print(f"[INFO] Keine Dateien in der Spalte '{FILE_COLUMN_NAME}' vorhanden.")
return
# SeaTable speichert Dateien normalerweise in einer Liste. Falls nicht: in Liste umwandeln
if not isinstance(file_data, list):
file_data = [file_data]
# In dieser Liste speichern wir nur Dateien, die NICHT (erfolgreich) gelöscht wurden
files_remaining = []
# 2) Jede Datei versuchen zu löschen
for file_info in file_data:
file_url = file_info.get("url")
file_name = file_info.get("name")
if not file_url:
# Keine gültige URL => kann nicht gelöscht werden => Verbleibt in der Spalte
print("[ERROR] Datei-Eintrag ohne gültige URL gefunden. Bleibt in der Liste.")
files_remaining.append(file_info)
continue
print("=== Datei-Infos ===")
print(f"Name: {file_name}")
print(f"URL: {file_url}")
print("===================\n")
# Relativen Pfad bestimmen
relative_path = get_relative_path_from_url(file_url)
if not relative_path:
print("[ERROR] Konnte keinen gültigen relativen Pfad ermitteln. Bleibt in der Liste.")
files_remaining.append(file_info)
continue
# Datei physisch vom Server löschen
success = delete_file_from_seatable_via_requests(server_url, api_token, relative_path)
if not success:
# Wenn die Datei NICHT gelöscht werden konnte => In Spalte behalten
files_remaining.append(file_info)
# 3) Spalte mit den übrig gebliebenen Dateien aktualisieren
# (= nur die, die wir nicht erfolgreich löschen konnten)
base.update_row(current_table, current_row["_id"], {FILE_COLUMN_NAME: files_remaining})
if len(files_remaining) == 0:
print("[INFO] Alle Dateien wurden erfolgreich gelöscht. Spalte ist nun leer.")
else:
print(f"[INFO] {len(files_remaining)} Datei(en) konnte(n) nicht gelöscht werden und verbleibt/verbleiben in der Spalte.")
print("\n=== Prozess abgeschlossen. ===")
except Exception as e:
print("[ERROR] Allgemeiner Fehler im Prozess:")
print(" ", str(e))
print(" Stacktrace:", traceback.format_exc())
###############################################################################
# SCRIPT-AUFRUF
###############################################################################
if __name__ == "__main__":
process_deletion()
Error Messages:
=== DEBUG: Verbindungsinformationen ===
Server URL: https://HOST.server.de
API Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkdGFibGVfdXVpZCI6ImYzMTBhMTg0LTcxZWYtNDBlMy05ZWI4LThiZDgwMmU4NTAxNiIsImFwcF9uYW1lIjoiN0JnNy5weSIsImV4cCI6MTczNTkwOTk4Mn0.CVqmDNoFf0vSAxX9kqVR9mos6mePPXPZL1PNgYLIwBg
Aktuelle Tabellen-ID: Table1
Aktuelle Zeile: {'_id': 'SFcowdvMTiSU8rwIsmte6Q', '_mtime': '2025-01-03T11:21:47.161+00:00', '_ctime': '2025-01-03T10:57:27.224+00:00', 'Datei': [{'name': '20250103 Belehrung-Verfall-Resturlaub_AusbZCIR 2025 (4).pdf', 'size': 116057, 'type': 'file', 'upload_time': '2025-01-03T11:14:48.162+00:00', 'url': 'https://HOST.server.de/workspace/31/asset/f310a184-71ef-40e3-9eb8-8bd802e85016/files/2025-01/20250103%20Belehrung-Verfall-Resturlaub_AusbZCIR%202025%20(4).pdf'}]}
========================================
=== Test des Endpunkts ===
Test-URL: https://HOST.server.de/api/v2.1/dtable/app-asset/
--- Versuche: OPTIONS ---
HTTP-Statuscode: 204
HTTP-Reason: No Content
Response-Text:
--------------------------------------------------
--- Versuche: HEAD ---
HTTP-Statuscode: 404
HTTP-Reason: Not Found
Response-Header: {'Alt-Svc': 'h3=":443"; ma=2592000', 'Content-Encoding': 'gzip', 'Content-Language': 'en', 'Content-Security-Policy': "block-all-mixed-content; default-src 'self'; style-src 'unsafe-inline' 'self'; script-src 'unsafe-inline' 'unsafe-eval' 'self' https://ooffice.HOST.server.de; script-src-elem 'unsafe-inline' 'self' HOST.server.de https://ooffice.HOST.server.de; font-src 'self' data:; img-src 'self' data: blob: https: ; form-action 'self' https://adfs.auf.bundeswehr.de; connect-src 'self' https:; frame-src 'self' HOST.server.de https://ooffice.HOST.server.de; frame-ancestors 'self' https://*.ecm.bundeswehr.org course-buddy.auf.bundeswehr.de course-buddy.auf.bundeswehr.de; worker-src 'self' blob:; manifest-src 'self'; object-src 'self'; base-uri 'self'", 'Content-Type': 'text/html; charset=utf-8', 'Date': 'Fri, 03 Jan 2025 12:13:03 GMT', 'Referrer-Policy': 'same-origin', 'Server': 'Caddy, nginx', 'Strict-Transport-Security': 'max-age=31536000;', 'Vary': 'Accept-Language, Cookie', 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'SAMEORIGIN', 'X-Xss-Protection': '1; mode=block'}
--------------------------------------------------
--- Versuche: GET ---
HTTP-Statuscode: 404
HTTP-Reason: Not Found
Response-Text:
<!DOCTYPE html>
<html lang="en">
<head>
<title>HOST</title>
<meta property="og:title" content= "SeaTable" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://HOST.server.de/api/v2.1/dtable/app-asset/" />
<meta property="og:image" content= "/media/img/og-seatable-logo.png" />
<meta property="og:description" content= "SeaTable - As simple as Excel, with the power of a database" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="keywords" content="File, Collaboration, Team, Organization" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="keywords" content="File, Collaboration, Team, Organization" />
<link rel="icon" href="/media/favicons/seatable-favicon.png">
<link rel="apple-touch-icon" href="/media/favicons/seatable-favicon.png">
<link rel="stylesheet" type="text/css" href="/media/css/dtable-font.css" />
<link rel="stylesheet" type="text/css" href="/media/css/seatable-ui.css?t=20240516" />
<link rel="stylesheet" type="text/css" href="/media/css/seatable-ui-extension.css?t=20240516" />
<link rel="stylesheet" type="text/css" href="/media/css/seahub_python.css?t=1398068110" />
<link rel="stylesheet" type="text/css" href="/custom-css/" />
</head>
<body>
<div id="wrapper" class="en d-flex flex-column h100">
<div id="header" class="d-flex">
<a href="/" id="logo">
<img src="/media/img/seatable-logo.png" title="HOST" alt="logo" width="" height="32" />
</a>
<span class="sf2-icon-menu side-nav-toggle hidden-md-up hide" title="Side nav menu" id="js-toggle-side-nav" aria-label="Side nav menu"></span>
<div id="lang">
<a href="#" id="lang-context" data-lang="en">English <span class="dtable-font dtable-icon-drop-down"></span></a>
<div id="lang-context-selector" class="sf-popover hide">
<ul class="sf-popover-con">
<li><a href="/i18n/?lang=de">Deutsch</a></li>
<li><a href="/i18n/?lang=en">English</a></li>
<li><a href="/i18n/?lang=es">Español</a></li>
<li><a href="/i18n/?lang=fr">Français</a></li>
<li><a href="/i18n/?lang=ru">Русский</a></li>
<li><a href="/i18n/?lang=pt">Portuguese</a></li>
<li><a href="/i18n/?lang=zh-cn">简体中文</a></li>
</ul>
</div>
</div>
</div>
<div id="main" class="container-fluid w100 h100">
<div class="row">
<div id="main-panel" class="w100 ovhd">
<div class="text-panel">
<p>Sorry, but the requested page could not be found.</p>
</div>
</div>
</div>
</div>
<div id="confirm-popup" class="hide">
<div id="confirm-con"></div>
<button id="confirm-yes">Yes</button>
<button class="simplemodal-close">No</button>
</div>
</div><!-- wrapper -->
<script type="text/javascript">
var SEAFILE_GLOBAL = {
csrfCookieName: 'dtable_csrftoken'
};
var app = {
ui : {
currentDropdown: false,
currentHighlightedItem: false,
freezeItemHightlight: false
}
}
</script>
<script type="text/javascript" src="/media/assets/scripts/lib/jquery-3.7.0.min.js" id="jquery"></script>
<script type="text/javascript">
function prepareAjaxErrorMsg(xhr) {
var error_msg;
if (xhr.responseText) {
var parsed_resp = JSON.parse(xhr.responseText);
// use `HTMLescape` for msgs which contain variable like 'path'
error_msg = HTMLescape(parsed_resp.error ||
parsed_resp.error_msg || parsed_resp.detail);
} else {
error_msg = gettext("Failed. Please check the network.");
}
return error_msg;
}
function ajaxErrorHandler(xhr, textStatus, errorThrown) {
var error_msg = prepareAjaxErrorMsg(xhr);
feedback(error_msg, 'error');
}
(function() {
var lang_context = $('#lang-context'),
lang_selector = $('#lang-context-selector');
lang_context.parent().css({'position':'relative'});
if ($('#header').is(':visible')) { // for login page
lang_selector.css({
'top': lang_context.position().top + lang_context.height() + 5,
'right': 0
});
}
var setLangSelectorMaxHeight = function() {
if ($('#header').is(':visible')) { // for login page
$('.sf-popover-con', lang_selector).css({
'max-height': $(window).height() - lang_selector.offset().top - 12
});
}
};
$(window).on('resize', function() {
if (lang_selector.is(':visible')) {
setLangSelectorMaxHeight();
}
});
lang_context.on('click', function() {
lang_selector.toggleClass('hide');
if (lang_selector.is(':visible')) {
setLangSelectorMaxHeight();
}
return false;
});
$(document).on('click', function(e) {
var element = e.target || e.srcElement;
if (element.id != 'lang-context-selector' && element.id != 'lang-context') {
lang_selector.addClass('hide');
}
});
})();
if ($('.side-nav').length) {
$('#logo').addClass('hidden-sm-down');
$('#js-toggle-side-nav').removeClass('hide');
}
$('#js-toggle-side-nav').on('click', function() {
$('.side-nav').addClass('side-nav-shown');
$('').modal({
overlayClose: true,
onClose: function() {
$('.side-nav').removeClass('side-nav-shown');
$.modal.close();
}});
$('#simplemodal-container').css({'display':'none'});
return false;
});
$('.js-close-side-nav').on('click', function() {
$('.side-nav').removeClass('side-nav-shown');
return false;
});
</script>
</body>
</html>
--------------------------------------------------
=== Ende des Endpunkt-Tests ===
=== Datei-Infos ===
Name: 20250103 Belehrung-Verfall-Resturlaub_AusbZCIR 2025 (4).pdf
URL: https://HOST.server.de/workspace/31/asset/f310a184-71ef-40e3-9eb8-8bd802e85016/files/2025-01/20250103%20Belehrung-Verfall-Resturlaub_AusbZCIR%202025%20(4).pdf
===================
[INFO] Ermittelter Pfad (über /files/): files/2025-01/20250103%20Belehrung-Verfall-Resturlaub_AusbZCIR%202025%20(4).pdf
[INFO] Starte Löschen der Datei via DELETE-Request:
URL: https://HOST.server.de/api/v2.1/dtable/app-asset/?path=files/2025-01/20250103%20Belehrung-Verfall-Resturlaub_AusbZCIR%202025%20(4).pdf
Headers: {'Authorization': 'Token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkdGFibGVfdXVpZCI6ImYzMTBhMTg0LTcxZWYtNDBlMy05ZWI4LThiZDgwMmU4NTAxNiIsImFwcF9uYW1lIjoiN0JnNy5weSIsImV4cCI6MTczNTkwOTk4Mn0.CVqmDNoFf0vSAxX9kqVR9mos6mePPXPZL1PNgYLIwBg', 'accept': 'application/json'}
[WARNING] Datei 'files/2025-01/20250103%20Belehrung-Verfall-Resturlaub_AusbZCIR%202025%20(4).pdf' war nicht vorhanden (404) - gilt als gelöscht.
[INFO] Alle Dateien wurden erfolgreich gelöscht. Spalte ist nun leer.
=== Prozess abgeschlossen. ===```