From 93609c5e0a3956de289cb0cb596d435ae42736de Mon Sep 17 00:00:00 2001 From: ufedaseyeuconsultant Date: Tue, 29 Sep 2026 19:09:56 +0300 Subject: [PATCH 1/6] feat: add archived field to CompetencyCriteriaGroup and CompetencyCriterion (#716) Adds archived = models.BooleanField(default=False) to both models, in one migration, per ADR 0002 Decision 7's archive-only retirement rule. No archived_at companion field: django-simple-history (ADR 0003) already tracks both models, covering the "when" without a separate timestamp. Both models' existing `.. no_pii:` class-level docstring annotation already covers the new field, matching CompetencyRuleProfile.archived's existing non-PII precedent; no separate registry change needed. This ticket makes no behavior changes: nothing here sets, reads, or filters on the field. That is downstream tickets' scope (#674, #675, #681). Co-Authored-By: Claude Sonnet 5 --- .../applets/cbe/models/criteria.py | 8 ++++ ...mpetencycriteriagroup_archived_and_more.py | 45 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/openedx_learning/migrations/0009_competencycriteriagroup_archived_and_more.py diff --git a/src/openedx_learning/applets/cbe/models/criteria.py b/src/openedx_learning/applets/cbe/models/criteria.py index 084ae81f2..0bf4baebd 100644 --- a/src/openedx_learning/applets/cbe/models/criteria.py +++ b/src/openedx_learning/applets/cbe/models/criteria.py @@ -96,6 +96,10 @@ class CompetencyCriteriaGroup(models.Model): "logic is moot; the application layer treats null the same as OR." ), ) + archived = models.BooleanField( + default=False, + help_text=_("Hides this row from the criteria-tree read endpoint by default, without removing it."), + ) history = HistoricalRecords() @@ -307,6 +311,10 @@ class CompetencyCriterion(models.Model): null=True, blank=True, help_text=_("Overrides rule_profile's rule_payload for this criterion. Set only when rule_profile is null."), ) + archived = models.BooleanField( + default=False, + help_text=_("Hides this row from the criteria-tree read endpoint by default, without removing it."), + ) history = HistoricalRecords() diff --git a/src/openedx_learning/migrations/0009_competencycriteriagroup_archived_and_more.py b/src/openedx_learning/migrations/0009_competencycriteriagroup_archived_and_more.py new file mode 100644 index 000000000..c44efd627 --- /dev/null +++ b/src/openedx_learning/migrations/0009_competencycriteriagroup_archived_and_more.py @@ -0,0 +1,45 @@ +# Generated by Django 5.2.17 on 2026-09-23 21:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("openedx_learning", "0008_criteria_group_unique_constraints"), + ] + + operations = [ + migrations.AddField( + model_name="competencycriteriagroup", + name="archived", + field=models.BooleanField( + default=False, + help_text="Hides this row from the criteria-tree read endpoint by default, without removing it.", + ), + ), + migrations.AddField( + model_name="competencycriterion", + name="archived", + field=models.BooleanField( + default=False, + help_text="Hides this row from the criteria-tree read endpoint by default, without removing it.", + ), + ), + migrations.AddField( + model_name="historicalcompetencycriteriagroup", + name="archived", + field=models.BooleanField( + default=False, + help_text="Hides this row from the criteria-tree read endpoint by default, without removing it.", + ), + ), + migrations.AddField( + model_name="historicalcompetencycriterion", + name="archived", + field=models.BooleanField( + default=False, + help_text="Hides this row from the criteria-tree read endpoint by default, without removing it.", + ), + ), + ] From e71d66dff472771a1212f895ab95997e82641d53 Mon Sep 17 00:00:00 2001 From: ufedaseyeuconsultant Date: Tue, 29 Sep 2026 19:24:11 +0300 Subject: [PATCH 2/6] test: cover archived field defaults on group and criterion (#716) Pins the same contract CompetencyRuleProfile.archived already has a test for (test_rule_profile.py's test_creating_a_rule_profile_persists_its_columns): a freshly created row is not archived unless told otherwise. Co-Authored-By: Claude Sonnet 5 --- .../applets/cbe/test_criteria_group.py | 13 +++++++++++++ .../applets/cbe/test_criterion.py | 17 +++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/tests/openedx_learning/applets/cbe/test_criteria_group.py b/tests/openedx_learning/applets/cbe/test_criteria_group.py index 39bb755f0..c304c6f71 100644 --- a/tests/openedx_learning/applets/cbe/test_criteria_group.py +++ b/tests/openedx_learning/applets/cbe/test_criteria_group.py @@ -21,6 +21,19 @@ pytestmark = pytest.mark.django_db +# --------------------------------------------------------------------------------------------- +# Columns + + +# --------------------------------------------------------------------------------------------- + + +def test_group_archived_defaults_to_false(tag: Tag) -> None: + """A newly created CompetencyCriteriaGroup is not archived unless told otherwise.""" + group = CompetencyCriteriaGroup.objects.create(tag=tag) + assert group.archived is False + + # --------------------------------------------------------------------------------------------- # Tree shape, and the two constraints ADR-0002 Decision 2 deliberately leaves out diff --git a/tests/openedx_learning/applets/cbe/test_criterion.py b/tests/openedx_learning/applets/cbe/test_criterion.py index d700dda6c..2e79a2e4e 100644 --- a/tests/openedx_learning/applets/cbe/test_criterion.py +++ b/tests/openedx_learning/applets/cbe/test_criterion.py @@ -22,6 +22,23 @@ ] +# --------------------------------------------------------------------------------------------- +# Columns + + +# --------------------------------------------------------------------------------------------- + + +def test_criterion_archived_defaults_to_false( + group: CompetencyCriteriaGroup, object_tag: ObjectTag, default_rule_profile: CompetencyRuleProfile +) -> None: + """A newly created CompetencyCriterion is not archived unless told otherwise.""" + criterion = CompetencyCriterion.objects.create( + group=group, object_tag=object_tag, rule_profile=default_rule_profile + ) + assert criterion.archived is False + + # --------------------------------------------------------------------------------------------- # Either a rule_profile or both overrides. Never both, never neither. From 4888a9306611146274fcc5aa78a65c31bd42b2b6 Mon Sep 17 00:00:00 2001 From: ufedaseyeuconsultant Date: Fri, 9 Oct 2026 17:22:25 +0300 Subject: [PATCH 3/6] fix: renumber #716's migration onto upstream's current tip Cherry-picked 39561a6 and 0e9513b (#716: add archived field to CompetencyCriteriaGroup and CompetencyCriterion) onto upstream/main to open a direct PR there instead of against this fork's lagging main. The original migration depended on 0008_criteria_group_unique_constraints, a migration from an unrelated, unmerged branch that doesn't exist on upstream/main. Deletes it and regenerates via makemigrations against upstream's current tip (0012_studentcompetencycriteriagroupstatus, from 48a04ee and 5984e3e), landing as 0013 with the identical field operations, just renumbered and re-based. Co-Authored-By: Claude Sonnet 5 --- ...e.py => 0013_competencycriteriagroup_archived_and_more.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/openedx_learning/migrations/{0009_competencycriteriagroup_archived_and_more.py => 0013_competencycriteriagroup_archived_and_more.py} (91%) diff --git a/src/openedx_learning/migrations/0009_competencycriteriagroup_archived_and_more.py b/src/openedx_learning/migrations/0013_competencycriteriagroup_archived_and_more.py similarity index 91% rename from src/openedx_learning/migrations/0009_competencycriteriagroup_archived_and_more.py rename to src/openedx_learning/migrations/0013_competencycriteriagroup_archived_and_more.py index c44efd627..10ebbb315 100644 --- a/src/openedx_learning/migrations/0009_competencycriteriagroup_archived_and_more.py +++ b/src/openedx_learning/migrations/0013_competencycriteriagroup_archived_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.17 on 2026-09-23 21:08 +# Generated by Django 5.2.17 on 2026-10-09 14:21 from django.db import migrations, models @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ("openedx_learning", "0008_criteria_group_unique_constraints"), + ("openedx_learning", "0012_studentcompetencycriteriagroupstatus"), ] operations = [ From ae5a42038780af1073d7d25c216eba601314b9dc Mon Sep 17 00:00:00 2001 From: ufedaseyeuconsultant Date: Fri, 9 Oct 2026 20:49:45 +0300 Subject: [PATCH 4/6] test: drop two docstrings that just restate the test name Review nit: test_group_archived_defaults_to_false and test_criterion_archived_defaults_to_false's docstrings add no information the function name doesn't already give. Co-Authored-By: Claude Sonnet 5 --- tests/openedx_learning/applets/cbe/test_criteria_group.py | 1 - tests/openedx_learning/applets/cbe/test_criterion.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/openedx_learning/applets/cbe/test_criteria_group.py b/tests/openedx_learning/applets/cbe/test_criteria_group.py index c304c6f71..4836c4292 100644 --- a/tests/openedx_learning/applets/cbe/test_criteria_group.py +++ b/tests/openedx_learning/applets/cbe/test_criteria_group.py @@ -29,7 +29,6 @@ def test_group_archived_defaults_to_false(tag: Tag) -> None: - """A newly created CompetencyCriteriaGroup is not archived unless told otherwise.""" group = CompetencyCriteriaGroup.objects.create(tag=tag) assert group.archived is False diff --git a/tests/openedx_learning/applets/cbe/test_criterion.py b/tests/openedx_learning/applets/cbe/test_criterion.py index 2e79a2e4e..5d69d87d0 100644 --- a/tests/openedx_learning/applets/cbe/test_criterion.py +++ b/tests/openedx_learning/applets/cbe/test_criterion.py @@ -32,7 +32,6 @@ def test_criterion_archived_defaults_to_false( group: CompetencyCriteriaGroup, object_tag: ObjectTag, default_rule_profile: CompetencyRuleProfile ) -> None: - """A newly created CompetencyCriterion is not archived unless told otherwise.""" criterion = CompetencyCriterion.objects.create( group=group, object_tag=object_tag, rule_profile=default_rule_profile ) From a684d6603db6e89c4b540fca389c809ac9433847 Mon Sep 17 00:00:00 2001 From: ufedaseyeuconsultant Date: Fri, 9 Oct 2026 20:50:57 +0300 Subject: [PATCH 5/6] fix: renumber #716's migration past upstream's new 0013 Rebased onto upstream/main, which merged #853 since this branch was last renumbered, adding its own 0013 migration depending on the same 0012_studentcompetencycriteriagroupstatus this branch's migration also depended on. Deletes the old 0013 and regenerates via makemigrations, landing as 0014; the field operations are otherwise identical to the deleted file. Co-Authored-By: Claude Sonnet 5 --- ...y => 0014_competencycriteriagroup_archived_and_more.py} | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) rename src/openedx_learning/migrations/{0013_competencycriteriagroup_archived_and_more.py => 0014_competencycriteriagroup_archived_and_more.py} (88%) diff --git a/src/openedx_learning/migrations/0013_competencycriteriagroup_archived_and_more.py b/src/openedx_learning/migrations/0014_competencycriteriagroup_archived_and_more.py similarity index 88% rename from src/openedx_learning/migrations/0013_competencycriteriagroup_archived_and_more.py rename to src/openedx_learning/migrations/0014_competencycriteriagroup_archived_and_more.py index 10ebbb315..4d2a2ac1b 100644 --- a/src/openedx_learning/migrations/0013_competencycriteriagroup_archived_and_more.py +++ b/src/openedx_learning/migrations/0014_competencycriteriagroup_archived_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.17 on 2026-10-09 14:21 +# Generated by Django 5.2.17 on 2026-10-09 17:50 from django.db import migrations, models @@ -6,7 +6,10 @@ class Migration(migrations.Migration): dependencies = [ - ("openedx_learning", "0012_studentcompetencycriteriagroupstatus"), + ( + "openedx_learning", + "0013_alter_historicalcompetencycriteriagroup_history_date_and_more", + ), ] operations = [ From ff828894b4a00ba178297820695d947804240f96 Mon Sep 17 00:00:00 2001 From: ufedaseyeuconsultant Date: Mon, 12 Oct 2026 17:53:47 +0300 Subject: [PATCH 6/6] fix: describe archived in terms of the row, not one future consumer The help_text on CompetencyCriteriaGroup.archived and CompetencyCriterion.archived named "the criteria-tree read endpoint" (#681, not yet built) as if it were the only consumer, when #674 and #675 are what actually set the flag and #847's create_leaf_group() will need to read it too. Reworded both to match CompetencyRuleProfile.archived's existing convention (ADR-0002 Decision 2): describe what archiving means for the row itself, not what one downstream endpoint does with it. Regenerates the migration (Django bakes help_text into it) with a descriptive name instead of the auto-generated "_and_more" one. Co-Authored-By: Claude Sonnet 5 --- src/openedx_learning/applets/cbe/models/criteria.py | 12 ++++++++++-- ..._add_archived_to_criteria_group_and_criterion.py} | 10 +++++----- 2 files changed, 15 insertions(+), 7 deletions(-) rename src/openedx_learning/migrations/{0014_competencycriteriagroup_archived_and_more.py => 0014_add_archived_to_criteria_group_and_criterion.py} (50%) diff --git a/src/openedx_learning/applets/cbe/models/criteria.py b/src/openedx_learning/applets/cbe/models/criteria.py index 0bf4baebd..d8086496e 100644 --- a/src/openedx_learning/applets/cbe/models/criteria.py +++ b/src/openedx_learning/applets/cbe/models/criteria.py @@ -98,7 +98,11 @@ class CompetencyCriteriaGroup(models.Model): ) archived = models.BooleanField( default=False, - help_text=_("Hides this row from the criteria-tree read endpoint by default, without removing it."), + help_text=_( + "Set instead of deleting this group once learner status exists for it or anything beneath " + "it. Hides the group from authoring and new associations while keeping it queryable, so its " + "criteria and existing learner status rows stay resolvable." + ), ) history = HistoricalRecords() @@ -313,7 +317,11 @@ class CompetencyCriterion(models.Model): ) archived = models.BooleanField( default=False, - help_text=_("Hides this row from the criteria-tree read endpoint by default, without removing it."), + help_text=_( + "Set instead of deleting this criterion once learner status exists for it. Hides the " + "criterion from authoring and new associations while keeping it queryable, so existing " + "learner status rows stay resolvable." + ), ) history = HistoricalRecords() diff --git a/src/openedx_learning/migrations/0014_competencycriteriagroup_archived_and_more.py b/src/openedx_learning/migrations/0014_add_archived_to_criteria_group_and_criterion.py similarity index 50% rename from src/openedx_learning/migrations/0014_competencycriteriagroup_archived_and_more.py rename to src/openedx_learning/migrations/0014_add_archived_to_criteria_group_and_criterion.py index 4d2a2ac1b..ad8da9468 100644 --- a/src/openedx_learning/migrations/0014_competencycriteriagroup_archived_and_more.py +++ b/src/openedx_learning/migrations/0014_add_archived_to_criteria_group_and_criterion.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.17 on 2026-10-09 17:50 +# Generated by Django 5.2.17 on 2026-10-12 14:50 from django.db import migrations, models @@ -18,7 +18,7 @@ class Migration(migrations.Migration): name="archived", field=models.BooleanField( default=False, - help_text="Hides this row from the criteria-tree read endpoint by default, without removing it.", + help_text="Set instead of deleting this group once learner status exists for it or anything beneath it. Hides the group from authoring and new associations while keeping it queryable, so its criteria and existing learner status rows stay resolvable.", ), ), migrations.AddField( @@ -26,7 +26,7 @@ class Migration(migrations.Migration): name="archived", field=models.BooleanField( default=False, - help_text="Hides this row from the criteria-tree read endpoint by default, without removing it.", + help_text="Set instead of deleting this criterion once learner status exists for it. Hides the criterion from authoring and new associations while keeping it queryable, so existing learner status rows stay resolvable.", ), ), migrations.AddField( @@ -34,7 +34,7 @@ class Migration(migrations.Migration): name="archived", field=models.BooleanField( default=False, - help_text="Hides this row from the criteria-tree read endpoint by default, without removing it.", + help_text="Set instead of deleting this group once learner status exists for it or anything beneath it. Hides the group from authoring and new associations while keeping it queryable, so its criteria and existing learner status rows stay resolvable.", ), ), migrations.AddField( @@ -42,7 +42,7 @@ class Migration(migrations.Migration): name="archived", field=models.BooleanField( default=False, - help_text="Hides this row from the criteria-tree read endpoint by default, without removing it.", + help_text="Set instead of deleting this criterion once learner status exists for it. Hides the criterion from authoring and new associations while keeping it queryable, so existing learner status rows stay resolvable.", ), ), ]