Delete files via python - Cloud vs. OnPremise

Your Setup:

  • SeaTable Edition: Enterprise
  • SeaTable Version: 5.0.8
    You can find your SeaTable Server version at https://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. ===```

If you get different results for SeaTable Cloud and your own server, than you should compare the versions:
SeaTable Cloud is on 5.1.9 and the python pipeline is 4.1.2.

If your system uses the same versions, the result of the script should be the same.

Thank you for the info, we were not up to date.

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.