Skip to content

Gradebook fails with UnboundLocalError in BulkRoleCache.prefetch (authz compat roles loop) #514

Open

Description

@BryanttV

Description

Opening the Gradebook from the Instructor Dashboard fails with a 500 error. GET on the gradebook API (lms/djangoapps/grades/rest_api/v1/gradebook_views.py) raises UnboundLocalError: cannot access local variable 'compat_role' where it is not associated with a value inside BulkRoleCache.prefetch.

Steps to reproduce

  1. Use an environment where openedx-authz compat roles are returned for at least one user listed in the gradebook (i.e. get_authz_compat_course_access_roles_for_user(user) returns a non-empty list). The user has no legacy CourseAccessRole entries, or the legacy loop didn't run.
  2. Open a course in the Instructor Dashboard and click through to the Gradebook.
  3. The gradebook API request fails with a 500.

Expected behavior

The Gradebook loads, and BulkRoleCache.prefetch caches the authz compat roles for each user.

Actual behavior

File "/openedx/edx-platform/lms/djangoapps/grades/rest_api/v1/gradebook_views.py", line 667, in get
    with bulk_gradebook_view_context(course_key, users):
File "/openedx/edx-platform/lms/djangoapps/grades/rest_api/v1/gradebook_views.py", line 91, in bulk_gradebook_view_context
    BulkRoleCache.prefetch(users)
File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 332, in prefetch
    user_roles_set_for_course.add(compat_role)
UnboundLocalError: cannot access local variable 'compat_role' where it is not associated with a value

Root cause

In common/djangoapps/student/roles.py, BulkRoleCache.prefetch has a second loop for openedx-authz roles that iterates with role but adds compat_role:

# openedx-authz roles
for user in users:
    compat_roles = get_authz_compat_course_access_roles_for_user(user)
    for role in compat_roles:
        course_id = get_role_cache_key_for_course(role.course_id)
        user_roles_set_for_course = roles_by_user[user.id][course_id]
        user_roles_set_for_course.add(compat_role)   # <-- should be `role`

compat_role is only assigned in the preceding legacy loop (CourseAccessRole). So:

  • If no legacy roles exist, compat_role is unbound and the code raises UnboundLocalError.
  • If legacy roles exist, it silently adds the last legacy role instead of the authz role. This is a latent wrong-data bug.

The bug was introduced in #38013 (12a46e6463, AuthZ compatibility layer for course authoring). It is still present on master and on release/verawood. The equivalent code in RoleCache.__init__ is correct.

Proposed fix

Add role instead of compat_role in the authz loop:

user_roles_set_for_course.add(role)

Also add a unit test for BulkRoleCache.prefetch with a user that has only authz compat roles, and one with both legacy and authz roles, asserting the cached sets contain the expected roles.

Impact

  • The Gradebook is unusable for courses where the listed users have authz compat roles.
  • Any other code path that calls BulkRoleCache.prefetch is affected as well.

Tasks

  • Fix BulkRoleCache.prefetch in master.
  • Add regression tests.
  • Backport the fix to release/verawood.

Activity

  1. moved this to Ready for Development in RBAC AuthZ Boardon Oct 2, 2026
  2. changed the title [-]# Gradebook fails with `UnboundLocalError` in `BulkRoleCache.prefetch` (authz compat roles loop)[/-] [+]Gradebook fails with `UnboundLocalError` in `BulkRoleCache.prefetch` (authz compat roles loop)[/+] on Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions