diff --git a/openedx_learning/__init__.py b/openedx_learning/__init__.py index 13243d24..0dc1d505 100644 --- a/openedx_learning/__init__.py +++ b/openedx_learning/__init__.py @@ -1,4 +1,4 @@ """ Open edX Learning ("Learning Core"). """ -__version__ = "0.4.4" +__version__ = "0.5.0" diff --git a/openedx_tagging/core/tagging/api.py b/openedx_tagging/core/tagging/api.py index 9cde39a4..c416f6f0 100644 --- a/openedx_tagging/core/tagging/api.py +++ b/openedx_tagging/core/tagging/api.py @@ -17,6 +17,7 @@ from django.db import models, transaction from django.db.models import F, QuerySet, Value from django.db.models.functions import Coalesce, Concat, Lower +from django.utils.text import slugify from django.utils.translation import gettext as _ from .data import TagDataQuerySet @@ -34,19 +35,26 @@ def create_taxonomy( allow_multiple=True, allow_free_text=False, taxonomy_class: type[Taxonomy] | None = None, + export_id: str | None = None, ) -> Taxonomy: """ Creates, saves, and returns a new Taxonomy with the given attributes. """ + if not export_id: + export_id = f"{Taxonomy.objects.count() + 1}-{slugify(name, allow_unicode=True)}" + taxonomy = Taxonomy( name=name, description=description or "", enabled=enabled, allow_multiple=allow_multiple, allow_free_text=allow_free_text, + export_id=export_id, ) if taxonomy_class: taxonomy.taxonomy_class = taxonomy_class + + taxonomy.full_clean() taxonomy.save() return taxonomy.cast() diff --git a/openedx_tagging/core/tagging/import_export/api.py b/openedx_tagging/core/tagging/import_export/api.py index 34c49687..94d628c5 100644 --- a/openedx_tagging/core/tagging/import_export/api.py +++ b/openedx_tagging/core/tagging/import_export/api.py @@ -121,7 +121,7 @@ def import_tags( task.end_success() return True, task, tag_import_plan - except Exception as exception: # pylint: disable=broad-exception-caught + except Exception as exception: # Log any exception task.log_exception(exception) return False, task, None diff --git a/openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py b/openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py new file mode 100644 index 00000000..cd654dd0 --- /dev/null +++ b/openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.22 on 2024-01-25 14:20 + +from django.db import migrations, models +from django.utils.text import slugify + + +def migrate_export_id(apps, schema_editor): + Taxonomy = apps.get_model("oel_tagging", "Taxonomy") + for taxonomy in Taxonomy.objects.all(): + # Adds the id of the taxonomy to avoid duplicates + taxonomy.export_id = f"{taxonomy.id}-{slugify(taxonomy.name, allow_unicode=True)}" + taxonomy.save(update_fields=["export_id"]) + +def reverse(app, schema_editor): + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('oel_tagging', '0014_minor_fixes'), + ] + + operations = [ + # Create the field allowing null + migrations.AddField( + model_name='taxonomy', + name='export_id', + field=models.CharField(help_text="User-facing ID that is used on import/export. Should only contain alphanumeric characters or '_' '-' '.'", max_length=255, null=True, unique=True), + ), + # Fill the field for created taxonomies + migrations.RunPython(migrate_export_id, reverse), + # Alter the field to not allowing null + migrations.AlterField( + model_name='taxonomy', + name='export_id', + field=models.CharField(help_text="User-facing ID that is used on import/export. Should only contain alphanumeric characters or '_' '-' '.'", max_length=255, null=False, unique=True), + ), + ] diff --git a/openedx_tagging/core/tagging/models/base.py b/openedx_tagging/core/tagging/models/base.py index 13beeb0a..c9b4439e 100644 --- a/openedx_tagging/core/tagging/models/base.py +++ b/openedx_tagging/core/tagging/models/base.py @@ -4,6 +4,7 @@ from __future__ import annotations import logging +import re from typing import List from django.core.exceptions import ValidationError @@ -228,6 +229,19 @@ class Taxonomy(models.Model): "Indicates whether this taxonomy should be visible to object authors." ), ) + # External ID that should only be used on import/export. + # NOT use for any other purposes, you can use the numeric ID of the model instead; + # this id is editable. + export_id = models.CharField( + null=False, + blank=False, + max_length=255, + help_text=_( + "User-facing ID that is used on import/export." + " Should only contain alphanumeric characters or '_' '-' '.'" + ), + unique=True, + ) _taxonomy_class = models.CharField( null=True, max_length=255, @@ -300,6 +314,14 @@ def system_defined(self) -> bool: """ return False + def clean(self): + super().clean() + + if not re.match(r'^[\w\-.]+$', self.export_id): + raise ValidationError( + "The export_id should only contain alphanumeric characters or '_' '-' '.'" + ) + def cast(self): """ Returns the current Taxonomy instance cast into its taxonomy_class. @@ -336,6 +358,7 @@ def copy(self, taxonomy: Taxonomy) -> Taxonomy: self.allow_multiple = taxonomy.allow_multiple self.allow_free_text = taxonomy.allow_free_text self.visible_to_authors = taxonomy.visible_to_authors + self.export_id = taxonomy.export_id self._taxonomy_class = taxonomy._taxonomy_class # pylint: disable=protected-access return self diff --git a/openedx_tagging/core/tagging/rest_api/v1/serializers.py b/openedx_tagging/core/tagging/rest_api/v1/serializers.py index a3af0bbb..10e3730e 100644 --- a/openedx_tagging/core/tagging/rest_api/v1/serializers.py +++ b/openedx_tagging/core/tagging/rest_api/v1/serializers.py @@ -72,6 +72,7 @@ class TaxonomySerializer(UserPermissionsSerializerMixin, serializers.ModelSerial can_change_taxonomy = serializers.SerializerMethodField(method_name='get_can_change') can_delete_taxonomy = serializers.SerializerMethodField(method_name='get_can_delete') can_tag_object = serializers.SerializerMethodField() + export_id = serializers.CharField(required=False) class Meta: model = Taxonomy @@ -88,6 +89,7 @@ class Meta: "can_change_taxonomy", "can_delete_taxonomy", "can_tag_object", + "export_id", ] def to_representation(self, instance): @@ -332,6 +334,7 @@ class TaxonomyImportNewBodySerializer(TaxonomyImportBodySerializer): # pylint: """ taxonomy_name = serializers.CharField(required=True) taxonomy_description = serializers.CharField(default="") + taxonomy_export_id = serializers.CharField(required=True) class TagImportTaskSerializer(serializers.ModelSerializer): diff --git a/openedx_tagging/core/tagging/rest_api/v1/views.py b/openedx_tagging/core/tagging/rest_api/v1/views.py index 3abf4f0d..5eed0e5a 100644 --- a/openedx_tagging/core/tagging/rest_api/v1/views.py +++ b/openedx_tagging/core/tagging/rest_api/v1/views.py @@ -3,6 +3,7 @@ """ from __future__ import annotations +from django.core import exceptions from django.db import models from django.http import Http404, HttpResponse from rest_framework import mixins, status @@ -57,6 +58,11 @@ class TaxonomyView(ModelViewSet): """ View to list, create, retrieve, update, delete, export or import Taxonomies. + TODO: We need to add a perform_update and call the api update function when is created. + This is because it is necessary to call the model validations. (`full_clean()`). + Currently those validations are not run, which means we could update `export_id` with any value + through this api. + **List Query Parameters** * enabled (optional) - Filter by enabled status. Valid values: true, false, 1, 0, "true", "false", "1" @@ -87,6 +93,8 @@ class TaxonomyView(ModelViewSet): **Create Parameters** * name (required): User-facing label used when applying tags from this taxonomy to Open edX objects. + * export_id (required): User-facing ID that is used on import/export. + Should only contain alphanumeric characters or '_' '-' '.'. * description (optional): Provides extra information for the user when applying tags from this taxonomy to an object. * enabled (optional): Only enabled taxonomies will be shown to authors @@ -101,6 +109,7 @@ class TaxonomyView(ModelViewSet): POST api/tagging/v1/taxonomy - Create a taxonomy { "name": "Taxonomy Name", + "export_id": "taxonomy_export_id", "description": "This is a description", "enabled": True, "allow_multiple": True, @@ -117,6 +126,8 @@ class TaxonomyView(ModelViewSet): **Update Request Body** * name (optional): User-facing label used when applying tags from this taxonomy to Open edX objects. + * export_id (optional): User-facing ID that is used on import/export. + Should only contain alphanumeric characters or '_' '-' '.'. * description (optional): Provides extra information for the user when applying tags from this taxonomy to an object. * enabled (optional): Only enabled taxonomies will be shown to authors. @@ -129,6 +140,7 @@ class TaxonomyView(ModelViewSet): PUT api/tagging/v1/taxonomy/:pk - Update a taxonomy { "name": "Taxonomy New Name", + "export_id": "taxonomy_new_name", "description": "This is a new description", "enabled": False, "allow_multiple": False, @@ -174,6 +186,7 @@ class TaxonomyView(ModelViewSet): POST /tagging/rest_api/v1/taxonomy/import/ { "taxonomy_name": "Taxonomy Name", + "taxonomy_export_id": "this_is_the_export_id", "taxonomy_description": "This is a description", "file": , } @@ -245,7 +258,10 @@ def perform_create(self, serializer) -> None: """ Create a new taxonomy. """ - serializer.instance = create_taxonomy(**serializer.validated_data) + try: + serializer.instance = create_taxonomy(**serializer.validated_data) + except exceptions.ValidationError as e: + raise ValidationError() from e @action(detail=True, methods=["get"]) def export(self, request, **_kwargs) -> HttpResponse: @@ -286,11 +302,12 @@ def create_import(self, request: Request, **_kwargs) -> Response: body.is_valid(raise_exception=True) taxonomy_name = body.validated_data["taxonomy_name"] + taxonomy_export_id = body.validated_data["taxonomy_export_id"] taxonomy_description = body.validated_data["taxonomy_description"] file = body.validated_data["file"].file parser_format = body.validated_data["parser_format"] - taxonomy = create_taxonomy(taxonomy_name, taxonomy_description) + taxonomy = create_taxonomy(taxonomy_name, taxonomy_description, export_id=taxonomy_export_id) try: import_success, task, _plan = import_tags(taxonomy, file, parser_format) diff --git a/tests/openedx_tagging/core/fixtures/tagging.yaml b/tests/openedx_tagging/core/fixtures/tagging.yaml index 164b9399..cd0204f3 100644 --- a/tests/openedx_tagging/core/fixtures/tagging.yaml +++ b/tests/openedx_tagging/core/fixtures/tagging.yaml @@ -230,6 +230,7 @@ enabled: true allow_multiple: false allow_free_text: false + export_id: life_on_earth - model: oel_tagging.taxonomy pk: 3 fields: @@ -238,6 +239,7 @@ enabled: true allow_multiple: false allow_free_text: false + export_id: user_authors _taxonomy_class: openedx_tagging.core.tagging.models.system_defined.UserSystemDefinedTaxonomy - model: oel_tagging.taxonomy pk: 4 @@ -247,6 +249,7 @@ enabled: true allow_multiple: false allow_free_text: false + export_id: system_defined_taxonomy _taxonomy_class: openedx_tagging.core.tagging.models.system_defined.SystemDefinedTaxonomy - model: oel_tagging.taxonomy pk: 5 @@ -256,4 +259,4 @@ enabled: true allow_multiple: false allow_free_text: false - + export_id: import_taxonomy_test 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 d23eb6dd..441587c4 100644 --- a/tests/openedx_tagging/core/tagging/import_export/test_api.py +++ b/tests/openedx_tagging/core/tagging/import_export/test_api.py @@ -197,7 +197,7 @@ def test_import_with_export_output(self) -> None: parser_format, ) file = BytesIO(output.encode()) - new_taxonomy = Taxonomy(name="New taxonomy") + new_taxonomy = Taxonomy(name="New taxonomy", export_id=f"new_taxonomy_{parser_format}") new_taxonomy.save() result, _task, _plan = import_export_api.import_tags( new_taxonomy, diff --git a/tests/openedx_tagging/core/tagging/test_api.py b/tests/openedx_tagging/core/tagging/test_api.py index 5befef5c..9cfeec37 100644 --- a/tests/openedx_tagging/core/tagging/test_api.py +++ b/tests/openedx_tagging/core/tagging/test_api.py @@ -56,6 +56,7 @@ def test_create_taxonomy(self) -> None: # Note: we must specify '-> None' to op "enabled": False, "allow_multiple": True, "allow_free_text": True, + "export_id": "difficulty", } taxonomy = tagging_api.create_taxonomy(**params) for param, value in params.items(): @@ -63,6 +64,16 @@ def test_create_taxonomy(self) -> None: # Note: we must specify '-> None' to op assert not taxonomy.system_defined assert taxonomy.visible_to_authors + def test_create_taxonomy_without_export_id(self) -> None: + params: dict[str, Any] = { + "name": "Taxonomy Data: test 3", + "enabled": False, + "allow_multiple": True, + "allow_free_text": True, + } + taxonomy = tagging_api.create_taxonomy(**params) + assert taxonomy.export_id == "7-taxonomy-data-test-3" + def test_bad_taxonomy_class(self) -> None: with self.assertRaises(ValueError) as exc: tagging_api.create_taxonomy( @@ -242,7 +253,10 @@ def test_get_children_tags_invalid_taxonomy(self) -> None: """ Calling get_children_tags on free text taxonomies gives an error. """ - free_text_taxonomy = Taxonomy.objects.create(allow_free_text=True, name="FreeText") + free_text_taxonomy = tagging_api.create_taxonomy( + name="FreeText", + allow_free_text=True, + ) tagging_api.tag_object(object_id="obj1", taxonomy=free_text_taxonomy, tags=["some_tag"]) with self.assertRaises(ValueError) as exc: tagging_api.get_children_tags(free_text_taxonomy, "some_tag") @@ -257,7 +271,11 @@ def test_get_children_tags_no_children(self) -> None: def test_resync_object_tags(self) -> None: self.taxonomy.allow_multiple = True self.taxonomy.save() - open_taxonomy = Taxonomy.objects.create(name="Freetext Life", allow_free_text=True, allow_multiple=True) + open_taxonomy = tagging_api.create_taxonomy( + name="Freetext Life", + allow_free_text=True, + allow_multiple=True, + ) object_id = "obj1" # Create some tags: diff --git a/tests/openedx_tagging/core/tagging/test_models.py b/tests/openedx_tagging/core/tagging/test_models.py index c730b16b..303e7e5f 100644 --- a/tests/openedx_tagging/core/tagging/test_models.py +++ b/tests/openedx_tagging/core/tagging/test_models.py @@ -38,7 +38,7 @@ def setUp(self): self.system_taxonomy = Taxonomy.objects.get(name="System defined taxonomy") self.language_taxonomy = LanguageTaxonomy.objects.get(name="Languages") self.user_taxonomy = Taxonomy.objects.get(name="User Authors").cast() - self.free_text_taxonomy = Taxonomy.objects.create(name="Free Text", allow_free_text=True) + self.free_text_taxonomy = api.create_taxonomy(name="Free Text", allow_free_text=True) # References to some tags: self.archaea = get_tag("Archaea") @@ -111,10 +111,10 @@ def create_100_taxonomies(self): """ dummy_taxonomies = [] for i in range(100): - taxonomy = Taxonomy.objects.create( + taxonomy = api.create_taxonomy( name=f"ZZ Dummy Taxonomy {i:03}", allow_free_text=True, - allow_multiple=True + allow_multiple=True, ) ObjectTag.objects.create( object_id="limit_tag_count", @@ -203,7 +203,7 @@ def test_taxonomy_cast(self): def test_taxonomy_cast_import_error(self): taxonomy = Taxonomy.objects.create( - name="Invalid cast", _taxonomy_class="not.a.class" + name="Invalid cast", export_id='invalid_cast', _taxonomy_class="not.a.class" ) # Error is logged, but ignored. cast_taxonomy = taxonomy.cast() @@ -268,6 +268,50 @@ def test_no_tab(self): with pytest.raises(ValidationError): api.add_tag_to_taxonomy(self.taxonomy, "first\tsecond") + @ddt.data( + ("test"), + ("lightcast"), + ("lightcast-skills"), + ("io.lightcast.open-skills"), + ("-3_languages"), + ("LIGHTCAST_V17"), + ("liGhtCaST"), + ("日本"), + ("Québec"), + ("123456789"), + ) + def test_export_id_format_valid(self, export_id): + self.taxonomy.export_id = export_id + self.taxonomy.full_clean() + + @ddt.data( + ("LightCast Skills"), + ("One,Two,Three"), + (" "), + ("Foo:Bar"), + ("X;Y;Z"), + ('"quotes"'), + (" test"), + ) + def test_export_id_format_invalid(self, export_id): + self.taxonomy.export_id = export_id + with pytest.raises(ValidationError): + self.taxonomy.full_clean() + + def test_unique_export_id(self): + # Valid + self.taxonomy.export_id = 'test_1' + self.free_text_taxonomy.export_id = 'test_2' + self.taxonomy.save() + self.free_text_taxonomy.save() + + # Invalid + self.taxonomy.export_id = 'test_1' + self.free_text_taxonomy.export_id = 'test_1' + self.taxonomy.save() + with pytest.raises(IntegrityError): + self.free_text_taxonomy.save() + @ddt.ddt class TestFilteredTagsClosedTaxonomy(TestTagTaxonomyMixin, TestCase): @@ -557,7 +601,7 @@ class TestFilteredTagsFreeTextTaxonomy(TestCase): def setUp(self): super().setUp() - self.taxonomy = Taxonomy.objects.create(allow_free_text=True, name="FreeText") + self.taxonomy = api.create_taxonomy(allow_free_text=True, name="FreeText") # The "triple" tag will be applied to three objects, "double" to two, and "solo" to one: api.tag_object(object_id="obj1", taxonomy=self.taxonomy, tags=["triple"]) api.tag_object(object_id="obj2", taxonomy=self.taxonomy, tags=["triple", "double"]) diff --git a/tests/openedx_tagging/core/tagging/test_system_defined_models.py b/tests/openedx_tagging/core/tagging/test_system_defined_models.py index 3c0cb676..e75754ac 100644 --- a/tests/openedx_tagging/core/tagging/test_system_defined_models.py +++ b/tests/openedx_tagging/core/tagging/test_system_defined_models.py @@ -93,12 +93,14 @@ def setUpClass(cls): taxonomy_class=LPTaxonomyTest, name="LearningPackage Taxonomy", allow_multiple=True, + export_id="learning_package_taxonomy", ) # Also create an "Author" taxonomy that can tag any object using user IDs/usernames: cls.author_taxonomy = UserSystemDefinedTaxonomy.objects.create( taxonomy_class=UserSystemDefinedTaxonomy, name="Authors", allow_multiple=True, + export_id="authors", ) def test_lp_taxonomy_validation(self): @@ -169,6 +171,7 @@ def test_case_insensitive_values(self): taxonomy_class=CaseInsensitiveTitleLPTaxonomy, name="LearningPackage Title Taxonomy", allow_multiple=True, + export_id="learning_package_title_taxonomy", ) api.tag_object(taxonomy, ["LEARNING PACKAGE 1"], object1_id) api.tag_object(taxonomy, ["Learning Package 1", "LEARNING PACKAGE 2"], object2_id) @@ -184,6 +187,7 @@ def test_multiple_taxonomies(self): taxonomy_class=UserSystemDefinedTaxonomy, name="Reviewer", allow_multiple=True, + export_id="reviewer", ) pr_1_id, pr_2_id = "pull_request_1", "pull_request_2" diff --git a/tests/openedx_tagging/core/tagging/test_views.py b/tests/openedx_tagging/core/tagging/test_views.py index c2ccd320..d29f56fd 100644 --- a/tests/openedx_tagging/core/tagging/test_views.py +++ b/tests/openedx_tagging/core/tagging/test_views.py @@ -56,6 +56,7 @@ def check_taxonomy( can_change_taxonomy=None, can_delete_taxonomy=None, can_tag_object=None, + export_id=None, ): """ Check taxonomy data @@ -71,6 +72,7 @@ def check_taxonomy( assert data["can_change_taxonomy"] == can_change_taxonomy assert data["can_delete_taxonomy"] == can_delete_taxonomy assert data["can_tag_object"] == can_tag_object + assert data["export_id"] == export_id class TestTaxonomyViewMixin(APITestCase): @@ -173,6 +175,7 @@ def test_list_taxonomy(self, user_attr: str | None, expected_status: int, tags_c "can_change_taxonomy": False, "can_delete_taxonomy": False, "can_tag_object": False, + "export_id": "-1-languages", }, { "id": taxonomy.id, @@ -190,6 +193,7 @@ def test_list_taxonomy(self, user_attr: str | None, expected_status: int, tags_c # can_tag_object is False because we default to not allowing users to tag arbitrary objects. # But specific uses of this code (like content_tagging) will override this perm for their use cases. "can_tag_object": False, + "export_id": "2-taxonomy-enabled-1", }, ] assert response.data.get("can_add_taxonomy") == is_admin @@ -294,6 +298,7 @@ def test_language_taxonomy(self): can_change_taxonomy=False, can_delete_taxonomy=False, can_tag_object=False, + export_id='-1-languages', ) @ddt.data( @@ -308,7 +313,11 @@ def test_language_taxonomy(self): def test_detail_taxonomy( self, user_attr: str | None, taxonomy_data: dict[str, bool], expected_status: int, is_admin=False, ): - create_data = {"name": "taxonomy detail test", **taxonomy_data} + create_data = { + "name": "taxonomy detail test", + "export_id": "taxonomy_detail_test", + **taxonomy_data + } taxonomy = api.create_taxonomy(**create_data) # type: ignore[arg-type] url = TAXONOMY_DETAIL_URL.format(pk=taxonomy.pk) @@ -361,6 +370,7 @@ def test_create_taxonomy(self, user_attr: str | None, expected_status: int): "description": "This is a description", "enabled": False, "allow_multiple": True, + "export_id": 'taxonomy_data_2', } if user_attr: @@ -381,9 +391,33 @@ def test_create_taxonomy(self, user_attr: str | None, expected_status: int): response = self.client.get(url) check_taxonomy(response.data, response.data["id"], **create_data) + def test_create_without_export_id(self): + url = TAXONOMY_LIST_URL + + create_data = { + "name": "Taxonomy Data 3", + "description": "This is a description", + "enabled": False, + "allow_multiple": True, + } + + self.client.force_authenticate(user=self.staff) + response = self.client.post(url, create_data, format="json") + assert response.status_code == status.HTTP_201_CREATED + create_data["can_change_taxonomy"] = True + create_data["can_delete_taxonomy"] = True + create_data["can_tag_object"] = False + check_taxonomy( + response.data, + response.data["id"], + export_id="2-taxonomy-data-3", + **create_data, + ) + @ddt.data( {}, - {"name": "Error taxonomy 3", "enabled": "Invalid value"}, + {"name": "Error taxonomy 1", "export_id": "Invalid value"}, # Invalid export_id + {"name": "Error taxonomy 3", "export_id": "test", "enabled": "Invalid value"}, ) def test_create_taxonomy_error(self, create_data: dict[str, str]): url = TAXONOMY_LIST_URL @@ -439,6 +473,7 @@ def test_update_taxonomy(self, user_attr, expected_status): "can_change_taxonomy": True, "can_delete_taxonomy": True, "can_tag_object": False, + "export_id": "2-test-update-taxonomy", }, ) @@ -498,6 +533,7 @@ def test_patch_taxonomy(self, user_attr, expected_status): "can_change_taxonomy": True, "can_delete_taxonomy": True, "can_tag_object": False, + "export_id": '2-test-patch-taxonomy', }, ) @@ -2343,6 +2379,7 @@ def test_import(self, file_format: str) -> None: { "taxonomy_name": "Imported Taxonomy name", "taxonomy_description": "Imported Taxonomy description", + "taxonomy_export_id": "imported_taxonomy_export_id", "file": file, }, format="multipart" @@ -2353,6 +2390,7 @@ def test_import(self, file_format: str) -> None: taxonomy = response.data assert taxonomy["name"] == "Imported Taxonomy name" assert taxonomy["description"] == "Imported Taxonomy description" + assert taxonomy["export_id"] == "imported_taxonomy_export_id" # Check if the tags were created url = TAXONOMY_TAGS_URL.format(pk=taxonomy["id"]) @@ -2373,6 +2411,7 @@ def test_import_no_file(self) -> None: { "taxonomy_name": "Imported Taxonomy name", "taxonomy_description": "Imported Taxonomy description", + "taxonomy_export_id": "imported_taxonomy_description", }, format="multipart" ) @@ -2419,6 +2458,7 @@ def test_import_invalid_format(self) -> None: { "taxonomy_name": "Imported Taxonomy name", "taxonomy_description": "Imported Taxonomy description", + "taxonomy_export_id": "imported_taxonomy_description", "file": file, }, format="multipart" @@ -2445,6 +2485,7 @@ def test_import_invalid_content(self, file_format) -> None: { "taxonomy_name": "Imported Taxonomy name", "taxonomy_description": "Imported Taxonomy description", + "taxonomy_export_id": "imported_taxonomy_export_id", "file": file, }, format="multipart" @@ -2503,9 +2544,7 @@ def _check_taxonomy_not_changed(self) -> None: def setUp(self): ImportTaxonomyMixin.setUp(self) - self.taxonomy = Taxonomy.objects.create( - name="Test import taxonomy", - ) + self.taxonomy = api.create_taxonomy(name="Test import taxonomy") tag_1 = Tag.objects.create( taxonomy=self.taxonomy, external_id="old_tag_1",