Repository navigation
feat: add blocked-courses endpoint for a competency - #7
Closed
alezconsultant wants to merge 2 commits into
Closed
alezconsultant wants to merge 2 commits into
alezconsultant wants to merge 2 commits into
Conversation
alezconsultant
force-pushed
the
alezconsultant/666-competency-hierarchy-dominance
branch
from
October 26, 2026 13:01
4957939 to
d5091d0
Compare
alezconsultant
force-pushed
the
alezconsultant/796-blocked-courses
branch
from
October 27, 2026 10:22
727cc84 to
2e373c5
Compare
alezconsultant
force-pushed
the
alezconsultant/666-competency-hierarchy-dominance
branch
2 times, most recently
from
October 28, 2026 10:58
a8114ea to
7033c48
Compare
Reject a CompetencyCriterion at creation time if any taxonomy ancestor or descendant of its competency tag already has a live CompetencyCriterion in the same course. Siblings, and a repeat criterion for the same tag in the same course, stay allowed. Archived criteria, and criteria under an archived leaf, course-level, or root group, do not count, since ADR 0002 hides archived rows from authoring and new associations. Add to openedx_tagging/api.py: get_ancestor_tags(tag) and get_descendant_tags(tag), both returning a QuerySet[Tag], mirroring Tag.descendant_count's own lineage-prefix mechanism and its free-text-taxonomy guard. Add to applets/cbe/api.py: get_courses_with_relative_criteria(tag_ids, course=None), a public function mapping each course already blocked by one of the given tags to the lowest-id conflicting tag, narrowed to one course when course is given. It only counts criteria on a leaf group, whose parent carries the course, so a criterion misplaced on another group is skipped rather than failing every create for the tag. Add _validate_containment(group, course), called from create_competency_criterion() before the object is tagged. create_competency_criterion() takes the caller's course_run instead of re-parsing object_id and fetching the course run a second time, and its rule fields are keyword-only. It runs inside the transaction and locks that associate_competency_criterion() holds. Add CompetencyCourseRun, a CBE-owned row whose primary key is a OneToOneField to the catalog's CourseRun (migration 0016). associate_competency_criterion() creates it with get_or_create() on a course's first criterion and locks it with select_for_update() right after the Tag lock, so two concurrent creates in the same course cannot both pass the containment check. Locking this row instead of CourseRun keeps the lock from blocking other apps' inserts that reference the course run, and keeps openedx_learning from querying catalog tables directly. Related to openedx#666 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds GET api/cbe/v1/competencies/<tag_id>/blocked-courses/, which lists the courses where a taxonomy ancestor or descendant of the competency already has a criterion, so authoring can gray them out before the create-criterion endpoint rejects them. An optional comma-separated course_keys parameter narrows the list, and the result is paginated. get_blocked_courses() reuses get_courses_with_relative_criteria(), so this read and the write-side check always agree. CompetencyReadPermission is copied verbatim from the criteria-groups endpoint's PR. Related to openedx#796 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alezconsultant
force-pushed
the
alezconsultant/796-blocked-courses
branch
from
October 28, 2026 15:48
2e373c5 to
f36c0a2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Implements openedx#796: a read-only endpoint that lists the courses where a competency
can't get a new
CompetencyCriterion, because a taxonomy ancestor or descendant of it already hasone there.
Each row carries the blocked course's key and the conflicting tag's id and value:
{"count": 1, "next": null, "previous": null, "results": [ {"course_key": "course-v1:Org+Course+Run", "conflicting_tag_id": 12, "conflicting_tag_value": "Critical Thinking"} ]}This PR is built on top of openedx#875 (pr), which provides
get_courses_with_relative_criteriaand thetagging hierarchy helpers.
CompetencyReadPermissionis copied verbatim from openedx#878 (openedx#681), so this PR doesn't depend onopenedx#878. The two copies are identical, so whichever PR merges second reuses the class from the
first, and the merge conflict there resolves to the same lines.
Changes
src/openedx_learning/applets/cbe/api.py: addsget_blocked_courses(tag, course_keys=None)(UNSTABLE) and the
BlockedCoursedataclass. It calls [BE] Add Competency hierarchy dominance validation to Competency Criteria Creation openedx/openedx-core#666'sget_courses_with_relative_criteriawithout a course, so the read and the write-side rejection share one query and always agree on
which courses are blocked and which tag is named. That includes feat: enforce competency-hierarchy dominance for criteria openedx/openedx-core#875's handling of archived rows:
an archived criterion, or one under an archived group, blocks nothing in either place.
rest_api/v1/views.py,serializers.py,urls.py,rest_api/paginators.py: addsCompetencyBlockedCoursesView,BlockedCourseSerializer,CompetencyBlockedCoursesQueryParamsSerializer, andCompetencyBlockedCoursesPagination(page size 100, max 500, pinned like
CompetencyRuleProfilePagination). Authentication andpermission match feat: add GET competency criteria-groups endpoint openedx/openedx-core#878's criteria-groups view.
rest_api/v1/permissions.py: addsCompetencyReadPermission, identical to feat: add GET competency criteria-groups endpoint openedx/openedx-core#878's.grandchild direction, plus API-level tests and the edge cases listed below.
Testing instructions
Automated:
pytest tests/openedx_learning/applets/cbe tests/openedx_tagging/test_api.py --no-cov.Manual, against a Tutor devstack with this branch's
openedx-coremounted. Mount the CBE API inyour local
cms/urls.pywithpath('api/cbe/', include('openedx_learning.urls')), runtutor dev exec cms ./manage.py cms migrate, and restart CMS. Log in as a staff user and get a JWTAuthorizationheader, then use three real course keys as Course X, Y and Z.POST /api/content_tagging/v1/taxonomies/with"taxonomy_type": "competency") with the tags "Cognitive Skills" > "Critical Thinking" >"Inference", plus "Analysis" under "Critical Thinking" as a sibling of "Inference".
POST /api/cbe/v1/competencies/<tag_id>/criteria/: "Critical Thinking" inCourse X, "Cognitive Skills" in Course Y, "Analysis" in Course Z.
GET /api/cbe/v1/competencies/<inference_id>/blocked-courses/. Expect 200 with Course X naming"Critical Thinking" and Course Y naming "Cognitive Skills". Course Z doesn't appear, because
"Analysis" is a sibling of "Inference".
GETthe same endpoint for "Critical Thinking". Expect Course Y naming "Cognitive Skills" andCourse Z naming "Analysis". Course X doesn't appear, because a tag's own criterion doesn't
block it.
?course_keys=<Course X>. Expect only Course X, withcount1.?page_size=1. Expectcount2, one row, and anextlink that returnsthe other course.
?course_keys=not-a-keyand with?course_keys=. Expect 400 for both.GETthe endpoint fortag_id=999999. Expect 404.GETthe endpoint as a non-staff user. Expect 403.Other information
openedx_learning.api. The change is purely additive.BlockedCourseSerializerand the query-params serializer carry# pylint: disable=abstract-method, the same as the plainSerializersubclasses inopenedx_taggingandopenedx_content.Related to openedx#796
🤖 Generated with the help of Claude Code