Skip to content

Instructor Dashboard - API Updates#38458

Open
brianjbuck-wgu wants to merge 9 commits intoopenedx:masterfrom
WGU-Open-edX:feature/special-exams-api-updates
Open

Instructor Dashboard - API Updates#38458
brianjbuck-wgu wants to merge 9 commits intoopenedx:masterfrom
WGU-Open-edX:feature/special-exams-api-updates

Conversation

@brianjbuck-wgu
Copy link
Copy Markdown
Contributor

@brianjbuck-wgu brianjbuck-wgu commented Apr 27, 2026

Description

Makes several tweaks to the Instructor API v2 endpoints:

  • Username in course metadata: GET /api/instructor/v2/courses/{course_id} now returns the username of the authenticated user, enabling the MFE to display the current user without a separate request.
  • Exam type field: Special exams list and detail endpoints now include an exam_type field derived from proctoring flags.
  • Ordering support: The allowances and attempts list endpoints (/special_exams/allowances, /special_exams/attempts) support ?ordering= since edx_proctoring APIs return lists instead of querysets.

Impacted user roles: Course Staff, Instructors.

Deadline

None

Other information

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Apr 27, 2026
@openedx-webhooks
Copy link
Copy Markdown

openedx-webhooks commented Apr 27, 2026

Thanks for the pull request, @brianjbuck-wgu!

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

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.

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.

@github-project-automation github-project-automation Bot moved this to Needs Triage in Contributions Apr 27, 2026
@brianjbuck-wgu brianjbuck-wgu requested a review from Copilot April 27, 2026 16:54
@brianjbuck-wgu brianjbuck-wgu changed the title Feature/special exams api updates Instructor Dashboard - API Updates Apr 27, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the Instructor API v2 “special exams” endpoints to expose additional exam attempt metadata and support client-driven ordering, while also making the due-date-change “reason” field optional/blank-safe.

Changes:

  • Allow reason to be omitted or blank in the due date change payload.
  • Add exam_type to exam attempt responses (derived from proctored exam flags).
  • Add ordering support (via in-memory sort) to course allowances and course exam attempts list endpoints, with accompanying tests.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
lms/djangoapps/instructor/views/serializers_v2.py Makes due-date reason accept blank/omitted and adds derived exam_type to exam attempts.
lms/djangoapps/instructor/views/api_v2.py Introduces _sort_in_memory and wiring for ordering on allowances/attempts endpoints.
lms/djangoapps/instructor/tests/views/test_special_exams_api_v2.py Adds coverage for exam_type and new ordering query param behavior.
lms/djangoapps/instructor/tests/test_api.py Adds regression test ensuring due-date changes work when reason is omitted/blank.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lms/djangoapps/instructor/views/api_v2.py Outdated
Comment thread lms/djangoapps/instructor/views/serializers_v2.py
Comment thread lms/djangoapps/instructor/tests/views/test_special_exams_api_v2.py Outdated
Comment thread lms/djangoapps/instructor/tests/views/test_special_exams_api_v2.py Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates Instructor Dashboard v2 APIs to support optional “reason” on due date extensions, expose an exam_type field on exam attempts, and add query-param sorting for special-exams allowances/attempts endpoints.

Changes:

  • Make reason optional (including blank) for the due date extension v2 serializer and add coverage for omitted/blank values.
  • Add exam_type to ExamAttemptSerializer, derived from proctored/practice flags.
  • Add ordering support for course allowances and course exam attempts endpoints, backed by a shared in-memory sort helper, with associated tests.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
lms/djangoapps/instructor/views/serializers_v2.py Makes due-date reason optional/blankable and adds derived exam_type to exam attempt serialization.
lms/djangoapps/instructor/views/api_v2.py Introduces _sort_in_memory and wires ordering into allowances + attempts list APIs.
lms/djangoapps/instructor/tests/views/test_special_exams_api_v2.py Adds/extends tests for exam_type and ordering support on special-exams APIs.
lms/djangoapps/instructor/tests/test_api.py Adds regression test ensuring due-date change works with omitted/blank reason.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lms/djangoapps/instructor/views/api_v2.py
Comment thread lms/djangoapps/instructor/tests/views/test_special_exams_api_v2.py
Comment thread lms/djangoapps/instructor/views/api_v2.py Outdated
Comment thread lms/djangoapps/instructor/views/api_v2.py
@brianjbuck-wgu brianjbuck-wgu force-pushed the feature/special-exams-api-updates branch 3 times, most recently from c2c97fe to d960f85 Compare April 28, 2026 16:41
@mphilbrick211 mphilbrick211 added the mao-onboarding Reviewing this will help onboard devs from an Axim mission-aligned organization (MAO). label Apr 29, 2026
@mphilbrick211 mphilbrick211 moved this from Needs Triage to Waiting on Author in Contributions Apr 29, 2026
@brianjbuck-wgu brianjbuck-wgu force-pushed the feature/special-exams-api-updates branch from d960f85 to d07978b Compare April 29, 2026 15:47
@brianjbuck-wgu brianjbuck-wgu force-pushed the feature/special-exams-api-updates branch from d07978b to b8a53d2 Compare April 29, 2026 18:50
@brianjbuck-wgu brianjbuck-wgu marked this pull request as ready for review April 29, 2026 19:12
Copy link
Copy Markdown
Contributor

@wgu-taylor-payne wgu-taylor-payne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice set of changes — clean structure overall. A few suggestions below.

Review assisted by Kiro

Comment on lines +4508 to +4520
ORDERING_FIELDS = {
'username': 'user.username',
'user.username': 'user.username',
'email': 'user.email',
'user.email': 'user.email',
'exam_name': 'proctored_exam.exam_name',
'proctored_exam.exam_name': 'proctored_exam.exam_name',
'time_limit': 'proctored_exam.time_limit_mins',
'proctored_exam.time_limit_mins': 'proctored_exam.time_limit_mins',
'started_at': 'started_at',
'completed_at': 'completed_at',
'status': 'status',
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The serializer outputs start_time and end_time (via source='started_at'/source='completed_at'), but the ordering params only accept started_at/completed_at. An API consumer sees start_time in the response JSON but has to know the internal field name to sort by it — and since unrecognized ordering values silently no-op, ?ordering=start_time would just return unsorted results with no error.

Consider accepting the serializer field names as aliases:

Suggested change
ORDERING_FIELDS = {
'username': 'user.username',
'user.username': 'user.username',
'email': 'user.email',
'user.email': 'user.email',
'exam_name': 'proctored_exam.exam_name',
'proctored_exam.exam_name': 'proctored_exam.exam_name',
'time_limit': 'proctored_exam.time_limit_mins',
'proctored_exam.time_limit_mins': 'proctored_exam.time_limit_mins',
'started_at': 'started_at',
'completed_at': 'completed_at',
'status': 'status',
}
ORDERING_FIELDS = {
'username': 'user.username',
'user.username': 'user.username',
'email': 'user.email',
'user.email': 'user.email',
'exam_name': 'proctored_exam.exam_name',
'proctored_exam.exam_name': 'proctored_exam.exam_name',
'time_limit': 'proctored_exam.time_limit_mins',
'proctored_exam.time_limit_mins': 'proctored_exam.time_limit_mins',
'start_time': 'started_at',
'started_at': 'started_at',
'end_time': 'completed_at',
'completed_at': 'completed_at',
'status': 'status',
}

Same idea for the docstring example (ordering=-started_atordering=-start_time).

Comment on lines +4522 to +4529
def _get_exam_type(self, attempt):
"""Derive exam type string for sorting purposes."""
exam = attempt.get('proctored_exam', {})
if exam.get('is_practice_exam'):
return 'practice'
if exam.get('is_proctored'):
return 'proctored'
return 'timed'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is identical to ExamAttemptSerializer.get_exam_type in serializers_v2.py. If the derivation logic changes, one could be updated without the other. Consider extracting a shared helper:

Suggested change
def _get_exam_type(self, attempt):
"""Derive exam type string for sorting purposes."""
exam = attempt.get('proctored_exam', {})
if exam.get('is_practice_exam'):
return 'practice'
if exam.get('is_proctored'):
return 'proctored'
return 'timed'
@staticmethod
def _get_exam_type(attempt):
"""Derive exam type string for sorting purposes."""
return ExamAttemptSerializer.get_exam_type_from_dict(
attempt.get('proctored_exam', {})
)

…or a module-level function both can call. Not blocking, just a maintenance consideration.

Comment on lines +671 to +675
# Verify ascending and descending both succeed
for prefix in ('', '-'):
response = self.client.get(self._url(), {'ordering': f'{prefix}{ordering}'})
assert response.status_code == status.HTTP_200_OK
assert len(response.json()['results']) == 2
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only asserts status 200 and count — it would still pass if ordering were completely ignored. test_sort_allowances has a stronger pattern (checking asc_results[0] == desc_results[1]). Consider mirroring that here:

Suggested change
# Verify ascending and descending both succeed
for prefix in ('', '-'):
response = self.client.get(self._url(), {'ordering': f'{prefix}{ordering}'})
assert response.status_code == status.HTTP_200_OK
assert len(response.json()['results']) == 2
# Verify ascending and descending both succeed and reverse order
asc_response = self.client.get(self._url(), {'ordering': ordering})
desc_response = self.client.get(self._url(), {'ordering': f'-{ordering}'})
assert asc_response.status_code == status.HTTP_200_OK
asc_results = asc_response.json()['results']
desc_results = desc_response.json()['results']
assert len(asc_results) == 2
assert asc_results[0] == desc_results[1]
assert asc_results[1] == desc_results[0]

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

Labels

mao-onboarding Reviewing this will help onboard devs from an Axim mission-aligned organization (MAO). open-source-contribution PR author is not from Axim or 2U

Projects

Status: Waiting on Author

Development

Successfully merging this pull request may close these issues.

5 participants