diff --git a/openedx_tagging/core/tagging/import_export/parsers.py b/openedx_tagging/core/tagging/import_export/parsers.py index 72d43822..823a8a24 100644 --- a/openedx_tagging/core/tagging/import_export/parsers.py +++ b/openedx_tagging/core/tagging/import_export/parsers.py @@ -166,17 +166,19 @@ def _load_tags_for_export(cls, taxonomy: Taxonomy) -> list[dict]: with required and optional fields The tags are ordered by hierarchy, first, parents and then children. - `get_tags` is in charge of returning this in a hierarchical way. + `get_filtered_tags` is in charge of returning this in a hierarchical + way. """ tags = taxonomy.get_filtered_tags().all() result = [] for tag in tags: result_tag = { - "id": tag["external_id"] or tag["id"], + "id": tag["external_id"] or tag["_id"], "value": tag["value"], } - if tag.parent: - result_tag["parent_id"] = tag.parent.external_id or tag.parent.id + if tag["parent_value"]: + parent_tag = next(t for t in tags if t["value"] == tag["parent_value"]) + result_tag["parent_id"] = parent_tag["external_id"] or parent_tag["_id"] result.append(result_tag) return result diff --git a/openedx_tagging/core/tagging/models/base.py b/openedx_tagging/core/tagging/models/base.py index b7f113d2..7900e440 100644 --- a/openedx_tagging/core/tagging/models/base.py +++ b/openedx_tagging/core/tagging/models/base.py @@ -370,8 +370,9 @@ def _get_filtered_tags_free_text( child_count=Value(0), external_id=Value(None, output_field=models.CharField()), parent_value=Value(None, output_field=models.CharField()), + _id=Value(None, output_field=models.CharField()), ) - qs = qs.values("value", "child_count", "depth", "parent_value", "external_id").order_by("value") + qs = qs.values("value", "child_count", "depth", "parent_value", "external_id", "_id").order_by("value") if include_counts: return qs.annotate(usage_count=models.Count("value")) else: @@ -402,7 +403,8 @@ def _get_filtered_tags_one_level( # Filter by search term: if search_term: qs = qs.filter(value__icontains=search_term) - qs = qs.values("value", "child_count", "depth", "parent_value", "external_id").order_by("value") + qs = qs.annotate(_id=F("id")) # ID has an underscore to encourage use of 'value' rather than this internal ID + qs = qs.values("value", "child_count", "depth", "parent_value", "external_id", "_id").order_by("value") if include_counts: # We need to include the count of how many times this tag is used to tag objects. # You'd think we could just use: @@ -468,7 +470,8 @@ def _get_filtered_tags_deep( )) # Add the parent value qs = qs.annotate(parent_value=F("parent__value")) - qs = qs.values("value", "child_count", "depth", "parent_value", "external_id").order_by("sort_key") + qs = qs.annotate(_id=F("id")) # ID has an underscore to encourage use of 'value' rather than this internal ID + qs = qs.values("value", "child_count", "depth", "parent_value", "external_id", "_id").order_by("sort_key") if include_counts: # Including the counts is a bit tricky; see the comment above in _get_filtered_tags_one_level() obj_tags = ObjectTag.objects.filter(tag_id=models.OuterRef("pk")).order_by().annotate( diff --git a/tests/openedx_tagging/core/tagging/test_models.py b/tests/openedx_tagging/core/tagging/test_models.py index c3ff48ae..a47ec5f1 100644 --- a/tests/openedx_tagging/core/tagging/test_models.py +++ b/tests/openedx_tagging/core/tagging/test_models.py @@ -223,6 +223,8 @@ def test_get_root(self) -> None: """ result = list(self.taxonomy.get_filtered_tags(depth=1, include_counts=False)) common_fields = {"depth": 0, "parent_value": None, "external_id": None} + for r in result: + del r["_id"] # Remove the internal database IDs; they aren't interesting here and a other tests check them assert result == [ # These are the root tags, in alphabetical order: {"value": "Archaea", "child_count": 3, **common_fields}, @@ -237,6 +239,8 @@ def test_get_child_tags_one_level(self) -> None: """ result = list(self.taxonomy.get_filtered_tags(depth=1, parent_tag_value="Eukaryota")) common_fields = {"depth": 1, "parent_value": "Eukaryota", "usage_count": 0, "external_id": None} + for r in result: + del r["_id"] # Remove the internal database IDs; they aren't interesting here and a other tests check them assert result == [ # These are the Eukaryota tags, in alphabetical order: {"value": "Animalia", "child_count": 7, **common_fields}, @@ -253,6 +257,8 @@ def test_get_grandchild_tags_one_level(self) -> None: """ result = list(self.taxonomy.get_filtered_tags(depth=1, parent_tag_value="Animalia")) common_fields = {"depth": 2, "parent_value": "Animalia", "usage_count": 0, "external_id": None} + for r in result: + del r["_id"] # Remove the internal database IDs; they aren't interesting here and a other tests check them assert result == [ # These are the Eukaryota tags, in alphabetical order: {"value": "Arthropoda", "child_count": 0, **common_fields}, @@ -277,6 +283,7 @@ def test_get_depth_1_search_term(self) -> None: "usage_count": 0, "parent_value": None, "external_id": None, + "_id": 2, # These IDs are hard-coded in the test fixture file }, ] # Note that other tags in the taxonomy match "ARCH" but are excluded because of the depth=1 search @@ -294,6 +301,7 @@ def test_get_depth_1_child_search_term(self) -> None: "usage_count": 0, "parent_value": "Bacteria", "external_id": None, + "_id": 5, # These IDs are hard-coded in the test fixture file }, ] # Note that other tags in the taxonomy match "ARCH" but are excluded because of the depth=1 search @@ -384,6 +392,7 @@ def test_tags_deep(self) -> None: "usage_count": 0, "child_count": 0, "external_id": None, + "_id": 21, # These IDs are hard-coded in the test fixture file } ] @@ -457,7 +466,7 @@ def test_get_filtered_tags(self): Without counts included. """ result = list(self.taxonomy.get_filtered_tags(include_counts=False)) - common_fields = {"child_count": 0, "depth": 0, "parent_value": None, "external_id": None} + common_fields = {"child_count": 0, "depth": 0, "parent_value": None, "external_id": None, "_id": None} assert result == [ # These should appear in alphabetical order: {"value": "double", **common_fields}, @@ -471,7 +480,7 @@ def test_get_filtered_tags_with_count(self): Without counts included. """ result = list(self.taxonomy.get_filtered_tags(include_counts=True)) - common_fields = {"child_count": 0, "depth": 0, "parent_value": None, "external_id": None} + common_fields = {"child_count": 0, "depth": 0, "parent_value": None, "external_id": None, "_id": None} assert result == [ # These should appear in alphabetical order: {"value": "double", "usage_count": 2, **common_fields}, @@ -494,7 +503,7 @@ def test_get_filtered_tags_with_search(self) -> None: Test basic retrieval of only matching tags. """ result1 = list(self.taxonomy.get_filtered_tags(search_term="le")) - common_fields = {"child_count": 0, "depth": 0, "parent_value": None, "external_id": None} + common_fields = {"child_count": 0, "depth": 0, "parent_value": None, "external_id": None, "_id": None} assert result1 == [ # These should appear in alphabetical order: {"value": "double", "usage_count": 2, **common_fields},