From ec2d2dbcaa4436354a24f9bc3e9ad93db4b7149b Mon Sep 17 00:00:00 2001 From: Iwantexpresso Date: Thu, 7 May 2026 13:15:16 -0500 Subject: [PATCH 1/6] attemtped fix on api null id --- specifyweb/specify/api/crud.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specifyweb/specify/api/crud.py b/specifyweb/specify/api/crud.py index d0923d3b5d9..e7b98c7e8b1 100644 --- a/specifyweb/specify/api/crud.py +++ b/specifyweb/specify/api/crud.py @@ -156,8 +156,6 @@ def update_obj(collection, agent, name: str, id, version, data: dict[str, Any], def delete_obj(obj, deleter: Callable[[Any, Any], None] | None=None, version=None, parent_obj=None, clean_predelete=None) -> None: # need to delete dependent -to-one records - # e.g. delete CollectionObjectAttribute when CollectionObject is deleted - # but have to delete the referring record first dependents_to_delete = [_f for _f in ( get_related_or_none(obj, field.name) for field in obj._meta.get_fields() @@ -173,13 +171,15 @@ def delete_obj(obj, deleter: Callable[[Any, Any], None] | None=None, version=Non if hasattr(obj, 'pre_constraints_delete'): obj.pre_constraints_delete() - if deleter: + # CRITICAL: Only call deleter if object still has an ID + # Dependent objects may already be cascade-deleted with id=None + if deleter and obj.id is not None: deleter(obj, parent_obj) obj.delete() - + for dep in dependents_to_delete: - delete_obj(dep, deleter, version, parent_obj=obj, clean_predelete=clean_predelete) + delete_obj(dep, deleter, version, parent_obj=obj, clean_predelete=clean_predelete) def update_or_create_resource(collection, agent, model, data, parent_obj, parent_relationship=None): if 'id' in data: From 559c66c8fc7885a0f4ceca1f9c091489e3a77eff Mon Sep 17 00:00:00 2001 From: Iwantexpresso Date: Mon, 11 May 2026 12:50:03 -0500 Subject: [PATCH 2/6] sotred obj,ids before deletion to bouded ID being Set to none during Cascade deletion --- specifyweb/specify/api/crud.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/specifyweb/specify/api/crud.py b/specifyweb/specify/api/crud.py index e7b98c7e8b1..c00205902cb 100644 --- a/specifyweb/specify/api/crud.py +++ b/specifyweb/specify/api/crud.py @@ -170,15 +170,18 @@ def delete_obj(obj, deleter: Callable[[Any, Any], None] | None=None, version=Non if hasattr(obj, 'pre_constraints_delete'): obj.pre_constraints_delete() + # store obj id and class before deletion to avoid accessing deleted obj + obj_id = obj.id - # CRITICAL: Only call deleter if object still has an ID - # Dependent objects may already be cascade-deleted with id=None - if deleter and obj.id is not None: + if deleter and (obj_id is not None): deleter(obj, parent_obj) obj.delete() - + +# before delete obj, store dep id to avoid accessing deleted obj in recursive delete calls for dep in dependents_to_delete: + dep_id = dep.id + if dep_id is not None: delete_obj(dep, deleter, version, parent_obj=obj, clean_predelete=clean_predelete) def update_or_create_resource(collection, agent, model, data, parent_obj, parent_relationship=None): From e55ddcb12446c879a18b3f5fbfcbe1a26ca0a3b4 Mon Sep 17 00:00:00 2001 From: Iwantexpresso Date: Thu, 21 May 2026 11:18:49 -0500 Subject: [PATCH 3/6] re-added accidentally deleted comments, commented otu obj_id check if its not non as its redundants, Fixed indentaiton on delete --- specifyweb/specify/api/crud.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/specifyweb/specify/api/crud.py b/specifyweb/specify/api/crud.py index c00205902cb..28f8fb5367a 100644 --- a/specifyweb/specify/api/crud.py +++ b/specifyweb/specify/api/crud.py @@ -156,6 +156,8 @@ def update_obj(collection, agent, name: str, id, version, data: dict[str, Any], def delete_obj(obj, deleter: Callable[[Any, Any], None] | None=None, version=None, parent_obj=None, clean_predelete=None) -> None: # need to delete dependent -to-one records + # e.g. delete CollectionObjectAttribute when CollectionObject is deleted + # but have to delete the referring record first dependents_to_delete = [_f for _f in ( get_related_or_none(obj, field.name) for field in obj._meta.get_fields() @@ -171,18 +173,19 @@ def delete_obj(obj, deleter: Callable[[Any, Any], None] | None=None, version=Non if hasattr(obj, 'pre_constraints_delete'): obj.pre_constraints_delete() # store obj id and class before deletion to avoid accessing deleted obj - obj_id = obj.id + # obj_id = obj.id + # temporary disabled to tests its necessity since the cases in which its obj_id is missing are on the dependent objects - if deleter and (obj_id is not None): - deleter(obj, parent_obj) + # if deleter and (obj_id is not None): + # deleter(obj, parent_obj) obj.delete() -# before delete obj, store dep id to avoid accessing deleted obj in recursive delete calls + # before delete obj, store dep id to avoid accessing deleted obj in recursive delete calls for dep in dependents_to_delete: - dep_id = dep.id - if dep_id is not None: - delete_obj(dep, deleter, version, parent_obj=obj, clean_predelete=clean_predelete) + dep_id = dep.id + if dep_id is not None: + delete_obj(dep, deleter, version, parent_obj=obj, clean_predelete=clean_predelete) def update_or_create_resource(collection, agent, model, data, parent_obj, parent_relationship=None): if 'id' in data: From e51d2b2e9343c286b5ed60e886d2b322b8b4a02d Mon Sep 17 00:00:00 2001 From: Iwantexpresso Date: Thu, 21 May 2026 11:28:02 -0500 Subject: [PATCH 4/6] corrected accidental comment out of deleter --- specifyweb/specify/api/crud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/specify/api/crud.py b/specifyweb/specify/api/crud.py index 28f8fb5367a..4a575167088 100644 --- a/specifyweb/specify/api/crud.py +++ b/specifyweb/specify/api/crud.py @@ -177,7 +177,7 @@ def delete_obj(obj, deleter: Callable[[Any, Any], None] | None=None, version=Non # temporary disabled to tests its necessity since the cases in which its obj_id is missing are on the dependent objects # if deleter and (obj_id is not None): - # deleter(obj, parent_obj) + deleter(obj, parent_obj) obj.delete() From 28ba89e1d03278bb31909a707f25a8a3a22a9e3b Mon Sep 17 00:00:00 2001 From: Iwantexpresso Date: Thu, 21 May 2026 11:39:36 -0500 Subject: [PATCH 5/6] changed to include back if deleter without the and block right after it --- specifyweb/specify/api/crud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specifyweb/specify/api/crud.py b/specifyweb/specify/api/crud.py index 4a575167088..799c7883b7b 100644 --- a/specifyweb/specify/api/crud.py +++ b/specifyweb/specify/api/crud.py @@ -176,8 +176,8 @@ def delete_obj(obj, deleter: Callable[[Any, Any], None] | None=None, version=Non # obj_id = obj.id # temporary disabled to tests its necessity since the cases in which its obj_id is missing are on the dependent objects - # if deleter and (obj_id is not None): - deleter(obj, parent_obj) + if deleter: + deleter(obj, parent_obj) obj.delete() From 234b78ddb907e425b60784e10fdfd1b6fab6450b Mon Sep 17 00:00:00 2001 From: Iwantexpresso Date: Thu, 21 May 2026 12:25:19 -0500 Subject: [PATCH 6/6] removed redundant code pointed out by Alejandro --- specifyweb/specify/api/crud.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/specifyweb/specify/api/crud.py b/specifyweb/specify/api/crud.py index 799c7883b7b..f9dcb9e9cd8 100644 --- a/specifyweb/specify/api/crud.py +++ b/specifyweb/specify/api/crud.py @@ -172,10 +172,6 @@ def delete_obj(obj, deleter: Callable[[Any, Any], None] | None=None, version=Non if hasattr(obj, 'pre_constraints_delete'): obj.pre_constraints_delete() - # store obj id and class before deletion to avoid accessing deleted obj - # obj_id = obj.id - # temporary disabled to tests its necessity since the cases in which its obj_id is missing are on the dependent objects - if deleter: deleter(obj, parent_obj)