Skip to content

feat: hide archived tagging records in Studio's content_tagging app - #39360

Draft
tbain wants to merge 8 commits into
openedx:masterfrom
Unicon:tbain/903_hide_archived_tagging_records
Draft

tbain wants to merge 8 commits into
openedx:masterfrom
Unicon:tbain/903_hide_archived_tagging_records

Conversation

@tbain

@tbain tbain commented Nov 9, 2026 •

Copy link
Copy Markdown

Description

Studio's content_tagging app now respects the archived state that openedx-core gives taxonomies, tags, and tag associations. Three places change: the taxonomy list's tags_count leaves archived tags out; get_all_object_tags, the one read behind the tag export, block duplication, clipboard copy, and library containers, returns only associations that archiving does not hide; and course tag import keeps the associations an author cannot see, and skips tags from a taxonomy that is archived on this instance instead of creating placeholders for them.

Why

openedx-core hides archived records on its read paths (openedx/openedx-core#778), and keeps an archived association when tag_object() replaces an object's tags (tbain/openedx-core#5). Studio's content_tagging app counts tags, reads associations, and imports course tags with its own queries, so without this change an author would still see archived tags in Studio's taxonomy counts and would carry archived associations along when exporting, duplicating, copying, or importing content. openedx/openedx-core#903 describes the gap and the acceptance criteria.

What changed

  • TaxonomyOrgView.get_queryset (rest_api/v1/views.py) annotates tags_count=Count("tag", filter=Q(tag__archived=False), distinct=True), the same expression openedx-core's TaxonomyView uses. One annotation covers the no-filter, org, and unassigned branches.
  • get_all_object_tags (api.py) starts its query from ObjectTag.objects.unarchived(). An association is left out when it is archived, or when its tag or taxonomy is archived. Because the CSV header is built from the returned rows, an archived taxonomy drops out of the header too. Associations under a disabled taxonomy are not hidden by archiving and still travel as before. Nothing changes at the call sites in generate_csv_rows, copy_object_tags, openedx/core/lib/xblock_serializer/block_serializer.py, or content_libraries/api/serializers.py.
  • set_exported_object_tags (api.py):
    • clears with openedx-core's delete_object_tags(object_id, keep_hidden=True), a keyword argument that feat: keep hidden tag associations when tag_object replaces an object's tags tbain/openedx-core#5 adds, so a hidden association (one that is archived, or whose tag or taxonomy is archived) survives a re-import into the same course. Placeholders, associations whose tag was deleted, and associations under a disabled taxonomy are still cleared and re-created from the file. A deletion-locked association is archived instead of deleted, as with every delete_object_tags call.
    • resolves each export id through the unfiltered Taxonomy.objects.filter(export_id=...).first(), because get_taxonomy_by_export_id returns None for an archived taxonomy and a missing one alike. A live taxonomy is tagged as before. An archived taxonomy is skipped, with no placeholder and no events; a placeholder would be wrong because ObjectTag.resync() would re-link it to the archived taxonomy. A missing taxonomy still gets placeholders.
    • returns the number of skipped tag values per archived taxonomy. import_course_tags_from_csv adds those up and writes one INFO log record per archived taxonomy for the whole import, with the count.
  • get_all_object_tags orders by object_id and then id, so the order of values within a taxonomy, and so within a tags.csv cell, is stable.
  • 17 new test functions in content_tagging/tests/test_api.py, rest_api/v1/tests/test_views.py, content_libraries/tests/test_api.py, and cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py, and two extended cms tests (test_duplicate_tags, test_copy_and_paste_unit_with_tags). One pre-existing test, test_get_course_object_tags_with_add_tags, now compares one block's value lists order-insensitively: it builds its expected lists from get_object_tags(), which sorts differently from the id order get_all_object_tags returns, and the test is about no tag being lost.

Who is affected

  • Course Authors, once an openedx-core release with archiving is installed and something produces archived rows: Studio's taxonomy list counts only live tags; exporting, duplicating, copying, pasting, and importing content no longer carries archived associations; and re-importing a course over itself leaves hidden associations in place. With no archived rows, behavior is unchanged.
  • Operators: this code requires an openedx-core release that contains the archive stack. On an older openedx-core it raises errors on the Taxonomies page, tag export, duplicate, copy, and course import, for every course. See "Blocked" below.
  • Developers: get_all_object_tags no longer returns hidden associations. set_exported_object_tags no longer clears them, and it returns a dict where it returned None. delete_object_tags(object_id) still removes hidden associations too; like every delete, it archives a deletion-locked association instead of deleting it.

Supporting information

Testing instructions

Automated, in a tutor dev CMS container with openedx-core mounted from the tbain/903_keep_hidden_object_tags_on_save branch (tbain/openedx-core#5) merged with openedx-core main. The merge is needed because current openedx-platform master imports openedx_learning.api.roll_up_competency_statuses, which is newer than that branch's base. The tests run on SQLite.

  1. The new and changed tests, with the files they live in:

    pytest --ds=cms.envs.test -p no:randomly \
      openedx/core/djangoapps/content_tagging/ \
      "openedx/core/djangoapps/content_libraries/tests/test_api.py::ContentLibraryContainersTest::test_copy_container_leaves_archived_tags_out" \
      "cms/djangoapps/contentstore/views/tests/test_block.py::TestDuplicateItem::test_duplicate_tags" \
      cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py

    All 670 pass on this branch (run on 2026-11-10, rebased on that day's master).

  2. pylint on the touched files, ruff check openedx/core/djangoapps/content_tagging/, and mypy openedx/core/djangoapps/content_tagging/, with DJANGO_SETTINGS_MODULE=cms.envs.test for pylint and mypy.

Manual, in Studio with the same openedx-core code. These four steps were also run on 2026-11-11 against the tutor dev MySQL database, through the Studio REST endpoints and the tag import function, with the results the steps describe. Nothing in the product archives a row yet, so archive one in a Django shell (./manage.py cms shell): from openedx_tagging.models import Tag; Tag.objects.filter(taxonomy__name="<your taxonomy>", value="<a tag>").update(archived=True). Then:

  1. Open the Taxonomies page. The taxonomy's tag count is one lower than before.
  2. Open a block tagged with that tag and export the course. tags.csv has no cell with that value.
  3. Duplicate the block, and copy and paste it into another unit. Neither copy carries the archived tag; the original block still has its row (ObjectTag.objects.filter(...) in the shell shows it).
  4. Import the exported course back into itself. The association to the archived tag is still there, exactly once, and still hidden.

Deadline

None.

Other information

  • No model changes, no migrations, no new dependencies.
  • Known gaps:
    • Copying a library unit into a course is pinned end to end by test_paste_from_library_unit_leaves_archived_tags_out. On that path the pasted blocks get their tags from openedx-core's copy_tags through the upstream link, so the end state is delivered by openedx-core's read filter ([BE] Exclude archived tagging records from read paths and block edits to them openedx-core#778); test_copy_container_leaves_archived_tags_out separately pins what the library serializer and the clipboard carry.
    • A block copied to the clipboard before one of its associations is archived still pastes that tag, because the clipboard stores tags at copy time. The ticket's scenario archives before the copy.
    • Re-importing a file that lists a hidden association's own value makes that association visible again: the import leaves the hidden row in place, and openedx-core's tag_object() un-archives a row whose tag is re-applied ([BE] Branch tag association deletes between archiving and hard delete openedx-core#779). A file exported from the same instance after the archive never lists a hidden value, so this needs an older export or a hand-edited file. No test in this PR pins it.
    • Tags skipped because their taxonomy is archived are not restored if the taxonomy is un-archived later; tags from a taxonomy that does not exist yet are, through placeholders.
    • test_list_taxonomy_query_count passes in the full view test file (498 tests pass) but 3 of its 7 cases fail when it is run alone with -k, each counting more queries than expected (17 against 10, 24 against 23, 26 against 25). It depends on earlier tests in the file warming caches. This PR does not edit that test, and its change to the view adds a filter to an existing count, not a query. Checked on 2026-11-11.
  • Suggested reading order: TaxonomyOrgView.get_queryset in views.py, then get_all_object_tags and set_exported_object_tags in api.py, then TestExportImportTags in tests/test_api.py.
  • AI usage: this PR was developed with Claude (Anthropic) in Cowork, with Sonnet sub-agents implementing each task test-first and a per-task review plus a whole-branch review by fresh sub-agents. I ran every test and lint command locally and reviewed the change.

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Nov 9, 2026
@openedx-webhooks

openedx-webhooks commented Nov 9, 2026 •

Copy link
Copy Markdown

Thanks for the pull request, @tbain!

This repository is currently maintained by @openedx/wg-maintenance-openedx-platform-oncall.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

🔘 Update the status of your PR

Your PR is currently marked as a draft. After completing the steps above, update its status by clicking "Ready for Review", or removing "WIP" from the title, as appropriate.


Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@github-project-automation github-project-automation Bot moved this to Needs Triage in Contributions Nov 9, 2026
@tbain tbain changed the title Tbain/903 hide archived tagging records feat: hide archived tagging records in Studio's content_tagging app Nov 10, 2026
tbain and others added 8 commits November 10, 2026 22:51
… tag import

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjwKFzQ6Tfh5HJ5xzgmGKr
…axonomies are skipped

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjwKFzQ6Tfh5HJ5xzgmGKr
…lock cases

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjwKFzQ6Tfh5HJ5xzgmGKr
… tag import contract

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjwKFzQ6Tfh5HJ5xzgmGKr
@tbain
tbain force-pushed the tbain/903_hide_archived_tagging_records branch from a40f74c to 0b53bc5 Compare November 11, 2026 20:11
@tbain
tbain marked this pull request as draft November 11, 2026 20:45
@tbain

tbain commented Nov 11, 2026 •

Copy link
Copy Markdown
Author

I used our backend-code-review skill to review this before sending it on to independent code review, here were the results:

Original review with some must fix/improvements/small things:

Details # Review of openedx-platform PR 39360: hide archived tagging records in Studio's content_tagging app

Review Details

Prerequisites (skip this section if you know the tagging models and this ticket)

Things the review looked at and is leaving to other tickets

What passed

The head commit merges into the current master without conflicts. With the openedx-core branch mounted, all 653 tests in content_tagging pass, as do the two edited cms tests and the new library test. ruff and mypy are clean on the touched files and pylint printed no messages. Every new or edited test failed when the production line it guards was broken, with one exception (the log assertion, under "Improvements worth doing"). Of 20 factual claims in the description, comments, and docstrings, 14 are true, including that the tag count expression matches openedx-core's, that the queryset delete still archives locked associations after unarchived(), and that pytest keeps only the last -k. MySQL and MariaDB were not run, because the container has SQLite only.

Action list

  1. Convert the pull request to a draft until an openedx-core release contains the archive stack, and correct the pin in the description to 1.21.0.
  2. Say in the description that the pin bump must merge first, or include it.
  3. Correct the delete_object_tags sentence in the docstring of set_exported_object_tags and in the description.
  4. Add a paste-into-course test for a library unit and its child component with archived associations, and correct the "no library fixture" sentence.
  5. Disable a taxonomy in the duplicate test and in the copy and paste test.
  6. Decide whether the import should call an openedx-core function to delete visible associations, and add that function to the openedx-core pull request if so.
  7. Decide and record what a re-import does when the file names a hidden association's value, with a test.
  8. Record that tags skipped under an archived taxonomy are not restored, and raise the resync() behavior in openedx-core.
  9. Decide whether the MariaDB locking and the stale clipboard need action.
  10. Consider the test and logging improvements and the small wording fixes.

Must fix before merge

The pull request calls openedx-core code that neither the pinned release 1.21.0 nor release 1.21.1 contains, so it cannot pass CI, and it is not marked as blocked.

The new code calls ObjectTag.objects.unarchived() in get_all_object_tags and in set_exported_object_tags, and filters on tag__archived in TaxonomyOrgView.get_queryset. None of those names exist in openedx-core 1.21.0, which is what requirements/edx/base.txt pins, or in 1.21.1. The review checked the release tags directly, and ran the content_tagging test package against the 1.21.0 source: 383 tests failed and 270 passed, while the same package passed 634 of 634 at the merge-base. On the pull request itself, 12 CI checks fail, including static analysis and the cms and lms unit test shards, and the pull request is not a draft. If it were merged as it stands, the tag export, block duplication, clipboard copy, the Studio Taxonomies page, and course import would all raise errors for every course, with or without archived records. The description does say the pull request is blocked on a release, but it names the pin as 1.20.0 and says nothing changes for authors until archived rows exist. Convert the pull request to a draft, correct the version to 1.21.0, and state that the openedx-core pin bump has to merge before this change or be part of it.

A docstring says a library function clears every tag association, which is false for deletion-locked associations: the function archives them and leaves them in the table.

The new docstring of set_exported_object_tags in content_tagging/api.py ends with "delete_object_tags clears everything". The review read delete_object_tags in openedx-core: it deletes through the same queryset delete(), so a deletion-locked association is archived and stays in the table. A developer who reads the docstring and reaches for delete_object_tags to remove every row will keep the locked ones without knowing. Reword the sentence to say that delete_object_tags also removes the associations that archiving hides, and still archives a deletion-locked one. The description's "use delete_object_tags for a full clear" needs the same correction.

The acceptance criterion about copying library content into a course has no test that fails if the behavior breaks.

Criterion 9 says that when a library unit with an archived association, and a child component with one, is copied and pasted into a course, neither pasted block carries an archived tag. The new test test_copy_container_leaves_archived_tags_out in content_libraries/tests/test_api.py tags a section and a subsection, not a unit and a component, and checks only what the serializer and the clipboard hold. The review read the paste code in cms/djangoapps/contentstore/helpers.py (_import_xml_node_to_parent): content pasted from a library gets an upstream link, and its tags are then copied from the library block by copy_tags_as_read_only, which is openedx-core's copy_tags. The clipboard's staged tags, which the new test checks, are not used on that path. The review did not run a paste to confirm that the upstream link is set on both the unit and its child. So the criterion's end state is delivered by openedx-core, and no test in this pull request fails if it breaks. The description says no library fixture can exercise the paste step, but ClipboardPasteFromV2LibraryTestCase in test_clipboard_paste.py already pastes library content into a course. Add a test there with an archived association on a library unit and on its child component, and correct that sentence in the description.

The acceptance criterion about tags from a disabled taxonomy surviving duplication and copy and paste is not tested for those two cases.

Criterion 10 says a block's tags from a disabled taxonomy are still carried when the block is exported, duplicated, or copied and pasted. Only the export has a test, test_generate_csv_rows_keeps_disabled_taxonomy. The review added a filter that drops disabled taxonomies to get_all_object_tags and ran the tests: that one test failed, and the duplicate test and the copy and paste test passed. A later change that filters disabled taxonomies out of this query would break duplication and paste without any test noticing. In test_duplicate_tags and test_copy_and_paste_unit_with_tags, disable one of the taxonomies in the setup and keep the existing assertions.

The course import deletes tag associations directly through openedx-core's database models instead of its API functions, so it depends on a delete override that openedx-core does not publish as supported behavior.

set_exported_object_tags used to clear a block's tags by calling the library function delete_object_tags. It now runs ObjectTag.objects.filter(object_id=...).unarchived().delete() itself, and it depends on the queryset's delete() override to archive deletion-locked associations instead of deleting them. openedx-core's AGENTS.md says of each app's api.py that "All mutation goes through here", and says to treat the models "as read-only from outside the app". This is the only place in openedx-platform's non-test code that deletes ObjectTag rows directly. The override's own docstring describes it as a safety net that raw SQL bypasses. If openedx-core later moves the lock handling into its API functions, this import would hard-delete associations that learner mastery depends on, and nothing in openedx-platform would fail. The openedx-core pull request for this ticket adds no function for "delete the visible associations of an object". The suggested change is to add a keyword argument such as keep_hidden=False to openedx-core's delete_object_tags in tbain/openedx-core#5 and call that from the import. The two direct reads in this pull request, ObjectTag.objects.unarchived() and the unfiltered Taxonomy.objects.filter(export_id=...), use the access openedx-core documents as supported and need no change.

Decisions to make or write down

Re-importing a course from a file that names a hidden association's value makes that association visible again, and it is undecided whether that is intended.

Criterion 14 says that after a course is re-imported into itself, a hidden association still exists and stays hidden. The review archived the course's Tag 1.1 association and imported a file that lists Tag 1.1 for the course: the same row came back un-archived and visible. This happens because the import leaves the hidden row in place and openedx-core's tag_object un-archives a row whose tag is applied again, which is what openedx/openedx-core#779 asks for. A file exported from the same instance never lists a hidden value, so this needs an older export or a hand-edited file. No test pins either outcome. Decide which behavior is intended, say so in the pull request, and add a test for it.

Tags skipped during import because their taxonomy is archived are not restored later, and some existing placeholders can still be linked to an archived taxonomy.

When the import meets a taxonomy that is archived on this instance, set_exported_object_tags skips its tags and creates no placeholder. If that taxonomy is un-archived later, the skipped tags do not come back, whereas tags from a taxonomy that does not exist yet do come back once it is created. Separately, openedx-core's resync() looks taxonomies up without the archive filter, so a placeholder that already exists when a taxonomy with its export id is archived will be linked to the archived taxonomy by the next taxonomy tag-file import. The review established both by reading the code. The first is a product decision to record in the pull request. The second is an openedx-core change, and it fits openedx/openedx-core#778, which refuses edits to archived records.

On MariaDB, the import's delete may lock tag and taxonomy rows that every course shares.

The delete() override in openedx-core locks the rows it is about to delete with select_for_update(). On MySQL and PostgreSQL it restricts the lock to the ObjectTag table; its comment says MariaDB does not support that restriction. Before this pull request the import's delete filtered on object_id only. With unarchived() the query joins the tag table and the taxonomy table twice. The review inferred, without running MariaDB, that each block's delete would then lock the joined taxonomy rows, which every course shares, so two imports running at once could wait on each other or deadlock. If the delete moves into an openedx-core function as suggested above, that function can select the visible primary keys first and lock only those rows.

A block copied before one of its tag associations is archived still pastes that tag afterward.

Copying a block stores its tags on the clipboard at copy time. A reviewer changed test_copy_and_paste_unit_with_tags so that the association is archived after the copy and before the paste, and the pasted block received both tags as visible associations. Criterion 8 archives the association before the copy, so the pull request meets the criterion as written. Only the reviewer from a different model family raised this. Decide whether a stale clipboard matters for this ticket.

Improvements worth doing

No single test runs the export and the import on the same data.

Chain export and import in one test. Criteria 6 and 11 describe a course that is exported and then imported as a new course. The tests check the export (test_generate_csv_rows_excludes_archived) and the import (test_import_skips_archived_taxonomy) separately, each with its own hand-written data. The review exported a course with an archived association and an archived taxonomy and imported the file into a second course key, and the result was correct. A test that does the same would catch a mismatch between what the export writes and what the import reads.

The test for one criterion never runs the later taxonomy import that the criterion describes.

Run the later taxonomy import that criterion 12 describes. Criterion 12 says tags left out by the import cannot reappear after an administrator later imports a tag file into a taxonomy. test_import_skips_archived_taxonomy asserts that no placeholder and no association under the archived taxonomy exist right after the course import, which is what makes the criterion hold, but it does not run the later taxonomy import. With the skip removed, the review ran that step and saw the placeholder linked to the archived taxonomy's tag. Adding a taxonomy tag-file import and a resync to the test makes it exercise the criterion as written.

One query returns its rows in an order the database is free to choose.

Give get_all_object_tags a stable order. get_all_object_tags orders its rows by object_id only, so the order of values within one taxonomy is whatever the database returns. That order feeds the cells of the exported tags.csv and the tags a copy creates. The pre-existing test test_get_course_object_tags_with_add_tags had to be changed to compare without regard to order, because the joins added by unarchived() changed the order on SQLite; the review confirmed the old form of the test fails on the new code and passes at the merge-base. MySQL was not run. Adding id as a second ordering key makes the order stable. A run showed that this does not restore the order the old test expected, so the test change stays either way.

The import writes one INFO log record for each block and archived taxonomy that lists every skipped value, and one record reached 115,028 characters for 2,000 values.

Log the skipped tags once, with a count. The import writes one INFO log record for each block and archived taxonomy, containing every skipped value. The review imported 2,000 values and got a single record of 115,028 characters. The values are taxonomy values chosen by authors, not learner data. Logging once per taxonomy for the whole import, with the number of skipped tags, carries the same information.

One test mixes assertions about stored data with assertions about logs and events.

Separate the behavior assertions from the log and event assertions. test_import_skips_archived_taxonomy asserts the number of events sent, that a log record exists, and the text of the first log record, and it keeps its assertions about stored rows inside the same with block. When the review removed the log.info line, the test failed with "no logs of level INFO", which is a failure about how the code is written. Keep the assertions about which rows exist, and drop or move the others.

A test helper takes the record to hide but hides a whole tag or taxonomy in two of its three cases.

Make the test helper hide only the row under test. The helper _hide_association in content_tagging/tests/test_api.py takes the primary key of the association to hide, but uses it in only one of its three branches. The other two archive a whole tag or a whole taxonomy by a fixed fixture. Pass the update to perform from the test's parameters, or filter by the row.

One test computes its expected rows the way the production code does, so it does not check the product independently.

Write the expected rows of the all-archived export test as a literal. test_generate_csv_rows_all_archived builds its expected rows by truncating the fixture's CSV and computes the block id prefix the same way the production code does. Its assertion that the first expected row is the three fixed column names checks the fixture, not the product. A literal expected value is an independent source.

Several tests differ only in their input and could be merged.

Merge tests that differ only in their input. test_list_taxonomy_tags_count_all_archived_is_zero can be a fourth row of the parameterized tag count test next to it. The course tests of get_all_object_tags for an archived association and for an archived tag differ only in what they archive and what they expect, so they fit one parameterized test. The repository already uses ddt for this.

Small things

A docstring says "as before", which describes history rather than the current state.

The docstring of set_exported_object_tags says placeholder associations are created "as before". The workspace writing rules ask for the current state, not history.

A docstring uses different wording for hidden associations than the tests and the ticket do.

The same docstring says "keeping what archiving hides" and names ObjectTag.objects.unarchived(), while the tests and the ticket say "hidden associations". One term throughout is easier to follow, and the docstring can state the rule without naming the queryset method.

The description gives the wrong test count for master.

The description says "653 tests on master". 653 is the count on this branch; the merge-base has 634.

The description's count of new tests leaves one out, and the testing commands do not run it.

The description counts 13 new test functions and 19 cases, which is right for the two files it names. The diff has a fourteenth, test_copy_container_leaves_archived_tags_out, and the testing commands do not run it.

The pull request title is not in conventional commit form.

The title "Tbain/903 hide archived tagging records" is not a conventional commit. The commitlint check passes because it checks the commits.

The documentation style of the new tests is inconsistent.

Some new tests have a docstring that restates the test name and others have only comments.

Accuracy check of this text

A separate accuracy reviewer compared this text with the review's own draft and returned "pass with fixes"; the five fixes it asked for are applied, and the cleanup added 0 definitions that nobody has confirmed.

The cleanup added no definitions. It added the one-sentence summary above each finding, and the accuracy reviewer checked each summary against its finding. Four summaries and one list of issue links were corrected as it asked.

For the reviewer: the structured review output

1. Verdict

Ready to merge: no. The code needs an openedx-core release that does not exist yet (CI is red on the current pin and the PR is not a draft), three acceptance criteria have a carrier or end state that no test guards, and one committed docstring states something false about delete_object_tags.

2. Inputs

3. Acceptance criteria

Criterion (quoted) In scope Implemented at Tested by Verdict
1 "A taxonomy's tag count leaves out archived tags" (no filter, org, unassigned) yes rest_api/v1/views.py:107 test_views.py::test_list_taxonomy_tags_count_excludes_archived (3 ddt cases); fails 3 == 2 when the filter is dropped Covered
2 "A taxonomy whose tags are all archived shows a count of zero" yes rest_api/v1/views.py:107 test_list_taxonomy_tags_count_all_archived_is_zero; fails under the same break Covered
3 "An archived association is left out of a tag export" yes content_tagging/api.py:183 test_get_course_object_tags_excludes_archived_association, test_generate_csv_rows_excludes_archived; fail when .unarchived() is removed Covered
4 "Tags under an archived tag or taxonomy are left out of a tag export ... not mentioned anywhere in the export" yes api.py:183; header from returned rows api.py:246-253 test_get_course_object_tags_excludes_archived_tag, ..._excludes_archived_taxonomy, test_generate_csv_rows_excludes_archived (header) Covered
5 "A course whose only tags are archived exports with no tags" yes api.py:183 test_generate_csv_rows_all_archived Covered
6 "Exporting and importing a course does not bring back archived associations" yes api.py:183 (export), api.py:308-327 (import) Export half and import half separately; no test chains them. A probe that exported and imported into a second course key gave the right result Covered in two halves (Suggestion S1)
7 "Duplicating a block does not copy archived associations ... original ... unchanged" yes api.py:183 via copy_object_tags test_block.py::TestDuplicateItem::test_duplicate_tags; fails when .unarchived() is removed Covered
8 "Copying and pasting a block does not carry archived associations" yes api.py:183 via block_serializer.py:105 test_clipboard_paste.py::test_copy_and_paste_unit_with_tags; fails under the same break Covered
9 "Copying a library container ... When the unit is copied and pasted into a course Then neither the pasted unit nor its child carries an archived tag" yes Course-side paste of library content runs copy_tags_as_read_only (cms/djangoapps/contentstore/helpers.py:635-640), which is openedx-core's copy_tags; this PR's change is not on that path content_libraries/tests/test_api.py::test_copy_container_leaves_archived_tags_out checks the serializer and clipboard for a section and a subsection Implemented (by openedx-core), untested here (Gap G3)
10 "Unarchived tags still travel exactly as they do today ... exported, duplicated, or copied and pasted ... including the disabled taxonomy's" yes api.py:183 (no enabled filter) Export: test_generate_csv_rows_keeps_disabled_taxonomy. Duplicate and copy and paste: none; filtering out disabled taxonomies failed only the export test Implemented, 2 of 3 carriers untested (Gap G4)
11 "Importing a course leaves out tags from a taxonomy archived on this instance" yes api.py:314-320 test_import_skips_archived_taxonomy; fails when the skip is removed and when the lookup is reverted Covered at the import function
12 "Left-out tags cannot reappear after a later taxonomy import" yes api.py:317-320 (no placeholder is created) test_import_skips_archived_taxonomy asserts no row with the export id and none under the taxonomy; the later taxonomy import step is not run Covered by the state assertion (Suggestion S2)
13 "Tags from a taxonomy that does not exist on this instance are still kept for later" yes api.py:321-327 Pre-existing test_import_tags_invalid_taxonomy (placeholder created); later resolution not tested in this repository, confirmed by probe Covered for the placeholder; resolution is openedx-core's resync
14 "Re-importing a course into itself keeps the associations an author cannot see" (3 rows) yes api.py:308 and openedx-core tag_object test_import_keeps_hidden_association (5 ddt cases); all fail when the clear loses .unarchived() or reverts to delete_object_tags Covered. See Question Q1 for a file that names the hidden value
15 "Saving a block's tags in Studio keeps the associations an author cannot see" no: delivered by openedx-core PR 5 in tag_object; the Studio view calls it at rest_api/v1/views.py:259 not in this PR none in this PR; this review did not review PR 5's tests Not judged here

Behavior the change adds that no criterion asked for: an INFO log line per block and archived taxonomy with every skipped value (api.py:319); a deletion-locked visible association is archived, not deleted, by the import's clear (follows from ticket 779).

4. Premise audit

20 claims checked. FALSE rows:

Claim Where Checked against Consequence
"delete_object_tags clears everything" docstring, content_tagging/api.py:303 openedx-core api.py:317-325: locked associations are archived, not deleted Gap G2
"use delete_object_tags for a full clear" PR body same Nit N3
"pins openedx-core==1.20.0" PR body requirements/edx/base.txt:841 is 1.21.0; v1.21.0 and v1.21.1 also lack the names Part of Gap G1
"653 tests on master" PR body --collect-only: 653 at head, 634 at the merge-base Nit N4
"a library paste never applies staged tags, so no library fixture can exercise that last step" PR body, Known gaps helpers.py:635-640: a library-to-course paste copies tags from the upstream; ClipboardPasteFromV2LibraryTestCase (test_clipboard_paste.py:413) is such a fixture Part of Gap G3
"13 new test functions (19 cases)" PR body True for the two files it names; the diff has a 14th function in content_libraries/tests/test_api.py Nit N5

No UNVERIFIABLE rows. Claim 7 (value order changed with the joins) is true as observed on SQLite; the mechanism is inferred and MySQL was not run.

5. Invariants

The diff adds no model, constraint, index, or lock of its own. The table had 8 rows for the rules the import relies on. Gap rows:

  • "Import never hard-deletes a deletion-locked association" is enforced by the ObjectTagQuerySet.delete override in openedx-core's model layer, which the platform now calls directly (Gap G5). On MariaDB the override cannot restrict its row lock to ObjectTag, and the clear now joins Tag and Taxonomy (Question Q3).
  • "No placeholder for an archived taxonomy" is enforced only at api.py:317-320; openedx-core's resync() still links an existing placeholder to an archived taxonomy (Question Q2).
  • "One block's import is atomic" has no enforcement; this predates the change (Dropped).

Backend skill items 10 and 12: no model field is added and no value is built into a bounded column. Item 11: the change adds no lock of its own; it reaches the library's select_for_update() through delete(), as the old delete_object_tags call did.

6. Findings

Bug

None.

Gap

  • G1. The PR cannot pass CI or merge until an openedx-core release contains the archive stack, and it is not marked as blocked. requirements/edx/base.txt:841, content_tagging/api.py:183, api.py:308, rest_api/v1/views.py:107. CONFIRMED: 12 CI checks fail (Static analysis: "Manager[ObjectTag]" has no attribute "unarchived"; cms and lms unit shards: AttributeError); isDraft is false. With v1.21.0 swapped in, the package ran 383 failed, 270 passed, while the merge-base passed 634 of 634. On the pin, tag export, duplicate, clipboard copy, the Taxonomies list (FieldError on tag__archived), and course import all raise. The PR body names the wrong pin (1.20.0) and says nothing changes for authors until archived rows exist. Suggested: convert to draft; state the pin as 1.21.0; say the pin bump must merge first or include it here.
  • G2. The docstring of set_exported_object_tags says "delete_object_tags clears everything", which is false. content_tagging/api.py:303. CONFIRMED against openedx-core api.py:317-325. Suggested: "delete_object_tags also removes the associations that archiving hides; it still archives a deletion-locked one."
  • G3. Criterion 9 has no test of its end state, and the test that stands in for it exercises a path a course paste does not use. content_libraries/tests/test_api.py:786; cms/djangoapps/contentstore/helpers.py:635-640. CONFIRMED by reading: the test tags a section and a subsection (the criterion says a unit and a child component) and never pastes; pasting library content into a course sets upstream (helpers.py:457-510, :609) and copies tags with copy_tags_as_read_only, ignoring the clipboard's staged tags. INFERRED: that upstream is set for both the unit and its child. Raised by three reviewers including the GPT agent. Suggested: add a paste-into-course test in ClipboardPasteFromV2LibraryTestCase with an archived association on a unit and on its child component, and correct the Known gaps sentence.
  • G4. Criterion 10 is untested for duplicate and for copy and paste. test_block.py:1542, test_clipboard_paste.py:284. CONFIRMED by break: adding .filter(taxonomy__enabled=True) to get_all_object_tags failed only test_generate_csv_rows_keeps_disabled_taxonomy. Suggested: disable one taxonomy in the setup of the two edited tests and keep their assertions.
  • G5. The import's clear mutates ObjectTag rows through the model layer and depends on a queryset override that the library does not publish as API. content_tagging/api.py:307-308. Rule: openedx-core AGENTS.md:141 ("All mutation goes through here", of api.py) and :150 ("Treat them as read-only from outside the app"). CONFIRMED: this is the only non-test place in the platform that deletes ObjectTag rows itself; the override's docstring (models/base.py:88-100) describes it as a safety net ("Raw SQL bypasses this"); PR 5 adds no API for "delete the visible associations of an object". Suggested: add keep_hidden: bool = False to openedx-core's delete_object_tags in PR 5 and call oel_tagging.delete_object_tags(content_key_str, keep_hidden=True). The two direct reads (ObjectTag.objects.unarchived(), Taxonomy.objects.filter(export_id=...)) are the opt-out the library documents and are fine.

Question

  • Q1. A re-import whose file names a hidden association's own value brings that association back. api.py:308, :321. CONFIRMED by probe: archive the course's Tag 1.1 association, import a file listing Tag 1.1; the same row is un-archived and visible. Criterion 14 says it "stays hidden"; ticket 779 says re-applying un-archives. A file exported from this instance never names the value, so this needs an older or hand-edited file. No test pins either outcome. Record which is intended and add a test.
  • Q2. Tags skipped because their taxonomy is archived are discarded for good, and resync() still links other placeholders to an archived taxonomy. api.py:317-320; openedx-core models/base.py:1085-1089. CONFIRMED by reading. If the taxonomy is un-archived later the skipped tags do not return, unlike a missing taxonomy's (criterion 13). A placeholder that exists before a taxonomy with its export id is archived is still linked by the next tag-file import. Record the first as a decision; the second belongs in openedx-core.
  • Q3. On MariaDB the clear may lock shared Tag and Taxonomy rows. api.py:308; openedx-core models/base.py:113-121. CONFIRMED: unarchived() adds three outer joins and the override uses of=("self",) only where the database supports it. INFERRED: MariaDB then locks the joined rows, so concurrent imports contend. Not run on MariaDB or MySQL.
  • Q4. A block copied before one of its associations is archived still pastes that tag as a visible association. cms/djangoapps/contentstore/helpers.py:641-648. Raised only by the GPT agent, which CONFIRMED it by moving the archive step after the copy in test_copy_and_paste_unit_with_tags (2 tags pasted). Criterion 8 archives before copying, so this is outside its letter. Decide whether a stale clipboard matters.

Suggestion

  • S1. Chain export and import in one test for criteria 6 and 11 (export to CSV, import into a second course key). tests/test_api.py:547, :748.
  • S2. In test_import_skips_archived_taxonomy, run a taxonomy tag-file import and resync_object_tags after the course import, so criterion 12's "later" step is executed. With the skip removed, a probe showed the placeholder re-linked to the archived taxonomy's tag.
  • S3. get_all_object_tags orders by object_id only (api.py:186), so value order inside a taxonomy, and inside a tags.csv cell, is whatever the database returns, and it changed with the new joins on SQLite. Add a tiebreaker (order_by("object_id", "id")). A run showed the tiebreaker makes the order stable but does not restore the old expected order, so the test edit stays either way.
  • S4. Log the skip once per taxonomy per import with a count, not once per block with every value (api.py:319). A probe produced one 115,028-character record for 2,000 values. Values are author-supplied, not PII.
  • S5. test_import_skips_archived_taxonomy asserts send_event.call_count == 1, assertLogs, and logs.output[0], and keeps its state assertions inside the with block. Removing the log.info line fails it with "no logs of level INFO". Keep the state assertions; drop or separate the rest.
  • S6. _hide_association ignores hidden_pk in two of three branches (tests/test_api.py:675-684); pass the update from the ddt data or filter by the row.
  • S7. test_generate_csv_rows_all_archived derives its expected rows from the fixture and its prefix the way the code does; assert expected_rows[0] == ["Name", "Type", "ID"] cannot fail for a product reason. Write the expected rows as a literal.
  • S8. Fold test_list_taxonomy_tags_count_all_archived_is_zero into the ddt test as a fourth row, and the archived-association and archived-tag course tests of get_all_object_tags into one ddt test.

Nit

  • N1. api.py:303: "as before" narrates history (workspace CLAUDE.md, "Write the current state, not its history").
  • N2. api.py:298, :300-301: "keeping what archiving hides" and naming ObjectTag.objects.unarchived() describe the implementation; the tests and ticket say "hidden associations". Use one term.
  • N3. PR body: "use delete_object_tags for a full clear" repeats G2.
  • N4. PR body: "653 tests on master" is the head count; the merge-base has 634.
  • N5. PR body: the test count and the testing commands omit test_copy_container_leaves_archived_tags_out.
  • N6. PR title "Tbain/903 hide archived tagging records" is not a conventional commit; the commitlint check passes because it lints commits.
  • N7. New tests mix docstrings that restate the test name with comment-only tests.

7. Tests checked

Test How the code was broken Failed? For the stated reason?
test_list_taxonomy_tags_count_* (4) Q(tag__archived=False) dropped yes yes
6 get_all_object_tags and csv tests; test_duplicate_tags; test_copy_and_paste_unit_with_tags; test_copy_container_leaves_archived_tags_out .unarchived() removed from get_all_object_tags yes yes
test_import_keeps_hidden_association x5 clear replaced by delete_object_tags; clear without .unarchived() yes, all 5 yes
test_import_skips_archived_taxonomy skip removed; lookup reverted yes yes
test_import_archives_locked_visible_association clear via plain models.QuerySet.delete yes yes
test_import_tags_invalid_taxonomy skip widened to missing taxonomies yes yes
test_generate_csv_rows_keeps_disabled_taxonomy disabled taxonomies filtered out yes, and only it yes
test_import_skips_archived_taxonomy log.info removed yes no (fails on the log)
test_import_keeps_hidden_association cases 4, 5 library tag_object removes hidden rows yes yes
any library tag_object stops un-archiving no not covered here

Edited tests: the old forms of test_duplicate_tags and test_copy_and_paste_unit_with_tags pass on the new code (the edits add coverage). The old form of test_get_course_object_tags_with_add_tags fails on the new code and passes at the merge-base, so its edit is needed; its fixture is per test.

8. Checks run and skipped

  • Merge of the head into origin/master: clean, no conflicts.
  • content_tagging/ at the head with the library branch: 653 passed. Two cms tests: passed. content_libraries/tests/test_api.py -k archived: 1 passed.
  • Same package against released v1.21.0: 383 failed, 270 passed.
  • ruff check on the 7 files: clean. pylint: no messages (exit code not captured). mypy on content_tagging/: clean with the library branch.
  • CI on the PR: 12 failing, 26 passing, 3 skipped.
  • Skipped: MySQL and MariaDB (the container has SQLite only); the migration graph (no migrations); triage of the 383 failures; a system-defined taxonomy import probe.
  • B6 (GPT-6.1-Sol through Codex) ran.

9. Declined to judge

  • Search index documents keep an archived taxonomy's tags until the content is re-indexed (raised by the GPT agent). The tickets that set archived (779, 780) must emit the change event or re-index; nothing in this change prevents that.
  • Criterion 15 belongs to tbain/openedx-core PR 5; this PR needs only the pin bump.
  • Making resync() ignore archived taxonomies belongs to openedx-core (ticket 778's "edits are refused"); this change's skip does not block it.

10. Dropped

  • "delete() on a joined queryset fails on MySQL with a self-referencing subquery": the override deletes by pk__in lists, so no subquery reaches the SQL.
  • "The in-place sort in the edited test leaks across tests": the fixture is assigned in setUp.
  • "all(not tags ...) in the library test is vacuous": the collection has two untagged entries.
  • "Hidden rows count toward the 100-tag limit": _check_new_tag_count uses unarchived().
  • "order_by('object_id', 'id') restores the old order": a run showed it does not.
  • "One block's clear and re-tag are not atomic": true, and it predates the change.
  • "The annotation leaks counts across orgs": the join is on taxonomy_id only.

11. Open questions

  1. Q1: should a re-import that names a hidden association's value un-archive it?
  2. Q2: are tags skipped under an archived taxonomy meant to be lost if the taxonomy is un-archived?
  3. Q4: does a stale clipboard matter?
  4. G5: is a keep_hidden flag on delete_object_tags acceptable in PR 5?

re-review after being addressed:

Details # Re-review of openedx-platform PR 39360 after the review fixes

Verdict: 20 of the 23 findings are fixed, 3 are partly addressed, nothing broke, and criteria 1 to 14 are met and tested against the openedx-core branch.

This re-review covers #39360 at head commit 0b53bc5084. It checks the 23 findings of the earlier review and the 15 acceptance criteria of openedx/openedx-core#903. The pull request has no reviews or inline comments on GitHub, so only the earlier review applies.

The branch was rebased onto the current master (d3a86602dc), and git range-diff shows the first seven commits are unchanged. One new commit, 0b53bc5084 ("feat: addressing found issues during dev code review"), holds every fix. The companion pull request tbain/openedx-core#5 gained one commit, 465bb1c, which adds a keep_hidden keyword argument to openedx-core's delete_object_tags. The pull request title and description were rewritten.

Was every review finding fixed?

I verified the fixes that concern tests by breaking the production code and watching the tests fail.

Partly addressed

  • The pull request is still not a draft. The description now says "Blocked on an openedx-core release, and draft until then", states the pin correctly as 1.21.0, and says the pin bump must merge first or be added. GitHub still reports the pull request as ready for review (isDraft is false), and 12 CI checks still fail because the pinned openedx-core 1.21.0 has none of the archive code. Convert the pull request to a draft so the description and its state agree.
  • The re-import that names a hidden association's own value is recorded, with no test. The earlier review asked for a decision and a test. The description's "Known gaps" now describes the behavior (the association becomes visible again, because openedx-core's tag_object un-archives a re-applied tag) and says "No test in this PR pins it". It does not say whether this is the intended outcome. Criterion 14 says a hidden association "stays hidden", so the ticket's author should confirm that an older or hand-edited file is allowed to bring one back.
  • The resync() behavior in openedx-core has no follow-up that I could find. The description now records that tags skipped under an archived taxonomy are not restored if the taxonomy is un-archived. The second half of that finding was that openedx-core's ObjectTag.resync() still links an existing placeholder to an archived taxonomy. A search of openedx-core issues found no ticket for it, and openedx-core pull request 5 does not change resync(). This is an openedx-core item, not a blocker here, but it needs a ticket or a note on [BE] Exclude archived tagging records from read paths and block edits to them openedx-core#778.

Fixed

Earlier finding What changed How I checked
The docstring said delete_object_tags clears everything The docstring of set_exported_object_tags now says delete_object_tags also removes the hidden associations and also archives a deletion-locked one Read against openedx-core's delete_object_tags
The library container criterion had no test of what lands in the course New test_paste_from_library_unit_leaves_archived_tags_out pastes a library unit with a child component into a course and reads the stored rows unfiltered Removing the archive filter from openedx-core's get_object_tags makes it fail
Disabled taxonomies were not guarded on duplicate or copy and paste test_duplicate_tags and test_copy_and_paste_unit_with_tags now disable a taxonomy and assert its tags arrive Filtering disabled taxonomies out of get_all_object_tags now fails both tests and the export test
The import deleted associations through openedx-core's models The import calls oel_tagging.delete_object_tags(content_key_str, keep_hidden=True) Changing it to keep_hidden=False fails all five test_import_keeps_hidden_association cases
On MariaDB the delete might lock shared tag and taxonomy rows openedx-core's delete_object_tags selects the visible primary keys first and deletes by primary key, so the locking query has no join Read the code; MariaDB was not run
A block copied before an association is archived still pastes the tag Recorded under "Known gaps" as accepted, with the reason Read the description
Export and import were tested separately New test_export_then_import_into_new_course_carries_only_visible_tags Fails when .unarchived() is removed from get_all_object_tags
The later taxonomy import of criterion 12 was never run New test_taxonomy_tag_import_does_not_restore_skipped_archived_taxonomy_tags runs a taxonomy tag-file import and a resync Fails when the import's taxonomy lookup is reverted
get_all_object_tags had no stable order The query orders by object_id, then id Read the code. No test fails when the second key is removed, which is expected for an ordering tiebreaker on SQLite
The skip was logged per block with every value set_exported_object_tags returns the skipped counts and import_course_tags_from_csv logs once per archived taxonomy with a count test_import_logs_skipped_archived_taxonomy_once
One test mixed state, event, and log assertions Split into three tests Read the tests
The test helper ignored the row it was given _hide_association now finds the tag or taxonomy through the row's primary key Read the code
The all-archived export test computed its expectation like the code The expected CSV is a literal Read the test
Tests that differed only in input The tag count tests and the archived association and archived tag tests are each one ddt test Read the tests
Six small items: "as before", mixed wording, the test count for master, the missing fourteenth test, the title, mixed docstring style All corrected. The title is now "feat: hide archived tagging records in Studio's content_tagging app" Read the diff and the description; I counted the 17 new test functions the description states

Are the new changes architecturally sound?

Yes. Three changes carry design weight, and each is acceptable.

  • The delete moved into the library. The platform no longer mutates ObjectTag rows itself. keep_hidden is a keyword-only argument that defaults to False, so existing callers of delete_object_tags keep today's behavior and the change needs no deprecation. The function reads the visible primary keys and then deletes them in a second statement. A row that another request hides between the two statements would still be deleted or archived by the second one. I consider that acceptable for a course import, and it is no worse than the earlier single statement.
  • set_exported_object_tags now returns a dict where it returned None. The function is in the public content_tagging API, and its only caller in the repository is import_course_tags_from_csv. A caller that ignores the return value is unaffected, and the description lists the change under "Developers".
  • The stable ordering key. Adding id to the ordering adds no join and makes the order within a tags.csv cell the order in which the associations were created.

The two direct model reads that remain (ObjectTag.objects.unarchived() in get_all_object_tags and the unfiltered taxonomy lookup in the import) use the access openedx-core documents as supported.

One thing I noticed in the new tests is outside this ticket: openedx-core's get_object_tags leaves out disabled taxonomies, so a library unit pasted into a course does not carry a disabled taxonomy's tags, while a course block copied and pasted does. That difference predates this pull request.

Did the changes break anything that worked before?

No. With the openedx-core branch mounted (pull request 5's head merged with openedx-core main), the test command from the description passes all 670 tests, which matches the number the description states. ruff and mypy are clean on the content_tagging package and the two edited cms test files.

I re-ran five one-line breaks of the production code, and each made the matching tests fail:

  • removing the archive filter from get_all_object_tags
  • filtering out disabled taxonomies
  • dropping keep_hidden
  • reverting the taxonomy lookup
  • removing the archive filter from openedx-core's get_object_tags

I ran everything in a throwaway copy of the head commit inside the CMS container and removed it afterward. The tests ran on SQLite only.

CI on the pull request is still red for the same reason as before: the pinned openedx-core release lacks the archive code. That is the declared blocker, not a regression.

Are all acceptance criteria met?

Against the openedx-core branch, criteria 1 to 14 are implemented and each now has a test that fails when the behavior breaks. That includes the three that were only partly guarded before: criterion 9 (library unit pasted into a course), criterion 10 (disabled taxonomy on duplicate and on copy and paste), and criterion 12 (the later taxonomy import). Criterion 15 (saving a block's tags in Studio keeps hidden associations) is delivered by openedx-core pull request 5, and this pull request adds no test for it. I did not review that pull request's tests.

None of the criteria is met on the openedx-core release that openedx-platform pins today, so the pull request cannot merge until a release contains the openedx-core stack and the pin is bumped.

What is left to do

  1. Convert the pull request to a draft until the openedx-core release exists.
  2. Have the ticket's author confirm that a re-import may make a hidden association visible when the file names its value, and add a test once that is decided.
  3. Open an openedx-core ticket, or add a note to issue 778, for resync() linking placeholders to an archived taxonomy.

@tbain
tbain requested a review from jesperhodge November 11, 2026 21:23
@tbain

tbain commented Nov 11, 2026 •

Copy link
Copy Markdown
Author

The various static checks for this PR are currently breaking because they are looking at the now current master for openedx-core rather than a version that includes the changes from tbain/openedx-core#5 and its associated stack. The static checks for this PR won't pass until that stack is merged in and this PR can be changed to reference that new version number.

This branch has not been deployed

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

Labels

open-source-contribution PR author is not from Axim or 2U

Projects

Status: Needs Triage

Development

Successfully merging this pull request may close these issues.

2 participants