Skip to content

[BE] Add an archived field to Tag, Taxonomy, and ObjectTag, and a deletion_locked field to ObjectTag #776

Open

Description

@thelmick-unicon

User Story

As a platform administrator, I want the tagging data that competency mastery depends on to be able to record that it has been retired, and to record which tag associations something depends on, in order to have my deletions preserve learner mastery history instead of destroying it.

Acceptance Criteria

There is no manual QA path. Acceptance is determined by a PR reviewer verifying the following against the model code, migration, and tests:

  • archived = models.BooleanField(default=False) exists on Taxonomy, Tag, and ObjectTag in src/openedx_tagging/models/base.py.
  • deletion_locked = models.BooleanField(default=False) exists on ObjectTag only; Tag and Taxonomy do not get their own deletion_locked field.
  • One additive migration adds all four columns; no data migration or backfill is included.
  • The migration applies cleanly against a database holding existing taxonomies, tags, and tag associations, and every existing row reads back as archived=False (and, for ObjectTag, deletion_locked=False) with no other field changed.
  • archived and deletion_locked can be set independently on an ObjectTag (one True while the other stays False) with no other field affected.
  • No archived_at or locked_at companion field was added.
  • Both fields are registered as non-PII; make pii_check passes.
  • No read path, write path, filter, or delete branch anywhere reads or checks either field; the fields are inert on merge.
  • Field definitions and help_text match [BE] Add archived field to Competency Criteria & Group models #716's shape for CompetencyCriteriaGroup.archived/CompetencyCriterion.archived, for consistency across the feature.

Description

Taxonomy, Tag, and ObjectTag in openedx_tagging can only be hard-deleted today. There is no way to retire one while keeping it readable, and no way for the tagging app to know that something outside it depends on a record. Both are needed before a delete can branch between removing a record and retiring it.

deletion_locked=True on an ObjectTag records that a learner status now depends on it: it means any future delete request against that association must archive it instead of removing the row. Tag and Taxonomy don't carry their own deletion_locked field; whether either one is locked is derived from whether any of the ObjectTags beneath it are locked. archived=True, on any of the three models, means a record has already been retired along that path: it is hidden from active use, but its row and the learner history pointing to it stay in place.

This ticket adds the four fields and nothing else. They are inert on merge: #777 adds the API that sets and reads the lock, #778 makes archived records inert on read and write paths, and #779 through #781 add the delete branches that use both.

Technical Details

This section is background and a suggested approach, not the ticket's source of truth. The User Story and Acceptance Criteria define what must be true when the work is done.

In short

Why archived goes on all three but deletion_locked goes on only one. Any of the three record types can be retired, so all three need somewhere to record that. But only a tag association is ever depended upon directly: competency mastery is earned through the association between a competency and a piece of content, and that association is the thing whose removal would orphan a learner's status. A tag or a taxonomy matters only because associations exist beneath it, so their answer to "is anything depending on this" is derived from those associations rather than stored separately. #777 provides that derivation.

Why deriving beats storing for the upper two. A stored flag on a tag would have to be kept correct as associations come and go beneath it, which means either clearing it when the last association is unlocked, and getting that wrong loses learner history, or never clearing it, and then it only ever accumulates and stops meaning anything. One association carrying the fact, and the levels above reading down to it, has no such bookkeeping.

Why a stored flag on the association rather than a lookup at delete time. openedx_tagging must not know that competency-based education exists, and must not query another app's tables to decide whether a delete is safe. Storing the answer on the association row lets the owning feature push it in, and makes "is this in use" answerable inside the tagging app alone. The pushing side is #777 and #782.

No timestamp companions. Neither field gets an archived_at or locked_at. Nothing in the approved approach reads one, and a column no query uses is not free on tables this size.

Implementation specifics

Files to modify

File Nature of modification
src/openedx_tagging/models/base.py add archived to Taxonomy, Tag, and ObjectTag; add deletion_locked to ObjectTag only
src/openedx_tagging/migrations/ one additive schema migration for the four new columns and any indexes
tests/openedx_tagging/ default-value, independence, and migration tests in the existing model test module

Context

Activity

  1. changed the title [-][BE] Add archived and deletion_locked fields to Tag, Taxonomy, and ObjectTag[/-] [+][BE] Add an archived field to Tag, Taxonomy, and ObjectTag, and a deletion_locked field to ObjectTag[/+] on Sep 14, 2026
  2. moved this from Needs additional details to Ready for Community Review in Competency Criteria and Student Progresson Sep 14, 2026
  3. moved this from Ready for Community Review to Final Axim Review in Competency Criteria and Student Progresson Sep 21, 2026
  4. thelmick-unicon commented on Sep 21, 2026

    @thelmick-unicon
    Author

    The discussion period for this ticket is now complete, and we appreciate all the feedback. We'll be moving forward with development based on the current ticket pending final Axim sign-off. That said, the conversation doesn't have to stop. If you still have feedback, please continue to add to this ticket. We'll review anything new and determine whether it fits within the current timeline or makes more sense as its own ticket for future work. Thanks again for helping make this better!

    @ormsbee This is ready for your final review. Thank you!

  5. ormsbee commented on Oct 6, 2026

    @ormsbee
    Contributor

    Accepted.

  6. tbain commented on Oct 8, 2026

    @tbain
    Contributor

    Pull request created at #856

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions