diff --git a/openedx_tagging/core/tagging/import_export/actions.py b/openedx_tagging/core/tagging/import_export/actions.py index b968a6b0b..679f36ae2 100644 --- a/openedx_tagging/core/tagging/import_export/actions.py +++ b/openedx_tagging/core/tagging/import_export/actions.py @@ -373,8 +373,7 @@ class DeleteTag(ImportAction): """ def __str__(self) -> str: - taxonomy_tag = self._get_tag() - return str(_("Delete tag (external_id={external_id})").format(external_id=taxonomy_tag.external_id)) + return str(_("Delete tag (external_id={external_id})").format(external_id=self.tag.id)) name = "delete" @@ -397,8 +396,11 @@ def execute(self) -> None: """ Delete a tag """ - taxonomy_tag = self._get_tag() - taxonomy_tag.delete() + try: + taxonomy_tag = self._get_tag() + taxonomy_tag.delete() + except Tag.DoesNotExist: + pass # The tag may be already cascade deleted if the parent tag was deleted class WithoutChanges(ImportAction): diff --git a/openedx_tagging/core/tagging/import_export/import_plan.py b/openedx_tagging/core/tagging/import_export/import_plan.py index f58390df1..b5ce41305 100644 --- a/openedx_tagging/core/tagging/import_export/import_plan.py +++ b/openedx_tagging/core/tagging/import_export/import_plan.py @@ -93,14 +93,16 @@ def _build_delete_actions(self, tags: dict): # Verify if there is not a parent update before if not self._search_parent_update(child.external_id, tag.external_id): # Change parent to avoid delete childs - self._build_action( - UpdateParentTag, - TagItem( - id=child.external_id, - value=child.value, - parent_id=None, - ), - ) + if child.external_id not in tags: + # Only update parent if the child is not going to be deleted + self._build_action( + UpdateParentTag, + TagItem( + id=child.external_id, + value=child.value, + parent_id=None, + ), + ) # Delete action self._build_action( diff --git a/tests/openedx_tagging/core/tagging/import_export/test_api.py b/tests/openedx_tagging/core/tagging/import_export/test_api.py index 9721fec44..039933d44 100644 --- a/tests/openedx_tagging/core/tagging/import_export/test_api.py +++ b/tests/openedx_tagging/core/tagging/import_export/test_api.py @@ -8,7 +8,7 @@ import openedx_tagging.core.tagging.import_export.api as import_export_api from openedx_tagging.core.tagging.import_export import ParserFormat -from openedx_tagging.core.tagging.models import LanguageTaxonomy, TagImportTask, TagImportTaskState, Taxonomy +from openedx_tagging.core.tagging.models import LanguageTaxonomy, Tag, TagImportTask, TagImportTaskState, Taxonomy from .mixins import TestImportExportMixin @@ -197,3 +197,42 @@ def test_import_with_export_output(self) -> None: if tag.parent: assert new_tag.parent assert tag.parent.external_id == new_tag.parent.external_id + + def test_import_removing_with_childs(self) -> None: + """ + Test import need to remove childs with parents that will also be removed + """ + new_taxonomy = Taxonomy(name="New taxonomy") + new_taxonomy.save() + level2 = Tag.objects.create( + id=1000, + external_id="tag_2", + value="Tag 2", + taxonomy=new_taxonomy, + ) + level1 = Tag.objects.create( + id=1001, + external_id="tag_1", + value="Tag 1", + taxonomy=new_taxonomy, + ) + level3 = Tag.objects.create( + id=1002, + external_id="tag_3", + value="Tag 3", + taxonomy=new_taxonomy, + ) + level2.parent = level1 + level2.save() + + level3.parent = level3 + level3.save() + + # Import with empty tags, to remove all tags + importFile = BytesIO(json.dumps({"tags": []}).encode()) + assert import_export_api.import_tags( + new_taxonomy, + importFile, + ParserFormat.JSON, + replace=True, + ) diff --git a/tests/openedx_tagging/core/tagging/import_export/test_import_plan.py b/tests/openedx_tagging/core/tagging/import_export/test_import_plan.py index 915ffb724..c8dbaa0c1 100644 --- a/tests/openedx_tagging/core/tagging/import_export/test_import_plan.py +++ b/tests/openedx_tagging/core/tagging/import_export/test_import_plan.py @@ -60,23 +60,15 @@ def test_build_delete_actions(self) -> None: assert len(self.import_plan.errors) == 0 # Check actions in order - # #1 Update parent of 'tag_2' - assert self.import_plan.actions[0].name == 'update_parent' - assert self.import_plan.actions[0].tag.id == 'tag_2' - assert self.import_plan.actions[0].tag.parent_id is None - # #2 Delete 'tag_1' + # #1 Delete 'tag_1' + assert self.import_plan.actions[0].name == 'delete' + assert self.import_plan.actions[0].tag.id == 'tag_1' + # #2 Delete 'tag_2' assert self.import_plan.actions[1].name == 'delete' - assert self.import_plan.actions[1].tag.id == 'tag_1' - # #3 Delete 'tag_2' + assert self.import_plan.actions[1].tag.id == 'tag_2' + # #3 Delete 'tag_3' assert self.import_plan.actions[2].name == 'delete' - assert self.import_plan.actions[2].tag.id == 'tag_2' - # #4 Update parent of 'tag_4' - assert self.import_plan.actions[3].name == 'update_parent' - assert self.import_plan.actions[3].tag.id == 'tag_4' - assert self.import_plan.actions[3].tag.parent_id is None - # #5 Delete 'tag_3' - assert self.import_plan.actions[4].name == 'delete' - assert self.import_plan.actions[4].tag.id == 'tag_3' + assert self.import_plan.actions[2].tag.id == 'tag_3' @ddt.data( # Test valid actions @@ -193,10 +185,6 @@ def test_build_delete_actions(self) -> None: 'name': 'without_changes', 'id': 'tag_4', }, - { - 'name': 'update_parent', - 'id': 'tag_2', - }, { 'name': 'delete', 'id': 'tag_1', @@ -330,13 +318,11 @@ def test_generate_actions(self, tags, replace, expected_errors, expected_actions "Import plan for Import Taxonomy Test\n" "--------------------------------\n" "#1: No changes needed for tag (external_id=tag_4)\n" - "#2: Update the parent of tag (external_id=tag_2) from parent (external_id=tag_1) " - "to parent (external_id=None).\n" - "#3: Delete tag (external_id=tag_1)\n" - "#4: Delete tag (external_id=tag_2)\n" - "#5: Update the parent of tag (external_id=tag_4) from parent (external_id=tag_3) " + "#2: Delete tag (external_id=tag_1)\n" + "#3: Delete tag (external_id=tag_2)\n" + "#4: Update the parent of tag (external_id=tag_4) from parent (external_id=tag_3) " "to parent (external_id=None).\n" - "#6: Delete tag (external_id=tag_3)\n" + "#5: Delete tag (external_id=tag_3)\n" ), ) @ddt.unpack