diff --git a/openedx/core/djangoapps/content_tagging/api.py b/openedx/core/djangoapps/content_tagging/api.py index 70aa7e2150..0df9e47003 100644 --- a/openedx/core/djangoapps/content_tagging/api.py +++ b/openedx/core/djangoapps/content_tagging/api.py @@ -19,6 +19,7 @@ def create_taxonomy( allow_multiple=True, allow_free_text=False, orgs: list[Organization] | None = None, + export_id: str | None = None, ) -> Taxonomy: """ Creates, saves, and returns a new Taxonomy with the given attributes. @@ -29,6 +30,7 @@ def create_taxonomy( enabled=enabled, allow_multiple=allow_multiple, allow_free_text=allow_free_text, + export_id=export_id, ) if orgs is not None: diff --git a/openedx/core/djangoapps/content_tagging/rest_api/v1/tests/test_views.py b/openedx/core/djangoapps/content_tagging/rest_api/v1/tests/test_views.py index 20b1deb661..f65be2cd8c 100644 --- a/openedx/core/djangoapps/content_tagging/rest_api/v1/tests/test_views.py +++ b/openedx/core/djangoapps/content_tagging/rest_api/v1/tests/test_views.py @@ -62,6 +62,7 @@ def check_taxonomy( allow_free_text=False, system_defined=False, visible_to_authors=True, + export_id=None, **_ ): """ @@ -75,6 +76,7 @@ def check_taxonomy( assert data["allow_free_text"] == allow_free_text assert data["system_defined"] == system_defined assert data["visible_to_authors"] == visible_to_authors + assert data["export_id"] == export_id class TestTaxonomyObjectsMixin: @@ -174,11 +176,11 @@ class TestTaxonomyObjectsMixin: Create taxonomies for testing """ # Orphaned taxonomy - self.ot1 = Taxonomy.objects.create(name="ot1", enabled=True) - self.ot2 = Taxonomy.objects.create(name="ot2", enabled=False) + self.ot1 = tagging_api.create_taxonomy(name="ot1", enabled=True) + self.ot2 = tagging_api.create_taxonomy(name="ot2", enabled=False) # System defined taxonomy - self.st1 = Taxonomy.objects.create(name="st1", enabled=True) + self.st1 = tagging_api.create_taxonomy(name="st1", enabled=True) self.st1.taxonomy_class = SystemDefinedTaxonomy self.st1.save() TaxonomyOrg.objects.create( @@ -186,7 +188,7 @@ class TestTaxonomyObjectsMixin: rel_type=TaxonomyOrg.RelType.OWNER, org=None, ) - self.st2 = Taxonomy.objects.create(name="st2", enabled=False) + self.st2 = tagging_api.create_taxonomy(name="st2", enabled=False) self.st2.taxonomy_class = SystemDefinedTaxonomy self.st2.save() TaxonomyOrg.objects.create( @@ -195,12 +197,12 @@ class TestTaxonomyObjectsMixin: ) # Global taxonomy, which contains tags - self.t1 = Taxonomy.objects.create(name="t1", enabled=True) + self.t1 = tagging_api.create_taxonomy(name="t1", enabled=True) TaxonomyOrg.objects.create( taxonomy=self.t1, rel_type=TaxonomyOrg.RelType.OWNER, ) - self.t2 = Taxonomy.objects.create(name="t2", enabled=False) + self.t2 = tagging_api.create_taxonomy(name="t2", enabled=False) TaxonomyOrg.objects.create( taxonomy=self.t2, rel_type=TaxonomyOrg.RelType.OWNER, @@ -213,11 +215,11 @@ class TestTaxonomyObjectsMixin: Tag.objects.create(taxonomy=self.t1, value="anvil", parent=root1) # OrgA taxonomy - self.tA1 = Taxonomy.objects.create(name="tA1", enabled=True) + self.tA1 = tagging_api.create_taxonomy(name="tA1", enabled=True) TaxonomyOrg.objects.create( taxonomy=self.tA1, org=self.orgA, rel_type=TaxonomyOrg.RelType.OWNER,) - self.tA2 = Taxonomy.objects.create(name="tA2", enabled=False) + self.tA2 = tagging_api.create_taxonomy(name="tA2", enabled=False) TaxonomyOrg.objects.create( taxonomy=self.tA2, org=self.orgA, @@ -225,13 +227,13 @@ class TestTaxonomyObjectsMixin: ) # OrgB taxonomy - self.tB1 = Taxonomy.objects.create(name="tB1", enabled=True) + self.tB1 = tagging_api.create_taxonomy(name="tB1", enabled=True) TaxonomyOrg.objects.create( taxonomy=self.tB1, org=self.orgB, rel_type=TaxonomyOrg.RelType.OWNER, ) - self.tB2 = Taxonomy.objects.create(name="tB2", enabled=False) + self.tB2 = tagging_api.create_taxonomy(name="tB2", enabled=False) TaxonomyOrg.objects.create( taxonomy=self.tB2, org=self.orgB, @@ -239,7 +241,7 @@ class TestTaxonomyObjectsMixin: ) # OrgA and OrgB taxonomy - self.tBA1 = Taxonomy.objects.create(name="tBA1", enabled=True) + self.tBA1 = tagging_api.create_taxonomy(name="tBA1", enabled=True) TaxonomyOrg.objects.create( taxonomy=self.tBA1, org=self.orgA, @@ -250,7 +252,7 @@ class TestTaxonomyObjectsMixin: org=self.orgB, rel_type=TaxonomyOrg.RelType.OWNER, ) - self.tBA2 = Taxonomy.objects.create(name="tBA2", enabled=False) + self.tBA2 = tagging_api.create_taxonomy(name="tBA2", enabled=False) TaxonomyOrg.objects.create( taxonomy=self.tBA2, org=self.orgA, @@ -466,6 +468,7 @@ class TestTaxonomyListCreateViewSet(TestTaxonomyObjectsMixin, APITestCase): "description": "This is a description", "enabled": True, "allow_multiple": True, + "export_id": "taxonomy_data", } if user_attr: @@ -1029,6 +1032,7 @@ class TestTaxonomyUpdateViewSet(TestTaxonomyChangeMixin, APITestCase): "name": "new name", "description": taxonomy.description, "enabled": taxonomy.enabled, + "export_id": taxonomy.export_id, }, ) @@ -1069,6 +1073,7 @@ class TestTaxonomyPatchViewSet(TestTaxonomyChangeMixin, APITestCase): "name": "new name", "description": taxonomy.description, "enabled": taxonomy.enabled, + "export_id": taxonomy.export_id, }, ) @@ -1327,8 +1332,14 @@ class TestObjectTagMixin(TestTaxonomyObjectsMixin): block_id='block_id' ) - self.multiple_taxonomy = Taxonomy.objects.create(name="Multiple Taxonomy", allow_multiple=True) - self.single_value_taxonomy = Taxonomy.objects.create(name="Required Taxonomy", allow_multiple=False) + self.multiple_taxonomy = tagging_api.create_taxonomy( + name="Multiple Taxonomy", + allow_multiple=True, + ) + self.single_value_taxonomy = tagging_api.create_taxonomy( + name="Required Taxonomy", + allow_multiple=False, + ) for i in range(20): # Valid ObjectTags Tag.objects.create(taxonomy=self.tA1, value=f"Tag {i}") @@ -1336,7 +1347,10 @@ class TestObjectTagMixin(TestTaxonomyObjectsMixin): Tag.objects.create(taxonomy=self.multiple_taxonomy, value=f"Tag {i}") Tag.objects.create(taxonomy=self.single_value_taxonomy, value=f"Tag {i}") - self.open_taxonomy = Taxonomy.objects.create(name="Enabled Free-Text Taxonomy", allow_free_text=True) + self.open_taxonomy = tagging_api.create_taxonomy( + name="Enabled Free-Text Taxonomy", + allow_free_text=True, + ) # Add org permissions to taxonomy TaxonomyOrg.objects.create( @@ -1701,6 +1715,7 @@ class TestCreateImportView(ImportTaxonomyMixin, APITestCase): { "taxonomy_name": "Imported Taxonomy name", "taxonomy_description": "Imported Taxonomy description", + "taxonomy_export_id": "imported_taxonomy", "file": file, }, format="multipart" @@ -1711,6 +1726,7 @@ class TestCreateImportView(ImportTaxonomyMixin, APITestCase): taxonomy = response.data assert taxonomy["name"] == "Imported Taxonomy name" assert taxonomy["description"] == "Imported Taxonomy description" + assert taxonomy["export_id"] == "imported_taxonomy" # Check if the tags were created url = TAXONOMY_TAGS_URL.format(pk=taxonomy["id"]) @@ -1746,6 +1762,7 @@ class TestCreateImportView(ImportTaxonomyMixin, APITestCase): { "taxonomy_name": "Imported Taxonomy name", "taxonomy_description": "Imported Taxonomy description", + "taxonomy_export_id": "imported_taxonomy", "file": file, }, format="multipart" @@ -1756,6 +1773,7 @@ class TestCreateImportView(ImportTaxonomyMixin, APITestCase): taxonomy = response.data assert taxonomy["name"] == "Imported Taxonomy name" assert taxonomy["description"] == "Imported Taxonomy description" + assert taxonomy["export_id"] == "imported_taxonomy" # Check if the tags were created url = TAXONOMY_TAGS_URL.format(pk=taxonomy["id"]) @@ -1780,6 +1798,7 @@ class TestCreateImportView(ImportTaxonomyMixin, APITestCase): { "taxonomy_name": "Imported Taxonomy name", "taxonomy_description": "Imported Taxonomy description", + "taxonomy_export_id": "imported_taxonomy", }, format="multipart" ) @@ -1804,6 +1823,7 @@ class TestCreateImportView(ImportTaxonomyMixin, APITestCase): url, { "taxonomy_description": "Imported Taxonomy description", + "taxonomy_export_id": "imported_taxonomy", "file": file, }, format="multipart" @@ -1814,6 +1834,29 @@ class TestCreateImportView(ImportTaxonomyMixin, APITestCase): # Check if the taxonomy was not created assert not Taxonomy.objects.filter(name="Imported Taxonomy name").exists() + @ddt.data( + "csv", + "json", + ) + def test_import_no_export_id(self, file_format) -> None: + url = TAXONOMY_CREATE_IMPORT_URL + file = SimpleUploadedFile(f"taxonomy.{file_format}", b"invalid file content") + self.client.force_authenticate(user=self.staff) + response = self.client.post( + url, + { + "taxonomt_name": "Imported Taxonomy name", + "taxonomy_description": "Imported Taxonomy description", + "file": file, + }, + format="multipart" + ) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["taxonomy_export_id"][0] == "This field is required." + + # Check if the taxonomy was not created + assert not Taxonomy.objects.filter(name="Imported Taxonomy name").exists() + def test_import_invalid_format(self) -> None: """ Tests importing a taxonomy with an invalid file format. @@ -1826,6 +1869,7 @@ class TestCreateImportView(ImportTaxonomyMixin, APITestCase): { "taxonomy_name": "Imported Taxonomy name", "taxonomy_description": "Imported Taxonomy description", + "taxonomy_export_id": "imported_taxonomy_id", "file": file, }, format="multipart" @@ -1852,6 +1896,7 @@ class TestCreateImportView(ImportTaxonomyMixin, APITestCase): { "taxonomy_name": "Imported Taxonomy name", "taxonomy_description": "Imported Taxonomy description", + "taxonomy_export_id": "imported_taxonomy", "file": file, }, format="multipart" @@ -1881,6 +1926,7 @@ class TestCreateImportView(ImportTaxonomyMixin, APITestCase): { "taxonomy_name": "Imported Taxonomy name", "taxonomy_description": "Imported Taxonomy description", + "taxonomy_export_id": "imported_taxonomy", "file": file, }, format="multipart" @@ -1900,7 +1946,7 @@ class TestImportTagsView(ImportTaxonomyMixin, APITestCase): def setUp(self): ImportTaxonomyMixin.setUp(self) - self.taxonomy = Taxonomy.objects.create( + self.taxonomy = tagging_api.create_taxonomy( name="Test import taxonomy", ) tag_1 = Tag.objects.create( @@ -2009,6 +2055,7 @@ class TestImportTagsView(ImportTaxonomyMixin, APITestCase): { "taxonomy_name": "Imported Taxonomy name", "taxonomy_description": "Imported Taxonomy description", + "taxonomy_export_id": "imported_taxonomy", "file": file, }, format="multipart" @@ -2078,6 +2125,7 @@ class TestImportTagsView(ImportTaxonomyMixin, APITestCase): { "taxonomy_name": "Imported Taxonomy name", "taxonomy_description": "Imported Taxonomy description", + "taxonomt_export_id": "imported_taxonomy", "file": file, }, format="multipart" diff --git a/requirements/constraints.txt b/requirements/constraints.txt index d286724c26..bd78dbb0d6 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -108,7 +108,7 @@ libsass==0.10.0 click==8.1.6 # pinning this version to avoid updates while the library is being developed -openedx-learning==0.4.4 +openedx-learning==0.5.1 # Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise. openai<=0.28.1 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 7931a98169..7d27ac9978 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -782,7 +782,7 @@ openedx-filters==1.6.0 # via # -r requirements/edx/kernel.in # lti-consumer-xblock -openedx-learning==0.4.4 +openedx-learning==0.5.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 4f60f09567..bfe3fbd2b7 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -1313,7 +1313,7 @@ openedx-filters==1.6.0 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # lti-consumer-xblock -openedx-learning==0.4.4 +openedx-learning==0.5.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 85bbca7f9a..92d84425d0 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -924,7 +924,7 @@ openedx-filters==1.6.0 # via # -r requirements/edx/base.txt # lti-consumer-xblock -openedx-learning==0.4.4 +openedx-learning==0.5.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 3f9553e501..eae1dbdfc3 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -982,7 +982,7 @@ openedx-filters==1.6.0 # via # -r requirements/edx/base.txt # lti-consumer-xblock -openedx-learning==0.4.4 +openedx-learning==0.5.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt