edx-proctoring 5.2.0__tar.gz → 6.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- edx-proctoring-5.2.0/edx_proctoring.egg-info/PKG-INFO → edx_proctoring-6.1.2/CHANGELOG.rst +64 -130
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/PKG-INFO +95 -4
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/__init__.py +1 -1
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/admin.py +2 -2
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/api.py +123 -4
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/apps.py +1 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/backends/backend.py +6 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/backends/rest.py +43 -14
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/backends/software_secure.py +4 -1
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/backends/tests/test_rest.py +111 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/backends/tests/test_software_secure.py +10 -1
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/exceptions.py +19 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/handlers.py +50 -49
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/management/commands/reset_attempts.py +13 -0
- edx_proctoring-6.1.2/edx_proctoring/management/commands/tests/test_reset_attempts.py +153 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/management/commands/tests/test_update_attempt_status_from_review.py +9 -3
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/models.py +12 -3
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/test_api.py +143 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/test_handlers.py +0 -9
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/test_models.py +1 -1
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/test_reviews.py +247 -1
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/test_views.py +8 -3
- edx-proctoring-5.2.0/CHANGELOG.rst → edx_proctoring-6.1.2/edx_proctoring.egg-info/PKG-INFO +221 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/base.txt +61 -64
- edx_proctoring-6.1.2/requirements/celery50.txt +9 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/ci.txt +17 -13
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/common_constraints.txt +1 -8
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/constraints.txt +0 -3
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/dev.txt +59 -52
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/pip-tools.txt +10 -8
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/pip.txt +4 -2
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/quality.txt +37 -35
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/test.txt +78 -78
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/setup.py +0 -2
- edx-proctoring-5.2.0/edx_proctoring/management/commands/tests/test_reset_attempts.py +0 -72
- edx-proctoring-5.2.0/requirements/celery50.txt +0 -9
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/LICENSE.txt +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/MANIFEST.in +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/README.rst +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/backends/LICENSE.txt +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/backends/README.txt +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/backends/__init__.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/backends/mock.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/backends/null.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/backends/tests/__init__.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/backends/tests/test_backend.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/backends/tests/test_review_payload.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/callbacks.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/constants.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/decorators.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/instructor_dashboard_exam_urls.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ar/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ar/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ar/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ar/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ca/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ca/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ca/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ca/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/config.yaml +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/cs/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/cs/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/cs/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/cs/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/de_DE/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/de_DE/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/de_DE/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/de_DE/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/el/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/el/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/el/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/el/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/en/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/en/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/en/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/en/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/eo/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/eo/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/eo/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/eo/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/es_419/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/es_419/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/es_419/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/es_419/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/es_AR/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/es_AR/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/es_AR/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/es_AR/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/es_ES/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/es_ES/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/es_ES/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/es_ES/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/eu_ES/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/eu_ES/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/eu_ES/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/eu_ES/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/fa_IR/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/fa_IR/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/fa_IR/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/fa_IR/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/fr/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/fr/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/fr/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/fr/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/fr_CA/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/fr_CA/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/fr_CA/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/fr_CA/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/he/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/he/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/he/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/he/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/id/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/id/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/id/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/id/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/it_IT/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/it_IT/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/it_IT/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/it_IT/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ja_JP/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ja_JP/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ka/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ka/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ka/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ka/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/lv/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/lv/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/lv/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/lv/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/mn/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/mn/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/mn/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/mn/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/nb/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/nb/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/nb/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/nb/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/pl/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/pl/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/pl/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/pl/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/pt_PT/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/pt_PT/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/pt_PT/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ro/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ro/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ro/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ro/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ru/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ru/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ru/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/ru/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/sw_KE/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/sw_KE/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/sw_KE/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/sw_KE/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/tr_TR/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/tr_TR/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/tr_TR/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/tr_TR/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/uk/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/uk/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/uk/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/uk/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/zh_CN/LC_MESSAGES/django.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/zh_CN/LC_MESSAGES/django.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/zh_CN/LC_MESSAGES/djangojs.mo +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/locale/zh_CN/LC_MESSAGES/djangojs.po +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/management/__init__.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/management/commands/__init__.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/management/commands/set_attempt_status.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/management/commands/set_is_attempt_active.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/management/commands/tests/__init__.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/management/commands/tests/test_set_attempt_status.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/management/commands/tests/test_set_is_attempt_active.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/management/commands/tests/test_update_attempts_for_exam.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/management/commands/update_attempt_status_from_review.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/management/commands/update_attempts_for_exam.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0001_initial.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0002_proctoredexamstudentattempt_is_status_acknowledged.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0003_auto_20160101_0525.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0004_auto_20160201_0523.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0005_proctoredexam_hide_after_due.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0006_allowed_time_limit_mins.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0007_proctoredexam_backend.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0008_auto_20181116_1551.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0009_proctoredexamreviewpolicy_remove_rules.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0010_update_backend.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0011_allow_multiple_attempts.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0012_proctoredexamstudentattempt_time_remaining_seconds.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0013_proctoredexamsoftwaresecurereview_is_active_attempt.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0014_add_is_resumable_to_proctoredexamstudentattempt.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0015_rm_proctoredexamstudentattempt_ips.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0016_nullable_proctoredexamstudentattempt_name.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0017_rm_proctoredexamstudentattempt_name.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0018_historicalproctoredexamstudentattempt.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0019_proctoredexamsoftwaresecurereview_encrypted_video_url.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0020_auto_20211028_1915.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0021_auto_20211029_1353.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0022_proctoredexamstudentattempt_add_readytoresume_resumed.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0023_historicalproctoredexam.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0024_delete_proctoredexamstudentattempthistory.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/0025_auto_20230727_2112.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/migrations/__init__.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/rules.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/runtime.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/scripts/obscure_user.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/scripts/version_check.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/serializers.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/services.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/settings/__init__.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/settings/common.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/settings/production.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/settings/test.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/signals.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/index.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/clippy.swf +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/close-modal-hover.png +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/close-modal.png +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/collections/proctored_exam_allowance_collection.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/collections/proctored_exam_attempt_grouped_collection.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/collections/proctored_exam_collection.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/collections/proctored_exam_onboarding_collection.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/dropdown.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/exam_action_handler.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/models/learner_onboarding_model.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/models/proctored_exam_allowance_model.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/models/proctored_exam_attempt_model.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/models/proctored_exam_bulk_allowance_model.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/models/proctored_exam_model.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/proctored_app.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/vendor/backbone.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/vendor/date.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/vendor/i18n.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/vendor/jquery.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/vendor/underscore.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/views/Backbone.ModalDialog.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/views/proctored_exam_add_bulk_allowance_view.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/views/proctored_exam_allowance_view.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/views/proctored_exam_attempt_view.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/views/proctored_exam_edit_allowance_view.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/views/proctored_exam_info.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/views/proctored_exam_instructor_launch.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/views/proctored_exam_onboarding_view.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/js/views/proctored_exam_view.js +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/templates/add-new-bulk-allowance.underscore +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/templates/course_grouped_allowances.underscore +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/templates/dashboard.underscore +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/templates/edit-allowance.underscore +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/templates/proctored-exam-info.underscore +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/templates/student-onboarding-status.underscore +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/static/proctoring/templates/student-proctored-exam-attempts-grouped.underscore +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/statuses.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/common/waiting_banner.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/emails/proctoring_attempt_satisfactory_email.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/emails/proctoring_attempt_submitted_email.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/emails/proctoring_attempt_unsatisfactory_email.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/onboarding_exam/entrance.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/onboarding_exam/error.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/onboarding_exam/ready_to_submit.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/onboarding_exam/rejected.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/onboarding_exam/submitted.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/onboarding_exam/verified.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/practice_exam/entrance.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/practice_exam/error.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/practice_exam/submitted.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/confirm-decline.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/entrance.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/error.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/error_modal.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/error_wrong_browser.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/expired.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/failed-prerequisites.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/footer.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/inactive_account.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/instructions.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/onboarding_error.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/pending-prerequisites.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/proctoring_launch_callback.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/proctoring_opt_out_button.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/ready_to_resume.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/ready_to_start.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/ready_to_submit.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/rejected.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/submitted.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/verified.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/proctored_exam/visit_exam_content.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/timed_exam/entrance.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/timed_exam/expired.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/timed_exam/footer.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/timed_exam/ready_to_submit.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/templates/timed_exam/submitted.html +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/__init__.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/test_email.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/test_mfe_views.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/test_serializer.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/test_services.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/test_student_view.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/test_utils/factories.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/test_utils/test_utils.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/test_utils/utils.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/tests/test_workerconfig.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/urls.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/utils.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring/views.py +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring.egg-info/SOURCES.txt +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring.egg-info/dependency_links.txt +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring.egg-info/entry_points.txt +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring.egg-info/not-zip-safe +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring.egg-info/requires.txt +14 -14
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/edx_proctoring.egg-info/top_level.txt +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/base.in +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/ci.in +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/dev.in +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/pip-tools.in +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/pip.in +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/quality.in +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/requirements/test.in +0 -0
- {edx-proctoring-5.2.0 → edx_proctoring-6.1.2}/setup.cfg +0 -0
|
@@ -1,133 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: edx-proctoring
|
|
3
|
-
Version: 5.2.0
|
|
4
|
-
Summary: Proctoring subsystem for Open edX
|
|
5
|
-
Home-page: https://github.com/openedx/edx-proctoring
|
|
6
|
-
Author: edX
|
|
7
|
-
Author-email: oscm@edx.org
|
|
8
|
-
License: AGPL 3.0
|
|
9
|
-
Keywords: Django edx
|
|
10
|
-
Classifier: Development Status :: 4 - Beta
|
|
11
|
-
Classifier: Framework :: Django
|
|
12
|
-
Classifier: Framework :: Django :: 4.2
|
|
13
|
-
Classifier: Framework :: Django :: 5.2
|
|
14
|
-
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
16
|
-
Classifier: Natural Language :: English
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
License-File: LICENSE.txt
|
|
21
|
-
|
|
22
|
-
edx-proctoring
|
|
23
|
-
==============
|
|
24
|
-
|
|
25
|
-
.. image:: https://img.shields.io/pypi/v/edx-proctoring.svg
|
|
26
|
-
:target: https://pypi.python.org/pypi/edx-proctoring/
|
|
27
|
-
:alt: PyPI
|
|
28
|
-
|
|
29
|
-
.. image:: https://github.com/openedx/edx-proctoring/workflows/Python%20CI/badge.svg
|
|
30
|
-
:target: https://github.com/openedx/edx-proctoring/actions?query=workflow%3A%22Python+CI%22
|
|
31
|
-
:alt: Python CI
|
|
32
|
-
|
|
33
|
-
.. image:: https://codecov.io/gh/edx/edx-proctoring/branch/master/graph/badge.svg
|
|
34
|
-
:target: https://codecov.io/gh/edx/edx-proctoring
|
|
35
|
-
:alt: Codecov
|
|
36
|
-
|
|
37
|
-
.. image:: https://img.shields.io/pypi/pyversions/edx-proctoring.svg
|
|
38
|
-
:target: https://pypi.python.org/pypi/edx-proctoring/
|
|
39
|
-
:alt: Supported Python versions
|
|
40
|
-
|
|
41
|
-
.. image:: https://img.shields.io/github/license/edx/django-component-views.svg
|
|
42
|
-
:target: https://github.com/openedx/edx-proctoring/blob/master/LICENSE.txt
|
|
43
|
-
:alt: License
|
|
44
|
-
|
|
45
|
-
This is the exam proctoring subsystem for the Open edX platform.
|
|
46
|
-
|
|
47
|
-
Overview
|
|
48
|
-
--------
|
|
49
|
-
|
|
50
|
-
Proctored exams are exams with time limits that learners complete while online
|
|
51
|
-
proctoring software monitors their computers and behavior for activity that
|
|
52
|
-
might be evidence of cheating. This Python library provides the proctoring
|
|
53
|
-
implementation used by Open edX.
|
|
54
|
-
|
|
55
|
-
Documentation
|
|
56
|
-
-------------
|
|
57
|
-
|
|
58
|
-
For authoring documentation, see `Including Proctored Exams In Your Course`_.
|
|
59
|
-
|
|
60
|
-
Installation
|
|
61
|
-
------------
|
|
62
|
-
|
|
63
|
-
To install edx-proctoring:
|
|
64
|
-
|
|
65
|
-
mkvirtualenv edx-proctoring
|
|
66
|
-
|
|
67
|
-
make install
|
|
68
|
-
|
|
69
|
-
If you want to run tests, first setup requirements. Note that JS tests
|
|
70
|
-
require Node 10, nvm install v10 if you are on a different version so
|
|
71
|
-
that gulp is installed for v10:
|
|
72
|
-
|
|
73
|
-
nvm install v10
|
|
74
|
-
|
|
75
|
-
make requirements-test
|
|
76
|
-
|
|
77
|
-
Run tests:
|
|
78
|
-
|
|
79
|
-
make test
|
|
80
|
-
|
|
81
|
-
Run just python tests:
|
|
82
|
-
|
|
83
|
-
make test-python
|
|
84
|
-
|
|
85
|
-
Run just JS tests:
|
|
86
|
-
|
|
87
|
-
make test-js
|
|
88
|
-
|
|
89
|
-
For a full list of Make targets:
|
|
90
|
-
|
|
91
|
-
make help
|
|
92
|
-
|
|
93
|
-
Developing
|
|
94
|
-
-------------
|
|
95
|
-
|
|
96
|
-
See the `developer guide`_ for configuration, devstack and sandbox setup, and other developer concerns.
|
|
97
|
-
|
|
98
|
-
.. _developer guide: ./docs/developing.rst
|
|
99
|
-
|
|
100
|
-
License
|
|
101
|
-
-------
|
|
102
|
-
|
|
103
|
-
The code in this repository is licensed under the AGPL 3.0 unless
|
|
104
|
-
otherwise noted.
|
|
105
|
-
|
|
106
|
-
Please see ``LICENSE.txt`` for details.
|
|
107
|
-
|
|
108
|
-
How To Contribute
|
|
109
|
-
-----------------
|
|
110
|
-
|
|
111
|
-
Contributions are very welcome.
|
|
112
|
-
|
|
113
|
-
Please read `How To Contribute <https://github.com/openedx/.github/blob/master/CONTRIBUTING.md>`_ for details.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
Reporting Security Issues
|
|
117
|
-
-------------------------
|
|
118
|
-
|
|
119
|
-
Please do not report security issues in public. Please email security@openedx.org.
|
|
120
|
-
|
|
121
|
-
Getting Help
|
|
122
|
-
------------
|
|
123
|
-
|
|
124
|
-
Have a question about this repository, or about Open edX in general? Please
|
|
125
|
-
refer to this `list of resources`_ if you need any assistance.
|
|
126
|
-
|
|
127
|
-
.. _list of resources: https://open.edx.org/getting-help
|
|
128
|
-
.. _Including Proctored Exams In Your Course: https://docs.openedx.org/en/latest/educators/concepts/proctored_exams/proctored_exams_overview.html
|
|
129
|
-
|
|
130
|
-
|
|
131
1
|
Change Log
|
|
132
2
|
----------
|
|
133
3
|
|
|
@@ -143,6 +13,70 @@ Change Log
|
|
|
143
13
|
|
|
144
14
|
Unreleased
|
|
145
15
|
~~~~~~~~~~
|
|
16
|
+
|
|
17
|
+
[6.1.2] - 2026-10-16
|
|
18
|
+
~~~~~~~~~~~~~~~~~~~~
|
|
19
|
+
|
|
20
|
+
* A passing review from the proctoring provider no longer marks an attempt ``verified``
|
|
21
|
+
when the learner never submitted it, for example because the proctoring session ended
|
|
22
|
+
while the attempt was still ``ready_to_start``. Such attempts are put into ``error``
|
|
23
|
+
instead, so the learner is told to retry and staff can see the exam was not completed.
|
|
24
|
+
* A review that arrives for an attempt that has since been reset (``onboarding_reset``)
|
|
25
|
+
no longer changes that attempt's status.
|
|
26
|
+
* A timeout treated as a submission (``ALLOW_TIMED_OUT_STATE`` off) now always records
|
|
27
|
+
``completed_at``, falling back to the current time when no timeout timestamp is passed,
|
|
28
|
+
as ``mark_exam_attempt_timeout`` does. As with any other submission, this also marks
|
|
29
|
+
the exam complete in the completion service.
|
|
30
|
+
* **Existing data:** attempts that are already ``verified`` but have no ``completed_at``
|
|
31
|
+
(they were verified without being submitted) move to ``error`` the next time their
|
|
32
|
+
review is processed, whether by a re-delivered passing review or by the
|
|
33
|
+
``update_attempt_status_from_review`` management command, and their credit
|
|
34
|
+
requirement changes to ``failed``. Run that command over a review date range to apply
|
|
35
|
+
this to existing attempts on purpose.
|
|
36
|
+
|
|
37
|
+
[6.1.1] - 2026-10-14
|
|
38
|
+
~~~~~~~~~~~~~~~~~~~~
|
|
39
|
+
|
|
40
|
+
* Fix PyPI publish workflow: use ``python -m build`` instead of ``setup.py sdist
|
|
41
|
+
bdist_wheel``, which broke on Python 3.12 because ``setuptools`` is no longer
|
|
42
|
+
bundled and was not installed by the workflow.
|
|
43
|
+
|
|
44
|
+
[6.1.0] - 2026-09-01
|
|
45
|
+
~~~~~~~~~~~~~~~~~~~~
|
|
46
|
+
|
|
47
|
+
* **Behaviour change for API consumers:** the instructor reset endpoint now responds with
|
|
48
|
+
**502** when the proctoring provider cannot remove the attempt, where it previously
|
|
49
|
+
returned a 500 or appeared to succeed. Anything driving the instructor dashboard reset
|
|
50
|
+
should handle that status.
|
|
51
|
+
* Make resetting a proctored exam attempt fail gracefully when the proctoring provider
|
|
52
|
+
is unavailable, instead of an unhandled 500 or a stuck reset:
|
|
53
|
+
|
|
54
|
+
* Add an explicit request timeout to the REST proctoring backend (default 30s,
|
|
55
|
+
overridable per backend via a ``timeout`` key in ``PROCTORING_BACKENDS``), applied to
|
|
56
|
+
every outbound provider request, so a slow provider cannot hang the request
|
|
57
|
+
indefinitely.
|
|
58
|
+
* Call the provider from ``remove_exam_attempt`` **before** the local delete (rather
|
|
59
|
+
than from the ``pre_delete`` signal). If the provider is unreachable/errors, a typed
|
|
60
|
+
``BackendProviderCannotRemoveAttempt`` (HTTP 502) is raised before anything is
|
|
61
|
+
deleted, so the caller can show the instructor a descriptive message and the local
|
|
62
|
+
attempt is preserved. Because the failure no longer originates inside ``delete()``'s
|
|
63
|
+
``pre_delete`` signal, it does not leave the DB connection in a needs-rollback state,
|
|
64
|
+
so a partially-failed multi-attempt reset can be retried in the same request.
|
|
65
|
+
* On removal, a provider ``404`` (the attempt is already gone upstream) is treated as
|
|
66
|
+
success so a retried, partially-failed reset converges; any other HTTP error is raised
|
|
67
|
+
so the local attempt is kept for a later retry instead of being silently dropped.
|
|
68
|
+
* Route the ``reset_attempts`` management command through the same provider-removal
|
|
69
|
+
path, since provider cleanup no longer happens in the ``pre_delete`` signal.
|
|
70
|
+
* Learner-initiated practice/onboarding resets stay best-effort, so a provider problem
|
|
71
|
+
does not block a learner from retrying their own onboarding exam.
|
|
72
|
+
* Deleting a ``ProctoredExam`` no longer notifies the proctoring provider about the
|
|
73
|
+
attempts that are cascade-deleted with it. Provider removal now happens at the three
|
|
74
|
+
call sites that reset attempts rather than in a ``pre_delete`` signal, so attempts
|
|
75
|
+
removed this way (a staff delete from Django admin) are left on the provider.
|
|
76
|
+
|
|
77
|
+
[5.2.1] - 2025-12-05
|
|
78
|
+
* Remove all references to Proctortrack
|
|
79
|
+
|
|
146
80
|
[5.2.0] - 2025-04-22
|
|
147
81
|
* adds support for django 5.2
|
|
148
82
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: edx-proctoring
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.1.2
|
|
4
4
|
Summary: Proctoring subsystem for Open edX
|
|
5
5
|
Home-page: https://github.com/openedx/edx-proctoring
|
|
6
6
|
Author: edX
|
|
@@ -14,10 +14,37 @@ Classifier: Framework :: Django :: 5.2
|
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
16
16
|
Classifier: Natural Language :: English
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
18
|
License-File: LICENSE.txt
|
|
19
|
+
Requires-Dist: event-tracking>=0.2.5
|
|
20
|
+
Requires-Dist: jsonfield
|
|
21
|
+
Requires-Dist: edx-opaque-keys>=0.4
|
|
22
|
+
Requires-Dist: edx-when>=0.1.3
|
|
23
|
+
Requires-Dist: Django>=2.2
|
|
24
|
+
Requires-Dist: django-webpack-loader>=0.6.0
|
|
25
|
+
Requires-Dist: rules
|
|
26
|
+
Requires-Dist: django-ipware>=1.1.0
|
|
27
|
+
Requires-Dist: djangorestframework
|
|
28
|
+
Requires-Dist: django-simple-history
|
|
29
|
+
Requires-Dist: pycryptodomex>=3.4.7
|
|
30
|
+
Requires-Dist: pyjwt[crypto]>=2.1.0
|
|
31
|
+
Requires-Dist: edx-drf-extensions
|
|
32
|
+
Requires-Dist: django-crum
|
|
33
|
+
Requires-Dist: python-dateutil>=2.1
|
|
34
|
+
Requires-Dist: pytz>=2018
|
|
35
|
+
Requires-Dist: django-model-utils
|
|
36
|
+
Requires-Dist: django-waffle
|
|
37
|
+
Requires-Dist: edx-rest-api-client>=1.9.2
|
|
38
|
+
Dynamic: author
|
|
39
|
+
Dynamic: author-email
|
|
40
|
+
Dynamic: classifier
|
|
41
|
+
Dynamic: description
|
|
42
|
+
Dynamic: home-page
|
|
43
|
+
Dynamic: keywords
|
|
44
|
+
Dynamic: license
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
Dynamic: requires-dist
|
|
47
|
+
Dynamic: summary
|
|
21
48
|
|
|
22
49
|
edx-proctoring
|
|
23
50
|
==============
|
|
@@ -143,6 +170,70 @@ Change Log
|
|
|
143
170
|
|
|
144
171
|
Unreleased
|
|
145
172
|
~~~~~~~~~~
|
|
173
|
+
|
|
174
|
+
[6.1.2] - 2026-10-16
|
|
175
|
+
~~~~~~~~~~~~~~~~~~~~
|
|
176
|
+
|
|
177
|
+
* A passing review from the proctoring provider no longer marks an attempt ``verified``
|
|
178
|
+
when the learner never submitted it, for example because the proctoring session ended
|
|
179
|
+
while the attempt was still ``ready_to_start``. Such attempts are put into ``error``
|
|
180
|
+
instead, so the learner is told to retry and staff can see the exam was not completed.
|
|
181
|
+
* A review that arrives for an attempt that has since been reset (``onboarding_reset``)
|
|
182
|
+
no longer changes that attempt's status.
|
|
183
|
+
* A timeout treated as a submission (``ALLOW_TIMED_OUT_STATE`` off) now always records
|
|
184
|
+
``completed_at``, falling back to the current time when no timeout timestamp is passed,
|
|
185
|
+
as ``mark_exam_attempt_timeout`` does. As with any other submission, this also marks
|
|
186
|
+
the exam complete in the completion service.
|
|
187
|
+
* **Existing data:** attempts that are already ``verified`` but have no ``completed_at``
|
|
188
|
+
(they were verified without being submitted) move to ``error`` the next time their
|
|
189
|
+
review is processed, whether by a re-delivered passing review or by the
|
|
190
|
+
``update_attempt_status_from_review`` management command, and their credit
|
|
191
|
+
requirement changes to ``failed``. Run that command over a review date range to apply
|
|
192
|
+
this to existing attempts on purpose.
|
|
193
|
+
|
|
194
|
+
[6.1.1] - 2026-10-14
|
|
195
|
+
~~~~~~~~~~~~~~~~~~~~
|
|
196
|
+
|
|
197
|
+
* Fix PyPI publish workflow: use ``python -m build`` instead of ``setup.py sdist
|
|
198
|
+
bdist_wheel``, which broke on Python 3.12 because ``setuptools`` is no longer
|
|
199
|
+
bundled and was not installed by the workflow.
|
|
200
|
+
|
|
201
|
+
[6.1.0] - 2026-09-01
|
|
202
|
+
~~~~~~~~~~~~~~~~~~~~
|
|
203
|
+
|
|
204
|
+
* **Behaviour change for API consumers:** the instructor reset endpoint now responds with
|
|
205
|
+
**502** when the proctoring provider cannot remove the attempt, where it previously
|
|
206
|
+
returned a 500 or appeared to succeed. Anything driving the instructor dashboard reset
|
|
207
|
+
should handle that status.
|
|
208
|
+
* Make resetting a proctored exam attempt fail gracefully when the proctoring provider
|
|
209
|
+
is unavailable, instead of an unhandled 500 or a stuck reset:
|
|
210
|
+
|
|
211
|
+
* Add an explicit request timeout to the REST proctoring backend (default 30s,
|
|
212
|
+
overridable per backend via a ``timeout`` key in ``PROCTORING_BACKENDS``), applied to
|
|
213
|
+
every outbound provider request, so a slow provider cannot hang the request
|
|
214
|
+
indefinitely.
|
|
215
|
+
* Call the provider from ``remove_exam_attempt`` **before** the local delete (rather
|
|
216
|
+
than from the ``pre_delete`` signal). If the provider is unreachable/errors, a typed
|
|
217
|
+
``BackendProviderCannotRemoveAttempt`` (HTTP 502) is raised before anything is
|
|
218
|
+
deleted, so the caller can show the instructor a descriptive message and the local
|
|
219
|
+
attempt is preserved. Because the failure no longer originates inside ``delete()``'s
|
|
220
|
+
``pre_delete`` signal, it does not leave the DB connection in a needs-rollback state,
|
|
221
|
+
so a partially-failed multi-attempt reset can be retried in the same request.
|
|
222
|
+
* On removal, a provider ``404`` (the attempt is already gone upstream) is treated as
|
|
223
|
+
success so a retried, partially-failed reset converges; any other HTTP error is raised
|
|
224
|
+
so the local attempt is kept for a later retry instead of being silently dropped.
|
|
225
|
+
* Route the ``reset_attempts`` management command through the same provider-removal
|
|
226
|
+
path, since provider cleanup no longer happens in the ``pre_delete`` signal.
|
|
227
|
+
* Learner-initiated practice/onboarding resets stay best-effort, so a provider problem
|
|
228
|
+
does not block a learner from retrying their own onboarding exam.
|
|
229
|
+
* Deleting a ``ProctoredExam`` no longer notifies the proctoring provider about the
|
|
230
|
+
attempts that are cascade-deleted with it. Provider removal now happens at the three
|
|
231
|
+
call sites that reset attempts rather than in a ``pre_delete`` signal, so attempts
|
|
232
|
+
removed this way (a staff delete from Django admin) are left on the provider.
|
|
233
|
+
|
|
234
|
+
[5.2.1] - 2025-12-05
|
|
235
|
+
* Remove all references to Proctortrack
|
|
236
|
+
|
|
146
237
|
[5.2.0] - 2025-04-22
|
|
147
238
|
* adds support for django 5.2
|
|
148
239
|
|
|
@@ -316,11 +316,11 @@ class ProctoredExamSoftwareSecureReviewAdmin(admin.ModelAdmin):
|
|
|
316
316
|
obj.reviewed_by = request.user
|
|
317
317
|
obj.save()
|
|
318
318
|
|
|
319
|
-
def lookup_allowed(self, lookup, value):
|
|
319
|
+
def lookup_allowed(self, lookup, value, request=None):
|
|
320
320
|
""" Checks if lookup allowed or not """
|
|
321
321
|
if lookup == 'exam__course_id':
|
|
322
322
|
return True
|
|
323
|
-
return super().lookup_allowed(lookup, value)
|
|
323
|
+
return super().lookup_allowed(lookup, value, request)
|
|
324
324
|
|
|
325
325
|
|
|
326
326
|
@admin.register(ProctoredExamSoftwareSecureReviewHistory)
|
|
@@ -26,8 +26,10 @@ from edx_proctoring.backends import get_backend_provider
|
|
|
26
26
|
from edx_proctoring.exceptions import (
|
|
27
27
|
AllowanceValueNotAllowedException,
|
|
28
28
|
BackendProviderCannotRegisterAttempt,
|
|
29
|
+
BackendProviderCannotRemoveAttempt,
|
|
29
30
|
BackendProviderNotConfigured,
|
|
30
31
|
BackendProviderOnboardingException,
|
|
32
|
+
BackendProviderRemovalNotConfirmed,
|
|
31
33
|
BackendProviderSentNoAttemptID,
|
|
32
34
|
ProctoredBaseException,
|
|
33
35
|
ProctoredExamAlreadyExists,
|
|
@@ -1472,7 +1474,7 @@ def update_attempt_status(attempt_id, to_status,
|
|
|
1472
1474
|
exam_attempt_obj.allowed_time_limit_mins = _calculate_allowed_mins(exam, exam_attempt_obj.user_id)
|
|
1473
1475
|
|
|
1474
1476
|
elif treat_timeout_as_submitted:
|
|
1475
|
-
exam_attempt_obj.completed_at = timeout_timestamp
|
|
1477
|
+
exam_attempt_obj.completed_at = timeout_timestamp or datetime.now(pytz.UTC)
|
|
1476
1478
|
elif to_status == ProctoredExamStudentAttemptStatus.submitted:
|
|
1477
1479
|
# likewise, when we transition to submitted mark
|
|
1478
1480
|
# when the exam has been completed
|
|
@@ -1771,7 +1773,7 @@ def create_proctoring_attempt_status_email(exam_attempt_obj, course_name, course
|
|
|
1771
1773
|
default_contact_url = f'{scheme}://{constants.SITE_NAME}/support/contact_us'
|
|
1772
1774
|
|
|
1773
1775
|
# If the course has a proctoring escalation email set, use that rather than edX Support.
|
|
1774
|
-
#
|
|
1776
|
+
# This will be set if 'requires_escalation_email' is 'True' in proctoring backend.
|
|
1775
1777
|
proctoring_escalation_email = _get_proctoring_escalation_email(course_id)
|
|
1776
1778
|
if proctoring_escalation_email:
|
|
1777
1779
|
contact_url = f'mailto:{proctoring_escalation_email}'
|
|
@@ -1880,7 +1882,9 @@ def reset_practice_exam(exam_id, user_id, requesting_user):
|
|
|
1880
1882
|
|
|
1881
1883
|
# resetting a submitted attempt that has not been reviewed will entirely remove that submission.
|
|
1882
1884
|
if exam_attempt_obj.status == ProctoredExamStudentAttemptStatus.submitted:
|
|
1883
|
-
|
|
1885
|
+
# This is the learner retrying their own onboarding exam, so a provider problem
|
|
1886
|
+
# must not block them: remove what we can upstream and reset locally regardless.
|
|
1887
|
+
remove_exam_attempt(exam_attempt_obj.id, requesting_user, require_backend_removal=False)
|
|
1884
1888
|
else:
|
|
1885
1889
|
exam_attempt_obj.status = ProctoredExamStudentAttemptStatus.onboarding_reset
|
|
1886
1890
|
exam_attempt_obj.save()
|
|
@@ -1890,9 +1894,115 @@ def reset_practice_exam(exam_id, user_id, requesting_user):
|
|
|
1890
1894
|
return create_exam_attempt(exam_id, user_id, taking_as_proctored=exam_attempt_obj.taking_as_proctored)
|
|
1891
1895
|
|
|
1892
1896
|
|
|
1893
|
-
def
|
|
1897
|
+
def _remove_exam_attempt_from_backend(attempt):
|
|
1898
|
+
"""
|
|
1899
|
+
Ask the proctoring backend to remove ``attempt`` on the provider's side, raising
|
|
1900
|
+
``BackendProviderCannotRemoveAttempt`` (HTTP 502) if it cannot.
|
|
1901
|
+
|
|
1902
|
+
This is called BEFORE the local delete (see ``remove_exam_attempt``). Doing the
|
|
1903
|
+
provider call outside the ``pre_delete`` signal means a provider outage can raise a
|
|
1904
|
+
descriptive error without the local delete being half-applied: raising from inside
|
|
1905
|
+
``delete()``'s ``pre_delete`` marks the DB connection needs-rollback, which then makes
|
|
1906
|
+
the surrounding request (and any retry) fail with ``TransactionManagementError``.
|
|
1907
|
+
|
|
1908
|
+
An attempt already gone upstream (provider 404) counts as success, so the local delete
|
|
1909
|
+
proceeds and a retry converges. Bulk, best-effort callers (onboarding cleanup, the
|
|
1910
|
+
reset_attempts command) should use ``_remove_exam_attempts_from_backend`` instead, which
|
|
1911
|
+
swallows these errors and skips a failing backend for the rest of the pass.
|
|
1912
|
+
"""
|
|
1913
|
+
exam = attempt.proctored_exam
|
|
1914
|
+
if not exam.is_proctored:
|
|
1915
|
+
return
|
|
1916
|
+
if not attempt.external_id:
|
|
1917
|
+
# The attempt was never registered with the provider; nothing to remove upstream.
|
|
1918
|
+
return
|
|
1919
|
+
# Single instructor-facing message for any provider-side removal failure. The specific
|
|
1920
|
+
# cause is logged (below), never surfaced to the user.
|
|
1921
|
+
# Translators: shown to an instructor when an exam attempt could not be reset because
|
|
1922
|
+
# the external proctoring provider is temporarily unavailable.
|
|
1923
|
+
unavailable_message = _(
|
|
1924
|
+
'The proctoring provider is temporarily unavailable, so this attempt could not '
|
|
1925
|
+
'be fully reset. Please try again in a few minutes.'
|
|
1926
|
+
)
|
|
1927
|
+
try:
|
|
1928
|
+
# Resolving the backend can itself raise (e.g. NotImplementedError for a
|
|
1929
|
+
# stale/unconfigured backend), so keep it inside the error-handling block.
|
|
1930
|
+
backend = get_backend_provider(name=exam.backend)
|
|
1931
|
+
if not backend:
|
|
1932
|
+
return
|
|
1933
|
+
result = backend.remove_exam_attempt(exam.external_id, attempt.external_id)
|
|
1934
|
+
except Exception as exc:
|
|
1935
|
+
log.exception(
|
|
1936
|
+
'Failed to remove attempt_id=%(attempt_id)s (external_id=%(external_id)s) for '
|
|
1937
|
+
'exam_id=%(exam_id)s user_id=%(user_id)s from backend=%(backend)s due to a provider error.',
|
|
1938
|
+
{
|
|
1939
|
+
'attempt_id': attempt.id,
|
|
1940
|
+
'external_id': attempt.external_id,
|
|
1941
|
+
'exam_id': exam.id,
|
|
1942
|
+
'user_id': attempt.user_id,
|
|
1943
|
+
'backend': exam.backend,
|
|
1944
|
+
}
|
|
1945
|
+
)
|
|
1946
|
+
raise BackendProviderCannotRemoveAttempt(unavailable_message) from exc
|
|
1947
|
+
if not result:
|
|
1948
|
+
# The provider responded but did not confirm removal, so keep the local attempt: a
|
|
1949
|
+
# retry can converge once the provider is healthy instead of silently dropping it.
|
|
1950
|
+
# This is attempt-specific -- the provider answered -- so it is raised as the more
|
|
1951
|
+
# specific subclass, letting bulk callers keep using the same backend.
|
|
1952
|
+
log.warning('Backend %s did not confirm removal of attempt_id=%s.', exam.backend, attempt.id)
|
|
1953
|
+
raise BackendProviderRemovalNotConfirmed(unavailable_message)
|
|
1954
|
+
|
|
1955
|
+
|
|
1956
|
+
def _remove_exam_attempts_from_backend(attempts, failed_backends=frozenset()):
|
|
1957
|
+
"""
|
|
1958
|
+
Best-effort provider-side removal for a collection of attempts.
|
|
1959
|
+
|
|
1960
|
+
Provider failures are swallowed (the caller is expected to delete the attempts locally
|
|
1961
|
+
regardless). Once a backend proves *unreachable* we stop calling it for the rest of this
|
|
1962
|
+
pass -- while still trying other, independently configured backends -- so a provider
|
|
1963
|
+
outage does not cost a full request timeout for every attempt. A reachable provider that
|
|
1964
|
+
merely fails to confirm one removal is not skipped, since the remaining attempts on it
|
|
1965
|
+
are still worth attempting.
|
|
1966
|
+
|
|
1967
|
+
Returns the set of backends that failed, so a caller working through batches can thread
|
|
1968
|
+
it back in and keep skipping them across calls.
|
|
1969
|
+
|
|
1970
|
+
Pass a queryset with ``select_related('proctored_exam')`` to avoid a query per attempt.
|
|
1971
|
+
"""
|
|
1972
|
+
# Fresh mutable set per call; the shared default is only ever copied from, never mutated.
|
|
1973
|
+
failed = set(failed_backends)
|
|
1974
|
+
for attempt in attempts:
|
|
1975
|
+
backend = attempt.proctored_exam.backend
|
|
1976
|
+
if backend in failed:
|
|
1977
|
+
continue
|
|
1978
|
+
try:
|
|
1979
|
+
_remove_exam_attempt_from_backend(attempt)
|
|
1980
|
+
except BackendProviderRemovalNotConfirmed:
|
|
1981
|
+
# The provider answered, it just did not confirm this one. It is still healthy,
|
|
1982
|
+
# so keep going -- skipping the rest would orphan them upstream for nothing.
|
|
1983
|
+
log.warning(
|
|
1984
|
+
'Backend %r did not confirm removal of attempt_id=%s; removing locally only.',
|
|
1985
|
+
backend, attempt.id,
|
|
1986
|
+
)
|
|
1987
|
+
except BackendProviderCannotRemoveAttempt:
|
|
1988
|
+
# The provider could not be reached at all, so stop paying its timeout for
|
|
1989
|
+
# every remaining attempt on that backend.
|
|
1990
|
+
log.warning(
|
|
1991
|
+
'Provider removal failed for backend %r; skipping further provider calls '
|
|
1992
|
+
'for it during this cleanup and removing locally only.', backend
|
|
1993
|
+
)
|
|
1994
|
+
failed.add(backend)
|
|
1995
|
+
return failed
|
|
1996
|
+
|
|
1997
|
+
|
|
1998
|
+
def remove_exam_attempt(attempt_id, requesting_user, require_backend_removal=True):
|
|
1894
1999
|
"""
|
|
1895
2000
|
Removes an exam attempt given the attempt id. requesting_user is passed through to the instructor_service.
|
|
2001
|
+
|
|
2002
|
+
The attempt is removed on the proctoring provider first. By default a provider failure
|
|
2003
|
+
raises ``BackendProviderCannotRemoveAttempt`` and the local attempt is left untouched so
|
|
2004
|
+
it can be retried. Pass ``require_backend_removal=False`` for learner-initiated resets,
|
|
2005
|
+
where the provider removal is best-effort and must not block the learner.
|
|
1896
2006
|
"""
|
|
1897
2007
|
|
|
1898
2008
|
log.info(
|
|
@@ -1916,6 +2026,15 @@ def remove_exam_attempt(attempt_id, requesting_user):
|
|
|
1916
2026
|
content_id = existing_attempt.proctored_exam.content_id
|
|
1917
2027
|
to_status = existing_attempt.status
|
|
1918
2028
|
|
|
2029
|
+
# Remove the attempt on the proctoring provider BEFORE deleting it locally, so a
|
|
2030
|
+
# provider outage surfaces a descriptive error instead of a rolled-back, half-applied
|
|
2031
|
+
# delete. See _remove_exam_attempt_from_backend for why this must not happen in the
|
|
2032
|
+
# pre_delete signal.
|
|
2033
|
+
if require_backend_removal:
|
|
2034
|
+
_remove_exam_attempt_from_backend(existing_attempt)
|
|
2035
|
+
else:
|
|
2036
|
+
_remove_exam_attempts_from_backend([existing_attempt])
|
|
2037
|
+
|
|
1919
2038
|
existing_attempt.delete_exam_attempt()
|
|
1920
2039
|
instructor_service = get_runtime_service('instructor')
|
|
1921
2040
|
grades_service = get_runtime_service('grades')
|
|
@@ -61,6 +61,12 @@ class ProctoringBackendProvider(metaclass=abc.ABCMeta):
|
|
|
61
61
|
def remove_exam_attempt(self, exam, attempt):
|
|
62
62
|
"""
|
|
63
63
|
Method that removes the exam attempt from the backend's system
|
|
64
|
+
|
|
65
|
+
Return ``True`` when the provider confirms the attempt was removed, or that it is
|
|
66
|
+
already gone upstream. Return ``False`` when the provider responded without
|
|
67
|
+
confirming the removal; the caller then keeps the local attempt so the removal can
|
|
68
|
+
be retried instead of being silently dropped. Raise to signal that the provider
|
|
69
|
+
could not be reached at all.
|
|
64
70
|
"""
|
|
65
71
|
raise NotImplementedError()
|
|
66
72
|
|
|
@@ -41,6 +41,14 @@ class BaseRestProctoringProvider(ProctoringBackendProvider):
|
|
|
41
41
|
has_dashboard = True
|
|
42
42
|
supports_onboarding = True
|
|
43
43
|
passing_statuses = (SoftwareSecureReviewStatus.clean,)
|
|
44
|
+
# Timeout (in seconds) for every outbound request to the provider, so a slow or
|
|
45
|
+
# unavailable provider cannot hang the request indefinitely. ``requests`` applies this
|
|
46
|
+
# as two separate inactivity limits -- how long to wait to connect, and how long to wait
|
|
47
|
+
# for more data -- not as a cap on total request time, so a response that keeps
|
|
48
|
+
# trickling can outlast it. A ``(connect, read)`` tuple sets those two phases
|
|
49
|
+
# independently. Overridable per backend via a ``timeout`` key in
|
|
50
|
+
# ``PROCTORING_BACKENDS``.
|
|
51
|
+
timeout = 30
|
|
44
52
|
|
|
45
53
|
@property
|
|
46
54
|
def exam_attempt_url(self):
|
|
@@ -148,7 +156,7 @@ class BaseRestProctoringProvider(ProctoringBackendProvider):
|
|
|
148
156
|
"""
|
|
149
157
|
url = self.config_url
|
|
150
158
|
log.debug('Requesting config from %r', url)
|
|
151
|
-
response = self.session.get(url, headers=self._get_language_headers()).json()
|
|
159
|
+
response = self.session.get(url, headers=self._get_language_headers(), timeout=self.timeout).json()
|
|
152
160
|
return response
|
|
153
161
|
|
|
154
162
|
def get_exam(self, exam):
|
|
@@ -157,7 +165,7 @@ class BaseRestProctoringProvider(ProctoringBackendProvider):
|
|
|
157
165
|
"""
|
|
158
166
|
url = self.exam_url.format(exam_id=exam['id'])
|
|
159
167
|
log.debug('Requesting exam from %r', url)
|
|
160
|
-
response = self.session.get(url).json()
|
|
168
|
+
response = self.session.get(url, timeout=self.timeout).json()
|
|
161
169
|
return response
|
|
162
170
|
|
|
163
171
|
def get_attempt(self, attempt):
|
|
@@ -187,7 +195,7 @@ class BaseRestProctoringProvider(ProctoringBackendProvider):
|
|
|
187
195
|
'Creating exam attempt for exam_id=%(exam_id)i (external_id=%(external_id)s) at %(url)s',
|
|
188
196
|
{'exam_id': exam['id'], 'external_id': exam['external_id'], 'url': url}
|
|
189
197
|
)
|
|
190
|
-
response = self.session.post(url, json=payload)
|
|
198
|
+
response = self.session.post(url, json=payload, timeout=self.timeout)
|
|
191
199
|
if response.status_code != 200:
|
|
192
200
|
raise BackendProviderCannotRegisterAttempt(response.content, response.status_code)
|
|
193
201
|
status_code = response.status_code
|
|
@@ -227,13 +235,29 @@ class BaseRestProctoringProvider(ProctoringBackendProvider):
|
|
|
227
235
|
|
|
228
236
|
def remove_exam_attempt(self, exam, attempt):
|
|
229
237
|
"""
|
|
230
|
-
Removes the exam attempt on the backend provider's server
|
|
238
|
+
Removes the exam attempt on the backend provider's server.
|
|
239
|
+
|
|
240
|
+
Returns ``True`` when the provider confirms removal with the documented
|
|
241
|
+
``{"status": "deleted"}`` payload, or on a ``404`` (the attempt is already gone
|
|
242
|
+
upstream, so a retried reset converges). Returns ``False`` when the provider responds
|
|
243
|
+
but does not confirm deletion (undecodable body or an unexpected status); the API
|
|
244
|
+
layer decides whether that is fatal. Any other HTTP error is raised so a transient
|
|
245
|
+
failure keeps the local attempt available for a later retry.
|
|
231
246
|
"""
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
247
|
+
if not attempt:
|
|
248
|
+
return False
|
|
249
|
+
url = self.exam_attempt_url.format(exam_id=exam, attempt_id=attempt)
|
|
250
|
+
log.debug('Removing attempt at %r', url)
|
|
251
|
+
response = self.session.request('DELETE', url, timeout=self.timeout)
|
|
252
|
+
if response.status_code == 404:
|
|
253
|
+
return True
|
|
254
|
+
response.raise_for_status()
|
|
255
|
+
try:
|
|
256
|
+
data = response.json()
|
|
257
|
+
except ValueError:
|
|
258
|
+
log.exception('Decoding attempt removal %r -> %r', attempt, response.content)
|
|
259
|
+
return False
|
|
260
|
+
return data.get('status', None) == 'deleted'
|
|
237
261
|
|
|
238
262
|
def mark_erroneous_exam_attempt(self, exam, attempt):
|
|
239
263
|
"""
|
|
@@ -272,7 +296,7 @@ class BaseRestProctoringProvider(ProctoringBackendProvider):
|
|
|
272
296
|
)
|
|
273
297
|
response = None
|
|
274
298
|
try:
|
|
275
|
-
response = self.session.post(url, json=exam)
|
|
299
|
+
response = self.session.post(url, json=exam, timeout=self.timeout)
|
|
276
300
|
data = response.json()
|
|
277
301
|
except Exception as exc: # pylint: disable=broad-exception-caught
|
|
278
302
|
if response:
|
|
@@ -327,16 +351,21 @@ class BaseRestProctoringProvider(ProctoringBackendProvider):
|
|
|
327
351
|
|
|
328
352
|
def retire_user(self, user_id):
|
|
329
353
|
url = self.user_info_url.format(user_id=user_id)
|
|
354
|
+
response = None
|
|
330
355
|
try:
|
|
331
|
-
response = self.session.delete(url)
|
|
356
|
+
response = self.session.delete(url, timeout=self.timeout)
|
|
332
357
|
data = response.json()
|
|
333
358
|
assert data in (True, False)
|
|
334
359
|
except Exception as exc:
|
|
335
360
|
# pylint: disable=no-member
|
|
336
361
|
if hasattr(exc, 'response') and exc.response is not None:
|
|
337
362
|
content = exc.response.content
|
|
338
|
-
|
|
363
|
+
elif response is not None:
|
|
339
364
|
content = response.content
|
|
365
|
+
else:
|
|
366
|
+
# The request failed before a response was received (e.g. a timeout), so
|
|
367
|
+
# there is no body to include.
|
|
368
|
+
content = None
|
|
340
369
|
raise BackendProviderCannotRetireUser(content) from exc
|
|
341
370
|
return data
|
|
342
371
|
|
|
@@ -346,7 +375,7 @@ class BaseRestProctoringProvider(ProctoringBackendProvider):
|
|
|
346
375
|
query_string = urlencode(kwargs)
|
|
347
376
|
url += '?' + query_string
|
|
348
377
|
|
|
349
|
-
response = self.session.get(url)
|
|
378
|
+
response = self.session.get(url, timeout=self.timeout)
|
|
350
379
|
|
|
351
380
|
if response.status_code != 200:
|
|
352
381
|
raise BackendProviderOnboardingProfilesException(response.content, response.status_code)
|
|
@@ -384,7 +413,7 @@ class BaseRestProctoringProvider(ProctoringBackendProvider):
|
|
|
384
413
|
if method == 'GET':
|
|
385
414
|
headers.update(self._get_language_headers())
|
|
386
415
|
log.debug('Making %r attempt request at %r', method, url)
|
|
387
|
-
response = self.session.request(method, url, json=payload, headers=headers)
|
|
416
|
+
response = self.session.request(method, url, json=payload, headers=headers, timeout=self.timeout)
|
|
388
417
|
try:
|
|
389
418
|
data = response.json()
|
|
390
419
|
except ValueError:
|