Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

[Follow-up BE] Hide archived tagging records in edx-platform's content_tagging app

Abierto
#903 0 comentarios 0 reacciones 1 asignado Ver en GitHub

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

  1. Keep hidden associations on a full-list save (openedx-core, src/openedx_tagging/api.py). tag_object replaces an object's associations for one taxonomy: _get_current_tags reads 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 that unarchived() 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 and set_all_object_tags on paste build their new list from the filtered get_object_tags, so without this step every save would delete the hidden rows the author cannot see. _check_new_tag_count still counts hidden rows toward OBJECT_MAX_TAGS (100); that is accepted.

  2. Tag count (openedx-platform). In openedx/core/djangoapps/content_tagging/rest_api/v1/views.py, TaxonomyOrgView.get_queryset annotates tags_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's TaxonomyView uses.

  3. Association reads (openedx-platform). In openedx/core/djangoapps/content_tagging/api.py, get_all_object_tags starts from ObjectTag.objects.unarchived(). This one query feeds:

    • the CSV export (generate_csv_rows, and the course tags.csv through export_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.py writes tags.csv only when openedx-core's get_object_tag_counts finds a tag. #778 already filters that count, so a course whose only tags are archived writes no tags.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.

  4. 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 of oel_tagging.delete_object_tags. A hidden association is exactly a row that unarchived() 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 queryset delete() so that #779's ObjectTagQuerySet.delete() override covers it: the override archives a locked visible row, and tag_object un-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 of get_taxonomy_by_export_id, which returns None for an archived and a missing taxonomy alike. Then branch on the result:
      • a live taxonomy: call tag_object as 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.info the content key, export id, and skipped values. A placeholder is wrong here because ObjectTag.resync() looks taxonomies up through plain Taxonomy.objects, so it would re-link the placeholder to the archived taxonomy;
      • a missing taxonomy: create placeholders as today.
    • 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.
  5. Paste needs no change beyond step 1. set_all_object_tags resolves taxonomies through the filtered get_taxonomy, and step 3 keeps hidden rows out of the copied OLX.

Out of scope:

  • Deleting content, where handlers.py calls delete_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

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de openedx/openedx-core

Todos los issues de openedx/openedx-core

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.