Skip to content

fix: keep course dates that a partial course_details PUT leaves out - #39328

Open
newairforces wants to merge 2 commits into
openedx:masterfrom
newairforces:fix/39094-partial-put-keeps-dates
Open

newairforces wants to merge 2 commits into
openedx:masterfrom
newairforces:fix/39094-partial-put-keeps-dates

Conversation

@newairforces

Copy link
Copy Markdown

Description

PUT /api/contentstore/v1/course_details/<course_id> is documented as taking partial bodies, but CourseDetails.update_from_json wrote None into every date field whose key was missing from the body: start, end, enrollment_start, enrollment_end, and certificate_available_date. So {"self_paced": true} on a course that hasn't started:

  • cleared the course's start date (and its end and enrollment dates),
  • which made can_toggle_course_pacing return False,
  • so the pacing change was skipped, and the view still answered 200 with self_paced: false.

This PR treats a missing date key as "leave it unchanged": each of the five else: converted = None branches now keeps the block's current value. An explicit null or "" still clears a date, so the authoring MFE, which always sends the whole form, behaves the same as before.

User roles affected: Developer (anyone calling the course details API with a partial body). Course Author only indirectly: a partial PUT no longer erases their course dates.

What this PR does not change:

  • A pacing change on a course that has already started is still refused with 200 and the old value, as before. That refusal is intentional in can_toggle_course_pacing. Whether it should answer 400 instead is the open question I asked on the issue, so I left it out of this PR.
  • In that same already-started case, update_course_details in cms/djangoapps/contentstore/utils.py still resets the certificate display behavior even though pacing doesn't change. That goes with the 400 question.

Supporting information

Fixes #39094

Claim and question on the issue: #39094 (comment)

I've signed the individual CLA, which @bmtcril asked for on the issue. This is my first Open edX PR, so CI will need a maintainer to start it.

Testing instructions

  1. Check out this branch, start a MongoDB for tests (docker run -d -p 27017:27017 mongo:7), and install the testing group (make test-requirements).
  2. Run the new view test, which sends the request from the issue:
    pytest -c cms/pytest.ini -p no:randomly cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_details.py -k partial_body
    
  3. Run the new model test:
    pytest -p no:randomly openedx/core/djangoapps/models/tests/test_course_details.py -k partial_update
    
  4. To see them fail, check out openedx/core/djangoapps/models/course_details.py from master and run steps 2 and 3 again.

Both new tests on the unfixed course_details.py:

E       assert False is True
FAILED cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_details.py::CourseDetailsViewTest::test_put_partial_body_self_paced
1 failed, 40 deselected, 16 warnings in 2.59s
E               AssertionError: start_date
E               assert None == datetime.datetime(2040, 1, 1, 0, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
FAILED openedx/core/djangoapps/models/tests/test_course_details.py::CourseDetailsTestCase::test_partial_update_keeps_dates
1 failed, 26 deselected, 11 warnings in 3.95s

My local repro (a throwaway test through Django's test client against the real view, not part of this PR), on a course starting 2040-01-01 with end and enrollment dates set, sending PUT {"self_paced": true}.

Before (master):

  -> HTTP 200, response self_paced=False
  -> stored block.start=None block.self_paced=False
  -> stored end=None enrollment_start=None enrollment_end=None

After (this branch):

  -> HTTP 200, response self_paced=True
  -> stored block.start=2040-01-01 00:00:00+00:00 block.self_paced=True
  -> stored end=2041-01-01 00:00:00+00:00 enrollment_start=2039-06-01 00:00:00+00:00 enrollment_end=2039-12-01 00:00:00+00:00

Checks I ran on this branch (macOS, Python 3.12, MongoDB 7, SQLite):

$ pytest -c cms/pytest.ini -p no:randomly -q cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_details.py
41 passed, 120 warnings in 12.10s
$ pytest -p no:randomly -q openedx/core/djangoapps/models/tests/test_course_details.py
27 passed, 37 warnings in 6.78s
$ pytest -c cms/pytest.ini -p no:randomly -q cms/djangoapps/contentstore/tests/test_course_settings.py
81 passed, 156 warnings in 12.15s
$ pylint openedx/core/djangoapps/models/course_details.py openedx/core/djangoapps/models/tests/test_course_details.py cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_details.py
(no messages, exit 0)

Not run locally: the full CI test shards and module-wide pylint. They need services and the dev requirements that I don't have set up, so I'm relying on CI for those.

Deadline

None.

Other information

  • No migrations, no settings changes, no dependencies on other PRs.
  • One risk: if some API client relies on leaving out a date key to clear that date, it would now need to send null. Every caller in this repo passes the request body straight through, so the change only matters to clients that send partial bodies (the issue notes that the MFE sends the whole form):
    $ git grep -n "CourseDetails.update_from_json\|update_course_details(" -- '*.py' ':!*/tests/*' ':!*test_*'
    cms/djangoapps/contentstore/rest_api/v1/views/course_details.py:255:            updated_data = update_course_details(request, course_key, request.data, course_block)
    cms/djangoapps/contentstore/rest_api/v3/views/course_details.py:265:            updated_data = update_course_details(request, course_key, request.data, course_block)
    cms/djangoapps/contentstore/utils.py:1291:def update_course_details(request, course_key, payload, course_block):
    cms/djangoapps/contentstore/utils.py:1369:    return CourseDetails.update_from_json(course_key, payload, request.user)
    cms/djangoapps/contentstore/views/course.py:1491:                    update_data = update_course_details(request, course_key, request.json, course_block)
    

AI disclosure, per the Open edX AI policy: I used Claude (through Claude Code) to help trace the request from the view to update_from_json, write the two tests and the local repro test, and draft this description. I picked the fix, ran every command above on my own machine, and checked the output against the code. The commits carry a Co-authored-by: Claude trailer.

newairforces and others added 2 commits October 31, 2026 16:52
CourseDetails.update_from_json wrote None into start, end, enrollment
start/end and certificate_available_date whenever their key was missing
from the body. A partial PUT such as {"self_paced": true} therefore
cleared the course's start date, which made can_toggle_course_pacing
return False, so the pacing change was dropped while the view answered
200. A missing date key now leaves the stored value unchanged; an
explicit null or "" still clears it.

Fixes openedx#39094

Co-authored-by: Claude <claude@anthropic.com>
Adds a model test that a body without date keys keeps all four schedule
dates (while an explicit None still clears one), and a view test with
the request from openedx#39094: PUT {"self_paced": true} on a course that has
not started yet switches it to self-paced and keeps its start date.
Both fail without the previous commit.

Co-authored-by: Claude <claude@anthropic.com>
@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Oct 31, 2026
@openedx-webhooks

Copy link
Copy Markdown

Thanks for the pull request, @newairforces!

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
🔘 Submit a signed contributor agreement (CLA)

⚠️ We ask all contributors to the Open edX project to submit a signed contributor agreement or indicate their institutional affiliation.
Please see the CONTRIBUTING file for more information.

If you've signed an agreement in the past, you may need to re-sign.
See The New Home of the Open edX Codebase for details.

Once you've signed the CLA, please allow 1 business day for it to be processed.
After this time, you can re-run the CLA check by adding a comment below that you have signed it.
If the CLA check continues to fail, you can tag the @openedx/cla-problems team in a comment for further assistance.

🔘 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.

Details
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.

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.

PUT course_details updates inconsistently (possibly requires start_date?)

2 participants