Repository navigation
[BE] Update taxonomy Create & Import endpoints to accept Taxonomy Type #614
Description
Use Case
As a Platform Administrator, I need the taxonomy Create and Import endpoints to accept a Taxonomy Type field so that I can indicate when a taxonomy should be treated as a Competency Taxonomy rather than a standard tag taxonomy.
Description
This issue updates the REST Create endpoint and REST Import action in openedx-core to accept an optional taxonomy_type field. The field accepts two values: "competency" (triggers creation of a CompetencyTaxonomy record alongside the base Taxonomy in a single transaction) and "tags" (creates a plain Taxonomy; also the default when the field is omitted). Unsupported values, including "system", must be rejected with a 400 — system taxonomies are created programmatically and must not be user-creatable via these endpoints.
Taxonomy type is a derived property of the model structure — a competency taxonomy has a CompetencyTaxonomy MTI child row, a system taxonomy has system_defined = True, and a tag taxonomy is neither. No new type column is added to the schema.
Architecture constraint: openedx_tagging is a standalone library with no dependencies on other openedx-core packages. It must not import or instantiate CompetencyTaxonomy. The creation of a CompetencyTaxonomy row belongs in the CBE applet (openedx_learning/applets/cbe/), which depends on openedx_tagging — not the other way around. This ticket updates the openedx_tagging serializer and view layer to accept and surface taxonomy_type, but the dispatching of competency-specific creation logic is handled at a higher layer. The specific mechanism (view override, mixin, or extension point in the CBE applet) is an implementation decision for the developer.
Endpoints in scope:
POST /api/tagging/v1/taxonomies/— the direct Create endpoint in openedx-corePOST /api/content_tagging/v1/taxonomies/import/— the Studio import endpoint in openedx-platform, whosecreate_import()action inherits fromTaxonomyViewin openedx-core. Most of the work is in the parent class; the openedx-platform layer needs verification thattaxonomy_typeflows through thesuper()call correctly.
Out of scope:
- Returning
taxonomy_typein GET/LIST responses (see [BE] Update Get endpoint to return Taxonomy Type. #618).
Acceptance Criteria
These scenarios are verifiable via Postman against a local or dev environment.
Scenario 1: Creating a Competency Taxonomy
Given a POST request to /api/content_tagging/v1/taxonomies/
And the request body includes taxonomy_type set to "competency"
When the endpoint processes the request
Then the response returns 201
And a CompetencyTaxonomy record is created linked to the new Taxonomy
Scenario 2: Creating a taxonomy with taxonomy_type set to "tags"
Given a POST request to /api/content_tagging/v1/taxonomies/
And the request body includes taxonomy_type set to "tags"
When the endpoint processes the request
Then the response returns 201
And no CompetencyTaxonomy record is created
Scenario 3: Creating a taxonomy without a taxonomy_type (default)
Given a POST request to /api/content_tagging/v1/taxonomies/
And the request body does not include a taxonomy_type field
When the endpoint processes the request
Then the response returns 201
And the behavior is identical to Scenario 2
Scenario 4: Rejected taxonomy_type values
Given a POST request to /api/content_tagging/v1/taxonomies/
And the request body includes taxonomy_type set to "system" or any unsupported value
When the endpoint processes the request
Then the response returns 400
And the validation error identifies taxonomy_type as the invalid field
Scenario 5: Importing a Competency Taxonomy
Given a multipart POST request to /api/content_tagging/v1/taxonomies/import/
And the request includes a valid taxonomy file and taxonomy_type set to "competency"
When the endpoint processes the request
Then the import completes successfully
And a CompetencyTaxonomy record is created linked to the new Taxonomy
Scenario 6: Importing without a taxonomy_type (default)
Given a multipart POST request to /api/content_tagging/v1/taxonomies/import/
And the request does not include a taxonomy_type field
When the endpoint processes the request
Then the import completes successfully
And no CompetencyTaxonomy record is created
Scenario 7: Rejected taxonomy_type on import
Given a multipart POST request to /api/content_tagging/v1/taxonomies/import/
And the request includes taxonomy_type set to "system" or any unsupported value
When the endpoint processes the request
Then the response returns 400
And the validation error identifies taxonomy_type as the invalid field
Technical Notes
Files to Create
The CBE applet will need a create_competency_taxonomy() function. If openedx_learning/applets/cbe/api.py does not already exist (from the data model ticket), it should be created here.
Modified Files
openedx-core
| File | Nature of modification |
|---|---|
src/openedx_tagging/rest_api/v1/serializers.py |
Add taxonomy_type as a write-only ChoiceField |
src/openedx_tagging/rest_api/v1/views.py |
Update perform_create() and create_import() to surface taxonomy_type for use by higher layers |
openedx_learning/applets/cbe/api.py |
Add create_competency_taxonomy() — wraps create_taxonomy() and creates the CompetencyTaxonomy row in a single transaction.atomic() |
openedx-platform
| File | Nature of modification |
|---|---|
openedx/core/djangoapps/content_tagging/rest_api/v1/views.py |
Verify TaxonomyOrgView.create_import() (lines 109–133) passes taxonomy_type through the super() call; patch if it does not |
Implementation Notes
-
Layering constraint.
openedx_tagging/api.py::create_taxonomy()creates only a plainTaxonomy. It must not import or call anything fromopenedx_learning. TheCompetencyTaxonomycreation belongs exclusively in the CBE applet. -
create_competency_taxonomy()in the CBE applet. This function callscreate_taxonomy()then creates a linkedCompetencyTaxonomyrow, wrapped intransaction.atomic(). It is the only place that knows about both layers. -
Routing in the REST layer. The
openedx_taggingview acceptstaxonomy_typefrom validated data and makes it available, but the dispatch tocreate_competency_taxonomy()happens at a layer that can see bothopenedx_taggingand the CBE applet. The specific mechanism (a CBE-provided view mixin that overridesperform_create(), or an extension point) is an implementation decision — choose the approach that best fits the existing extension patterns in this codebase. -
Validation in the serializer. Add
taxonomy_typeas aChoiceFieldwithrequired=False,write_only=True, andchoices=["tags", "competency"]. Omitting the field is equivalent to"tags"."system"is intentionally absent, producing a standard DRF 400 if attempted. -
Coordination with [BE] Update Get endpoint to return Taxonomy Type. #618 (GET responses). This ticket owns the write side of
taxonomy_type: awrite_only=TrueChoiceFieldonopenedx_tagging's serializer. Issue [BE] Update Get endpoint to return Taxonomy Type. #618 owns the read side, implemented separately in openedx-platform'sTaxonomyOrgSerializerper ADR 0013 (docs/openedx_tagging/decisions/0013-competency-taxonomy-detection.rst) — a different serializer in a different repo, not a second field on this one. The two don't share code or constants: [BE] Update Get endpoint to return Taxonomy Type. #618 derives its value directly from theCompetencyTaxonomyrelation rather than reading anything this ticket writes. -
create_import()action. Locate this action inTaxonomyViewinviews.pyand apply the sametaxonomy_typehandling asperform_create(). The openedx-platformTaxonomyOrgView.create_import()callssuper().create_import(), so if the parent handlestaxonomy_type, the platform layer likely requires no code changes — only verification that the field is not stripped before thesuper()call.
Example Resolution Prompt
Across openedx-core and openedx-platform, add taxonomy_type support to taxonomy creation and import. In openedx-core/src/openedx_tagging/rest_api/v1/serializers.py, add taxonomy_type as a write-only ChoiceField with required=False and choices=["tags", "competency"]. In src/openedx_tagging/rest_api/v1/views.py, update both perform_create() (around line 262) and the create_import() action to surface taxonomy_type from validated data for use by higher layers — do not call CompetencyTaxonomy directly from this layer. In openedx_learning/applets/cbe/api.py, add create_competency_taxonomy(): call create_taxonomy() from openedx_tagging, then create the linked CompetencyTaxonomy row, wrapped in transaction.atomic(). Wire the dispatch to create_competency_taxonomy() at a layer that can see both openedx_tagging and the CBE applet — a CBE-provided view mixin overriding perform_create() is one viable approach. In openedx-platform/openedx/core/djangoapps/content_tagging/rest_api/v1/views.py, verify that TaxonomyOrgView.create_import() (lines 109–133) does not strip taxonomy_type before calling super().create_import(); patch if needed. Reject "system" and all other unsupported values with a 400 on both endpoints.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
[-]Update taxonomy Create endpoint to accept Taxonomy Type[/-][+][BE] Update taxonomy Create endpoint to accept Taxonomy Type[/+]jesperhodge commentedon Jun 30, 2026
Some key findings from Claude - I'm evaluating whether they are correct:
"
Major architecture problem: layering violation. The Technical Notes have openedx_tagging/api.py importing CompetencyTaxonomy from openedx_learning. ADR 0002 for tagging explicitly says openedx_tagging is a standalone library with no dependencies on other Open edX projects. Having openedx_tagging/api.py call CompetencyTaxonomy(taxonomy).save() from openedx_learning violates this contract and will fail .importlinter enforcement.
The correct split: openedx_tagging/api.py::create_taxonomy() creates only a plain Taxonomy. A new openedx_learning/applets/cbe/api.py::create_competency_taxonomy() calls create_taxonomy() then wraps it. The REST endpoint needs to either (a) live in openedx_learning and call both, or (b) use a registered hook/extension point. This is an open design question the issue doesn't address at all and it's a blocker.
write_only=True conflicts with Issue #618. If taxonomy_type is write_only in the serializer, it won't appear in GET responses. But #618 is a separate issue that adds it as a SerializerMethodField. The two-field approach (a write-only ChoiceField for input and a separate read-only SerializerMethodField for output) is fine but needs to be explicit and the shared constant needs to be defined once. Currently neither issue references this coordination clearly.
"
[-][BE] Update taxonomy Create endpoint to accept Taxonomy Type[/-][+][BE] Update taxonomy Create & Import endpoints to accept Taxonomy Type[/+]bradenmacdonald commentedon Jun 30, 2026
Related to this suggestion from Claude, do we allow users to create a regular taxonomy and then convert it to a Competency taxonomy?
I guess I have a slight preference for the Competency app being built "on top" of the tagging app, without the tagging app being aware of competencies / taxonomy types, but its not a strong opinion.
jesperhodge commentedon Jul 1, 2026
I think
The correct splitidea by Claude makes sense.The taxonomy/tagging API having a side effect that creates a DB entry for
CompetencyTaxonomyis the kind of mix of concerns that we were trying to avoid by making the competency work an applet, and avoiding anis_competencyfield on the taxonomy table in the first place.So I think if we define all competency-related APIs in the CBE applet and have that applet depend on
openedx_tagging, and call the business logic for creating a taxonomy atomically together with creating theCompetencyTaxonomytable row, that resolves this neatly.As far as I know there's no problem with openedx_learning depending on openedx_tagging - or am I wrong?
At any rate this applies to the models as well. The competency criteria models have foreign keys to the tagging / taxonomy models, so openedx_learning already depends on openedx_tagging.
jesperhodge commentedon Jul 1, 2026
We were discussing splitting up this issue into two parts.
I would like us to make sure that we have at least one issue for the openedx-core changes and one for the edx-platform changes. That makes it a lot easier to get the related PRs across the finish line.
mgwozdz-unicon commentedon Jul 1, 2026
@jesperhodge
Okay, I updated the ticket description to address the issues that were identified here:
Let me know if anyone disagrees with any of these conclusions.
@bradenmacdonald
That is not currently in scope, but I don't think the current approach precludes it from being added later. I could see some folks potentially wanting that down the road.
mgwozdz-unicon commentedon Jul 1, 2026
Note that #615 is the frontend counterpart to this ticket.
24 remaining items
feat: add taxonomy_type to TaxonomyOrgView
feat: add taxonomy_type to TaxonomyOrgView
feat: add taxonomy_type to TaxonomyOrgView (#39086)
dvarenikqaconsultant commentedon Sep 17, 2026
Test report:
taxonomy_typeon Create and Import endpointsEnvironment:
https://studio.master.openedx.io(Studio/CMS API),https://master.openedx.io(LMS, OAuth2 token issuance)Test user:
dmitryvarenikqa(superuser, staff)Test type: API testing via Postman
Overall result: All Acceptance Criteria scenarios from #614 pass on this environment.
Create endpoint —
POST /api/content_tagging/v1/taxonomies/taxonomy_type"competency"Taxonomyand a linkedCompetencyTaxonomyCompetencyTaxonomyrecord was confirmed in Django admin (/admin/openedx_learning/competencytaxonomy/) with the same id as the created taxonomy."tags"TaxonomyCompetencyTaxonomyrecord was created.CompetencyTaxonomyrecord was created."system"taxonomy_type{"taxonomy_type": ["\"system\" is not a valid choice."]}. Taxonomy was not created."unknown_type"taxonomy_type{"taxonomy_type": ["\"unknown_type\" is not a valid choice."]}. Taxonomy was not created."Competency"(capitalized)""(empty string){"taxonomy_type": ["\"\" is not a valid choice."]}. Taxonomy was not created."competency")taxonomy_type(write-only field)Import endpoint —
POST /api/content_tagging/v1/taxonomies/import/taxonomy_type"competency"CompetencyTaxonomytags_count: 4; matchingCompetencyTaxonomyrecord confirmed in Django admin with the same id."tags"CompetencyTaxonomyrecord was created.CompetencyTaxonomyrecord was created."system"taxonomy_type"unknown_type"taxonomy_type{"taxonomy_type": ["\"unknown_type\" is not a valid choice."]}.Additionally tested
{"file": ["The submitted data was not a file. Check the encoding type on the form."]}. Not part of the original Acceptance Criteria, but confirms the endpoint's baseline required-field validation still holds with the new field in place.