From 1212e3550c6e85a775aba1c67e7942b6ea9dcdd9 Mon Sep 17 00:00:00 2001 From: Nathan Sprenkle Date: Mon, 31 Jan 2022 11:09:41 -0500 Subject: [PATCH] feat: ora staff grader backend (#29828) - Adds Enhanced Staff Grader (ESG) backend-for-frontend (BFF) in `lms/djangoapps/ora_staff_grader` - Adds routing to ESG BFF at `{lms_url}/api/ora_staff_grader/*` - Adds mock implementation routing at `{lms_url}/api/ora_staff_grader/mock/*` - Adds `ORA_GRADING_MICROFRONTEND_URL` setting for routing to ESG microfrontend (MFE) - Updates to the teams app: - Add`get_teams_in_teamset` to the teams API. - Add `get_team_names` to teams service. - Adds `openassessment.staffgrader` app for appropriate ORA migrations. - Modifies management commands for creation of users. - Updates test factory to return display org with course overview. Co-authored-by: jansenk Co-authored-by: Leangseu Kim Co-authored-by: Ben Warzeski --- .github/workflows/pylint-checks.yml | 2 +- .github/workflows/unit-test-shards.json | 1 + cms/envs/common.py | 1 + .../management/commands/_create_users.py | 36 +- .../management/commands/create_test_users.py | 8 +- .../tests/test_create_test_users.py | 35 +- lms/djangoapps/ora_staff_grader/README.md | 17 + lms/djangoapps/ora_staff_grader/__init__.py | 3 + lms/djangoapps/ora_staff_grader/constants.py | 13 + lms/djangoapps/ora_staff_grader/errors.py | 116 + .../lms.postman_collection.json | 105 + .../ora_staff_grader/mock/README.md | 22 + .../ora_staff_grader/mock/__init__.py | 3 + .../mock/data/course_metadata.json | 14 + .../mock/data/ora_metadata.json | 93 + .../ora_staff_grader/mock/data/responses.json | 137 + .../mock/data/submissions.json | 178 + lms/djangoapps/ora_staff_grader/mock/urls.py | 30 + lms/djangoapps/ora_staff_grader/mock/utils.py | 79 + lms/djangoapps/ora_staff_grader/mock/views.py | 135 + lms/djangoapps/ora_staff_grader/ora_api.py | 169 + .../ora_staff_grader.postman_collection.json | 3218 +++++++++++++++++ .../ora_staff_grader/serializers.py | 304 ++ .../ora_staff_grader/tests/__init__.py | 0 .../ora_staff_grader/tests/test_data.py | 178 + .../tests/test_serializers.py | 698 ++++ .../ora_staff_grader/tests/test_views.py | 697 ++++ lms/djangoapps/ora_staff_grader/urls.py | 31 + lms/djangoapps/ora_staff_grader/utils.py | 76 + lms/djangoapps/ora_staff_grader/views.py | 398 ++ lms/djangoapps/teams/api.py | 16 + lms/djangoapps/teams/services.py | 9 + lms/djangoapps/teams/tests/test_api.py | 26 + lms/djangoapps/teams/tests/test_services.py | 25 + lms/envs/common.py | 8 + lms/envs/devstack.py | 1 + lms/urls.py | 5 + .../course_overviews/tests/factories.py | 7 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/testing.txt | 2 +- 41 files changed, 6885 insertions(+), 15 deletions(-) create mode 100644 lms/djangoapps/ora_staff_grader/README.md create mode 100644 lms/djangoapps/ora_staff_grader/__init__.py create mode 100644 lms/djangoapps/ora_staff_grader/constants.py create mode 100644 lms/djangoapps/ora_staff_grader/errors.py create mode 100644 lms/djangoapps/ora_staff_grader/lms.postman_collection.json create mode 100644 lms/djangoapps/ora_staff_grader/mock/README.md create mode 100644 lms/djangoapps/ora_staff_grader/mock/__init__.py create mode 100644 lms/djangoapps/ora_staff_grader/mock/data/course_metadata.json create mode 100644 lms/djangoapps/ora_staff_grader/mock/data/ora_metadata.json create mode 100644 lms/djangoapps/ora_staff_grader/mock/data/responses.json create mode 100644 lms/djangoapps/ora_staff_grader/mock/data/submissions.json create mode 100644 lms/djangoapps/ora_staff_grader/mock/urls.py create mode 100644 lms/djangoapps/ora_staff_grader/mock/utils.py create mode 100644 lms/djangoapps/ora_staff_grader/mock/views.py create mode 100644 lms/djangoapps/ora_staff_grader/ora_api.py create mode 100644 lms/djangoapps/ora_staff_grader/ora_staff_grader.postman_collection.json create mode 100644 lms/djangoapps/ora_staff_grader/serializers.py create mode 100644 lms/djangoapps/ora_staff_grader/tests/__init__.py create mode 100644 lms/djangoapps/ora_staff_grader/tests/test_data.py create mode 100644 lms/djangoapps/ora_staff_grader/tests/test_serializers.py create mode 100644 lms/djangoapps/ora_staff_grader/tests/test_views.py create mode 100644 lms/djangoapps/ora_staff_grader/urls.py create mode 100644 lms/djangoapps/ora_staff_grader/utils.py create mode 100644 lms/djangoapps/ora_staff_grader/views.py diff --git a/.github/workflows/pylint-checks.yml b/.github/workflows/pylint-checks.yml index e643efd61e..f9f86a096b 100644 --- a/.github/workflows/pylint-checks.yml +++ b/.github/workflows/pylint-checks.yml @@ -17,7 +17,7 @@ jobs: - module-name: lms-1 path: "lms/djangoapps/badges/ lms/djangoapps/branding/ lms/djangoapps/bulk_email/ lms/djangoapps/bulk_enroll/ lms/djangoapps/bulk_user_retirement/ lms/djangoapps/ccx/ lms/djangoapps/certificates/ lms/djangoapps/commerce/ lms/djangoapps/course_api/ lms/djangoapps/course_blocks/ lms/djangoapps/course_home_api/ lms/djangoapps/course_wiki/ lms/djangoapps/coursewarehistoryextended/ lms/djangoapps/debug/ lms/djangoapps/courseware/ lms/djangoapps/course_goals/ lms/djangoapps/rss_proxy/ lms/djangoapps/save_for_later/" - module-name: lms-2 - path: "lms/djangoapps/gating/ lms/djangoapps/grades/ lms/djangoapps/instructor/ lms/djangoapps/instructor_analytics/ lms/djangoapps/discussion/ lms/djangoapps/edxnotes/ lms/djangoapps/email_marketing/ lms/djangoapps/experiments/ lms/djangoapps/instructor_task/ lms/djangoapps/learner_dashboard/ lms/djangoapps/lms_initialization/ lms/djangoapps/lms_xblock/ lms/djangoapps/lti_provider/ lms/djangoapps/mailing/ lms/djangoapps/mobile_api/ lms/djangoapps/monitoring/ lms/djangoapps/program_enrollments/ lms/djangoapps/rss_proxy lms/djangoapps/static_template_view/ lms/djangoapps/staticbook/ lms/djangoapps/support/ lms/djangoapps/survey/ lms/djangoapps/teams/ lms/djangoapps/tests/ lms/djangoapps/user_tours/ lms/djangoapps/verify_student/ lms/envs/ lms/lib/ lms/tests.py" + path: "lms/djangoapps/gating/ lms/djangoapps/grades/ lms/djangoapps/instructor/ lms/djangoapps/instructor_analytics/ lms/djangoapps/discussion/ lms/djangoapps/edxnotes/ lms/djangoapps/email_marketing/ lms/djangoapps/experiments/ lms/djangoapps/instructor_task/ lms/djangoapps/learner_dashboard/ lms/djangoapps/lms_initialization/ lms/djangoapps/lms_xblock/ lms/djangoapps/lti_provider/ lms/djangoapps/mailing/ lms/djangoapps/mobile_api/ lms/djangoapps/monitoring/ lms/djangoapps/ora_staff_grader/ lms/djangoapps/program_enrollments/ lms/djangoapps/rss_proxy lms/djangoapps/static_template_view/ lms/djangoapps/staticbook/ lms/djangoapps/support/ lms/djangoapps/survey/ lms/djangoapps/teams/ lms/djangoapps/tests/ lms/djangoapps/user_tours/ lms/djangoapps/verify_student/ lms/envs/ lms/lib/ lms/tests.py" - module-name: openedx-1 path: "openedx/core/types/ openedx/core/djangoapps/ace_common/ openedx/core/djangoapps/agreements/ openedx/core/djangoapps/api_admin/ openedx/core/djangoapps/auth_exchange/ openedx/core/djangoapps/bookmarks/ openedx/core/djangoapps/cache_toolbox/ openedx/core/djangoapps/catalog/ openedx/core/djangoapps/ccxcon/ openedx/core/djangoapps/commerce/ openedx/core/djangoapps/common_initialization/ openedx/core/djangoapps/common_views/ openedx/core/djangoapps/config_model_utils/ openedx/core/djangoapps/content/ openedx/core/djangoapps/content_libraries/ openedx/core/djangoapps/contentserver/ openedx/core/djangoapps/cookie_metadata/ openedx/core/djangoapps/cors_csrf/ openedx/core/djangoapps/course_apps/ openedx/core/djangoapps/course_date_signals/ openedx/core/djangoapps/course_groups/ openedx/core/djangoapps/coursegraph/ openedx/core/djangoapps/courseware_api/ openedx/core/djangoapps/crawlers/ openedx/core/djangoapps/credentials/ openedx/core/djangoapps/credit/ openedx/core/djangoapps/dark_lang/ openedx/core/djangoapps/debug/ openedx/core/djangoapps/demographics/ openedx/core/djangoapps/discussions/ openedx/core/djangoapps/django_comment_common/ openedx/core/djangoapps/embargo/ openedx/core/djangoapps/enrollments/ openedx/core/djangoapps/external_user_ids/ openedx/core/djangoapps/zendesk_proxy/ openedx/core/djangolib/ openedx/core/lib/ openedx/core/tests/" - module-name: openedx-2 diff --git a/.github/workflows/unit-test-shards.json b/.github/workflows/unit-test-shards.json index 58e4b8fddc..a30423815e 100644 --- a/.github/workflows/unit-test-shards.json +++ b/.github/workflows/unit-test-shards.json @@ -59,6 +59,7 @@ "lms/djangoapps/mailing/", "lms/djangoapps/mobile_api/", "lms/djangoapps/monitoring/", + "lms/djangoapps/ora_staff_grader/", "lms/djangoapps/program_enrollments/", "lms/djangoapps/rss_proxy/", "lms/djangoapps/save_for_later/", diff --git a/cms/envs/common.py b/cms/envs/common.py index ccf6adc514..4cf5cc8567 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -1816,6 +1816,7 @@ OPTIONAL_APPS = ( ('openassessment', 'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'), ('openassessment.assessment', 'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'), ('openassessment.fileupload', 'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'), + ('openassessment.staffgrader', 'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'), ('openassessment.workflow', 'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'), ('openassessment.xblock', 'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'), diff --git a/common/djangoapps/student/management/commands/_create_users.py b/common/djangoapps/student/management/commands/_create_users.py index 3b0aeb0d90..f3e739ac48 100644 --- a/common/djangoapps/student/management/commands/_create_users.py +++ b/common/djangoapps/student/management/commands/_create_users.py @@ -1,18 +1,25 @@ """ Shared behavior between create_test_users and create_random_users """ +from django.contrib.auth import get_user_model +from django.core.validators import ValidationError from xmodule.modulestore.django import modulestore + from lms.djangoapps.instructor.access import allow_access from openedx.core.djangoapps.user_authn.views.registration_form import AccountCreationForm -from common.djangoapps.student.helpers import do_create_account +from common.djangoapps.student.helpers import do_create_account, AccountValidationError from common.djangoapps.student.models import CourseEnrollment +User = get_user_model() + + def create_users( course_key, user_data, enrollment_mode=None, course_staff=False, - activate=False + activate=False, + ignore_user_already_exists=False, ): """Create users, enrolling them in course_key if it's not None""" for single_user_data in user_data: @@ -21,7 +28,26 @@ def create_users( tos_required=False ) - (user, _, _) = do_create_account(account_creation_form) + user_already_exists = False + try: + (user, _, _) = do_create_account(account_creation_form) + except (ValidationError, AccountValidationError) as account_creation_error: + # It would be convenient if we just had the AccountValidationError raised, because we include a + # helpful error code in there, but we also do form validation on account_creation_form and those + # are pretty opaque. + try: + # Check to see if there's a user with our username. If the username and email match our input, + # we're good, it's the same user, probably. If the email doesn't match, just to be safe we will + # continue to fail. + user = User.objects.get(username=single_user_data['username']) + if user.email == single_user_data['email'] and ignore_user_already_exists: + user_already_exists = True + print(f'Test user {user.username} already exists. Continuing to attempt to enroll.') + else: + raise account_creation_error + except User.DoesNotExist: + # If a user with the username doesn't exist the error was probably something else, so reraise + raise account_creation_error # pylint: disable=raise-missing-from if activate: user.is_active = True @@ -33,7 +59,7 @@ def create_users( course = modulestore().get_course(course_key, depth=1) allow_access(course, user, 'staff', send_email=False) - if course_key and course_staff: + if course_key and course_staff and not user_already_exists: print(f'Created user {user.username} as course staff') - else: + elif not user_already_exists: print(f'Created user {user.username}') diff --git a/common/djangoapps/student/management/commands/create_test_users.py b/common/djangoapps/student/management/commands/create_test_users.py index 8c44f5ebdd..9881c37e7f 100644 --- a/common/djangoapps/student/management/commands/create_test_users.py +++ b/common/djangoapps/student/management/commands/create_test_users.py @@ -64,6 +64,11 @@ class Command(BaseCommand): ), action='store_true' ) + parser.add_argument( + '--ignore_user_already_exists', + help="Don't fail if a user already exists. Log the error and attempt to enroll them in the course.", + action='store_true' + ) def handle(self, *args, **options): course_key = options['course'] @@ -78,5 +83,6 @@ class Command(BaseCommand): ), enrollment_mode=enrollment_mode, course_staff=course_staff, - activate=True + activate=True, + ignore_user_already_exists=options['ignore_user_already_exists'] ) diff --git a/common/djangoapps/student/management/tests/test_create_test_users.py b/common/djangoapps/student/management/tests/test_create_test_users.py index c1c0f1c427..9258b15f4f 100644 --- a/common/djangoapps/student/management/tests/test_create_test_users.py +++ b/common/djangoapps/student/management/tests/test_create_test_users.py @@ -29,10 +29,18 @@ class CreateTestUsersTestCase(SharedModuleStoreTestCase): self.user_model = get_user_model() self.num_users_start = len(self.user_model.objects.all()) - def call_command(self, users, course=None, mode=None, password=None, domain=None, course_staff=False): + def call_command( + self, + users, + course=None, + mode=None, + password=None, + domain=None, + course_staff=False, + ignore_user_already_exists=False + ): """ Helper method to call the management command with various arguments """ - args = ['create_test_users'] - args.extend(users) + args = list(users) if course: args.extend(['--course', course]) if mode: @@ -43,7 +51,10 @@ class CreateTestUsersTestCase(SharedModuleStoreTestCase): args.extend(['--domain', domain]) if course_staff: args.append('--course_staff') - call_command(*args) + if ignore_user_already_exists: + args.append('--ignore_user_already_exists') + + call_command('create_test_users', *args) def test_create_users(self): """ @@ -203,3 +214,19 @@ class CreateTestUsersTestCase(SharedModuleStoreTestCase): user = self.user_model.objects.get(username=username) assert not CourseAccessRole.objects.filter(user=user).exists() assert not CourseEnrollment.objects.filter(user=user).exists() + + def test_create_user__ignore_user_already_exists(self): + """ + Test that ignore_user_already_exists will allow us to specify a username + that already exists without raising an exception + """ + test_username = 'IgnoreUserAlreadyExistsUser' + assert not self.user_model.objects.filter(username=test_username).exists() + + self.call_command([test_username]) + assert self.user_model.objects.filter(username=test_username).exists() + + with self.assertRaises(ValidationError): + self.call_command([test_username], ignore_user_already_exists=False) + + self.call_command([test_username], ignore_user_already_exists=True) diff --git a/lms/djangoapps/ora_staff_grader/README.md b/lms/djangoapps/ora_staff_grader/README.md new file mode 100644 index 0000000000..32e5a932ae --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/README.md @@ -0,0 +1,17 @@ +# Enhanced Staff Grader (ESG) App + +A backend-for-frontend (BFF) for ESG. It provides endpoints at the path `{lms-url}/api/ora_staff_grader/{endpoint}`. + +ESG is an application on top of Open Response Assessments (ORA) designed to simplify staff grading of assignments. The BFF is designed to service the ESG microfrontend (MFE) by aggregating and packaging requests to both `edx-platform` and `edx-ora2`. + +The BFF includes both an API and mock API (/mock) for testing. Exercise either with the attached Postman collections (and included examples) or see [Enhanced Staff Grader Data Flow Design](https://openedx.atlassian.net/wiki/spaces/PT/pages/3154542730/Enhanced+Staff+Grader+Data+Flow+Design) for API reference. + +## Quickstart + +Connect to or exercise endpoints at `{lms-url}/api/ora_staff_grader/{endpoint}`. + +Alternatively, use the attached postman collections to perform headless testing of endpoints. Following the setup below: + +1. Perform headless login: in `lms.postman_collection.json` perform the `GET Login` call to generate a new CSRF token followed by a `POST Login` with valid staff credentials to authenticate with LMS. +2. Configure needed envirionment variables including `{{mock}} = False` +2. Exercise endpoints: in `ora_staff_grader.postman_collection.json` diff --git a/lms/djangoapps/ora_staff_grader/__init__.py b/lms/djangoapps/ora_staff_grader/__init__.py new file mode 100644 index 0000000000..1be6141580 --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/__init__.py @@ -0,0 +1,3 @@ +""" +App for Enhanced Staff Grader (ESG) backend-for-frontend (BFF) +""" diff --git a/lms/djangoapps/ora_staff_grader/constants.py b/lms/djangoapps/ora_staff_grader/constants.py new file mode 100644 index 0000000000..d1ef62287b --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/constants.py @@ -0,0 +1,13 @@ +""" Constants used throughout ESG """ + +# Query params +PARAM_ORA_LOCATION = "oraLocation" +PARAM_SUBMISSION_ID = "submissionUUID" + +# Error codes +ERR_UNKNOWN = "ERR_UNKNOWN" +ERR_INTERNAL = "ERR_INTERNAL" +ERR_MISSING_PARAM = "ERR_MISSING_PARAM" +ERR_BAD_ORA_LOCATION = "ERR_BAD_ORA_LOCATION" +ERR_LOCK_CONTESTED = "ERR_LOCK_CONTESTED" +ERR_GRADE_CONTESTED = "ERR_GRADE_CONTESTED" diff --git a/lms/djangoapps/ora_staff_grader/errors.py b/lms/djangoapps/ora_staff_grader/errors.py new file mode 100644 index 0000000000..801ecc32da --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/errors.py @@ -0,0 +1,116 @@ +""" Error codes and exceptions for ESG """ +from rest_framework import serializers +from rest_framework.response import Response + +from lms.djangoapps.ora_staff_grader.constants import ( + ERR_BAD_ORA_LOCATION, + ERR_GRADE_CONTESTED, + ERR_INTERNAL, + ERR_LOCK_CONTESTED, + ERR_MISSING_PARAM, + ERR_UNKNOWN, +) + + +class ExceptionWithContext(Exception): + """An exception with optional context dict to be supplied in serialized result""" + + def __init__(self, context=None): + super().__init__(self) + self.context = context + + +class XBlockInternalError(ExceptionWithContext): + """Errors from XBlock handlers""" + + +class LockContestedError(ExceptionWithContext): + """Signal for trying to operate on a lock owned by someone else""" + + +class ErrorSerializer(serializers.Serializer): + """Returns error code and unpacks additional context, returns unknown error code if not supplied""" + + error = serializers.CharField(default=ERR_UNKNOWN) + + def to_representation(self, instance): + """Override to unpack context alongside error code""" + output = super().to_representation(instance) + + if self.context: + for key, value in self.context.items(): + output[key] = value + + return output + + +class StaffGraderErrorResponse(Response): + """An HTTP error response that returns serialized error data with additional provided context""" + + status = 500 + err_code = ERR_UNKNOWN + + def __init__(self, context=None): + # Unpack provided content into error structure + content = ErrorSerializer({"error": self.err_code}, context=context).data + super().__init__(content, status=self.status) + + +class BadOraLocationResponse(StaffGraderErrorResponse): + """ + Error response for when the requested ORA_LOCATION could not be found in a course. + Returns an HTTP 400 with error code. + """ + + status = 400 + err_code = ERR_BAD_ORA_LOCATION + + +class MissingParamResponse(StaffGraderErrorResponse): + """ + Error response for when a request is missing a required param/body. + Returns an HTTP 400 with error code. + """ + + status = 400 + err_code = ERR_MISSING_PARAM + + +class LockContestedResponse(StaffGraderErrorResponse): + """ + Error response for when a user tries to operate on a submission that they do not have a lock for. + Returns an HTTP 409 with error code and updated lock status. + """ + + status = 409 + err_code = ERR_LOCK_CONTESTED + + +class GradeContestedResponse(StaffGraderErrorResponse): + """ + Error response for when a user tries to operate on a submission that they do not have a lock for. + Returns an HTTP 409 with error code and updated submission status. + """ + + status = 409 + err_code = ERR_GRADE_CONTESTED + + +class InternalErrorResponse(StaffGraderErrorResponse): + """ + Generic error response for an issue in an XBlock handler. + Returns an HTTP 500 with internal error code. + """ + + status = 500 + err_code = ERR_INTERNAL + + +class UnknownErrorResponse(StaffGraderErrorResponse): + """ + Blanket exception for when something strange breaks + Returns an HTTP 500 with generic error code. + """ + + status = 500 + err_code = ERR_UNKNOWN diff --git a/lms/djangoapps/ora_staff_grader/lms.postman_collection.json b/lms/djangoapps/ora_staff_grader/lms.postman_collection.json new file mode 100644 index 0000000000..db7fb3762f --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/lms.postman_collection.json @@ -0,0 +1,105 @@ +{ + "info": { + "_postman_id": "68587cc2-2edc-45c7-a89a-7be3d5d69cd3", + "name": "LMS", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Login", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var xsrfCookie = postman.getResponseCookie(\"csrftoken\");", + "postman.setEnvironmentVariable('csrftoken', xsrfCookie.value);" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "formdata", + "formdata": [] + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/login", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "login" + ], + "query": [ + { + "key": "", + "value": null, + "disabled": true + } + ] + }, + "description": "Run this to get an initial CSRF token. Follow with POST to login as {{user}}" + }, + "response": [] + }, + { + "name": "Login", + "request": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "email", + "value": "{{user_email}}", + "type": "text" + }, + { + "key": "password", + "value": "{{user_password}}", + "type": "text" + } + ] + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/user/v1/account/login_session/", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "user", + "v1", + "account", + "login_session", + "" + ] + }, + "description": "Run this to authenticate with LMS. Requires running GET Login first to obtain a CSRF token." + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/lms/djangoapps/ora_staff_grader/mock/README.md b/lms/djangoapps/ora_staff_grader/mock/README.md new file mode 100644 index 0000000000..9eb5486eff --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/mock/README.md @@ -0,0 +1,22 @@ +# Mock Enhanced Staff Grader (ESG) + +A mock backend-for-frontend (BFF) for ESG. It provides mocked endpoints at the path http(s)://{lms-url}/api/ora_staff_grader/mock/{endpoint}. + +This is differentiated from the "real" BFF endpoints, which omit the `mock` part of the path. This should make it easy to switch between real/mocked versions by configuring the base API path. + +The mock is, effectively, a wrapper on top of a JSON data store. All the important data is stored in the lms/djangoapps/ora_staff_grader/mock/data/ directory. Data is generally grouped by a key that would be supplied in the request (usually the submissionUUID and/or ora_location). To add/edit data, simply edit the underlying JSON files. + +For some endpoints (e.g. lock/unlock/grade), there is simple interactivity; hitting the endpoint will save a change to the underlying data. These can be verified by reading the updated JSON files or reverted by doing a git checkout of the file. + +## Quickstart + +Connect to or exercise endpoints at `{devstack-url}/api/ora_staff_grader/mock/{endpoint}`. + +Alternatively, use the attached postman collections to perform headless testing of endpoints. Following the setup below: + +1. Perform headless login: in `lms.postman_collection.json` perform the `GET Login` call to generate a new CSRF token followed by a `POST Login` with valid credentials to authenticate with LMS. +2. Exercise mock endpoints: in `ora_staff_grader.postman_collection.json`, after configuring the environment variables including `{{mock}} = True`, run the example requests. + +## API Reference + +See [Enhanced Staff Grader Data Flow Design](https://openedx.atlassian.net/wiki/spaces/PT/pages/3154542730/Enhanced+Staff+Grader+Data+Flow+Design) diff --git a/lms/djangoapps/ora_staff_grader/mock/__init__.py b/lms/djangoapps/ora_staff_grader/mock/__init__.py new file mode 100644 index 0000000000..af6b04a694 --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/mock/__init__.py @@ -0,0 +1,3 @@ +""" +Mock tooling for ESG +""" diff --git a/lms/djangoapps/ora_staff_grader/mock/data/course_metadata.json b/lms/djangoapps/ora_staff_grader/mock/data/course_metadata.json new file mode 100644 index 0000000000..1b734dab94 --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/mock/data/course_metadata.json @@ -0,0 +1,14 @@ +{ + "block-a": { + "title": "Defense against the dark arts", + "org": "Hogwarts", + "number": "DADA101", + "courseId": "course-v1:Hogwarts+DADA101+2021_Winter" + }, + "block-b": { + "title": "Introduction to Time Travel", + "org": "Oxford", + "number": "TT101", + "courseId": "course-v1:Oxford+TT101+2021_Winter" + } +} diff --git a/lms/djangoapps/ora_staff_grader/mock/data/ora_metadata.json b/lms/djangoapps/ora_staff_grader/mock/data/ora_metadata.json new file mode 100644 index 0000000000..c3b34d8f1b --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/mock/data/ora_metadata.json @@ -0,0 +1,93 @@ +{ + "block-a": { + "name": "Individual ORA", + "prompts": [ + "

Enter a text/files response.

" + ], + "type": "individual", + "rubricConfig": { + "feedback_prompt": "How would you grade this response?", + "feedback_default_text": "I believe this response...", + "feedback": "optional", + "criteria": [ + { + "orderNum": 0, + "name": "grammar", + "label": "Grammar", + "prompt": "How correct is the submitter's grammar?", + "feedback": "optional", + "options": [ + { + "orderNum": 0, + "name": "poor", + "label": "Poor", + "explanation": "Absolute rubbish", + "points": 0 + }, + { + "orderNum": 1, + "name": "good", + "label": "Good", + "explanation": "pretty good", + "points": 3 + }, + { + "orderNum": 2, + "name": "excellent", + "label": "Excelent", + "explanation": "Not absolute rubbish", + "points": 5 + } + ] + } + ] + }, + "textResponseConfig": "optional", + "fileUploadResponseConfig": "optional" + }, + "block-b": { + "name": "Team ORA", + "prompts": [ + "

Enter a text/files response.

" + ], + "type": "team", + "rubricConfig": { + "feedbackPrompt": "How would you grade this response?", + "feedback": "optional", + "criteria": [ + { + "orderNum": 0, + "name": "correctness", + "label": "Correctness", + "prompt": "How correct is the team's answer?", + "feedback": "optional", + "options": [ + { + "orderNum": 0, + "name": "poor", + "label": "Poor", + "explanation": "Absolute rubbish", + "points": 0 + }, + { + "orderNum": 1, + "name": "good", + "label": "Good", + "explanation": "pretty good", + "points": 3 + }, + { + "orderNum": 2, + "name": "excellent", + "label": "Excelent", + "explanation": "Not absolute rubbish", + "points": 5 + } + ] + } + ] + }, + "textResponseConfig": "optional", + "fileUploadResponseConfig": "required" + } +} diff --git a/lms/djangoapps/ora_staff_grader/mock/data/responses.json b/lms/djangoapps/ora_staff_grader/mock/data/responses.json new file mode 100644 index 0000000000..7c8d3c9b52 --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/mock/data/responses.json @@ -0,0 +1,137 @@ +{ + "SUBMISSION_ID-0": { + "text": [ + "

Response Title

Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna. Phasellus porttitor vel magna et auctor. Nulla porttitor convallis aliquam. Donec cursus, ipsum ut egestas bibendum, purus metus dignissim est, ac condimentum leo felis eget diam. In magna mi, tincidunt id sapien id, fermentum vestibulum quam. Quisque et dui sed urna convallis rutrum pellentesque quis sapien. Cras non lectus velit. Praesent semper eros id risus mollis, quis interdum quam imperdiet. Sed nec vulputate tortor, at tristique tortor.
" + ], + "files": [ + { + "name": "0_sample.bmp", + "description": "This is some descriptive text for (su-0). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.bmp", + "size": 818058 + }, + { + "name": "0_sample.doc", + "description": "This is some descriptive text for (su-0). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.doc", + "size": 32768 + }, + { + "name": "0_sample.docx", + "description": "This is some descriptive text for (su-0). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.docx", + "size": 14169117 + }, + { + "name": "0_sample.html", + "description": "This is some descriptive text for (su-0). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.html", + "size": 58707 + }, + { + "name": "0_sample.jpeg", + "description": "This is some descriptive text for (su-0). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.jpeg", + "size": 88731 + }, + { + "name": "0_sample.jpg", + "description": "This is some descriptive text for (su-0). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.jpg", + "size": 88731 + }, + { + "name": "0_sample.ppt", + "description": "This is some descriptive text for (su-0). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.ppt", + "size": 530432 + }, + { + "name": "0_sample.pptx", + "description": "This is some descriptive text for (su-0). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.pptx", + "size": 413895 + }, + { + "name": "0_sample.tiff", + "description": "This is some descriptive text for (su-0). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.tiff", + "size": 818184 + }, + { + "name": "0_sample.txt", + "description": "This is some descriptive text for (su-0). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.txt", + "size": 3541 + }, + { + "name": "0_sample.xls", + "description": "This is some descriptive text for (su-0). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.xls", + "size": 13312 + }, + { + "name": "0_sample.xlsx", + "description": "This is some descriptive text for (su-0). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.xlsx", + "size": 13246 + } + ] + }, + "SUBMISSION_ID-1": { + "text": [ + "

Response Title

Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna. Phasellus porttitor vel magna et auctor. Nulla porttitor convallis aliquam. Donec cursus, ipsum ut egestas bibendum, purus metus dignissim est, ac condimentum leo felis eget diam. In magna mi, tincidunt id sapien id, fermentum vestibulum quam. Quisque et dui sed urna convallis rutrum pellentesque quis sapien. Cras non lectus velit. Praesent semper eros id risus mollis, quis interdum quam imperdiet. Sed nec vulputate tortor, at tristique tortor.
" + ], + "files": [ + { + "name": "1_sample.csv", + "description": "This is some descriptive text for (su-1). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.csv", + "size": 7918 + }, + { + "name": "1_sample.mp3", + "description": "This is some descriptive text for (su-1). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.mp3", + "size": 1693405 + }, + { + "name": "1_sample.mp4", + "description": "This is some descriptive text for (su-1). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/sample.mp4", + "size": 574823 + } + ] + }, + "SUBMISSION_ID-2": { + "text": [ + "

Response Title

Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna. Phasellus porttitor vel magna et auctor. Nulla porttitor convallis aliquam. Donec cursus, ipsum ut egestas bibendum, purus metus dignissim est, ac condimentum leo felis eget diam. In magna mi, tincidunt id sapien id, fermentum vestibulum quam. Quisque et dui sed urna convallis rutrum pellentesque quis sapien. Cras non lectus velit. Praesent semper eros id risus mollis, quis interdum quam imperdiet. Sed nec vulputate tortor, at tristique tortor.
" + ], + "files": [ + { + "name": "2_edX_2021_Internal_BrandTMGuidelines_v1.0.9.pdf", + "description": "This is some descriptive text for (su-2). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/edX_2021_Internal_BrandTMGuidelines_v1.0.9.pdf", + "size": 1000000 + }, + { + "name": "2_irs_p5564.pdf", + "description": "This is some descriptive text for (su-2). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/irs_p5564.pdf", + "size": 0 + }, + { + "name": "2_mit_Cohen_GRL16.pdf", + "description": "This is some descriptive text for (su-2). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.", + "downloadUrl": "samples/mit_Cohen_GRL16.pdf", + "size": 1000 + } + ] + }, + "SUBMISSION_ID-3": { + "text": [ + "

Response Title

Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna. Phasellus porttitor vel magna et auctor. Nulla porttitor convallis aliquam. Donec cursus, ipsum ut egestas bibendum, purus metus dignissim est, ac condimentum leo felis eget diam. In magna mi, tincidunt id sapien id, fermentum vestibulum quam. Quisque et dui sed urna convallis rutrum pellentesque quis sapien. Cras non lectus velit. Praesent semper eros id risus mollis, quis interdum quam imperdiet. Sed nec vulputate tortor, at tristique tortor.
" + ], + "files": [] + } +} diff --git a/lms/djangoapps/ora_staff_grader/mock/data/submissions.json b/lms/djangoapps/ora_staff_grader/mock/data/submissions.json new file mode 100644 index 0000000000..2e06b06fca --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/mock/data/submissions.json @@ -0,0 +1,178 @@ +{ + "block-a": { + "SUBMISSION_ID-0": { + "submissionUUID": "SUBMISSION_ID-0", + "username": "USERNAME-0", + "dateSubmitted": 1631215154955, + "score": { + "pointsEarned": 70, + "pointsPossible": 100 + }, + "gradeData": { + "showValidation": false, + "overallFeedback": "", + "criteria": [ + { + "orderNum": 0, + "name": "grammar", + "selectedOption": "excellent", + "feedback": "test3" + } + ], + "score": { + "pointsEarned": 0, + "pointsPossible": 100 + } + }, + "gradeStatus": "graded", + "lockStatus": "unlocked" + }, + "SUBMISSION_ID-1": { + "submissionUUID": "SUBMISSION_ID-1", + "username": "USERNAME-1", + "dateSubmitted": 1631301554955, + "score": { + "pointsEarned": 70, + "pointsPossible": 100 + }, + "gradeData": { + "score": { + "pointsEarned": 70, + "pointsPossible": 100 + }, + "overallFeedback": "was okay", + "criteria": [ + { + "name": "firstCriterion", + "feedback": "did alright", + "selectedOption": "good" + } + ] + }, + "gradeStatus": "graded", + "lockStatus": "locked" + }, + "SUBMISSION_ID-2": { + "submissionUUID": "SUBMISSION_ID-2", + "username": "USERNAME-2", + "dateSubmitted": 1631387954955, + "score": { + "pointsEarned": 80, + "pointsPossible": 100 + }, + "gradeData": { + "score": { + "pointsEarned": 80, + "pointsPossible": 100 + }, + "overallFeedback": "was okay", + "criteria": [ + { + "name": "firstCriterion", + "feedback": "did alright", + "selectedOption": "good" + } + ] + }, + "gradeStatus": "graded", + "lockStatus": "unlocked" + }, + "SUBMISSION_ID-3": { + "submissionUUID": "SUBMISSION_ID-3", + "username": "USERNAME-3", + "dateSubmitted": 1631474354955, + "score": null, + "gradeData": null, + "gradeStatus": "ungraded", + "lockStatus": "in-progress" + }, + "SUBMISSION_ID-4": { + "submissionUUID": "SUBMISSION_ID-4", + "username": "USERNAME-4", + "dateSubmitted": 1631474354955, + "score": null, + "gradeData": null, + "gradeStatus": "ungraded", + "lockStatus": "locked" + } + }, + "block-b": { + "TEAM_SUBMISSION_ID-0": { + "submissionUUID": "TEAM_SUBMISSION_ID-0", + "teamName": "TEAMNAME-0", + "dateSubmitted": 1631215154955, + "score": null, + "gradeData": null, + "gradeStatus": "ungraded", + "lockStatus": "in-progress" + }, + "TEAM_SUBMISSION_ID-1": { + "submissionUUID": "TEAM_SUBMISSION_ID-1", + "teamName": "TEAMNAME-1", + "dateSubmitted": 1631301554955, + "score": { + "pointsEarned": 70, + "pointsPossible": 100 + }, + "gradeData": { + "score": { + "pointsEarned": 70, + "pointsPossible": 100 + }, + "overallFeedback": "was okay", + "criteria": [ + { + "name": "firstCriterion", + "feedback": "did alright", + "selectedOption": "good" + } + ] + }, + "gradeStatus": "graded", + "lockStatus": "locked" + }, + "TEAM_SUBMISSION_ID-2": { + "submissionUUID": "TEAM_SUBMISSION_ID-2", + "teamName": "TEAMNAME-2", + "dateSubmitted": 1631387954955, + "score": { + "pointsEarned": 80, + "pointsPossible": 100 + }, + "gradeData": { + "score": { + "pointsEarned": 80, + "pointsPossible": 100 + }, + "overallFeedback": "was okay", + "criteria": [ + { + "name": "firstCriterion", + "feedback": "did alright", + "selectedOption": "good" + } + ] + }, + "gradeStatus": "graded", + "lockStatus": "unlocked" + }, + "TEAM_SUBMISSION_ID-3": { + "submissionUUID": "TEAM_SUBMISSION_ID-3", + "teamName": "TEAMNAME-3", + "dateSubmitted": 1631474354955, + "score": null, + "gradeData": null, + "gradeStatus": "ungraded", + "lockStatus": "in-progress" + }, + "TEAM_SUBMISSION_ID-4": { + "submissionUUID": "TEAM_SUBMISSION_ID-4", + "teamName": "TEAMNAME-4", + "dateSubmitted": 1631474354955, + "score": null, + "gradeData": null, + "gradeStatus": "ungraded", + "lockStatus": "locked" + } + } +} \ No newline at end of file diff --git a/lms/djangoapps/ora_staff_grader/mock/urls.py b/lms/djangoapps/ora_staff_grader/mock/urls.py new file mode 100644 index 0000000000..b27e704e67 --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/mock/urls.py @@ -0,0 +1,30 @@ +""" +URLs for Enhanced Staff Grader (ESG) backend-for-frontend (BFF) + +NOTE - This should be the same as ../urls.py +""" +from django.urls import path + +from lms.djangoapps.ora_staff_grader.mock.views import ( + InitializeView, + SubmissionStatusFetchView, + SubmissionLockView, + UpdateGradeView, + SubmissionFetchView, +) + + +urlpatterns = [] +app_name = "mock-ora-staff-grader" + +urlpatterns += [ + path("initialize", InitializeView.as_view(), name="initialize"), + path( + "submission/status", + SubmissionStatusFetchView.as_view(), + name="fetch-submission-status", + ), + path("submission/lock", SubmissionLockView.as_view(), name="lock-submission"), + path("submission/grade", UpdateGradeView.as_view(), name="update-grade"), + path("submission", SubmissionFetchView.as_view(), name="fetch-submission"), +] diff --git a/lms/djangoapps/ora_staff_grader/mock/utils.py b/lms/djangoapps/ora_staff_grader/mock/utils.py new file mode 100644 index 0000000000..43318becb0 --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/mock/utils.py @@ -0,0 +1,79 @@ +""" +Mocking/testing utils for ESG +""" +import json + +from os import path + + +DATA_ROOT = "/edx/app/edxapp/edx-platform/lms/djangoapps/ora_staff_grader/mock/data" + + +def read_data_file(file_name): + """Return data from a JSON file in the /data dir""" + with open(path.join(DATA_ROOT, file_name), "r") as data_file: + return json.load(data_file) + + +def update_data_file(file_name, update_key_path, update_value): + """ + Update a single key/value within a JSON file + + params: + - file_name: string path of file relative to DATA_ROOT + - update_key_path: array-like list of keys to traverse, necessary for editing multiple levels down in hierarchy + """ + update_data = read_data_file(file_name) + + # Adapted from https://ipindersinghsuri.medium.com/updating-dynamic-nested-dictionary-in-python-92f5afbd1755 + def get_updated_dict(dict_to_update, key_list, value): + obj = dict_to_update + + for k in key_list[:-1]: + obj = obj[k] + + obj[key_list[-1]] = value + + get_updated_dict(update_data, update_key_path, update_value) + + with open(path.join(DATA_ROOT, file_name), "w") as data_file: + json.dump(update_data, data_file, indent=4) + + +def get_course_metadata(ora_location): + """Get course metadata, indexed by ORA block location""" + return read_data_file("course_metadata.json")[ora_location] + + +def get_ora_metadata(ora_location): + """Get ORA metadata, indexed by ORA block location""" + return read_data_file("ora_metadata.json")[ora_location] + + +def get_submissions(ora_location): # pylint: disable=unused-argument + """Get Submission list, scoped by ORA block location""" + submissions = read_data_file("submissions.json")[ora_location] + + # For the list view, we don't return grade data + # pylint: disable=unused-variable + for (submission_id, submission) in submissions.items(): + submission.pop("gradeData") + + return submissions + + +def fetch_submission(ora_location, submission_id): + """Fetch an individual submission, indexed first by ORA block location then Submission ID""" + return read_data_file("submissions.json")[ora_location].get(submission_id) + + +def fetch_response(submission_id): # pylint: disable=unused-argument + """Return a default response, the same for all submissions""" + return read_data_file("responses.json").get(submission_id) + + +def save_submission_update(ora_location, submission): + """Update a submission key with new data""" + update_data_file( + "submissions.json", [ora_location, submission["submissionUUID"]], submission + ) diff --git a/lms/djangoapps/ora_staff_grader/mock/views.py b/lms/djangoapps/ora_staff_grader/mock/views.py new file mode 100644 index 0000000000..ca60f555e3 --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/mock/views.py @@ -0,0 +1,135 @@ +""" +Mock views for ESG +""" + +# pylint: disable=arguments-differ +from rest_framework.response import Response +from rest_framework.generics import RetrieveAPIView +from rest_framework.views import APIView + +from lms.djangoapps.ora_staff_grader.mock.utils import ( + get_course_metadata, + get_ora_metadata, + get_submissions, + fetch_submission, + fetch_response, + save_submission_update, +) + +PARAM_ORA_LOCATION = "oraLocation" +PRAM_SUBMISSION_ID = "submissionUUID" + + +class InitializeView(RetrieveAPIView): + """Returns initial app state""" + + def get(self, request): + ora_location = request.query_params[PARAM_ORA_LOCATION] + + return Response( + { + "courseMetadata": get_course_metadata(ora_location), + "oraMetadata": get_ora_metadata(ora_location), + "submissions": get_submissions(ora_location), + } + ) + + +class SubmissionFetchView(RetrieveAPIView): + """Get a submission""" + + def get(self, request): + ora_location = request.query_params[PARAM_ORA_LOCATION] + submission_id = request.query_params[PRAM_SUBMISSION_ID] + + submission = fetch_submission(ora_location, submission_id) + response = fetch_response(submission_id) + + return Response( + { + "gradeData": submission["gradeData"], + "response": response, + "gradeStatus": submission["gradeStatus"], + "lockStatus": submission["lockStatus"], + } + ) + + +class SubmissionStatusFetchView(RetrieveAPIView): + """Get a submission status, leaving out the response""" + + def get(self, request): + ora_location = request.query_params[PARAM_ORA_LOCATION] + submission_id = request.query_params[PRAM_SUBMISSION_ID] + + submission = fetch_submission(ora_location, submission_id) + + return Response( + { + "gradeStatus": submission["gradeStatus"], + "lockStatus": submission["lockStatus"], + "gradeData": submission["gradeData"], + } + ) + + +class SubmissionLockView(APIView): + """Lock a submission for grading""" + + def post(self, request): + """Claim a submission lock, updating lock status""" + ora_location = request.query_params[PARAM_ORA_LOCATION] + submission_id = request.query_params[PRAM_SUBMISSION_ID] + + submission = fetch_submission(ora_location, submission_id) + submission["lockStatus"] = "in-progress" + + save_submission_update(ora_location, submission) + + return Response({"lockStatus": submission["lockStatus"]}) + + def delete(self, request): + """Delete a submission lock, updating lock status""" + ora_location = request.query_params[PARAM_ORA_LOCATION] + submission_id = request.query_params[PRAM_SUBMISSION_ID] + + submission = fetch_submission(ora_location, submission_id) + submission["lockStatus"] = "unlocked" + + save_submission_update(ora_location, submission) + + return Response({"lockStatus": submission["lockStatus"]}) + + +class UpdateGradeView(RetrieveAPIView): + """Submit a grade""" + + def update_grade_data(self, submission, grade_data): + """Mutate grade shape and add a mock score""" + submission["gradeData"] = grade_data + submission["gradeStatus"] = "graded" + submission["lockStatus"] = "unlocked" + submission["score"] = { + "pointsEarned": 70, + "pointsPossible": 100, + } + + def post(self, request): + """Save a grade update to the data store""" + ora_location = request.query_params[PARAM_ORA_LOCATION] + submission_id = request.query_params[PRAM_SUBMISSION_ID] + grade_data = request.data + + # this is static test data + grade_data["score"] = {"pointsEarned": 70, "pointsPossible": 100} + + submission = fetch_submission(ora_location, submission_id) + self.update_grade_data(submission, grade_data) + save_submission_update(ora_location, submission) + return Response( + { + "gradeStatus": submission["gradeStatus"], + "lockStatus": submission["lockStatus"], + "gradeData": grade_data, + } + ) diff --git a/lms/djangoapps/ora_staff_grader/ora_api.py b/lms/djangoapps/ora_staff_grader/ora_api.py new file mode 100644 index 0000000000..b245f06524 --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/ora_api.py @@ -0,0 +1,169 @@ +""" +Functions used for interacting with ORA + +All XBlock handlers are wrapped to return: +- Data, on success +- Exception, on failure + +Some XBlock handlers natively return error codes for errors. +These are caught by status code and raise XBlockInternalError by convention. + +Other handlers return status OK even for an error, but contain error info in the returned payload. +These are checked (usually by checking for a {"success":false} response) and raise errors, possibly with extra context. +""" +import json +from lms.djangoapps.ora_staff_grader.errors import ( + LockContestedError, + XBlockInternalError, +) + +from lms.djangoapps.ora_staff_grader.utils import call_xblock_json_handler + + +def get_submissions(request, usage_id): + """ + Get a list of submissions from the ORA's 'list_staff_workflows' XBlock.json_handler + """ + handler_name = "list_staff_workflows" + response = call_xblock_json_handler(request, usage_id, handler_name, {}) + + if response.status_code != 200: + raise XBlockInternalError(context={"handler": handler_name}) + + return json.loads(response.content) + + +def get_rubric_config(request, usage_id): + """ + Get rubric data from the ORA's 'get_rubric' XBlock.json_handler + """ + handler_name = "get_rubric" + data = {"target_rubric_block_id": usage_id} + response = call_xblock_json_handler(request, usage_id, handler_name, data) + + # Unhandled errors might not be JSON, catch before loading + if response.status_code != 200: + raise XBlockInternalError(context={"handler": handler_name}) + + response_data = json.loads(response.content) + + # Handled faillure still returns HTTP 200 but with 'success': False and supplied error message "msg" + if not response_data.get("success", False): + raise XBlockInternalError( + context={"handler": handler_name, "msg": response_data.get("msg", "")} + ) + + return response_data["rubric"] + + +def get_submission_info(request, usage_id, submission_uuid): + """ + Get submission content from ORA 'get_submission_info' XBlock.json_handler + """ + handler_name = "get_submission_info" + data = {"submission_uuid": submission_uuid} + response = call_xblock_json_handler(request, usage_id, handler_name, data) + + if response.status_code != 200: + raise XBlockInternalError(context={"handler": handler_name}) + + return json.loads(response.content) + + +def get_assessment_info(request, usage_id, submission_uuid): + """ + Get assessment data from ORA 'get_assessment_info' XBlock.json_handler + """ + handler_name = "get_assessment_info" + data = {"submission_uuid": submission_uuid} + response = call_xblock_json_handler(request, usage_id, handler_name, data) + + if response.status_code != 200: + raise XBlockInternalError(context={"handler": handler_name}) + + return json.loads(response.content) + + +def submit_grade(request, usage_id, grade_data): + """ + Submit a grade for an assessment. + + Returns: {'success': True/False, 'msg': err_msg} + """ + handler_name = "staff_assess" + response = call_xblock_json_handler(request, usage_id, handler_name, grade_data) + + # Unhandled errors might not be JSON, catch before loading + if response.status_code != 200: + raise XBlockInternalError(context={"handler": handler_name}) + + response_data = json.loads(response.content) + + # Handled faillure still returns HTTP 200 but with 'success': False and supplied error message 'msg' + if not response_data.get("success", False): + raise XBlockInternalError( + context={"handler": handler_name, "msg": response_data.get("msg", "")} + ) + + return response_data + + +def check_submission_lock(request, usage_id, submission_uuid): + """ + Look up lock info for the given submission by calling the ORA's 'check_submission_lock' XBlock.json_handler + """ + handler_name = "check_submission_lock" + data = {"submission_uuid": submission_uuid} + response = call_xblock_json_handler(request, usage_id, handler_name, data) + + # Unclear that there would every be an error (except network/auth) but good to catch here + if response.status_code != 200: + raise XBlockInternalError(context={"handler": handler_name}) + + return json.loads(response.content) + + +def claim_submission_lock(request, usage_id, submission_uuid): + """ + Attempt to claim a submission lock for grading. + + Returns: + - lockStatus (string) - One of ['not-locked', 'locked', 'in-progress'] + """ + handler_name = "claim_submission_lock" + body = {"submission_uuid": submission_uuid} + response = call_xblock_json_handler(request, usage_id, handler_name, body) + + # Lock contested returns a 403 + if response.status_code == 403: + raise LockContestedError() + + # Other errors should raise a blanket exception + if response.status_code != 200: + raise XBlockInternalError(context={"handler": handler_name}) + + return json.loads(response.content) + + +def delete_submission_lock(request, usage_id, submission_uuid): + """ + Attempt to claim a submission lock for grading. + + Returns: + - lockStatus (string) - One of ['not-locked', 'locked', 'in-progress'] + """ + handler_name = "delete_submission_lock" + body = {"submission_uuid": submission_uuid} + + # Return raw response to preserve HTTP status codes for failure states + response = call_xblock_json_handler(request, usage_id, handler_name, body) + + # Lock contested returns a 403 + if response.status_code == 403: + raise LockContestedError() + + # Other errors should raise a blanket exception + if response.status_code != 200: + raise XBlockInternalError(context={"handler": handler_name}) + + return json.loads(response.content) diff --git a/lms/djangoapps/ora_staff_grader/ora_staff_grader.postman_collection.json b/lms/djangoapps/ora_staff_grader/ora_staff_grader.postman_collection.json new file mode 100644 index 0000000000..ed3b64a126 --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/ora_staff_grader.postman_collection.json @@ -0,0 +1,3218 @@ +{ + "info": { + "_postman_id": "da8b1a76-d44c-4f0a-b546-c20a3116f285", + "name": "Enhanced Staff Grader", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "BFF", + "item": [ + { + "name": "Initialize", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": {} + }, + "request": { + "method": "GET", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/initialize?oraLocation={{block_id_encoded}}", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "initialize" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + } + ] + } + }, + "response": [ + { + "name": "Success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/initialize?oraLocation={{block_id_encoded}}", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "initialize" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Fri, 21 Jan 2022 17:34:49 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Vary", + "value": "Accept, Accept-Language, Origin, Cookie" + }, + { + "key": "Allow", + "value": "GET, HEAD, OPTIONS" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=6370.949000000001;desc=\"User CPU time\", TimerPanel_stime;dur=1318.842;desc=\"System CPU time\", TimerPanel_total;dur=7689.791000000001;desc=\"Total CPU time\", TimerPanel_total_time;dur=123276.58319473267;desc=\"Elapsed time\", SQLPanel_sql_time;dur=188.4617805480957;desc=\"SQL 30 queries\"" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Content-Length", + "value": "3528" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Fri, 04 Feb 2022 17:34:49 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"courseMetadata\": {\n \"title\": \"ORA Smoke Testing\",\n \"org\": \"DevX\",\n \"number\": \"ORA101\",\n \"courseId\": \"course-v1:DevX+ORA101+T12020\"\n },\n \"oraMetadata\": {\n \"name\": \"Open Response Assessment\",\n \"prompts\": [\n {\n \"description\": \"

Enter a text/files response.

\"\n }\n ],\n \"type\": \"individual\",\n \"textResponseConfig\": \"optional\",\n \"fileUploadResponseConfig\": \"required\",\n \"rubricConfig\": {\n \"feedbackPrompt\": \"(Optional) What aspects of this response stood out to you? What did it do well? How could it be improved?\\n\",\n \"criteria\": [\n {\n \"label\": \"Ideas\",\n \"prompt\": \"Determine if there is a unifying theme or main idea.\",\n \"feedback\": \"optional\",\n \"name\": \"Ideas\",\n \"orderNum\": 0,\n \"options\": [\n {\n \"label\": \"Poor\",\n \"points\": 0,\n \"explanation\": \"Difficult for the reader to discern the main idea. Too brief or too repetitive to establish or maintain a focus.\",\n \"name\": \"Poor\",\n \"orderNum\": 0\n },\n {\n \"label\": \"Fair\",\n \"points\": 3,\n \"explanation\": \"Presents a unifying theme or main idea, but may include minor tangents. Stays somewhat focused on topic and task.\",\n \"name\": \"Fair\",\n \"orderNum\": 1\n },\n {\n \"label\": \"Good\",\n \"points\": 5,\n \"explanation\": \"Presents a unifying theme or main idea without going off on tangents. Stays completely focused on topic and task.\",\n \"name\": \"Good\",\n \"orderNum\": 2\n }\n ]\n },\n {\n \"label\": \"Content\",\n \"prompt\": \"Assess the content of the submission\",\n \"feedback\": \"disabled\",\n \"name\": \"Content\",\n \"orderNum\": 1,\n \"options\": [\n {\n \"label\": \"Poor\",\n \"points\": 0,\n \"explanation\": \"Includes little information with few or no details or unrelated details. Unsuccessful in attempts to explore any facets of the topic.\",\n \"name\": \"Poor\",\n \"orderNum\": 0\n },\n {\n \"label\": \"Fair\",\n \"points\": 1,\n \"explanation\": \"Includes little information and few or no details. Explores only one or two facets of the topic.\",\n \"name\": \"Fair\",\n \"orderNum\": 1\n },\n {\n \"label\": \"Good\",\n \"points\": 3,\n \"explanation\": \"Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.) Explores some facets of the topic.\",\n \"name\": \"Good\",\n \"orderNum\": 2\n },\n {\n \"label\": \"Excellent\",\n \"points\": 5,\n \"explanation\": \"Includes in-depth information and exceptional supporting details that are fully developed. Explores all facets of the topic.\",\n \"name\": \"Excellent\",\n \"orderNum\": 3\n }\n ]\n }\n ]\n }\n },\n \"submissions\": {\n \"034442b6-2ae8-4b17-815f-0c2e08aaa7ab\": {\n \"submissionUUID\": \"034442b6-2ae8-4b17-815f-0c2e08aaa7ab\",\n \"username\": \"test1\",\n \"teamName\": null,\n \"dateSubmitted\": \"2020-12-09 21:02:29.493046+00:00\",\n \"dateGraded\": \"2020-12-09 21:06:04.235926+00:00\",\n \"gradedBy\": \"staff\",\n \"gradingStatus\": \"graded\",\n \"lockStatus\": \"unlocked\",\n \"score\": {\n \"pointsEarned\": 6,\n \"pointsPossible\": 10\n }\n },\n \"a80465d1-cf77-405d-9d6d-94a0f94ff13a\": {\n \"submissionUUID\": \"a80465d1-cf77-405d-9d6d-94a0f94ff13a\",\n \"username\": \"test2\",\n \"teamName\": null,\n \"dateSubmitted\": \"2020-12-09 21:09:46.286584+00:00\",\n \"dateGraded\": \"2020-12-09 21:13:53.798227+00:00\",\n \"gradedBy\": \"staff\",\n \"gradingStatus\": \"graded\",\n \"lockStatus\": \"in-progress\",\n \"score\": {\n \"pointsEarned\": 4,\n \"pointsPossible\": 10\n }\n },\n \"e273d6b6-77dc-4161-9efa-ad35ea0ca6af\": {\n \"submissionUUID\": \"e273d6b6-77dc-4161-9efa-ad35ea0ca6af\",\n \"username\": \"verified\",\n \"teamName\": null,\n \"dateSubmitted\": \"2021-03-04 21:01:26.295960+00:00\",\n \"dateGraded\": \"2021-10-27 14:49:16.236821+00:00\",\n \"gradedBy\": \"staff\",\n \"gradingStatus\": \"graded\",\n \"lockStatus\": \"unlocked\",\n \"score\": {\n \"pointsEarned\": 4,\n \"pointsPossible\": 10\n }\n },\n \"3e34fdba-befd-490c-a2bd-a8d2e4dae028\": {\n \"submissionUUID\": \"3e34fdba-befd-490c-a2bd-a8d2e4dae028\",\n \"username\": \"staff\",\n \"teamName\": null,\n \"dateSubmitted\": \"2021-05-19 16:42:36.078273+00:00\",\n \"dateGraded\": \"2021-10-27 14:49:45.967842+00:00\",\n \"gradedBy\": \"staff\",\n \"gradingStatus\": \"graded\",\n \"lockStatus\": \"unlocked\",\n \"score\": {\n \"pointsEarned\": 10,\n \"pointsPossible\": 10\n }\n }\n }\n}" + }, + { + "name": "Missing Param", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/initialize", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "initialize" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}", + "disabled": true + } + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 20 Dec 2021 23:01:04 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Vary", + "value": "Accept, Accept-Language, Origin, Cookie" + }, + { + "key": "Allow", + "value": "GET, HEAD, OPTIONS" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=19.137000000000626;desc=\"User CPU time\", TimerPanel_stime;dur=1.4929999999999666;desc=\"System CPU time\", TimerPanel_total;dur=20.630000000000592;desc=\"Total CPU time\", TimerPanel_total_time;dur=22.34625816345215;desc=\"Elapsed time\", SQLPanel_sql_time;dur=0;desc=\"SQL 0 queries\"" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Content-Length", + "value": "29" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Mon, 03 Jan 2022 23:01:04 GMT; Max-Age=1209600; Path=/" + } + ], + "cookie": [], + "body": "{\n \"error\": \"ERR_MISSING_PARAM\"\n}" + }, + { + "name": "Bad ORA location", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/initialize?oraLocation=foo", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "initialize" + ], + "query": [ + { + "key": "oraLocation", + "value": "foo" + } + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 20 Dec 2021 23:01:58 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Vary", + "value": "Accept, Accept-Language, Origin, Cookie" + }, + { + "key": "Allow", + "value": "GET, HEAD, OPTIONS" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=77.86000000000115;desc=\"User CPU time\", TimerPanel_stime;dur=12.359000000000009;desc=\"System CPU time\", TimerPanel_total;dur=90.21900000000116;desc=\"Total CPU time\", TimerPanel_total_time;dur=93.50705146789551;desc=\"Elapsed time\", SQLPanel_sql_time;dur=0;desc=\"SQL 0 queries\"" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Content-Length", + "value": "32" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Mon, 03 Jan 2022 23:01:58 GMT; Max-Age=1209600; Path=/" + } + ], + "cookie": [], + "body": "{\n \"error\": \"ERR_BAD_ORA_LOCATION\"\n}" + } + ] + }, + { + "name": "Fetch Submission", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/submission?oraLocation={{block_id_encoded}}&submissionUUID={{submission_id}}", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "submission" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + }, + { + "key": "submissionUUID", + "value": "{{submission_id}}" + } + ] + } + }, + "response": [ + { + "name": "Fetch Submission Success", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/submission?oraLocation={{block_id_encoded}}&submissionUUID={{submission_id}}", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "submission" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + }, + { + "key": "submissionUUID", + "value": "{{submission_id}}" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 15 Nov 2021 18:24:04 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Vary", + "value": "Accept, Accept-Language, Origin, Cookie" + }, + { + "key": "Allow", + "value": "GET, HEAD, OPTIONS" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=34.623000000001625;desc=\"User CPU time\", TimerPanel_stime;dur=3.391000000000144;desc=\"System CPU time\", TimerPanel_total;dur=38.01400000000177;desc=\"Total CPU time\", TimerPanel_total_time;dur=52.39248275756836;desc=\"Elapsed time\", SQLPanel_sql_time;dur=0;desc=\"SQL 0 queries\"" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Content-Length", + "value": "1553" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Mon, 29 Nov 2021 18:24:04 GMT; Max-Age=1209600; Path=/" + } + ], + "cookie": [], + "body": "{\n \"gradeData\": {\n \"score\": {\n \"pointsEarned\": 90,\n \"pointsPossible\": 100\n },\n \"overallFeedback\": \"was pretty good\",\n \"criteria\": [\n {\n \"name\": \"firstCriterion\",\n \"feedback\": \"did alright\",\n \"selectedOption\": \"good\"\n }\n ]\n },\n \"response\": {\n \"text\": [\n \"

Response Title

Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna. Phasellus porttitor vel magna et auctor. Nulla porttitor convallis aliquam. Donec cursus, ipsum ut egestas bibendum, purus metus dignissim est, ac condimentum leo felis eget diam. In magna mi, tincidunt id sapien id, fermentum vestibulum quam. Quisque et dui sed urna convallis rutrum pellentesque quis sapien. Cras non lectus velit. Praesent semper eros id risus mollis, quis interdum quam imperdiet. Sed nec vulputate tortor, at tristique tortor.
\"\n ],\n \"files\": []\n },\n \"gradeStatus\": \"graded\",\n \"lockStatus\": \"unlocked\"\n}" + }, + { + "name": "Success with files", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/submission?oraLocation={{block_id_encoded}}&submissionUUID={{submission_id}}", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "submission" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + }, + { + "key": "submissionUUID", + "value": "{{submission_id}}" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Tue, 30 Nov 2021 22:22:28 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Vary", + "value": "Accept, Accept-Language, Origin, Cookie" + }, + { + "key": "Allow", + "value": "GET, HEAD, OPTIONS" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=1151.775999999998;desc=\"User CPU time\", TimerPanel_stime;dur=349.742;desc=\"System CPU time\", TimerPanel_total;dur=1501.517999999998;desc=\"Total CPU time\", TimerPanel_total_time;dur=2281.3472747802734;desc=\"Elapsed time\", SQLPanel_sql_time;dur=26.540517807006836;desc=\"SQL 22 queries\"" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Content-Length", + "value": "376" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Tue, 14 Dec 2021 22:22:28 GMT; Max-Age=1209600; Path=/" + } + ], + "cookie": [], + "body": "{\n \"gradeData\": {},\n \"gradeStatus\": \"ungraded\",\n \"lockStatus\": \"unlocked\",\n \"response\": {\n \"files\": [\n {\n \"downloadUrl\": \"http://localhost:18000/media/submissions_attachments/c84e8e5335234f279676e178044e191c_course-v1%3ADevX%2BORA101%2BT12020_block-v1%3ADevX%2BORA101%2BT12020%2Btype%40openassessment%2Bblock%408c235f76c46948ec80c9d59bf5686d69\",\n \"description\": \"\",\n \"name\": \"my-image.png\",\n \"size\": 3141592\n }\n ],\n \"text\": [\n \"This is a response with an attached file, 'my-image'\"\n ]\n }\n}" + } + ] + }, + { + "name": "Fetch Submission Status", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/submission/status?oraLocation={{block_id_encoded}}&submissionUUID={{submission_id}}", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "submission", + "status" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + }, + { + "key": "submissionUUID", + "value": "{{submission_id}}" + } + ] + } + }, + "response": [ + { + "name": "Fetch Submission Status (Ungraded, Unlocked)", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/submissionStatus?oraLocation={{block_id_encoded}}&submissionUUID=SUBMISSION_ID-0", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "submissionStatus" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + }, + { + "key": "submissionUUID", + "value": "SUBMISSION_ID-0" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Fri, 24 Sep 2021 18:57:03 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Vary", + "value": "Accept, Accept-Language, Origin, Cookie" + }, + { + "key": "Allow", + "value": "GET, HEAD, OPTIONS" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=28.50000000000108;desc=\"User CPU time\", TimerPanel_stime;dur=5.303000000000058;desc=\"System CPU time\", TimerPanel_total;dur=33.803000000001134;desc=\"Total CPU time\", TimerPanel_total_time;dur=45.25923728942871;desc=\"Elapsed time\", SQLPanel_sql_time;dur=0;desc=\"SQL 0 queries\"" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Content-Length", + "value": "67" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Fri, 08 Oct 2021 18:57:03 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"gradeStatus\": \"ungraded\",\n \"lockStatus\": \"unlocked\",\n \"gradeData\": null\n}" + }, + { + "name": "Fetch Submission Status Success (Graded, Locked)", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/submissionStatus?oraLocation={{block_id_encoded}}&submissionUUID=SUBMISSION_ID-1", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "submissionStatus" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + }, + { + "key": "submissionUUID", + "value": "SUBMISSION_ID-1" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Fri, 24 Sep 2021 18:55:44 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Vary", + "value": "Accept, Accept-Language, Origin, Cookie" + }, + { + "key": "Allow", + "value": "GET, HEAD, OPTIONS" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=28.64000000000111;desc=\"User CPU time\", TimerPanel_stime;dur=5.395999999999734;desc=\"System CPU time\", TimerPanel_total;dur=34.03600000000084;desc=\"Total CPU time\", TimerPanel_total_time;dur=55.06443977355957;desc=\"Elapsed time\", SQLPanel_sql_time;dur=0;desc=\"SQL 0 queries\"" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Content-Length", + "value": "226" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Fri, 08 Oct 2021 18:55:44 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"gradeStatus\": \"graded\",\n \"lockStatus\": \"locked\",\n \"gradeData\": {\n \"score\": {\n \"pointsEarned\": 70,\n \"pointsPossible\": 100\n },\n \"overallFeedback\": \"was okay\",\n \"criteria\": [\n {\n \"name\": \"firstCriterion\",\n \"feedback\": \"did alright\",\n \"selectedOption\": \"good\"\n }\n ]\n }\n}" + }, + { + "name": "Fetch Submission Status (Graded, Unlocked)", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/submissionStatus?oraLocation={{block_id_encoded}}&submissionUUID=SUBMISSION_ID-2", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "submissionStatus" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + }, + { + "key": "submissionUUID", + "value": "SUBMISSION_ID-2" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Fri, 24 Sep 2021 18:57:56 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Vary", + "value": "Accept, Accept-Language, Origin, Cookie" + }, + { + "key": "Allow", + "value": "GET, HEAD, OPTIONS" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=34.06099999999945;desc=\"User CPU time\", TimerPanel_stime;dur=4.2439999999999145;desc=\"System CPU time\", TimerPanel_total;dur=38.30499999999937;desc=\"Total CPU time\", TimerPanel_total_time;dur=60.15753746032715;desc=\"Elapsed time\", SQLPanel_sql_time;dur=0;desc=\"SQL 0 queries\"" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Content-Length", + "value": "228" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Fri, 08 Oct 2021 18:57:56 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"gradeStatus\": \"graded\",\n \"lockStatus\": \"unlocked\",\n \"gradeData\": {\n \"score\": {\n \"pointsEarned\": 80,\n \"pointsPossible\": 100\n },\n \"overallFeedback\": \"was okay\",\n \"criteria\": [\n {\n \"name\": \"firstCriterion\",\n \"feedback\": \"did alright\",\n \"selectedOption\": \"good\"\n }\n ]\n }\n}" + }, + { + "name": "Fetch Submission Status (Ungraded, Locked)", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/submissionStatus?oraLocation={{block_id_encoded}}&submissionUUID=SUBMISSION_ID-3", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "submissionStatus" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + }, + { + "key": "submissionUUID", + "value": "SUBMISSION_ID-3" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Fri, 24 Sep 2021 18:58:11 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Vary", + "value": "Accept, Accept-Language, Origin, Cookie" + }, + { + "key": "Allow", + "value": "GET, HEAD, OPTIONS" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=41.81600000000074;desc=\"User CPU time\", TimerPanel_stime;dur=3.610000000000113;desc=\"System CPU time\", TimerPanel_total;dur=45.426000000000855;desc=\"Total CPU time\", TimerPanel_total_time;dur=56.12611770629883;desc=\"Elapsed time\", SQLPanel_sql_time;dur=0;desc=\"SQL 0 queries\"" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Content-Length", + "value": "70" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Fri, 08 Oct 2021 18:58:11 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"gradeStatus\": \"ungraded\",\n \"lockStatus\": \"in-progress\",\n \"gradeData\": null\n}" + } + ] + }, + { + "name": "Lock Submission", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/submission/lock?oraLocation={{block_id_encoded}}&submissionUUID={{submission_id}}", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "submission", + "lock" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + }, + { + "key": "submissionUUID", + "value": "{{submission_id}}" + } + ] + } + }, + "response": [ + { + "name": "Lock Submission Success", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/submission/lock?oraLocation={{block_id_encoded}}&submissionUUID={{submission_id}}", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "submission", + "lock" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + }, + { + "key": "submissionUUID", + "value": "{{submission_id}}" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 15 Nov 2021 19:31:37 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Vary", + "value": "Accept, Accept-Language, Origin, Cookie" + }, + { + "key": "Allow", + "value": "POST, DELETE, OPTIONS" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=45.421000000001044;desc=\"User CPU time\", TimerPanel_stime;dur=5.9869999999997425;desc=\"System CPU time\", TimerPanel_total;dur=51.40800000000078;desc=\"Total CPU time\", TimerPanel_total_time;dur=103.04784774780273;desc=\"Elapsed time\", SQLPanel_sql_time;dur=0;desc=\"SQL 0 queries\"" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Content-Length", + "value": "28" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Mon, 29 Nov 2021 19:31:37 GMT; Max-Age=1209600; Path=/" + } + ], + "cookie": [], + "body": "{\n \"lockStatus\": \"in-progress\"\n}" + } + ] + }, + { + "name": "Unlock Submission", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/submission/lock?oraLocation={{block_id_encoded}}&submissionUUID={{submission_id}}", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "submission", + "lock" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + }, + { + "key": "submissionUUID", + "value": "{{submission_id}}" + } + ] + } + }, + "response": [ + { + "name": "Unlock Submission Success", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/submission/lock?oraLocation={{block_id_encoded}}&submissionUUID={{submission_id}}", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "submission", + "lock" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + }, + { + "key": "submissionUUID", + "value": "{{submission_id}}" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 15 Nov 2021 19:29:46 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Vary", + "value": "Accept, Accept-Language, Origin, Cookie" + }, + { + "key": "Allow", + "value": "POST, DELETE, OPTIONS" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=62.70800000000065;desc=\"User CPU time\", TimerPanel_stime;dur=8.10999999999984;desc=\"System CPU time\", TimerPanel_total;dur=70.8180000000005;desc=\"Total CPU time\", TimerPanel_total_time;dur=106.28485679626465;desc=\"Elapsed time\", SQLPanel_sql_time;dur=0;desc=\"SQL 0 queries\"" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Content-Length", + "value": "25" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Mon, 29 Nov 2021 19:29:46 GMT; Max-Age=1209600; Path=/" + } + ], + "cookie": [], + "body": "{\n \"lockStatus\": \"unlocked\"\n}" + } + ] + }, + { + "name": "Update Grade Data", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"overallFeedback\": \"was pretty good\",\n \"criteria\": [\n {\n \"name\": \"Ideas\",\n \"feedback\": \"did alright\",\n \"selectedOption\": \"Fair\"\n },\n {\n \"name\": \"Content\",\n \"selectedOption\": \"Excellent\"\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/submission/grade?oraLocation={{block_id_encoded}}&submissionUUID={{submission_id}}", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "submission", + "grade" + ], + "query": [ + { + "key": "oraLocation", + "value": "{{block_id_encoded}}" + }, + { + "key": "submissionUUID", + "value": "{{submission_id}}" + } + ] + } + }, + "response": [ + { + "name": "Update Grade Data Success", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"score\": {\n \"pointsEarned\": 70,\n \"pointsPossible\": 100\n },\n \"overallFeedback\": \"was okay\",\n \"criteria\": [\n {\n \"name\": \"firstCriterion\",\n \"feedback\": \"did alright\",\n \"selectedOption\": \"good\"\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/api/ora_staff_grader{{mock}}/updateGrade?submissionUUID={{submission_id}}", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "api", + "ora_staff_grader{{mock}}", + "updateGrade" + ], + "query": [ + { + "key": "submissionUUID", + "value": "{{submission_id}}" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Fri, 24 Sep 2021 20:45:55 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Vary", + "value": "Accept, Accept-Language, Origin, Cookie" + }, + { + "key": "Allow", + "value": "GET, POST, HEAD, OPTIONS" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=162.01099999999968;desc=\"User CPU time\", TimerPanel_stime;dur=45.31599999999969;desc=\"System CPU time\", TimerPanel_total;dur=207.32699999999937;desc=\"Total CPU time\", TimerPanel_total_time;dur=2530.384063720703;desc=\"Elapsed time\", SQLPanel_sql_time;dur=0;desc=\"SQL 0 queries\"" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Content-Length", + "value": "181" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Fri, 08 Oct 2021 20:45:55 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"gradeData\": {\n \"score\": {\n \"pointsEarned\": 70,\n \"pointsPossible\": 100\n },\n \"overallFeedback\": \"was okay\",\n \"criteria\": [\n {\n \"name\": \"firstCriterion\",\n \"feedback\": \"did alright\",\n \"selectedOption\": \"good\"\n }\n ]\n }\n}" + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "pm.environment.set(\"course_id_encoded\", encodeURIComponent(pm.environment.get(\"course_id\")));", + "pm.environment.set(\"block_id_encoded\", encodeURIComponent(pm.environment.get(\"block_id\")));" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + }, + { + "name": "ORA XBlock Handlers", + "item": [ + { + "name": "Submission Locking API", + "item": [ + { + "name": "check_submission_lock", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"{{submission_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/check_submission_lock", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "check_submission_lock" + ] + } + }, + "response": [ + { + "name": "Submission not locked", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"{{submission_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/check_submission_lock", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "check_submission_lock" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Tue, 21 Sep 2021 21:53:41 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "2" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=957.209000000006;desc=\"User CPU time\", TimerPanel_stime;dur=215.66100000000432;desc=\"System CPU time\", TimerPanel_total;dur=1172.8700000000104;desc=\"Total CPU time\", TimerPanel_total_time;dur=2320.283889770508;desc=\"Elapsed time\", SQLPanel_sql_time;dur=29.979705810546875;desc=\"SQL 14 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Tue, 05 Oct 2021 21:53:41 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"lock_status\": \"unlocked\"\n}" + }, + { + "name": "Submission locked", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"{{submission_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/check_submission_lock", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "check_submission_lock" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Tue, 21 Sep 2021 21:54:36 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "152" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=648.6289999999997;desc=\"User CPU time\", TimerPanel_stime;dur=139.0349999999998;desc=\"System CPU time\", TimerPanel_total;dur=787.6639999999995;desc=\"Total CPU time\", TimerPanel_total_time;dur=1444.2543983459473;desc=\"Elapsed time\", SQLPanel_sql_time;dur=14.47296142578125;desc=\"SQL 11 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Tue, 05 Oct 2021 21:54:36 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"submission_uuid\": \"e34ef789-a4b1-48cf-b1bc-b3edacfd4eb2\",\n \"owner_id\": \"10ab03f1b75b4f9d9ab13a1fd1dccca1\",\n \"created_at\": \"2021-09-21T21:54:09.901221Z\",\n \"lock_status\": \"in-progress\"\n}" + } + ] + }, + { + "name": "claim_submission_lock", + "request": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"{{submission_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/claim_submission_lock", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "claim_submission_lock" + ] + } + }, + "response": [ + { + "name": "Claimed submission lock", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"{{submission_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/claim_submission_lock", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "claim_submission_lock" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Tue, 21 Sep 2021 21:54:10 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "152" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=629.8699999999968;desc=\"User CPU time\", TimerPanel_stime;dur=141.32800000000145;desc=\"System CPU time\", TimerPanel_total;dur=771.1979999999983;desc=\"Total CPU time\", TimerPanel_total_time;dur=1322.0055103302002;desc=\"Elapsed time\", SQLPanel_sql_time;dur=19.69456672668457;desc=\"SQL 15 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Tue, 05 Oct 2021 21:54:10 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"submission_uuid\": \"b086331a-5c50-428a-8348-5a85e5029299\",\n \"owner_id\": \"10ab03f1b75b4f9d9ab13a1fd1dccca1\",\n \"created_at\": \"2021-12-16T22:11:27.986161Z\",\n \"lock_status\": \"in-progress\"\n}" + }, + { + "name": "Submission already locked", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"{{submission_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/claim_submission_lock", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "claim_submission_lock" + ] + } + }, + "status": "Forbidden", + "code": 403, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Tue, 21 Sep 2021 21:54:52 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "38" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=658.7760000000031;desc=\"User CPU time\", TimerPanel_stime;dur=156.65099999999654;desc=\"System CPU time\", TimerPanel_total;dur=815.4269999999997;desc=\"Total CPU time\", TimerPanel_total_time;dur=1530.5724143981934;desc=\"Elapsed time\", SQLPanel_sql_time;dur=23.58722686767578;desc=\"SQL 11 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Tue, 05 Oct 2021 21:54:52 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"error\": \"ERR_LOCK_CONTESTED\"\n}" + } + ] + }, + { + "name": "delete_submission_lock", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"{{submission_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/delete_submission_lock", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "delete_submission_lock" + ] + } + }, + "response": [ + { + "name": "Deleted submission lock", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"{{submission_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/delete_submission_lock", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "delete_submission_lock" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Tue, 21 Sep 2021 21:55:44 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "2" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=1039.5369999999957;desc=\"User CPU time\", TimerPanel_stime;dur=334.534000000005;desc=\"System CPU time\", TimerPanel_total;dur=1374.0710000000008;desc=\"Total CPU time\", TimerPanel_total_time;dur=2091.7656421661377;desc=\"Elapsed time\", SQLPanel_sql_time;dur=87.89372444152832;desc=\"SQL 12 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Tue, 05 Oct 2021 21:55:44 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"lock_status\": \"unlocked\"\n}" + }, + { + "name": "Can't delete submission lock", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"{{submission_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/delete_submission_lock", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "delete_submission_lock" + ] + } + }, + "status": "Forbidden", + "code": 403, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Tue, 21 Sep 2021 21:54:52 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "38" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=658.7760000000031;desc=\"User CPU time\", TimerPanel_stime;dur=156.65099999999654;desc=\"System CPU time\", TimerPanel_total;dur=815.4269999999997;desc=\"Total CPU time\", TimerPanel_total_time;dur=1530.5724143981934;desc=\"Elapsed time\", SQLPanel_sql_time;dur=23.58722686767578;desc=\"SQL 11 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Tue, 05 Oct 2021 21:54:52 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"error\": \"ERR_LOCK_CONTESTED\"\n}" + } + ] + } + ] + }, + { + "name": "List Submissions", + "request": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{ }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/list_staff_workflows", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "list_staff_workflows" + ] + } + }, + "response": [ + { + "name": "List Submissions Success", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{ }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/list_staff_workflows", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "list_staff_workflows" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 10 Jan 2022 20:33:25 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "989" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=10699.066999999997;desc=\"User CPU time\", TimerPanel_stime;dur=4121.165999999999;desc=\"System CPU time\", TimerPanel_total;dur=14820.232999999997;desc=\"Total CPU time\", TimerPanel_total_time;dur=30413.792371749878;desc=\"Elapsed time\", SQLPanel_sql_time;dur=234.12847518920898;desc=\"SQL 21 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Mon, 24 Jan 2022 20:33:25 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"95c8cc70-2ef6-4846-99e0-bae5f934b2ec\": {\n \"submissionUuid\": \"95c8cc70-2ef6-4846-99e0-bae5f934b2ec\",\n \"dateSubmitted\": \"2021-06-02 16:19:56.255097+00:00\",\n \"dateGraded\": \"2021-06-02 16:20:58.562861+00:00\",\n \"gradingStatus\": \"graded\",\n \"lockStatus\": \"unlocked\",\n \"gradedBy\": \"staff\",\n \"username\": \"staff\",\n \"score\": {\n \"pointsEarned\": 10,\n \"pointsPossible\": 10\n }\n },\n \"31014c5f-6971-4052-8fac-ec9c4e26000a\": {\n \"submissionUuid\": \"31014c5f-6971-4052-8fac-ec9c4e26000a\",\n \"dateSubmitted\": \"2021-11-24 16:55:12.170170+00:00\",\n \"dateGraded\": \"None\",\n \"gradingStatus\": \"ungraded\",\n \"lockStatus\": \"unlocked\",\n \"gradedBy\": \"staff\",\n \"username\": \"audit\",\n \"score\": {}\n },\n \"b086331a-5c50-428a-8348-5a85e5029299\": {\n \"submissionUuid\": \"b086331a-5c50-428a-8348-5a85e5029299\",\n \"dateSubmitted\": \"2021-11-30 21:55:06.784024+00:00\",\n \"dateGraded\": \"2021-11-30 22:28:38.333610+00:00\",\n \"gradingStatus\": \"graded\",\n \"lockStatus\": \"unlocked\",\n \"gradedBy\": \"staff\",\n \"username\": \"verified\",\n \"score\": {\n \"pointsEarned\": 8,\n \"pointsPossible\": 10\n }\n }\n}" + }, + { + "name": "List Team Submissions (Not Supported)", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{ }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/list_staff_workflows", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "list_staff_workflows" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Thu, 13 Jan 2022 22:09:14 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "53" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=608.826999999998;desc=\"User CPU time\", TimerPanel_stime;dur=120.71200000000104;desc=\"System CPU time\", TimerPanel_total;dur=729.538999999999;desc=\"Total CPU time\", TimerPanel_total_time;dur=1232.0339679718018;desc=\"Elapsed time\", SQLPanel_sql_time;dur=14.171123504638672;desc=\"SQL 14 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Thu, 27 Jan 2022 22:09:14 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + }, + { + "key": "Set-Cookie", + "value": "lms_sessionid=1|uq3uodbpk8mj7x0sxgjejn8bveuhclsu|hDb19rkUM4wn|IjM4ZWEyMzQ1NTAwOWViYjgxZDA3M2M3MTE0NGI2MDEzY2JmYTAyZmUxZjU3ZmNkZTk4NTMzODc1YjMwZjRiOWMi:1n88HS:JFQ9ZvQoE5zNfD9X47eFuNe3CYc; expires=Thu, 10 Feb 2022 22:09:14 GMT; HttpOnly; Max-Age=2419200; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Team Submissions not currently supported\"\n}" + } + ] + }, + { + "name": "Get Rubric (No Longer Used for ESG)", + "request": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"target_rubric_block_id\": \"{{block_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/get_rubric", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "get_rubric" + ] + } + }, + "response": [ + { + "name": "Get Rubric Success", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"target_rubric_block_id\": \"{{block_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/get_rubric", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "get_rubric" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Thu, 06 Jan 2022 22:02:43 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "1971" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=2451.288;desc=\"User CPU time\", TimerPanel_stime;dur=420.0790000000003;desc=\"System CPU time\", TimerPanel_total;dur=2871.367;desc=\"Total CPU time\", TimerPanel_total_time;dur=3969.7911739349365;desc=\"Elapsed time\", SQLPanel_sql_time;dur=21.48580551147461;desc=\"SQL 15 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Thu, 20 Jan 2022 22:02:43 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"success\": true,\n \"rubric\": {\n \"criteria\": [\n {\n \"label\": \"Ideas\",\n \"prompt\": \"Determine if there is a unifying theme or main idea.\",\n \"feedback\": \"optional\",\n \"options\": [\n {\n \"label\": \"Poor\",\n \"points\": 0,\n \"explanation\": \"Difficult for the reader to discern the main idea. Too brief or too repetitive to establish or maintain a focus.\",\n \"name\": \"Poor\",\n \"order_num\": 0\n },\n {\n \"label\": \"Fair\",\n \"points\": 3,\n \"explanation\": \"Presents a unifying theme or main idea, but may include minor tangents. Stays somewhat focused on topic and task.\",\n \"name\": \"Fair\",\n \"order_num\": 1\n },\n {\n \"label\": \"Good\",\n \"points\": 5,\n \"explanation\": \"Presents a unifying theme or main idea without going off on tangents. Stays completely focused on topic and task.\",\n \"name\": \"Good\",\n \"order_num\": 2\n }\n ],\n \"name\": \"Ideas\",\n \"order_num\": 0\n },\n {\n \"label\": \"Content\",\n \"prompt\": \"Assess the content of the submission\",\n \"feedback\": \"disabled\",\n \"options\": [\n {\n \"label\": \"Poor\",\n \"points\": 0,\n \"explanation\": \"Includes little information with few or no details or unrelated details. Unsuccessful in attempts to explore any facets of the topic.\",\n \"name\": \"Poor\",\n \"order_num\": 0\n },\n {\n \"label\": \"Fair\",\n \"points\": 1,\n \"explanation\": \"Includes little information and few or no details. Explores only one or two facets of the topic.\",\n \"name\": \"Fair\",\n \"order_num\": 1\n },\n {\n \"label\": \"Good\",\n \"points\": 3,\n \"explanation\": \"Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.) Explores some facets of the topic.\",\n \"name\": \"Good\",\n \"order_num\": 2\n },\n {\n \"label\": \"Excellent\",\n \"points\": 5,\n \"explanation\": \"Includes in-depth information and exceptional supporting details that are fully developed. Explores all facets of the topic.\",\n \"name\": \"Excellent\",\n \"order_num\": 3\n }\n ],\n \"name\": \"Content\",\n \"order_num\": 1\n }\n ],\n \"feedback_prompt\": \"(Optional) What aspects of this response stood out to you? What did it do well? How could it be improved?\\n\",\n \"feedback_default_text\": \"I think that this response...\\n\"\n }\n}" + }, + { + "name": "Get Rubric Invalid Target Block", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"target_rubric_block_id\": \"bad-block-id\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/get_rubric", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "get_rubric" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 10 Jan 2022 20:24:43 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "46" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=326.18300000000033;desc=\"User CPU time\", TimerPanel_stime;dur=90.84300000000044;desc=\"System CPU time\", TimerPanel_total;dur=417.02600000000075;desc=\"Total CPU time\", TimerPanel_total_time;dur=706.4623832702637;desc=\"Elapsed time\", SQLPanel_sql_time;dur=11.38925552368164;desc=\"SQL 10 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Mon, 24 Jan 2022 20:24:43 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"success\": false,\n \"msg\": \"Invalid block id.\"\n}" + } + ] + }, + { + "name": "Get Submission", + "request": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"{{submission_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/get_submission_info", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "get_submission_info" + ] + } + }, + "response": [ + { + "name": "Get Submission Success", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"{{submission_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/get_submission_info", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "get_submission_info" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 10 Jan 2022 22:42:27 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "319" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=847.9209999999995;desc=\"User CPU time\", TimerPanel_stime;dur=191.6739999999999;desc=\"System CPU time\", TimerPanel_total;dur=1039.5949999999993;desc=\"Total CPU time\", TimerPanel_total_time;dur=2411.195755004883;desc=\"Elapsed time\", SQLPanel_sql_time;dur=31.938791275024414;desc=\"SQL 13 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Mon, 24 Jan 2022 22:42:27 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"files\": [\n {\n \"download_url\": \"/media/submissions_attachments/c84e8e5335234f279676e178044e191c_course-v1%3ADevX%2BORA101%2BT12020_block-v1%3ADevX%2BORA101%2BT12020%2Btype%40openassessment%2Bblock%408c235f76c46948ec80c9d59bf5686d69\",\n \"description\": \"\",\n \"name\": \"\",\n \"size\": 0\n }\n ],\n \"text\": [\n \"ajp oijng;ou 4n,.mvn ap890345 y\"\n ]\n}" + }, + { + "name": "Get Submission Not Found", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"{{missing_submission_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/get_submission_info", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "get_submission_info" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 10 Jan 2022 22:46:28 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "51" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=1475.6300000000024;desc=\"User CPU time\", TimerPanel_stime;dur=462.1140000000006;desc=\"System CPU time\", TimerPanel_total;dur=1937.7440000000029;desc=\"Total CPU time\", TimerPanel_total_time;dur=4415.6999588012695;desc=\"Elapsed time\", SQLPanel_sql_time;dur=50.32706260681152;desc=\"SQL 12 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Mon, 24 Jan 2022 22:46:28 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"error\": \"No submission matching uuid {{missing_submission_id}}\"\n}" + }, + { + "name": "Get Submission Bad Submission ID", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"bad submission ID\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/get_submission_info", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "get_submission_info" + ] + } + }, + "status": "Internal Server Error", + "code": 500, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 10 Jan 2022 22:46:28 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "51" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=1475.6300000000024;desc=\"User CPU time\", TimerPanel_stime;dur=462.1140000000006;desc=\"System CPU time\", TimerPanel_total;dur=1937.7440000000029;desc=\"Total CPU time\", TimerPanel_total_time;dur=4415.6999588012695;desc=\"Elapsed time\", SQLPanel_sql_time;dur=50.32706260681152;desc=\"SQL 12 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Mon, 24 Jan 2022 22:46:28 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Internal error getting submission info\"\n}" + } + ] + }, + { + "name": "Get Assessment", + "request": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"{{submission_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/get_assessment_info", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "get_assessment_info" + ] + } + }, + "response": [ + { + "name": "Get Assessment Success", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"{{submission_id}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/get_assessment_info", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "get_assessment_info" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Tue, 11 Jan 2022 16:32:49 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "247" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=1716.2579999999962;desc=\"User CPU time\", TimerPanel_stime;dur=64.76999999999578;desc=\"System CPU time\", TimerPanel_total;dur=1781.027999999992;desc=\"Total CPU time\", TimerPanel_total_time;dur=3267.075777053833;desc=\"Elapsed time\", SQLPanel_sql_time;dur=105.37838935852051;desc=\"SQL 22 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Tue, 25 Jan 2022 16:32:49 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"feedback\": \"was pretty good\",\n \"score\": {\n \"pointsEarned\": 8,\n \"pointsPossible\": 10\n },\n \"criteria\": [\n {\n \"name\": \"Ideas\",\n \"option\": \"Fair\",\n \"points\": 3,\n \"feedback\": \"did alright\"\n },\n {\n \"name\": \"Content\",\n \"option\": \"Excellent\",\n \"points\": 5,\n \"feedback\": \"\"\n }\n ]\n}" + }, + { + "name": "Get Assessment Bad Submission", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"submission_uuid\": \"bad_submission_uuid\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/get_assessment_info", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "get_assessment_info" + ] + } + }, + "status": "Internal Server Error", + "code": 500, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Tue, 11 Jan 2022 16:33:49 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "51" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=1563.7589999999905;desc=\"User CPU time\", TimerPanel_stime;dur=623.6419999999966;desc=\"System CPU time\", TimerPanel_total;dur=2187.400999999987;desc=\"Total CPU time\", TimerPanel_total_time;dur=5517.813205718994;desc=\"Elapsed time\", SQLPanel_sql_time;dur=136.62052154541016;desc=\"SQL 12 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Tue, 25 Jan 2022 16:33:49 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Internal error getting submission info\"\n}" + } + ] + }, + { + "name": "Submit Grade", + "request": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"options_selected\": {\n \"Ideas\": \"Good\",\n \"Content\": \"Good\"\n },\n \"criterion_feedback\": {\n \"Ideas\": \"did alright\"\n },\n \"overall_feedback\": \"was okay\",\n \"submission_uuid\": \"{{submission_id}}\",\n \"assess_type\": \"full-grade\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/staff_assess", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "staff_assess" + ] + } + }, + "response": [ + { + "name": "Submit Grade Failure (Handled)", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"options_selected\": {\n \"invalidCriterion\": \"invalidSelection\"\n },\n \"criterion_feedback\": {\n \"invalidCriterion\": \"invalid feedback\"\n },\n \"overall_feedback\": \"invalid\",\n \"submission_uuid\": \"{{submission_uuid}}\",\n \"assess_type\": \"full-grade\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/staff_assess", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "staff_assess" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Tue, 11 Jan 2022 16:39:29 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "74" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=1591.8819999999982;desc=\"User CPU time\", TimerPanel_stime;dur=123.18400000000196;desc=\"System CPU time\", TimerPanel_total;dur=1715.0660000000003;desc=\"Total CPU time\", TimerPanel_total_time;dur=3924.5645999908447;desc=\"Elapsed time\", SQLPanel_sql_time;dur=160.31265258789062;desc=\"SQL 20 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Tue, 25 Jan 2022 16:39:29 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"success\": false,\n \"msg\": \"Your staff assessment could not be submitted.\"\n}" + }, + { + "name": "Submit Team Grade Failure (Handled)", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"options_selected\": {\n \"invalidCriterion\": \"invalidSelection\"\n },\n \"criterion_feedback\": {\n \"invalidCriterion\": \"invalid feedback\"\n },\n \"overall_feedback\": \"invalid\",\n \"submission_uuid\": \"{{submission_uuid}}\",\n \"assess_type\": \"full-grade\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/staff_assess", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "staff_assess" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Tue, 11 Jan 2022 16:39:29 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "74" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=1591.8819999999982;desc=\"User CPU time\", TimerPanel_stime;dur=123.18400000000196;desc=\"System CPU time\", TimerPanel_total;dur=1715.0660000000003;desc=\"Total CPU time\", TimerPanel_total_time;dur=3924.5645999908447;desc=\"Elapsed time\", SQLPanel_sql_time;dur=160.31265258789062;desc=\"SQL 20 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Tue, 25 Jan 2022 16:39:29 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"success\": false,\n \"msg\": \"Your team assessment could not be submitted.\"\n}" + }, + { + "name": "Submit Grade Failure (Unhandled)", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"options_selected\": {\n \"Ideas\": \"Good\",\n \"Content\": \"Good\"\n },\n \"criterion_feedback\": {\n \"Ideas\": \"did alright\"\n },\n \"overall_feedback\": \"was okay\",\n \"submission_uuid\": \"NOT_A_REAL_SUBMISSION_UUID\",\n \"assess_type\": \"full-grade\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/staff_assess", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "staff_assess" + ] + } + }, + "status": "Internal Server Error", + "code": 500, + "_postman_previewlanguage": "html", + "header": [ + { + "key": "Date", + "value": "Tue, 11 Jan 2022 17:03:44 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "text/html" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=4136.346000000003;desc=\"User CPU time\", TimerPanel_stime;dur=497.3269999999985;desc=\"System CPU time\", TimerPanel_total;dur=4633.673000000002;desc=\"Total CPU time\", TimerPanel_total_time;dur=10006.266593933105;desc=\"Elapsed time\", SQLPanel_sql_time;dur=229.7189235687256;desc=\"SQL 34 queries\"" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Content-Length", + "value": "478345" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Tue, 25 Jan 2022 17:03:44 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "\n\n \n \n \n AssessmentWorkflowNotFoundError\n at /courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess\n \n \n \n \n
\n

AssessmentWorkflowNotFoundError\n at /courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess

\n
No assessment workflow matching submission_uuid {{submission_uuid}}
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Request Method:POST
Request URL:http://localhost:18000/courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess
Django Version:3.2.11
Exception Type:AssessmentWorkflowNotFoundError
Exception Value:\n
No assessment workflow matching submission_uuid {{submission_uuid}}
\n
Exception Location:\n /edx/src/edx-ora2/openassessment/workflow/api.py, line 371, in _get_workflow_model\n
Python Executable:/edx/app/edxapp/venvs/edxapp/bin/python
Python Version:3.8.10
Python Path:\n
['/edx/app/edx_ansible/edx_ansible/docker/plays',\n '/edx/app/edxapp/edx-platform',\n '/edx/app/edxapp/venvs/edxapp/lib/python38.zip',\n '/edx/app/edxapp/venvs/edxapp/lib/python3.8',\n '/edx/app/edxapp/venvs/edxapp/lib/python3.8/lib-dynload',\n '/usr/lib/python3.8',\n '/edx/app/edxapp/venvs/edxapp/lib/python3.8/site-packages',\n '/edx/app/edxapp/edx-platform/common/lib/capa',\n '/edx/app/edxapp/edx-platform/common/lib/xmodule',\n '/edx/app/edxapp/venvs/edxapp/src/xblock-google-drive',\n '/edx/app/edxapp/edx-platform/openedx/core/lib/xblock_builtin/xblock_discussion',\n '/edx/app/edxapp/edx-platform/common/lib/symmath',\n '/edx/app/edxapp/edx-platform/common/lib/sandbox-packages',\n '/edx/app/edxapp/edx-platform/common/lib/safe_lxml',\n '/edx/app/edxapp/venvs/edxapp/src/rate-xblock',\n '/edx/app/edxapp/edx-platform',\n '/edx/app/edxapp/venvs/edxapp/src/olxcleaner',\n '/edx/app/edxapp/venvs/edxapp/src/edx-jsme',\n '/edx/app/edxapp/venvs/edxapp/src/django-wiki',\n '/edx/app/edxapp/venvs/edxapp/src/codejail',\n '/edx/src/edx-ora2']
\n
Server time:Tue, 11 Jan 2022 17:03:37 +0000
\n
\n
\n

Traceback \n \n \n Switch to copy-and-paste view\n \n

\n
\n
    \n
  • \n /edx/app/edxapp/venvs/edxapp/lib/python3.8/site-packages/django/core/handlers/exception.py, line 47, in inner\n
    \n
      \n
    1. \n
                      response = await sync_to_async(response_for_exception, thread_sensitive=False)(request, exc)
      \n
    2. \n
    3. \n
                  return response
      \n
    4. \n
    5. \n
              return inner
      \n
    6. \n
    7. \n
          else:
      \n
    8. \n
    9. \n
              @wraps(get_response)
      \n
    10. \n
    11. \n
              def inner(request):
      \n
    12. \n
    13. \n
                  try:
      \n
    14. \n
    \n
      \n
    1. \n
                      response = get_response(request)
      \n \n
    2. \n
    \n
      \n
    1. \n
                  except Exception as exc:
      \n
    2. \n
    3. \n
                      response = response_for_exception(request, exc)
      \n
    4. \n
    5. \n
                  return response
      \n
    6. \n
    7. \n
              return inner
      \n
    8. \n
    9. \n
      \n                                
    10. \n
    11. \n
      \n                                
    12. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    exc\n
    AssessmentWorkflowNotFoundError('No assessment workflow matching submission_uuid {{submission_uuid}}')
    \n
    get_response\n
    <bound method BaseHandler._get_response of <django.core.handlers.wsgi.WSGIHandler object at 0x7f003285d5e0>>
    \n
    request\n
    <WSGIRequest: POST '/courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess'>
    \n
    \n
  • \n
  • \n /edx/app/edxapp/venvs/edxapp/lib/python3.8/site-packages/django/core/handlers/base.py, line 181, in _get_response\n
    \n
      \n
    1. \n
      \n                                
    2. \n
    3. \n
              if response is None:
      \n
    4. \n
    5. \n
                  wrapped_callback = self.make_view_atomic(callback)
      \n
    6. \n
    7. \n
                  # If it is an asynchronous view, run it in a subthread.
      \n
    8. \n
    9. \n
                  if asyncio.iscoroutinefunction(wrapped_callback):
      \n
    10. \n
    11. \n
                      wrapped_callback = async_to_sync(wrapped_callback)
      \n
    12. \n
    13. \n
                  try:
      \n
    14. \n
    \n
      \n
    1. \n
                      response = wrapped_callback(request, *callback_args, **callback_kwargs)
      \n \n
    2. \n
    \n
      \n
    1. \n
                  except Exception as e:
      \n
    2. \n
    3. \n
                      response = self.process_exception_by_middleware(e, request)
      \n
    4. \n
    5. \n
                      if response is None:
      \n
    6. \n
    7. \n
                          raise
      \n
    8. \n
    9. \n
      \n                                
    10. \n
    11. \n
              # Complain if the view returned None (a common error).
      \n
    12. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    callback\n
    <function handle_xblock_callback at 0x7f00194b7dc0>
    \n
    callback_args\n
    ()
    \n
    callback_kwargs\n
    {'course_id': 'course-v1:DevX+ORA101+T12020',\n 'handler': 'staff_assess',\n 'usage_id': 'block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69'}
    \n
    middleware_method\n
    <bound method EnsureJWTAuthSettingsMiddleware.process_view of <edx_rest_framework_extensions.auth.jwt.middleware.EnsureJWTAuthSettingsMiddleware object at 0x7f00170a28b0>>
    \n
    request\n
    <WSGIRequest: POST '/courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess'>
    \n
    response\n
    None
    \n
    self\n
    <django.core.handlers.wsgi.WSGIHandler object at 0x7f003285d5e0>
    \n
    wrapped_callback\n
    <function handle_xblock_callback at 0x7f00194b7dc0>
    \n
    \n
  • \n
  • \n /edx/app/edxapp/venvs/edxapp/lib/python3.8/site-packages/django/views/decorators/csrf.py, line 54, in wrapped_view\n
    \n
      \n
    1. \n
      \n                                
    2. \n
    3. \n
      \n                                
    4. \n
    5. \n
      def csrf_exempt(view_func):
      \n
    6. \n
    7. \n
          """Mark a view function as being exempt from the CSRF view protection."""
      \n
    8. \n
    9. \n
          # view_func.csrf_exempt = True would also work, but decorators are nicer
      \n
    10. \n
    11. \n
          # if they don't have side effects, so return a new function.
      \n
    12. \n
    13. \n
          def wrapped_view(*args, **kwargs):
      \n
    14. \n
    \n
      \n
    1. \n
              return view_func(*args, **kwargs)
      \n \n
    2. \n
    \n
      \n
    1. \n
          wrapped_view.csrf_exempt = True
      \n
    2. \n
    3. \n
          return wraps(view_func)(wrapped_view)
      \n
    4. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    args\n
    (<WSGIRequest: POST '/courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess'>,)
    \n
    kwargs\n
    {'course_id': 'course-v1:DevX+ORA101+T12020',\n 'handler': 'staff_assess',\n 'usage_id': 'block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69'}
    \n
    view_func\n
    <function handle_xblock_callback at 0x7f00194b7d30>
    \n
    \n
  • \n
  • \n /edx/app/edxapp/venvs/edxapp/lib/python3.8/site-packages/django/views/decorators/clickjacking.py, line 50, in wrapped_view\n
    \n
      \n
    1. \n
          XFrameOptionsMiddleware to NOT set the X-Frame-Options HTTP header. Usage:
      \n
    2. \n
    3. \n
      \n                                
    4. \n
    5. \n
          @xframe_options_exempt
      \n
    6. \n
    7. \n
          def some_view(request):
      \n
    8. \n
    9. \n
              ...
      \n
    10. \n
    11. \n
          """
      \n
    12. \n
    13. \n
          def wrapped_view(*args, **kwargs):
      \n
    14. \n
    \n
      \n
    1. \n
              resp = view_func(*args, **kwargs)
      \n \n
    2. \n
    \n
      \n
    1. \n
              resp.xframe_options_exempt = True
      \n
    2. \n
    3. \n
              return resp
      \n
    4. \n
    5. \n
          return wraps(view_func)(wrapped_view)
      \n
    6. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    args\n
    (<WSGIRequest: POST '/courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess'>,)
    \n
    kwargs\n
    {'course_id': 'course-v1:DevX+ORA101+T12020',\n 'handler': 'staff_assess',\n 'usage_id': 'block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69'}
    \n
    view_func\n
    <function handle_xblock_callback at 0x7f00194b7ca0>
    \n
    \n
  • \n
  • \n /edx/app/edxapp/edx-platform/lms/djangoapps/courseware/module_render.py, line 1042, in handle_xblock_callback\n
    \n
      \n
    1. \n
      \n                                
    2. \n
    3. \n
          with modulestore().bulk_operations(course_key):
      \n
    4. \n
    5. \n
              try:
      \n
    6. \n
    7. \n
                  course = modulestore().get_course(course_key)
      \n
    8. \n
    9. \n
              except ItemNotFoundError:
      \n
    10. \n
    11. \n
                  raise Http404(f'{course_id} does not exist in the modulestore')  # lint-amnesty, pylint: disable=raise-missing-from
      \n
    12. \n
    13. \n
      \n                                
    14. \n
    \n
      \n
    1. \n
              return _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, course=course)
      \n \n
    2. \n
    \n
      \n
    1. \n
      \n                                
    2. \n
    3. \n
      \n                                
    4. \n
    5. \n
      def _get_usage_key_for_course(course_key, usage_id) -> UsageKey:
      \n
    6. \n
    7. \n
          """
      \n
    8. \n
    9. \n
          Returns UsageKey mapped into the course for a given usage_id string
      \n
    10. \n
    11. \n
          """
      \n
    12. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    course\n
    <CourseBlockWithMixins @5FD6 license=None, parent=None, name=None, tags=[], display_name='ORA Smoke Testing', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<bson.tz_util.FixedOffset object at 0x7f00152755b0>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, children=[BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'chapter', '686fc2e208a34d3ba94e81582835b87f')], default_time_limit_minutes=None, exam_review_rules='', hide_after_due=False, is_entrance_exam=False, is_onboarding_exam=False, is_practice_exam=False, is_proctored_enabled=False, is_time_limited=False, position=None, xml_attributes={}, advanced_modules=[], advertised_start=None, allow_anonymous=True, allow_anonymous_to_peers=False, allow_proctoring_opt_out=False, allow_public_wiki_access=False, allow_unsupported_xblocks=False, announcement=None, banner_image='images_course_image.jpg', catalog_visibility='both', ccx_connector='', cert_html_view_enabled=True, cert_html_view_overrides={}, cert_name_long='', cert_name_short='', certificate_available_date=datetime.datetime(2021, 1, 3, 0, 0, tzinfo=<bson.tz_util.FixedOffset object at 0x7f0015275550>), certificates={}, certificates_display_behavior='end', certificates_show_before_end=False, cohort_config={}, cosmetic_display_price=0, course_image='ora-monitor.jpg', course_survey_name=None, course_survey_required=False, course_visibility='private', course_wide_css=[], course_wide_js=[], create_zendesk_tickets=True, css_class='', disable_progress_graph=False, discussion_blackouts=[], discussion_link=None, discussion_sort_alpha=False, discussion_topics={'General': {'id': 'course'}}, discussions_settings={'enable_in_context': True, 'enable_graded_units': False, 'unit_level_visibility': False}, display_coursenumber='', display_organization=None, due_date_display_format=None, enable_ccx=False, enable_proctored_exams=False, enable_subsection_gating=False, enable_timed_exams=True, end=datetime.datetime(2022, 1, 1, 0, 0, tzinfo=<bson.tz_util.FixedOffset object at 0x7f00152755b0>), end_of_course_survey_url=None, enrollment_domain=None, enrollment_end=datetime.datetime(2022, 1, 1, 0, 0, tzinfo=<bson.tz_util.FixedOffset object at 0x7f00152755b0>), enrollment_start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<bson.tz_util.FixedOffset object at 0x7f00152755b0>), entrance_exam_enabled=False, entrance_exam_id=None, entrance_exam_minimum_score_pct=65, grading_policy={'GRADER': [{'type': 'Homework', 'short_label': 'HW', 'min_count': 12, 'drop_count': 2, 'weight': 0.15}, {'type': 'Lab', 'min_count': 12, 'drop_count': 2, 'weight': 0.15}, {'type': 'Midterm Exam', 'short_label': 'Midterm', 'min_count': 1, 'drop_count': 0, 'weight': 0.3}, {'type': 'Final Exam', 'short_label': 'Final', 'min_count': 1, 'drop_count': 0, 'weight': 0.4}], 'GRADE_CUTOFFS': {'Pass': 0.5}}, hide_progress_tab=None, highlights_enabled_for_messaging=False, html_textbooks=[], info_sidebar_name='Course Handouts', instructor_info={'instructors': []}, invitation_only=False, is_new=None, issue_badges=True, language='en', learning_info=[], lti_passports=[], max_student_enrollments_allowed=None, minimum_grade_credit=0.8, mobile_available=False, no_grade=Fa… <trimmed 5315 bytes string>
    \n
    course_id\n
    'course-v1:DevX+ORA101+T12020'
    \n
    course_key\n
    CourseLocator('DevX', 'ORA101', 'T12020', None, None)
    \n
    error\n
    None
    \n
    handler\n
    'staff_assess'
    \n
    request\n
    <WSGIRequest: POST '/courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess'>
    \n
    suffix\n
    None
    \n
    usage_id\n
    'block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69'
    \n
    \n
  • \n
  • \n /edx/app/edxapp/edx-platform/lms/djangoapps/courseware/module_render.py, line 1191, in _invoke_xblock_handler\n
    \n
      \n
    1. \n
                      if is_xblock_aside(usage_key):
      \n
    2. \n
    3. \n
                          # In this case, 'instance' is the XBlock being wrapped by the aside, so
      \n
    4. \n
    5. \n
                          # the actual aside instance needs to be retrieved in order to invoke its
      \n
    6. \n
    7. \n
                          # handler method.
      \n
    8. \n
    9. \n
                          handler_instance = get_aside_from_xblock(instance, usage_key.aside_type)
      \n
    10. \n
    11. \n
                      else:
      \n
    12. \n
    13. \n
                          handler_instance = instance
      \n
    14. \n
    \n
      \n
    1. \n
                      resp = handler_instance.handle(handler, req, suffix)
      \n \n
    2. \n
    \n
      \n
    1. \n
                      if suffix == 'problem_check' \\
      \n
    2. \n
    3. \n
                              and course \\
      \n
    4. \n
    5. \n
                              and getattr(course, 'entrance_exam_enabled', False) \\
      \n
    6. \n
    7. \n
                              and getattr(instance, 'in_entrance_exam', False):
      \n
    8. \n
    9. \n
                          ee_data = {'entrance_exam_passed': user_has_passed_entrance_exam(request.user, course)}
      \n
    10. \n
    11. \n
                          resp = append_data_to_webob_response(resp, ee_data)
      \n
    12. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    _\n
    {'module': {'display_name': 'Open Response Assessment',\n            'usage_key': 'block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69'}}
    \n
    block_usage_key\n
    BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'openassessment', '8c235f76c46948ec80c9d59bf5686d69')
    \n
    course\n
    <CourseBlockWithMixins @5FD6 license=None, parent=None, name=None, tags=[], display_name='ORA Smoke Testing', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<bson.tz_util.FixedOffset object at 0x7f00152755b0>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, children=[BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'chapter', '686fc2e208a34d3ba94e81582835b87f')], default_time_limit_minutes=None, exam_review_rules='', hide_after_due=False, is_entrance_exam=False, is_onboarding_exam=False, is_practice_exam=False, is_proctored_enabled=False, is_time_limited=False, position=None, xml_attributes={}, advanced_modules=[], advertised_start=None, allow_anonymous=True, allow_anonymous_to_peers=False, allow_proctoring_opt_out=False, allow_public_wiki_access=False, allow_unsupported_xblocks=False, announcement=None, banner_image='images_course_image.jpg', catalog_visibility='both', ccx_connector='', cert_html_view_enabled=True, cert_html_view_overrides={}, cert_name_long='', cert_name_short='', certificate_available_date=datetime.datetime(2021, 1, 3, 0, 0, tzinfo=<bson.tz_util.FixedOffset object at 0x7f0015275550>), certificates={}, certificates_display_behavior='end', certificates_show_before_end=False, cohort_config={}, cosmetic_display_price=0, course_image='ora-monitor.jpg', course_survey_name=None, course_survey_required=False, course_visibility='private', course_wide_css=[], course_wide_js=[], create_zendesk_tickets=True, css_class='', disable_progress_graph=False, discussion_blackouts=[], discussion_link=None, discussion_sort_alpha=False, discussion_topics={'General': {'id': 'course'}}, discussions_settings={'enable_in_context': True, 'enable_graded_units': False, 'unit_level_visibility': False}, display_coursenumber='', display_organization=None, due_date_display_format=None, enable_ccx=False, enable_proctored_exams=False, enable_subsection_gating=False, enable_timed_exams=True, end=datetime.datetime(2022, 1, 1, 0, 0, tzinfo=<bson.tz_util.FixedOffset object at 0x7f00152755b0>), end_of_course_survey_url=None, enrollment_domain=None, enrollment_end=datetime.datetime(2022, 1, 1, 0, 0, tzinfo=<bson.tz_util.FixedOffset object at 0x7f00152755b0>), enrollment_start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<bson.tz_util.FixedOffset object at 0x7f00152755b0>), entrance_exam_enabled=False, entrance_exam_id=None, entrance_exam_minimum_score_pct=65, grading_policy={'GRADER': [{'type': 'Homework', 'short_label': 'HW', 'min_count': 12, 'drop_count': 2, 'weight': 0.15}, {'type': 'Lab', 'min_count': 12, 'drop_count': 2, 'weight': 0.15}, {'type': 'Midterm Exam', 'short_label': 'Midterm', 'min_count': 1, 'drop_count': 0, 'weight': 0.3}, {'type': 'Final Exam', 'short_label': 'Final', 'min_count': 1, 'drop_count': 0, 'weight': 0.4}], 'GRADE_CUTOFFS': {'Pass': 0.5}}, hide_progress_tab=None, highlights_enabled_for_messaging=False, html_textbooks=[], info_sidebar_name='Course Handouts', instructor_info={'instructors': []}, invitation_only=False, is_new=None, issue_badges=True, language='en', learning_info=[], lti_passports=[], max_student_enrollments_allowed=None, minimum_grade_credit=0.8, mobile_available=False, no_grade=Fa… <trimmed 5315 bytes string>
    \n
    course_id\n
    'course-v1:DevX+ORA101+T12020'
    \n
    course_key\n
    CourseLocator('DevX', 'ORA101', 'T12020', None, None)
    \n
    descriptor\n
    <OpenAssessmentBlockWithMixins @8532 parent=BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'vertical', 'd01f8519647b420d8f4e7cfdc0a5608b'), name=None, tags=[], display_name='Open Response Assessment', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<UTC>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, allow_file_upload=False, allow_latex=False, allow_multiple_files=True, editor_assessments_order=['student-training', 'peer-assessment', 'self-assessment', 'staff-assessment'], file_upload_response_raw='optional', file_upload_type_raw='pdf-and-image', has_saved=False, leaderboard_show=0, no_peers=False, prompt='<p>Staff assessment only, files optio...', prompts_type='html', rubric_assessments=[{'required': True, 'name': 'staff-assessment', 'enable_flexible_grading': False, 'due': None, 'start': None}], rubric_criteria=[{'label': 'Ideas', 'prompt': 'Determine if there is a unifying theme or main idea.', 'feedback': 'optional', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Difficult for the reader to discern the main idea.  Too brief or too repetitive to establish or maintain a focus.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 3, 'explanation': 'Presents a unifying theme or main idea, but may include minor tangents.  Stays somewhat focused on topic and task.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 5, 'explanation': 'Presents a unifying theme or main idea without going off on tangents.  Stays completely focused on topic and task.', 'name': 'Good', 'order_num': 2}], 'name': 'Ideas', 'order_num': 0}, {'label': 'Content', 'prompt': 'Assess the content of the submission', 'feedback': 'disabled', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Includes little information with few or no details or unrelated details.  Unsuccessful in attempts to explore any facets of the topic.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 1, 'explanation': 'Includes little information and few or no details.  Explores only one or two facets of the topic.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 3, 'explanation': 'Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.)  Explores some facets of the topic.', 'name': 'Good', 'order_num': 2}, {'label': 'Excellent', 'points': 5, 'explanation': 'Includes in-depth information and exceptional supporting details that are fully developed.  Explores all facets of the topic.', 'name': 'Excellent', 'order_num': 3}], 'name': 'Content', 'order_num': 1}], rubric_feedback_default_text='I think that this response...', rubric_feedback_prompt='(Optional) What aspects of this respo...', saved_files_descriptions='', saved_files_names='', saved_files_sizes='', saved_response='', selected_teamset_id='', show_rubric_during_response=False, submission_due='2029-01-01T00:00:00+00:00', submission_start='2001-01-01T00:00:00+00:00', submission_uuid='95c8cc70-2ef6-4846-99e0-bae5f934b2ec', teams_enabled=False, text_response_editor='text', text_response_raw='required', title='Open Response Assessment', weight=None, white_listed_file_types=['pdf', 'gif… <trimmed 4146 bytes string>
    \n
    error_msg\n
    None
    \n
    files\n
    {}
    \n
    handler\n
    'staff_assess'
    \n
    handler_instance\n
    <OpenAssessmentBlockWithMixins @8532 parent=BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'vertical', 'd01f8519647b420d8f4e7cfdc0a5608b'), name=None, tags=[], display_name='Open Response Assessment', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<UTC>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, allow_file_upload=False, allow_latex=False, allow_multiple_files=True, editor_assessments_order=['student-training', 'peer-assessment', 'self-assessment', 'staff-assessment'], file_upload_response_raw='optional', file_upload_type_raw='pdf-and-image', has_saved=False, leaderboard_show=0, no_peers=False, prompt='<p>Staff assessment only, files optio...', prompts_type='html', rubric_assessments=[{'required': True, 'name': 'staff-assessment', 'enable_flexible_grading': False, 'due': None, 'start': None}], rubric_criteria=[{'label': 'Ideas', 'prompt': 'Determine if there is a unifying theme or main idea.', 'feedback': 'optional', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Difficult for the reader to discern the main idea.  Too brief or too repetitive to establish or maintain a focus.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 3, 'explanation': 'Presents a unifying theme or main idea, but may include minor tangents.  Stays somewhat focused on topic and task.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 5, 'explanation': 'Presents a unifying theme or main idea without going off on tangents.  Stays completely focused on topic and task.', 'name': 'Good', 'order_num': 2}], 'name': 'Ideas', 'order_num': 0}, {'label': 'Content', 'prompt': 'Assess the content of the submission', 'feedback': 'disabled', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Includes little information with few or no details or unrelated details.  Unsuccessful in attempts to explore any facets of the topic.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 1, 'explanation': 'Includes little information and few or no details.  Explores only one or two facets of the topic.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 3, 'explanation': 'Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.)  Explores some facets of the topic.', 'name': 'Good', 'order_num': 2}, {'label': 'Excellent', 'points': 5, 'explanation': 'Includes in-depth information and exceptional supporting details that are fully developed.  Explores all facets of the topic.', 'name': 'Excellent', 'order_num': 3}], 'name': 'Content', 'order_num': 1}], rubric_feedback_default_text='I think that this response...', rubric_feedback_prompt='(Optional) What aspects of this respo...', saved_files_descriptions='', saved_files_names='', saved_files_sizes='', saved_response='', selected_teamset_id='', show_rubric_during_response=False, submission_due='2029-01-01T00:00:00+00:00', submission_start='2001-01-01T00:00:00+00:00', submission_uuid='95c8cc70-2ef6-4846-99e0-bae5f934b2ec', teams_enabled=False, text_response_editor='text', text_response_raw='required', title='Open Response Assessment', weight=None, white_listed_file_types=['pdf', 'gif… <trimmed 4146 bytes string>
    \n
    handler_method\n
    <bound method verify_assessment_parameters.<locals>.verify_and_call of <OpenAssessmentBlockWithMixins @8532 parent=BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'vertical', 'd01f8519647b420d8f4e7cfdc0a5608b'), name=None, tags=[], display_name='Open Response Assessment', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<UTC>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, allow_file_upload=False, allow_latex=False, allow_multiple_files=True, editor_assessments_order=['student-training', 'peer-assessment', 'self-assessment', 'staff-assessment'], file_upload_response_raw='optional', file_upload_type_raw='pdf-and-image', has_saved=False, leaderboard_show=0, no_peers=False, prompt='<p>Staff assessment only, files optio...', prompts_type='html', rubric_assessments=[{'required': True, 'name': 'staff-assessment', 'enable_flexible_grading': False, 'due': None, 'start': None}], rubric_criteria=[{'label': 'Ideas', 'prompt': 'Determine if there is a unifying theme or main idea.', 'feedback': 'optional', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Difficult for the reader to discern the main idea.  Too brief or too repetitive to establish or maintain a focus.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 3, 'explanation': 'Presents a unifying theme or main idea, but may include minor tangents.  Stays somewhat focused on topic and task.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 5, 'explanation': 'Presents a unifying theme or main idea without going off on tangents.  Stays completely focused on topic and task.', 'name': 'Good', 'order_num': 2}], 'name': 'Ideas', 'order_num': 0}, {'label': 'Content', 'prompt': 'Assess the content of the submission', 'feedback': 'disabled', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Includes little information with few or no details or unrelated details.  Unsuccessful in attempts to explore any facets of the topic.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 1, 'explanation': 'Includes little information and few or no details.  Explores only one or two facets of the topic.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 3, 'explanation': 'Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.)  Explores some facets of the topic.', 'name': 'Good', 'order_num': 2}, {'label': 'Excellent', 'points': 5, 'explanation': 'Includes in-depth information and exceptional supporting details that are fully developed.  Explores all facets of the topic.', 'name': 'Excellent', 'order_num': 3}], 'name': 'Content', 'order_num': 1}], rubric_feedback_default_text='I think that this response...', rubric_feedback_prompt='(Optional) What aspects of this respo...', saved_files_descriptions='', saved_files_names='', saved_files_sizes='', saved_response='', selected_teamset_id='', show_rubric_during_response=False, submission_due='2029-01-01T00:00:00+00:00', submission_start='2001-01-01T00:00:00+00:00', submission_uuid='95c8cc70-2ef6-4846-99e0-bae5f934b2ec', teams_enabled=False, text_response_editor='text', text_response_raw='required', title='Open … <trimmed 4218 bytes string>
    \n
    instance\n
    <OpenAssessmentBlockWithMixins @8532 parent=BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'vertical', 'd01f8519647b420d8f4e7cfdc0a5608b'), name=None, tags=[], display_name='Open Response Assessment', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<UTC>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, allow_file_upload=False, allow_latex=False, allow_multiple_files=True, editor_assessments_order=['student-training', 'peer-assessment', 'self-assessment', 'staff-assessment'], file_upload_response_raw='optional', file_upload_type_raw='pdf-and-image', has_saved=False, leaderboard_show=0, no_peers=False, prompt='<p>Staff assessment only, files optio...', prompts_type='html', rubric_assessments=[{'required': True, 'name': 'staff-assessment', 'enable_flexible_grading': False, 'due': None, 'start': None}], rubric_criteria=[{'label': 'Ideas', 'prompt': 'Determine if there is a unifying theme or main idea.', 'feedback': 'optional', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Difficult for the reader to discern the main idea.  Too brief or too repetitive to establish or maintain a focus.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 3, 'explanation': 'Presents a unifying theme or main idea, but may include minor tangents.  Stays somewhat focused on topic and task.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 5, 'explanation': 'Presents a unifying theme or main idea without going off on tangents.  Stays completely focused on topic and task.', 'name': 'Good', 'order_num': 2}], 'name': 'Ideas', 'order_num': 0}, {'label': 'Content', 'prompt': 'Assess the content of the submission', 'feedback': 'disabled', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Includes little information with few or no details or unrelated details.  Unsuccessful in attempts to explore any facets of the topic.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 1, 'explanation': 'Includes little information and few or no details.  Explores only one or two facets of the topic.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 3, 'explanation': 'Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.)  Explores some facets of the topic.', 'name': 'Good', 'order_num': 2}, {'label': 'Excellent', 'points': 5, 'explanation': 'Includes in-depth information and exceptional supporting details that are fully developed.  Explores all facets of the topic.', 'name': 'Excellent', 'order_num': 3}], 'name': 'Content', 'order_num': 1}], rubric_feedback_default_text='I think that this response...', rubric_feedback_prompt='(Optional) What aspects of this respo...', saved_files_descriptions='', saved_files_names='', saved_files_sizes='', saved_response='', selected_teamset_id='', show_rubric_during_response=False, submission_due='2029-01-01T00:00:00+00:00', submission_start='2001-01-01T00:00:00+00:00', submission_uuid='95c8cc70-2ef6-4846-99e0-bae5f934b2ec', teams_enabled=False, text_response_editor='text', text_response_raw='required', title='Open Response Assessment', weight=None, white_listed_file_types=['pdf', 'gif… <trimmed 4146 bytes string>
    \n
    nr_tx_name\n
    'OpenAssessmentBlockWithMixins.staff_assess'
    \n
    req\n
    <DjangoWebobRequest at 0x7f00151f7e80 POST http://localhost:18000/courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess>
    \n
    request\n
    <WSGIRequest: POST '/courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess'>
    \n
    suffix\n
    None
    \n
    tracking_context\n
    {'module': {'display_name': 'Open Response Assessment',\n            'usage_key': 'block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69'}}
    \n
    tracking_context_name\n
    'module_callback_handler'
    \n
    usage_id\n
    'block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69'
    \n
    usage_key\n
    BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'openassessment', '8c235f76c46948ec80c9d59bf5686d69')
    \n
    will_recheck_access\n
    False
    \n
    \n
  • \n
  • \n /edx/app/edxapp/venvs/edxapp/lib/python3.8/site-packages/xblock/mixins.py, line 84, in handle\n
    \n
      \n
    1. \n
              The wrapped function must return a `webob.Response` object.
      \n
    2. \n
    3. \n
              """
      \n
    4. \n
    5. \n
              func._is_xblock_handler = True      # pylint: disable=protected-access
      \n
    6. \n
    7. \n
              return func
      \n
    8. \n
    9. \n
      \n                                
    10. \n
    11. \n
          def handle(self, handler_name, request, suffix=''):
      \n
    12. \n
    13. \n
              """Handle `request` with this block's runtime."""
      \n
    14. \n
    \n
      \n
    1. \n
              return self.runtime.handle(self, handler_name, request, suffix)
      \n \n
    2. \n
    \n
      \n
    1. \n
      \n                                
    2. \n
    3. \n
      \n                                
    4. \n
    5. \n
      class RuntimeServicesMixin:
      \n
    6. \n
    7. \n
          """
      \n
    8. \n
    9. \n
          This mixin provides all of the machinery needed for an XBlock-style object
      \n
    10. \n
    11. \n
          to declare dependencies on particular runtime services.
      \n
    12. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    handler_name\n
    'staff_assess'
    \n
    request\n
    <DjangoWebobRequest at 0x7f00151f7e80 POST http://localhost:18000/courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess>
    \n
    self\n
    <OpenAssessmentBlockWithMixins @8532 parent=BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'vertical', 'd01f8519647b420d8f4e7cfdc0a5608b'), name=None, tags=[], display_name='Open Response Assessment', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<UTC>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, allow_file_upload=False, allow_latex=False, allow_multiple_files=True, editor_assessments_order=['student-training', 'peer-assessment', 'self-assessment', 'staff-assessment'], file_upload_response_raw='optional', file_upload_type_raw='pdf-and-image', has_saved=False, leaderboard_show=0, no_peers=False, prompt='<p>Staff assessment only, files optio...', prompts_type='html', rubric_assessments=[{'required': True, 'name': 'staff-assessment', 'enable_flexible_grading': False, 'due': None, 'start': None}], rubric_criteria=[{'label': 'Ideas', 'prompt': 'Determine if there is a unifying theme or main idea.', 'feedback': 'optional', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Difficult for the reader to discern the main idea.  Too brief or too repetitive to establish or maintain a focus.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 3, 'explanation': 'Presents a unifying theme or main idea, but may include minor tangents.  Stays somewhat focused on topic and task.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 5, 'explanation': 'Presents a unifying theme or main idea without going off on tangents.  Stays completely focused on topic and task.', 'name': 'Good', 'order_num': 2}], 'name': 'Ideas', 'order_num': 0}, {'label': 'Content', 'prompt': 'Assess the content of the submission', 'feedback': 'disabled', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Includes little information with few or no details or unrelated details.  Unsuccessful in attempts to explore any facets of the topic.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 1, 'explanation': 'Includes little information and few or no details.  Explores only one or two facets of the topic.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 3, 'explanation': 'Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.)  Explores some facets of the topic.', 'name': 'Good', 'order_num': 2}, {'label': 'Excellent', 'points': 5, 'explanation': 'Includes in-depth information and exceptional supporting details that are fully developed.  Explores all facets of the topic.', 'name': 'Excellent', 'order_num': 3}], 'name': 'Content', 'order_num': 1}], rubric_feedback_default_text='I think that this response...', rubric_feedback_prompt='(Optional) What aspects of this respo...', saved_files_descriptions='', saved_files_names='', saved_files_sizes='', saved_response='', selected_teamset_id='', show_rubric_during_response=False, submission_due='2029-01-01T00:00:00+00:00', submission_start='2001-01-01T00:00:00+00:00', submission_uuid='95c8cc70-2ef6-4846-99e0-bae5f934b2ec', teams_enabled=False, text_response_editor='text', text_response_raw='required', title='Open Response Assessment', weight=None, white_listed_file_types=['pdf', 'gif… <trimmed 4146 bytes string>
    \n
    suffix\n
    None
    \n
    \n
  • \n
  • \n /edx/app/edxapp/edx-platform/common/lib/xmodule/xmodule/x_module.py, line 1458, in handle\n
    \n
      \n
    1. \n
                      getattr(block, 'location', ''),
      \n
    2. \n
    3. \n
                  )
      \n
    4. \n
    5. \n
      \n                                
    6. \n
    7. \n
          def handle(self, block, handler_name, request, suffix=''):  # lint-amnesty, pylint: disable=missing-function-docstring
      \n
    8. \n
    9. \n
              start_time = time.time()
      \n
    10. \n
    11. \n
              try:
      \n
    12. \n
    13. \n
                  status = "success"
      \n
    14. \n
    \n
      \n
    1. \n
                  return super().handle(block, handler_name, request, suffix=suffix)
      \n \n
    2. \n
    \n
      \n
    1. \n
      \n                                
    2. \n
    3. \n
              except:
      \n
    4. \n
    5. \n
                  status = "failure"
      \n
    6. \n
    7. \n
                  raise
      \n
    8. \n
    9. \n
      \n                                
    10. \n
    11. \n
              finally:
      \n
    12. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    __class__\n
    <class 'xmodule.x_module.MetricsMixin'>
    \n
    block\n
    <OpenAssessmentBlockWithMixins @8532 parent=BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'vertical', 'd01f8519647b420d8f4e7cfdc0a5608b'), name=None, tags=[], display_name='Open Response Assessment', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<UTC>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, allow_file_upload=False, allow_latex=False, allow_multiple_files=True, editor_assessments_order=['student-training', 'peer-assessment', 'self-assessment', 'staff-assessment'], file_upload_response_raw='optional', file_upload_type_raw='pdf-and-image', has_saved=False, leaderboard_show=0, no_peers=False, prompt='<p>Staff assessment only, files optio...', prompts_type='html', rubric_assessments=[{'required': True, 'name': 'staff-assessment', 'enable_flexible_grading': False, 'due': None, 'start': None}], rubric_criteria=[{'label': 'Ideas', 'prompt': 'Determine if there is a unifying theme or main idea.', 'feedback': 'optional', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Difficult for the reader to discern the main idea.  Too brief or too repetitive to establish or maintain a focus.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 3, 'explanation': 'Presents a unifying theme or main idea, but may include minor tangents.  Stays somewhat focused on topic and task.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 5, 'explanation': 'Presents a unifying theme or main idea without going off on tangents.  Stays completely focused on topic and task.', 'name': 'Good', 'order_num': 2}], 'name': 'Ideas', 'order_num': 0}, {'label': 'Content', 'prompt': 'Assess the content of the submission', 'feedback': 'disabled', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Includes little information with few or no details or unrelated details.  Unsuccessful in attempts to explore any facets of the topic.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 1, 'explanation': 'Includes little information and few or no details.  Explores only one or two facets of the topic.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 3, 'explanation': 'Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.)  Explores some facets of the topic.', 'name': 'Good', 'order_num': 2}, {'label': 'Excellent', 'points': 5, 'explanation': 'Includes in-depth information and exceptional supporting details that are fully developed.  Explores all facets of the topic.', 'name': 'Excellent', 'order_num': 3}], 'name': 'Content', 'order_num': 1}], rubric_feedback_default_text='I think that this response...', rubric_feedback_prompt='(Optional) What aspects of this respo...', saved_files_descriptions='', saved_files_names='', saved_files_sizes='', saved_response='', selected_teamset_id='', show_rubric_during_response=False, submission_due='2029-01-01T00:00:00+00:00', submission_start='2001-01-01T00:00:00+00:00', submission_uuid='95c8cc70-2ef6-4846-99e0-bae5f934b2ec', teams_enabled=False, text_response_editor='text', text_response_raw='required', title='Open Response Assessment', weight=None, white_listed_file_types=['pdf', 'gif… <trimmed 4146 bytes string>
    \n
    course_id\n
    CourseLocator('DevX', 'ORA101', 'T12020', None, None)
    \n
    duration\n
    1.3147268295288086
    \n
    end_time\n
    1641920616.8631818
    \n
    handler_name\n
    'staff_assess'
    \n
    request\n
    <DjangoWebobRequest at 0x7f00151f7e80 POST http://localhost:18000/courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess>
    \n
    self\n
    LmsModuleSystem{'request_token': '695f6868730011eca52d0242ac120009', 'id_reader': <xmodule.modulestore.split_mongo.id_manager.SplitMongoIdManager object at 0x7f0014943e80>, '_services': {'fs': File system object, 'field-data': LmsFieldData(ReadOnlyFieldData(<edx_when.field_data.DateLookupFieldData object at 0x7f001521a820>), KvsFieldData(<lms.djangoapps.courseware.model_data.DjangoKeyValueStore object at 0x7f001531fd30>)), 'mako': <common.djangoapps.edxmako.services.MakoService object at 0x7f0015abe370>, 'user': <common.djangoapps.xblock_django.user_service.DjangoXBlockUserService object at 0x7f0015abe460>, 'verification': <lms.djangoapps.verify_student.services.XBlockVerificationService object at 0x7f00153edb50>, 'proctoring': <edx_proctoring.services.ProctoringService object at 0x7f0004e598e0>, 'milestones': <milestones.services.MilestonesService object at 0x7f0004e59fa0>, 'credit': <openedx.core.djangoapps.credit.services.CreditService object at 0x7f00153ed4c0>, 'bookmarks': <openedx.core.djangoapps.bookmarks.services.BookmarksService object at 0x7f00153ed040>, 'gating': <openedx.core.lib.gating.services.GatingService object at 0x7f00153c83d0>, 'grade_utils': <lms.djangoapps.grades.util_services.GradesUtilService object at 0x7f001529dc10>, 'user_state': <lms.djangoapps.courseware.services.UserStateService object at 0x7f001529d310>, 'content_type_gating': <openedx.features.content_type_gating.services.ContentTypeGatingService object at 0x7f001529d610>, 'xqueue': <capa.xqueue_interface.XQueueService object at 0x7f001521a0d0>, 'completion': <completion.services.CompletionService object at 0x7f001529de80>, 'i18n': <class 'xmodule.modulestore.django.ModuleI18nService'>, 'library_tools': <xmodule.library_tools.LibraryToolsService object at 0x7f001507b6a0>, 'partitions': <xmodule.partitions.partitions_service.PartitionService object at 0x7f001529d460>, 'settings': <xmodule.services.SettingsService object at 0x7f001529dbe0>, 'user_tags': <lms.djangoapps.lms_xblock.runtime.UserTagsService object at 0x7f001529d5e0>, 'teams': <lms.djangoapps.teams.services.TeamsService object at 0x7f001529d040>, 'teams_configuration': <xmodule.services.TeamsConfigurationService object at 0x7f001529d970>, 'call_to_action': <openedx.core.lib.xblock_services.call_to_action.CallToActionService object at 0x7f001529dca0>}, '_deprecated_per_instance_field_data': None, 'default_class': None, 'select': None, '_deprecated_per_instance_user_id': None, 'mixologist': <xblock.runtime.Mixologist object at 0x7f0015290250>, 'id_generator': <xmodule.modulestore.split_mongo.id_manager.SplitMongoIdManager object at 0x7f0014943e80>, 'wrappers': [functools.partial(<function wrap_with_license at 0x7f001f79bb80>, mako_service=<common.djangoapps.edxmako.services.MakoService object at 0x7f0015abe370>), functools.partial(<function wrap_xblock at 0x7f001ae00e50>, 'LmsRuntime', extra_data={'course-id': 'course-v1:DevX+ORA101+T12020'}, usage_id_serializer=<function get_module_system_for_user.<locals>.<lambda> at 0x7f0004190550>, request_token='695f6868730011eca52d0242ac120009'), functools.partial(<function replace_static_urls at 0x7f001ae0d8b0>, None, course_id=CourseLocator('DevX', 'ORA101', 'T12020', None, None), static_asset_path=''), functools.partial(<function replace_course_urls at 0x7f001ae0d820>, CourseLocator('DevX', 'ORA101', 'T12020', None, None)), functools.partial(<function replace_jump_to_id_urls at 0x7f001ae00f70>, CourseLocator('DevX', 'ORA101', 'T12020', None, None), '/courses/course-v1:DevX+ORA101+T12020/jump_to_id/'), functools.partial(<function display_access_messages at 0x7f00194b7700>, <User: staff>), functools.partial(<function course_expiration_wrapper at 0x7f001dbbc790>, <User: staff>), functools.partial(<function offer_banner_wrapper at 0x7f00194e3820>, <User: staff>), functools.partial(<function add_staff_markup at 0x7f001ae0da60>, <User: staff>, False)], 'wrappers_asides': [], 'STATIC_URL': '/static/', 'track_function': <function make_track_function.<locals>.function at 0x7f000419ad30>, 'filestore': OSFS('/edx/var/edxapp/data/DevX/ORA101/T12020'),… <trimmed 5891 bytes string>
    \n
    start_time\n
    1641920615.548455
    \n
    status\n
    'failure'
    \n
    suffix\n
    None
    \n
    tags\n
    ['handler_name:staff_assess',\n 'action:handle',\n 'action_status:failure',\n 'course_id:course-v1:DevX+ORA101+T12020',\n 'block_type:openassessment',\n 'block_family:xblock.v1']
    \n
    \n
  • \n
  • \n /edx/app/edxapp/venvs/edxapp/lib/python3.8/site-packages/xblock/runtime.py, line 1081, in handle\n
    \n
      \n
    1. \n
              :param request: The request to handle
      \n
    2. \n
    3. \n
              :type request: webob.Request
      \n
    4. \n
    5. \n
              :param suffix: The remainder of the url, after the handler url prefix, if available
      \n
    6. \n
    7. \n
              """
      \n
    8. \n
    9. \n
              handler = getattr(block, handler_name, None)
      \n
    10. \n
    11. \n
              if handler and getattr(handler, '_is_xblock_handler', False):
      \n
    12. \n
    13. \n
                  # Cache results of the handler call for later saving
      \n
    14. \n
    \n
      \n
    1. \n
                  results = handler(request, suffix)
      \n \n
    2. \n
    \n
      \n
    1. \n
              else:
      \n
    2. \n
    3. \n
                  fallback_handler = getattr(block, "fallback_handler", None)
      \n
    4. \n
    5. \n
                  if fallback_handler and getattr(fallback_handler, '_is_xblock_handler', False):
      \n
    6. \n
    7. \n
                      # Cache results of the handler call for later saving
      \n
    8. \n
    9. \n
                      results = fallback_handler(handler_name, request, suffix)
      \n
    10. \n
    11. \n
                  else:
      \n
    12. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    block\n
    <OpenAssessmentBlockWithMixins @8532 parent=BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'vertical', 'd01f8519647b420d8f4e7cfdc0a5608b'), name=None, tags=[], display_name='Open Response Assessment', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<UTC>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, allow_file_upload=False, allow_latex=False, allow_multiple_files=True, editor_assessments_order=['student-training', 'peer-assessment', 'self-assessment', 'staff-assessment'], file_upload_response_raw='optional', file_upload_type_raw='pdf-and-image', has_saved=False, leaderboard_show=0, no_peers=False, prompt='<p>Staff assessment only, files optio...', prompts_type='html', rubric_assessments=[{'required': True, 'name': 'staff-assessment', 'enable_flexible_grading': False, 'due': None, 'start': None}], rubric_criteria=[{'label': 'Ideas', 'prompt': 'Determine if there is a unifying theme or main idea.', 'feedback': 'optional', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Difficult for the reader to discern the main idea.  Too brief or too repetitive to establish or maintain a focus.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 3, 'explanation': 'Presents a unifying theme or main idea, but may include minor tangents.  Stays somewhat focused on topic and task.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 5, 'explanation': 'Presents a unifying theme or main idea without going off on tangents.  Stays completely focused on topic and task.', 'name': 'Good', 'order_num': 2}], 'name': 'Ideas', 'order_num': 0}, {'label': 'Content', 'prompt': 'Assess the content of the submission', 'feedback': 'disabled', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Includes little information with few or no details or unrelated details.  Unsuccessful in attempts to explore any facets of the topic.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 1, 'explanation': 'Includes little information and few or no details.  Explores only one or two facets of the topic.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 3, 'explanation': 'Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.)  Explores some facets of the topic.', 'name': 'Good', 'order_num': 2}, {'label': 'Excellent', 'points': 5, 'explanation': 'Includes in-depth information and exceptional supporting details that are fully developed.  Explores all facets of the topic.', 'name': 'Excellent', 'order_num': 3}], 'name': 'Content', 'order_num': 1}], rubric_feedback_default_text='I think that this response...', rubric_feedback_prompt='(Optional) What aspects of this respo...', saved_files_descriptions='', saved_files_names='', saved_files_sizes='', saved_response='', selected_teamset_id='', show_rubric_during_response=False, submission_due='2029-01-01T00:00:00+00:00', submission_start='2001-01-01T00:00:00+00:00', submission_uuid='95c8cc70-2ef6-4846-99e0-bae5f934b2ec', teams_enabled=False, text_response_editor='text', text_response_raw='required', title='Open Response Assessment', weight=None, white_listed_file_types=['pdf', 'gif… <trimmed 4146 bytes string>
    \n
    handler\n
    <bound method verify_assessment_parameters.<locals>.verify_and_call of <OpenAssessmentBlockWithMixins @8532 parent=BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'vertical', 'd01f8519647b420d8f4e7cfdc0a5608b'), name=None, tags=[], display_name='Open Response Assessment', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<UTC>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, allow_file_upload=False, allow_latex=False, allow_multiple_files=True, editor_assessments_order=['student-training', 'peer-assessment', 'self-assessment', 'staff-assessment'], file_upload_response_raw='optional', file_upload_type_raw='pdf-and-image', has_saved=False, leaderboard_show=0, no_peers=False, prompt='<p>Staff assessment only, files optio...', prompts_type='html', rubric_assessments=[{'required': True, 'name': 'staff-assessment', 'enable_flexible_grading': False, 'due': None, 'start': None}], rubric_criteria=[{'label': 'Ideas', 'prompt': 'Determine if there is a unifying theme or main idea.', 'feedback': 'optional', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Difficult for the reader to discern the main idea.  Too brief or too repetitive to establish or maintain a focus.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 3, 'explanation': 'Presents a unifying theme or main idea, but may include minor tangents.  Stays somewhat focused on topic and task.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 5, 'explanation': 'Presents a unifying theme or main idea without going off on tangents.  Stays completely focused on topic and task.', 'name': 'Good', 'order_num': 2}], 'name': 'Ideas', 'order_num': 0}, {'label': 'Content', 'prompt': 'Assess the content of the submission', 'feedback': 'disabled', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Includes little information with few or no details or unrelated details.  Unsuccessful in attempts to explore any facets of the topic.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 1, 'explanation': 'Includes little information and few or no details.  Explores only one or two facets of the topic.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 3, 'explanation': 'Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.)  Explores some facets of the topic.', 'name': 'Good', 'order_num': 2}, {'label': 'Excellent', 'points': 5, 'explanation': 'Includes in-depth information and exceptional supporting details that are fully developed.  Explores all facets of the topic.', 'name': 'Excellent', 'order_num': 3}], 'name': 'Content', 'order_num': 1}], rubric_feedback_default_text='I think that this response...', rubric_feedback_prompt='(Optional) What aspects of this respo...', saved_files_descriptions='', saved_files_names='', saved_files_sizes='', saved_response='', selected_teamset_id='', show_rubric_during_response=False, submission_due='2029-01-01T00:00:00+00:00', submission_start='2001-01-01T00:00:00+00:00', submission_uuid='95c8cc70-2ef6-4846-99e0-bae5f934b2ec', teams_enabled=False, text_response_editor='text', text_response_raw='required', title='Open … <trimmed 4218 bytes string>
    \n
    handler_name\n
    'staff_assess'
    \n
    request\n
    <DjangoWebobRequest at 0x7f00151f7e80 POST http://localhost:18000/courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess>
    \n
    self\n
    LmsModuleSystem{'request_token': '695f6868730011eca52d0242ac120009', 'id_reader': <xmodule.modulestore.split_mongo.id_manager.SplitMongoIdManager object at 0x7f0014943e80>, '_services': {'fs': File system object, 'field-data': LmsFieldData(ReadOnlyFieldData(<edx_when.field_data.DateLookupFieldData object at 0x7f001521a820>), KvsFieldData(<lms.djangoapps.courseware.model_data.DjangoKeyValueStore object at 0x7f001531fd30>)), 'mako': <common.djangoapps.edxmako.services.MakoService object at 0x7f0015abe370>, 'user': <common.djangoapps.xblock_django.user_service.DjangoXBlockUserService object at 0x7f0015abe460>, 'verification': <lms.djangoapps.verify_student.services.XBlockVerificationService object at 0x7f00153edb50>, 'proctoring': <edx_proctoring.services.ProctoringService object at 0x7f0004e598e0>, 'milestones': <milestones.services.MilestonesService object at 0x7f0004e59fa0>, 'credit': <openedx.core.djangoapps.credit.services.CreditService object at 0x7f00153ed4c0>, 'bookmarks': <openedx.core.djangoapps.bookmarks.services.BookmarksService object at 0x7f00153ed040>, 'gating': <openedx.core.lib.gating.services.GatingService object at 0x7f00153c83d0>, 'grade_utils': <lms.djangoapps.grades.util_services.GradesUtilService object at 0x7f001529dc10>, 'user_state': <lms.djangoapps.courseware.services.UserStateService object at 0x7f001529d310>, 'content_type_gating': <openedx.features.content_type_gating.services.ContentTypeGatingService object at 0x7f001529d610>, 'xqueue': <capa.xqueue_interface.XQueueService object at 0x7f001521a0d0>, 'completion': <completion.services.CompletionService object at 0x7f001529de80>, 'i18n': <class 'xmodule.modulestore.django.ModuleI18nService'>, 'library_tools': <xmodule.library_tools.LibraryToolsService object at 0x7f001507b6a0>, 'partitions': <xmodule.partitions.partitions_service.PartitionService object at 0x7f001529d460>, 'settings': <xmodule.services.SettingsService object at 0x7f001529dbe0>, 'user_tags': <lms.djangoapps.lms_xblock.runtime.UserTagsService object at 0x7f001529d5e0>, 'teams': <lms.djangoapps.teams.services.TeamsService object at 0x7f001529d040>, 'teams_configuration': <xmodule.services.TeamsConfigurationService object at 0x7f001529d970>, 'call_to_action': <openedx.core.lib.xblock_services.call_to_action.CallToActionService object at 0x7f001529dca0>}, '_deprecated_per_instance_field_data': None, 'default_class': None, 'select': None, '_deprecated_per_instance_user_id': None, 'mixologist': <xblock.runtime.Mixologist object at 0x7f0015290250>, 'id_generator': <xmodule.modulestore.split_mongo.id_manager.SplitMongoIdManager object at 0x7f0014943e80>, 'wrappers': [functools.partial(<function wrap_with_license at 0x7f001f79bb80>, mako_service=<common.djangoapps.edxmako.services.MakoService object at 0x7f0015abe370>), functools.partial(<function wrap_xblock at 0x7f001ae00e50>, 'LmsRuntime', extra_data={'course-id': 'course-v1:DevX+ORA101+T12020'}, usage_id_serializer=<function get_module_system_for_user.<locals>.<lambda> at 0x7f0004190550>, request_token='695f6868730011eca52d0242ac120009'), functools.partial(<function replace_static_urls at 0x7f001ae0d8b0>, None, course_id=CourseLocator('DevX', 'ORA101', 'T12020', None, None), static_asset_path=''), functools.partial(<function replace_course_urls at 0x7f001ae0d820>, CourseLocator('DevX', 'ORA101', 'T12020', None, None)), functools.partial(<function replace_jump_to_id_urls at 0x7f001ae00f70>, CourseLocator('DevX', 'ORA101', 'T12020', None, None), '/courses/course-v1:DevX+ORA101+T12020/jump_to_id/'), functools.partial(<function display_access_messages at 0x7f00194b7700>, <User: staff>), functools.partial(<function course_expiration_wrapper at 0x7f001dbbc790>, <User: staff>), functools.partial(<function offer_banner_wrapper at 0x7f00194e3820>, <User: staff>), functools.partial(<function add_staff_markup at 0x7f001ae0da60>, <User: staff>, False)], 'wrappers_asides': [], 'STATIC_URL': '/static/', 'track_function': <function make_track_function.<locals>.function at 0x7f000419ad30>, 'filestore': OSFS('/edx/var/edxapp/data/DevX/ORA101/T12020'),… <trimmed 5891 bytes string>
    \n
    suffix\n
    None
    \n
    \n
  • \n
  • \n /edx/app/edxapp/venvs/edxapp/lib/python3.8/site-packages/xblock/mixins.py, line 63, in wrapper\n
    \n
      \n
    1. \n
                  if request.method != "POST":
      \n
    2. \n
    3. \n
                      return JsonHandlerError(405, "Method must be POST").get_response(allow=["POST"])
      \n
    4. \n
    5. \n
                  try:
      \n
    6. \n
    7. \n
                      request_json = json.loads(request.body.decode('utf-8'))
      \n
    8. \n
    9. \n
                  except ValueError:
      \n
    10. \n
    11. \n
                      return JsonHandlerError(400, "Invalid JSON").get_response()
      \n
    12. \n
    13. \n
                  try:
      \n
    14. \n
    \n
      \n
    1. \n
                      response = func(self, request_json, suffix)
      \n \n
    2. \n
    \n
      \n
    1. \n
                  except JsonHandlerError as err:
      \n
    2. \n
    3. \n
                      return err.get_response()
      \n
    4. \n
    5. \n
                  if isinstance(response, Response):
      \n
    6. \n
    7. \n
                      return response
      \n
    8. \n
    9. \n
                  else:
      \n
    10. \n
    11. \n
                      return Response(json.dumps(response), content_type='application/json', charset='utf8')
      \n
    12. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    func\n
    <function verify_assessment_parameters.<locals>.verify_and_call at 0x7f0016b16e50>
    \n
    request\n
    <DjangoWebobRequest at 0x7f00151f7e80 POST http://localhost:18000/courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess>
    \n
    request_json\n
    {'assess_type': 'full-grade',\n 'criterion_feedback': {'Ideas': 'did alright'},\n 'options_selected': {'Content': 'Good', 'Ideas': 'Good'},\n 'overall_feedback': 'was okay',\n 'submission_uuid': '{{submission_uuid}}'}
    \n
    self\n
    <OpenAssessmentBlockWithMixins @8532 parent=BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'vertical', 'd01f8519647b420d8f4e7cfdc0a5608b'), name=None, tags=[], display_name='Open Response Assessment', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<UTC>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, allow_file_upload=False, allow_latex=False, allow_multiple_files=True, editor_assessments_order=['student-training', 'peer-assessment', 'self-assessment', 'staff-assessment'], file_upload_response_raw='optional', file_upload_type_raw='pdf-and-image', has_saved=False, leaderboard_show=0, no_peers=False, prompt='<p>Staff assessment only, files optio...', prompts_type='html', rubric_assessments=[{'required': True, 'name': 'staff-assessment', 'enable_flexible_grading': False, 'due': None, 'start': None}], rubric_criteria=[{'label': 'Ideas', 'prompt': 'Determine if there is a unifying theme or main idea.', 'feedback': 'optional', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Difficult for the reader to discern the main idea.  Too brief or too repetitive to establish or maintain a focus.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 3, 'explanation': 'Presents a unifying theme or main idea, but may include minor tangents.  Stays somewhat focused on topic and task.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 5, 'explanation': 'Presents a unifying theme or main idea without going off on tangents.  Stays completely focused on topic and task.', 'name': 'Good', 'order_num': 2}], 'name': 'Ideas', 'order_num': 0}, {'label': 'Content', 'prompt': 'Assess the content of the submission', 'feedback': 'disabled', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Includes little information with few or no details or unrelated details.  Unsuccessful in attempts to explore any facets of the topic.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 1, 'explanation': 'Includes little information and few or no details.  Explores only one or two facets of the topic.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 3, 'explanation': 'Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.)  Explores some facets of the topic.', 'name': 'Good', 'order_num': 2}, {'label': 'Excellent', 'points': 5, 'explanation': 'Includes in-depth information and exceptional supporting details that are fully developed.  Explores all facets of the topic.', 'name': 'Excellent', 'order_num': 3}], 'name': 'Content', 'order_num': 1}], rubric_feedback_default_text='I think that this response...', rubric_feedback_prompt='(Optional) What aspects of this respo...', saved_files_descriptions='', saved_files_names='', saved_files_sizes='', saved_response='', selected_teamset_id='', show_rubric_during_response=False, submission_due='2029-01-01T00:00:00+00:00', submission_start='2001-01-01T00:00:00+00:00', submission_uuid='95c8cc70-2ef6-4846-99e0-bae5f934b2ec', teams_enabled=False, text_response_editor='text', text_response_raw='required', title='Open Response Assessment', weight=None, white_listed_file_types=['pdf', 'gif… <trimmed 4146 bytes string>
    \n
    suffix\n
    None
    \n
    \n
  • \n
  • \n /edx/src/edx-ora2/openassessment/xblock/staff_area_mixin.py, line 78, in _wrapped\n
    \n
      \n
    1. \n
                      "STUDENT_GRADE": xblock._("You do not have permission to access ORA staff grading."),
      \n
    2. \n
    3. \n
                  }
      \n
    4. \n
    5. \n
      \n                                
    6. \n
    7. \n
                  if not xblock.is_course_staff and with_json_handler:
      \n
    8. \n
    9. \n
                      return {"success": False, "msg": permission_errors[error_key]}
      \n
    10. \n
    11. \n
                  elif not xblock.is_course_staff or xblock.in_studio_preview:
      \n
    12. \n
    13. \n
                      return xblock.render_error(permission_errors[error_key])
      \n
    14. \n
    \n
      \n
    1. \n
                  return func(xblock, *args, **kwargs)
      \n \n
    2. \n
    \n
      \n
    1. \n
              return _wrapped
      \n
    2. \n
    3. \n
          return _decorator
      \n
    4. \n
    5. \n
      \n                                
    6. \n
    7. \n
      \n                                
    8. \n
    9. \n
      class StaffAreaMixin:
      \n
    10. \n
    11. \n
          """
      \n
    12. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    args\n
    ({'assess_type': 'full-grade',\n  'criterion_feedback': {'Ideas': 'did alright'},\n  'options_selected': {'Content': 'Good', 'Ideas': 'Good'},\n  'overall_feedback': 'was okay',\n  'submission_uuid': '{{submission_uuid}}'},\n None)
    \n
    error_key\n
    'STUDENT_INFO'
    \n
    func\n
    <function verify_assessment_parameters.<locals>.verify_and_call at 0x7f0016b16dc0>
    \n
    kwargs\n
    {}
    \n
    permission_errors\n
    {'STAFF_AREA': 'You do not have permission to access the ORA staff area',\n 'STUDENT_GRADE': 'You do not have permission to access ORA staff grading.',\n 'STUDENT_INFO': 'You do not have permission to access ORA learner '\n                 'information.'}
    \n
    with_json_handler\n
    False
    \n
    xblock\n
    <OpenAssessmentBlockWithMixins @8532 parent=BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'vertical', 'd01f8519647b420d8f4e7cfdc0a5608b'), name=None, tags=[], display_name='Open Response Assessment', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<UTC>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, allow_file_upload=False, allow_latex=False, allow_multiple_files=True, editor_assessments_order=['student-training', 'peer-assessment', 'self-assessment', 'staff-assessment'], file_upload_response_raw='optional', file_upload_type_raw='pdf-and-image', has_saved=False, leaderboard_show=0, no_peers=False, prompt='<p>Staff assessment only, files optio...', prompts_type='html', rubric_assessments=[{'required': True, 'name': 'staff-assessment', 'enable_flexible_grading': False, 'due': None, 'start': None}], rubric_criteria=[{'label': 'Ideas', 'prompt': 'Determine if there is a unifying theme or main idea.', 'feedback': 'optional', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Difficult for the reader to discern the main idea.  Too brief or too repetitive to establish or maintain a focus.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 3, 'explanation': 'Presents a unifying theme or main idea, but may include minor tangents.  Stays somewhat focused on topic and task.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 5, 'explanation': 'Presents a unifying theme or main idea without going off on tangents.  Stays completely focused on topic and task.', 'name': 'Good', 'order_num': 2}], 'name': 'Ideas', 'order_num': 0}, {'label': 'Content', 'prompt': 'Assess the content of the submission', 'feedback': 'disabled', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Includes little information with few or no details or unrelated details.  Unsuccessful in attempts to explore any facets of the topic.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 1, 'explanation': 'Includes little information and few or no details.  Explores only one or two facets of the topic.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 3, 'explanation': 'Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.)  Explores some facets of the topic.', 'name': 'Good', 'order_num': 2}, {'label': 'Excellent', 'points': 5, 'explanation': 'Includes in-depth information and exceptional supporting details that are fully developed.  Explores all facets of the topic.', 'name': 'Excellent', 'order_num': 3}], 'name': 'Content', 'order_num': 1}], rubric_feedback_default_text='I think that this response...', rubric_feedback_prompt='(Optional) What aspects of this respo...', saved_files_descriptions='', saved_files_names='', saved_files_sizes='', saved_response='', selected_teamset_id='', show_rubric_during_response=False, submission_due='2029-01-01T00:00:00+00:00', submission_start='2001-01-01T00:00:00+00:00', submission_uuid='95c8cc70-2ef6-4846-99e0-bae5f934b2ec', teams_enabled=False, text_response_editor='text', text_response_raw='required', title='Open Response Assessment', weight=None, white_listed_file_types=['pdf', 'gif… <trimmed 4146 bytes string>
    \n
    \n
  • \n
  • \n /edx/src/edx-ora2/openassessment/xblock/data_conversion.py, line 282, in verify_and_call\n
    \n
      \n
    1. \n
          def verify_and_call(instance, data, suffix):
      \n
    2. \n
    3. \n
              """ Inner Method. """
      \n
    4. \n
    5. \n
              # Validate the request
      \n
    6. \n
    7. \n
              msg = _verify_assessment_data(instance._, data)
      \n
    8. \n
    9. \n
              if msg:
      \n
    10. \n
    11. \n
                  return {'success': False, 'msg': msg}
      \n
    12. \n
    13. \n
      \n                                
    14. \n
    \n
      \n
    1. \n
              return func(instance, data, suffix)
      \n \n
    2. \n
    \n
      \n
    1. \n
          return verify_and_call
      \n
    2. \n
    3. \n
      \n                                
    4. \n
    5. \n
      \n                                
    6. \n
    7. \n
      def verify_multiple_assessment_parameters(func):
      \n
    8. \n
    9. \n
          """
      \n
    10. \n
    11. \n
          Verify that the wrapped function receives the required parameters.
      \n
    12. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    data\n
    {'assess_type': 'full-grade',\n 'criterion_feedback': {'Ideas': 'did alright'},\n 'options_selected': {'Content': 'Good', 'Ideas': 'Good'},\n 'overall_feedback': 'was okay',\n 'submission_uuid': '{{submission_uuid}}'}
    \n
    func\n
    <function StaffAssessmentMixin.staff_assess at 0x7f0016b16d30>
    \n
    instance\n
    <OpenAssessmentBlockWithMixins @8532 parent=BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'vertical', 'd01f8519647b420d8f4e7cfdc0a5608b'), name=None, tags=[], display_name='Open Response Assessment', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<UTC>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, allow_file_upload=False, allow_latex=False, allow_multiple_files=True, editor_assessments_order=['student-training', 'peer-assessment', 'self-assessment', 'staff-assessment'], file_upload_response_raw='optional', file_upload_type_raw='pdf-and-image', has_saved=False, leaderboard_show=0, no_peers=False, prompt='<p>Staff assessment only, files optio...', prompts_type='html', rubric_assessments=[{'required': True, 'name': 'staff-assessment', 'enable_flexible_grading': False, 'due': None, 'start': None}], rubric_criteria=[{'label': 'Ideas', 'prompt': 'Determine if there is a unifying theme or main idea.', 'feedback': 'optional', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Difficult for the reader to discern the main idea.  Too brief or too repetitive to establish or maintain a focus.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 3, 'explanation': 'Presents a unifying theme or main idea, but may include minor tangents.  Stays somewhat focused on topic and task.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 5, 'explanation': 'Presents a unifying theme or main idea without going off on tangents.  Stays completely focused on topic and task.', 'name': 'Good', 'order_num': 2}], 'name': 'Ideas', 'order_num': 0}, {'label': 'Content', 'prompt': 'Assess the content of the submission', 'feedback': 'disabled', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Includes little information with few or no details or unrelated details.  Unsuccessful in attempts to explore any facets of the topic.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 1, 'explanation': 'Includes little information and few or no details.  Explores only one or two facets of the topic.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 3, 'explanation': 'Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.)  Explores some facets of the topic.', 'name': 'Good', 'order_num': 2}, {'label': 'Excellent', 'points': 5, 'explanation': 'Includes in-depth information and exceptional supporting details that are fully developed.  Explores all facets of the topic.', 'name': 'Excellent', 'order_num': 3}], 'name': 'Content', 'order_num': 1}], rubric_feedback_default_text='I think that this response...', rubric_feedback_prompt='(Optional) What aspects of this respo...', saved_files_descriptions='', saved_files_names='', saved_files_sizes='', saved_response='', selected_teamset_id='', show_rubric_during_response=False, submission_due='2029-01-01T00:00:00+00:00', submission_start='2001-01-01T00:00:00+00:00', submission_uuid='95c8cc70-2ef6-4846-99e0-bae5f934b2ec', teams_enabled=False, text_response_editor='text', text_response_raw='required', title='Open Response Assessment', weight=None, white_listed_file_types=['pdf', 'gif… <trimmed 4146 bytes string>
    \n
    msg\n
    None
    \n
    suffix\n
    None
    \n
    \n
  • \n
  • \n /edx/src/edx-ora2/openassessment/xblock/staff_assessment_mixin.py, line 94, in staff_assess\n
    \n
      \n
    1. \n
          @XBlock.json_handler
      \n
    2. \n
    3. \n
          @require_course_staff("STUDENT_INFO")
      \n
    4. \n
    5. \n
          @verify_assessment_parameters
      \n
    6. \n
    7. \n
          def staff_assess(self, data, suffix=''):  # pylint: disable=unused-argument
      \n
    8. \n
    9. \n
              """
      \n
    10. \n
    11. \n
              Create a staff assessment from a staff submission.
      \n
    12. \n
    13. \n
              """
      \n
    14. \n
    \n
      \n
    1. \n
              success, err_msg = self.do_staff_assessment(data)
      \n \n
    2. \n
    \n
      \n
    1. \n
              return {'success': success, 'msg': err_msg}
      \n
    2. \n
    3. \n
      \n                                
    4. \n
    5. \n
          @XBlock.json_handler
      \n
    6. \n
    7. \n
          @require_course_staff("STUDENT_INFO")
      \n
    8. \n
    9. \n
          @verify_multiple_assessment_parameters
      \n
    10. \n
    11. \n
          def bulk_staff_assess(self, data, suffix=''):  # pylint: disable=unused-argument
      \n
    12. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    data\n
    {'assess_type': 'full-grade',\n 'criterion_feedback': {'Ideas': 'did alright'},\n 'options_selected': {'Content': 'Good', 'Ideas': 'Good'},\n 'overall_feedback': 'was okay',\n 'submission_uuid': '{{submission_uuid}}'}
    \n
    self\n
    <OpenAssessmentBlockWithMixins @8532 parent=BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'vertical', 'd01f8519647b420d8f4e7cfdc0a5608b'), name=None, tags=[], display_name='Open Response Assessment', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<UTC>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, allow_file_upload=False, allow_latex=False, allow_multiple_files=True, editor_assessments_order=['student-training', 'peer-assessment', 'self-assessment', 'staff-assessment'], file_upload_response_raw='optional', file_upload_type_raw='pdf-and-image', has_saved=False, leaderboard_show=0, no_peers=False, prompt='<p>Staff assessment only, files optio...', prompts_type='html', rubric_assessments=[{'required': True, 'name': 'staff-assessment', 'enable_flexible_grading': False, 'due': None, 'start': None}], rubric_criteria=[{'label': 'Ideas', 'prompt': 'Determine if there is a unifying theme or main idea.', 'feedback': 'optional', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Difficult for the reader to discern the main idea.  Too brief or too repetitive to establish or maintain a focus.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 3, 'explanation': 'Presents a unifying theme or main idea, but may include minor tangents.  Stays somewhat focused on topic and task.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 5, 'explanation': 'Presents a unifying theme or main idea without going off on tangents.  Stays completely focused on topic and task.', 'name': 'Good', 'order_num': 2}], 'name': 'Ideas', 'order_num': 0}, {'label': 'Content', 'prompt': 'Assess the content of the submission', 'feedback': 'disabled', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Includes little information with few or no details or unrelated details.  Unsuccessful in attempts to explore any facets of the topic.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 1, 'explanation': 'Includes little information and few or no details.  Explores only one or two facets of the topic.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 3, 'explanation': 'Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.)  Explores some facets of the topic.', 'name': 'Good', 'order_num': 2}, {'label': 'Excellent', 'points': 5, 'explanation': 'Includes in-depth information and exceptional supporting details that are fully developed.  Explores all facets of the topic.', 'name': 'Excellent', 'order_num': 3}], 'name': 'Content', 'order_num': 1}], rubric_feedback_default_text='I think that this response...', rubric_feedback_prompt='(Optional) What aspects of this respo...', saved_files_descriptions='', saved_files_names='', saved_files_sizes='', saved_response='', selected_teamset_id='', show_rubric_during_response=False, submission_due='2029-01-01T00:00:00+00:00', submission_start='2001-01-01T00:00:00+00:00', submission_uuid='95c8cc70-2ef6-4846-99e0-bae5f934b2ec', teams_enabled=False, text_response_editor='text', text_response_raw='required', title='Open Response Assessment', weight=None, white_listed_file_types=['pdf', 'gif… <trimmed 4146 bytes string>
    \n
    suffix\n
    None
    \n
    \n
  • \n
  • \n /edx/src/edx-ora2/openassessment/xblock/staff_assessment_mixin.py, line 63, in do_staff_assessment\n
    \n
      \n
    1. \n
                          data['options_selected'],
      \n
    2. \n
    3. \n
                          clean_criterion_feedback(self.rubric_criteria, data['criterion_feedback']),
      \n
    4. \n
    5. \n
                          data['overall_feedback'],
      \n
    6. \n
    7. \n
                          create_rubric_dict(self.prompts, self.rubric_criteria_with_labels)
      \n
    8. \n
    9. \n
                      )
      \n
    10. \n
    11. \n
                      assess_type = data.get('assess_type', 'regrade')
      \n
    12. \n
    13. \n
                      self.publish_assessment_event("openassessmentblock.staff_assess", assessment, type=assess_type)
      \n
    14. \n
    \n
      \n
    1. \n
                      workflow_api.update_from_assessments(
      \n \n
    2. \n
    \n
      \n
    1. \n
                          assessment["submission_uuid"],
      \n
    2. \n
    3. \n
                          None,
      \n
    4. \n
    5. \n
                          override_submitter_requirements=(assess_type == 'regrade')
      \n
    6. \n
    7. \n
                      )
      \n
    8. \n
    9. \n
                  except StaffAssessmentRequestError:
      \n
    10. \n
    11. \n
                      logger.warning(
      \n
    12. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    assess_type\n
    'full-grade'
    \n
    assessment\n
    {'feedback': 'was okay',\n 'id': 45,\n 'parts': [{'criterion': {'label': 'Ideas',\n                          'name': 'Ideas',\n                          'options': [OrderedDict([('order_num', 0),\n                                                   ('points', 0),\n                                                   ('name', 'Poor'),\n                                                   ('label', 'Poor'),\n                                                   ('explanation',\n                                                    'Difficult for the reader '\n                                                    'to discern the main '\n                                                    'idea.  Too brief or too '\n                                                    'repetitive to establish '\n                                                    'or maintain a focus.')]),\n                                      OrderedDict([('order_num', 1),\n                                                   ('points', 3),\n                                                   ('name', 'Fair'),\n                                                   ('label', 'Fair'),\n                                                   ('explanation',\n                                                    'Presents a unifying theme '\n                                                    'or main idea, but may '\n                                                    'include minor tangents.  '\n                                                    'Stays somewhat focused on '\n                                                    'topic and task.')]),\n                                      OrderedDict([('order_num', 2),\n                                                   ('points', 5),\n                                                   ('name', 'Good'),\n                                                   ('label', 'Good'),\n                                                   ('explanation',\n                                                    'Presents a unifying theme '\n                                                    'or main idea without '\n                                                    'going off on tangents.  '\n                                                    'Stays completely focused '\n                                                    'on topic and task.'),\n                                                   ('criterion',\n                                                    <Recursion on dict with id=139638062532032>)])],\n                          'order_num': 0,\n                          'points_possible': 5,\n                          'prompt': 'Determine if there is a unifying theme or '\n                                    'main idea.'},\n            'feedback': 'did alright',\n            'option': OrderedDict([('order_num', 2),\n                                   ('points', 5),\n                                   ('name', 'Good'),\n                                   ('label', 'Good'),\n                                   ('explanation',\n                                    'Presents a unifying theme or main idea '\n                                    'without going off on tangents.  Stays '\n                                    'completely focused on topic and task.'),\n                                   ('criterion',\n                                    {'label': 'Ideas',\n                                     'name': 'Ideas',\n                                     'options': [OrderedDict([('order_num', 0),\n                                                              ('points', 0),\n                                                              ('name', 'Poor'),\n                                                              ('label', 'Poor'),\n                                                              ('explanation',\n                                                               'Difficult for '\n                                                               'the reader to '\n                                                               'discern the '\n                                                             … <trimmed 23690 bytes string>
    \n
    data\n
    {'assess_type': 'full-grade',\n 'criterion_feedback': {'Ideas': 'did alright'},\n 'options_selected': {'Content': 'Good', 'Ideas': 'Good'},\n 'overall_feedback': 'was okay',\n 'submission_uuid': '{{submission_uuid}}'}
    \n
    self\n
    <OpenAssessmentBlockWithMixins @8532 parent=BlockUsageLocator(CourseLocator('DevX', 'ORA101', 'T12020', None, None), 'vertical', 'd01f8519647b420d8f4e7cfdc0a5608b'), name=None, tags=[], display_name='Open Response Assessment', course_edit_method='Studio', days_early_for_beta=None, due=None, edxnotes=False, edxnotes_visibility=True, giturl=None, graceperiod=None, graded=False, group_access={}, in_entrance_exam=False, matlab_api_key=None, max_attempts=None, relative_weeks_due=None, rerandomize='never', self_paced=False, show_correctness='always', show_reset_button=False, showanswer='finished', start=datetime.datetime(2020, 1, 1, 0, 0, tzinfo=<UTC>), static_asset_path='', use_latex_compiler=False, user_partitions=[UserPartition(id=1594940370, name='Content Groups', description='The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.', groups=[Group(id=262595704, name='Test')], scheme=<class 'openedx.core.djangoapps.course_groups.partition_scheme.CohortPartitionScheme'>, parameters={}, active=True)], video_auto_advance=False, video_bumper={}, video_speed_optimizations=True, visible_to_staff_only=False, xqa_key=None, chrome=None, default_tab=None, format=None, hide_from_toc=False, source_file=None, allow_file_upload=False, allow_latex=False, allow_multiple_files=True, editor_assessments_order=['student-training', 'peer-assessment', 'self-assessment', 'staff-assessment'], file_upload_response_raw='optional', file_upload_type_raw='pdf-and-image', has_saved=False, leaderboard_show=0, no_peers=False, prompt='<p>Staff assessment only, files optio...', prompts_type='html', rubric_assessments=[{'required': True, 'name': 'staff-assessment', 'enable_flexible_grading': False, 'due': None, 'start': None}], rubric_criteria=[{'label': 'Ideas', 'prompt': 'Determine if there is a unifying theme or main idea.', 'feedback': 'optional', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Difficult for the reader to discern the main idea.  Too brief or too repetitive to establish or maintain a focus.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 3, 'explanation': 'Presents a unifying theme or main idea, but may include minor tangents.  Stays somewhat focused on topic and task.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 5, 'explanation': 'Presents a unifying theme or main idea without going off on tangents.  Stays completely focused on topic and task.', 'name': 'Good', 'order_num': 2}], 'name': 'Ideas', 'order_num': 0}, {'label': 'Content', 'prompt': 'Assess the content of the submission', 'feedback': 'disabled', 'options': [{'label': 'Poor', 'points': 0, 'explanation': 'Includes little information with few or no details or unrelated details.  Unsuccessful in attempts to explore any facets of the topic.', 'name': 'Poor', 'order_num': 0}, {'label': 'Fair', 'points': 1, 'explanation': 'Includes little information and few or no details.  Explores only one or two facets of the topic.', 'name': 'Fair', 'order_num': 1}, {'label': 'Good', 'points': 3, 'explanation': 'Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.)  Explores some facets of the topic.', 'name': 'Good', 'order_num': 2}, {'label': 'Excellent', 'points': 5, 'explanation': 'Includes in-depth information and exceptional supporting details that are fully developed.  Explores all facets of the topic.', 'name': 'Excellent', 'order_num': 3}], 'name': 'Content', 'order_num': 1}], rubric_feedback_default_text='I think that this response...', rubric_feedback_prompt='(Optional) What aspects of this respo...', saved_files_descriptions='', saved_files_names='', saved_files_sizes='', saved_response='', selected_teamset_id='', show_rubric_during_response=False, submission_due='2029-01-01T00:00:00+00:00', submission_start='2001-01-01T00:00:00+00:00', submission_uuid='95c8cc70-2ef6-4846-99e0-bae5f934b2ec', teams_enabled=False, text_response_editor='text', text_response_raw='required', title='Open Response Assessment', weight=None, white_listed_file_types=['pdf', 'gif… <trimmed 4146 bytes string>
    \n
    \n
  • \n
  • \n /edx/src/edx-ora2/openassessment/workflow/api.py, line 270, in update_from_assessments\n
    \n
      \n
    1. \n
                      'self': {
      \n
    2. \n
    3. \n
                          'complete': False
      \n
    4. \n
    5. \n
                      }
      \n
    6. \n
    7. \n
                  }
      \n
    8. \n
    9. \n
              }
      \n
    10. \n
    11. \n
      \n                                
    12. \n
    13. \n
          """
      \n
    14. \n
    \n
      \n
    1. \n
          workflow = _get_workflow_model(submission_uuid)
      \n \n
    2. \n
    \n
      \n
    1. \n
      \n                                
    2. \n
    3. \n
          try:
      \n
    4. \n
    5. \n
              workflow.update_from_assessments(assessment_requirements, override_submitter_requirements)
      \n
    6. \n
    7. \n
              logger.info(
      \n
    8. \n
    9. \n
                  "Updated workflow for submission UUID %s with requirements %s",
      \n
    10. \n
    11. \n
                  submission_uuid,
      \n
    12. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    assessment_requirements\n
    None
    \n
    override_submitter_requirements\n
    False
    \n
    submission_uuid\n
    '{{submission_uuid}}'
    \n
    \n
  • \n
  • \n /edx/src/edx-ora2/openassessment/workflow/api.py, line 371, in _get_workflow_model\n
    \n
      \n
    1. \n
                  "Could not get assessment workflow with submission_uuid {} due to error: {}"
      \n
    2. \n
    3. \n
                  .format(submission_uuid, exc)
      \n
    4. \n
    5. \n
              )
      \n
    6. \n
    7. \n
              logger.exception(err_msg)
      \n
    8. \n
    9. \n
              raise AssessmentWorkflowInternalError(err_msg) from exc
      \n
    10. \n
    11. \n
      \n                                
    12. \n
    13. \n
          if workflow is None:
      \n
    14. \n
    \n
      \n
    1. \n
              raise AssessmentWorkflowNotFoundError(
      \n \n
    2. \n
    \n
      \n
    1. \n
                  f"No assessment workflow matching submission_uuid {submission_uuid}"
      \n
    2. \n
    3. \n
              )
      \n
    4. \n
    5. \n
      \n                                
    6. \n
    7. \n
          return workflow
      \n
    8. \n
    9. \n
      \n                                
    10. \n
    11. \n
      \n                                
    12. \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    VariableValue
    submission_uuid\n
    '{{submission_uuid}}'
    \n
    workflow\n
    None
    \n
    \n
  • \n
\n
\n
\n
\n \n \n \n \n \n
\n
\n \n \n
\n
\n
\n
\n

Request information

\n

USER

\n

staff

\n

GET

\n

No GET data

\n

POST

\n

No POST data

\n

FILES

\n

No FILES data

\n

COOKIES

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
VariableValue
csrftoken\n
'uqkKjVhUqNxftsFBVXmmO1cBzKKthLlEd2xcKKR2JvCz2v5Z4VHpqPGOVCqLGT91'
\n
edx-user-info\n
('{"version": 1, "username": "staff", "header_urls": {"logout": '\n '"http://localhost:18000/logout", "account_settings": '\n '"http://localhost:18000/account/settings", "learner_profile": '\n '"http://localhost:18000/u/staff", "resume_block": '\n '"http://localhost:18000/api/user/v1/account/login_session/"}, '\n '"user_image_urls": {"full": '\n '"http://localhost:18000/static/images/profiles/default_500.png", "large": '\n '"http://localhost:18000/static/images/profiles/default_120.png", "medium": '\n '"http://localhost:18000/static/images/profiles/default_50.png", "small": '\n '"http://localhost:18000/static/images/profiles/default_30.png"}}')
\n
edxloggedin\n
'true'
\n
experiments_is_enterprise\n
'false'
\n
lms_sessionid\n
'3niydvnut3aj3beefl2lvj8epus0l3b9'
\n
openedx-language-preference\n
'en'
\n
\n

META

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
VariableValue
BOK_CHOY_CMS_PORT\n
'18031'
\n
BOK_CHOY_HOSTNAME\n
'edx.devstack.lms'
\n
BOK_CHOY_LMS_PORT\n
'18003'
\n
BOTO_CONFIG\n
'/edx/app/edxapp/.boto'
\n
CONFIGURATION_REPO\n
'https://github.com/edx/configuration.git'
\n
CONFIGURATION_VERSION\n
'master'
\n
CONFIG_ROOT\n
'/edx/app/edxapp'
\n
CONTENT_LENGTH\n
'267'
\n
CONTENT_TYPE\n
'application/json'
\n
CSRF_COOKIE\n
'uqkKjVhUqNxftsFBVXmmO1cBzKKthLlEd2xcKKR2JvCz2v5Z4VHpqPGOVCqLGT91'
\n
DJANGO_SETTINGS_MODULE\n
'lms.envs.devstack_docker'
\n
DJANGO_WATCHMAN_TIMEOUT\n
'30'
\n
EDXAPP_TEST_MONGO_HOST\n
'edx.devstack.mongo'
\n
EDX_PLATFORM_SETTINGS\n
'devstack_docker'
\n
GATEWAY_INTERFACE\n
'CGI/1.1'
\n
HOME\n
'/root'
\n
HOSTNAME\n
'lms.devstack.edx'
\n
HTTP_ACCEPT\n
'*/*'
\n
HTTP_ACCEPT_ENCODING\n
'gzip, deflate, br'
\n
HTTP_ACCEPT_LANGUAGE\n
'en;q=1.0'
\n
HTTP_CACHE_CONTROL\n
'no-cache'
\n
HTTP_CONNECTION\n
'keep-alive'
\n
HTTP_COOKIE\n
('csrftoken=uqkKjVhUqNxftsFBVXmmO1cBzKKthLlEd2xcKKR2JvCz2v5Z4VHpqPGOVCqLGT91; '\n 'edx-user-info="{\\\\"version\\\\": 1\\\\054 \\\\"username\\\\": \\\\"staff\\\\"\\\\054 '\n '\\\\"header_urls\\\\": {\\\\"logout\\\\": \\\\"http://localhost:18000/logout\\\\"\\\\054 '\n '\\\\"account_settings\\\\": \\\\"http://localhost:18000/account/settings\\\\"\\\\054 '\n '\\\\"learner_profile\\\\": \\\\"http://localhost:18000/u/staff\\\\"\\\\054 '\n '\\\\"resume_block\\\\": '\n '\\\\"http://localhost:18000/api/user/v1/account/login_session/\\\\"}\\\\054 '\n '\\\\"user_image_urls\\\\": {\\\\"full\\\\": '\n '\\\\"http://localhost:18000/static/images/profiles/default_500.png\\\\"\\\\054 '\n '\\\\"large\\\\": '\n '\\\\"http://localhost:18000/static/images/profiles/default_120.png\\\\"\\\\054 '\n '\\\\"medium\\\\": '\n '\\\\"http://localhost:18000/static/images/profiles/default_50.png\\\\"\\\\054 '\n '\\\\"small\\\\": '\n '\\\\"http://localhost:18000/static/images/profiles/default_30.png\\\\"}}"; '\n 'edxloggedin=true; experiments_is_enterprise=false; '\n 'lms_sessionid=1|3niydvnut3aj3beefl2lvj8epus0l3b9|5uYSHMN3Znql|ImViYWI2YWQyNWJhNzkyMTNmOThjMmI1YjRlOTM2MWQ1N2FjODk2MTY3MjI5ZDdiNDFhYzZmZmVkOTE3OGE5MTEi:1n71AV:lu0S_NhXlzrjrPZ9-n6M0-8Oy6Q; '\n 'openedx-language-preference=en')
\n
HTTP_HOST\n
'localhost:18000'
\n
HTTP_POSTMAN_TOKEN\n
'********************'
\n
HTTP_USER_AGENT\n
'PostmanRuntime/7.28.4'
\n
HTTP_X_CSRFTOKEN\n
'********************'
\n
LANG\n
'en_US.UTF-8'
\n
LANGUAGE\n
'en_US:en'
\n
LC_ALL\n
'en_US.UTF-8'
\n
LMS_CFG\n
'/edx/etc/lms.yml'
\n
NO_PYTHON_UNINSTALL\n
'1'
\n
OPENEDX_RELEASE\n
'master'
\n
PATH\n
'/edx/app/edxapp/venvs/edxapp/bin:/edx/app/edxapp/edx-platform/bin:/edx/app/edxapp/edx-platform/node_modules/.bin:/edx/app/edxapp/nodeenvs/edxapp/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
\n
PATH_INFO\n
'/courses/course-v1:DevX+ORA101+T12020/xblock/block-v1:DevX+ORA101+T12020+type@openassessment+block@8c235f76c46948ec80c9d59bf5686d69/handler/staff_assess'
\n
PWD\n
'/edx/app/edx_ansible/edx_ansible/docker/plays'
\n
QUERY_STRING\n
''
\n
REMOTE_ADDR\n
'172.18.0.1'
\n
REMOTE_HOST\n
''
\n
REQUEST_METHOD\n
'POST'
\n
REVISION_CFG\n
'/edx/etc/revisions.yml'
\n
RUN_MAIN\n
'true'
\n
SCRIPT_NAME\n
''
\n
SELENIUM_BROWSER\n
'firefox'
\n
SELENIUM_HOST\n
'edx.devstack.firefox'
\n
SELENIUM_PORT\n
'4444'
\n
SERVER_NAME\n
'localhost:18000'
\n
SERVER_PORT\n
'18000'
\n
SERVER_PROTOCOL\n
'HTTP/1.1'
\n
SERVER_SOFTWARE\n
'WSGIServer/0.2'
\n
SERVICE_VARIANT\n
'lms'
\n
SHLVL\n
'2'
\n
SKIP_WS_MIGRATIONS\n
'1'
\n
STUDIO_CFG\n
'/edx/etc/studio.yml'
\n
TERM\n
'xterm'
\n
TZ\n
'UTC'
\n
_\n
'/edx/app/edxapp/venvs/edxapp/bin/python'
\n
wsgi.errors\n
<_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>
\n
wsgi.file_wrapper\n
<class 'wsgiref.util.FileWrapper'>
\n
wsgi.input\n
<django.core.handlers.wsgi.LimitedStream object at 0x7f0015272a00>
\n
wsgi.multiprocess\n
False
\n
wsgi.multithread\n
True
\n
wsgi.run_once\n
False
\n
wsgi.url_scheme\n
'http'
\n
wsgi.version\n
(1, 0)
\n
\n

Settings

\n

Using settings module \n lms.envs.devstack_docker\n

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SettingValue
ABSOLUTE_URL_OVERRIDES\n
{}
\n
ACCOUNT_MICROFRONTEND_URL\n
'http://localhost:1997'
\n
ACCOUNT_VISIBILITY_CONFIGURATION\n
{'admin_fields': ['account_privacy',\n                  'profile_image',\n                  'username',\n                  'bio',\n                  'course_certificates',\n                  'country',\n                  'date_joined',\n                  'language_proficiencies',\n                  'level_of_education',\n                  'social_links',\n                  'time_zone',\n                  'accomplishments_shared',\n                  'name',\n                  'email',\n                  'id',\n                  'verified_name',\n                  'extended_profile',\n                  'gender',\n                  'state',\n                  'goals',\n                  'is_active',\n                  'last_login',\n                  'mailing_address',\n                  'requires_parental_consent',\n                  'secondary_email',\n                  'secondary_email_enabled',\n                  'year_of_birth',\n                  'phone_number',\n                  'activation_key',\n                  'pending_name_change'],\n 'bulk_shareable_fields': ['account_privacy',\n                           'profile_image',\n                           'username',\n                           'bio',\n                           'course_certificates',\n                           'country',\n                           'date_joined',\n                           'language_proficiencies',\n                           'level_of_education',\n                           'social_links',\n                           'time_zone',\n                           'accomplishments_shared'],\n 'custom_shareable_fields': ['account_privacy',\n                             'profile_image',\n                             'username',\n                             'bio',\n                             'course_certificates',\n                             'country',\n                             'date_joined',\n                             'language_proficiencies',\n                             'level_of_education',\n                             'social_links',\n                             'time_zone',\n                             'accomplishments_shared',\n                             'name'],\n 'default_visibility': 'all_users',\n 'public_fields': ['account_privacy', 'profile_image', 'username']}
\n
ACE_CHANNEL_DEFAULT_EMAIL\n
'file_email'
\n
ACE_CHANNEL_SAILTHRU_API_KEY\n
'********************'
\n
ACE_CHANNEL_SAILTHRU_API_SECRET\n
'********************'
\n
ACE_CHANNEL_SAILTHRU_DEBUG\n
True
\n
ACE_CHANNEL_SAILTHRU_TEMPLATE_NAME\n
'Automated Communication Engine Email'
\n
ACE_CHANNEL_TRANSACTIONAL_EMAIL\n
'file_email'
\n
ACE_ENABLED_CHANNELS\n
['file_email']
\n
ACE_ENABLED_POLICIES\n
['bulk_email_optout']
\n
ACE_ROUTING_KEY\n
'********************'
\n
ACTIVATION_EMAIL_FROM_ADDRESS\n
''
\n
ACTIVATION_EMAIL_SUPPORT_LINK\n
''
\n
ADMINS\n
()
\n
AFFILIATE_COOKIE_NAME\n
'dev_affiliate_id'
\n
ALLOWED_HOSTS\n
['*', 'edx.devstack.lms:18000', 'preview.localhost:18000']
\n
ALL_LANGUAGES\n
[['aa', 'Afar'],\n ['ab', 'Abkhazian'],\n ['af', 'Afrikaans'],\n ['ak', 'Akan'],\n ['sq', 'Albanian'],\n ['am', 'Amharic'],\n ['ar', 'Arabic'],\n ['an', 'Aragonese'],\n ['hy', 'Armenian'],\n ['as', 'Assamese'],\n ['av', 'Avaric'],\n ['ae', 'Avestan'],\n ['ay', 'Aymara'],\n ['az', 'Azerbaijani'],\n ['ba', 'Bashkir'],\n ['bm', 'Bambara'],\n ['eu', 'Basque'],\n ['be', 'Belarusian'],\n ['bn', 'Bengali'],\n ['bh', 'Bihari languages'],\n ['bi', 'Bislama'],\n ['bs', 'Bosnian'],\n ['br', 'Breton'],\n ['bg', 'Bulgarian'],\n ['my', 'Burmese'],\n ['ca', 'Catalan'],\n ['ch', 'Chamorro'],\n ['ce', 'Chechen'],\n ['zh', 'Chinese'],\n ['zh_HANS', 'Simplified Chinese'],\n ['zh_HANT', 'Traditional Chinese'],\n ['cu', 'Church Slavic'],\n ['cv', 'Chuvash'],\n ['kw', 'Cornish'],\n ['co', 'Corsican'],\n ['cr', 'Cree'],\n ['cs', 'Czech'],\n ['da', 'Danish'],\n ['dv', 'Divehi'],\n ['nl', 'Dutch'],\n ['dz', 'Dzongkha'],\n ['en', 'English'],\n ['eo', 'Esperanto'],\n ['et', 'Estonian'],\n ['ee', 'Ewe'],\n ['fo', 'Faroese'],\n ['fj', 'Fijian'],\n ['fi', 'Finnish'],\n ['fr', 'French'],\n ['fy', 'Western Frisian'],\n ['ff', 'Fulah'],\n ['ka', 'Georgian'],\n ['de', 'German'],\n ['gd', 'Gaelic'],\n ['ga', 'Irish'],\n ['gl', 'Galician'],\n ['gv', 'Manx'],\n ['el', 'Greek'],\n ['gn', 'Guarani'],\n ['gu', 'Gujarati'],\n ['ht', 'Haitian'],\n ['ha', 'Hausa'],\n ['he', 'Hebrew'],\n ['hz', 'Herero'],\n ['hi', 'Hindi'],\n ['ho', 'Hiri Motu'],\n ['hr', 'Croatian'],\n ['hu', 'Hungarian'],\n ['ig', 'Igbo'],\n ['is', 'Icelandic'],\n ['io', 'Ido'],\n ['ii', 'Sichuan Yi'],\n ['iu', 'Inuktitut'],\n ['ie', 'Interlingue'],\n ['ia', 'Interlingua'],\n ['id', 'Indonesian'],\n ['ik', 'Inupiaq'],\n ['it', 'Italian'],\n ['jv', 'Javanese'],\n ['ja', 'Japanese'],\n ['kl', 'Kalaallisut'],\n ['kn', 'Kannada'],\n ['ks', 'Kashmiri'],\n ['kr', 'Kanuri'],\n ['kk', 'Kazakh'],\n ['km', 'Central Khmer'],\n ['ki', 'Kikuyu'],\n ['rw', 'Kinyarwanda'],\n ['ky', 'Kirghiz'],\n ['kv', 'Komi'],\n ['kg', 'Kongo'],\n ['ko', 'Korean'],\n ['kj', 'Kuanyama'],\n ['ku', 'Kurdish'],\n ['lo', 'Lao'],\n ['la', 'Latin'],\n ['lv', 'Latvian'],\n ['li', 'Limburgan'],\n ['ln', 'Lingala'],\n ['lt', 'Lithuanian'],\n ['lb', 'Luxembourgish'],\n ['lu', 'Luba-Katanga'],\n ['lg', 'Ganda'],\n ['mk', 'Macedonian'],\n ['mh', 'Marshallese'],\n ['ml', 'Malayalam'],\n ['mi', 'Maori'],\n ['mr', 'Marathi'],\n ['ms', 'Malay'],\n ['mg', 'Malagasy'],\n ['mt', 'Maltese'],\n ['mn', 'Mongolian'],\n ['na', 'Nauru'],\n ['nv', 'Navajo'],\n ['nr', 'Ndebele, South'],\n ['nd', 'Ndebele, North'],\n ['ng', 'Ndonga'],\n ['ne', 'Nepali'],\n ['nn', 'Norwegian Nynorsk'],\n ['nb', 'Bokmål, Norwegian'],\n ['no', 'Norwegian'],\n ['ny', 'Chichewa'],\n ['oc', 'Occitan'],\n ['oj', 'Ojibwa'],\n ['or', 'Oriya'],\n ['om', 'Oromo'],\n ['os', 'Ossetian'],\n ['pa', 'Panjabi'],\n ['fa', 'Persian'],\n ['pi', 'Pali'],\n ['pl', 'Polish'],\n ['pt', 'Portuguese'],\n ['ps', 'Pushto'],\n ['qu', 'Quechua'],\n ['rm', 'Romansh'],\n ['ro', 'Romanian'],\n ['rn', 'Rundi'],\n ['ru', 'Russian'],\n ['sg', 'Sango'],\n ['sa', 'Sanskrit'],\n ['si', 'Sinhala'],\n ['sk', 'Slovak'],\n ['sl', 'Slovenian'],\n ['se', 'Northern Sami'],\n ['sm', 'Samoan'],\n ['sn', 'Shona'],\n ['sd', 'Sindhi'],\n ['so', 'Somali'],\n ['st', 'Sotho, Southern'],\n ['es', 'Spanish'],\n ['sc', 'Sardinian'],\n ['sr', 'Serbian'],\n ['ss', 'Swati'],\n ['su', 'Sundanese'],\n ['sw', 'Swahili'],\n ['sv', 'Swedish'],\n ['ty', 'Tahitian'],\n ['ta', 'Tamil'],\n ['tt', 'Tatar'],\n ['te', 'Telugu'],\n ['tg', 'Tajik'],\n ['tl', 'Tagalog'],\n ['th', 'Thai'],\n ['bo', 'Tibetan'],\n ['ti', 'Tigrinya'],\n ['to', 'Tonga (Tonga Islands)'],\n ['tn', 'Tswana'],\n ['ts', 'Tsonga'],\n ['tk', 'Turkmen'],\n ['tr', 'Turkish'],\n ['tw', 'Twi'],\n ['ug', 'Uighur'],\n ['uk', 'Ukrainian'],\n ['ur', 'Urdu'],\n ['uz', 'Uzbek'],\n ['ve', 'Venda'],\n ['vi', 'Vietnamese'],\n ['vo', 'Volapük'],\n ['cy', 'Welsh'],\n ['wa', 'Walloon'],\n ['wo', 'Wolof'],\n ['xh', 'Xhosa'],\n ['yi', 'Yiddish'],\n ['yo', 'Yoruba'],\n ['za', 'Zhuang'],\n ['zu', 'Zulu']]
\n
ALTERNATE_ENV_TASKS\n
{}
\n
ALTERNATE_QUEUES\n
['edx.cms.core.default']
\n
ALTERNATE_QUEUE_ENVS\n
['cms']
\n
ALTERNATE_WORKER_QUEUES\n
'cms'
\n
ANALYTICS_API_CLIENT\n
'********************'
\n
ANALYTICS_API_KEY\n
'********************'
\n
ANALYTICS_API_URL\n
'********************'
\n
ANALYTICS_DASHBOARD_NAME\n
'Your Platform Name Here Insights'
\n
ANALYTICS_DASHBOARD_URL\n
None
\n
API_ACCESS_FROM_EMAIL\n
'********************'
\n
API_ACCESS_MANAGER_EMAIL\n
'********************'
\n
API_DOCUMENTATION_URL\n
'********************'
\n
APPEND_SLASH\n
True
\n
APP_UPGRADE_CACHE_TIMEOUT\n
3600
\n
ASSET_IGNORE_REGEX\n
'(^\\\\._.*$)|(^\\\\.DS_Store$)|(^.*~$)'
\n
ASSET_KEY_PATTERN\n
'********************'
\n
AUTHENTICATION_BACKENDS\n
['common.djangoapps.third_party_auth.dummy.DummyBackend',\n 'social_core.backends.google.GoogleOAuth2',\n 'social_core.backends.linkedin.LinkedinOAuth2',\n 'social_core.backends.facebook.FacebookOAuth2',\n 'social_core.backends.azuread.AzureADOAuth2',\n 'common.djangoapps.third_party_auth.appleid.AppleIdAuth',\n 'common.djangoapps.third_party_auth.identityserver3.IdentityServer3',\n 'common.djangoapps.third_party_auth.saml.SAMLAuthBackend',\n 'common.djangoapps.third_party_auth.lti.LTIAuthBackend',\n 'rules.permissions.ObjectPermissionBackend',\n 'openedx.core.djangoapps.oauth_dispatch.dot_overrides.backends.EdxRateLimitedAllowAllUsersModelBackend',\n 'bridgekeeper.backends.RulePermissionBackend']
\n
AUTHN_MICROFRONTEND_DOMAIN\n
'localhost:1999'
\n
AUTHN_MICROFRONTEND_URL\n
'http://localhost:1999'
\n
AUTH_DOCUMENTATION_URL\n
'http://course-catalog-api-guide.readthedocs.io/en/latest/authentication/index.html'
\n
AUTH_PASSWORD_VALIDATORS\n
'********************'
\n
AUTH_TOKENS\n
'********************'
\n
AUTH_USER_MODEL\n
'auth.User'
\n
AWS_ACCESS_KEY_ID\n
'********************'
\n
AWS_QUERYSTRING_AUTH\n
False
\n
AWS_QUERYSTRING_EXPIRE\n
315360000
\n
AWS_S3_CUSTOM_DOMAIN\n
'SET-ME-PLEASE (ex. bucket-name.s3.amazonaws.com)'
\n
AWS_SECRET_ACCESS_KEY\n
'********************'
\n
AWS_SES_REGION_ENDPOINT\n
'email.us-east-1.amazonaws.com'
\n
AWS_SES_REGION_NAME\n
'us-east-1'
\n
AWS_STORAGE_BUCKET_NAME\n
'SET-ME-PLEASE (ex. bucket-name)'
\n
BADGING_BACKEND\n
'lms.djangoapps.badges.backends.badgr.BadgrBackend'
\n
BADGR_BASE_URL\n
'http://localhost:8005'
\n
BADGR_ENABLE_NOTIFICATIONS\n
False
\n
BADGR_ISSUER_SLUG\n
'example-issuer'
\n
BADGR_PASSWORD\n
'********************'
\n
BADGR_TIMEOUT\n
10
\n
BADGR_TOKENS_CACHE_KEY\n
'********************'
\n
BADGR_USERNAME\n
None
\n
BASE_COOKIE_DOMAIN\n
'localhost'
\n
BLOCKSTORE_API_AUTH_TOKEN\n
'********************'
\n
BLOCKSTORE_API_URL\n
'********************'
\n
BLOCKSTORE_BUNDLE_CACHE_TIMEOUT\n
3000
\n
BLOCKSTORE_PUBLIC_URL_ROOT\n
'http://localhost:18250'
\n
BLOCK_STRUCTURES_SETTINGS\n
{'COURSE_PUBLISH_TASK_DELAY': 30,\n 'PRUNING_ACTIVE': False,\n 'TASK_DEFAULT_RETRY_DELAY': 30,\n 'TASK_MAX_RETRIES': 5}
\n
BRANCH_IO_KEY\n
'********************'
\n
BROKER_CONNECTION_TIMEOUT\n
1
\n
BROKER_HEARTBEAT\n
60.0
\n
BROKER_HEARTBEAT_CHECKRATE\n
2
\n
BROKER_POOL_LIMIT\n
0
\n
BROKER_TRANSPORT_OPTIONS\n
{'fanout_patterns': True, 'fanout_prefix': True}
\n
BROKER_URL\n
'redis://:@localhost/'
\n
BROKER_USE_SSL\n
False
\n
BUGS_EMAIL\n
'bugs@example.com'
\n
BULK_COURSE_EMAIL_LAST_LOGIN_ELIGIBILITY_PERIOD\n
None
\n
BULK_EMAIL_DEFAULT_FROM_EMAIL\n
'no-reply@example.com'
\n
BULK_EMAIL_DEFAULT_RETRY_DELAY\n
30
\n
BULK_EMAIL_EMAILS_PER_TASK\n
500
\n
BULK_EMAIL_INFINITE_RETRY_CAP\n
1000
\n
BULK_EMAIL_JOB_SIZE_THRESHOLD\n
100
\n
BULK_EMAIL_LOG_SENT_EMAILS\n
False
\n
BULK_EMAIL_MAX_RETRIES\n
5
\n
BULK_EMAIL_RETRY_DELAY_BETWEEN_SENDS\n
0.02
\n
BULK_EMAIL_ROUTING_KEY\n
'********************'
\n
BULK_EMAIL_ROUTING_KEY_SMALL_JOBS\n
'********************'
\n
CACHES\n
{'celery': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',\n            'KEY_FUNCTION': '********************',\n            'KEY_PREFIX': '********************',\n            'LOCATION': ['edx.devstack.memcached:11211'],\n            'TIMEOUT': '7200'},\n 'configuration': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',\n                   'KEY_FUNCTION': '********************',\n                   'KEY_PREFIX': '********************',\n                   'LOCATION': ['edx.devstack.memcached:11211']},\n 'course_structure_cache': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',\n                            'KEY_FUNCTION': '********************',\n                            'KEY_PREFIX': '********************',\n                            'LOCATION': ['edx.devstack.memcached:11211'],\n                            'TIMEOUT': '7200'},\n 'default': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',\n             'KEY_FUNCTION': '********************',\n             'KEY_PREFIX': '********************',\n             'LOCATION': ['edx.devstack.memcached:11211'],\n             'VERSION': '1'},\n 'general': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',\n             'KEY_FUNCTION': '********************',\n             'KEY_PREFIX': '********************',\n             'LOCATION': ['edx.devstack.memcached:11211']},\n 'loc_cache': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',\n               'LOCATION': 'edx_location_mem_cache'},\n 'mongo_metadata_inheritance': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',\n                                'KEY_FUNCTION': '********************',\n                                'KEY_PREFIX': '********************',\n                                'LOCATION': ['edx.devstack.memcached:11211'],\n                                'TIMEOUT': 300},\n 'staticfiles': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',\n                 'KEY_FUNCTION': '********************',\n                 'KEY_PREFIX': '********************',\n                 'LOCATION': ['edx.devstack.memcached:11211']}}
\n
CACHE_MIDDLEWARE_ALIAS\n
'default'
\n
CACHE_MIDDLEWARE_KEY_PREFIX\n
'********************'
\n
CACHE_MIDDLEWARE_SECONDS\n
600
\n
CALCULATOR_HELP_URL\n
'https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/exercises_tools/calculator.html'
\n
CAS_ATTRIBUTE_CALLBACK\n
''
\n
CAS_EXTRA_LOGIN_PARAMS\n
''
\n
CAS_SERVER_URL\n
''
\n
CCX_MAX_STUDENTS_ALLOWED\n
200
\n
CC_MERCHANT_NAME\n
'Your Platform Name Here'
\n
CELERYBEAT_SCHEDULE\n
{'refresh-saml-metadata': {'schedule': datetime.timedelta(days=1),\n                           'task': 'common.djangoapps.third_party_auth.fetch_saml_metadata'}}
\n
CELERYBEAT_SCHEDULER\n
'celery.beat:PersistentScheduler'
\n
CELERYD_HIJACK_ROOT_LOGGER\n
False
\n
CELERYD_PREFETCH_MULTIPLIER\n
1
\n
CELERY_ALWAYS_EAGER\n
True
\n
CELERY_BROKER_HOSTNAME\n
'localhost'
\n
CELERY_BROKER_PASSWORD\n
'********************'
\n
CELERY_BROKER_TRANSPORT\n
'redis'
\n
CELERY_BROKER_USER\n
''
\n
CELERY_BROKER_USE_SSL\n
False
\n
CELERY_BROKER_VHOST\n
''
\n
CELERY_CREATE_MISSING_QUEUES\n
True
\n
CELERY_DEFAULT_EXCHANGE\n
'edx.lms.core'
\n
CELERY_DEFAULT_EXCHANGE_TYPE\n
'direct'
\n
CELERY_DEFAULT_QUEUE\n
'edx.lms.core.default'
\n
CELERY_DEFAULT_ROUTING_KEY\n
'********************'
\n
CELERY_EVENT_QUEUE_TTL\n
None
\n
CELERY_IGNORE_RESULT\n
False
\n
CELERY_IMPORTS\n
('poll.tasks',)
\n
CELERY_MESSAGE_COMPRESSION\n
'gzip'
\n
CELERY_QUEUES\n
{'edx.cms.core.default': {},\n 'edx.lms.core.default': {},\n 'edx.lms.core.high': {},\n 'edx.lms.core.high_mem': {}}
\n
CELERY_QUEUE_HA_POLICY\n
'all'
\n
CELERY_RESULT_BACKEND\n
'django-cache'
\n
CELERY_RESULT_SERIALIZER\n
'json'
\n
CELERY_ROUTES\n
'openedx.core.lib.celery.routers.route_task'
\n
CELERY_SEND_EVENTS\n
True
\n
CELERY_SEND_TASK_SENT_EVENT\n
True
\n
CELERY_STORE_ERRORS_EVEN_IF_IGNORED\n
True
\n
CELERY_TASK_SERIALIZER\n
'json'
\n
CELERY_TIMEZONE\n
'UTC'
\n
CELERY_TRACK_STARTED\n
True
\n
CERTIFICATE_DATE_FORMAT\n
'%B %-d, %Y'
\n
CERTIFICATE_TEMPLATE_LANGUAGES\n
{'en': 'English', 'es': 'Español'}
\n
CERT_NAME_LONG\n
'Certificate of Achievement'
\n
CERT_NAME_SHORT\n
'Certificate'
\n
CERT_QUEUE\n
'certificates'
\n
CHECKPOINT_PATTERN\n
'(?P<checkpoint_name>[^/]+)'
\n
CHROME_DISABLE_SUBFRAME_DIALOG_SUPPRESSION_TOKEN\n
'********************'
\n
CMS_BASE\n
'localhost:18010'
\n
CODE_JAIL\n
{'limits': {'CPU': 1,\n            'FSIZE': 1048576,\n            'PROXY': 0,\n            'REALTIME': 3,\n            'VMEM': 536870912},\n 'python_bin': '/edx/app/edxapp/venvs/edxapp-sandbox/bin/python',\n 'user': 'sandbox'}
\n
CODE_JAIL_REST_SERVICE_CONNECT_TIMEOUT\n
0.5
\n
CODE_JAIL_REST_SERVICE_HOST\n
'http://127.0.0.1:8550'
\n
CODE_JAIL_REST_SERVICE_READ_TIMEOUT\n
3.5
\n
CODE_JAIL_REST_SERVICE_REMOTE_EXEC\n
'common.lib.capa.capa.safe_exec.remote_exec.send_safe_exec_request_v0'
\n
COMMENTS_SERVICE_KEY\n
'********************'
\n
COMMENTS_SERVICE_URL\n
'http://edx.devstack.forum:4567'
\n
COMMON_ROOT\n
Path('/edx/app/edxapp/edx-platform/common')
\n
COMPLETION_BY_VIEWING_DELAY_MS\n
5000
\n
COMPLETION_VIDEO_COMPLETE_PERCENTAGE\n
0.95
\n
COMPREHENSIVE_THEME_DIRS\n
['']
\n
COMPREHENSIVE_THEME_LOCALE_PATHS\n
[]
\n
CONFIG_FILE\n
'/edx/etc/lms.yml'
\n
CONFIG_PREFIX\n
'lms.'
\n
CONTACT_EMAIL\n
'info@example.com'
\n
CONTACT_MAILING_ADDRESS\n
'SET-ME-PLEASE'
\n
CONTENTSTORE\n
{'ADDITIONAL_OPTIONS': {},\n 'DOC_STORE_CONFIG': {'authsource': '',\n                      'collection': 'modulestore',\n                      'connectTimeoutMS': 2000,\n                      'db': 'edxapp',\n                      'host': ['edx.devstack.mongo'],\n                      'password': '********************',\n                      'port': 27017,\n                      'read_preference': 'SECONDARY_PREFERRED',\n                      'replicaSet': '',\n                      'socketTimeoutMS': 3000,\n                      'ssl': False,\n                      'user': 'edxapp'},\n 'ENGINE': 'xmodule.contentstore.mongo.MongoContentStore',\n 'OPTIONS': {'auth_source': '',\n             'db': 'edxapp',\n             'host': ['edx.devstack.mongo'],\n             'password': '********************',\n             'port': 27017,\n             'ssl': False,\n             'user': 'edxapp'}}
\n
CONTENT_TYPE_GATE_GROUP_IDS\n
{'full_access': 2, 'limited_access': 1}
\n
CONTEXT_PROCESSORS\n
['django.template.context_processors.request',\n 'django.template.context_processors.static',\n 'django.template.context_processors.i18n',\n 'django.contrib.auth.context_processors.auth',\n 'django.template.context_processors.csrf',\n 'django.template.context_processors.media',\n 'django.template.context_processors.tz',\n 'django.contrib.messages.context_processors.messages',\n 'sekizai.context_processors.sekizai',\n 'common.djangoapps.edxmako.shortcuts.marketing_link_context_processor',\n 'lms.djangoapps.courseware.context_processor.user_timezone_locale_prefs',\n 'help_tokens.context_processor',\n 'openedx.core.djangoapps.site_configuration.context_processors.configuration_context',\n 'lms.djangoapps.mobile_api.context_processor.is_from_mobile_app',\n 'social_django.context_processors.backends',\n 'social_django.context_processors.login_redirect']
\n
CORS_ALLOW_CREDENTIALS\n
True
\n
CORS_ALLOW_HEADERS\n
('accept',\n 'accept-encoding',\n 'authorization',\n 'content-type',\n 'dnt',\n 'origin',\n 'user-agent',\n 'x-csrftoken',\n 'x-requested-with',\n 'use-jwt-cookie')
\n
CORS_ORIGIN_ALLOW_ALL\n
True
\n
CORS_ORIGIN_WHITELIST\n
()
\n
COUNTRIES_FIRST\n
[]
\n
COUNTRIES_OVERRIDE\n
{'TW': 'Taiwan', 'XK': 'Kosovo'}
\n
COURSES_API_CACHE_TIMEOUT\n
'********************'
\n
COURSES_ROOT\n
Path('/edx/app/edxapp/data')
\n
COURSES_WITH_UNSAFE_CODE\n
[]
\n
COURSE_ABOUT_VISIBILITY_PERMISSION\n
'see_exists'
\n
COURSE_BLOCKS_API_EXTRA_FIELDS\n
'********************'
\n
COURSE_CATALOG_API_URL\n
'********************'
\n
COURSE_CATALOG_URL_ROOT\n
'http://edx.devstack.discovery:18381'
\n
COURSE_CATALOG_VISIBILITY_PERMISSION\n
'see_exists'
\n
COURSE_DISCOVERY_MEANINGS\n
{'language': {'name': 'Language',\n              'terms': {'aa': 'Afar',\n                        'ab': 'Abkhazian',\n                        'ae': 'Avestan',\n                        'af': 'Afrikaans',\n                        'ak': 'Akan',\n                        'am': 'Amharic',\n                        'an': 'Aragonese',\n                        'ar': 'Arabic',\n                        'as': 'Assamese',\n                        'av': 'Avaric',\n                        'ay': 'Aymara',\n                        'az': 'Azerbaijani',\n                        'ba': 'Bashkir',\n                        'be': 'Belarusian',\n                        'bg': 'Bulgarian',\n                        'bh': 'Bihari languages',\n                        'bi': 'Bislama',\n                        'bm': 'Bambara',\n                        'bn': 'Bengali',\n                        'bo': 'Tibetan',\n                        'br': 'Breton',\n                        'bs': 'Bosnian',\n                        'ca': 'Catalan',\n                        'ce': 'Chechen',\n                        'ch': 'Chamorro',\n                        'co': 'Corsican',\n                        'cr': 'Cree',\n                        'cs': 'Czech',\n                        'cu': 'Church Slavic',\n                        'cv': 'Chuvash',\n                        'cy': 'Welsh',\n                        'da': 'Danish',\n                        'de': 'German',\n                        'dv': 'Divehi',\n                        'dz': 'Dzongkha',\n                        'ee': 'Ewe',\n                        'el': 'Greek',\n                        'en': 'English',\n                        'eo': 'Esperanto',\n                        'es': 'Spanish',\n                        'et': 'Estonian',\n                        'eu': 'Basque',\n                        'fa': 'Persian',\n                        'ff': 'Fulah',\n                        'fi': 'Finnish',\n                        'fj': 'Fijian',\n                        'fo': 'Faroese',\n                        'fr': 'French',\n                        'fy': 'Western Frisian',\n                        'ga': 'Irish',\n                        'gd': 'Gaelic',\n                        'gl': 'Galician',\n                        'gn': 'Guarani',\n                        'gu': 'Gujarati',\n                        'gv': 'Manx',\n                        'ha': 'Hausa',\n                        'he': 'Hebrew',\n                        'hi': 'Hindi',\n                        'ho': 'Hiri Motu',\n                        'hr': 'Croatian',\n                        'ht': 'Haitian',\n                        'hu': 'Hungarian',\n                        'hy': 'Armenian',\n                        'hz': 'Herero',\n                        'ia': 'Interlingua',\n                        'id': 'Indonesian',\n                        'ie': 'Interlingue',\n                        'ig': 'Igbo',\n                        'ii': 'Sichuan Yi',\n                        'ik': 'Inupiaq',\n                        'io': 'Ido',\n                        'is': 'Icelandic',\n                        'it': 'Italian',\n                        'iu': 'Inuktitut',\n                        'ja': 'Japanese',\n                        'jv': 'Javanese',\n                        'ka': 'Georgian',\n                        'kg': 'Kongo',\n                        'ki': 'Kikuyu',\n                        'kj': 'Kuanyama',\n                        'kk': 'Kazakh',\n                        'kl': 'Kalaallisut',\n                        'km': 'Central Khmer',\n                        'kn': 'Kannada',\n                        'ko': 'Korean',\n                        'kr': 'Kanuri',\n                        'ks': 'Kashmiri',\n                        'ku': 'Kurdish',\n                        'kv': 'Komi',\n                        'kw': 'Cornish',\n                        'ky': 'Kirghiz',\n                        'la': 'Latin',\n                        'lb': 'Luxembourgish',\n                        'lg': 'Ganda',\n                        'li': 'Limburgan',\n                        'ln': 'Lingala',\n                        'lo': 'Lao',\n                        'lt': 'Lithuanian',\n                        'lu': 'Luba-Katanga',\n                        'lv': 'Latvian',\n                        'mg': 'Malagasy',\n                        'mh': 'Marshallese',\n                        'mi': 'Maori',\n                        'mk': 'Macedonian',\n                        'ml': 'Malayalam',\n                        'mn': 'Mongolian',\n                        'mr': 'Marathi',\n                        'ms': 'Malay',\n                        'mt': 'Maltese',\n                        'my': 'Burmese',\n                        'na': 'Nauru',\n                        'nb': 'Bokmål, Norwegian',\n                        'nd': 'Ndebele, North',\n                        'ne': 'Nepali',\n                        'ng': 'Ndonga',\n                        'nl': 'Dutch',\n                        'nn': 'Norwegian Nynorsk',\n                        'no': 'Norwegian',\n                        'nr': 'Ndebele, South',\n                        'nv': 'Navajo',\n                        'ny': 'Chichewa',\n                        'oc': 'Occitan',\n                        'oj': 'Ojibwa',\n                        'om': 'Oromo',\n                        'or': 'Oriya',\n                        'os': 'Ossetian',\n                        'pa': 'Panjabi',\n                        'pi': 'Pali',\n                        'pl': 'Polish',\n                        'ps': 'Pushto',\n                        'pt': 'Portuguese',\n                        'qu': 'Quechua',\n                        'rm': 'Romansh',\n                        'rn': 'Rundi',\n                        'ro': 'Romanian',\n                        'ru': 'Russian',\n                        'rw': 'Kinyarwanda',\n                        'sa': 'Sanskrit',\n                        'sc': 'Sardinian',\n                        'sd': 'Sindhi',\n                        'se': 'Northern Sami',\n                        'sg': 'Sango',\n                        'si': 'Sinhala',\n                        'sk': 'Slovak',\n                        'sl': 'Slovenian',\n                        'sm': 'Samoan',\n                        'sn': 'Shona',\n                        'so': 'Somali',\n                        'sq': 'Albanian',\n                        'sr': 'Serbian',\n                        'ss': 'Swati',\n                        'st': 'Sotho, Southern',\n                        'su': 'Sundanese',\n                        'sv': 'Swedish',\n                        'sw': 'Swahili',\n                        'ta': 'Tamil',\n                        'te': 'Telugu',\n                        'tg': 'Tajik',\n                        'th': 'Thai',\n                        'ti': 'Tigrinya',\n                        'tk': 'Turkmen',\n                        'tl': 'Tagalog',\n                        'tn': 'Tswana',\n                        'to': 'Tonga (Tonga Islands)',\n                        'tr': 'Turkish',\n                        'ts': 'Tsonga',\n                        'tt': 'Tatar',\n                        'tw': 'Twi',\n                        'ty': 'Tahitian',\n                        'ug': 'Uighur',\n                        'uk': 'Ukrainian',\n                        'ur': 'Urdu',\n                        'uz': 'Uzbek',\n                        've': 'Venda',\n                        'vi': 'Vietnamese',\n                        'vo': 'Volapük',\n                        'wa': 'Walloon',\n                        'wo': 'Wolof',\n                        'xh': 'Xhosa',\n                        'yi': 'Yiddish',\n                        'yo': 'Yoruba',\n                        'za': 'Zhuang',\n                        'zh': 'Chinese',\n                        'zh_HANS': 'Simplified Chinese',\n                        'zh_HANT': 'Traditional Chinese',\n                        'zu': 'Zulu'}},\n 'modes': {'name': 'Course Type',\n           'terms': {'honor': 'Honor', 'verified': 'Verified'}},\n 'org': {'name': 'Organization'}}
\n
COURSE_ENROLLMENT_MODES\n
{'audit': {'display_name': 'Audit', 'id': 1, 'min_price': 0, 'slug': 'audit'},\n 'credit': {'display_name': 'Credit',\n            'id': 5,\n            'min_price': 0,\n            'slug': 'credit'},\n 'executive-education': {'display_name': 'Executive Education',\n                         'id': 8,\n                         'min_price': 1,\n                         'slug': 'executive-educations'},\n 'honor': {'display_name': 'Honor', 'id': 6, 'min_price': 0, 'slug': 'honor'},\n 'masters': {'display_name': "Master's",\n             'id': 7,\n             'min_price': 0,\n             'slug': 'masters'},\n 'no-id-professional': {'display_name': 'No-Id-Professional',\n                        'id': 4,\n                        'min_price': 0,\n                        'slug': 'no-id-professional'},\n 'professional': {'display_name': 'Professional',\n                  'id': 3,\n                  'min_price': 1,\n                  'slug': 'professional'},\n 'verified': {'display_name': 'Verified',\n              'id': 2,\n              'min_price': 1,\n              'slug': 'verified'}}
\n
COURSE_ID_PATTERN\n
'(?P<course_id>[^/+]+(/|\\\\+)[^/+]+(/|\\\\+)[^/?]+)'
\n
COURSE_KEY_PATTERN\n
'********************'
\n
COURSE_KEY_REGEX\n
'********************'
\n
COURSE_LISTINGS\n
{}
\n
COURSE_MEMBER_API_ENROLLMENT_LIMIT\n
'********************'
\n
COURSE_MESSAGE_ALERT_DURATION_IN_DAYS\n
14
\n
COURSE_MODE_DEFAULTS\n
{'bulk_sku': None,\n 'currency': 'usd',\n 'description': None,\n 'expiration_datetime': None,\n 'min_price': 0,\n 'name': 'Audit',\n 'sku': None,\n 'slug': 'audit',\n 'suggested_prices': ''}
\n
COURSE_OLX_VALIDATION_IGNORE_LIST\n
None
\n
COURSE_OLX_VALIDATION_STAGE\n
1
\n
CREDENTIALS_GENERATION_ROUTING_KEY\n
'********************'
\n
CREDENTIALS_INTERNAL_SERVICE_URL\n
'http://edx.devstack.credentials:18150'
\n
CREDENTIALS_PUBLIC_SERVICE_URL\n
'http://localhost:18150'
\n
CREDENTIALS_SERVICE_USERNAME\n
'credentials_worker'
\n
CREDIT_HELP_LINK_URL\n
''
\n
CREDIT_NOTIFICATION_CACHE_TIMEOUT\n
18000
\n
CREDIT_PROVIDER_SECRET_KEYS\n
'********************'
\n
CREDIT_PROVIDER_TIMESTAMP_EXPIRATION\n
900
\n
CREDIT_TASK_DEFAULT_RETRY_DELAY\n
30
\n
CREDIT_TASK_MAX_RETRIES\n
5
\n
CROSS_DOMAIN_CSRF_COOKIE_DOMAIN\n
''
\n
CROSS_DOMAIN_CSRF_COOKIE_NAME\n
''
\n
CSRF_COOKIE_AGE\n
31449600
\n
CSRF_COOKIE_DOMAIN\n
None
\n
CSRF_COOKIE_HTTPONLY\n
False
\n
CSRF_COOKIE_NAME\n
'csrftoken'
\n
CSRF_COOKIE_PATH\n
'/'
\n
CSRF_COOKIE_SAMESITE\n
'Lax'
\n
CSRF_COOKIE_SECURE\n
False
\n
CSRF_FAILURE_VIEW\n
'django.views.csrf.csrf_failure'
\n
CSRF_HEADER_NAME\n
'HTTP_X_CSRFTOKEN'
\n
CSRF_TRUSTED_ORIGINS\n
[]
\n
CSRF_USE_SESSIONS\n
False
\n
CSV_EXPIRATION_DAYS\n
90
\n
CUSTOM_PAGES_HELP_URL\n
'https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_assets/pages.html#adding-custom-pages'
\n
DASHBOARD_COURSE_LIMIT\n
None
\n
DATABASES\n
{'default': {'ATOMIC_REQUESTS': True,\n             'AUTOCOMMIT': True,\n             'CONN_MAX_AGE': 0,\n             'ENGINE': 'django.db.backends.mysql',\n             'HOST': 'edx.devstack.mysql57',\n             'NAME': 'edxapp',\n             'OPTIONS': {'isolation_level': 'read committed'},\n             'PASSWORD': '********************',\n             'PORT': '3306',\n             'TEST': {'CHARSET': None,\n                      'COLLATION': None,\n                      'MIGRATE': True,\n                      'MIRROR': None,\n                      'NAME': None},\n             'TIME_ZONE': None,\n             'USER': 'edxapp001'},\n 'read_replica': {'ATOMIC_REQUESTS': False,\n                  'AUTOCOMMIT': True,\n                  'CONN_MAX_AGE': 0,\n                  'ENGINE': 'django.db.backends.mysql',\n                  'HOST': 'edx.devstack.mysql57',\n                  'NAME': 'edxapp',\n                  'OPTIONS': {'isolation_level': 'read committed'},\n                  'PASSWORD': '********************',\n                  'PORT': '3306',\n                  'TEST': {'CHARSET': None,\n                           'COLLATION': None,\n                           'MIGRATE': True,\n                           'MIRROR': None,\n                           'NAME': None},\n                  'TIME_ZONE': None,\n                  'USER': 'edxapp001'},\n 'student_module_history': {'ATOMIC_REQUESTS': False,\n                            'AUTOCOMMIT': True,\n                            'CONN_MAX_AGE': 0,\n                            'ENGINE': 'django.db.backends.mysql',\n                            'HOST': 'edx.devstack.mysql57',\n                            'NAME': 'edxapp_csmh',\n                            'OPTIONS': {'isolation_level': 'read committed'},\n                            'PASSWORD': '********************',\n                            'PORT': '3306',\n                            'TEST': {'CHARSET': None,\n                                     'COLLATION': None,\n                                     'MIGRATE': True,\n                                     'MIRROR': None,\n                                     'NAME': None},\n                            'TIME_ZONE': None,\n                            'USER': 'edxapp001'}}
\n
DATABASE_ROUTERS\n
['openedx.core.lib.django_courseware_routers.StudentModuleHistoryExtendedRouter',\n 'edx_django_utils.db.read_replica.ReadReplicaRouter']
\n
DATADOG\n
{}
\n
DATA_CONSENT_SHARE_CACHE_TIMEOUT\n
28800
\n
DATA_DIR\n
Path('/edx/var/edxapp')
\n
DATA_UPLOAD_MAX_MEMORY_SIZE\n
None
\n
DATA_UPLOAD_MAX_NUMBER_FIELDS\n
None
\n
DATETIME_FORMAT\n
'N j, Y, P'
\n
DATETIME_INPUT_FORMATS\n
['%Y-%m-%d %H:%M:%S',\n '%Y-%m-%d %H:%M:%S.%f',\n '%Y-%m-%d %H:%M',\n '%m/%d/%Y %H:%M:%S',\n '%m/%d/%Y %H:%M:%S.%f',\n '%m/%d/%Y %H:%M',\n '%m/%d/%y %H:%M:%S',\n '%m/%d/%y %H:%M:%S.%f',\n '%m/%d/%y %H:%M']
\n
DATE_FORMAT\n
'N j, Y'
\n
DATE_INPUT_FORMATS\n
['%Y-%m-%d',\n '%m/%d/%Y',\n '%m/%d/%y',\n '%b %d %Y',\n '%b %d, %Y',\n '%d %b %Y',\n '%d %b, %Y',\n '%B %d %Y',\n '%B %d, %Y',\n '%d %B %Y',\n '%d %B, %Y']
\n
DCS_SESSION_COOKIE_SAMESITE\n
'Lax'
\n
DCS_SESSION_COOKIE_SAMESITE_FORCE_ALL\n
True
\n
DEBUG\n
True
\n
DEBUG_PROPAGATE_EXCEPTIONS\n
False
\n
DEBUG_TOOLBAR_CONFIG\n
{'SHOW_TOOLBAR_CALLBACK': 'lms.envs.devstack.should_show_debug_toolbar'}
\n
DEBUG_TOOLBAR_PANELS\n
('debug_toolbar.panels.versions.VersionsPanel',\n 'debug_toolbar.panels.timer.TimerPanel',\n 'debug_toolbar.panels.settings.SettingsPanel',\n 'debug_toolbar.panels.headers.HeadersPanel',\n 'debug_toolbar.panels.request.RequestPanel',\n 'debug_toolbar.panels.sql.SQLPanel',\n 'debug_toolbar.panels.signals.SignalsPanel',\n 'debug_toolbar.panels.logging.LoggingPanel',\n 'debug_toolbar.panels.history.HistoryPanel')
\n
DEBUG_TOOLBAR_PATCH_SETTINGS\n
False
\n
DEBUG_TRACK_LOG\n
False
\n
DECIMAL_SEPARATOR\n
'.'
\n
DEFAULT_AUTO_FIELD\n
'django.db.models.AutoField'
\n
DEFAULT_CHARSET\n
'utf-8'
\n
DEFAULT_COURSE_ABOUT_IMAGE_URL\n
'images/pencils.jpg'
\n
DEFAULT_COURSE_VISIBILITY_IN_CATALOG\n
'both'
\n
DEFAULT_EMAIL_LOGO_URL\n
'https://edx-cdn.org/v3/default/logo.png'
\n
DEFAULT_ENTERPRISE_API_URL\n
'********************'
\n
DEFAULT_ENTERPRISE_CONSENT_API_URL\n
'********************'
\n
DEFAULT_EXCEPTION_REPORTER\n
'django.views.debug.ExceptionReporter'
\n
DEFAULT_EXCEPTION_REPORTER_FILTER\n
'django.views.debug.SafeExceptionReporterFilter'
\n
DEFAULT_FEEDBACK_EMAIL\n
'feedback@example.com'
\n
DEFAULT_FILE_STORAGE\n
'django.core.files.storage.FileSystemStorage'
\n
DEFAULT_FROM_EMAIL\n
'registration@example.com'
\n
DEFAULT_GROUPS\n
[]
\n
DEFAULT_HASHING_ALGORITHM\n
'sha1'
\n
DEFAULT_INDEX_TABLESPACE\n
''
\n
DEFAULT_JWT_ISSUER\n
{'AUDIENCE': 'lms-key',\n 'ISSUER': 'http://edx.devstack.lms:18000/oauth2',\n 'SECRET_KEY': '********************'}
\n
DEFAULT_MOBILE_AVAILABLE\n
False
\n
DEFAULT_PRIORITY_QUEUE\n
'edx.lms.core.default'
\n
DEFAULT_SITE_THEME\n
''
\n
DEFAULT_TABLESPACE\n
''
\n
DEFAULT_TEMPLATE_ENGINE\n
{'APP_DIRS': False,\n 'BACKEND': 'django.template.backends.django.DjangoTemplates',\n 'DIRS': [Path('/edx/app/edxapp/edx-platform/lms/templates'),\n          Path('/edx/app/edxapp/edx-platform/common/templates'),\n          Path('/edx/app/edxapp/edx-platform/common/lib/capa/capa/templates'),\n          Path('/edx/app/edxapp/edx-platform/common/djangoapps/pipeline_mako/templates'),\n          Path('/edx/app/edxapp/edx-platform/common/static')],\n 'NAME': 'django',\n 'OPTIONS': {'context_processors': ['django.template.context_processors.request',\n                                    'django.template.context_processors.static',\n                                    'django.template.context_processors.i18n',\n                                    'django.contrib.auth.context_processors.auth',\n                                    'django.template.context_processors.csrf',\n                                    'django.template.context_processors.media',\n                                    'django.template.context_processors.tz',\n                                    'django.contrib.messages.context_processors.messages',\n                                    'sekizai.context_processors.sekizai',\n                                    'common.djangoapps.edxmako.shortcuts.marketing_link_context_processor',\n                                    'lms.djangoapps.courseware.context_processor.user_timezone_locale_prefs',\n                                    'help_tokens.context_processor',\n                                    'openedx.core.djangoapps.site_configuration.context_processors.configuration_context',\n                                    'lms.djangoapps.mobile_api.context_processor.is_from_mobile_app',\n                                    'social_django.context_processors.backends',\n                                    'social_django.context_processors.login_redirect'],\n             'debug': True,\n             'loaders': ['openedx.core.djangoapps.theming.template_loaders.ThemeTemplateLoader',\n                         'common.djangoapps.edxmako.makoloader.MakoFilesystemLoader',\n                         'common.djangoapps.edxmako.makoloader.MakoAppDirectoriesLoader']}}
\n
DEFAULT_TEMPLATE_ENGINE_DIRS\n
[Path('/edx/app/edxapp/edx-platform/lms/templates'),\n Path('/edx/app/edxapp/edx-platform/common/templates'),\n Path('/edx/app/edxapp/edx-platform/common/lib/capa/capa/templates'),\n Path('/edx/app/edxapp/edx-platform/common/djangoapps/pipeline_mako/templates'),\n Path('/edx/app/edxapp/edx-platform/common/static')]
\n
DEPRECATED_ADVANCED_COMPONENT_TYPES\n
[]
\n
DEV_CONTENT\n
True
\n
DISABLE_ACCOUNT_ACTIVATION_REQUIREMENT_SWITCH\n
'verify_student_disable_account_activation_requirement'
\n
DISALLOWED_USER_AGENTS\n
[]
\n
DISCUSSIONS_HELP_URL\n
'https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_components/create_discussion.html'
\n
DISCUSSIONS_MICROFRONTEND_URL\n
'http://localhost:2002'
\n
DISCUSSION_SETTINGS\n
{'COURSE_PUBLISH_TASK_DELAY': 30, 'MAX_COMMENT_DEPTH': 2}
\n
DJFS\n
{'directory_root': 'lms/static/djpyfs',\n 'type': 'osfs',\n 'url_root': '/static/djpyfs'}
\n
DOC_STORE_CONFIG\n
{'authsource': '',\n 'collection': 'modulestore',\n 'connectTimeoutMS': 2000,\n 'db': 'edxapp',\n 'host': ['edx.devstack.mongo'],\n 'password': '********************',\n 'port': 27017,\n 'read_preference': 'SECONDARY_PREFERRED',\n 'replicaSet': '',\n 'socketTimeoutMS': 3000,\n 'ssl': False,\n 'user': 'edxapp'}
\n
ECOMMERCE_API_SIGNING_KEY\n
'********************'
\n
ECOMMERCE_API_TIMEOUT\n
'********************'
\n
ECOMMERCE_API_URL\n
'********************'
\n
ECOMMERCE_ORDERS_API_CACHE_TIMEOUT\n
'********************'
\n
ECOMMERCE_PUBLIC_URL_ROOT\n
'http://localhost:18130'
\n
ECOMMERCE_SERVICE_WORKER_USERNAME\n
'ecommerce_worker'
\n
EDXMKTG_LOGGED_IN_COOKIE_NAME\n
'edxloggedin'
\n
EDXMKTG_USER_INFO_COOKIE_NAME\n
'edx-user-info'
\n
EDXMKTG_USER_INFO_COOKIE_VERSION\n
1
\n
EDXNOTES_CLIENT_NAME\n
'edx_notes_api-backend-service'
\n
EDXNOTES_CONNECT_TIMEOUT\n
0.5
\n
EDXNOTES_HELP_URL\n
'https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/exercises_tools/notes.html'
\n
EDXNOTES_INTERNAL_API\n
'********************'
\n
EDXNOTES_PUBLIC_API\n
'********************'
\n
EDXNOTES_READ_TIMEOUT\n
1.5
\n
EDX_API_KEY\n
'********************'
\n
EDX_BRAZE_API_KEY\n
'********************'
\n
EDX_BRAZE_API_SERVER\n
'********************'
\n
EDX_DRF_EXTENSIONS\n
{'JWT_PAYLOAD_USER_ATTRIBUTE_MAPPING': {}}
\n
EDX_PLATFORM_REVISION\n
'master'
\n
EDX_ROOT_URL\n
''
\n
ELASTIC_SEARCH_CONFIG\n
[{}]
\n
EMAIL_BACKEND\n
'django.core.mail.backends.filebased.EmailBackend'
\n
EMAIL_FILE_PATH\n
'/edx/src/ace_messages/'
\n
EMAIL_HOST\n
'localhost'
\n
EMAIL_HOST_PASSWORD\n
'********************'
\n
EMAIL_HOST_USER\n
''
\n
EMAIL_OPTIN_MINIMUM_AGE\n
13
\n
EMAIL_PORT\n
25
\n
EMAIL_SSL_CERTFILE\n
None
\n
EMAIL_SSL_KEYFILE\n
'********************'
\n
EMAIL_SUBJECT_PREFIX\n
'[Django] '
\n
EMAIL_TIMEOUT\n
None
\n
EMAIL_USE_LOCALTIME\n
False
\n
EMAIL_USE_SSL\n
False
\n
EMAIL_USE_TLS\n
False
\n
EMBARGO_SITE_REDIRECT_URL\n
None
\n
ENABLE_AUTHN_RESET_PASSWORD_HIBP_POLICY\n
'********************'
\n
ENABLE_CODEJAIL_REST_SERVICE\n
False
\n
ENABLE_COMPREHENSIVE_THEMING\n
False
\n
ENABLE_COPPA_COMPLIANCE\n
False
\n
ENABLE_CREDIT_ELIGIBILITY\n
True
\n
ENABLE_JASMINE\n
False
\n
ENABLE_MKTG_SITE\n
False
\n
ENABLE_MULTICOURSE\n
False
\n
ENABLE_REQUIRE_THIRD_PARTY_AUTH\n
False
\n
ENABLE_SAVE_FOR_LATER\n
False
\n
ENROLLMENT_COURSE_DETAILS_CACHE_TIMEOUT\n
60
\n
ENTERPRISE_ADMIN_PORTAL_BASE_URL\n
'http://localhost:1991'
\n
ENTERPRISE_ADMIN_PORTAL_NETLOC\n
'localhost:1991'
\n
ENTERPRISE_ADMIN_ROLE\n
'enterprise_admin'
\n
ENTERPRISE_ALL_SERVICE_USERNAMES\n
['ecommerce_worker',\n 'enterprise_worker',\n 'license_manager_worker',\n 'enterprise_catalog_worker',\n 'enterprise_channel_worker']
\n
ENTERPRISE_API_CACHE_TIMEOUT\n
'********************'
\n
ENTERPRISE_API_URL\n
'********************'
\n
ENTERPRISE_CATALOG_ADMIN_ROLE\n
'catalog_admin'
\n
ENTERPRISE_CATALOG_INTERNAL_ROOT_URL\n
'http://enterprise.catalog.app:18160'
\n
ENTERPRISE_CONSENT_API_URL\n
'********************'
\n
ENTERPRISE_COURSE_ENROLLMENT_AUDIT_MODES\n
['audit', 'honor']
\n
ENTERPRISE_CUSTOMER_CATALOG_DEFAULT_CONTENT_FILTER\n
{}
\n
ENTERPRISE_CUSTOMER_COOKIE_NAME\n
'enterprise_customer_uuid'
\n
ENTERPRISE_CUSTOMER_LOGO_IMAGE_SIZE\n
512
\n
ENTERPRISE_CUSTOMER_SUCCESS_EMAIL\n
'customersuccess@edx.org'
\n
ENTERPRISE_DASHBOARD_ADMIN_ROLE\n
'dashboard_admin'
\n
ENTERPRISE_ENROLLMENT_API_ADMIN_ROLE\n
'********************'
\n
ENTERPRISE_ENROLLMENT_API_URL\n
'********************'
\n
ENTERPRISE_EXCLUDED_REGISTRATION_FIELDS\n
{'age',\n 'gender',\n 'goals',\n 'level_of_education',\n 'mailing_address',\n 'year_of_birth'}
\n
ENTERPRISE_INTEGRATIONS_EMAIL\n
'enterprise-integrations@edx.org'
\n
ENTERPRISE_LEARNER_PORTAL_BASE_URL\n
'http://localhost:8734'
\n
ENTERPRISE_LEARNER_PORTAL_NETLOC\n
'localhost:8734'
\n
ENTERPRISE_MARKETING_FOOTER_QUERY_PARAMS\n
{}
\n
ENTERPRISE_OPERATOR_ROLE\n
'enterprise_openedx_operator'
\n
ENTERPRISE_PLATFORM_WELCOME_TEMPLATE\n
'Welcome to {platform_name}.'
\n
ENTERPRISE_PROXY_LOGIN_WELCOME_TEMPLATE\n
"{start_bold}{enterprise_name}{end_bold} has partnered with {start_bold}{platform_name}{end_bold} to offer you high-quality learning opportunities from the world's best institutions and universities."
\n
ENTERPRISE_PUBLIC_ENROLLMENT_API_URL\n
'********************'
\n
ENTERPRISE_READONLY_ACCOUNT_FIELDS\n
['username', 'name', 'email', 'country']
\n
ENTERPRISE_REPORTING_CONFIG_ADMIN_ROLE\n
'reporting_config_admin'
\n
ENTERPRISE_SERVICE_WORKER_USERNAME\n
'enterprise_worker'
\n
ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE\n
'You have left the {start_bold}{enterprise_name}{end_bold} website and are now on the {platform_name} site. {enterprise_name} has partnered with {platform_name} to offer you high-quality, always available learning programs to help you advance your knowledge and career. {line_break}Please note that {platform_name} has a different {privacy_policy_link_start}Privacy Policy{privacy_policy_link_end} from {enterprise_name}.'
\n
ENTERPRISE_SUPPORT_URL\n
''
\n
ENTERPRISE_TAGLINE\n
''
\n
ENTITLEMENTS_EXPIRATION_ROUTING_KEY\n
'********************'
\n
ENTITLEMENT_EXPIRED_ALERT_PERIOD\n
90
\n
ENV_CELERY_QUEUES\n
['edx.lms.core.default', 'edx.lms.core.high', 'edx.lms.core.high_mem']
\n
ENV_FEATURES\n
{'AUTH_USE_OPENID_PROVIDER': True,\n 'AUTOMATIC_AUTH_FOR_TESTING': False,\n 'CUSTOM_COURSES_EDX': False,\n 'ENABLE_BULK_ENROLLMENT_VIEW': False,\n 'ENABLE_COMBINED_LOGIN_REGISTRATION': True,\n 'ENABLE_CORS_HEADERS': False,\n 'ENABLE_COUNTRY_ACCESS': False,\n 'ENABLE_CREDIT_API': '********************',\n 'ENABLE_CREDIT_ELIGIBILITY': False,\n 'ENABLE_CROSS_DOMAIN_CSRF_COOKIE': False,\n 'ENABLE_CSMH_EXTENDED': True,\n 'ENABLE_DISCUSSION_HOME_PANEL': True,\n 'ENABLE_DISCUSSION_SERVICE': True,\n 'ENABLE_EDXNOTES': True,\n 'ENABLE_ENROLLMENT_RESET': False,\n 'ENABLE_EXPORT_GIT': False,\n 'ENABLE_GRADE_DOWNLOADS': True,\n 'ENABLE_LTI_PROVIDER': False,\n 'ENABLE_MKTG_SITE': False,\n 'ENABLE_MOBILE_REST_API': '********************',\n 'ENABLE_OAUTH2_PROVIDER': False,\n 'ENABLE_PUBLISHER': False,\n 'ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES': True,\n 'ENABLE_SPECIAL_EXAMS': False,\n 'ENABLE_SYSADMIN_DASHBOARD': False,\n 'ENABLE_THIRD_PARTY_AUTH': True,\n 'ENABLE_VIDEO_UPLOAD_PIPELINE': False,\n 'PREVIEW_LMS_BASE': 'preview.localhost:18000',\n 'SHOW_FOOTER_LANGUAGE_SELECTOR': False,\n 'SHOW_HEADER_LANGUAGE_SELECTOR': False}
\n
ENV_ROOT\n
Path('/edx/app/edxapp')
\n
ENV_TOKENS\n
'********************'
\n
EVENT_TRACKING_BACKENDS\n
{'segmentio': {'ENGINE': 'eventtracking.backends.routing.RoutingBackend',\n               'OPTIONS': {'backends': {'segment': {'ENGINE': 'eventtracking.backends.segment.SegmentBackend'}},\n                           'processors': [{'ENGINE': 'eventtracking.processors.whitelist.NameWhitelistProcessor',\n                                           'OPTIONS': {'whitelist': []}},\n                                          {'ENGINE': 'common.djangoapps.track.shim.GoogleAnalyticsProcessor'}]}},\n 'tracking_logs': {'ENGINE': 'eventtracking.backends.routing.RoutingBackend',\n                   'OPTIONS': {'backends': {'logger': {'ENGINE': 'eventtracking.backends.logger.LoggerBackend',\n                                                       'OPTIONS': {'max_event_size': 50000,\n                                                                   'name': 'tracking'}}},\n                               'processors': [{'ENGINE': 'common.djangoapps.track.shim.LegacyFieldMappingProcessor'},\n                                              {'ENGINE': 'common.djangoapps.track.shim.PrefixedEventProcessor'}]}}}
\n
EVENT_TRACKING_ENABLED\n
True
\n
EVENT_TRACKING_PROCESSORS\n
[]
\n
EVENT_TRACKING_SEGMENTIO_EMIT_WHITELIST\n
[]
\n
EXPLICIT_QUEUES\n
{'common.djangoapps.entitlements.tasks.expire_old_entitlements': {'queue': 'edx.lms.core.default'},\n 'lms.djangoapps.bulk_email.tasks.send_course_email': {'queue': 'edx.lms.core.high'},\n 'lms.djangoapps.grades.tasks.recalculate_course_and_subsection_grades_for_user': {'queue': 'edx.lms.core.default'},\n 'lms.djangoapps.grades.tasks.recalculate_subsection_grade_v3': {'queue': 'edx.lms.core.default'},\n 'lms.djangoapps.instructor_task.tasks.calculate_grades_csv': {'queue': 'edx.lms.core.high_mem'},\n 'lms.djangoapps.instructor_task.tasks.calculate_problem_grade_report': {'queue': 'edx.lms.core.high_mem'},\n 'lms.djangoapps.instructor_task.tasks.generate_certificates': {'queue': 'edx.lms.core.high_mem'},\n 'lms.djangoapps.verify_student.tasks.send_ace_message': {'queue': 'edx.lms.core.default'},\n 'lms.djangoapps.verify_student.tasks.send_request_to_ss_for_user': {'queue': 'edx.lms.core.high'},\n 'lms.djangoapps.verify_student.tasks.send_verification_status_email': {'queue': 'edx.lms.core.default'},\n 'openedx.core.djangoapps.content.course_overviews.tasks.async_course_overview_update': {'queue': 'edx.lms.core.high_mem'},\n 'openedx.core.djangoapps.heartbeat.tasks.sample_task': {'queue': 'edx.lms.core.high'},\n 'openedx.core.djangoapps.programs.tasks.award_course_certificate': {'queue': 'edx.lms.core.default'},\n 'openedx.core.djangoapps.programs.tasks.award_program_certificates': {'queue': 'edx.lms.core.default'},\n 'openedx.core.djangoapps.programs.tasks.revoke_program_certificates': {'queue': 'edx.lms.core.default'},\n 'openedx.core.djangoapps.programs.tasks.update_certificate_visible_date_on_course_update': {'queue': 'edx.lms.core.default'},\n 'openedx.core.djangoapps.schedules.tasks._course_update_schedule_send': {'queue': 'edx.lms.core.default'},\n 'openedx.core.djangoapps.schedules.tasks._recurring_nudge_schedule_send': {'queue': 'edx.lms.core.default'},\n 'openedx.core.djangoapps.schedules.tasks._upgrade_reminder_schedule_send': {'queue': 'edx.lms.core.default'},\n 'openedx.core.djangoapps.schedules.tasks.v1.tasks.send_grade_to_credentials': {'queue': 'edx.lms.core.default'}}
\n
EXTRA_MIDDLEWARE_CLASSES\n
[]
\n
FACEBOOK_API_VERSION\n
'********************'
\n
FACEBOOK_APP_ID\n
'FACEBOOK_APP_ID'
\n
FACEBOOK_APP_SECRET\n
'********************'
\n
FAVICON_PATH\n
'images/favicon.ico'
\n
FAVICON_URL\n
None
\n
FEATURES\n
{'ALLOW_ADMIN_ENTERPRISE_COURSE_ENROLLMENT_DELETION': False,\n 'ALLOW_AUTOMATED_SIGNUPS': False,\n 'ALLOW_COURSE_STAFF_GRADE_DOWNLOADS': False,\n 'ALLOW_EMAIL_ADDRESS_CHANGE': True,\n 'ALLOW_HIDING_DISCUSSION_TAB': False,\n 'ALLOW_PUBLIC_ACCOUNT_CREATION': True,\n 'ALLOW_WIKI_ROOT_ACCESS': True,\n 'ALWAYS_REDIRECT_HOMEPAGE_TO_DASHBOARD_FOR_AUTHENTICATED_USER': True,\n 'ANNOUNCEMENTS_PER_PAGE': 5,\n 'AUTH_USE_OPENID_PROVIDER': True,\n 'AUTOMATIC_AUTH_FOR_TESTING': True,\n 'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True,\n 'AUTOPLAY_VIDEOS': False,\n 'BATCH_ENROLLMENT_NOTIFY_USERS_DEFAULT': True,\n 'CERTIFICATES_HTML_VIEW': True,\n 'CERTIFICATES_INSTRUCTOR_GENERATION': False,\n 'COURSES_ARE_BROWSABLE': True,\n 'COURSES_ARE_BROWSEABLE': True,\n 'CUSTOM_CERTIFICATE_TEMPLATES_ENABLED': False,\n 'CUSTOM_COURSES_EDX': False,\n 'DISABLE_AUDIT_CERTIFICATES': False,\n 'DISABLE_HONOR_CERTIFICATES': False,\n 'DISABLE_LOGIN_BUTTON': False,\n 'DISABLE_MOBILE_COURSE_AVAILABLE': False,\n 'DISABLE_START_DATES': False,\n 'DISPLAY_ANALYTICS_ENROLLMENTS': True,\n 'DISPLAY_DEBUG_INFO_TO_STAFF': True,\n 'DISPLAY_HISTOGRAMS_TO_STAFF': False,\n 'EMBARGO': False,\n 'ENABLED_PAYMENT_REPORTS': ['refund_report',\n                             'itemized_purchase_report',\n                             'university_revenue_share',\n                             'certificate_status'],\n 'ENABLE_ACCOUNT_DELETION': True,\n 'ENABLE_ANNOUNCEMENTS': False,\n 'ENABLE_AUTHN_MICROFRONTEND': False,\n 'ENABLE_AUTOADVANCE_VIDEOS': False,\n 'ENABLE_AUTOMATED_SIGNUPS_EXTRA_FIELDS': False,\n 'ENABLE_BULK_ENROLLMENT_VIEW': False,\n 'ENABLE_BULK_USER_RETIREMENT': False,\n 'ENABLE_CCX_ANALYTICS_DASHBOARD_URL': False,\n 'ENABLE_CHANGE_USER_PASSWORD_ADMIN': '********************',\n 'ENABLE_COMBINED_LOGIN_REGISTRATION': True,\n 'ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER': False,\n 'ENABLE_COOKIE_CONSENT': False,\n 'ENABLE_CORS_HEADERS': True,\n 'ENABLE_COSMETIC_DISPLAY_PRICE': True,\n 'ENABLE_COUNTRY_ACCESS': False,\n 'ENABLE_COURSEWARE_SEARCH': False,\n 'ENABLE_COURSEWARE_SEARCH_FOR_COURSE_STAFF': True,\n 'ENABLE_COURSE_ASSESSMENT_GRADE_CHANGE_SIGNAL': False,\n 'ENABLE_COURSE_DISCOVERY': False,\n 'ENABLE_COURSE_FILENAME_CCX_SUFFIX': False,\n 'ENABLE_COURSE_HOME_REDIRECT': True,\n 'ENABLE_COURSE_OLX_VALIDATION': False,\n 'ENABLE_COURSE_SORTING_BY_START_DATE': True,\n 'ENABLE_CREDIT_API': '********************',\n 'ENABLE_CREDIT_ELIGIBILITY': False,\n 'ENABLE_CROSS_DOMAIN_CSRF_COOKIE': False,\n 'ENABLE_CSMH_EXTENDED': True,\n 'ENABLE_DASHBOARD_SEARCH': False,\n 'ENABLE_DEBUG_RUN_PYTHON': False,\n 'ENABLE_DISCUSSION_EMAIL_DIGEST': False,\n 'ENABLE_DISCUSSION_HOME_PANEL': True,\n 'ENABLE_DISCUSSION_SERVICE': True,\n 'ENABLE_DJANGO_ADMIN_SITE': True,\n 'ENABLE_EDXNOTES': True,\n 'ENABLE_ENROLLMENT_RESET': True,\n 'ENABLE_ENROLLMENT_TRACK_USER_PARTITION': True,\n 'ENABLE_ENTERPRISE_INTEGRATION': True,\n 'ENABLE_EXPORT_GIT': False,\n 'ENABLE_FOOTER_MOBILE_APP_LINKS': False,\n 'ENABLE_GRADE_DOWNLOADS': True,\n 'ENABLE_HELP_LINK': True,\n 'ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA': False,\n 'ENABLE_INSTRUCTOR_BACKGROUND_TASKS': True,\n 'ENABLE_LMS_MIGRATION': False,\n 'ENABLE_LOGIN_MICROFRONTEND': False,\n 'ENABLE_LTI_PROVIDER': False,\n 'ENABLE_MASQUERADE': True,\n 'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': False,\n 'ENABLE_MKTG_EMAIL_OPT_IN': False,\n 'ENABLE_MKTG_SITE': False,\n 'ENABLE_MOBILE_REST_API': '********************',\n 'ENABLE_OAUTH2_PROVIDER': True,\n 'ENABLE_ONE_CLICK_PROGRAM_PURCHASE': False,\n 'ENABLE_OPENBADGES': False,\n 'ENABLE_ORA_ALL_FILE_URLS': False,\n 'ENABLE_ORA_USERNAMES_ON_DATA_EXPORT': False,\n 'ENABLE_ORA_USER_STATE_UPLOAD_DATA': False,\n 'ENABLE_PASSWORD_RESET_FAILURE_EMAIL': '********************',\n 'ENABLE_PREREQUISITE_COURSES': True,\n 'ENABLE_PUBLISHER': False,\n 'ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES': True,\n 'ENABLE_SERVICE_STATUS': False,\n 'ENABLE_SOFTWARE_SECURE_FAKE': True,\n 'ENABLE_SPECIAL_EXAMS': True,\n 'ENABLE_STUDENT_HISTORY_VIEW': True,\n 'ENABLE_SYSADMIN_DASHBOARD': False,\n 'ENABLE_TEAMS': True,\n 'ENABLE_TEXTBOOK': True,\n 'ENABLE_THIRD_PARTY_AUTH': True,\n 'ENABLE_UNICODE_USERNAME': False,\n 'ENABLE_V2_CERT_DISPLAY_SETTINGS': False,\n 'ENABLE_VERIFIED_CERTIFICATES': False,\n 'ENABLE_VIDEO_ABSTRACTION_LAYER_API': '********************',\n 'ENABLE_VIDEO_BUMPER': False,\n 'ENABLE_VIDEO_UPLOAD_PIPELINE': False,\n 'ENABLE_XBLOCK_VIEW_ENDPOINT': False,\n 'ENABLE_XBLOCK_XML_VALIDATION': True,\n 'ENTRANCE_EXAMS': True,\n 'EXPOSE_CACHE_PROGRAMS_ENDPOINT': False,\n 'FALLBACK_TO_ENGLISH_TRANSCRIPTS': True,\n 'HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED': False,\n 'INDIVIDUAL_DUE_DATES': False,\n 'LICENSING': True,\n 'LOG_POSTPAY_CALLBACKS': True,\n 'MAX_ENROLLMENT_INSTR_BUTTONS': 200,\n 'MAX_PROBLEM_RESPONSES_COUNT': 5000,\n 'MILESTONES_APP': True,\n 'MODE_CREATION_FOR_TESTING': False,\n 'PREVENT_CONCURRENT_LOGINS': False,\n 'PREVIEW_LMS_BASE': 'preview.localhost:18000',\n 'REROUTE_ACTIVATION_EMAIL': False,\n 'RESTRICT_AUTOMATIC_AUTH': True,\n 'SHOW_BUMPER_PERIODICITY': 604800,\n 'SHOW_FOOTER_LANGUAGE_SELECTOR': False,\n 'SHOW_HEADER_LANGUAGE_SELECTOR': True,\n 'SKIP_EMAIL_VALIDATION': False,\n 'SQUELCH_PII_IN_LOGS': False,\n 'UNSUPPORTED_BROWSER_ALERT_VERSIONS': '{i:10,f:-3,o:-3,s:-3,c:-3}',\n 'test_django_plugin': True}
\n
FEEDBACK_SUBMISSION_EMAIL\n
''
\n
FERNET_KEYS\n
'********************'
\n
FIELDS_STORED_IN_SESSION\n
['auth_entry', 'next']
\n
FIELD_OVERRIDE_PROVIDERS\n
('openedx.features.personalized_learner_schedules.show_answer.show_answer_field_override.ShowAnswerFieldOverride',)
\n
FILE_UPLOAD_DIRECTORY_PERMISSIONS\n
None
\n
FILE_UPLOAD_HANDLERS\n
['django.core.files.uploadhandler.MemoryFileUploadHandler',\n 'django.core.files.uploadhandler.TemporaryFileUploadHandler']
\n
FILE_UPLOAD_MAX_MEMORY_SIZE\n
2621440
\n
FILE_UPLOAD_PERMISSIONS\n
420
\n
FILE_UPLOAD_STORAGE_BUCKET_NAME\n
'SET-ME-PLEASE (ex. bucket-name)'
\n
FILE_UPLOAD_STORAGE_PREFIX\n
'submissions_attachments'
\n
FILE_UPLOAD_TEMP_DIR\n
None
\n
FINANCE_EMAIL\n
''
\n
FINANCIAL_ASSISTANCE_MAX_LENGTH\n
2500
\n
FINANCIAL_ASSISTANCE_MIN_LENGTH\n
1250
\n
FINANCIAL_REPORTS\n
{'BUCKET': None, 'ROOT_PATH': 'sandbox', 'STORAGE_TYPE': 'localfs'}
\n
FIRST_DAY_OF_WEEK\n
0
\n
FIXTURE_DIRS\n
[]
\n
FOOTER_BROWSER_CACHE_MAX_AGE\n
300
\n
FOOTER_CACHE_TIMEOUT\n
1800
\n
FOOTER_CSS\n
{'edx': {'ltr': 'style-lms-footer-edx', 'rtl': 'style-lms-footer-edx-rtl'},\n 'openedx': {'ltr': 'style-lms-footer', 'rtl': 'style-lms-footer-rtl'}}
\n
FOOTER_OPENEDX_LOGO_IMAGE\n
'https://files.edx.org/openedx-logos/open-edx-logo-tag.png'
\n
FOOTER_OPENEDX_URL\n
'https://open.edx.org'
\n
FOOTER_ORGANIZATION_IMAGE\n
'images/logo.png'
\n
FORCE_SCRIPT_NAME\n
None
\n
FORMAT_MODULE_PATH\n
None
\n
FORM_RENDERER\n
'django.forms.renderers.DjangoTemplates'
\n
GENERATE_PROFILE_SCORES\n
False
\n
GEOIP_PATH\n
Path('/edx/app/edxapp/edx-platform/common/static/data/geoip/GeoLite2-Country.mmdb')
\n
GITHUB_REPO_ROOT\n
'/edx/var/edxapp/data'
\n
GIT_REPO_DIR\n
'/edx/var/edxapp/course_repos'
\n
GOOGLE_ANALYTICS_ACCOUNT\n
None
\n
GOOGLE_ANALYTICS_LINKEDIN\n
''
\n
GOOGLE_ANALYTICS_TRACKING_ID\n
''
\n
GOOGLE_SITE_VERIFICATION_ID\n
''
\n
GRADES_DOWNLOAD\n
{'BUCKET': '',\n 'ROOT_PATH': '',\n 'STORAGE_CLASS': 'django.core.files.storage.FileSystemStorage',\n 'STORAGE_KWARGS': {'location': '/tmp/edx-s3/grades'},\n 'STORAGE_TYPE': ''}
\n
GRADES_DOWNLOAD_ROUTING_KEY\n
'********************'
\n
HEARTBEAT_CELERY_ROUTING_KEY\n
'********************'
\n
HEARTBEAT_CELERY_TIMEOUT\n
5
\n
HEARTBEAT_CHECKS\n
['openedx.core.djangoapps.heartbeat.default_checks.check_modulestore',\n 'openedx.core.djangoapps.heartbeat.default_checks.check_database']
\n
HEARTBEAT_EXTENDED_CHECKS\n
('openedx.core.djangoapps.heartbeat.default_checks.check_celery',)
\n
HELP_TOKENS_BOOKS\n
'********************'
\n
HELP_TOKENS_INI_FILE\n
'********************'
\n
HELP_TOKENS_LANGUAGE_CODE\n
'********************'
\n
HELP_TOKENS_VERSION\n
'********************'
\n
HIGH_MEM_QUEUE\n
'edx.lms.core.high_mem'
\n
HIGH_PRIORITY_QUEUE\n
'edx.lms.core.high'
\n
HOMEPAGE_COURSE_MAX\n
9
\n
HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS\n
{'preview.localhost': 'draft-preferred'}
\n
HTTPS\n
'off'
\n
ICP_LICENSE\n
None
\n
ICP_LICENSE_INFO\n
{}
\n
IDA_LOGOUT_URI_LIST\n
['http://localhost:18130/logout/',\n 'http://localhost:18150/logout/',\n 'http://localhost:18381/logout/',\n 'http://localhost:18010/logout/']
\n
ID_VERIFICATION_SUPPORT_LINK\n
''
\n
IGNORABLE_404_URLS\n
[]
\n
INSTALLED_APPS\n
['django.contrib.auth',\n 'django.contrib.contenttypes',\n 'django.contrib.humanize',\n 'django.contrib.messages',\n 'django.contrib.redirects',\n 'django.contrib.sessions',\n 'django.contrib.sites',\n 'django.contrib.staticfiles',\n 'django_celery_results',\n 'openedx.core.djangoapps.common_initialization.apps.CommonInitializationConfig',\n 'lms.djangoapps.lms_initialization.apps.LMSInitializationConfig',\n 'openedx.core.djangoapps.common_views',\n 'simple_history',\n 'config_models',\n 'openedx.core.djangoapps.config_model_utils',\n 'waffle',\n 'openedx.core.djangoapps.service_status',\n 'common.djangoapps.status',\n 'common.djangoapps.edxmako.apps.EdxMakoConfig',\n 'pipeline',\n 'common.djangoapps.static_replace',\n 'webpack_loader',\n 'web_fragments',\n 'openedx.core.djangoapps.plugin_api',\n 'openedx.core.djangoapps.contentserver',\n 'openedx.core.djangoapps.site_configuration',\n 'openedx.core.djangoapps.video_config',\n 'openedx.core.djangoapps.video_pipeline',\n 'lms.djangoapps.courseware',\n 'lms.djangoapps.coursewarehistoryextended',\n 'common.djangoapps.student.apps.StudentConfig',\n 'common.djangoapps.split_modulestore_django.apps.SplitModulestoreDjangoBackendAppConfig',\n 'lms.djangoapps.static_template_view',\n 'lms.djangoapps.staticbook',\n 'common.djangoapps.track',\n 'eventtracking.django.apps.EventTrackingConfig',\n 'common.djangoapps.util',\n 'lms.djangoapps.certificates.apps.CertificatesConfig',\n 'lms.djangoapps.instructor_task',\n 'openedx.core.djangoapps.course_groups',\n 'lms.djangoapps.bulk_email',\n 'lms.djangoapps.branding',\n 'lms.djangoapps.course_home_api',\n 'lms.djangoapps.user_tours',\n 'openedx.core.djangoapps.xblock.apps.LmsXBlockAppConfig',\n 'lms.djangoapps.support',\n 'oauth2_provider',\n 'openedx.core.djangoapps.oauth_dispatch.apps.OAuthDispatchAppConfig',\n 'common.djangoapps.third_party_auth',\n 'openedx.core.djangoapps.system_wide_roles',\n 'openedx.core.djangoapps.auth_exchange',\n 'wiki',\n 'django_notify',\n 'lms.djangoapps.course_wiki',\n 'mptt',\n 'sekizai',\n 'wiki.plugins.links',\n 'lms.djangoapps.course_wiki.plugins.markdownedx',\n 'django.contrib.admin',\n 'lms.djangoapps.debug',\n 'openedx.core.djangoapps.util.apps.UtilConfig',\n 'openedx.core.djangoapps.django_comment_common',\n 'lms.djangoapps.edxnotes',\n 'splash',\n 'rest_framework',\n 'openedx.core.djangoapps.user_api',\n 'common.djangoapps.course_modes.apps.CourseModesConfig',\n 'openedx.core.djangoapps.enrollments.apps.EnrollmentsConfig',\n 'common.djangoapps.entitlements.apps.EntitlementsConfig',\n 'lms.djangoapps.bulk_enroll',\n 'lms.djangoapps.verify_student.apps.VerifyStudentConfig',\n 'openedx.core.djangoapps.dark_lang',\n 'lms.djangoapps.rss_proxy',\n 'openedx.core.djangoapps.embargo',\n 'common.djangoapps.course_action_state',\n 'edx_jsme',\n 'django_countries',\n 'lms.djangoapps.mobile_api.apps.MobileApiConfig',\n 'social_django',\n 'lms.djangoapps.survey.apps.SurveyConfig',\n 'lms.djangoapps.lms_xblock.apps.LMSXBlockConfig',\n 'submissions',\n 'openassessment',\n 'openassessment.assessment',\n 'openassessment.fileupload',\n 'openassessment.workflow',\n 'openassessment.xblock',\n 'edxval',\n 'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig',\n 'openedx.core.djangoapps.content.block_structure.apps.BlockStructureConfig',\n 'lms.djangoapps.course_blocks',\n 'openedx.core.djangoapps.coursegraph.apps.CoursegraphConfig',\n 'lms.djangoapps.mailing',\n 'corsheaders',\n 'openedx.core.djangoapps.cors_csrf',\n 'lms.djangoapps.commerce.apps.CommerceConfig',\n 'openedx.core.djangoapps.credit.apps.CreditConfig',\n 'lms.djangoapps.teams',\n 'common.djangoapps.xblock_django',\n 'openedx.core.djangoapps.programs.apps.ProgramsConfig',\n 'openedx.core.djangoapps.catalog',\n 'openedx.core.djangoapps.self_paced',\n 'sorl.thumbnail',\n 'milestones',\n 'lms.djangoapps.gating.apps.GatingConfig',\n 'statici18n',\n 'openedx.core.djangoapps.api_admin',\n 'openedx.core.djangoapps.verified_track_content',\n 'lms.djangoapps.learner_dashboard',\n 'lms.djangoapps.badges.apps.BadgesConfig',\n 'django_sites_extensions',\n 'lms.djangoapps.email_marketing.apps.EmailMarketingConfig',\n 'release_util',\n 'rules.apps.AutodiscoverRulesConfig',\n 'bridgekeeper',\n 'celery_utils',\n 'openedx.core.djangoapps.crawlers',\n 'common.djangoapps.database_fixups',\n 'openedx.core.djangoapps.waffle_utils',\n 'lms.djangoapps.course_goals.apps.CourseGoalsConfig',\n 'openedx.features.calendar_sync',\n 'openedx.features.course_bookmarks',\n 'openedx.features.course_experience',\n 'openedx.features.course_search',\n 'openedx.features.enterprise_support.apps.EnterpriseSupportConfig',\n 'openedx.features.learner_profile',\n 'openedx.features.course_duration_limits',\n 'openedx.features.content_type_gating',\n 'openedx.features.discounts',\n 'openedx.features.effort_estimation',\n 'openedx.features.name_affirmation_api.apps.NameAffirmationApiConfig',\n 'lms.djangoapps.experiments',\n 'django_filters',\n 'drf_yasg',\n 'csrf.apps.CsrfAppConfig',\n 'xss_utils',\n 'openedx.core.djangoapps.heartbeat',\n 'openedx.core.djangoapps.course_date_signals',\n 'openedx.core.djangoapps.external_user_ids',\n 'openedx.core.djangoapps.demographics',\n 'openedx.core.djangoapps.schedules',\n 'rest_framework_jwt',\n 'openedx.core.djangoapps.content.learning_sequences.apps.LearningSequencesConfig',\n 'ratelimitbackend',\n 'organizations',\n 'lms.djangoapps.bulk_user_retirement',\n 'openedx.core.djangoapps.agreements',\n 'edx_django_utils.user',\n 'pylti1p3.contrib.django.lti1p3_tool_config',\n 'edx_ace',\n 'lms.djangoapps.save_for_later',\n 'edx_sga',\n 'enterprise',\n 'consent',\n 'integrated_channels.integrated_channel',\n 'integrated_channels.degreed',\n 'integrated_channels.degreed2',\n 'integrated_channels.sap_success_factors',\n 'integrated_channels.cornerstone',\n 'integrated_channels.xapi',\n 'integrated_channels.blackboard',\n 'integrated_channels.canvas',\n 'integrated_channels.moodle',\n 'django_object_actions',\n 'openedx.core.djangoapps.ace_common.apps.AceCommonConfig',\n 'openedx.features.announcements.apps.AnnouncementsConfig',\n 'openedx.core.djangoapps.bookmarks.apps.BookmarksConfig',\n 'openedx.core.djangoapps.content_libraries.apps.ContentLibrariesConfig',\n 'openedx.core.djangoapps.course_apps.apps.CourseAppsConfig',\n 'openedx.core.djangoapps.courseware_api.apps.CoursewareAPIConfig',\n 'openedx.core.djangoapps.credentials.apps.CredentialsConfig',\n 'lms.djangoapps.discussion.apps.DiscussionConfig',\n 'openedx.core.djangoapps.discussions.apps.DiscussionsConfig',\n 'lms.djangoapps.grades.apps.GradesConfig',\n 'lms.djangoapps.instructor.apps.InstructorConfig',\n 'openedx.core.djangoapps.password_policy.apps.PasswordPolicyConfig',\n 'openedx.core.djangoapps.plugins.apps.PluginsConfig',\n 'lms.djangoapps.program_enrollments.apps.ProgramEnrollmentsConfig',\n 'openedx.core.djangoapps.theming.apps.ThemingConfig',\n 'openedx.core.djangoapps.user_authn.apps.UserAuthnConfig',\n 'openedx.core.djangoapps.zendesk_proxy.apps.ZendeskProxyConfig',\n 'edx_toggles.apps.TogglesConfig',\n 'super_csv.apps.SuperCSVConfig',\n 'bulk_grades.apps.BulkGradesConfig',\n 'completion.apps.CompletionAppConfig',\n 'edx_proctoring.apps.EdxProctoringConfig',\n 'lti_consumer.apps.LTIConsumerApp',\n 'edx_name_affirmation.apps.EdxNameAffirmationConfig',\n 'edx_when.apps.EdxWhenConfig',\n 'debug_toolbar']
\n
INTEGRATED_CHANNELS_API_CHUNK_TRANSMISSION_LIMIT\n
'********************'
\n
INTERNAL_IPS\n
('127.0.0.1',)
\n
INVOICE_CORP_ADDRESS\n
'Please place your corporate address\\nin this configuration'
\n
INVOICE_PAYMENT_INSTRUCTIONS\n
'This is where you can\\nput directions on how people\\nbuying registration codes'
\n
JWT_AUTH\n
{'JWT_ALGORITHM': 'HS256',\n 'JWT_AUDIENCE': 'lms-key',\n 'JWT_AUTH_COOKIE': 'edx-jwt-cookie',\n 'JWT_AUTH_COOKIE_HEADER_PAYLOAD': 'edx-jwt-cookie-header-payload',\n 'JWT_AUTH_COOKIE_SIGNATURE': '********************',\n 'JWT_AUTH_HEADER_PREFIX': 'JWT',\n 'JWT_AUTH_REFRESH_COOKIE': 'edx-jwt-refresh-cookie',\n 'JWT_DECODE_HANDLER': 'edx_rest_framework_extensions.auth.jwt.decoder.jwt_decode_handler',\n 'JWT_EXPIRATION': 30,\n 'JWT_IN_COOKIE_EXPIRATION': 3600,\n 'JWT_ISSUER': 'http://localhost:18000/oauth2',\n 'JWT_ISSUERS': [{'AUDIENCE': 'lms-key',\n                  'ISSUER': 'http://localhost:18000/oauth2',\n                  'SECRET_KEY': '********************'}],\n 'JWT_LEEWAY': 1,\n 'JWT_LOGIN_CLIENT_ID': 'login-service-client-id',\n 'JWT_LOGIN_SERVICE_USERNAME': 'login_service_user',\n 'JWT_PAYLOAD_GET_USERNAME_HANDLER': <function <lambda> at 0x7f0032c42d30>,\n 'JWT_PRIVATE_SIGNING_JWK': '{"e": "AQAB", "d": '\n                            '"RQ6k4NpRU3RB2lhwCbQ452W86bMMQiPsa7EJiFJUg-qBJthN0FMNQVbArtrCQ0xA1BdnQHThFiUnHcXfsTZUwmwvTuiqEGR_MI6aI7h5D8vRj_5x-pxOz-0MCB8TY8dcuK9FkljmgtYvV9flVzCk_uUb3ZJIBVyIW8En7n7nV7JXpS9zey1yVLld2AbRG6W5--Pgqr9JCI5-bLdc2otCLuen2sKyuUDHO5NIj30qGTaKUL-OW_PgVmxrwKwccF3w5uGNEvMQ-IcicosCOvzBwdIm1uhdm9rnHU1-fXz8VLRHNhGVv7z6moghjNI0_u4smhUkEsYeshPv7RQEWTdkOQ", '\n                            '"n": '\n                            '"smKFSYowG6nNUAdeqH1jQQnH1PmIHphzBmwJ5vRf1vu48BUI5VcVtUWIPqzRK_LDSlZYh9D0YFL0ZTxIrlb6Tn3Xz7pYvpIAeYuQv3_H5p8tbz7Fb8r63c1828wXPITVTv8f7oxx5W3lFFgpFAyYMmROC4Ee9qG5T38LFe8_oAuFCEntimWxN9F3P-FJQy43TL7wG54WodgiM0EgzkeLr5K6cDnyckWjTuZbWI-4ffcTgTZsL_Kq1owa_J2ngEfxMCObnzGy5ZLcTUomo4rZLjghVpq6KZxfS6I1Vz79ZsMVUWEdXOYePCKKsrQG20ogQEkmTf9FT_SouC6jPcHLXw", '\n                            '"q": '\n                            '"7KWj7l-ZkfCElyfvwsl7kiosvi-ppOO7Imsv90cribf88DexcO67xdMPesjM9Nh5X209IT-TzbsOtVTXSQyEsy42NY72WETnd1_nAGLAmfxGdo8VV4ZDnRsA8N8POnWjRDwYlVBUEEeuT_MtMWzwIKU94bzkWVnHCY5vbhBYLeM", '\n                            '"p": '\n                            '"wPkfnjavNV1Hqb5Qqj2crBS9HQS6GDQIZ7WF9hlBb2ofDNe2K2dunddFqCOdvLXr7ydRcK51ZwSeHjcjgD1aJkHA9i1zqyboxgd0uAbxVDo6ohnlVqYLtap2tXXcavKm4C9MTpob_rk6FBfEuq4uSsuxFvCER4yG3CYBBa4gZVU", '\n                            '"kid": "devstack_key", "kty": "RSA"}',\n 'JWT_PUBLIC_SIGNING_JWK_SET': '{"keys": [{"kid": "devstack_key", "e": "AQAB", '\n                               '"kty": "RSA", "n": '\n                               '"smKFSYowG6nNUAdeqH1jQQnH1PmIHphzBmwJ5vRf1vu48BUI5VcVtUWIPqzRK_LDSlZYh9D0YFL0ZTxIrlb6Tn3Xz7pYvpIAeYuQv3_H5p8tbz7Fb8r63c1828wXPITVTv8f7oxx5W3lFFgpFAyYMmROC4Ee9qG5T38LFe8_oAuFCEntimWxN9F3P-FJQy43TL7wG54WodgiM0EgzkeLr5K6cDnyckWjTuZbWI-4ffcTgTZsL_Kq1owa_J2ngEfxMCObnzGy5ZLcTUomo4rZLjghVpq6KZxfS6I1Vz79ZsMVUWEdXOYePCKKsrQG20ogQEkmTf9FT_SouC6jPcHLXw"}]}',\n 'JWT_SECRET_KEY': '********************',\n 'JWT_SIGNING_ALGORITHM': 'RS512',\n 'JWT_SUPPORTED_VERSION': '1.2.0',\n 'JWT_VERIFY_EXPIRATION': True}
\n
JWT_EXPIRATION\n
30
\n
JWT_ISSUER\n
'http://edx.devstack.lms:18000/oauth2'
\n
JWT_PRIVATE_SIGNING_KEY\n
'********************'
\n
KEYS_WITH_MERGED_VALUES\n
'********************'
\n
LANGUAGES\n
[('en', 'English'),\n ('rtl', 'Right-to-Left Test Language'),\n ('eo', 'Dummy Language (Esperanto)'),\n ('am', 'አማርኛ'),\n ('ar', 'العربية'),\n ('az', 'azərbaycanca'),\n ('bg-bg', 'български (България)'),\n ('bn-bd', 'বাংলা (বাংলাদেশ)'),\n ('bn-in', 'বাংলা (ভারত)'),\n ('bs', 'bosanski'),\n ('ca', 'Català'),\n ('ca@valencia', 'Català (València)'),\n ('cs', 'Čeština'),\n ('cy', 'Cymraeg'),\n ('da', 'dansk'),\n ('de-de', 'Deutsch (Deutschland)'),\n ('el', 'Ελληνικά'),\n ('en-uk', 'English (United Kingdom)'),\n ('en@lolcat', 'LOLCAT English'),\n ('en@pirate', 'Pirate English'),\n ('es-419', 'Español (Latinoamérica)'),\n ('es-ar', 'Español (Argentina)'),\n ('es-ec', 'Español (Ecuador)'),\n ('es-es', 'Español (España)'),\n ('es-mx', 'Español (México)'),\n ('es-pe', 'Español (Perú)'),\n ('et-ee', 'Eesti (Eesti)'),\n ('eu-es', 'euskara (Espainia)'),\n ('fa', 'فارسی'),\n ('fa-ir', 'فارسی (ایران)'),\n ('fi-fi', 'Suomi (Suomi)'),\n ('fil', 'Filipino'),\n ('fr', 'Français'),\n ('gl', 'Galego'),\n ('gu', 'ગુજરાતી'),\n ('he', 'עברית'),\n ('hi', 'हिन्दी'),\n ('hr', 'hrvatski'),\n ('hu', 'magyar'),\n ('hy-am', 'Հայերեն (Հայաստան)'),\n ('id', 'Bahasa Indonesia'),\n ('it-it', 'Italiano (Italia)'),\n ('ja-jp', '日本語 (日本)'),\n ('kk-kz', 'қазақ тілі (Қазақстан)'),\n ('km-kh', 'ភាសាខ្មែរ (កម្ពុជា)'),\n ('kn', 'ಕನ್ನಡ'),\n ('ko-kr', '한국어 (대한민국)'),\n ('lt-lt', 'Lietuvių (Lietuva)'),\n ('ml', 'മലയാളം'),\n ('mn', 'Монгол хэл'),\n ('mr', 'मराठी'),\n ('ms', 'Bahasa Melayu'),\n ('nb', 'Norsk bokmål'),\n ('ne', 'नेपाली'),\n ('nl-nl', 'Nederlands (Nederland)'),\n ('or', 'ଓଡ଼ିଆ'),\n ('pl', 'Polski'),\n ('pt-br', 'Português (Brasil)'),\n ('pt-pt', 'Português (Portugal)'),\n ('ro', 'română'),\n ('ru', 'Русский'),\n ('si', 'සිංහල'),\n ('sk', 'Slovenčina'),\n ('sl', 'Slovenščina'),\n ('sq', 'shqip'),\n ('sr', 'Српски'),\n ('sv', 'svenska'),\n ('sw', 'Kiswahili'),\n ('ta', 'தமிழ்'),\n ('te', 'తెలుగు'),\n ('th', 'ไทย'),\n ('tr-tr', 'Türkçe (Türkiye)'),\n ('uk', 'Українська'),\n ('ur', 'اردو'),\n ('vi', 'Tiếng Việt'),\n ('uz', 'Ўзбек'),\n ('zh-cn', '中文 (简体)'),\n ('zh-hk', '中文 (香港)'),\n ('zh-tw', '中文 (台灣)')]
\n
LANGUAGES_BIDI\n
('he', 'ar', 'fa', 'ur', 'fa-ir', 'rtl')
\n
LANGUAGE_CODE\n
'en'
\n
LANGUAGE_COOKIE\n
'openedx-language-preference'
\n
LANGUAGE_COOKIE_AGE\n
None
\n
LANGUAGE_COOKIE_DOMAIN\n
None
\n
LANGUAGE_COOKIE_HTTPONLY\n
False
\n
LANGUAGE_COOKIE_NAME\n
'openedx-language-preference'
\n
LANGUAGE_COOKIE_PATH\n
'/'
\n
LANGUAGE_COOKIE_SAMESITE\n
None
\n
LANGUAGE_COOKIE_SECURE\n
False
\n
LANGUAGE_DICT\n
{'am': 'አማርኛ',\n 'ar': 'العربية',\n 'az': 'azərbaycanca',\n 'bg-bg': 'български (България)',\n 'bn-bd': 'বাংলা (বাংলাদেশ)',\n 'bn-in': 'বাংলা (ভারত)',\n 'bs': 'bosanski',\n 'ca': 'Català',\n 'ca@valencia': 'Català (València)',\n 'cs': 'Čeština',\n 'cy': 'Cymraeg',\n 'da': 'dansk',\n 'de-de': 'Deutsch (Deutschland)',\n 'el': 'Ελληνικά',\n 'en': 'English',\n 'en-uk': 'English (United Kingdom)',\n 'en@lolcat': 'LOLCAT English',\n 'en@pirate': 'Pirate English',\n 'eo': 'Dummy Language (Esperanto)',\n 'es-419': 'Español (Latinoamérica)',\n 'es-ar': 'Español (Argentina)',\n 'es-ec': 'Español (Ecuador)',\n 'es-es': 'Español (España)',\n 'es-mx': 'Español (México)',\n 'es-pe': 'Español (Perú)',\n 'et-ee': 'Eesti (Eesti)',\n 'eu-es': 'euskara (Espainia)',\n 'fa': 'فارسی',\n 'fa-ir': 'فارسی (ایران)',\n 'fi-fi': 'Suomi (Suomi)',\n 'fil': 'Filipino',\n 'fr': 'Français',\n 'gl': 'Galego',\n 'gu': 'ગુજરાતી',\n 'he': 'עברית',\n 'hi': 'हिन्दी',\n 'hr': 'hrvatski',\n 'hu': 'magyar',\n 'hy-am': 'Հայերեն (Հայաստան)',\n 'id': 'Bahasa Indonesia',\n 'it-it': 'Italiano (Italia)',\n 'ja-jp': '日本語 (日本)',\n 'kk-kz': 'қазақ тілі (Қазақстан)',\n 'km-kh': 'ភាសាខ្មែរ (កម្ពុជា)',\n 'kn': 'ಕನ್ನಡ',\n 'ko-kr': '한국어 (대한민국)',\n 'lt-lt': 'Lietuvių (Lietuva)',\n 'ml': 'മലയാളം',\n 'mn': 'Монгол хэл',\n 'mr': 'मराठी',\n 'ms': 'Bahasa Melayu',\n 'nb': 'Norsk bokmål',\n 'ne': 'नेपाली',\n 'nl-nl': 'Nederlands (Nederland)',\n 'or': 'ଓଡ଼ିଆ',\n 'pl': 'Polski',\n 'pt-br': 'Português (Brasil)',\n 'pt-pt': 'Português (Portugal)',\n 'ro': 'română',\n 'rtl': 'Right-to-Left Test Language',\n 'ru': 'Русский',\n 'si': 'සිංහල',\n 'sk': 'Slovenčina',\n 'sl': 'Slovenščina',\n 'sq': 'shqip',\n 'sr': 'Српски',\n 'sv': 'svenska',\n 'sw': 'Kiswahili',\n 'ta': 'தமிழ்',\n 'te': 'తెలుగు',\n 'th': 'ไทย',\n 'tr-tr': 'Türkçe (Türkiye)',\n 'uk': 'Українська',\n 'ur': 'اردو',\n 'uz': 'Ўзбек',\n 'vi': 'Tiếng Việt',\n 'zh-cn': '中文 (简体)',\n 'zh-hk': '中文 (香港)',\n 'zh-tw': '中文 (台灣)'}
\n
LANGUAGE_MAP\n
{'name': 'Language',\n 'terms': {'aa': 'Afar',\n           'ab': 'Abkhazian',\n           'ae': 'Avestan',\n           'af': 'Afrikaans',\n           'ak': 'Akan',\n           'am': 'Amharic',\n           'an': 'Aragonese',\n           'ar': 'Arabic',\n           'as': 'Assamese',\n           'av': 'Avaric',\n           'ay': 'Aymara',\n           'az': 'Azerbaijani',\n           'ba': 'Bashkir',\n           'be': 'Belarusian',\n           'bg': 'Bulgarian',\n           'bh': 'Bihari languages',\n           'bi': 'Bislama',\n           'bm': 'Bambara',\n           'bn': 'Bengali',\n           'bo': 'Tibetan',\n           'br': 'Breton',\n           'bs': 'Bosnian',\n           'ca': 'Catalan',\n           'ce': 'Chechen',\n           'ch': 'Chamorro',\n           'co': 'Corsican',\n           'cr': 'Cree',\n           'cs': 'Czech',\n           'cu': 'Church Slavic',\n           'cv': 'Chuvash',\n           'cy': 'Welsh',\n           'da': 'Danish',\n           'de': 'German',\n           'dv': 'Divehi',\n           'dz': 'Dzongkha',\n           'ee': 'Ewe',\n           'el': 'Greek',\n           'en': 'English',\n           'eo': 'Esperanto',\n           'es': 'Spanish',\n           'et': 'Estonian',\n           'eu': 'Basque',\n           'fa': 'Persian',\n           'ff': 'Fulah',\n           'fi': 'Finnish',\n           'fj': 'Fijian',\n           'fo': 'Faroese',\n           'fr': 'French',\n           'fy': 'Western Frisian',\n           'ga': 'Irish',\n           'gd': 'Gaelic',\n           'gl': 'Galician',\n           'gn': 'Guarani',\n           'gu': 'Gujarati',\n           'gv': 'Manx',\n           'ha': 'Hausa',\n           'he': 'Hebrew',\n           'hi': 'Hindi',\n           'ho': 'Hiri Motu',\n           'hr': 'Croatian',\n           'ht': 'Haitian',\n           'hu': 'Hungarian',\n           'hy': 'Armenian',\n           'hz': 'Herero',\n           'ia': 'Interlingua',\n           'id': 'Indonesian',\n           'ie': 'Interlingue',\n           'ig': 'Igbo',\n           'ii': 'Sichuan Yi',\n           'ik': 'Inupiaq',\n           'io': 'Ido',\n           'is': 'Icelandic',\n           'it': 'Italian',\n           'iu': 'Inuktitut',\n           'ja': 'Japanese',\n           'jv': 'Javanese',\n           'ka': 'Georgian',\n           'kg': 'Kongo',\n           'ki': 'Kikuyu',\n           'kj': 'Kuanyama',\n           'kk': 'Kazakh',\n           'kl': 'Kalaallisut',\n           'km': 'Central Khmer',\n           'kn': 'Kannada',\n           'ko': 'Korean',\n           'kr': 'Kanuri',\n           'ks': 'Kashmiri',\n           'ku': 'Kurdish',\n           'kv': 'Komi',\n           'kw': 'Cornish',\n           'ky': 'Kirghiz',\n           'la': 'Latin',\n           'lb': 'Luxembourgish',\n           'lg': 'Ganda',\n           'li': 'Limburgan',\n           'ln': 'Lingala',\n           'lo': 'Lao',\n           'lt': 'Lithuanian',\n           'lu': 'Luba-Katanga',\n           'lv': 'Latvian',\n           'mg': 'Malagasy',\n           'mh': 'Marshallese',\n           'mi': 'Maori',\n           'mk': 'Macedonian',\n           'ml': 'Malayalam',\n           'mn': 'Mongolian',\n           'mr': 'Marathi',\n           'ms': 'Malay',\n           'mt': 'Maltese',\n           'my': 'Burmese',\n           'na': 'Nauru',\n           'nb': 'Bokmål, Norwegian',\n           'nd': 'Ndebele, North',\n           'ne': 'Nepali',\n           'ng': 'Ndonga',\n           'nl': 'Dutch',\n           'nn': 'Norwegian Nynorsk',\n           'no': 'Norwegian',\n           'nr': 'Ndebele, South',\n           'nv': 'Navajo',\n           'ny': 'Chichewa',\n           'oc': 'Occitan',\n           'oj': 'Ojibwa',\n           'om': 'Oromo',\n           'or': 'Oriya',\n           'os': 'Ossetian',\n           'pa': 'Panjabi',\n           'pi': 'Pali',\n           'pl': 'Polish',\n           'ps': 'Pushto',\n           'pt': 'Portuguese',\n           'qu': 'Quechua',\n           'rm': 'Romansh',\n           'rn': 'Rundi',\n           'ro': 'Romanian',\n           'ru': 'Russian',\n           'rw': 'Kinyarwanda',\n           'sa': 'Sanskrit',\n           'sc': 'Sardinian',\n           'sd': 'Sindhi',\n           'se': 'Northern Sami',\n           'sg': 'Sango',\n           'si': 'Sinhala',\n           'sk': 'Slovak',\n           'sl': 'Slovenian',\n           'sm': 'Samoan',\n           'sn': 'Shona',\n           'so': 'Somali',\n           'sq': 'Albanian',\n           'sr': 'Serbian',\n           'ss': 'Swati',\n           'st': 'Sotho, Southern',\n           'su': 'Sundanese',\n           'sv': 'Swedish',\n           'sw': 'Swahili',\n           'ta': 'Tamil',\n           'te': 'Telugu',\n           'tg': 'Tajik',\n           'th': 'Thai',\n           'ti': 'Tigrinya',\n           'tk': 'Turkmen',\n           'tl': 'Tagalog',\n           'tn': 'Tswana',\n           'to': 'Tonga (Tonga Islands)',\n           'tr': 'Turkish',\n           'ts': 'Tsonga',\n           'tt': 'Tatar',\n           'tw': 'Twi',\n           'ty': 'Tahitian',\n           'ug': 'Uighur',\n           'uk': 'Ukrainian',\n           'ur': 'Urdu',\n           'uz': 'Uzbek',\n           've': 'Venda',\n           'vi': 'Vietnamese',\n           'vo': 'Volapük',\n           'wa': 'Walloon',\n           'wo': 'Wolof',\n           'xh': 'Xhosa',\n           'yi': 'Yiddish',\n           'yo': 'Yoruba',\n           'za': 'Zhuang',\n           'zh': 'Chinese',\n           'zh_HANS': 'Simplified Chinese',\n           'zh_HANT': 'Traditional Chinese',\n           'zu': 'Zulu'}}
\n
LEARNER_PORTAL_URL_ROOT\n
'http://localhost:8734'
\n
LEARNING_MICROFRONTEND_URL\n
'http://localhost:2000'
\n
LMS_BASE\n
'localhost:18000'
\n
LMS_ENROLLMENT_API_PATH\n
'********************'
\n
LMS_INTERNAL_ROOT_URL\n
'http://localhost:18000'
\n
LMS_MIGRATION_ALLOWED_IPS\n
[]
\n
LMS_ROOT_URL\n
'http://localhost:18000'
\n
LMS_SEGMENT_KEY\n
'********************'
\n
LOCALE_PATHS\n
[Path('/edx/app/edxapp/edx-platform/conf/locale')]
\n
LOCAL_LOGLEVEL\n
'INFO'
\n
LOGGING\n
{'disable_existing_loggers': False,\n 'filters': {'remoteip_context': {'()': 'edx_django_utils.logging.RemoteIpFilter'},\n             'require_debug_false': {'()': 'django.utils.log.RequireDebugFalse'},\n             'userid_context': {'()': 'edx_django_utils.logging.UserIdFilter'}},\n 'formatters': {'raw': {'format': '%(message)s'},\n                'standard': {'format': '%(asctime)s %(levelname)s %(process)d '\n                                       '[%(name)s] [user %(userid)s] [ip '\n                                       '%(remoteip)s] %(filename)s:%(lineno)d '\n                                       '- %(message)s'},\n                'syslog_format': {'format': '[service_variant=lms][%(name)s][env:sandbox] '\n                                            '%(levelname)s [lms  %(process)d] '\n                                            '[user %(userid)s] [ip '\n                                            '%(remoteip)s] '\n                                            '[%(filename)s:%(lineno)d] - '\n                                            '%(message)s'}},\n 'handlers': {'console': {'class': 'logging.StreamHandler',\n                          'filters': ['userid_context', 'remoteip_context'],\n                          'formatter': 'standard',\n                          'level': 'INFO',\n                          'stream': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>},\n              'local': {'class': 'logging.NullHandler'},\n              'mail_admins': {'class': 'django.utils.log.AdminEmailHandler',\n                              'filters': ['require_debug_false'],\n                              'level': 'ERROR'},\n              'tracking': {'class': 'logging.NullHandler'}},\n 'loggers': {'': {'handlers': ['console', 'local'],\n                  'level': 'INFO',\n                  'propagate': False},\n             'django.request': {'handlers': ['mail_admins'],\n                                'level': 'ERROR',\n                                'propagate': True},\n             'requests.packages.urllib3': {'level': 'WARN'},\n             'tracking': {'handlers': ['console'],\n                          'level': 'DEBUG',\n                          'propagate': False}},\n 'version': 1}
\n
LOGGING_CONFIG\n
'logging.config.dictConfig'
\n
LOGGING_ENV\n
'sandbox'
\n
LOGIN_AND_REGISTER_FORM_RATELIMIT\n
'100/5m'
\n
LOGIN_ISSUE_SUPPORT_LINK\n
''
\n
LOGIN_REDIRECT_URL\n
'/login'
\n
LOGIN_REDIRECT_WHITELIST\n
['localhost:18010',\n 'localhost:1997',\n 'localhost:1976',\n 'localhost:1994',\n 'localhost:2000',\n 'localhost:2001',\n 'localhost:3001',\n 'localhost:18400',\n 'localhost:1993',\n 'localhost:8734',\n 'localhost:1991']
\n
LOGIN_URL\n
'/login'
\n
LOGISTRATION_API_RATELIMIT\n
'********************'
\n
LOGISTRATION_PER_EMAIL_RATELIMIT_RATE\n
'30/5m'
\n
LOGISTRATION_RATELIMIT_RATE\n
'100/5m'
\n
LOGOUT_REDIRECT_URL\n
None
\n
LOGO_IMAGE_EXTRA_TEXT\n
''
\n
LOGO_TRADEMARK_URL\n
None
\n
LOGO_URL\n
None
\n
LOGO_URL_PNG\n
None
\n
LOG_DIR\n
'/edx/var/log/edx'
\n
LOG_OVERRIDES\n
[('common.djangoapps.track.contexts', 50),\n ('common.djangoapps.track.middleware', 50),\n ('lms.djangoapps.discussion.django_comment_client.utils', 50)]
\n
LTI_AGGREGATE_SCORE_PASSBACK_DELAY\n
'********************'
\n
LTI_USER_EMAIL_DOMAIN\n
'lti.example.com'
\n
MAILCHIMP_NEW_USER_LIST_ID\n
None
\n
MAINTENANCE_BANNER_TEXT\n
'Sample banner message'
\n
MAKO_MODULE_DIR\n
'/tmp/mako_lms'
\n
MAKO_TEMPLATE_DIRS_BASE\n
[Path('/edx/app/edxapp/edx-platform/lms/templates'),\n Path('/edx/app/edxapp/edx-platform/common/templates'),\n Path('/edx/app/edxapp/edx-platform/common/lib/capa/capa/templates'),\n Path('/edx/app/edxapp/edx-platform/common/djangoapps/pipeline_mako/templates'),\n Path('/edx/app/edxapp/edx-platform/openedx/core/djangoapps/cors_csrf/templates'),\n Path('/edx/app/edxapp/edx-platform/openedx/core/djangoapps/dark_lang/templates'),\n Path('/edx/app/edxapp/edx-platform/openedx/core/lib/license/templates'),\n Path('/edx/app/edxapp/edx-platform/openedx/features/course_experience/templates')]
\n
MANAGERS\n
()
\n
MARKETING_EMAILS_OPT_IN\n
False
\n
MARKETING_SITE_ROOT\n
'http://localhost:8080'
\n
MAX_BLOCKS_PER_CONTENT_LIBRARY\n
1000
\n
MAX_BOOKMARKS_PER_COURSE\n
100
\n
MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED\n
6
\n
MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS\n
1800
\n
MAX_FILEUPLOADS_PER_INPUT\n
20
\n
MEDIA_ROOT\n
'/edx/var/edxapp/uploads'
\n
MEDIA_URL\n
'/media/'
\n
MESSAGE_STORAGE\n
'django.contrib.messages.storage.session.SessionStorage'
\n
MICROSITE_CONFIGURATION\n
{}
\n
MICROSITE_ROOT_DIR\n
'/edx/app/edxapp/edx-microsite'
\n
MIDDLEWARE\n
['openedx.core.lib.x_forwarded_for.middleware.XForwardedForMiddleware',\n 'crum.CurrentRequestUserMiddleware',\n 'edx_django_utils.monitoring.DeploymentMonitoringMiddleware',\n 'edx_django_utils.cache.middleware.RequestCacheMiddleware',\n 'edx_django_utils.monitoring.CodeOwnerMonitoringMiddleware',\n 'openedx.core.djangoapps.cookie_metadata.middleware.CookieNameChange',\n 'openedx.core.lib.request_utils.ExpectedErrorMiddleware',\n 'edx_django_utils.monitoring.CachedCustomMonitoringMiddleware',\n 'openedx.core.lib.request_utils.CookieMonitoringMiddleware',\n 'lms.djangoapps.mobile_api.middleware.AppVersionUpgrade',\n 'openedx.core.djangoapps.header_control.middleware.HeaderControlMiddleware',\n 'lms.djangoapps.discussion.django_comment_client.middleware.AjaxExceptionMiddleware',\n 'django.middleware.common.CommonMiddleware',\n 'django.contrib.sites.middleware.CurrentSiteMiddleware',\n 'edx_rest_framework_extensions.auth.jwt.middleware.JwtAuthCookieMiddleware',\n 'django_sites_extensions.middleware.RedirectMiddleware',\n 'openedx.core.djangoapps.safe_sessions.middleware.SafeSessionMiddleware',\n 'openedx.core.djangoapps.cache_toolbox.middleware.CacheBackedAuthenticationMiddleware',\n 'common.djangoapps.student.middleware.UserStandingMiddleware',\n 'openedx.core.djangoapps.contentserver.middleware.StaticContentServer',\n 'openedx.core.djangoapps.user_api.middleware.UserTagsEventContextMiddleware',\n 'django.contrib.messages.middleware.MessageMiddleware',\n 'common.djangoapps.track.middleware.TrackMiddleware',\n 'corsheaders.middleware.CorsMiddleware',\n 'openedx.core.djangoapps.cors_csrf.middleware.CorsCSRFMiddleware',\n 'openedx.core.djangoapps.cors_csrf.middleware.CsrfCrossDomainCookieMiddleware',\n 'django.middleware.csrf.CsrfViewMiddleware',\n 'splash.middleware.SplashMiddleware',\n 'openedx.core.djangoapps.geoinfo.middleware.CountryMiddleware',\n 'openedx.core.djangoapps.embargo.middleware.EmbargoMiddleware',\n 'enterprise.middleware.EnterpriseLanguagePreferenceMiddleware',\n 'openedx.core.djangoapps.lang_pref.middleware.LanguagePreferenceMiddleware',\n 'openedx.core.djangoapps.dark_lang.middleware.DarkLangMiddleware',\n 'django.middleware.locale.LocaleMiddleware',\n 'lms.djangoapps.discussion.django_comment_client.utils.ViewNameMiddleware',\n 'codejail.django_integration.ConfigureCodeJailMiddleware',\n 'ratelimitbackend.middleware.RateLimitMiddleware',\n 'openedx.core.djangoapps.session_inactivity_timeout.middleware.SessionInactivityTimeout',\n 'django.middleware.clickjacking.XFrameOptionsMiddleware',\n 'lms.djangoapps.courseware.middleware.CacheCourseIdMiddleware',\n 'lms.djangoapps.courseware.middleware.RedirectMiddleware',\n 'lms.djangoapps.course_wiki.middleware.WikiAccessMiddleware',\n 'openedx.core.djangoapps.theming.middleware.CurrentSiteThemeMiddleware',\n 'waffle.middleware.WaffleMiddleware',\n 'edx_django_utils.cache.middleware.TieredCacheMiddleware',\n 'edx_rest_framework_extensions.middleware.RequestCustomAttributesMiddleware',\n 'edx_rest_framework_extensions.auth.jwt.middleware.EnsureJWTAuthSettingsMiddleware',\n 'simple_history.middleware.HistoryRequestMiddleware',\n 'openedx.core.djangoapps.site_configuration.middleware.SessionCookieDomainOverrideMiddleware',\n 'lms.djangoapps.discussion.django_comment_client.utils.QueryCountDebugMiddleware',\n 'debug_toolbar.middleware.DebugToolbarMiddleware',\n 'common.djangoapps.third_party_auth.middleware.ExceptionMiddleware']
\n
MIGRATION_MODULES\n
{}
\n
MKTG_URLS\n
{'ABOUT': '/about',\n 'ACCESSIBILITY': '/accessibility',\n 'AFFILIATES': '/affiliate-program',\n 'BLOG': '/blog',\n 'CAREERS': '/careers',\n 'CONTACT': '/support/contact_us',\n 'COURSES': '/course',\n 'DONATE': '/donate',\n 'ENTERPRISE': '/enterprise',\n 'FAQ': '/student-faq',\n 'HONOR': '/edx-terms-service',\n 'HOW_IT_WORKS': '/how-it-works',\n 'MEDIA_KIT': '/media-kit',\n 'NEWS': '/news-announcements',\n 'PRESS': '/press',\n 'PRIVACY': '/edx-privacy-policy',\n 'ROOT': 'http://localhost:8080',\n 'SCHOOLS': '/schools-partners',\n 'SITE_MAP': '/sitemap',\n 'TOS': '/edx-terms-service',\n 'TOS_AND_HONOR': '/edx-terms-service',\n 'TRADEMARKS': '/trademarks',\n 'WHAT_IS_VERIFIED_CERT': '/verified-certificate'}
\n
MKTG_URL_LINK_MAP\n
{'ABOUT': 'about',\n 'BLOG': 'blog',\n 'CONTACT': 'contact',\n 'COURSES': 'courses',\n 'DONATE': 'donate',\n 'FAQ': 'help',\n 'HONOR': 'honor',\n 'PRESS': 'press',\n 'PRIVACY': 'privacy',\n 'ROOT': 'root',\n 'SITEMAP.XML': 'sitemap_xml',\n 'TOS': 'tos',\n 'TOS_AND_HONOR': 'edx-terms-service',\n 'WHAT_IS_VERIFIED_CERT': 'verified-certificate'}
\n
MKTG_URL_OVERRIDES\n
{}
\n
MOBILE_APP_USER_AGENT_REGEXES\n
['edX/org.edx.mobile']
\n
MOBILE_STORE_URLS\n
{}
\n
MODULESTORE\n
{'default': {'ENGINE': 'xmodule.modulestore.mixed.MixedModuleStore',\n             'OPTIONS': {'mappings': {},\n                         'stores': [{'DOC_STORE_CONFIG': {'authsource': '',\n                                                          'collection': 'modulestore',\n                                                          'connectTimeoutMS': 2000,\n                                                          'db': 'edxapp',\n                                                          'host': ['edx.devstack.mongo'],\n                                                          'password': '********************',\n                                                          'port': 27017,\n                                                          'read_preference': 'SECONDARY_PREFERRED',\n                                                          'replicaSet': '',\n                                                          'socketTimeoutMS': 3000,\n                                                          'ssl': False,\n                                                          'user': 'edxapp'},\n                                     'ENGINE': 'xmodule.modulestore.split_mongo.split_draft.DraftVersioningModuleStore',\n                                     'NAME': 'split',\n                                     'OPTIONS': {'default_class': 'xmodule.hidden_module.HiddenDescriptor',\n                                                 'fs_root': '/edx/var/edxapp/data',\n                                                 'render_template': 'common.djangoapps.edxmako.shortcuts.render_to_string'}},\n                                    {'DOC_STORE_CONFIG': {'authsource': '',\n                                                          'collection': 'modulestore',\n                                                          'connectTimeoutMS': 2000,\n                                                          'db': 'edxapp',\n                                                          'host': ['edx.devstack.mongo'],\n                                                          'password': '********************',\n                                                          'port': 27017,\n                                                          'read_preference': 'PRIMARY',\n                                                          'replicaSet': '',\n                                                          'socketTimeoutMS': 3000,\n                                                          'ssl': False,\n                                                          'user': 'edxapp'},\n                                     'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore',\n                                     'NAME': 'draft',\n                                     'OPTIONS': {'default_class': 'xmodule.hidden_module.HiddenDescriptor',\n                                                 'fs_root': '/edx/var/edxapp/data',\n                                                 'render_template': 'common.djangoapps.edxmako.shortcuts.render_to_string'}}]}}}
\n
MODULESTORE_BRANCH\n
'published-only'
\n
MODULESTORE_FIELD_OVERRIDE_PROVIDERS\n
('openedx.features.content_type_gating.field_override.ContentTypeGatingFieldOverride',\n 'lms.djangoapps.courseware.self_paced_overrides.SelfPacedDateOverrideProvider')
\n
MONGODB_LOG\n
{}
\n
MONTH_DAY_FORMAT\n
'F j'
\n
NODE_MODULES_ROOT\n
Path('/edx/app/edxapp/edx-platform/node_modules')
\n
NODE_PATH\n
'/edx/app/edxapp/edx-platform/common/static/js/vendor:/edx/app/edxapp/edx-platform/node_modules'
\n
NOTES_DISABLED_TABS\n
['course_structure', 'tags']
\n
NOTIFICATION_EMAIL_CSS\n
'templates/credit_notifications/credit_notification.css'
\n
NOTIFICATION_EMAIL_EDX_LOGO\n
'templates/credit_notifications/edx-logo-header.png'
\n
NUMBER_GROUPING\n
0
\n
OAUTH2_DEFAULT_SCOPES\n
{'email': 'Know your email address',\n 'profile': 'Know your name and username',\n 'read': 'Read access',\n 'write': 'Write access'}
\n
OAUTH2_PROVIDER\n
{'DEFAULT_SCOPES': {'email': 'Know your email address',\n                    'profile': 'Know your name and username',\n                    'read': 'Read access',\n                    'write': 'Write access'},\n 'ERROR_RESPONSE_WITH_SCOPES': True,\n 'OAUTH2_VALIDATOR_CLASS': 'openedx.core.djangoapps.oauth_dispatch.dot_overrides.validators.EdxOAuth2Validator',\n 'REFRESH_TOKEN_EXPIRE_SECONDS': '********************',\n 'REQUEST_APPROVAL_PROMPT': 'auto_even_if_expired',\n 'SCOPES': {'certificates:read': 'Retrieve your course certificates',\n            'email': 'Know your email address',\n            'grades:read': 'Retrieve your grades for your enrolled courses',\n            'profile': 'Know your name and username',\n            'read': 'Read access',\n            'tpa:read': 'Retrieve your third-party authentication username mapping',\n            'user_id': 'Know your user identifier',\n            'write': 'Write access'},\n 'SCOPES_BACKEND_CLASS': 'openedx.core.djangoapps.oauth_dispatch.scopes.ApplicationModelScopes'}
\n
OAUTH2_PROVIDER_APPLICATION_MODEL\n
'oauth2_provider.Application'
\n
OAUTH_DELETE_EXPIRED\n
True
\n
OAUTH_ENFORCE_SECURE\n
False
\n
OAUTH_EXPIRE_CONFIDENTIAL_CLIENT_DAYS\n
365
\n
OAUTH_EXPIRE_PUBLIC_CLIENT_DAYS\n
30
\n
OAUTH_ID_TOKEN_EXPIRATION\n
'********************'
\n
OPENAPI_CACHE_TIMEOUT\n
'********************'
\n
OPENEDX_ROOT\n
Path('/edx/app/edxapp/edx-platform/openedx')
\n
OPTIMIZELY_PROJECT_ID\n
None
\n
OPTIONAL_APPS\n
[('problem_builder',\n  'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'),\n ('edx_sga', None),\n ('submissions',\n  'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'),\n ('openassessment',\n  'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'),\n ('openassessment.assessment',\n  'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'),\n ('openassessment.fileupload',\n  'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'),\n ('openassessment.workflow',\n  'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'),\n ('openassessment.xblock',\n  'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'),\n ('edxval',\n  'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'),\n ('enterprise', None),\n ('consent', None),\n ('integrated_channels.integrated_channel', None),\n ('integrated_channels.degreed', None),\n ('integrated_channels.degreed2', None),\n ('integrated_channels.sap_success_factors', None),\n ('integrated_channels.cornerstone', None),\n ('integrated_channels.xapi', None),\n ('integrated_channels.blackboard', None),\n ('integrated_channels.canvas', None),\n ('integrated_channels.moodle', None),\n ('django_object_actions', None)]
\n
ORA2_FILEUPLOAD_BACKEND\n
'django'
\n
ORA2_FILE_PREFIX\n
'default_env-default_deployment/ora2'
\n
ORA_GRADING_MICROFRONTEND_URL\n
None
\n
ORDER_HISTORY_MICROFRONTEND_URL\n
None
\n
ORGANIZATIONS_AUTOCREATE\n
True
\n
P3P_HEADER\n
'CP="Open EdX does not have a P3P policy."'
\n
PAID_COURSE_REGISTRATION_CURRENCY\n
['usd', '$']
\n
PARENTAL_CONSENT_AGE_LIMIT\n
13
\n
PARTNER_SUPPORT_EMAIL\n
''
\n
PASSWORD_HASHERS\n
'********************'
\n
PASSWORD_POLICY_COMPLIANCE_ROLLOUT_CONFIG\n
'********************'
\n
PASSWORD_RESET_EMAIL_RATE\n
'********************'
\n
PASSWORD_RESET_IP_RATE\n
'********************'
\n
PASSWORD_RESET_SUPPORT_LINK\n
'********************'
\n
PASSWORD_RESET_TIMEOUT\n
'********************'
\n
PASSWORD_RESET_TIMEOUT_DAYS\n
'********************'
\n
PAYMENT_SUPPORT_EMAIL\n
'billing@example.com'
\n
PDF_RECEIPT_BILLING_ADDRESS\n
'Enter your receipt billing\\naddress here.\\n'
\n
PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM\n
12
\n
PDF_RECEIPT_COBRAND_LOGO_PATH\n
''
\n
PDF_RECEIPT_DISCLAIMER_TEXT\n
'ENTER YOUR RECEIPT DISCLAIMER TEXT HERE.\\n'
\n
PDF_RECEIPT_FOOTER_TEXT\n
'Enter your receipt footer text here.\\n'
\n
PDF_RECEIPT_LOGO_HEIGHT_MM\n
12
\n
PDF_RECEIPT_LOGO_PATH\n
''
\n
PDF_RECEIPT_TAX_ID\n
'00-0000000'
\n
PDF_RECEIPT_TAX_ID_LABEL\n
'fake Tax ID'
\n
PDF_RECEIPT_TERMS_AND_CONDITIONS\n
'Enter your receipt terms and conditions here.\\n'
\n
PIPELINE\n
{'CSS_COMPRESSOR': None,\n 'DISABLE_WRAPPER': True,\n 'JAVASCRIPT': {'application': {'output_filename': 'js/lms-application.js',\n                                'source_filenames': ['js/src/ajax_prefix.js',\n                                                     'js/src/jquery.immediateDescendents.js',\n                                                     'js/src/xproblem.js',\n                                                     'common/js/xblock/core.js',\n                                                     'common/js/xblock/runtime.v1.js',\n                                                     'lms/js/xblock/lms.runtime.v1.js',\n                                                     'js/src/utility.js',\n                                                     'js/src/logger.js',\n                                                     'js/user_dropdown_v1.js',\n                                                     'js/dialog_tab_controls.js',\n                                                     'js/src/string_utils.js',\n                                                     'js/form.ext.js',\n                                                     'js/src/ie_shim.js',\n                                                     'js/src/accessibility_tools.js',\n                                                     'js/toggle_login_modal.js',\n                                                     'js/src/lang_edx.js',\n                                                     'js/calculator.js',\n                                                     'js/feedback_form.js',\n                                                     'js/main.js',\n                                                     'js/sticky_filter.js',\n                                                     'js/query-params.js',\n                                                     'common/js/vendor/moment-with-locales.js',\n                                                     'common/js/vendor/moment-timezone-with-data.js']},\n                'base_application': {'output_filename': 'js/lms-base-application.js',\n                                     'source_filenames': ['js/src/utility.js',\n                                                          'js/src/logger.js',\n                                                          'js/user_dropdown_v1.js',\n                                                          'js/dialog_tab_controls.js',\n                                                          'js/src/string_utils.js',\n                                                          'js/form.ext.js',\n                                                          'js/src/ie_shim.js',\n                                                          'js/src/accessibility_tools.js',\n                                                          'js/toggle_login_modal.js',\n                                                          'js/src/lang_edx.js']},\n                'base_vendor': {'output_filename': 'js/lms-base-vendor.js',\n                                'source_filenames': ['common/js/vendor/jquery.js',\n                                                     'common/js/vendor/jquery-migrate.js',\n                                                     'js/vendor/jquery.cookie.js',\n                                                     'js/vendor/url.min.js',\n                                                     'common/js/vendor/underscore.js',\n                                                     'common/js/vendor/underscore.string.js',\n                                                     'common/js/vendor/picturefill.js',\n                                                     'edx-ui-toolkit/js/utils/global-loader.js',\n                                                     'edx-ui-toolkit/js/utils/string-utils.js',\n                                                     'edx-ui-toolkit/js/utils/html-utils.js',\n                                                     'common/js/vendor/require.js',\n                                                     'js/RequireJS-namespace-undefine.js',\n                                                     'js/vendor/URI.min.js',\n                                                     'common/js/vendor/backbone.js']},\n                'ccx': {'output_filename': 'js/ccx.js',\n                        'source_filenames': ['js/ccx/schedule.js']},\n                'certificates_wv': {'output_filename': 'js/certificates/web_view.js',\n                                    'source_filenames': ['common/js/vendor/jquery.js',\n                                                         'common/js/vendor/jquery-migrate.js',\n                                                         'js/vendor/jquery.cookie.js',\n                                                         'js/src/logger.js',\n                                                         'js/utils/facebook.js']},\n                'courseware': {'output_filename': 'js/lms-courseware.js',\n                               'source_filenames': ['js/ajax-error.js',\n                                                    'js/courseware.js',\n                                                    'js/histogram.js',\n                                                    'js/navigation.js',\n                                                    'js/modules/tab.js']},\n                'credit_wv': {'output_filename': 'js/credit/web_view.js',\n                              'source_filenames': ['common/js/vendor/jquery.js',\n                                                   'common/js/vendor/jquery-migrate.js',\n                                                   'js/vendor/jquery.cookie.js',\n                                                   'js/src/logger.js']},\n                'dashboard': {'output_filename': 'js/dashboard.js',\n                              'source_filenames': ['js/dashboard/credit.js',\n                                                   'js/dashboard/donation.js',\n                                                   'js/dashboard/dropdown.js',\n                                                   'js/dashboard/legacy.js',\n                                                   'js/dashboard/track_events.js']},\n                'discussion': {'output_filename': 'js/discussion.js',\n                               'source_filenames': ['js/customwmd.js',\n                                                    'js/mathjax_accessible.js',\n                                                    'js/mathjax_delay_renderer.js',\n                                                    'common/js/discussion/content.js',\n                                                    'common/js/discussion/discussion.js',\n                                                    'common/js/discussion/mathjax_include.js',\n                                                    'common/js/discussion/models/discussion_course_settings.js',\n                                                    'common/js/discussion/models/discussion_user.js',\n                                                    'common/js/discussion/utils.js',\n                                                    'common/js/discussion/views/discussion_content_view.js',\n                                                    'common/js/discussion/views/discussion_inline_view.js',\n                                                    'common/js/discussion/views/discussion_thread_edit_view.js',\n                                                    'common/js/discussion/views/discussion_thread_list_view.js',\n                                                    'common/js/discussion/views/discussion_thread_profile_view.js',\n                                                    'common/js/discussion/views/discussion_thread_show_view.js',\n                                                    'common/js/discussion/views/discussion_thread_view.js',\n                                                    'common/js/discussion/views/discussion_topic_menu_view.js',\n                                                    'common/js/discussion/views/new_post_view.js',\n                                                    'common/js/discussion/views/response_comment_edit_view.js',\n                                                    'common/js/discussion/views/response_comment_show_view.js',\n                                                    'common/js/discussion/views/response_comment_view.js',\n                                                    'common/js/discussion/views/thread_response_edit_view.js',\n                                                    'common/js/discussion/views/thread_response_show_view.js',\n                                                    'common/js/discussion/views/thread_response_view.js']},\n                'discussion_vendor': {'output_filename': 'js/discussion_vendor.js',\n                                      'source_filenames': ['js/Markdown.Converter.js',\n                                                           'js/Markdown.Sanitizer.js',\n                                                           'js/Markdown.Editor.js',\n                                                           'js/vendor/jquery.timeago.js',\n                                                           'js/src/jquery.timeago.locale.js',\n                                                           'js/vendor/jquery.truncate.js',\n                                                           'js/jquery.ajaxfileupload.js',\n                                                           'js/split.js']},\n                'footer_edx': {'output_filename': 'js/footer-edx.js',\n                               'source_filenames': ['js/footer-edx.js']},\n                'incourse_reverify': {'output_filename': 'js/incourse_reverify.js',\n                                      'source_filenames': ['js/verify_student/views/error_view.js',\n                                                           'js/verify_student/views/image_input_view.js',\n                                                           'js/verify_student/views/webcam_photo_view.js',\n                                                           'js/verify_student/models/verification_model.js',\n                                                           'js/verify_student/views/incourse_reverify_view.js',\n                                                           'js/verify_student/incourse_reverify.js']},\n                'instructor_dash': {'output_filename': 'js/instructor_dash.js',\n                                    'source_filenames': ['js/instructor_dashboard/certificates.js',\n                                                         'js/instructor_dashboard/cohort_management.js',\n                                                         'js/instructor_dashboard/course_info.js',\n                                                         'js/instructor_dashboard/data_download.js',\n                                                         'js/instructor_dashboard/data_download_2.js',\n                                                         'js/instructor_dashboard/discussions_management.js',\n                                                         'js/instructor_dashboard/e-commerce.js',\n                                                         'js/instructor_dashboard/ecommerce.js',\n                                                         'js/instructor_dashboard/extensions.js',\n                                                         'js/instructor_dashboard/instructor_dashboard.js',\n                                                         'js/instructor_dashboard/membership.js',\n                                                         'js/instructor_dashboard/metrics.js',\n                                                         'js/instructor_dashboard/open_response_assessment.js',\n                                                         'js/instructor_dashboard/proctoring.js',\n                                                         'js/instructor_dashboard/send_email.js',\n                                                         'js/instructor_dashboard/student_admin.js',\n                                                         'js/instructor_dashboard/util.js']},\n                'main_vendor': {'output_filename': 'js/lms-main_vendor.js',\n                                'source_filenames': ['common/js/vendor/jquery.js',\n                                                     'common/js/vendor/jquery-migrate.js',\n                                                     'js/vendor/jquery.cookie.js',\n                                                     'js/vendor/url.min.js',\n                                                     'common/js/vendor/underscore.js',\n                                                     'common/js/vendor/underscore.string.js',\n                                                     'common/js/vendor/picturefill.js',\n                                                     'edx-ui-toolkit/js/utils/global-loader.js',\n                                                     'edx-ui-toolkit/js/utils/string-utils.js',\n                                                     'edx-ui-toolkit/js/utils/html-utils.js',\n                                                     'common/js/vendor/require.js',\n                                                     'js/RequireJS-namespace-undefine.js',\n                                                     'js/vendor/URI.min.js',\n                                                     'common/js/vendor/backbone.js',\n                                                     'js/vendor/json2.js',\n                                                     'js/vendor/jquery-ui.min.js',\n                                                     'js/vendor/jquery.qtip.min.js',\n                                                     'js/vendor/jquery.ba-bbq.min.js']},\n                'module-descriptor-js': {'output_filename': 'js/lms-module-descriptors.js',\n                                         'source_filenames': ['xmodule/descriptors/js/000-58032517f54c5c1a704a908d850cbe64.js',\n                                                              'xmodule/descriptors/js/001-043e45378109d53c4919131b4001dff2.js',\n                                                              'xmodule/descriptors/js/001-81f6a04b11b9b6b4b1bcd710dcf5777a.js',\n                                                              'xmodule/descriptors/js/001-8723f83c97a354f267ea559bc714ee1a.js',\n                                                              'xmodule/descriptors/js/001-91056aaf5a20b34890b4f435926e57f5.js',\n                                                              'xmodule/descriptors/js/001-c88750f95f4884a8666f9261eecfa285.js',\n                                                              'xmodule/descriptors/js/001-d7842ab69993e5eb58e8d4a4e80c23a2.js',\n                                                              'xmodule/descriptors/js/001-f7c2cfb3cff0dd3aefa932f8e02d1435.js']},\n                'module-js': {'output_filename': 'js/lms-modules.js',\n                              'source_filenames': ['xmodule/modules/js/000-58032517f54c5c1a704a908d850cbe64.js',\n                                                   'xmodule/modules/js/001-3918b2d4f383c04fed8227cc9f523d6e.js',\n                                                   'xmodule/modules/js/001-3ed86006526f75d6c844739193a84c11.js',\n                                                   'xmodule/modules/js/001-550e26b7e4efbc0c68a580f6dbecf66c.js',\n                                                   'xmodule/modules/js/001-8705061d9ba87dfcb875b9db3026aff6.js',\n                                                   'xmodule/modules/js/001-8ba509e3404fc2aea58a9e95e718fefb.js',\n                                                   'xmodule/modules/js/001-ce60a84636ea45ab98f1d6e5bfc70965.js',\n                                                   'xmodule/modules/js/001-e45ae357eab0e4e2784fa6d04d4e16c1.js',\n                                                   'xmodule/modules/js/002-37ce0eea8f9a85c26819ef3b8b3a37d4.js',\n                                                   'xmodule/modules/js/002-3918b2d4f383c04fed8227cc9f523d6e.js',\n                                                   'xmodule/modules/js/002-8b615179a51250c4dc203f1daa97be63.js',\n                                                   'xmodule/modules/js/002-e32c61651b0379c8503ad932a91e7651.js',\n                                                   'xmodule/modules/js/003-3918b2d4f383c04fed8227cc9f523d6e.js',\n                                                   'xmodule/modules/js/003-b3206f2283964743c4772b9d72c67d64.js',\n                                                   'xmodule/modules/js/003-e9bdd6eeb6d44351f9504a2e45b3fa98.js',\n                                                   'xmodule/modules/js/004-866df6ea65aa331217cdf46290ead28e.js',\n                                                   'xmodule/modules/js/004-b0c34afa95eaa6b45d843d92ca523a94.js',\n                                                   'xmodule/modules/js/004-b3206f2283964743c4772b9d72c67d64.js',\n                                                   'xmodule/modules/js/005-26caba6f71877f63a7dd4f6796109bf6.js',\n                                                   'xmodule/modules/js/005-fc8bd2dc5b96b86d1abefdd417dd8ba5.js']},\n                'proctoring': {'output_filename': 'js/lms-proctoring.js',\n                               'source_filenames': ['proctoring/js/models/proctored_exam_allowance_model.js',\n                                                    'proctoring/js/models/proctored_exam_attempt_model.js',\n                                                    'proctoring/js/models/proctored_exam_bulk_allowance_model.js',\n                                                    'proctoring/js/models/proctored_exam_model.js',\n                                                    'proctoring/js/models/learner_onboarding_model.js',\n                                                    'proctoring/js/collections/proctored_exam_allowance_collection.js',\n                                                    'proctoring/js/collections/proctored_exam_attempt_grouped_collection.js',\n                                                    'proctoring/js/collections/proctored_exam_onboarding_collection.js',\n                                                    'proctoring/js/collections/proctored_exam_collection.js',\n                                                    'proctoring/js/views/Backbone.ModalDialog.js',\n                                                    'proctoring/js/views/proctored_exam_add_allowance_view.js',\n                                                    'proctoring/js/views/proctored_exam_add_bulk_allowance_view.js',\n                                                    'proctoring/js/views/proctored_exam_allowance_view.js',\n                                                    'proctoring/js/views/proctored_exam_attempt_view.js',\n                                                    'proctoring/js/views/proctored_exam_edit_allowance_view.js',\n                                                    'proctoring/js/views/proctored_exam_onboarding_view.js',\n                                                    'proctoring/js/views/proctored_exam_view.js',\n                                                    'proctoring/js/views/proctored_exam_info.js',\n                                                    'proctoring/js/views/proctored_exam_instructor_launch.js',\n                                                    'proctoring/js/proctored_app.js',\n                                                    'proctoring/js/exam_action_handler.js',\n                                                    'proctoring/js/dropdown.js']},\n                'reverify': {'output_filename': 'js/reverify.js',\n                             'source_filenames': ['js/verify_student/views/error_view.js',\n                                                  'js/verify_student/views/image_input_view.js',\n                                                  'js/verify_student/views/webcam_photo_view.js',\n                                                  'js/verify_student/views/step_view.js',\n                                                  'js/verify_student/views/face_photo_step_view.js',\n                                                  'js/verify_student/views/id_photo_step_view.js',\n                                                  'js/verify_student/views/review_photos_step_view.js',\n                                                  'js/verify_student/views/reverify_success_step_view.js',\n                                                  'js/verify_student/models/verification_model.js',\n                                                  'js/verify_student/views/reverify_view.js',\n                                                  'js/verify_student/reverify.js']},\n                'verify_student': {'output_filename': 'js/verify_student.js',\n                                   'source_filenames': ['js/sticky_filter.js',\n                                                        'js/query-params.js',\n                                                        'js/verify_student/models/verification_model.js',\n                                                        'js/verify_student/views/error_view.js',\n                                                        'js/verify_student/views/image_input_view.js',\n                                                        'js/verify_student/views/webcam_photo_view.js',\n                                                        'js/verify_student/views/step_view.js',\n                                                        'js/verify_student/views/intro_step_view.js',\n                                                        'js/verify_student/views/make_payment_step_view.js',\n                                                        'js/verify_student/views/face_photo_step_view.js',\n                                                        'js/verify_student/views/id_photo_step_view.js',\n                                                        'js/verify_student/views/review_photos_step_view.js',\n                                                        'js/verify_student/views/enrollment_confirmation_step_view.js',\n                                                        'js/verify_student/views/pay_and_verify_view.js',\n                                                        'js/verify_student/pay_and_verify.js']}},\n 'JS_COMPRESSOR': None,\n 'PIPELINE_ENABLED': False,\n 'SASS_ARGUMENTS': '--debug-info',\n 'STYLESHEETS': {'style-certificates': {'output_filename': 'css/certificates-style.css',\n                                        'source_filenames': ['certificates/css/main-ltr.css',\n                                                             'css/vendor/font-awesome.css']},\n                 'style-certificates-rtl': {'output_filename': 'css/certificates-style-rtl.css',\n                                            'source_filenames': ['certificates/css/main-rtl.css',\n                                                                 'css/vendor/font-awesome.css']},\n                 'style-course': {'output_filename': 'css/lms-course.css',\n                                  'source_filenames': ['css/lms-course.css']},\n                 'style-course-rtl': {'output_filename': 'css/lms-course-rtl.css',\n                                      'source_filenames': ['css/lms-course-rtl.css']},\n                 'style-course-vendor': {'output_filename': 'css/lms-style-course-vendor.css',\n                                         'source_filenames': ['js/vendor/CodeMirror/codemirror.css',\n                                                              'css/vendor/jquery.treeview.css',\n                                                              'css/vendor/ui-lightness/jquery-ui-1.8.22.custom.css']},\n                 'style-inline-discussion': {'output_filename': 'css/discussion/inline-discussion.css',\n                                             'source_filenames': ['css/discussion/inline-discussion.css']},\n                 'style-inline-discussion-rtl': {'output_filename': 'css/discussion/inline-discussion-rtl.css',\n                                                 'source_filenames': ['css/discussion/inline-discussion-rtl.css']},\n                 'style-lms-footer': {'output_filename': 'css/lms-footer.css',\n                                      'source_filenames': ['css/lms-footer.css']},\n                 'style-lms-footer-edx': {'output_filename': 'css/lms-footer-edx.css',\n                                          'source_filenames': ['css/lms-footer-edx.css']},\n                 'style-lms-footer-edx-rtl': {'output_filename': 'css/lms-footer-edx-rtl.css',\n                                              'source_filenames': ['css/lms-footer-edx-rtl.css']},\n                 'style-lms-footer-rtl': {'output_filename': 'css/lms-footer-rtl.css',\n                                          'source_filenames': ['css/lms-footer-rtl.css']},\n                 'style-main-v1': {'output_filename': 'css/lms-main-v1.css',\n                                   'source_filenames': ['css/lms-main-v1.css']},\n                 'style-main-v1-rtl': {'output_filename': 'css/lms-main-v1-rtl.css',\n                                       'source_filenames': ['css/lms-main-v1-rtl.css']},\n                 'style-mobile': {'output_filename': 'css/lms-mobile.css',\n                                  'source_filenames': ['css/lms-mobile.css']},\n                 'style-mobile-rtl': {'output_filename': 'css/lms-mobile-rtl.css',\n                                      'source_filenames': ['css/lms-mobile-rtl.css']},\n                 'style-student-notes': {'output_filename': 'css/lms-style-student-notes.css',\n                                         'source_filenames': ['css/vendor/edxnotes/annotator.min.css']},\n                 'style-vendor': {'output_filename': 'css/lms-style-vendor.css',\n                                  'source_filenames': ['css/vendor/font-awesome.css',\n                                                       'css/vendor/jquery.qtip.min.css']},\n                 'style-vendor-tinymce-content': {'output_filename': 'css/lms-style-vendor-tinymce-content.css',\n                                                  'source_filenames': ['js/vendor/tinymce/js/tinymce/skins/studio-tmce4/content.min.css']},\n                 'style-vendor-tinymce-skin': {'output_filename': 'css/lms-style-vendor-tinymce-skin.css',\n                                               'source_filenames': ['js/vendor/tinymce/js/tinymce/skins/studio-tmce4/skin.min.css']}},\n 'UGLIFYJS_BINARY': 'node_modules/.bin/uglifyjs'}
\n
PLATFORM_DESCRIPTION\n
'Your Platform Description Here'
\n
PLATFORM_FACEBOOK_ACCOUNT\n
'http://www.facebook.com/YourPlatformFacebookAccount'
\n
PLATFORM_NAME\n
'Your Platform Name Here'
\n
PLATFORM_TWITTER_ACCOUNT\n
'@YourPlatformTwitterAccount'
\n
POLICY_CHANGE_GRADES_ROUTING_KEY\n
'********************'
\n
POLICY_CHANGE_TASK_RATE_LIMIT\n
'300/h'
\n
PREPEND_WWW\n
False
\n
PRESS_EMAIL\n
'press@example.com'
\n
PREVIEW_DOMAIN\n
'preview.localhost'
\n
PROCTORED_EXAM_VIEWABLE_PAST_DUE\n
False
\n
PROCTORING_BACKENDS\n
{'DEFAULT': 'null', 'null': {}}
\n
PROCTORING_SETTINGS\n
{}
\n
PROCTORING_USER_OBFUSCATION_KEY\n
'********************'
\n
PROFILE_IMAGE_BACKEND\n
{'class': 'openedx.core.storage.OverwriteStorage',\n 'options': {'base_url': '/media/profile-images/',\n             'location': '/edx/var/edxapp/media/profile-images/'}}
\n
PROFILE_IMAGE_DEFAULT_FILENAME\n
'images/profiles/default'
\n
PROFILE_IMAGE_DEFAULT_FILE_EXTENSION\n
'png'
\n
PROFILE_IMAGE_HASH_SEED\n
'placeholder_secret_key'
\n
PROFILE_IMAGE_MAX_BYTES\n
1048576
\n
PROFILE_IMAGE_MIN_BYTES\n
100
\n
PROFILE_IMAGE_SIZES_MAP\n
{'full': 500, 'large': 120, 'medium': 50, 'small': 30}
\n
PROFILE_MICROFRONTEND_URL\n
None
\n
PROGRAM_CERTIFICATES_ROUTING_KEY\n
'********************'
\n
PROGRAM_CONSOLE_MICROFRONTEND_URL\n
None
\n
PROGRESS_HELP_URL\n
'https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_assets/pages.html?highlight=progress#hiding-or-showing-the-wiki-or-progress-pages'
\n
PROJECT_ROOT\n
Path('/edx/app/edxapp/edx-platform/lms')
\n
PYTHON_LIB_FILENAME\n
'python_lib.zip'
\n
QUEUE_VARIANT\n
'lms.'
\n
RATELIMIT_ENABLE\n
True
\n
RATELIMIT_RATE\n
'120/m'
\n
RATE_LIMIT_FOR_VIDEO_METADATA_API\n
'********************'
\n
RECALCULATE_GRADES_ROUTING_KEY\n
'********************'
\n
REDIRECT_CACHE_KEY_PREFIX\n
'********************'
\n
REDIRECT_CACHE_TIMEOUT\n
None
\n
REGISTRATION_CODE_LENGTH\n
8
\n
REGISTRATION_EMAIL_PATTERNS_ALLOWED\n
None
\n
REGISTRATION_EXTENSION_FORM\n
None
\n
REGISTRATION_EXTRA_FIELDS\n
{'city': 'hidden',\n 'confirm_email': 'hidden',\n 'country': 'required',\n 'gender': 'optional',\n 'goals': 'optional',\n 'honor_code': 'required',\n 'level_of_education': 'optional',\n 'mailing_address': 'hidden',\n 'terms_of_service': 'hidden',\n 'year_of_birth': 'optional'}
\n
REGISTRATION_FIELD_ORDER\n
['name',\n 'first_name',\n 'last_name',\n 'username',\n 'email',\n 'confirm_email',\n 'password',\n 'city',\n 'state',\n 'country',\n 'gender',\n 'year_of_birth',\n 'level_of_education',\n 'specialty',\n 'professioncompany',\n 'title',\n 'mailing_address',\n 'goals',\n 'honor_code',\n 'terms_of_service']
\n
REGISTRATION_RATELIMIT\n
'60/7d'
\n
REGISTRATION_VALIDATION_RATELIMIT\n
'30/7d'
\n
REPO_ROOT\n
Path('/edx/app/edxapp/edx-platform')
\n
REQUIRE_BASE_URL\n
'./'
\n
REQUIRE_BUILD_PROFILE\n
'lms/js/build.js'
\n
REQUIRE_DEBUG\n
True
\n
REQUIRE_JS\n
'common/js/vendor/require.js'
\n
REQUIRE_JS_PATH_OVERRIDES\n
{'course_bookmarks/js/views/bookmark_button': 'course_bookmarks/js/views/bookmark_button.js',\n 'draggabilly': 'js/vendor/draggabilly.js',\n 'hls': 'common/js/vendor/hls.js',\n 'js/courseware/accordion_events': 'js/courseware/accordion_events.js',\n 'js/courseware/course_info_events': 'js/courseware/course_info_events.js',\n 'js/courseware/courseware_factory': 'js/courseware/courseware_factory.js',\n 'js/courseware/link_clicked_events': 'js/courseware/link_clicked_events.js',\n 'js/courseware/toggle_element_visibility': 'js/courseware/toggle_element_visibility.js',\n 'js/dateutil_factory': 'js/dateutil_factory.js',\n 'js/groups/discussions_management/discussions_dashboard_factory': 'js/discussions_management/views/discussions_dashboard_factory.js',\n 'js/groups/views/cohorts_dashboard_factory': 'js/groups/views/cohorts_dashboard_factory.js',\n 'js/student_account/logistration_factory': 'js/student_account/logistration_factory.js',\n 'js/views/message_banner': 'js/views/message_banner.js',\n 'moment': 'common/js/vendor/moment-with-locales.js',\n 'moment-timezone': 'common/js/vendor/moment-timezone-with-data.js'}
\n
RESET_PASSWORD_API_RATELIMIT\n
'********************'
\n
RESET_PASSWORD_TOKEN_VALIDATE_API_RATELIMIT\n
'********************'
\n
REST_FRAMEWORK\n
{'DEFAULT_PAGINATION_CLASS': 'edx_rest_framework_extensions.paginators.DefaultPagination',\n 'DEFAULT_RENDERER_CLASSES': ('rest_framework.renderers.JSONRenderer',\n                              'rest_framework.renderers.BrowsableAPIRenderer'),\n 'DEFAULT_THROTTLE_RATES': {'registration_validation': '30/minute',\n                            'service_user': '800/minute',\n                            'user': '60/minute'},\n 'EXCEPTION_HANDLER': 'openedx.core.lib.request_utils.expected_error_exception_handler',\n 'PAGE_SIZE': 10,\n 'URL_FORMAT_OVERRIDE': None}
\n
RETIRED_EMAIL_DOMAIN\n
'retired.invalid'
\n
RETIRED_EMAIL_FMT\n
'retired__user_{}@retired.invalid'
\n
RETIRED_EMAIL_PREFIX\n
'retired__user_'
\n
RETIRED_USERNAME_FMT\n
'retired__user_{}'
\n
RETIRED_USERNAME_PREFIX\n
'retired__user_'
\n
RETIRED_USER_SALTS\n
['OVERRIDE ME WITH A RANDOM VALUE', 'ROTATE SALTS BY APPENDING NEW VALUES']
\n
RETIREMENT_SERVICE_WORKER_USERNAME\n
'retirement_worker'
\n
RETIREMENT_STATES\n
['PENDING', 'ERRORED', 'ABORTED', 'COMPLETE']
\n
RETRY_ACTIVATION_EMAIL_MAX_ATTEMPTS\n
5
\n
RETRY_ACTIVATION_EMAIL_TIMEOUT\n
0.5
\n
RETRY_CALENDAR_SYNC_EMAIL_MAX_ATTEMPTS\n
5
\n
REVISION_CONFIG\n
{'EDX_PLATFORM_REVISION': 'master'}
\n
REVISION_CONFIG_FILE\n
'/edx/etc/revisions.yml'
\n
ROOT_URLCONF\n
'lms.urls'
\n
RSS_PROXY_CACHE_TIMEOUT\n
3600
\n
SAVE_FOR_LATER_EMAIL_RATE_LIMIT\n
'5/h'
\n
SAVE_FOR_LATER_IP_RATE_LIMIT\n
'100/d'
\n
SEARCH_ENGINE\n
'search.elastic.ElasticSearchEngine'
\n
SEARCH_FILTER_GENERATOR\n
'lms.lib.courseware_search.lms_filter_generator.LmsSearchFilterGenerator'
\n
SEARCH_INITIALIZER\n
'lms.lib.courseware_search.lms_search_initializer.LmsSearchInitializer'
\n
SEARCH_RESULT_PROCESSOR\n
'lms.lib.courseware_search.lms_result_processor.LmsSearchResultProcessor'
\n
SEARCH_SKIP_ENROLLMENT_START_DATE_FILTERING\n
True
\n
SECRET_KEY\n
'********************'
\n
SECURE_BROWSER_XSS_FILTER\n
False
\n
SECURE_CONTENT_TYPE_NOSNIFF\n
True
\n
SECURE_HSTS_INCLUDE_SUBDOMAINS\n
False
\n
SECURE_HSTS_PRELOAD\n
False
\n
SECURE_HSTS_SECONDS\n
0
\n
SECURE_PROXY_SSL_HEADER\n
('HTTP_X_FORWARDED_PROTO', 'https')
\n
SECURE_REDIRECT_EXEMPT\n
[]
\n
SECURE_REFERRER_POLICY\n
'same-origin'
\n
SECURE_SSL_HOST\n
None
\n
SECURE_SSL_REDIRECT\n
False
\n
SEGMENT_KEY\n
'********************'
\n
SERVER_EMAIL\n
'sre@example.com'
\n
SERVICE_VARIANT\n
'lms'
\n
SESSION_CACHE_ALIAS\n
'default'
\n
SESSION_COOKIE_AGE\n
1209600
\n
SESSION_COOKIE_DOMAIN\n
''
\n
SESSION_COOKIE_HTTPONLY\n
True
\n
SESSION_COOKIE_NAME\n
'lms_sessionid'
\n
SESSION_COOKIE_PATH\n
'/'
\n
SESSION_COOKIE_SAMESITE\n
'Lax'
\n
SESSION_COOKIE_SECURE\n
False
\n
SESSION_ENGINE\n
'django.contrib.sessions.backends.cache'
\n
SESSION_EXPIRE_AT_BROWSER_CLOSE\n
False
\n
SESSION_FILE_PATH\n
None
\n
SESSION_INACTIVITY_TIMEOUT_IN_SECONDS\n
None
\n
SESSION_SAVE_EVERY_REQUEST\n
False
\n
SESSION_SERIALIZER\n
'openedx.core.lib.session_serializers.PickleSerializer'
\n
SETTINGS_MODULE\n
'lms.envs.devstack_docker'
\n
SHARED_COOKIE_DOMAIN\n
''
\n
SHIBBOLETH_DOMAIN_PREFIX\n
'shib:'
\n
SHORT_DATETIME_FORMAT\n
'm/d/Y P'
\n
SHORT_DATE_FORMAT\n
'm/d/Y'
\n
SHOW_ACCOUNT_ACTIVATION_CTA\n
False
\n
SHOW_ACTIVATE_CTA_POPUP_COOKIE_NAME\n
'show-account-activation-popup'
\n
SIGNING_BACKEND\n
'django.core.signing.TimestampSigner'
\n
SILENCED_SYSTEM_CHECKS\n
[]
\n
SIMPLE_WIKI_REQUIRE_LOGIN_EDIT\n
True
\n
SIMPLE_WIKI_REQUIRE_LOGIN_VIEW\n
False
\n
SITE_ID\n
1
\n
SITE_NAME\n
'localhost:18000'
\n
SOCIAL_AUTH_AZUREAD_OAUTH2_AUTH_EXTRA_ARGUMENTS\n
{'msafed': 0}
\n
SOCIAL_AUTH_CLEAN_USERNAMES\n
True
\n
SOCIAL_AUTH_CLEAN_USERNAME_FUNCTION\n
'common.djangoapps.third_party_auth.models.clean_username'
\n
SOCIAL_AUTH_INACTIVE_USER_LOGIN\n
True
\n
SOCIAL_AUTH_INACTIVE_USER_URL\n
'/auth/inactive'
\n
SOCIAL_AUTH_LOGIN_ERROR_URL\n
'/'
\n
SOCIAL_AUTH_LOGIN_REDIRECT_URL\n
'/dashboard'
\n
SOCIAL_AUTH_LTI_CONSUMER_SECRETS\n
'********************'
\n
SOCIAL_AUTH_OAUTH_SECRETS\n
'********************'
\n
SOCIAL_AUTH_PIPELINE\n
['common.djangoapps.third_party_auth.pipeline.parse_query_params',\n 'social_core.pipeline.social_auth.social_details',\n 'social_core.pipeline.social_auth.social_uid',\n 'social_core.pipeline.social_auth.auth_allowed',\n 'social_core.pipeline.social_auth.social_user',\n 'common.djangoapps.third_party_auth.pipeline.associate_by_email_if_login_api',\n 'common.djangoapps.third_party_auth.pipeline.associate_by_email_if_saml',\n 'common.djangoapps.third_party_auth.pipeline.associate_by_email_if_oauth',\n 'common.djangoapps.third_party_auth.pipeline.get_username',\n 'common.djangoapps.third_party_auth.pipeline.set_pipeline_timeout',\n 'common.djangoapps.third_party_auth.pipeline.ensure_user_information',\n 'social_core.pipeline.user.create_user',\n 'social_core.pipeline.social_auth.associate_user',\n 'enterprise.tpa_pipeline.handle_enterprise_logistration',\n 'social_core.pipeline.social_auth.load_extra_data',\n 'social_core.pipeline.user.user_details',\n 'common.djangoapps.third_party_auth.pipeline.user_details_force_sync',\n 'common.djangoapps.third_party_auth.pipeline.set_id_verification_status',\n 'common.djangoapps.third_party_auth.pipeline.set_logged_in_cookies',\n 'common.djangoapps.third_party_auth.pipeline.login_analytics']
\n
SOCIAL_AUTH_PIPELINE_TIMEOUT\n
600
\n
SOCIAL_AUTH_PROTECTED_USER_FIELDS\n
['email']
\n
SOCIAL_AUTH_RAISE_EXCEPTIONS\n
False
\n
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY\n
'********************'
\n
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY_DICT\n
'********************'
\n
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT\n
''
\n
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT_DICT\n
{}
\n
SOCIAL_AUTH_SANITIZE_REDIRECTS\n
False
\n
SOCIAL_AUTH_STRATEGY\n
'common.djangoapps.third_party_auth.strategy.ConfigurationModelStrategy'
\n
SOCIAL_AUTH_UUID_LENGTH\n
4
\n
SOCIAL_MEDIA_FOOTER_DISPLAY\n
{'facebook': {'action': 'Like {platform_name} on Facebook',\n              'icon': 'fa-facebook-square',\n              'title': 'Facebook'},\n 'instagram': {'action': 'Follow {platform_name} on Instagram',\n               'icon': 'fa-instagram',\n               'title': 'Instagram'},\n 'linkedin': {'action': 'Follow {platform_name} on LinkedIn',\n              'icon': 'fa-linkedin-square',\n              'title': 'LinkedIn'},\n 'meetup': {'icon': 'fa-calendar', 'title': 'Meetup'},\n 'reddit': {'action': 'Subscribe to the {platform_name} subreddit',\n            'icon': 'fa-reddit-square',\n            'title': 'Reddit'},\n 'tumblr': {'icon': 'fa-tumblr', 'title': 'Tumblr'},\n 'twitter': {'action': 'Follow {platform_name} on Twitter',\n             'icon': 'fa-twitter-square',\n             'title': 'Twitter'},\n 'vk': {'icon': 'fa-vk', 'title': 'VK'},\n 'weibo': {'icon': 'fa-weibo', 'title': 'Weibo'},\n 'youtube': {'action': 'Subscribe to the {platform_name} YouTube channel',\n             'icon': 'fa-youtube-square',\n             'title': 'Youtube'}}
\n
SOCIAL_MEDIA_FOOTER_NAMES\n
['facebook', 'twitter', 'linkedin', 'instagram', 'reddit']
\n
SOCIAL_MEDIA_FOOTER_URLS\n
{}
\n
SOCIAL_PLATFORMS\n
{'facebook': {'display_name': 'Facebook',\n              'example': 'https://www.facebook.com/username',\n              'url_stub': 'facebook.com/'},\n 'linkedin': {'display_name': 'LinkedIn',\n              'example': 'www.linkedin.com/in/username',\n              'url_stub': 'linkedin.com/in/'},\n 'twitter': {'display_name': 'Twitter',\n             'example': 'https://www.twitter.com/username',\n             'url_stub': 'twitter.com/'}}
\n
SOCIAL_SHARING_SETTINGS\n
{'CERTIFICATE_FACEBOOK': False,\n 'CERTIFICATE_TWITTER': False,\n 'CUSTOM_COURSE_URLS': False,\n 'DASHBOARD_FACEBOOK': False,\n 'DASHBOARD_TWITTER': False}
\n
SOFTWARE_SECURE_REQUEST_RETRY_DELAY\n
3600
\n
SOFTWARE_SECURE_RETRY_MAX_ATTEMPTS\n
6
\n
SOFTWARE_SECURE_VERIFICATION_ROUTING_KEY\n
'********************'
\n
SSL_AUTH_DN_FORMAT_STRING\n
('/C=US/ST=Massachusetts/O=Massachusetts Institute of Technology/OU=Client CA '\n 'v1/CN={0}/emailAddress={1}')
\n
SSL_AUTH_EMAIL_DOMAIN\n
'MIT.EDU'
\n
STATICFILES_DIRS\n
[Path('/edx/app/edxapp/edx-platform/common/static'),\n Path('/edx/app/edxapp/edx-platform/lms/static'),\n Path('/edx/app/edxapp/edx-platform/node_modules/@edx')]
\n
STATICFILES_FINDERS\n
['openedx.core.djangoapps.theming.finders.ThemeFilesFinder',\n 'django.contrib.staticfiles.finders.FileSystemFinder',\n 'django.contrib.staticfiles.finders.AppDirectoriesFinder']
\n
STATICFILES_IGNORE_PATTERNS\n
('*.py',\n '*.pyc',\n 'sass/*.scss',\n 'sass/*/*.scss',\n 'sass/*/*/*.scss',\n 'sass/*/*/*/*.scss',\n 'spec',\n 'spec_helpers',\n 'xmodule_js')
\n
STATICFILES_STORAGE\n
'openedx.core.storage.DevelopmentStorage'
\n
STATICFILES_STORAGE_KWARGS\n
{}
\n
STATICI18N_DOMAIN\n
'djangojs'
\n
STATICI18N_FILENAME_FUNCTION\n
'statici18n.utils.legacy_filename'
\n
STATICI18N_NAMESPACE\n
None
\n
STATICI18N_OUTPUT_DIR\n
'js/i18n'
\n
STATICI18N_PACKAGES\n
'django.conf'
\n
STATICI18N_ROOT\n
Path('/edx/app/edxapp/edx-platform/lms/static')
\n
STATIC_GRAB\n
False
\n
STATIC_ROOT\n
Path('/edx/var/edxapp/staticfiles')
\n
STATIC_ROOT_BASE\n
'/edx/var/edxapp/staticfiles'
\n
STATIC_TEMPLATE_VIEW_DEFAULT_FILE_EXTENSION\n
'html'
\n
STATIC_URL\n
'/static/'
\n
STATIC_URL_BASE\n
'/static/'
\n
STATUS_MESSAGE_PATH\n
Path('/edx/app/edxapp/status_message.json')
\n
STUDENTMODULEHISTORYEXTENDED_OFFSET\n
10000
\n
STUDENT_FILEUPLOAD_MAX_SIZE\n
4000000
\n
STUDIO_NAME\n
'Studio'
\n
STUDIO_SHORT_NAME\n
'Studio'
\n
SUPPORT_HOW_TO_UNENROLL_LINK\n
''
\n
SUPPORT_SITE_LINK\n
''
\n
SWAGGER_SETTINGS\n
{'DEFAULT_INFO': 'openedx.core.apidocs.api_info'}
\n
SWIFT_AUTH_URL\n
None
\n
SWIFT_AUTH_VERSION\n
None
\n
SWIFT_KEY\n
'********************'
\n
SWIFT_REGION_NAME\n
None
\n
SWIFT_TEMP_URL_DURATION\n
1800
\n
SWIFT_TEMP_URL_KEY\n
'********************'
\n
SWIFT_TENANT_ID\n
None
\n
SWIFT_TENANT_NAME\n
None
\n
SWIFT_USERNAME\n
None
\n
SWIFT_USE_TEMP_URLS\n
False
\n
SYSLOG_SERVER\n
''
\n
SYSTEM_TO_FEATURE_ROLE_MAPPING\n
{'enterprise_admin': ['dashboard_admin',\n                      'catalog_admin',\n                      'enrollment_api_admin',\n                      'reporting_config_admin'],\n 'enterprise_openedx_operator': ['dashboard_admin',\n                                 'catalog_admin',\n                                 'enrollment_api_admin',\n                                 'reporting_config_admin']}
\n
SYSTEM_WIDE_ROLE_CLASSES\n
['system_wide_roles.SystemWideRoleAssignment',\n 'enterprise.SystemWideEnterpriseUserRoleAssignment']
\n
TEAMS_HELP_URL\n
'https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_features/teams/teams_setup.html'
\n
TECH_SUPPORT_EMAIL\n
'technical@example.com'
\n
TEMPLATES\n
[{'APP_DIRS': False,\n  'BACKEND': 'django.template.backends.django.DjangoTemplates',\n  'DIRS': [Path('/edx/app/edxapp/edx-platform/lms/templates'),\n           Path('/edx/app/edxapp/edx-platform/common/templates'),\n           Path('/edx/app/edxapp/edx-platform/common/lib/capa/capa/templates'),\n           Path('/edx/app/edxapp/edx-platform/common/djangoapps/pipeline_mako/templates'),\n           Path('/edx/app/edxapp/edx-platform/common/static')],\n  'NAME': 'django',\n  'OPTIONS': {'context_processors': ['django.template.context_processors.request',\n                                     'django.template.context_processors.static',\n                                     'django.template.context_processors.i18n',\n                                     'django.contrib.auth.context_processors.auth',\n                                     'django.template.context_processors.csrf',\n                                     'django.template.context_processors.media',\n                                     'django.template.context_processors.tz',\n                                     'django.contrib.messages.context_processors.messages',\n                                     'sekizai.context_processors.sekizai',\n                                     'common.djangoapps.edxmako.shortcuts.marketing_link_context_processor',\n                                     'lms.djangoapps.courseware.context_processor.user_timezone_locale_prefs',\n                                     'help_tokens.context_processor',\n                                     'openedx.core.djangoapps.site_configuration.context_processors.configuration_context',\n                                     'lms.djangoapps.mobile_api.context_processor.is_from_mobile_app',\n                                     'social_django.context_processors.backends',\n                                     'social_django.context_processors.login_redirect'],\n              'debug': True,\n              'loaders': ['openedx.core.djangoapps.theming.template_loaders.ThemeTemplateLoader',\n                          'common.djangoapps.edxmako.makoloader.MakoFilesystemLoader',\n                          'common.djangoapps.edxmako.makoloader.MakoAppDirectoriesLoader']}},\n {'APP_DIRS': False,\n  'BACKEND': 'common.djangoapps.edxmako.backend.Mako',\n  'DIRS': [Path('/edx/app/edxapp/edx-platform/lms/templates'),\n           Path('/edx/app/edxapp/edx-platform/common/templates'),\n           Path('/edx/app/edxapp/edx-platform/common/lib/capa/capa/templates'),\n           Path('/edx/app/edxapp/edx-platform/common/djangoapps/pipeline_mako/templates'),\n           Path('/edx/app/edxapp/edx-platform/openedx/core/djangoapps/cors_csrf/templates'),\n           Path('/edx/app/edxapp/edx-platform/openedx/core/djangoapps/dark_lang/templates'),\n           Path('/edx/app/edxapp/edx-platform/openedx/core/lib/license/templates'),\n           Path('/edx/app/edxapp/edx-platform/openedx/features/course_experience/templates')],\n  'NAME': 'mako',\n  'OPTIONS': {'context_processors': ['django.template.context_processors.request',\n                                     'django.template.context_processors.static',\n                                     'django.template.context_processors.i18n',\n                                     'django.contrib.auth.context_processors.auth',\n                                     'django.template.context_processors.csrf',\n                                     'django.template.context_processors.media',\n                                     'django.template.context_processors.tz',\n                                     'django.contrib.messages.context_processors.messages',\n                                     'sekizai.context_processors.sekizai',\n                                     'common.djangoapps.edxmako.shortcuts.marketing_link_context_processor',\n                                     'lms.djangoapps.courseware.context_processor.user_timezone_locale_prefs',\n                                     'help_tokens.context_processor',\n                                     'openedx.core.djangoapps.site_configuration.context_processors.configuration_context',\n                                     'lms.djangoapps.mobile_api.context_processor.is_from_mobile_app',\n                                     'social_django.context_processors.backends',\n                                     'social_django.context_processors.login_redirect'],\n              'debug': False}}]
\n
TEST_NON_SERIALIZED_APPS\n
[]
\n
TEST_RUNNER\n
'django.test.runner.DiscoverRunner'
\n
TEXTBOOKS_HELP_URL\n
'https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_assets/textbooks.html'
\n
THIRD_PARTY_AUTH_BACKENDS\n
['social_core.backends.google.GoogleOAuth2',\n 'social_core.backends.linkedin.LinkedinOAuth2',\n 'social_core.backends.facebook.FacebookOAuth2',\n 'social_core.backends.azuread.AzureADOAuth2',\n 'common.djangoapps.third_party_auth.appleid.AppleIdAuth',\n 'common.djangoapps.third_party_auth.identityserver3.IdentityServer3',\n 'common.djangoapps.third_party_auth.saml.SAMLAuthBackend',\n 'common.djangoapps.third_party_auth.lti.LTIAuthBackend']
\n
THIRD_PARTY_AUTH_CUSTOM_AUTH_FORMS\n
{}
\n
THIRD_PARTY_AUTH_OLD_CONFIG\n
None
\n
THOUSAND_SEPARATOR\n
','
\n
TIME_FORMAT\n
'P'
\n
TIME_INPUT_FORMATS\n
['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
\n
TIME_ZONE\n
'UTC'
\n
TIME_ZONE_DISPLAYED_FOR_DEADLINES\n
'UTC'
\n
TPA_PROVIDER_BURST_THROTTLE\n
'10/min'
\n
TPA_PROVIDER_SUSTAINED_THROTTLE\n
'50/hr'
\n
TRACKING_BACKENDS\n
{'logger': {'ENGINE': 'common.djangoapps.track.backends.logger.LoggerBackend',\n            'OPTIONS': {'name': 'tracking'}}}
\n
TRACKING_IGNORE_URL_PATTERNS\n
['^/event', '^/login', '^/heartbeat', '^/segmentio/event', '^/performance']
\n
TRACKING_SEGMENTIO_ALLOWED_TYPES\n
['track']
\n
TRACKING_SEGMENTIO_DISALLOWED_SUBSTRING_NAMES\n
[]
\n
TRACKING_SEGMENTIO_SOURCE_MAP\n
{'analytics-android': 'mobile', 'analytics-ios': 'mobile'}
\n
TRACKING_SEGMENTIO_WEBHOOK_SECRET\n
'********************'
\n
TRACK_MAX_EVENT\n
50000
\n
TRANSLATORS_GUIDE\n
'https://edx.readthedocs.org/projects/edx-developer-guide/en/latest/conventions/internationalization/i18n_translators_guide.html'
\n
UNIVERSITY_EMAIL\n
'university@example.com'
\n
USAGE_ID_PATTERN\n
'(?P<usage_id>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))'
\n
USAGE_KEY_PATTERN\n
'********************'
\n
USERNAME_PATTERN\n
'(?P<username>[\\\\w .@_+-]+)'
\n
USERNAME_REGEX_PARTIAL\n
'[\\\\w .@_+-]+'
\n
USERNAME_REPLACEMENT_WORKER\n
'OVERRIDE THIS WITH A VALID USERNAME'
\n
USER_STATE_BATCH_SIZE\n
5000
\n
USE_I18N\n
True
\n
USE_L10N\n
True
\n
USE_THOUSAND_SEPARATOR\n
False
\n
USE_TZ\n
True
\n
USE_X_FORWARDED_HOST\n
False
\n
USE_X_FORWARDED_PORT\n
False
\n
VERIFICATION_EXPIRY_EMAIL\n
{'DAYS_RANGE': 1, 'DEFAULT_EMAILS': 2, 'RESEND_DAYS': 15}
\n
VERIFY_STUDENT\n
{'DAYS_GOOD_FOR': 365,\n 'EXPIRING_SOON_WINDOW': 28,\n 'SOFTWARE_SECURE': {'API_ACCESS_KEY': '********************',\n                     'API_SECRET_KEY': '********************'}}
\n
VIDEO_CDN_URL\n
{'EXAMPLE_COUNTRY_CODE': 'http://example.com/edx/video?s3_url='}
\n
VIDEO_IMAGE_MAX_AGE\n
31536000
\n
VIDEO_IMAGE_SETTINGS\n
{'DIRECTORY_PREFIX': 'video-images/',\n 'STORAGE_KWARGS': {'base_url': '/media/',\n                    'location': '/edx/var/edxapp/media//'},\n 'VIDEO_IMAGE_MAX_BYTES': 2097152,\n 'VIDEO_IMAGE_MIN_BYTES': 2048}
\n
VIDEO_TRANSCRIPTS_MAX_AGE\n
31536000
\n
VIDEO_TRANSCRIPTS_SETTINGS\n
{'DIRECTORY_PREFIX': 'video-transcripts/',\n 'STORAGE_KWARGS': {'base_url': '/media/',\n                    'location': '/edx/var/edxapp/media//'},\n 'VIDEO_TRANSCRIPTS_MAX_BYTES': 3145728}
\n
VIDEO_UPLOAD_PIPELINE\n
{'BUCKET': '', 'ROOT_PATH': ''}
\n
WEBPACK_CONFIG_PATH\n
'webpack.dev.config.js'
\n
WEBPACK_LOADER\n
{'DEFAULT': {'BUNDLE_DIR_NAME': 'bundles/',\n             'STATS_FILE': Path('/edx/var/edxapp/staticfiles/webpack-stats.json'),\n             'TIMEOUT': 5},\n 'WORKERS': {'BUNDLE_DIR_NAME': 'bundles/',\n             'STATS_FILE': Path('/edx/var/edxapp/staticfiles/webpack-worker-stats.json')}}
\n
WIKI_ACCOUNT_HANDLING\n
False
\n
WIKI_ANONYMOUS\n
False
\n
WIKI_CAN_ASSIGN\n
<function CAN_ASSIGN at 0x7f0032c428b0>
\n
WIKI_CAN_CHANGE_PERMISSIONS\n
<function CAN_CHANGE_PERMISSIONS at 0x7f0032c42820>
\n
WIKI_CAN_DELETE\n
<function CAN_DELETE at 0x7f0032c3d700>
\n
WIKI_CAN_MODERATE\n
<function CAN_MODERATE at 0x7f0032c42790>
\n
WIKI_EDITOR\n
'lms.djangoapps.course_wiki.editors.CodeMirror'
\n
WIKI_ENABLED\n
True
\n
WIKI_HELP_URL\n
'https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_assets/course_wiki.html'
\n
WIKI_LINK_DEFAULT_LEVEL\n
2
\n
WIKI_LINK_LIVE_LOOKUPS\n
False
\n
WIKI_SHOW_MAX_CHILDREN\n
0
\n
WIKI_USE_BOOTSTRAP_SELECT_WIDGET\n
False
\n
WRITABLE_GRADEBOOK_URL\n
'http://localhost:1994'
\n
WSGI_APPLICATION\n
None
\n
XBLOCK_EXTRA_MIXINS\n
()
\n
XBLOCK_FIELD_DATA_WRAPPERS\n
('lms.djangoapps.courseware.field_overrides:OverrideModulestoreFieldData.wrap',)
\n
XBLOCK_FS_STORAGE_BUCKET\n
None
\n
XBLOCK_FS_STORAGE_PREFIX\n
None
\n
XBLOCK_MIXINS\n
(<class 'lms.djangoapps.lms_xblock.mixin.LmsBlockMixin'>,\n <class 'xmodule.modulestore.inheritance.InheritanceMixin'>,\n <class 'xmodule.x_module.XModuleMixin'>,\n <class 'xmodule.modulestore.edit_info.EditInfoMixin'>)
\n
XBLOCK_RUNTIME_V2_EPHEMERAL_DATA_CACHE\n
'default'
\n
XBLOCK_SELECT_FUNCTION\n
<function prefer_xmodules at 0x7f00333229d0>
\n
XBLOCK_SETTINGS\n
{'VideoBlock': {'YOUTUBE_API_KEY': '********************',\n                'licensing_enabled': False}}
\n
XDOMAIN_PROXY_CACHE_TIMEOUT\n
900
\n
XQUEUE_INTERFACE\n
{'basic_auth': ['edx', 'edx'],\n 'django_auth': {'password': '********************', 'username': 'lms'},\n 'url': 'http://edx.devstack.xqueue:18040'}
\n
XQUEUE_WAITTIME_BETWEEN_REQUESTS\n
5
\n
X_FRAME_OPTIONS\n
'DENY'
\n
YEAR_MONTH_FORMAT\n
'F Y'
\n
YOUTUBE\n
{'API': '********************',\n 'IMAGE_API': '********************',\n 'METADATA_URL': 'https://www.googleapis.com/youtube/v3/videos/',\n 'TEST_TIMEOUT': 1500,\n 'TEXT_API': '********************'}
\n
YOUTUBE_API_KEY\n
'********************'
\n
ZENDESK_API_KEY\n
'********************'
\n
ZENDESK_CUSTOM_FIELDS\n
{}
\n
ZENDESK_GROUP_ID_MAPPING\n
{}
\n
ZENDESK_OAUTH_ACCESS_TOKEN\n
'********************'
\n
ZENDESK_URL\n
''
\n
ZENDESK_USER\n
''
\n
\n
\n
\n

\n You’re seeing this error because you have \n DEBUG = True in your\n Django settings file. Change that to\n False, and Django will\n display a standard page generated by the handler for this status code.\n

\n
\n \n \n \n
\n \n
\n
\n D\n JDT\n
\n
\n
\n
\n \n

Versions

\n
\n
\n
\n
\n
\n
\n
\n
\n \n

Time

\n
\n
\n
\n
\n
\n
\n
\n
\n \n

Settings from lms.envs.devstack_docker

\n
\n
\n
\n
\n
\n
\n
\n
\n \n

Headers

\n
\n
\n
\n
\n
\n
\n
\n
\n \n

Request

\n
\n
\n
\n
\n
\n
\n
\n
\n \n

SQL queries from 2 connections

\n
\n
\n
\n
\n
\n
\n
\n
\n \n

Signals

\n
\n
\n
\n
\n
\n
\n
\n
\n \n

Log messages

\n
\n
\n
\n
\n
\n
\n
\n
\n \n

History

\n
\n
\n
\n
\n
\n
\n
\n
\n \n" + }, + { + "name": "Submit Grade Success", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"options_selected\": {\n \"Ideas\": \"Good\",\n \"Content\": \"Good\"\n },\n \"criterion_feedback\": {\n \"Ideas\": \"did alright\"\n },\n \"overall_feedback\": \"was okay\",\n \"submission_uuid\": \"{{submission_id}}\",\n \"assess_type\": \"full-grade\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/staff_assess", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "staff_assess" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Tue, 11 Jan 2022 17:05:49 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Content-Length", + "value": "28" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=2573.0000000000073;desc=\"User CPU time\", TimerPanel_stime;dur=394.36299999999846;desc=\"System CPU time\", TimerPanel_total;dur=2967.3630000000057;desc=\"Total CPU time\", TimerPanel_total_time;dur=6811.989307403564;desc=\"Elapsed time\", SQLPanel_sql_time;dur=212.1577262878418;desc=\"SQL 32 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Tue, 25 Jan 2022 17:05:49 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "{\n \"success\": true,\n \"msg\": \"\"\n}" + }, + { + "name": "Submit Grade Auth Failure", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "X-CSRFToken", + "value": "{{csrftoken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"options_selected\": {\n \"Ideas\": \"Good\",\n \"Content\": \"Good\"\n },\n \"criterion_feedback\": {\n \"Ideas\": \"did alright\"\n },\n \"overall_feedback\": \"was okay\",\n \"submission_uuid\": \"{{submission_id}}\",\n \"assess_type\": \"full-grade\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{lms_url}}/courses/{{course_id}}/xblock/{{block_id}}/handler/staff_assess", + "protocol": "{{protocol}}", + "host": [ + "{{lms_url}}" + ], + "path": [ + "courses", + "{{course_id}}", + "xblock", + "{{block_id}}", + "handler", + "staff_assess" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "html", + "header": [ + { + "key": "Date", + "value": "Wed, 12 Jan 2022 21:41:49 GMT" + }, + { + "key": "Server", + "value": "WSGIServer/0.2 CPython/3.8.10" + }, + { + "key": "Content-Type", + "value": "application/html" + }, + { + "key": "Content-Length", + "value": "73" + }, + { + "key": "Server-Timing", + "value": "TimerPanel_utime;dur=1252.0779999999972;desc=\"User CPU time\", TimerPanel_stime;dur=391.41200000000254;desc=\"System CPU time\", TimerPanel_total;dur=1643.4899999999998;desc=\"Total CPU time\", TimerPanel_total_time;dur=4194.627523422241;desc=\"Elapsed time\", SQLPanel_sql_time;dur=81.1922550201416;desc=\"SQL 17 queries\"" + }, + { + "key": "Vary", + "value": "Accept-Language, Origin, Cookie" + }, + { + "key": "Content-Language", + "value": "en" + }, + { + "key": "Set-Cookie", + "value": "openedx-language-preference=en; expires=Wed, 26 Jan 2022 21:41:49 GMT; Max-Age=1209600; Path=/; SameSite=Lax" + } + ], + "cookie": [], + "body": "
You do not have permission to access ORA learner information.
" + } + ] + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var xsrfCookie = postman.getResponseCookie(\"csrftoken\");", + "postman.setEnvironmentVariable('csrftoken', xsrfCookie.value);" + ] + } + } + ], + "variable": [ + { + "key": "course_id_encoded", + "value": "" + }, + { + "key": "block_id_encoded", + "value": "" + } + ] +} \ No newline at end of file diff --git a/lms/djangoapps/ora_staff_grader/serializers.py b/lms/djangoapps/ora_staff_grader/serializers.py new file mode 100644 index 0000000000..ad01f5212a --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/serializers.py @@ -0,0 +1,304 @@ +""" +Serializers for Enhanced Staff Grader (ESG) +""" +# pylint: disable=abstract-method +# pylint: disable=missing-function-docstring + +from rest_framework import serializers + +from openedx.core.djangoapps.content.course_overviews.models import CourseOverview + + +class GradeStatusField(serializers.ChoiceField): + """Field that can have the values ['graded' 'ungraded']""" + + def __init__(self, *args, **kwargs): + kwargs["choices"] = ["graded", "ungraded"] + super().__init__(*args, **kwargs) + + +class LockStatusField(serializers.ChoiceField): + """Field that can have the values ['unlocked', 'locked', 'in-progress']""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs, choices=["unlocked", "locked", "in-progress"]) + + +class CourseMetadataSerializer(serializers.Serializer): + """ + Serialize top-level info about a course, used for creating header in ESG + """ + + title = serializers.CharField(source="display_name") + org = serializers.CharField(source="display_org_with_default") + number = serializers.CharField(source="display_number_with_default") + courseId = serializers.CharField(source="id") + + class Meta: + model = CourseOverview + + fields = [ + "title", + "org", + "number", + "courseId", + ] + read_only_fields = fields + + +class RubricCriterionOptionsSerializer(serializers.Serializer): + """Serializer for selectable options in a rubric criterion""" + + label = serializers.CharField() + points = serializers.IntegerField() + explanation = serializers.CharField() + name = serializers.CharField() + orderNum = serializers.IntegerField(source="order_num") + + +class RubricCriterionSerializer(serializers.Serializer): + """Serializer for individual criteria in a rubric""" + + label = serializers.CharField() + prompt = serializers.CharField() + feedback = serializers.ChoiceField( + required=False, choices=["optional", "disabled", "required"], default="disabled" + ) + name = serializers.CharField() + orderNum = serializers.IntegerField(source="order_num") + options = serializers.ListField(child=RubricCriterionOptionsSerializer()) + + +class RubricConfigSerializer(serializers.Serializer): + """Serializer for rubric config""" + + feedbackPrompt = serializers.CharField(source="rubric_feedback_prompt") + criteria = serializers.ListField( + source="rubric_criteria", child=RubricCriterionSerializer() + ) + + +class OpenResponseMetadataSerializer(serializers.Serializer): + """ + Serialize ORA metadata, used for setting up views in ESG + """ + + name = serializers.CharField(source="display_name") + prompts = serializers.ListField() + type = serializers.SerializerMethodField() + textResponseConfig = serializers.SerializerMethodField() + fileUploadResponseConfig = serializers.SerializerMethodField() + rubricConfig = RubricConfigSerializer(source="*") + + def get_textResponseConfig(self, instance): + return instance.text_response or "none" + + def get_fileUploadResponseConfig(self, instance): + return instance.file_upload_response or "none" + + def get_type(self, instance): + return "team" if instance.teams_enabled else "individual" + + class Meta: + fields = [ + "name", + "prompts", + "type", + "textResponseConfig", + "fileUploadResponseConfig", + "rubricConfig", + ] + read_only_fields = fields + + +class ScoreField(serializers.Field): + """Returns None if score is not given for a submission""" + + def to_representation(self, value): + if ("pointsEarned" not in value) and ("pointsPossible" not in value): + return None + return ScoreSerializer(value).data + + +class ScoreSerializer(serializers.Serializer): + """ + Score (points earned/possible) for use in SubmissionMetadataSerializer + """ + + pointsEarned = serializers.IntegerField(required=False) + pointsPossible = serializers.IntegerField(required=False) + + +class SubmissionMetadataSerializer(serializers.Serializer): + """ + Submission metadata for displaying submissions table in ESG + """ + + submissionUUID = serializers.CharField(source="submissionUuid") + username = serializers.CharField(allow_null=True) + teamName = serializers.CharField(allow_null=True) + dateSubmitted = serializers.DateTimeField() + dateGraded = serializers.DateTimeField(allow_null=True) + gradedBy = serializers.CharField(allow_null=True) + gradingStatus = GradeStatusField() + lockStatus = LockStatusField() + score = ScoreField() + + class Meta: + fields = [ + "submissionUUID", + "username", + "teamName", + "dateSubmitted", + "dateGraded", + "gradedBy", + "gradingStatus", + "lockStatus", + "score", + ] + read_only_fields = fields + + +class InitializeSerializer(serializers.Serializer): + """ + Serialize info for the initialize call. Packages ORA, course, submission, and rubric data. + """ + + courseMetadata = CourseMetadataSerializer() + oraMetadata = OpenResponseMetadataSerializer() + submissions = serializers.DictField(child=SubmissionMetadataSerializer()) + + class Meta: + fields = [ + "courseMetadata", + "oraMetadata", + "submissions", + ] + read_only_fields = fields + + +class UploadedFileSerializer(serializers.Serializer): + """Serializer for a file uploaded as a part of a response""" + + downloadUrl = serializers.URLField(source="download_url") + description = serializers.CharField() + name = serializers.CharField() + size = serializers.IntegerField() + + +class ResponseSerializer(serializers.Serializer): + """Serializer for the responseData api construct, which represents the contents of a submitted learner response""" + + files = serializers.ListField(child=UploadedFileSerializer(), allow_empty=True) + text = serializers.ListField(child=serializers.CharField(), allow_empty=True) + + +class AssessmentCriteriaSerializer(serializers.Serializer): + """Serializer for information about a criterion, in the context of a completed assessment""" + + name = serializers.CharField() + feedback = serializers.CharField() + points = serializers.IntegerField() + selectedOption = serializers.CharField(source="option") + + +class GradeDataSerializer(serializers.Serializer): + """Serializer for the `gradeData` api construct, which represents a completed staff assessment""" + + score = ScoreField(required=False) + overallFeedback = serializers.CharField(source="feedback", required=False) + criteria = serializers.ListField( + child=AssessmentCriteriaSerializer(), allow_empty=True, required=False + ) + + +class SubmissionStatusFetchSerializer(serializers.Serializer): + """Serializer for the response from the submission status fetch endpoint""" + + gradeData = GradeDataSerializer(source="assessment_info") + gradeStatus = serializers.SerializerMethodField() + lockStatus = LockStatusField(source="lock_info.lock_status") + + def get_gradeStatus(self, obj): + if not obj.get("assessment_info", {}) == {}: + return "graded" + else: + return "ungraded" + + +class SubmissionFetchSerializer(SubmissionStatusFetchSerializer): + """ + Serializer for the response from the submission fetch endpoint + Same as the SubmissionStatusFetchSerializer with an added submission_info field + """ + + response = ResponseSerializer(source="submission_info") + + +class LockStatusSerializer(serializers.Serializer): + """ + Info about the status of a submission lock, with extra metadata stripped out. + """ + + lockStatus = LockStatusField(source="lock_status") + + class Meta: + fields = ["lockStatus"] + read_only_fields = fields + + +class StaffAssessSerializer(serializers.Serializer): + """ + Converts grade data to the format used for doing staff assessments + + From: { + "overallFeedback": "was pretty good", + "criteria": [ + { + "name": "", + "feedback": (string), + "selectedOption": + } + ] + } + + To: { + 'options_selected': { + '': , + '': , + }, + 'criterion_feedback': { + '': (string) + }, + 'overall_feedback': (string) + 'submission_uuid': (string) + 'assess_type': (string) one of ['regrade', full-grade'] + } + """ + + # Context should include 'submission_uuid' for serialization + requires_context = True + + options_selected = serializers.SerializerMethodField() + criterion_feedback = serializers.SerializerMethodField() + overall_feedback = serializers.CharField(source="overallFeedback", allow_null=True) + submission_uuid = serializers.SerializerMethodField() + assess_type = serializers.CharField(default="full-grade") + + def get_options_selected(self, instance): + options_selected = {} + for criterion in instance.get("criteria"): + options_selected[criterion["name"]] = criterion["selectedOption"] + + return options_selected + + def get_criterion_feedback(self, instance): + criterion_feedback = {} + for criterion in instance.get("criteria"): + if criterion.get("feedback"): + criterion_feedback[criterion["name"]] = criterion["feedback"] + + return criterion_feedback + + def get_submission_uuid(self, instance): # pylint: disable=unused-argument + return self.context.get("submission_uuid") diff --git a/lms/djangoapps/ora_staff_grader/tests/__init__.py b/lms/djangoapps/ora_staff_grader/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/ora_staff_grader/tests/test_data.py b/lms/djangoapps/ora_staff_grader/tests/test_data.py new file mode 100644 index 0000000000..b2f212f54d --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/tests/test_data.py @@ -0,0 +1,178 @@ +""" Data shapes used for testing ESG """ + +# Options split for reuse +example_rubric_options = [ + { + "order_num": 0, + "name": "troll", + "label": "Troll", + "explanation": "Failing grade", + "points": 0, + }, + { + "order_num": 1, + "name": "dreadful", + "label": "Dreadful", + "explanation": "Failing grade", + "points": 1, + }, + { + "order_num": 2, + "name": "poor", + "label": "Poor", + "explanation": "Failing grade (may repeat)", + "points": 2, + }, + { + "order_num": 3, + "name": "poor", + "label": "Poor", + "explanation": "Failing grade (may repeat)", + "points": 3, + }, + { + "order_num": 4, + "name": "acceptable", + "label": "Acceptable", + "explanation": "Passing grade (may continue to N.E.W.T)", + "points": 4, + }, + { + "order_num": 5, + "name": "exceeds_expectations", + "label": "Exceeds Expectations", + "explanation": "Passing grade (may continue to N.E.W.T)", + "points": 5, + }, + { + "order_num": 6, + "name": "outstanding", + "label": "Outstanding", + "explanation": "Passing grade (will continue to N.E.W.T)", + "points": 6, + }, +] + +example_rubric_options_serialized = [ + { + "orderNum": 0, + "name": "troll", + "label": "Troll", + "explanation": "Failing grade", + "points": 0, + }, + { + "orderNum": 1, + "name": "dreadful", + "label": "Dreadful", + "explanation": "Failing grade", + "points": 1, + }, + { + "orderNum": 2, + "name": "poor", + "label": "Poor", + "explanation": "Failing grade (may repeat)", + "points": 2, + }, + { + "orderNum": 3, + "name": "poor", + "label": "Poor", + "explanation": "Failing grade (may repeat)", + "points": 3, + }, + { + "orderNum": 4, + "name": "acceptable", + "label": "Acceptable", + "explanation": "Passing grade (may continue to N.E.W.T)", + "points": 4, + }, + { + "orderNum": 5, + "name": "exceeds_expectations", + "label": "Exceeds Expectations", + "explanation": "Passing grade (may continue to N.E.W.T)", + "points": 5, + }, + { + "orderNum": 6, + "name": "outstanding", + "label": "Outstanding", + "explanation": "Passing grade (will continue to N.E.W.T)", + "points": 6, + }, +] + +example_rubric = { + "rubric_feedback_prompt": "How did this student do?", + "rubric_feedback_default_text": "For the O.W.L exams, this student...", + "rubric_criteria": [ + { + "order_num": 0, + "name": "potions", + "label": "Potions", + "prompt": "How did this student perform in the Potions exam", + "feedback": "optional", + "options": example_rubric_options, + }, + { + "order_num": 1, + "name": "charms", + "label": "Charms", + "prompt": "How did this student perform in the Charms exam", + "options": example_rubric_options, + }, + ], +} + +example_submission_list = { + "b086331a-5c50-428a-8348-5a85e5029299": { + "submissionUuid": "b086331a-5c50-428a-8348-5a85e5029299", + "username": "buzz", + "teamName": None, + "dateSubmitted": "1969-07-16 13:32:00", + "dateGraded": None, + "gradedBy": None, + "gradingStatus": "ungraded", + "lockStatus": "unlocked", + "score": {"pointsEarned": 0, "pointsPossible": 10}, + } +} + +example_submission = { + "text": ["This is the answer"], + "files": [ + { + "name": "name_0", + "description": "description_0", + "download_url": "www.file_url.com/key_0", + "size": 123455, + } + ], +} + +example_assessment = { + "feedback": "Base Assessment Feedback", + "score": { + "pointsEarned": 5, + "pointsPossible": 6, + }, + "criteria": [ + { + "name": "Criterion 1", + "option": "Three", + "points": 3, + "feedback": "Feedback 1", + }, + ], +} + +example_grade_data = { + "overallFeedback": "was pretty good", + "criteria": [ + {"name": "Ideas", "feedback": "did alright", "selectedOption": "Fair"}, + {"name": "Content", "selectedOption": "Excellent"}, + ], +} diff --git a/lms/djangoapps/ora_staff_grader/tests/test_serializers.py b/lms/djangoapps/ora_staff_grader/tests/test_serializers.py new file mode 100644 index 0000000000..58719473a8 --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/tests/test_serializers.py @@ -0,0 +1,698 @@ +""" +Tests for ESG Serializers +""" +from unittest.mock import Mock, MagicMock, patch + +import ddt +from django.test import TestCase +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase + +from lms.djangoapps.ora_staff_grader.errors import ERR_UNKNOWN, ErrorSerializer +from lms.djangoapps.ora_staff_grader.serializers import ( + AssessmentCriteriaSerializer, + CourseMetadataSerializer, + GradeDataSerializer, + InitializeSerializer, + LockStatusSerializer, + LockStatusField, + OpenResponseMetadataSerializer, + ResponseSerializer, + RubricConfigSerializer, + ScoreField, + ScoreSerializer, + StaffAssessSerializer, + SubmissionFetchSerializer, + SubmissionStatusFetchSerializer, + SubmissionMetadataSerializer, + UploadedFileSerializer, +) +from lms.djangoapps.ora_staff_grader.tests import test_data +from openedx.core.djangoapps.content.course_overviews.tests.factories import ( + CourseOverviewFactory, +) + + +class TestErrorSerializer(TestCase): + """Tests for error serialization""" + + def test_no_error_code(self): + # If no error code is provided, fall back to an unknown code + input_data = {} + data = ErrorSerializer(input_data).data + + assert data == {"error": ERR_UNKNOWN} + + def test_no_context(self): + # The serializer may return just the error info + input_data = {"error": "ERR_CODE"} + data = ErrorSerializer(input_data).data + + assert data == {"error": "ERR_CODE"} + + def test_added_context(self): + # The serializer may also add context which gets unpacked into the output + input_data = {"error": "ERR_CODE"} + added_context = {"a": "b", "c": {"d": ["e", "f"]}} + data = ErrorSerializer(input_data, context=added_context).data + + # Extra context should be added to the output + assert data == {"error": "ERR_CODE", "a": "b", "c": {"d": ["e", "f"]}} + + +class TestCourseMetadataSerializer(SharedModuleStoreTestCase): + """ + Tests for CourseMetadataSerializer + """ + + course_data = { + "org": "Oxford", + "display_name": "Introduction to Time Travel", + "display_number_with_default": "TT101", + "run": "2054", + } + + course_id = "course-v1:Oxford+TT101+2054" + + def setUp(self): + super().setUp() + + self.course_overview = CourseOverviewFactory.create(**self.course_data) + + def test_course_serialize(self): + data = CourseMetadataSerializer(self.course_overview).data + + assert data == { + "title": self.course_data["display_name"], + "org": self.course_data["org"], + "number": self.course_data["display_number_with_default"], + "courseId": self.course_id, + } + + +@ddt.ddt +class TestOpenResponseMetadataSerializer(TestCase): + """ + Tests for OpenResponseMetadataSerializer + """ + + def setUp(self): + super().setUp() + + self.ora_data = { + "display_name": "Week 1: Time Travel Paradoxes", + "prompts": [ + "

In your own words, explain a famous time travel paradox

" + ], + "teams_enabled": False, + "text_response": None, + "file_upload_response": None, + **test_data.example_rubric, + } + + self.mock_ora_instance = Mock(name="openassessment-block", **self.ora_data) + + def test_individual_ora(self): + # An ORA with teams disabled should have type "individual" + data = OpenResponseMetadataSerializer(self.mock_ora_instance).data + + assert data == { + "name": self.ora_data["display_name"], + "prompts": self.ora_data["prompts"], + "type": "individual", + "textResponseConfig": "none", + "fileUploadResponseConfig": "none", + "rubricConfig": { + "feedbackPrompt": "How did this student do?", + "criteria": [ + { + "orderNum": 0, + "name": "potions", + "label": "Potions", + "prompt": "How did this student perform in the Potions exam", + "feedback": "optional", + "options": test_data.example_rubric_options_serialized, + }, + { + "orderNum": 1, + "name": "charms", + "label": "Charms", + "prompt": "How did this student perform in the Charms exam", + "feedback": "disabled", + "options": test_data.example_rubric_options_serialized, + }, + ], + }, + } + + def test_team_ora(self): + # An ORA with teams enabled should have type "team" + self.mock_ora_instance.teams_enabled = True + data = OpenResponseMetadataSerializer(self.mock_ora_instance).data + + assert data == { + "name": self.ora_data["display_name"], + "prompts": self.ora_data["prompts"], + "type": "team", + "textResponseConfig": "none", + "fileUploadResponseConfig": "none", + "rubricConfig": { + "feedbackPrompt": "How did this student do?", + "criteria": [ + { + "orderNum": 0, + "name": "potions", + "label": "Potions", + "prompt": "How did this student perform in the Potions exam", + "feedback": "optional", + "options": test_data.example_rubric_options_serialized, + }, + { + "orderNum": 1, + "name": "charms", + "label": "Charms", + "prompt": "How did this student perform in the Charms exam", + "feedback": "disabled", + "options": test_data.example_rubric_options_serialized, + }, + ], + }, + } + + @ddt.unpack + @ddt.data(("optional", "optional"), ("required", "required")) + def test_response_config(self, text_response, file_upload_response): + self.mock_ora_instance.text_response = text_response + self.mock_ora_instance.file_upload_response = file_upload_response + + data = OpenResponseMetadataSerializer(self.mock_ora_instance).data + + assert data["textResponseConfig"] == text_response + assert data["fileUploadResponseConfig"] == file_upload_response + + def test_response_config_none(self): + self.mock_ora_instance.text_response = None + self.mock_ora_instance.file_upload_response = None + + data = OpenResponseMetadataSerializer(self.mock_ora_instance).data + + assert data["textResponseConfig"] == "none" + assert data["fileUploadResponseConfig"] == "none" + + +class TestSubmissionMetadataSerializer(TestCase): + """ + Tests for SubmissionMetadataSerializer. Implicitly, this also exercises ScoreSerializer. + SubmissionMetadata comes from the ORA list_staff_workflows XBlock.json_handler and has the shape: + "": { + "submissionUuid": "", + "username": "", + "teamName": "", + "dateSubmitted": "", + "dateGraded": "", + "gradedBy": "", + "gradingStatus": "", + "lockStatus": "", + "score": { + "pointsEarned": , + "pointsPossible": + } + } + Right now, this is just passed through with only one name transform + """ + + submission_data = { + "a": { + "submissionUuid": "a", + "username": "foo", + "teamName": "", + "dateSubmitted": "1969-07-16 13:32:00", + "dateGraded": "None", + "gradedBy": "", + "gradingStatus": "ungraded", + "lockStatus": "unlocked", + "score": {"pointsEarned": 0, "pointsPossible": 10}, + }, + "b": { + "submissionUuid": "b", + "username": "", + "teamName": "bar", + "dateSubmitted": "1969-07-20 20:17:40", + "dateGraded": "None", + "gradedBy": "", + "gradingStatus": "ungraded", + "lockStatus": "in-progress", + "score": {"pointsEarned": 0, "pointsPossible": 10}, + }, + "c": { + "submissionUuid": "c", + "username": "baz", + "teamName": "", + "dateSubmitted": "1969-07-21 21:35:00", + "dateGraded": "1969-07-24 16:44:00", + "gradedBy": "buz", + "gradingStatus": "graded", + "lockStatus": "unlocked", + "score": {"pointsEarned": 9, "pointsPossible": 10}, + }, + } + + def test_submission_serialize(self): + for submission_id, submission_data in self.submission_data.items(): + data = SubmissionMetadataSerializer(submission_data).data + + # For each submission, the only transform is to change "submissionUuid" to "submissionUUID" + # Create that "expected" object here by updating the key name + expected_data = self.submission_data[submission_id].copy() + expected_data["submissionUUID"] = expected_data.pop("submissionUuid") + + assert data == expected_data + + def test_empty_score(self): + """ + An empty score dict should be serialized as None + """ + submission = { + "submissionUuid": "empty-score", + "username": "WOPR", + "dateSubmitted": "1983-06-03 00:00:00", + "dateGraded": None, + "gradedBy": None, + "gradingStatus": "ungraded", + "lockStatus": "unlocked", + "score": {}, + } + + expected_output = { + "submissionUUID": "empty-score", + "username": "WOPR", + "teamName": None, + "dateSubmitted": "1983-06-03 00:00:00", + "dateGraded": None, + "gradedBy": None, + "gradingStatus": "ungraded", + "lockStatus": "unlocked", + "score": None, + } + + data = SubmissionMetadataSerializer(submission).data + + assert data == expected_output + + +class TestInitializeSerializer(TestCase): + """ + Tests for InitializeSerializer + """ + + def set_up_ora(self): + """Create a mock Open Repsponse Assessment for serialization""" + ora_data = { + "display_name": "Week 1: Time Travel Paradoxes", + "prompts": [ + "

In your own words, explain a famous time travel paradox

" + ], + "teams_enabled": False, + } + + # Add rubric data here for succinctness + ora_data.update(test_data.example_rubric) + return Mock(name="openassessment-block", **ora_data) + + def set_up_course_metadata(self): + """Create mock course metadata for serialization""" + course_org = "Oxford" + course_name = "Introduction to Time Travel" + course_number = "TT101" + course_run = "2054" + + return CourseOverviewFactory.create( + org=course_org, + display_name=course_name, + display_number_with_default=course_number, + run=course_run, + ) + + def setUp(self): + super().setUp() + + self.mock_ora_instance = self.set_up_ora() + self.mock_course_metadata = self.set_up_course_metadata() + self.mock_submissions_data = test_data.example_submission_list.copy() + + def test_serializer_output(self): + input_data = { + "courseMetadata": self.mock_course_metadata, + "oraMetadata": self.mock_ora_instance, + "submissions": self.mock_submissions_data, + } + + output_data = InitializeSerializer(input_data).data + + expected_course_data = CourseMetadataSerializer(self.mock_course_metadata).data + expected_ora_data = OpenResponseMetadataSerializer(self.mock_ora_instance).data + expected_submissions_data = {} + + # There's a level of unpacking that happens in the serializer, perform that here + for submission_id, submission_data in self.mock_submissions_data.items(): + serialized_data = SubmissionMetadataSerializer(submission_data).data + expected_submissions_data[submission_id] = serialized_data + + # Check that each of the sub-serializers assembles data correctly + assert output_data["courseMetadata"] == expected_course_data + assert output_data["oraMetadata"] == expected_ora_data + assert output_data["submissions"] == expected_submissions_data + + +class TestRubricConfigSerializer(TestCase): + """Tests for RubricConfigSerializer""" + + def basic_test_case(self): + """Basic test for complex rubric""" + assert RubricConfigSerializer(test_data.example_rubric).data == { + "feedbackPrompt": "How did this student do?", + "criteria": [ + { + "orderNum": 0, + "name": "potions", + "label": "Potions", + "prompt": "How did this student perform in the Potions exam", + "feedback": "optional", + "options": test_data.example_rubric_options_serialized, + }, + { + "order_num": 1, + "name": "charms", + "label": "Charms", + "prompt": "How did this student perform in the Charms exam", + "feedback": "disabled", + "options": test_data.example_rubric_options_serialized, + }, + ], + } + + +@ddt.ddt +class TestScoreFieldAndSerializer(TestCase): + """Tests for ScoreField and ScoreSerializer""" + + def test_field_no_values(self): + """An empty dict passed to the field should return None""" + assert ScoreField().to_representation({}) is None + + @ddt.data("pointsEarned", "pointsPossible") + def test_field_missing(self, missing_field): + """Missing fields should just be ignored""" + value = {"pointsEarned": 30, "pointsPossible": 50} + del value[missing_field] + + assert ScoreField().to_representation(value) == value + + def test_field(self): + """Base serialization behavior for ScoreField""" + data = {"pointsEarned": 20, "pointsPossible": 40} + representation = ScoreField().to_representation(data) + assert representation == data + + def test_serializer_no_values(self): + """Passing the ScoreSerializer an empty dict should result in an empty serializer""" + # pylint: disable=use-implicit-booleaness-not-comparison + assert ScoreSerializer({}).data == {} + + def test_serialier(self): + """Base serialization behavior for ScoreSerializer""" + input_data = {"pointsEarned": 10, "pointsPossible": 200} + data = ScoreSerializer(input_data).data + assert data == input_data + + @ddt.data("pointsEarned", "pointsPossible") + def test_serializer_missing_field(self, missing_field): + """Missing fields should just be ignored""" + value = {"pointsEarned": 30, "pointsPossible": 50} + del value[missing_field] + + assert ScoreSerializer(value).data == value + + +class TestUploadedFileSerializer(TestCase): + """Tests for UploadedFileSerializer""" + + def test_uploaded_file_serializer(self): + """Base serialization behavior""" + input_data = MagicMock(size=89794) + data = UploadedFileSerializer(input_data).data + + expected_value = { + "downloadUrl": str(input_data.download_url), + "description": str(input_data.description), + "name": str(input_data.name), + "size": input_data.size, + } + assert data == expected_value + + +@ddt.ddt +class TestResponseSerializer(TestCase): + """Tests for ResponseSerializer""" + + def test_response_serializer__empty(self): + """Empty fields should be allowed""" + input_data = {"files": [], "text": []} + assert ResponseSerializer(input_data).data == input_data + + @ddt.unpack + @ddt.data((True, True), (True, False), (False, True), (False, False)) + def test_response_serializer(self, has_text, has_files): + """Base serialization behavior""" + input_data = MagicMock() + if has_files: + input_data.files = [Mock(size=111), Mock(size=222), Mock(size=333)] + if has_text: + input_data.text = [Mock(), Mock(), Mock()] + + data = ResponseSerializer(input_data).data + expected_value = { + "files": [ + UploadedFileSerializer(mock_file).data for mock_file in input_data.files + ] + if has_files + else [], + "text": [str(mock_text) for mock_text in input_data.text] + if has_text + else [], + } + assert data == expected_value + + +class TestAssessmentCriteriaSerializer(TestCase): + """Tests for AssessmentCriteriaSerializer""" + + def test_assessment_criteria_serializer(self): + """Base serialization behavior""" + input_data = Mock(points=595) + data = AssessmentCriteriaSerializer(input_data).data + + expected_value = { + "name": str(input_data.name), + "feedback": str(input_data.feedback), + "points": input_data.points, + "selectedOption": str(input_data.option), + } + assert data == expected_value + + def test_assessment_criteria_serializer__feedback_only(self): + """Test for serialization behavior of a feedback-only criterion""" + input_data = { + "name": "SomeCriterioOn", + "feedback": "Pathetic Effort", + "points": None, + "option": None, + } + data = AssessmentCriteriaSerializer(input_data).data + + expected_value = dict(input_data) + expected_value["selectedOption"] = expected_value["option"] + del expected_value["option"] + + assert data == expected_value + + +@ddt.ddt +class TestGradeDataSerializer(TestCase): + """Tests for GradeDataSerializer""" + + def test_grade_data_serializer__no_assessment(self): + """Passing an empty dict should result in an empty dict""" + # pylint: disable=use-implicit-booleaness-not-comparison + assert GradeDataSerializer({}).data == {} + + @ddt.data(True, False) + def test_grade_data_serializer__assessment(self, has_criteria): + """Base serialization behavior, with and without criteria""" + input_data = MagicMock() + if has_criteria: + input_data.criteria = [Mock(points=123), Mock(points=11), Mock(points=22)] + data = GradeDataSerializer(input_data).data + + expected_value = { + "score": ScoreField().to_representation(input_data.score), + "overallFeedback": str(input_data.feedback), + } + if has_criteria: + expected_value["criteria"] = [ + AssessmentCriteriaSerializer(criterion).data + for criterion in input_data.criteria + ] + else: + expected_value["criteria"] = [] + assert data == expected_value + + +@ddt.ddt +class TestSubmissionStatusFetchSerializer(TestCase): + """Tests for SubmissionStatusFetchSerializer""" + + def test_submission_status_fetch_serializer(self): + """Base serialization behavior""" + input_data = MagicMock() + serializer = SubmissionStatusFetchSerializer(input_data) + with patch.object(serializer, "get_gradeStatus") as mock_get_grade_status: + data = serializer.data + + expected_value = { + "gradeData": GradeDataSerializer(input_data.assessment_info).data, + "gradeStatus": mock_get_grade_status.return_value, + "lockStatus": LockStatusField().to_representation( + input_data.lock_info.lock_status + ), + } + mock_get_grade_status.assert_called_once_with(input_data) + assert data == expected_value + + @ddt.data(True, False) + def test_get__gradeStatus(self, has_assessment): + """Unit test for get_gradeStatus""" + assessment = {"somekey": "somevalue"} if has_assessment else {} + input_data = {"assessment_info": assessment} + value = SubmissionStatusFetchSerializer().get_gradeStatus(input_data) + expected = "graded" if has_assessment else "ungraded" + assert value == expected + + +class TestSubmissionFetchSerializer(TestCase): + """Tests for the SubmissionFetchSerializer""" + + def test_submission_fetch_serializer(self): + """Base serialization behavior""" + input_data = MagicMock() + serializer = SubmissionFetchSerializer(input_data) + with patch.object(serializer, "get_gradeStatus") as mock_get_grade_status: + data = serializer.data + + expected_value = { + "gradeData": GradeDataSerializer(input_data.assessment_info).data, + "gradeStatus": mock_get_grade_status.return_value, + "lockStatus": LockStatusField().to_representation( + input_data.lock_info.lock_status + ), + "response": ResponseSerializer(input_data.submission_info).data, + } + mock_get_grade_status.assert_called_once_with(input_data) + assert data == expected_value + + +class TestLockStatusSerializer(SharedModuleStoreTestCase): + """ + Tests for LockStatusSerializer + """ + + lock_in_progress = { + "submission_uuid": "e34ef789-a4b1-48cf-b1bc-b3edacfd4eb2", + "owner_id": "10ab03f1b75b4f9d9ab13a1fd1dccca1", + "created_at": "2021-09-21T21:54:09.901221Z", + "lock_status": "in-progress", + } + + lock_in_progress_expected = {"lockStatus": "in-progress"} + + lock_owned_by_other_user = { + "submission_uuid": "e34ef789-a4b1-48cf-b1bc-b3edacfd4eb2", + "owner_id": "10ab03f1b75b4f9d9ab13a1fd1dccca1", + "created_at": "2021-09-21T21:54:09.901221Z", + "lock_status": "locked", + } + + lock_owned_by_other_user_expected = {"lockStatus": "locked"} + + course_id = "course-v1:Oxford+TT101+2054" + + def test_happy_path(self): + """For simple cases, lock status is passed through directly""" + data = LockStatusSerializer(self.lock_in_progress).data + assert data == self.lock_in_progress_expected + + data = LockStatusSerializer(self.lock_owned_by_other_user).data + assert data == self.lock_owned_by_other_user_expected + + +class TestStaffAssessSerializer(TestCase): + """Tests for StaffAssessSerializer""" + + grade_data = { + "overallFeedback": "was pretty good", + "criteria": [ + { + "name": "firstCriterion", + "feedback": "did alright", + "selectedOption": "good", + }, + {"name": "secondCriterion", "selectedOption": "fair"}, + ], + } + + grade_data_no_feedback = { + "overallFeedback": "", + "criteria": [ + {"name": "firstCriterion", "selectedOption": "good"}, + {"name": "secondCriterion", "selectedOption": "fair"}, + ], + } + + submission_uuid = "foo" + + def test_staff_assess_serializer(self): + """Base serialization behavior""" + context = {"submission_uuid": self.submission_uuid} + serializer = StaffAssessSerializer(self.grade_data, context=context) + + expected_value = { + "options_selected": { + "firstCriterion": "good", + "secondCriterion": "fair", + }, + "criterion_feedback": { + "firstCriterion": "did alright", + }, + "overall_feedback": "was pretty good", + "submission_uuid": self.submission_uuid, + "assess_type": "full-grade", + } + + assert serializer.data == expected_value + + def test_staff_assess_no_feedback(self): + """Verify that empty feedback returns a reasonable shape""" + context = {"submission_uuid": self.submission_uuid} + serializer = StaffAssessSerializer(self.grade_data_no_feedback, context=context) + + expected_value = { + "options_selected": { + "firstCriterion": "good", + "secondCriterion": "fair", + }, + "criterion_feedback": {}, + "overall_feedback": "", + "submission_uuid": self.submission_uuid, + "assess_type": "full-grade", + } + + assert serializer.data == expected_value diff --git a/lms/djangoapps/ora_staff_grader/tests/test_views.py b/lms/djangoapps/ora_staff_grader/tests/test_views.py new file mode 100644 index 0000000000..1f05580800 --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/tests/test_views.py @@ -0,0 +1,697 @@ +""" +Tests for ESG views +""" +import json +from unittest.mock import Mock, patch +from uuid import uuid4 + +import ddt +from django.http import QueryDict +from django.urls import reverse +from rest_framework.test import APITestCase +from xmodule.modulestore.tests.django_utils import ( + TEST_DATA_SPLIT_MODULESTORE, + SharedModuleStoreTestCase, +) +from xmodule.modulestore.tests.factories import CourseFactory +from xmodule.modulestore.tests.factories import ItemFactory + +from common.djangoapps.student.tests.factories import StaffFactory +from lms.djangoapps.ora_staff_grader.constants import ( + ERR_BAD_ORA_LOCATION, + ERR_GRADE_CONTESTED, + ERR_INTERNAL, + ERR_LOCK_CONTESTED, + ERR_MISSING_PARAM, + ERR_UNKNOWN, + PARAM_ORA_LOCATION, + PARAM_SUBMISSION_ID, +) +from lms.djangoapps.ora_staff_grader.errors import ( + LockContestedError, + XBlockInternalError, +) +import lms.djangoapps.ora_staff_grader.tests.test_data as test_data +from openedx.core.djangoapps.content.course_overviews.tests.factories import ( + CourseOverviewFactory, +) + + +class BaseViewTest(SharedModuleStoreTestCase, APITestCase): + """Base class for shared test utils and setup""" + + MODULESTORE = TEST_DATA_SPLIT_MODULESTORE + + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.api_url = reverse(cls.view_name) + + cls.course = CourseFactory.create() + cls.course_key = cls.course.location.course_key + + cls.ora_block = ItemFactory.create( + category="openassessment", + parent_location=cls.course.location, + display_name="test", + ) + cls.ora_usage_key = str(cls.ora_block.location) + + cls.password = "password" + cls.staff = StaffFactory(course_key=cls.course_key, password=cls.password) + + def log_in(self): + """Log in as staff""" + self.client.login(username=self.staff.username, password=self.password) + + def url_with_params(self, params): + """For DRF client.posts, you can't add query params easily. This helper adds it to the request URL""" + query_dictionary = QueryDict("", mutable=True) + query_dictionary.update(params) + + return "{base_url}?{querystring}".format( + base_url=reverse(self.view_name), querystring=query_dictionary.urlencode() + ) + + +@ddt.ddt +class TestInitializeView(BaseViewTest): + """ + Tests for the /initialize view, creating setup data for ESG + """ + + view_name = "ora-staff-grader:initialize" + + def setUp(self): + super().setUp() + self.log_in() + + @ddt.data({}, {PARAM_ORA_LOCATION: ""}) + def test_missing_param(self, query_params): + """Missing ORA location param should return 400 and error message""" + response = self.client.get(self.api_url, query_params) + + assert response.status_code == 400 + assert json.loads(response.content) == {"error": ERR_MISSING_PARAM} + + def test_bad_ora_location(self): + """Bad ORA location should return a 400 and error message""" + response = self.client.get( + self.api_url, {PARAM_ORA_LOCATION: "not_a_real_location"} + ) + + assert response.status_code == 400 + assert json.loads(response.content) == {"error": ERR_BAD_ORA_LOCATION} + + @patch("lms.djangoapps.ora_staff_grader.views.get_submissions") + @patch("lms.djangoapps.ora_staff_grader.views.get_course_overview_or_none") + def test_init(self, mock_get_course_overview, mock_get_submissions): + """Any failure to fetch info returns an unknown error response""" + mock_course_overview = CourseOverviewFactory.create() + mock_get_course_overview.return_value = mock_course_overview + mock_get_submissions.return_value = test_data.example_submission_list + + response = self.client.get( + self.api_url, {PARAM_ORA_LOCATION: self.ora_usage_key} + ) + + expected_keys = set(["courseMetadata", "oraMetadata", "submissions"]) + assert response.status_code == 200 + assert response.data.keys() == expected_keys + + @patch("lms.djangoapps.ora_staff_grader.views.get_submissions") + @patch("lms.djangoapps.ora_staff_grader.views.get_course_overview_or_none") + def test_init_xblock_exception( + self, mock_get_course_overview, mock_get_submissions + ): + """If one of the XBlock handlers fails, the exception should be caught""" + mock_course_overview = CourseOverviewFactory.create() + mock_get_course_overview.return_value = mock_course_overview + # Mock an error getting submissions + mock_get_submissions.side_effect = XBlockInternalError( + context={"handler": "list_staff_workflows"} + ) + + response = self.client.get( + self.api_url, {PARAM_ORA_LOCATION: self.ora_usage_key} + ) + + assert response.status_code == 500 + assert json.loads(response.content) == { + "error": ERR_INTERNAL, + "handler": "list_staff_workflows", + } + + @patch("lms.djangoapps.ora_staff_grader.views.get_submissions") + @patch("lms.djangoapps.ora_staff_grader.views.get_course_overview_or_none") + def test_init_generic_exception( + self, mock_get_course_overview, mock_get_submissions + ): + """If something else strange fails (e.g. bad data shape), an "unknown" error should be surfaced""" + mock_course_overview = CourseOverviewFactory.create() + mock_get_course_overview.return_value = mock_course_overview + # Mock a bad returned data shape which would break serialization + mock_get_submissions.return_value = {"bad": "wolf"} + + response = self.client.get( + self.api_url, {PARAM_ORA_LOCATION: self.ora_usage_key} + ) + + assert response.status_code == 500 + assert json.loads(response.content) == {"error": ERR_UNKNOWN} + + +@ddt.ddt +class TestFetchSubmissionView(BaseViewTest): + """ + Tests for the submission fetch view + """ + + view_name = "ora-staff-grader:fetch-submission" + + def setUp(self): + super().setUp() + self.log_in() + + @ddt.data({}, {PARAM_ORA_LOCATION: "", PARAM_SUBMISSION_ID: ""}) + def test_missing_params(self, query_params): + """Missing or blank params should return 400 and error message""" + response = self.client.get(self.api_url, query_params) + + assert response.status_code == 400 + assert json.loads(response.content) == {"error": ERR_MISSING_PARAM} + + @ddt.data(True, False) + @patch("lms.djangoapps.ora_staff_grader.views.get_submission_info") + @patch("lms.djangoapps.ora_staff_grader.views.get_assessment_info") + @patch("lms.djangoapps.ora_staff_grader.views.check_submission_lock") + def test_fetch_submission( + self, + has_assessment, + mock_check_submission_lock, + mock_get_assessment_info, + mock_get_submission_info, + ): + """Successfull submission fetch status returns submission, lock, and grade data""" + mock_get_submission_info.return_value = test_data.example_submission + mock_get_assessment_info.return_value = ( + {} if not has_assessment else test_data.example_assessment + ) + mock_check_submission_lock.return_value = {"lock_status": "unlocked"} + + ora_location, submission_uuid = Mock(), Mock() + response = self.client.get( + self.api_url, + {PARAM_ORA_LOCATION: ora_location, PARAM_SUBMISSION_ID: submission_uuid}, + ) + + assert response.status_code == 200 + assert response.data.keys() == set( + ["gradeData", "response", "gradeStatus", "lockStatus"] + ) + assert response.data["response"].keys() == set(["files", "text"]) + expected_assessment_keys = ( + set(["score", "overallFeedback", "criteria"]) if has_assessment else set() + ) + assert response.data["gradeData"].keys() == expected_assessment_keys + + @patch("lms.djangoapps.ora_staff_grader.views.get_submission_info") + @patch("lms.djangoapps.ora_staff_grader.views.get_assessment_info") + @patch("lms.djangoapps.ora_staff_grader.views.check_submission_lock") + def test_fetch_submission_generic_exception( + self, + mock_check_submission_lock, + mock_get_assessment_info, + mock_get_submission_info, + ): + """Other generic exceptions should return the "unknown" error response""" + mock_get_submission_info.return_value = test_data.example_submission + # Mock an error in getting the assessment info + mock_get_assessment_info.side_effect = XBlockInternalError( + context={"handler": "get_assessment_info"} + ) + mock_check_submission_lock.return_value = {"lock_status": "unlocked"} + + ora_location, submission_uuid = Mock(), Mock() + response = self.client.get( + self.api_url, + {PARAM_ORA_LOCATION: ora_location, PARAM_SUBMISSION_ID: submission_uuid}, + ) + + assert response.status_code == 500 + assert json.loads(response.content) == { + "error": ERR_INTERNAL, + "handler": "get_assessment_info", + } + + @patch("lms.djangoapps.ora_staff_grader.views.get_submission_info") + @patch("lms.djangoapps.ora_staff_grader.views.get_assessment_info") + @patch("lms.djangoapps.ora_staff_grader.views.check_submission_lock") + def test_fetch_submission_xblock_exception( + self, + mock_check_submission_lock, + mock_get_assessment_info, + mock_get_submission_info, + ): + """An exception in any XBlock handler returns an error response""" + mock_get_submission_info.return_value = test_data.example_submission + mock_get_assessment_info.return_value = test_data.example_assessment + # Mock a bad data shape to break serialization + mock_check_submission_lock.return_value = {"mad": "hatter"} + + ora_location, submission_uuid = Mock(), Mock() + response = self.client.get( + self.api_url, + {PARAM_ORA_LOCATION: ora_location, PARAM_SUBMISSION_ID: submission_uuid}, + ) + + assert response.status_code == 500 + assert json.loads(response.content) == {"error": ERR_UNKNOWN} + + +@ddt.ddt +class TestFetchSubmissionStatusView(BaseViewTest): + """ + Tests for the submission fetch view + """ + + view_name = "ora-staff-grader:fetch-submission-status" + + def setUp(self): + super().setUp() + self.log_in() + + @ddt.data( + {}, + {PARAM_ORA_LOCATION: "", PARAM_SUBMISSION_ID: Mock()}, + {PARAM_ORA_LOCATION: Mock(), PARAM_SUBMISSION_ID: ""}, + ) + def test_missing_param(self, query_params): + """Missing ORA location or submission ID param should return 400 and error message""" + response = self.client.get(self.api_url, query_params) + + assert response.status_code == 400 + assert json.loads(response.content) == {"error": ERR_MISSING_PARAM} + + @ddt.data(True, False) + @patch("lms.djangoapps.ora_staff_grader.views.get_assessment_info") + @patch("lms.djangoapps.ora_staff_grader.views.check_submission_lock") + def test_fetch_submission_status( + self, + has_assessment, + mock_check_submission_lock, + mock_get_assessment_info, + ): + """Successful fetch submission returns submission and related lock/assessment info""" + mock_get_assessment_info.return_value = ( + {} if not has_assessment else test_data.example_assessment + ) + mock_check_submission_lock.return_value = {"lock_status": "in-progress"} + + ora_location, submission_uuid = Mock(), Mock() + response = self.client.get( + self.api_url, + {PARAM_ORA_LOCATION: ora_location, PARAM_SUBMISSION_ID: submission_uuid}, + ) + + assert response.status_code == 200 + actual = response.json() + expected = { + "gradeStatus": "graded" if has_assessment else "ungraded", + "lockStatus": mock_check_submission_lock.return_value["lock_status"], + "gradeData": {} + if not has_assessment + else { + "score": test_data.example_assessment["score"], + "overallFeedback": test_data.example_assessment["feedback"], + "criteria": [ + { + "name": "Criterion 1", + "selectedOption": "Three", + "points": 3, + "feedback": "Feedback 1", + }, + ], + }, + } + assert actual == expected + + @patch("lms.djangoapps.ora_staff_grader.views.get_assessment_info") + @patch("lms.djangoapps.ora_staff_grader.views.check_submission_lock") + def test_fetch_submission_status_xblock_exception( + self, mock_check_submission_lock, mock_get_assessment_info + ): + """Exceptions within an XBlock return an internal error response""" + # Mock a bad data shape to throw a serializer exception + mock_get_assessment_info.return_value = {} + mock_check_submission_lock.side_effect = XBlockInternalError( + context={"handler": "claim_submission_lock"} + ) + + ora_location, submission_uuid = Mock(), Mock() + response = self.client.get( + self.api_url, + {PARAM_ORA_LOCATION: ora_location, PARAM_SUBMISSION_ID: submission_uuid}, + ) + + assert response.status_code == 500 + assert json.loads(response.content) == { + "error": ERR_INTERNAL, + "handler": "claim_submission_lock", + } + + @patch("lms.djangoapps.ora_staff_grader.views.get_assessment_info") + @patch("lms.djangoapps.ora_staff_grader.views.check_submission_lock") + def test_fetch_submission_status_generic_exception( + self, mock_check_submission_lock, mock_get_assessment_info + ): + """Exceptions outside of an XBlock return a generic error response""" + mock_get_assessment_info.return_value = {} + # Mock a bad data shape to throw a serializer exception + mock_check_submission_lock.return_value = {"jekyll", "hyde"} + + ora_location, submission_uuid = Mock(), Mock() + response = self.client.get( + self.api_url, + {PARAM_ORA_LOCATION: ora_location, PARAM_SUBMISSION_ID: submission_uuid}, + ) + + assert response.status_code == 500 + assert json.loads(response.content) == {"error": ERR_UNKNOWN} + + +class TestSubmissionLockView(BaseViewTest): + """ + Tests for the /lock view, locking or unlocking a submission for grading + """ + + view_name = "ora-staff-grader:lock" + + test_submission_uuid = str(uuid4()) + test_anon_user_id = "anon-user-id" + test_other_anon_user_id = "anon-user-id-2" + test_timestamp = "2020-08-29T02:14:00-04:00" + + def setUp(self): + super().setUp() + + # Lock requests must include ORA location and submission UUID + self.test_lock_params = { + PARAM_ORA_LOCATION: self.ora_usage_key, + PARAM_SUBMISSION_ID: self.test_submission_uuid, + } + + self.log_in() + + def claim_lock(self, params): + """Wrapper for easier calling of 'claim_submission_lock'""" + return self.client.post(self.url_with_params(params)) + + def delete_lock(self, params): + """Wrapper for easier calling of 'delete_submission_lock'""" + return self.client.delete(self.url_with_params(params)) + + # Tests for claiming a lock (POST) + + def test_claim_lock_invalid_ora(self): + """An invalid ORA returns a 400""" + self.test_lock_params[PARAM_ORA_LOCATION] = "not_a_real_location" + + response = self.claim_lock(self.test_lock_params) + + assert response.status_code == 400 + assert json.loads(response.content) == {"error": ERR_BAD_ORA_LOCATION} + + @patch("lms.djangoapps.ora_staff_grader.views.claim_submission_lock") + def test_claim_lock(self, mock_claim_lock): + """POST tries to claim a submission lock. Success returns lock status 'in-progress'.""" + mock_return_data = { + "submission_uuid": self.test_submission_uuid, + "owner_id": self.test_anon_user_id, + "created_at": self.test_timestamp, + "lock_status": "in-progress", + } + mock_claim_lock.return_value = mock_return_data + + response = self.claim_lock(self.test_lock_params) + + expected_value = {"lockStatus": "in-progress"} + assert response.status_code == 200 + assert json.loads(response.content) == expected_value + + @patch("lms.djangoapps.ora_staff_grader.views.check_submission_lock") + @patch("lms.djangoapps.ora_staff_grader.views.claim_submission_lock") + def test_claim_lock_contested(self, mock_claim_lock, mock_check_lock): + """Attempting to claim a lock owned by another user returns a 403 - forbidden and passes error code.""" + mock_claim_lock.side_effect = LockContestedError() + mock_check_lock.return_value = { + "submission_uuid": self.test_submission_uuid, + "owner_id": self.test_other_anon_user_id, + "created_at": self.test_timestamp, + "lock_status": "locked", + } + + response = self.claim_lock(self.test_lock_params) + + expected_value = {"error": ERR_LOCK_CONTESTED, "lockStatus": "locked"} + assert response.status_code == 409 + assert json.loads(response.content) == expected_value + + @patch("lms.djangoapps.ora_staff_grader.views.claim_submission_lock") + def test_claim_lock_xblock_exception( + self, + mock_claim_lock, + ): + """In the unlikely event of an error, the exits are to your left and behind you""" + mock_claim_lock.side_effect = XBlockInternalError( + context={"handler": "claim_submission_lock"} + ) + + response = self.claim_lock(self.test_lock_params) + + assert response.status_code == 500 + assert json.loads(response.content) == { + "error": ERR_INTERNAL, + "handler": "claim_submission_lock", + } + + @patch("lms.djangoapps.ora_staff_grader.views.claim_submission_lock") + def test_claim_lock_generic_exception( + self, + mock_claim_lock, + ): + """In the even more unlikely event of an unhandled error, shrug exuberantly""" + # Mock a bad data shape to break serialiation and raise a generic exception + mock_claim_lock.return_value = {"android": "Rachel"} + + response = self.claim_lock(self.test_lock_params) + + assert response.status_code == 500 + assert json.loads(response.content) == {"error": ERR_UNKNOWN} + + # Tests for deleting a lock (DELETE) + + @patch("lms.djangoapps.ora_staff_grader.views.delete_submission_lock") + def test_delete_lock(self, mock_delete_lock): + """DELETE indicates to clear submission lock. Success returns lock status 'unlocked'.""" + mock_delete_lock.return_value = {"lock_status": "unlocked"} + + response = self.delete_lock(self.test_lock_params) + + expected_value = {"lockStatus": "unlocked"} + assert response.status_code == 200 + assert json.loads(response.content) == expected_value + + @patch("lms.djangoapps.ora_staff_grader.views.check_submission_lock") + @patch("lms.djangoapps.ora_staff_grader.views.delete_submission_lock") + def test_delete_lock_contested(self, mock_delete_lock, mock_check_lock): + """Attempting to delete a lock owned by another user returns a 403 - forbidden and passes error code.""" + mock_delete_lock.side_effect = LockContestedError() + mock_check_lock.return_value = { + "submission_uuid": self.test_submission_uuid, + "owner_id": self.test_other_anon_user_id, + "created_at": self.test_timestamp, + "lock_status": "locked", + } + + response = self.delete_lock(self.test_lock_params) + + expected_value = {"error": ERR_LOCK_CONTESTED, "lockStatus": "locked"} + assert response.status_code == 409 + assert json.loads(response.content) == expected_value + + @patch("lms.djangoapps.ora_staff_grader.views.delete_submission_lock") + def test_delete_lock_xblock_exception(self, mock_delete_lock): + """In the unlikely event of an error, the exits are to your left and behind you""" + mock_delete_lock.side_effect = XBlockInternalError( + context={"handler": "delete_submission_lock"} + ) + + response = self.delete_lock(self.test_lock_params) + + assert response.status_code == 500 + assert json.loads(response.content) == { + "error": ERR_INTERNAL, + "handler": "delete_submission_lock", + } + + @patch("lms.djangoapps.ora_staff_grader.views.delete_submission_lock") + def test_delete_lock_generic_exception(self, mock_delete_lock): + """In the even more unlikely event of an unhandled error, shrug exuberantly""" + # Mock a bad data shape to break serialiation and raise a generic exception + mock_delete_lock.return_value = {"android": "Roy Batty"} + + response = self.delete_lock(self.test_lock_params) + + assert response.status_code == 500 + assert json.loads(response.content) == {"error": ERR_UNKNOWN} + + +class TestUpdateGradeView(BaseViewTest): + """ + Tests for updating a grade for a submission + """ + + view_name = "ora-staff-grader:update-grade" + + submission_uuid = str(uuid4()) + ora_location = Mock() + test_anon_user_id = "anon-user-id" + test_timestamp = "2020-08-29T02:14:00-04:00" + + def setUp(self): + super().setUp() + self.log_in() + + @patch("lms.djangoapps.ora_staff_grader.views.check_submission_lock") + @patch("lms.djangoapps.ora_staff_grader.views.submit_grade") + def test_submit_grade_xblock_exception(self, mock_submit_grade, mock_check_lock): + """A handled ORA failure to submit a grade returns a server error""" + mock_check_lock.return_value = {"lock_status": "in-progress"} + mock_submit_grade.side_effect = XBlockInternalError( + context={"handler": "staff_assess", "msg": "Danger, Will Robinson!"} + ) + url = self.url_with_params( + { + PARAM_ORA_LOCATION: self.ora_location, + PARAM_SUBMISSION_ID: self.submission_uuid, + } + ) + data = test_data.example_grade_data + + response = self.client.post(url, data, format="json") + assert response.status_code == 500 + assert json.loads(response.content) == { + "error": ERR_INTERNAL, + "handler": "staff_assess", + "msg": "Danger, Will Robinson!", + } + + @patch("lms.djangoapps.ora_staff_grader.views.check_submission_lock") + @patch("lms.djangoapps.ora_staff_grader.views.submit_grade") + def test_submit_grade_generic_exception(self, mock_submit_grade, mock_check_lock): + """A fall-through failure returns an unknown error""" + mock_check_lock.return_value = {"lock_status": "in-progress"} + mock_submit_grade.return_value = {"error": "time paradox encountered"} + url = self.url_with_params( + { + PARAM_ORA_LOCATION: self.ora_location, + PARAM_SUBMISSION_ID: self.submission_uuid, + } + ) + data = test_data.example_grade_data + + response = self.client.post(url, data, format="json") + assert response.status_code == 500 + assert json.loads(response.content) == {"error": ERR_UNKNOWN} + + @patch("lms.djangoapps.ora_staff_grader.views.check_submission_lock") + @patch("lms.djangoapps.ora_staff_grader.views.get_assessment_info") + @patch("lms.djangoapps.ora_staff_grader.views.delete_submission_lock") + @patch("lms.djangoapps.ora_staff_grader.views.submit_grade") + def test_submit_grade_success( + self, mock_submit_grade, mock_delete_lock, mock_get_info, mock_check_lock + ): + """A grade update success should clear the submission lock and return submission meta""" + mock_check_lock.side_effect = [ + {"lock_status": "in-progress"}, + {"lock_status": "unlocked"}, + ] + mock_submit_grade.return_value = {"success": True, "msg": ""} + mock_get_info.return_value = test_data.example_assessment + + url = self.url_with_params( + { + PARAM_ORA_LOCATION: self.ora_location, + PARAM_SUBMISSION_ID: self.submission_uuid, + } + ) + data = test_data.example_grade_data + + response = self.client.post(url, data, format="json") + + expected_response = { + "gradeStatus": "graded", + "lockStatus": "unlocked", + "gradeData": { + "score": test_data.example_assessment["score"], + "overallFeedback": test_data.example_assessment["feedback"], + "criteria": [ + { + "name": "Criterion 1", + "selectedOption": "Three", + "points": 3, + "feedback": "Feedback 1", + }, + ], + }, + } + + assert response.status_code == 200 + assert json.loads(response.content) == expected_response + + # Verify that clear lock was called + mock_delete_lock.assert_called_once() + + @patch("lms.djangoapps.ora_staff_grader.views.check_submission_lock") + @patch("lms.djangoapps.ora_staff_grader.views.get_assessment_info") + @patch("lms.djangoapps.ora_staff_grader.views.submit_grade") + def test_submit_grade_contested( + self, mock_submit_grade, mock_get_info, mock_check_lock + ): + """Submitting a grade should be blocked if someone else has obtained the lock""" + mock_check_lock.side_effect = [{"lock_status": "unlocked"}] + mock_get_info.return_value = test_data.example_assessment + + url = self.url_with_params( + { + PARAM_ORA_LOCATION: self.ora_location, + PARAM_SUBMISSION_ID: self.submission_uuid, + } + ) + data = test_data.example_grade_data + + response = self.client.post(url, data, format="json") + + assert response.status_code == 409 + assert json.loads(response.content) == { + "error": ERR_GRADE_CONTESTED, + "gradeStatus": "graded", + "lockStatus": "unlocked", + "gradeData": { + "score": test_data.example_assessment["score"], + "overallFeedback": test_data.example_assessment["feedback"], + "criteria": [ + { + "name": "Criterion 1", + "selectedOption": "Three", + "points": 3, + "feedback": "Feedback 1", + }, + ], + }, + } + + # Verify that submit grade was not called + mock_submit_grade.assert_not_called() diff --git a/lms/djangoapps/ora_staff_grader/urls.py b/lms/djangoapps/ora_staff_grader/urls.py new file mode 100644 index 0000000000..ed100114e0 --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/urls.py @@ -0,0 +1,31 @@ +""" +URLs for Enhanced Staff Grader (ESG) backend-for-frontend (BFF) +""" +from django.conf.urls import include +from django.urls import path + + +from lms.djangoapps.ora_staff_grader.views import ( + InitializeView, + SubmissionFetchView, + SubmissionLockView, + SubmissionStatusFetchView, + UpdateGradeView, +) + + +urlpatterns = [] +app_name = "ora-staff-grader" + +urlpatterns += [ + path("mock/", include("lms.djangoapps.ora_staff_grader.mock.urls")), + path("initialize", InitializeView.as_view(), name="initialize"), + path( + "submission/status", + SubmissionStatusFetchView.as_view(), + name="fetch-submission-status", + ), + path("submission/lock", SubmissionLockView.as_view(), name="lock"), + path("submission/grade", UpdateGradeView.as_view(), name="update-grade"), + path("submission", SubmissionFetchView.as_view(), name="fetch-submission"), +] diff --git a/lms/djangoapps/ora_staff_grader/utils.py b/lms/djangoapps/ora_staff_grader/utils.py new file mode 100644 index 0000000000..c307f77cf3 --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/utils.py @@ -0,0 +1,76 @@ +""" +Various helpful utilities for ESG +""" +from functools import wraps +import json + +from opaque_keys.edx.keys import UsageKey +from rest_framework.request import clone_request + +from lms.djangoapps.courseware.module_render import handle_xblock_callback_noauth +from lms.djangoapps.ora_staff_grader.errors import MissingParamResponse + + +def require_params(param_names): + """ + Adds the required query params to the view function. Returns 404 if param(s) missing. + + Params: + - param_name (string): the query param to unpack + + Raises: + - MissingParamResponse (HTTP 400) + """ + + def decorator(function): + @wraps(function) + def wrapped_function( + self, request, *args, **kwargs + ): # pylint: disable=unused-argument + passed_parameters = [] + + for param_name in param_names: + param = request.query_params.get(param_name) + + if not param: + return MissingParamResponse() + + passed_parameters.append(param) + return function(self, request, *passed_parameters, *args, **kwargs) + + return wrapped_function + + return decorator + + +def call_xblock_json_handler(request, usage_id, handler_name, data): + """ + WARN: Tested only for use in ESG. Consult before use outside of ESG. + + Create an internally-routed XBlock.json_handler request. The internal auth code/param unpacking requires a POST + request with payload in the body. Ideally, we would be able to call functions on XBlocks without this sort of + hacky request proxying but this is what we have to work with right now. + + params: + request (HttpRequest): Originating web request, we're going to borrow auth headers/cookies from this + usage_id (str): Usage ID of the XBlock for running the handler + handler_name (str): the name of the XBlock handler method + data (dict): Data to be encoded and sent as the body of the POST request + returns: + response (HttpResponse): get response data with json.loads(response.content) + """ + # XBlock.json_handler operates through a POST request + proxy_request = clone_request(request, "POST") + proxy_request.META["REQUEST_METHOD"] = "POST" + + # The body is an encoded JSON blob + proxy_request.body = json.dumps(data).encode() + + # Course ID can be retrieved from the usage_id + usage_key = UsageKey.from_string(usage_id) + course_id = str(usage_key.course_key) + + # Send the request and return the HTTP response from the XBlock + return handle_xblock_callback_noauth( + proxy_request, course_id, usage_id, handler_name + ) diff --git a/lms/djangoapps/ora_staff_grader/views.py b/lms/djangoapps/ora_staff_grader/views.py new file mode 100644 index 0000000000..655dd9b995 --- /dev/null +++ b/lms/djangoapps/ora_staff_grader/views.py @@ -0,0 +1,398 @@ +""" +Views for Enhanced Staff Grader +""" +# NOTE: we intentionally do broad exception checking to return a clean error shape +# pylint: disable=broad-except + +# NOTE: we intentionally add extra args using @require_params +# pylint: disable=arguments-differ + +from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication +from edx_rest_framework_extensions.auth.session.authentication import ( + SessionAuthenticationAllowInactiveUser, +) +from opaque_keys import InvalidKeyError +from opaque_keys.edx.keys import UsageKey +from rest_framework.generics import RetrieveAPIView +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from xmodule.modulestore.django import modulestore +from xmodule.modulestore.exceptions import ItemNotFoundError + +from lms.djangoapps.ora_staff_grader.constants import ( + PARAM_ORA_LOCATION, + PARAM_SUBMISSION_ID, +) +from lms.djangoapps.ora_staff_grader.errors import ( + BadOraLocationResponse, + GradeContestedResponse, + InternalErrorResponse, + LockContestedError, + LockContestedResponse, + UnknownErrorResponse, + XBlockInternalError, +) +from lms.djangoapps.ora_staff_grader.ora_api import ( + check_submission_lock, + claim_submission_lock, + delete_submission_lock, + get_assessment_info, + get_submission_info, + get_submissions, + submit_grade, +) +from lms.djangoapps.ora_staff_grader.serializers import ( + InitializeSerializer, + LockStatusSerializer, + StaffAssessSerializer, + SubmissionFetchSerializer, + SubmissionStatusFetchSerializer, +) +from lms.djangoapps.ora_staff_grader.utils import require_params +from openedx.core.djangoapps.content.course_overviews.api import ( + get_course_overview_or_none, +) +from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser + + +class StaffGraderBaseView(RetrieveAPIView): + """ + Base view for common auth/permission setup used across ESG views. + """ + + authentication_classes = ( + JwtAuthentication, + BearerAuthenticationAllowInactiveUser, + SessionAuthenticationAllowInactiveUser, + ) + + permission_classes = (IsAuthenticated,) + + +class InitializeView(StaffGraderBaseView): + """ + GET course metadata + + Response: { + courseMetadata + oraMetadata + submissions + } + + Errors: + - MissingParamResponse (HTTP 400) for missing params + - BadOraLocationResponse (HTTP 400) for bad ORA location + - XBlockInternalError (HTTP 500) for an issue with ORA + - UnknownError (HTTP 500) for other errors + """ + + @require_params([PARAM_ORA_LOCATION]) + def get(self, request, ora_location, *args, **kwargs): + try: + init_data = {} + + # Get ORA block and config (incl. rubric) + ora_usage_key = UsageKey.from_string(ora_location) + init_data["oraMetadata"] = modulestore().get_item(ora_usage_key) + + # Get course metadata + course_id = str(ora_usage_key.course_key) + init_data["courseMetadata"] = get_course_overview_or_none(course_id) + + # Get list of submissions for this ORA + init_data["submissions"] = get_submissions(request, ora_location) + + return Response(InitializeSerializer(init_data).data) + + # Catch bad ORA location + except (InvalidKeyError, ItemNotFoundError): + return BadOraLocationResponse() + + # Issues with the XBlock handlers + except XBlockInternalError as ex: + return InternalErrorResponse(context=ex.context) + + # Blanket exception handling + except Exception: + return UnknownErrorResponse() + + +class SubmissionFetchView(StaffGraderBaseView): + """ + GET submission contents and assessment info, if any + + Response: { + gradeData: { + score: (dict or None) { + pointsEarned: (int) earned points + pointsPossible: (int) possible points + } + overallFeedback: (string) overall feedback + criteria: (list of dict) [{ + name: (str) name of criterion + feedback: (str) feedback for criterion + points: (int) points of selected option or None if feedback-only criterion + selectedOption: (str) name of selected option or None if feedback-only criterion + }] + } + response: { + text: (list of string), [the html content of text responses] + files: (list of dict) [{ + downloadUrl: (string) file download url + description: (string) file description + name: (string) filename + }] + } + } + + Errors: + - MissingParamResponse (HTTP 400) for missing params + - XBlockInternalError (HTTP 500) for an issue with ORA + - UnknownError (HTTP 500) for other errors + """ + + @require_params([PARAM_ORA_LOCATION, PARAM_SUBMISSION_ID]) + def get(self, request, ora_location, submission_uuid, *args, **kwargs): + try: + submission_info = get_submission_info( + request, ora_location, submission_uuid + ) + assessment_info = get_assessment_info( + request, ora_location, submission_uuid + ) + lock_info = check_submission_lock(request, ora_location, submission_uuid) + + serializer = SubmissionFetchSerializer( + { + "submission_info": submission_info, + "assessment_info": assessment_info, + "lock_info": lock_info, + } + ) + + return Response(serializer.data) + + # Issues with the XBlock handlers + except XBlockInternalError as ex: + return InternalErrorResponse(context=ex.context) + + # Blanket exception handling + except Exception: + return UnknownErrorResponse() + + +class SubmissionStatusFetchView(StaffGraderBaseView): + """ + GET submission grade status, lock status, and grade data + + Response: { + gradeStatus: (str) one of [graded, ungraded] + lockStatus: (str) one of [locked, unlocked, in-progress] + gradeData: { + score: (dict or None) { + pointsEarned: (int) earned points + pointsPossible: (int) possible points + } + overallFeedback: (string) overall feedback + criteria: (list of dict) [{ + name: (str) name of criterion + feedback: (str) feedback for criterion + points: (int) points of selected option or None if feedback-only criterion + selectedOption: (str) name of selected option or None if feedback-only criterion + }] + } + } + + Errors: + - MissingParamResponse (HTTP 400) for missing params + - XBlockInternalError (HTTP 500) for an issue with ORA + - UnknownError (HTTP 500) for other errors + """ + + @require_params([PARAM_ORA_LOCATION, PARAM_SUBMISSION_ID]) + def get(self, request, ora_location, submission_uuid, *args, **kwargs): + try: + assessment_info = get_assessment_info( + request, ora_location, submission_uuid + ) + lock_info = check_submission_lock(request, ora_location, submission_uuid) + + serializer = SubmissionStatusFetchSerializer( + { + "assessment_info": assessment_info, + "lock_info": lock_info, + } + ) + + return Response(serializer.data) + + # Issues with the XBlock handlers + except XBlockInternalError as ex: + return InternalErrorResponse(context=ex.context) + + # Blanket exception handling + except Exception: + return UnknownErrorResponse() + + +class UpdateGradeView(StaffGraderBaseView): + """ + POST submit a grade for a submission + + Body: { + overallFeedback: (string) overall feedback + criteria: [ + { + name: (string) name of criterion + feedback: (string, optional) feedback for criterion + selectedOption: (string) name of selected option or None if feedback-only criterion + }, + ... (one per criteria) + ] + } + + Response: { + gradeStatus: (string) - One of ['graded', 'ungraded'] + lockStatus: (string) - One of ['unlocked', 'locked', 'in-progress'] + gradeData: { + score: (dict or None) { + pointsEarned: (int) earned points + pointsPossible: (int) possible points + } + overallFeedback: (string) overall feedback + criteria: (list of dict) [{ + name: (str) name of criterion + feedback: (str) feedback for criterion + selectedOption: (str) name of selected option or None if feedback-only criterion + }] + } + } + + Errors: + - MissingParamResponse (HTTP 400) for missing params + - GradeContestedResponse (HTTP 409) for trying to submit a grade for a submission you don't have an active lock for + - XBlockInternalError (HTTP 500) for an issue with ORA + - UnknownError (HTTP 500) for other errors + """ + + @require_params([PARAM_ORA_LOCATION, PARAM_SUBMISSION_ID]) + def post(self, request, ora_location, submission_uuid, *args, **kwargs): + """Update a grade""" + try: + # Reassert that we have ownership of the submission lock + lock_info = check_submission_lock(request, ora_location, submission_uuid) + if not lock_info.get("lock_status") == "in-progress": + assessment_info = get_assessment_info( + request, ora_location, submission_uuid + ) + submission_status = SubmissionStatusFetchSerializer( + { + "assessment_info": assessment_info, + "lock_info": lock_info, + } + ).data + return GradeContestedResponse(context=submission_status) + + # Transform grade data and submit assessment, rasies on failure + context = {"submission_uuid": submission_uuid} + grade_data = StaffAssessSerializer(request.data, context=context).data + submit_grade(request, ora_location, grade_data) + + # Clear the lock on the graded submission + delete_submission_lock(request, ora_location, submission_uuid) + + # Return submission status info to frontend + assessment_info = get_assessment_info( + request, ora_location, submission_uuid + ) + lock_info = check_submission_lock(request, ora_location, submission_uuid) + serializer = SubmissionStatusFetchSerializer( + { + "assessment_info": assessment_info, + "lock_info": lock_info, + } + ) + return Response(serializer.data) + + # Issues with the XBlock handlers + except XBlockInternalError as ex: + return InternalErrorResponse(context=ex.context) + + # Blanket exception handling + except Exception: + return UnknownErrorResponse() + + +class SubmissionLockView(StaffGraderBaseView): + """ + POST claim a submission lock for grading + DELETE release a submission lock + + Params: + - ora_location (str/UsageID): ORA location for XBlock handling + - submissionUUID (UUID): A submission to lock/unlock + + Response: { + lockStatus + } + + Errors: + - MissingParamResponse (HTTP 400) for missing params + - LockContestedResponse (HTTP 409) for contested lock + - XBlockInternalError (HTTP 500) for an issue with ORA + - UnknownError (HTTP 500) for other errors + """ + + @require_params([PARAM_ORA_LOCATION, PARAM_SUBMISSION_ID]) + def post(self, request, ora_location, submission_uuid, *args, **kwargs): + """Claim a submission lock""" + try: + # Validate ORA location + UsageKey.from_string(ora_location) + lock_info = claim_submission_lock(request, ora_location, submission_uuid) + return Response(LockStatusSerializer(lock_info).data) + + # Catch bad ORA location + except (InvalidKeyError, ItemNotFoundError): + return BadOraLocationResponse() + + # Return updated lock info on error + except LockContestedError: + lock_info = check_submission_lock(request, ora_location, submission_uuid) + lock_status = LockStatusSerializer(lock_info).data + return LockContestedResponse(context=lock_status) + + # Issues with the XBlock handlers + except XBlockInternalError as ex: + return InternalErrorResponse(context=ex.context) + + # Blanket exception handling + except Exception: + return UnknownErrorResponse() + + @require_params([PARAM_ORA_LOCATION, PARAM_SUBMISSION_ID]) + def delete(self, request, ora_location, submission_uuid, *args, **kwargs): + """Clear a submission lock""" + try: + # Validate ORA location + UsageKey.from_string(ora_location) + lock_info = delete_submission_lock(request, ora_location, submission_uuid) + return Response(LockStatusSerializer(lock_info).data) + + # Catch bad ORA location + except (InvalidKeyError, ItemNotFoundError): + return BadOraLocationResponse() + + # Return updated lock info on error + except LockContestedError: + lock_info = check_submission_lock(request, ora_location, submission_uuid) + lock_status = LockStatusSerializer(lock_info).data + return LockContestedResponse(context=lock_status) + + # Issues with the XBlock handlers + except XBlockInternalError as ex: + return InternalErrorResponse(context=ex.context) + + # Blanket exception handling + except Exception: + return UnknownErrorResponse() diff --git a/lms/djangoapps/teams/api.py b/lms/djangoapps/teams/api.py index fabceba379..a5d2da67d9 100644 --- a/lms/djangoapps/teams/api.py +++ b/lms/djangoapps/teams/api.py @@ -375,6 +375,22 @@ def get_team_for_user_course_topic(user, course_id, topic_id): ).first() +def get_teams_in_teamset(course_id, topic_id): + """ + Given a course_id and topic_id, return all CourseTeams in the course and topic + """ + try: + course_key = CourseKey.from_string(course_id) + except InvalidKeyError as exc: + raise ValueError("The supplied course id {course_id} is not valid.".format( + course_id=course_id + )) from exc + return CourseTeam.objects.filter( + course_id=course_key, + topic_id=topic_id, + ).all() + + def anonymous_user_ids_for_team(user, team): """ Get the anonymous user IDs for members of a team, used in team submissions Requesting user must be a member of the team or course staff diff --git a/lms/djangoapps/teams/services.py b/lms/djangoapps/teams/services.py index 5c5b5c6bf0..dce876fc6b 100644 --- a/lms/djangoapps/teams/services.py +++ b/lms/djangoapps/teams/services.py @@ -11,6 +11,15 @@ class TeamsService: from . import api return api.get_team_for_user_course_topic(user, course_id, topic_id) + def get_team_names(self, course_id, topic_id): + """ + Given a course and topic id, return a dict mapping from team id to team name for teams in that topic + """ + from . import api + teams = api.get_teams_in_teamset(course_id, topic_id) + name_mapping = {team.team_id: team.name for team in teams} + return name_mapping + def get_team_by_team_id(self, team_id): from . import api return api.get_team_by_team_id(team_id) diff --git a/lms/djangoapps/teams/tests/test_api.py b/lms/djangoapps/teams/tests/test_api.py index 0632bc832f..bc9679a8f6 100644 --- a/lms/djangoapps/teams/tests/test_api.py +++ b/lms/djangoapps/teams/tests/test_api.py @@ -222,6 +222,32 @@ class PythonAPITests(SharedModuleStoreTestCase): team_anonymous_user_ids = teams_api.anonymous_user_ids_for_team(user_staff, self.team1) assert len(self.team1.users.all()) == len(team_anonymous_user_ids) + def test_get_teams_in_teamset__bad_course_id(self): + bad_course_id = 'badcourseid' + with self.assertRaisesMessage(ValueError, f'The supplied course id {bad_course_id} is not valid'): + teams_api.get_teams_in_teamset(bad_course_id, 'teamset-id') + + def test_get_teams_in_teamset_1_1(self): + result = teams_api.get_teams_in_teamset(str(COURSE_KEY1), TOPIC1) + assert len(result) == 2 + assert self.team1 in result + assert self.team1a in result + + def test_get_teams_in_teamset_1_2(self): + result = teams_api.get_teams_in_teamset(str(COURSE_KEY1), TOPIC2) + assert len(result) == 0 + + def test_get_teams_in_teamset_2_2(self): + result = teams_api.get_teams_in_teamset(str(COURSE_KEY2), TOPIC2) + assert len(result) == 2 + assert self.team2 in result + assert self.team2a in result + + def test_get_teams_in_teamset_2_3(self): + result = teams_api.get_teams_in_teamset(str(COURSE_KEY2), TOPIC3) + assert len(result) == 1 + assert self.team3 in result + @ddt.ddt class TeamAccessTests(SharedModuleStoreTestCase): diff --git a/lms/djangoapps/teams/tests/test_services.py b/lms/djangoapps/teams/tests/test_services.py index a9a5c1642f..22baeefadf 100644 --- a/lms/djangoapps/teams/tests/test_services.py +++ b/lms/djangoapps/teams/tests/test_services.py @@ -44,3 +44,28 @@ class TeamsServiceTests(ModuleStoreTestCase): split_url = team_detail_url.split('/') assert split_url[1:] ==\ ['courses', str(self.course_run['key']), 'teams', '#teams', self.team.topic_id, self.team.team_id] + + def test_get_team_names(self): + """ + get_team_names will return a dict mapping the team id to the team name for all teams in the given teamset + """ + additional_teams = [ + CourseTeamFactory.create(course_id=self.course_key, topic_id=self.team.topic_id) + for _ in range(3) + ] + + result = self.service.get_team_names(self.course_key, self.team.topic_id) + + assert result == { + self.team.team_id: self.team.name, + additional_teams[0].team_id: additional_teams[0].name, + additional_teams[1].team_id: additional_teams[1].name, + additional_teams[2].team_id: additional_teams[2].name, + } + + def test_get_team_names__none(self): + """ If there are no teams in the teamset, the function will return an empty list""" + course_run = CourseRunFactory.create() + course_key = course_run['key'] + result = self.service.get_team_names(course_key, "some-topic-id") + assert result == {} diff --git a/lms/envs/common.py b/lms/envs/common.py index 693a0cf40e..5969d7abb0 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -3895,6 +3895,7 @@ OPTIONAL_APPS = [ ('openassessment', 'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'), ('openassessment.assessment', 'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'), ('openassessment.fileupload', 'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'), + ('openassessment.staffgrader', 'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'), ('openassessment.workflow', 'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'), ('openassessment.xblock', 'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'), @@ -4731,6 +4732,13 @@ PROGRAM_CONSOLE_MICROFRONTEND_URL = None # .. setting_default: None # .. setting_description: Base URL of the micro-frontend-based courseware page. LEARNING_MICROFRONTEND_URL = None +# .. setting_name: ORA_GRADING_MICROFRONTEND_URL +# .. setting_default: None +# .. setting_description: Base URL of the micro-frontend-based openassessment grading page. +# This is will be show in the open response tab list data. +# .. setting_warning: Also set site's openresponseassessment.enhanced_staff_grader +# waffle flag. +ORA_GRADING_MICROFRONTEND_URL = None # .. setting_name: DISCUSSIONS_MICROFRONTEND_URL # .. setting_default: None # .. setting_description: Base URL of the micro-frontend-based discussions page. diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py index fe422a1cef..be26f49719 100644 --- a/lms/envs/devstack.py +++ b/lms/envs/devstack.py @@ -277,6 +277,7 @@ LOGIN_REDIRECT_WHITELIST.extend([ 'localhost:2001', # frontend-app-course-authoring 'localhost:3001', # frontend-app-library-authoring 'localhost:18400', # frontend-app-publisher + 'localhost:1993', # frontend-app-ora-grading ENTERPRISE_LEARNER_PORTAL_NETLOC, # frontend-app-learner-portal-enterprise ENTERPRISE_ADMIN_PORTAL_NETLOC, # frontend-app-admin-portal ]) diff --git a/lms/urls.py b/lms/urls.py index 350e7f2b10..53e5ee2df3 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -1026,3 +1026,8 @@ if settings.ENABLE_SAVE_FOR_LATER: urlpatterns += [ path('', include('lms.djangoapps.save_for_later.urls')), ] + +# Enhanced Staff Grader (ESG) URLs +urlpatterns += [ + path('api/ora_staff_grader/', include('lms.djangoapps.ora_staff_grader.urls', 'ora-staff-grader')), +] diff --git a/openedx/core/djangoapps/content/course_overviews/tests/factories.py b/openedx/core/djangoapps/content/course_overviews/tests/factories.py index 0604ac9831..b7c1ca1b15 100644 --- a/openedx/core/djangoapps/content/course_overviews/tests/factories.py +++ b/openedx/core/djangoapps/content/course_overviews/tests/factories.py @@ -20,6 +20,7 @@ class CourseOverviewFactory(DjangoModelFactory): # lint-amnesty, pylint: disabl version = CourseOverview.VERSION pre_requisite_courses = [] org = 'edX' + display_number_with_default = 'toy' run = factory.Sequence('2012_Fall_{}'.format) @factory.lazy_attribute @@ -32,7 +33,11 @@ class CourseOverviewFactory(DjangoModelFactory): # lint-amnesty, pylint: disabl @factory.lazy_attribute def id(self): - return CourseLocator(self.org, 'toy', self.run) + return CourseLocator(self.org, self.display_number_with_default, self.run) + + @factory.lazy_attribute + def display_org_with_default(self): + return self.org @factory.lazy_attribute def display_name(self): diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 7fa677a89a..ebb5e8aa83 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -704,7 +704,7 @@ openedx-events==0.7.1 # via -r requirements/edx/base.in openedx-filters==0.4.3 # via -r requirements/edx/base.in -ora2==3.7.8 +ora2==3.8.1 # via -r requirements/edx/base.in packaging==21.3 # via diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index b0a52361ac..c00ae564b0 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -939,7 +939,7 @@ openedx-events==0.7.1 # via -r requirements/edx/testing.txt openedx-filters==0.4.3 # via -r requirements/edx/testing.txt -ora2==3.7.8 +ora2==3.8.1 # via -r requirements/edx/testing.txt packaging==21.3 # via diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 6be25ac863..13b993fd0d 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -889,7 +889,7 @@ openedx-events==0.7.1 # via -r requirements/edx/base.txt openedx-filters==0.4.3 # via -r requirements/edx/base.txt -ora2==3.7.8 +ora2==3.8.1 # via -r requirements/edx/base.txt packaging==21.3 # via