openedx / openedx/openedx-core

[BE] Build Update / Delete (archive) endpoint for a Competency Rule Profile (scoped to user's permissions)

Open
#632 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
10
Forks
32
Avg merge
2d 17h
Merged PRs (30d)
12

Description

Blocked by: #613/#641 (data model), and #631's shared scaffolding (RuleProfileSerializer, is_rule_profile_in_use, the rest_api/v1/ package) — whichever of #631/#632/#633 lands first creates it. Also the one-time openedx-platform wiring from #664.

Repo: openedx-core.

Use Case

As a Platform Administrator, I want to update a Competency Rule Profile's rule (and archive a taxonomy-scoped one I no longer want), so that I can adjust the default mastery threshold — while being warned before a change affects learners who already have recorded statuses, and without ever hard-deleting a profile that learner history depends on.

Description

Current state

No CompetencyRuleProfile API exists. Per the merged ADR: a profile's scope fields are immutable after creation — only rule_type/rule_payload may be edited. Retirement is archive-only, never a hard delete, even pre-use (a carve-out from the general delete-protection rule) via the archived column; archived profiles remain queryable so existing criteria and learner history stay resolvable. The system-default profile is not edited through this API at all (6.7, Django admin only) — this ticket covers taxonomy-scoped profiles exclusively.

Requested change

  • Update PATCH /cbe/rest_api/v1/rule-profiles/<pk>/ — edits rule_type/rule_payload only, for a taxonomy-scoped profile (permission: can_change_taxonomy on its taxonomy; the system default is 6.7/Django-admin only, not this endpoint) and to the system-default profile (permission: is_taxonomy_admin/platform-admin only. Attempting to change any scope field returns 400 (never silently ignored). No backend in-use gate (2026-07-20): ADR 0003 Decision 4's warning/confirmation requirement is a Studio-level guardrail (0003-competency-criteria-versioning.rst:40-43), not an API requirement — this endpoint applies the update unconditionally; the warning dialog lives in Studio (6.5).
  • Delete = archive POST /cbe/rest_api/v1/rule-profiles/<pk>/archive/ — sets archived = true, idempotent. Taxonomy-scoped only; the system-default cannot be archived/deleted. No in-use gate on archive — archiving doesn't retroactively change what already-resolved criteria point to (ADR 0003 Decision 4 lists edit / reassignment / override-switching as the warning triggers; archiving is not among them). Never a hard delete; no DELETE verb.

Explicitly out of scope

  • Creating profiles (#631) and Get (#633).
  • Organization/course-scoped profiles (deferred) and their fields.
  • System-default profile updates (2026-07-20: Ticket 7, Django admin only — not this endpoint).
  • Un-archive/reactivation.
  • Reassignment mechanics on CompetencyCriterion rows (6.8 owns it).
  • Any UI (6.5).

Acceptance Criteria

Verifiable via Postman.

Scenario: Update a taxonomy-scoped profile's rule when not in use
  Given a taxonomy-scoped profile exists and is not in use, requester has can_change_taxonomy
  When a PATCH updates "rule_type"/"rule_payload"
  Then the response is 200 and the change is applied

Scenario: In-use edit requires confirmation
  Given a taxonomy-scoped (or system-default) profile is in use
  When a PATCH changes a rule field  Then the response is 200 and the change is applied

Scenario: Reject a scope-field change
  Given any profile exists
  When a PATCH includes "competency_taxonomy_id" (or organization_id/course_id)
  Then the response is 400 identifying the scope field as immutable


Scenario: Archive a taxonomy-scoped profile (no in-use gate)
  Given a taxonomy-scoped profile, in use or not
  When an archive request is sent (no confirm flag)
  Then the response is 200 and the profile is archived
  And a subsequent read shows "archived": true, and the row + referencing criteria remain resolvable

Scenario: System-default cannot be archived
  When an archive request targets the system-default profile
  Then the response is 400/403 (archiving the seeded default is not allowed)

Scenario: No hard delete
  When a DELETE verb is sent to the profile endpoint
  Then the response is 405

Scenario: Reject update/archive without permission
  Given the requester lacks the applicable permission
  Then update and archive return 403

Context

  • Merged ADR (0002 Decisions 3 & 7, 0003 Decision 4, PR #636): scope immutability; archive-only, never hard-delete; the three in-use warning triggers (edit, reassignment, override-switch) — archive is not one.
  • 07-14 meeting: system-level rule "people in these areas will not be able to edit"; taxonomy-level editable by taxonomy/course authors.
  • #631: shared scaffolding, RuleProfileSerializer, is_rule_profile_in_use, permission reuse.
  • src/openedx_tagging/rules.py (is_taxonomy_admin, can_change_taxonomy), rest_api/v1/permissions.py (TaxonomyObjectPermissions).

Technical Notes

Files to Modify
File Nature
src/openedx_learning/applets/cbe/api.py add update_rule_profile(profile_id, *, rule_type=None, rule_payload=None) and archive_rule_profile(profile_id) (idempotent)
src/openedx_learning/applets/cbe/rest_api/v1/views.py add RuleProfileDetailView(generics.RetrieveUpdateAPIView) (patch only for writes here; get is #633) and RuleProfileArchiveView(generics.GenericAPIView)
src/openedx_learning/applets/cbe/rest_api/v1/permissions.py can_change_taxonomy for taxonomy-scoped
src/openedx_learning/applets/cbe/rest_api/v1/urls.py register rule-profiles/<int:pk>/ (patch) and rule-profiles/<int:pk>/archive/ (post)
.../rest_api/v1/tests/test_views.py update / immutable-scope / archive / no-hard-delete / permission tests
Implementation Notes

Archive is a dedicated POST .../archive/ action, not PATCH {"archived": true} — keeps rule-editing and lifecycle retirement on separate code paths, legible from the URL (neither is backend in-use-gated, only frontend in-use-gated). perform_update: reject any scope-field key (400); else call update_rule_profile(...) unconditionally (never serializer.save() directly; no in-use confirmation gate). Resolve the applicable permission via can_change_taxonomy on the target row's taxonomy (system-default is out of scope for this endpoint so no platform-admin branch is needed here). archive_rule_profile refuses the system-default. http_method_names excludes delete on the detail view (→ 405).

Example Resolution Prompt

In openedx-core, add Update and Archive for CompetencyRuleProfile (reusing #631's RuleProfileSerializer, is_rule_profile_in_use). Add update_rule_profile(profile_id, *, rule_type=None, rule_payload=None) and archive_rule_profile(profile_id) (idempotent; refuses the system-default) to applets/cbe/api.py. Add RuleProfileDetailView(generics.RetrieveUpdateAPIView) at rule-profiles/<int:pk>/ (http_method_names excludes delete) whose perform_update rejects scope-field changes (400) and calls update_rule_profile unconditionally (no in-use confirmation gate). Add RuleProfileArchiveView at rule-profiles/<int:pk>/archive/ (POST, no in-use gate). Gate taxonomy-scoped edits to can_change_taxonomy; system-default is out of scope for this endpoint (6.7, Django admin) and its archive is disallowed. Return 200/400/403/405 per the AC. Never implement a hard delete.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the shared RuleProfileSerializer and scaffolding from #631, then read src/openedx_learning/applets/cbe/api.py, rest_api/v1/views.py, permissions.py, urls.py, and the existing tests. Implement taxonomy-scoped rule updates and the dedicated archive action with immutable-scope validation, permission checks, and no DELETE route. Run rest_api/v1/tests/test_views.py and verify the listed 200/400/403/405 behaviors.

Written by the indexing model from the issue text.

Assessment

Tech stack
django, python
Domain
api, authorization, backend, testing
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.