[Follow-up BE] Hide archived tagging records in edx-platform's content_tagging app
Los mantenedores suelen responder en 1 día
@tbain ya está trabajando en esto.
Desde el 23/10/2026.
Evaluación
Este issue todavía no se ha evaluado.
Descripción
User Story
As a course author, I want archived taxonomies, tags, and tag associations to stay out of Studio's tag counts, tag exports, and copied or imported content, in order to not see or bring back something that is no longer in use.
Description
This is a follow-up ticket to a gap uncovered while implementing and code reviewing #778. #778 hides archived records on openedx-core's read paths, but edx-platform's content_tagging app counts tags, reads tag associations, and imports course tags with its own queries. We need those places to respect the archived state the same way openedx-core does.
Context
- Depends on #778, which adds the
unarchived()querysets and documents the opt-out (default managers return every row). - #779 and #780 are what set
archived=True; until they land, nothing in the product produces an archived row. - #814 covers importing over an archived tag's value inside openedx-core.
- Raised in the review of #778's PR (tbain/openedx-core#1), finding 5.
Acceptance Criteria
Scenario Outline: A taxonomy's tag count leaves out archived tags
Given a taxonomy with three tags, one of them archived
When taxonomies are listed in Studio <filtered>
Then that taxonomy shows a tag count of two
Examples:
| filtered |
| with no filter |
| for the organization that owns it |
| as unassigned taxonomies |
Scenario: A taxonomy whose tags are all archived shows a count of zero
Given an unarchived taxonomy whose only tags are archived
When taxonomies are listed in Studio
Then the taxonomy appears with a tag count of zero
Scenario: An archived association is left out of a tag export
Given a course with two tagged blocks, one tagged through an archived association and one through an unarchived association
When the course's tags are exported
Then only the unarchived association appears in the export
Scenario: Tags under an archived tag or taxonomy are left out of a tag export
Given one block tagged with an archived tag and another tagged from an archived taxonomy, with neither association itself archived
When the course's tags are exported
Then neither tag appears in the export
And the archived taxonomy is not mentioned anywhere in the export
Scenario: A course whose only tags are archived exports with no tags
Given a course whose every tag association is archived
When the course's tags are exported from Studio's tag export
Then every block is listed with no tags
Scenario: Exporting and importing a course does not bring back archived associations
Given a course block with one archived and one unarchived association
When the course is exported and imported as a new course
Then the new course's block carries only the unarchived tag
Scenario: Duplicating a block does not copy archived associations
Given a block with one archived and one unarchived association
When the block is duplicated in Studio
Then the duplicate carries only the unarchived tag
And the original block's archived association is unchanged
Scenario: Copying and pasting a block does not carry archived associations
Given a course block with one archived and one unarchived association
When the block is copied and pasted into another unit
Then the pasted block carries only the unarchived tag
Scenario: Copying a library container does not carry archived associations
Given a library unit with an archived association, and a child component that also has an archived association
When the unit is copied and pasted into a course
Then neither the pasted unit nor its child carries an archived tag
Scenario: Unarchived tags still travel exactly as they do today
Given a block with tags from two unarchived taxonomies, one of them disabled
When the block is exported, duplicated, or copied and pasted
Then every tag, including the disabled taxonomy's, is carried as it is today
Scenario: Importing a course leaves out tags from a taxonomy archived on this instance
Given a course export, made before the taxonomy was archived or on another instance, whose tags come from one taxonomy archived on this instance and one live taxonomy
When the course is imported as a new course
Then the import completes
And each block carries its tags from the live taxonomy
And no block carries a tag from the archived taxonomy
Scenario: Left-out tags cannot reappear after a later taxonomy import
Given a course imported as a new course, whose import left out tags from an archived taxonomy
When an administrator later imports a tag file into any taxonomy
Then those blocks have no association with the archived taxonomy, visible or hidden
Scenario: Tags from a taxonomy that does not exist on this instance are still kept for later
Given a course export whose tags come from a taxonomy that does not exist on this instance
When the course is imported
And a matching taxonomy is later created from a tag file containing those values
Then the blocks carry those tags, as they do today
Scenario Outline: Re-importing a course into itself keeps the associations an author cannot see
Given a course block with <hidden association> and one association the author can see
When the course is exported and re-imported into the same course
Then the hidden association still exists, exactly once, and stays hidden
And the block's visible tags are replaced from the file as they are today
Examples:
| hidden association |
| an archived association |
| an association to an archived tag in a live taxonomy |
| an association under a taxonomy archived on this instance |
Scenario Outline: Saving a block's tags in Studio keeps the associations an author cannot see
Given a course block with <hidden association> and one association the author can see
When the author saves the block's tags in Studio, changing only the tags they can see
Then the hidden association still exists, exactly once, and stays hidden
And the block's visible tags match what the author saved
Examples:
| hidden association |
| an archived association |
| an association to an archived tag in a live taxonomy |
Out of scope: an incoming value, on paste or import, that belongs to an archived tag in a live taxonomy. This ticket accepts that value as-is, and the read paths already hide the association it creates.
Technical Details
This is a suggested approach, not the source of truth. The User Story, Description, and Acceptance Criteria define what must be true when the work is done.
Summary
Two repositories change. In openedx-core, tag_object stops deleting the associations that unarchived() hides, so a full-list save or import leaves them alone. In openedx-platform, the Studio taxonomy list counts only unarchived tags, and get_all_object_tags, the one read behind every export, duplicate, and copy path, reads through ObjectTag.objects.unarchived(). Course tag import clears only the associations that unarchived() returns, and it uses an unfiltered lookup to tell an archived taxonomy apart from a missing one: it skips and logs tags from an archived taxonomy, and creates placeholders only for a missing one.
Approach
-
Keep hidden associations on a full-list save (openedx-core,
src/openedx_tagging/api.py).tag_objectreplaces an object's associations for one taxonomy:_get_current_tagsreads them through the unfiltered manager, each tag in the new list claims its matching row, and the rows left unclaimed are removed. The removal step skips every row thatunarchived()hides. A hidden row therefore survives a save that lists other tags of its taxonomy, and it is still matched when its own tag is re-applied, so #779's un-archive on re-apply keeps working. This change lands after #779 and applies to the removal set that #779 builds, which it then splits into locked rows to archive and the rest to delete. Studio's tag drawer andset_all_object_tagson paste build their new list from the filteredget_object_tags, so without this step every save would delete the hidden rows the author cannot see._check_new_tag_countstill counts hidden rows towardOBJECT_MAX_TAGS(100); that is accepted. -
Tag count (openedx-platform). In
openedx/core/djangoapps/content_tagging/rest_api/v1/views.py,TaxonomyOrgView.get_querysetannotatestags_count=Count("tag", filter=Q(tag__archived=False), distinct=True)on all three branches (org, unassigned, and neither). This is the same expression that openedx-core'sTaxonomyViewuses. -
Association reads (openedx-platform). In
openedx/core/djangoapps/content_tagging/api.py,get_all_object_tagsstarts fromObjectTag.objects.unarchived(). This one query feeds:- the CSV export (
generate_csv_rows, and the coursetags.csvthroughexport_tags_in_csv_file); - duplication (
copy_object_tags); - clipboard copy (
openedx/core/lib/xblock_serializer/block_serializer.py); - library containers (
openedx/core/djangoapps/content_libraries/api/serializers.py).
The CSV header is built from the returned rows, so an archived taxonomy drops out of the header too.
xmodule/modulestore/xml_exporter.pywritestags.csvonly when openedx-core'sget_object_tag_countsfinds a tag. #778 already filters that count, so a course whose only tags are archived writes notags.csv. That count also skips disabled taxonomies, so a test that exports a disabled taxonomy's tags needs a live tag in the same course. - the CSV export (
-
Course tag import (openedx-platform), in
set_exported_object_tags:- Clearing. Clear with
ObjectTag.objects.filter(object_id=content_key_str).unarchived().delete()in place ofoel_tagging.delete_object_tags. A hidden association is exactly a row thatunarchived()excludes: an archived association, or one whose tag, tag's taxonomy, or own taxonomy is archived. Rows in disabled taxonomies, rows whose tag was deleted, and placeholders (taxonomy=None) are still cleared and re-created from the file as today. The clear stays a querysetdelete()so that #779'sObjectTagQuerySet.delete()override covers it: the override archives a locked visible row, andtag_objectun-archives that row if the file lists it again. - Resolving taxonomies. Resolve each export id with one unfiltered
Taxonomy.objects.filter(export_id=...).first()in place ofget_taxonomy_by_export_id, which returnsNonefor an archived and a missing taxonomy alike. Then branch on the result:- a live taxonomy: call
tag_objectas today, which keeps the block's hidden rows in that taxonomy because of step 1; - an archived taxonomy: skip it, with no placeholder and no events, and
log.infothe content key, export id, and skipped values. A placeholder is wrong here becauseObjectTag.resync()looks taxonomies up through plainTaxonomy.objects, so it would re-link the placeholder to the archived taxonomy; - a missing taxonomy: create placeholders as today.
- a live taxonomy: call
- Hand-built files. A hand-built file that lists a hidden association's own tag re-applies that association, and after #779 the re-application un-archives it. That behavior is accepted. A file exported from this instance never lists a hidden association's tag.
- Clearing. Clear with
-
Paste needs no change beyond step 1.
set_all_object_tagsresolves taxonomies through the filteredget_taxonomy, and step 3 keeps hidden rows out of the copied OLX.
Out of scope:
- Deleting content, where
handlers.pycallsdelete_object_tags(#779). - Upstream-linked paste and library sync, which use openedx-core's
copy_tags(#778, #779).
Files to create and modify
| File | Change |
|---|---|
| openedx-core: src/openedx_tagging/api.py | modified: tag_object removal step skips hidden rows |
| openedx-core: tests/openedx_tagging/test_api.py | modified: hidden rows survive a full-list save; re-apply still un-archives |
| openedx-platform: openedx/core/djangoapps/content_tagging/rest_api/v1/views.py | modified: TaxonomyOrgView.get_queryset |
| openedx-platform: openedx/core/djangoapps/content_tagging/api.py | modified: get_all_object_tags, set_exported_object_tags, module logger |
| openedx-platform: requirements/edx/*.txt | modified: openedx-core pin, if the regular upgrade has not already bumped it |
Additional context
- openedx-platform pins
openedx-core==1.4.0. The openedx-platform part needs an openedx-core release that contains #778, #779, and step 1, so the openedx-core part of this ticket ships first.
- Lenguaje dominante
- Python
- Estrellas
- 10
- Forks
- 33
- Merge medio
- 4 d 2 h
- PR fusionados (30 d)
- 31
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de openedx/openedx-core
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
openedx/openedx-core#831 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
openedx/openedx-core#827 ·
Los mantenedores suelen responder en 1 día
-
[Release Docs - Follow up] Docs Notes for Competency ID after additional work is completedPosiblemente ocupada @thelmick-unicon la tomó hoy. Abierto
openedx/openedx-core#917 · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 28/100
openedx/openedx-core#916 ·
Los mantenedores suelen responder en 1 día
-
[Bug] Competency Management page: the tree/course divider can be dragged until the course panel is fully hiddenPosiblemente ocupada @AShatsila la tomó hace 1 día. Abierto
openedx/openedx-core#910 · 1 asignado ·
Los mantenedores suelen responder en 1 día
Todos los issues de openedx/openedx-core
Issues similares
-
Context/namespace resources fail in-cluster: "Invalid kube-config file. No configuration found."Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
-
bug documentation
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
awslabs/visual-asset-management-system#880 ·
Los mantenedores suelen responder en 1 día
-
netbox status: needs owner type: documentation
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
netbox-community/netbox#23533 ·
Los mantenedores suelen responder en 1 día
-
app: scripts status: backlog type: documentation
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
netbox-community/netbox-scripts#68 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 91/100
marin-community/marin#10765 ·
Los mantenedores suelen responder en 1 día