mirror of
https://github.com/apache/superset.git
synced 2026-04-26 19:44:58 +00:00
fix(tags): expire tag relationship after deleting all tagged objects (#38163)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -378,4 +378,11 @@ class TagDAO(BaseDAO[Tag]):
|
||||
object_id,
|
||||
tag.name,
|
||||
)
|
||||
# After deleting tagged objects, we need to expire the tag's 'objects'
|
||||
# relationship to clear references to deleted TaggedObject instances.
|
||||
# This prevents SQLAlchemy errors when the tag is later added to the
|
||||
# session, as it would otherwise still hold references to deleted objects.
|
||||
if tagged_objects_to_delete:
|
||||
db.session.expire(tag, ["objects"])
|
||||
|
||||
db.session.add_all(tagged_objects)
|
||||
|
||||
@@ -204,3 +204,63 @@ def test_update_command_failed_validation(
|
||||
"objects_to_tag": objects_to_tag,
|
||||
},
|
||||
).run()
|
||||
|
||||
|
||||
def test_update_command_remove_all_tagged_objects(
|
||||
session_with_data: Session, mocker: MockerFixture
|
||||
):
|
||||
"""Test that removing all tagged objects from a tag works correctly.
|
||||
|
||||
This is a regression test for GitHub issue #36074 where bulk untagging
|
||||
(removing all objects from a tag) caused a SQLAlchemy error because
|
||||
the tag's 'objects' relationship still held references to deleted
|
||||
TaggedObject instances.
|
||||
"""
|
||||
from superset.commands.tag.create import CreateCustomTagWithRelationshipsCommand
|
||||
from superset.commands.tag.update import UpdateTagCommand
|
||||
from superset.daos.tag import TagDAO
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.tags.models import ObjectType, TaggedObject
|
||||
|
||||
dashboard = db.session.query(Dashboard).first()
|
||||
chart = db.session.query(Slice).first()
|
||||
|
||||
mocker.patch(
|
||||
"superset.security.SupersetSecurityManager.is_admin", return_value=True
|
||||
)
|
||||
mocker.patch("superset.daos.chart.ChartDAO.find_by_id", return_value=chart)
|
||||
mocker.patch(
|
||||
"superset.daos.dashboard.DashboardDAO.find_by_id", return_value=dashboard
|
||||
)
|
||||
|
||||
# Create a tag with multiple objects
|
||||
objects_to_tag = [
|
||||
(ObjectType.dashboard, dashboard.id),
|
||||
(ObjectType.chart, chart.id),
|
||||
]
|
||||
|
||||
CreateCustomTagWithRelationshipsCommand(
|
||||
data={"name": "test_tag", "objects_to_tag": objects_to_tag}
|
||||
).run()
|
||||
|
||||
tag_to_update = TagDAO.find_by_name("test_tag")
|
||||
assert len(tag_to_update.objects) == 2
|
||||
|
||||
# Remove all tagged objects by passing an empty list
|
||||
# This should not raise a SQLAlchemy error about deleted instances
|
||||
updated_tag = UpdateTagCommand(
|
||||
tag_to_update.id,
|
||||
{
|
||||
"name": "test_tag",
|
||||
"description": "updated description",
|
||||
"objects_to_tag": [],
|
||||
},
|
||||
).run()
|
||||
|
||||
assert updated_tag is not None
|
||||
assert updated_tag.description == "updated description"
|
||||
# Verify all tagged objects were removed
|
||||
assert (
|
||||
len(db.session.query(TaggedObject).filter_by(tag_id=updated_tag.id).all()) == 0
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user