diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bcbc44d82ec..234e9d1da10 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,12 +14,24 @@ Change Log Unreleased ~~~~~~~~~~ -[3.18.0] - 2021-07-16 +[3.19.0] - 2021-07-16 ~~~~~~~~~~~~~~~~~~~~~ * Updated allowance modal to allow bulk allowances to be added. * Added waffle flag to enable/disable bulk allowances feature. -[3.17.1] - 2021-07-2 +[3.18.0] - 2021-07-15 +~~~~~~~~~~~~~~~~~~~~~ +* Remove old proctored exam attempt url. +* Fix onboarding link generation in proctored exam attempt view when exam attempt is in + onboarding errors status, don't return the link to exams that are not accessible to user. +* Update onboarding link url in student onboarding status view to link + to the learning mfe page instead of LMS. + +[3.17.3] - 2021-07-14 +~~~~~~~~~~~~~~~~~~~~~ +* Add missing get_proctoring_config method to base backend provider class. + +[3.17.2] - 2021-07-2 ~~~~~~~~~~~~~~~~~~~~~ * Updated ProctoredExamAttempt view to use the content id from the query. diff --git a/edx_proctoring/admin.py b/edx_proctoring/admin.py index 0c7c693ff6b..ddf9d8405f7 100644 --- a/edx_proctoring/admin.py +++ b/edx_proctoring/admin.py @@ -308,12 +308,12 @@ def has_delete_permission(self, request, obj=None): """ Allow deletes """ return True - def save_model(self, request, review, form, change): # pylint: disable=arguments-differ + def save_model(self, request, obj, form, change): """ Override callback so that we can inject the user_id that made the change """ - review.reviewed_by = request.user - review.save() + obj.reviewed_by = request.user + obj.save() def get_form(self, request, obj=None, change=False, **kwargs): """ Returns software secure review form """ @@ -322,11 +322,11 @@ def get_form(self, request, obj=None, change=False, **kwargs): del form.base_fields['video_url'] return form - def lookup_allowed(self, key, value): # pylint: disable=arguments-differ + def lookup_allowed(self, lookup, value): """ Checks if lookup allowed or not """ - if key == 'exam__course_id': + if lookup == 'exam__course_id': return True - return super().lookup_allowed(key, value) + return super().lookup_allowed(lookup, value) class ProctoredExamSoftwareSecureReviewHistoryAdmin(ProctoredExamSoftwareSecureReviewAdmin): @@ -344,7 +344,7 @@ class ProctoredExamSoftwareSecureReviewHistoryAdmin(ProctoredExamSoftwareSecureR 'modified', ] - def save_model(self, request, review, form, change): + def save_model(self, request, obj, form, change): # pylint: disable=unused-argument """ History can't be updated """ diff --git a/edx_proctoring/api.py b/edx_proctoring/api.py index 5366e7f1936..2693de0736c 100644 --- a/edx_proctoring/api.py +++ b/edx_proctoring/api.py @@ -58,15 +58,17 @@ ) from edx_proctoring.statuses import InstructorDashboardOnboardingAttemptStatus, ProctoredExamStudentAttemptStatus from edx_proctoring.utils import ( + categorize_inaccessible_exams_by_date, emit_event, get_exam_due_date, get_exam_type, + get_exam_url, get_time_remaining_for_attempt, + get_user_course_outline_details, has_due_date_passed, humanized_time, is_reattempting_exam, obscured_user_id, - resolve_exam_url_for_learning_mfe, verify_and_add_wait_deadline ) @@ -81,6 +83,38 @@ USER_MODEL = get_user_model() +def get_onboarding_exam_link(course_id, user, is_learning_mfe): + """ + Get link to the onboarding exam available to the user for given course. + + Parameters: + * course_id: id of a course where search for the onboarding exam is performed + * user: user for whom the link is built + * is_learning_mfe: Boolean, indicates if the url should be built for the learning mfe application. + + Returns: url pointing to the available exam, if there is no onboarding exam available + to the user returns empty string. + """ + onboarding_exams = list(ProctoredExam.get_practice_proctored_exams_for_course(course_id).order_by('-created')) + if not onboarding_exams or not get_backend_provider(name=onboarding_exams[0].backend).supports_onboarding: + onboarding_link = '' + else: + details = get_user_course_outline_details(user, course_id) + categorized_exams = categorize_inaccessible_exams_by_date(onboarding_exams, details) + non_date_inaccessible_exams = categorized_exams[0] + + # remove onboarding exams not accessible to learners + for onboarding_exam in non_date_inaccessible_exams: + onboarding_exams.remove(onboarding_exam) + + if onboarding_exams: + onboarding_exam = onboarding_exams[0] + onboarding_link = get_exam_url(course_id, onboarding_exam.content_id, is_learning_mfe) + else: + onboarding_link = '' + return onboarding_link + + def get_total_allowed_time_for_exam(exam, user_id): """ Returns total allowed time in humanized form for exam including allowance time. @@ -752,10 +786,7 @@ def get_exam_attempt_data(exam_id, attempt_id, is_learning_mfe=False): # resolve the LMS url, note we can't assume we're running in # a same process as the LMS - if is_learning_mfe: - exam_url_path = resolve_exam_url_for_learning_mfe(exam['course_id'], exam['content_id']) - else: - exam_url_path = reverse('jump_to', args=[exam['course_id'], exam['content_id']]) + exam_url_path = get_exam_url(exam['course_id'], exam['content_id'], is_learning_mfe) attempt_data = { 'in_timed_exam': True, diff --git a/edx_proctoring/backends/backend.py b/edx_proctoring/backends/backend.py index e71479b5263..54baeb45f00 100644 --- a/edx_proctoring/backends/backend.py +++ b/edx_proctoring/backends/backend.py @@ -139,3 +139,9 @@ def get_onboarding_profile_info(self, course_id, **kwargs): Returns onboarding profile information for a given course and optional user """ return None + + def get_proctoring_config(self): + """ + Returns the metadata and configuration options for the proctoring service + """ + return None diff --git a/edx_proctoring/backends/rest.py b/edx_proctoring/backends/rest.py index ecafd28c00b..3de639cd625 100644 --- a/edx_proctoring/backends/rest.py +++ b/edx_proctoring/backends/rest.py @@ -104,7 +104,7 @@ def get_javascript(self): Returns the url of the javascript bundle into which the provider's JS will be loaded """ # use the defined npm_module name, or else the python package name - package = getattr(self, 'npm_module', self.__class__.__module__.split('.')[0]) + package = getattr(self, 'npm_module', self.__class__.__module__.split('.', maxsplit=1)[0]) js_url = '' try: bundle_chunks = get_files(package, config="WORKERS") diff --git a/edx_proctoring/backends/software_secure.py b/edx_proctoring/backends/software_secure.py index 0409c835975..a265eae53dd 100644 --- a/edx_proctoring/backends/software_secure.py +++ b/edx_proctoring/backends/software_secure.py @@ -390,3 +390,15 @@ def should_block_access_to_exam_material(self): req = get_current_request() # pylint: disable=illegal-waffle-usage return not req.get_signed_cookie('exam', default=False) + + def get_proctoring_config(self): + """ + Returns the metadata and configuration options for the proctoring service + """ + proctoring_config = { + 'download_url': self.get_software_download_url(), + 'name': self.verbose_name, + 'rules': {}, + 'instructions': [] + } + return proctoring_config diff --git a/edx_proctoring/backends/tests/test_backend.py b/edx_proctoring/backends/tests/test_backend.py index 6392a73f3b2..2674d394967 100644 --- a/edx_proctoring/backends/tests/test_backend.py +++ b/edx_proctoring/backends/tests/test_backend.py @@ -226,6 +226,8 @@ def test_raises_exception(self): self.assertIsNone(provider.get_onboarding_profile_info(course_id='test')) + self.assertIsNone(provider.get_proctoring_config()) + def test_null_provider(self): """ Assert that the Null provider does nothing diff --git a/edx_proctoring/backends/tests/test_software_secure.py b/edx_proctoring/backends/tests/test_software_secure.py index 1cc6fb0fc7b..3eaaf07a9e6 100644 --- a/edx_proctoring/backends/tests/test_software_secure.py +++ b/edx_proctoring/backends/tests/test_software_secure.py @@ -125,6 +125,17 @@ def test_get_software_download_url(self): provider = get_backend_provider() self.assertEqual(provider.get_software_download_url(), 'http://example.com') + def test_get_proctoring_config(self): + """ + Makes sure proctoring config is returned + """ + + provider = get_backend_provider() + config = provider.get_proctoring_config() + self.assertIsNotNone(config) + self.assertEqual(config['name'], provider.verbose_name) + self.assertEqual(config['download_url'], 'http://example.com') + def test_register_attempt(self): """ Makes sure we can register an attempt diff --git a/edx_proctoring/tests/test_mfe_views.py b/edx_proctoring/tests/test_mfe_views.py index 4a2b74e0d2b..a8c369edfa1 100644 --- a/edx_proctoring/tests/test_mfe_views.py +++ b/edx_proctoring/tests/test_mfe_views.py @@ -7,17 +7,21 @@ import ddt from mock import patch +from opaque_keys.edx.locator import BlockUsageLocator from django.conf import settings from django.test.utils import override_settings from django.urls import reverse +from django.utils import timezone from edx_proctoring.api import get_exam_by_id, get_review_policy_by_exam_id from edx_proctoring.exceptions import BackendProviderNotConfigured, ProctoredExamNotFoundException from edx_proctoring.models import ProctoredExam, ProctoredExamStudentAllowance +from edx_proctoring.runtime import set_runtime_service from edx_proctoring.statuses import ProctoredExamStudentAttemptStatus from edx_proctoring.utils import humanized_time +from .test_services import MockLearningSequencesService, MockScheduleItemData from .utils import ProctoredExamTestCase @@ -48,6 +52,18 @@ def setUp(self): self.expected_exam_url = '{}/course/{}/{}'.format( settings.LEARNING_MICROFRONTEND_URL, self.course_id, self.content_id ) + yesterday = timezone.now() - timezone.timedelta(days=1) + self.course_scheduled_sections = { + BlockUsageLocator.from_string(self.content_id_onboarding): MockScheduleItemData(yesterday), + BlockUsageLocator.from_string( + 'block-v1:test+course+1+type@sequential+block@assignment' + ): MockScheduleItemData(yesterday), + } + + set_runtime_service('learning_sequences', MockLearningSequencesService( + list(self.course_scheduled_sections.keys()), + self.course_scheduled_sections, + )) def assertHasExamData(self, response_data, has_attempt, has_verification_url=False, has_download_url=False, content_id=None): @@ -286,9 +302,7 @@ def test_exam_data_contains_link_to_onboarding_exam_if_attempt_in_onboarding_err """ self._create_exam_attempt(self.proctored_exam_id, status=status) - onboarding_exam = ProctoredExam.objects.filter( - course_id=self.course_id, is_active=True, is_practice_exam=True - ).first() + onboarding_exam = ProctoredExam.objects.get(id=self.onboarding_exam_id) if is_learning_mfe: expected_exam_url = '{}/course/{}/{}'.format( @@ -332,7 +346,35 @@ def test_exam_data_does_not_fail_if_onboarding_errors_and_no_onboarding_exam(sel response = self.client.get(url) response_data = json.loads(response.content.decode('utf-8')) exam = response_data['exam'] - assert 'onboarding_link' not in exam + assert 'onboarding_link' in exam + self.assertEqual(exam['onboarding_link'], '') + + def test_onboarding_errors_and_onboarding_exam_not_available(self): + """ + Tests the GET exam attempts data not contain link to onboarding exam if + when user tries to take proctored exam and has not yet completed required + onboarding exam and onboarding exams are not available for the user. + """ + self._create_exam_attempt(self.proctored_exam_id, status=ProctoredExamStudentAttemptStatus.onboarding_missing) + set_runtime_service('learning_sequences', MockLearningSequencesService( + [], # sections user can see (none) + self.course_scheduled_sections, # all scheduled sections + )) + url = reverse( + 'edx_proctoring:proctored_exam.exam_attempts', + kwargs={ + 'course_id': self.course_id, + } + ) + '?' + urlencode({ + 'content_id': self.content_id, + 'is_learning_mfe': True, + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + response_data = json.loads(response.content.decode('utf-8')) + exam = response_data['exam'] + assert 'onboarding_link' in exam + self.assertEqual(exam['onboarding_link'], '') class ProctoredSettingsViewTests(ProctoredExamTestCase): diff --git a/edx_proctoring/tests/test_views.py b/edx_proctoring/tests/test_views.py index 852089c658c..457c7567af5 100644 --- a/edx_proctoring/tests/test_views.py +++ b/edx_proctoring/tests/test_views.py @@ -55,7 +55,7 @@ ) from edx_proctoring.tests import mock_perm from edx_proctoring.urls import urlpatterns -from edx_proctoring.utils import obscured_user_id +from edx_proctoring.utils import obscured_user_id, resolve_exam_url_for_learning_mfe from edx_proctoring.views import require_course_or_global_staff, require_staff from mock_apps.models import Profile @@ -551,6 +551,22 @@ def test_no_onboarding_exam(self): message = 'There is no onboarding exam related to this course id.' self.assertEqual(response_data['detail'], message) + @override_settings(LEARNING_MICROFRONTEND_URL='https://learningmfe') + def test_onboarding_mfe_link(self): + """ + Test that the request returns correct link to onboarding exam for learning mfe application. + """ + response = self.client.get( + reverse('edx_proctoring:user_onboarding.status') + + '?course_id={}&is_learning_mfe=True'.format(self.course_id) + ) + self.assertEqual(response.status_code, 200) + response_data = json.loads(response.content.decode('utf-8')) + self.assertEqual( + response_data['onboarding_link'], + resolve_exam_url_for_learning_mfe(self.course_id, self.onboarding_exam.content_id) + ) + def test_no_exam_attempts(self): """ Test that the onboarding status is None if there are no exam attempts diff --git a/edx_proctoring/urls.py b/edx_proctoring/urls.py index fb3bf3bfcac..24e7e7ba09a 100644 --- a/edx_proctoring/urls.py +++ b/edx_proctoring/urls.py @@ -131,13 +131,6 @@ views.ProctoredExamAttemptView.as_view(), name='proctored_exam.exam_attempts' ), - # TODO: remove url after updating frontend-lib-special-exams - url( - r'edx_proctoring/v1/proctored_exam/attempt/course_id/{}/content_id/{}$'.format( - settings.COURSE_ID_PATTERN, CONTENT_ID_PATTERN), - views.ProctoredExamAttemptView.as_view(), - name='proctored_exam.exam_attempts_old' - ), url( r'edx_proctoring/v1/proctored_exam/settings/exam_id/(?P\d+)/$', views.ProctoredSettingsView.as_view(), diff --git a/edx_proctoring/utils.py b/edx_proctoring/utils.py index 62c75703da1..e664de4515a 100644 --- a/edx_proctoring/utils.py +++ b/edx_proctoring/utils.py @@ -13,14 +13,17 @@ from eventtracking import tracker from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey, UsageKey +from opaque_keys.edx.locator import BlockUsageLocator from rest_framework.authentication import SessionAuthentication from rest_framework.permissions import IsAuthenticated from rest_framework.views import APIView from django.conf import settings +from django.urls import reverse from django.utils.translation import ugettext as _ from edx_proctoring.models import ProctoredExamStudentAttempt, ProctoredExamStudentAttemptHistory +from edx_proctoring.runtime import get_runtime_service from edx_proctoring.statuses import ProctoredExamStudentAttemptStatus log = logging.getLogger(__name__) @@ -329,3 +332,66 @@ def resolve_exam_url_for_learning_mfe(course_id, content_id): usage_key = UsageKey.from_string(content_id) url = '{}/course/{}/{}'.format(settings.LEARNING_MICROFRONTEND_URL, course_key, usage_key) return url + + +def get_exam_url(course_id, content_id, is_learning_mfe): + """ Helper to build exam url depending if it is requested for the learning MFE app or not. """ + if is_learning_mfe: + return resolve_exam_url_for_learning_mfe(course_id, content_id) + return reverse('jump_to', args=[course_id, content_id]) + + +def get_user_course_outline_details(user, course_id): + """ Helper to get user's course outline details """ + learning_sequences_service = get_runtime_service('learning_sequences') + course_key = CourseKey.from_string(course_id) + details = learning_sequences_service.get_user_course_outline_details( + course_key, user, pytz.utc.localize(datetime.now()) + ) + return details + + +def categorize_inaccessible_exams_by_date(onboarding_exams, details): + """ + Categorize a list of inaccessible onboarding exams based on whether they are + inaccessible because they are in the future, because they are in the past, or because + they are inaccessible for reason unrelated to the exam schedule (e.g. visibility settings, + content gating, etc.) + + Parameters: + * onboarding_exams: a list of onboarding exams + * details: a UserCourseOutlineData returned by the learning sequences API + + Returns: a tuple containing three lists + * non_date_inaccessible_exams: a list of onboarding exams not accessible to the learner for + reasons other than the exam schedule + * future_exams: a list of onboarding exams not accessible to the learner because the exams are released + in the future + * past_due_exams: a list of onboarding exams not accessible to the learner because the exams are past their + due date + """ + non_date_inaccessible_exams = [] + future_exams = [] + past_due_exams = [] + + for onboarding_exam in onboarding_exams: + usage_key = BlockUsageLocator.from_string(onboarding_exam.content_id) + if usage_key not in details.outline.accessible_sequences: + sequence_schedule = details.schedule.sequences.get(usage_key) + + if sequence_schedule: + effective_start = details.schedule.sequences.get(usage_key).effective_start + due_date = get_visibility_check_date(details.schedule, usage_key) + + if effective_start and pytz.utc.localize(datetime.now()) < effective_start: + future_exams.append(onboarding_exam) + elif due_date and pytz.utc.localize(datetime.now()) > due_date: + past_due_exams.append(onboarding_exam) + else: + non_date_inaccessible_exams.append(onboarding_exam) + else: + # if the sequence schedule is not available, then the sequence is not available + # to the learner + non_date_inaccessible_exams.append(onboarding_exam) + + return non_date_inaccessible_exams, future_exams, past_due_exams diff --git a/edx_proctoring/views.py b/edx_proctoring/views.py index d185229e194..bd1086deafb 100644 --- a/edx_proctoring/views.py +++ b/edx_proctoring/views.py @@ -4,14 +4,11 @@ import json import logging -from datetime import datetime from urllib.parse import urlencode -import pytz import waffle # pylint: disable=invalid-django-waffle-import from crum import get_current_request from edx_django_utils.monitoring import set_custom_attribute -from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import BlockUsageLocator from rest_framework import status from rest_framework.negotiation import BaseContentNegotiation @@ -47,6 +44,7 @@ get_exam_by_id, get_last_verified_onboarding_attempts_per_user, get_onboarding_attempt_data_for_learner, + get_onboarding_exam_link, get_proctoring_settings_by_exam_id, get_review_policy_by_exam_id, get_total_allowed_time_for_exam, @@ -101,13 +99,14 @@ ) from edx_proctoring.utils import ( AuthenticatedAPIView, + categorize_inaccessible_exams_by_date, get_exam_type, + get_exam_url, get_time_remaining_for_attempt, - get_visibility_check_date, + get_user_course_outline_details, humanized_time, locate_attempt_by_attempt_code, - obscured_user_id, - resolve_exam_url_for_learning_mfe + obscured_user_id ) ATTEMPTS_PER_PAGE = 25 @@ -275,18 +274,7 @@ def get(self, request, course_id, content_id=None): # proctored exam we need to navigate them to it with a link if (exam['is_proctored'] and attempt_data and attempt_data['attempt_status'] in ProctoredExamStudentAttemptStatus.onboarding_errors): - onboarding_exam = ProctoredExam.objects.filter( - course_id=course_id, is_active=True, is_practice_exam=True - ).first() - if onboarding_exam: - if is_learning_mfe: - onboarding_link = resolve_exam_url_for_learning_mfe( - course_id, - onboarding_exam.content_id - ) - else: - onboarding_link = reverse('jump_to', args=[course_id, onboarding_exam.content_id]) - exam['onboarding_link'] = onboarding_link + exam['onboarding_link'] = get_onboarding_exam_link(course_id, request.user, is_learning_mfe) exam.update({'attempt': attempt_data}) except ProctoredExamNotFoundException: exam = {} @@ -496,12 +484,13 @@ class StudentOnboardingStatusView(ProctoredAPIView): HTTP GET: returns the learner's onboarding status relative to the given course_id HTTP GET - /edx_proctoring/v1/user_onboarding/status?course_id={course_id}&username={username} + /edx_proctoring/v1/user_onboarding/status?course_id={course_id}&username={username}&is_learning_mfe={} **Query Parameters** * 'course_id': The unique identifier for the course. * 'username': Optional. If not given, the endpoint will return the user's own status. ** In order to view other users' statuses, the user must be course or global staff. + * 'is_learning_mfe': Optional. If set to True, onboarding link will point to the learning mfe application page. **Response Values** * 'onboarding_status': String specifying the learner's onboarding status. @@ -526,6 +515,7 @@ def get(self, request): # pylint: disable=too-many-statements username = request.GET.get('username') course_id = request.GET.get('course_id') + is_learning_mfe = request.GET.get('is_learning_mfe') in ['1', 'true', 'True'] if not course_id: # This parameter is currently required, as the onboarding experience is tied @@ -553,20 +543,16 @@ def get(self, request): # pylint: disable=too-many-statements data={'detail': _('There is no onboarding exam related to this course id.')} ) + # If there are multiple onboarding exams, use the first exam accessible to the user backend = get_backend_provider(name=onboarding_exams[0].backend) - # If there are multiple onboarding exams, use the first exam accessible to the user - learning_sequences_service = get_runtime_service('learning_sequences') - course_key = CourseKey.from_string(course_id) user = get_user_model().objects.get(username=(username or request.user.username)) - details = learning_sequences_service.get_user_course_outline_details( - course_key, user, pytz.utc.localize(datetime.now()) - ) + details = get_user_course_outline_details(user, course_id) # in order to gather information about future or past due exams, we must determine which exams # are inaccessible for other reasons (e.g. visibility settings, content gating, etc.), so that # we can correctly filter them out of the onboarding exams in the course - categorized_exams = self._categorize_inaccessible_exams_by_date(onboarding_exams, details) + categorized_exams = categorize_inaccessible_exams_by_date(onboarding_exams, details) (non_date_inaccessible_exams, future_exams, past_due_exams) = categorized_exams # remove onboarding exams not accessible to learners @@ -593,7 +579,7 @@ def get(self, request): # pylint: disable=too-many-statements if currently_available_exams: onboarding_exam = currently_available_exams[0] - data['onboarding_link'] = reverse('jump_to', args=[course_id, onboarding_exam.content_id]) + data['onboarding_link'] = get_exam_url(course_id, onboarding_exam.content_id, is_learning_mfe) elif future_exams: onboarding_exam = future_exams[0] else: @@ -669,52 +655,6 @@ def get(self, request): # pylint: disable=too-many-statements return Response(data) - def _categorize_inaccessible_exams_by_date(self, onboarding_exams, details): - """ - Categorize a list of inaccessible onboarding exams based on whether they are - inaccessible because they are in the future, because they are in the past, or because - they are inaccessible for reason unrelated to the exam schedule (e.g. visibility settings, - content gating, etc.) - - Parameters: - * onboarding_exams: a list of onboarding exams - * details: a UserCourseOutlineData returned by the learning sequences API - - Returns: a tuple containing three lists - * non_date_inaccessible_exams: a list of onboarding exams not accesible to the learner for - reasons other than the exam schedule - * future_exams: a list of onboarding exams not accessible to the learner because the exams are released - in the future - * past_due_exams: a list of onboarding exams not accessible to the learner because the exams are past their - due date - """ - non_date_inaccessible_exams = [] - future_exams = [] - past_due_exams = [] - - for onboarding_exam in onboarding_exams: - usage_key = BlockUsageLocator.from_string(onboarding_exam.content_id) - - if usage_key not in details.outline.accessible_sequences: - sequence_schedule = details.schedule.sequences.get(usage_key) - - if sequence_schedule: - effective_start = details.schedule.sequences.get(usage_key).effective_start - due_date = get_visibility_check_date(details.schedule, usage_key) - - if effective_start and pytz.utc.localize(datetime.now()) < effective_start: - future_exams.append(onboarding_exam) - elif due_date and pytz.utc.localize(datetime.now()) > due_date: - past_due_exams.append(onboarding_exam) - else: - non_date_inaccessible_exams.append(onboarding_exam) - else: - # if the sequence schedule is not available, then the sequence is not available - # to the learner - non_date_inaccessible_exams.append(onboarding_exam) - - return non_date_inaccessible_exams, future_exams, past_due_exams - class StudentOnboardingStatusByCourseView(ProctoredAPIView): """ diff --git a/requirements/base.txt b/requirements/base.txt index d142afd15f4..263ea8ca280 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # make upgrade @@ -20,12 +20,6 @@ cffi==1.14.5 # via cryptography chardet==4.0.0 # via requests -click-didyoumean==0.0.3 - # via celery -click-plugins==1.1.1 - # via celery -click-repl==0.2.0 - # via celery click==7.1.2 # via # -c requirements/constraints.txt @@ -33,8 +27,28 @@ click==7.1.2 # click-didyoumean # click-plugins # click-repl +click-didyoumean==0.0.3 + # via celery +click-plugins==1.1.1 + # via celery +click-repl==0.2.0 + # via celery cryptography==3.4.7 # via pyjwt +django==2.2.24 + # via + # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt + # -r requirements/base.in + # django-crum + # django-model-utils + # djangorestframework + # drf-jwt + # edx-django-utils + # edx-drf-extensions + # edx-when + # event-tracking + # jsonfield2 + # rest-condition django-crum==0.7.9 # via # -r requirements/base.in @@ -50,22 +64,10 @@ django-waffle==2.2.0 # -r requirements/base.in # edx-django-utils # edx-drf-extensions -django-webpack-loader==1.0.0 - # via -r requirements/base.in -django==2.2.24 +django-webpack-loader==0.7.0 # via - # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt + # -c requirements/constraints.txt # -r requirements/base.in - # django-crum - # django-model-utils - # djangorestframework - # drf-jwt - # edx-django-utils - # edx-drf-extensions - # edx-when - # event-tracking - # jsonfield2 - # rest-condition djangorestframework==3.12.4 # via # -r requirements/base.in @@ -85,7 +87,7 @@ edx-drf-extensions==6.5.0 # via # -r requirements/base.in # edx-when -edx-opaque-keys==2.2.1 +edx-opaque-keys==2.2.2 # via # -r requirements/base.in # edx-drf-extensions @@ -112,11 +114,11 @@ lxml==4.6.3 # via xblock markupsafe==2.0.1 # via xblock -newrelic==6.4.1.158 +newrelic==6.4.4.161 # via edx-django-utils pbr==5.6.0 # via stevedore -prompt-toolkit==3.0.18 +prompt-toolkit==3.0.19 # via click-repl psutil==5.8.0 # via edx-django-utils @@ -181,7 +183,7 @@ stevedore==3.3.0 # via # edx-django-utils # edx-opaque-keys -urllib3==1.26.5 +urllib3==1.26.6 # via requests vine==5.0.0 # via diff --git a/requirements/celery50.txt b/requirements/celery50.txt index 50985f8960c..4ba0a37dd3a 100644 --- a/requirements/celery50.txt +++ b/requirements/celery50.txt @@ -1,9 +1,9 @@ amqp==5.0.6 billiard==3.6.4.0 celery==5.0.4 +click==7.1.2 click-didyoumean==0.0.3 click-repl==0.2.0 -click==7.1.2 kombu==5.1.0 -prompt-toolkit==3.0.18 +prompt-toolkit==3.0.19 vine==5.0.0 diff --git a/requirements/ci.txt b/requirements/ci.txt index 5c413a5cc69..a19923ab517 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # make upgrade @@ -22,7 +22,7 @@ filelock==3.0.12 # virtualenv idna==2.10 # via requests -packaging==20.9 +packaging==21.0 # via tox pluggy==0.13.1 # via tox @@ -38,13 +38,13 @@ six==1.16.0 # virtualenv toml==0.10.2 # via tox -tox-battery==0.6.1 - # via -r requirements/ci.in tox==3.23.1 # via # -r requirements/ci.in # tox-battery -urllib3==1.26.5 +tox-battery==0.6.1 + # via -r requirements/ci.in +urllib3==1.26.6 # via requests virtualenv==20.4.7 # via tox diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 2c273a9b131..e06f065ba8a 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -18,3 +18,6 @@ jsonfield2==3.0.3 celery==5.0.4 # celery latest version requires click<8.0.0 click<8.0.0 + +# pinning because to match pin in edx-platform: https://github.com/edx/edx-platform/blob/e25f00f35cb2ed70502bb0b28909535d55e5525e/requirements/constraints.txt#L83 +django-webpack-loader<1.0.0 diff --git a/requirements/dev.txt b/requirements/dev.txt index 31542846361..c5bc75ca938 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,12 +1,12 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # make upgrade # appdirs==1.4.4 # via virtualenv -astroid==2.5.6 +astroid==2.6.2 # via # pylint # pylint-celery @@ -18,8 +18,6 @@ chardet==4.0.0 # via # diff-cover # requests -click-log==0.3.2 - # via edx-lint click==7.1.2 # via # -c requirements/constraints.txt @@ -27,11 +25,13 @@ click==7.1.2 # code-annotations # edx-lint # pip-tools +click-log==0.3.2 + # via edx-lint code-annotations==1.1.2 # via edx-lint colorama==0.4.4 # via twine -diff-cover==5.1.2 +diff-cover==6.0.0 # via -r requirements/dev.in distlib==0.3.2 # via virtualenv @@ -42,9 +42,8 @@ django==2.2.24 # -r requirements/quality.in # edx-i18n-tools # edx-lint -docutils==0.16 +docutils==0.17.1 # via - # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # readme-renderer # rstcheck edx-i18n-tools==0.5.3 @@ -59,23 +58,23 @@ filelock==3.0.12 # virtualenv idna==2.10 # via requests -importlib-metadata==4.5.0 +importlib-metadata==4.6.1 # via # keyring # twine inflect==5.3.0 # via jinja2-pluralize -isort==5.8.0 +isort==5.9.2 # via # -r requirements/quality.in # pylint -jinja2-pluralize==0.3.0 - # via diff-cover jinja2==3.0.1 # via # code-annotations # diff-cover # jinja2-pluralize +jinja2-pluralize==0.3.0 + # via diff-cover keyring==23.0.1 # via twine lazy-object-proxy==1.6.0 @@ -84,21 +83,21 @@ markupsafe==2.0.1 # via jinja2 mccabe==0.6.1 # via pylint -packaging==20.9 +packaging==21.0 # via # bleach # tox +path==16.0.0 + # via path.py path.py==12.5.0 # via # -r requirements/dev.in # edx-i18n-tools -path==15.1.2 - # via path.py pbr==5.6.0 # via stevedore pep517==0.10.0 # via pip-tools -pip-tools==6.1.0 +pip-tools==6.2.0 # via -r requirements/dev.in pkginfo==1.7.0 # via twine @@ -118,6 +117,12 @@ pygments==2.9.0 # via # diff-cover # readme-renderer +pylint==2.9.3 + # via + # edx-lint + # pylint-celery + # pylint-django + # pylint-plugin-utils pylint-celery==0.3 # via edx-lint pylint-django==2.4.4 @@ -126,12 +131,6 @@ pylint-plugin-utils==0.6 # via # pylint-celery # pylint-django -pylint==2.8.3 - # via - # edx-lint - # pylint-celery - # pylint-django - # pylint-plugin-utils pyparsing==2.4.7 # via packaging python-slugify==5.0.2 @@ -144,12 +143,12 @@ pyyaml==5.4.1 # edx-i18n-tools readme-renderer==29.0 # via twine -requests-toolbelt==0.9.1 - # via twine requests==2.25.1 # via # requests-toolbelt # twine +requests-toolbelt==0.9.1 + # via twine rfc3986==1.5.0 # via twine rstcheck==3.3.1 @@ -175,30 +174,33 @@ toml==0.10.2 # pep517 # pylint # tox -tox-battery==0.6.1 - # via -r requirements/dev.in tox==3.23.1 # via # -r requirements/dev.in # tox-battery -tqdm==4.61.0 +tox-battery==0.6.1 + # via -r requirements/dev.in +tqdm==4.61.2 # via twine twine==3.4.1 # via # -r requirements/dev.in # -r requirements/quality.in -urllib3==1.26.5 +urllib3==1.26.6 # via requests virtualenv==20.4.7 # via tox webencodings==0.5.1 # via bleach wheel==0.36.2 - # via -r requirements/dev.in + # via + # -r requirements/dev.in + # pip-tools wrapt==1.12.1 # via astroid -zipp==3.4.1 +zipp==3.5.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: # pip +# setuptools diff --git a/requirements/doc.txt b/requirements/doc.txt index cedabef1a19..e1c7ab92b3b 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # make upgrade @@ -28,12 +28,6 @@ chardet==4.0.0 # via # doc8 # requests -click-didyoumean==0.0.3 - # via celery -click-plugins==1.1.1 - # via celery -click-repl==0.2.0 - # via celery click==7.1.2 # via # -c requirements/constraints.txt @@ -41,8 +35,28 @@ click==7.1.2 # click-didyoumean # click-plugins # click-repl +click-didyoumean==0.0.3 + # via celery +click-plugins==1.1.1 + # via celery +click-repl==0.2.0 + # via celery cryptography==3.4.7 # via pyjwt +django==2.2.24 + # via + # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt + # -r requirements/base.in + # django-crum + # django-model-utils + # djangorestframework + # drf-jwt + # edx-django-utils + # edx-drf-extensions + # edx-when + # event-tracking + # jsonfield2 + # rest-condition django-crum==0.7.9 # via # -r requirements/base.in @@ -58,22 +72,10 @@ django-waffle==2.2.0 # -r requirements/base.in # edx-django-utils # edx-drf-extensions -django-webpack-loader==1.0.0 - # via -r requirements/base.in -django==2.2.24 +django-webpack-loader==0.7.0 # via - # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt + # -c requirements/constraints.txt # -r requirements/base.in - # django-crum - # django-model-utils - # djangorestframework - # drf-jwt - # edx-django-utils - # edx-drf-extensions - # edx-when - # event-tracking - # jsonfield2 - # rest-condition djangorestframework==3.12.4 # via # -r requirements/base.in @@ -82,9 +84,8 @@ djangorestframework==3.12.4 # rest-condition doc8==0.8.1 # via -r requirements/doc.in -docutils==0.16 +docutils==0.17.1 # via - # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # doc8 # readme-renderer # restructuredtext-lint @@ -102,14 +103,14 @@ edx-drf-extensions==6.5.0 # via # -r requirements/base.in # edx-when -edx-opaque-keys==2.2.1 +edx-opaque-keys==2.2.2 # via # -r requirements/base.in # edx-drf-extensions # edx-when edx-rest-api-client==5.3.0 # via -r requirements/base.in -edx-sphinx-theme==2.1.0 +edx-sphinx-theme==3.0.0 # via -r requirements/doc.in edx-when==2.0.0 # via -r requirements/base.in @@ -137,9 +138,9 @@ markupsafe==2.0.1 # via # jinja2 # xblock -newrelic==6.4.1.158 +newrelic==6.4.4.161 # via edx-django-utils -packaging==20.9 +packaging==21.0 # via # bleach # sphinx @@ -147,7 +148,7 @@ pbr==5.6.0 # via stevedore pockets==0.9.1 # via sphinxcontrib-napoleon -prompt-toolkit==3.0.18 +prompt-toolkit==3.0.19 # via click-repl psutil==5.8.0 # via edx-django-utils @@ -227,7 +228,7 @@ slumber==0.7.1 # via edx-rest-api-client snowballstemmer==2.1.0 # via sphinx -sphinx==4.0.2 +sphinx==4.0.3 # via # -r requirements/doc.in # edx-sphinx-theme @@ -252,7 +253,7 @@ stevedore==3.3.0 # doc8 # edx-django-utils # edx-opaque-keys -urllib3==1.26.5 +urllib3==1.26.6 # via requests vine==5.0.0 # via diff --git a/requirements/pip.txt b/requirements/pip.txt index 2f010c11dd6..58aa05fbe27 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # make upgrade diff --git a/requirements/quality.txt b/requirements/quality.txt index 71ba9ff6f8b..27e16bdd37b 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -1,10 +1,10 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # make upgrade # -astroid==2.5.6 +astroid==2.6.2 # via # pylint # pylint-celery @@ -14,14 +14,14 @@ certifi==2021.5.30 # via requests chardet==4.0.0 # via requests -click-log==0.3.2 - # via edx-lint click==7.1.2 # via # -c requirements/constraints.txt # click-log # code-annotations # edx-lint +click-log==0.3.2 + # via edx-lint code-annotations==1.1.2 # via edx-lint colorama==0.4.4 @@ -31,20 +31,19 @@ django==2.2.24 # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/quality.in # edx-lint -docutils==0.16 +docutils==0.17.1 # via - # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # readme-renderer # rstcheck edx-lint==5.0.0 # via -r requirements/quality.in idna==2.10 # via requests -importlib-metadata==4.5.0 +importlib-metadata==4.6.1 # via # keyring # twine -isort==5.8.0 +isort==5.9.2 # via # -r requirements/quality.in # pylint @@ -58,7 +57,7 @@ markupsafe==2.0.1 # via jinja2 mccabe==0.6.1 # via pylint -packaging==20.9 +packaging==21.0 # via bleach pbr==5.6.0 # via stevedore @@ -70,6 +69,12 @@ pydocstyle==6.1.1 # via -r requirements/quality.in pygments==2.9.0 # via readme-renderer +pylint==2.9.3 + # via + # edx-lint + # pylint-celery + # pylint-django + # pylint-plugin-utils pylint-celery==0.3 # via edx-lint pylint-django==2.4.4 @@ -78,12 +83,6 @@ pylint-plugin-utils==0.6 # via # pylint-celery # pylint-django -pylint==2.8.3 - # via - # edx-lint - # pylint-celery - # pylint-django - # pylint-plugin-utils pyparsing==2.4.7 # via packaging python-slugify==5.0.2 @@ -94,12 +93,12 @@ pyyaml==5.4.1 # via code-annotations readme-renderer==29.0 # via twine -requests-toolbelt==0.9.1 - # via twine requests==2.25.1 # via # requests-toolbelt # twine +requests-toolbelt==0.9.1 + # via twine rfc3986==1.5.0 # via twine rstcheck==3.3.1 @@ -119,15 +118,15 @@ text-unidecode==1.3 # via python-slugify toml==0.10.2 # via pylint -tqdm==4.61.0 +tqdm==4.61.2 # via twine twine==3.4.1 # via -r requirements/quality.in -urllib3==1.26.5 +urllib3==1.26.6 # via requests webencodings==0.5.1 # via bleach wrapt==1.12.1 # via astroid -zipp==3.4.1 +zipp==3.5.0 # via importlib-metadata diff --git a/requirements/test.txt b/requirements/test.txt index d5dd5269325..51b69e2d500 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,12 +1,10 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # make upgrade # # via kombu -apipkg==1.5 - # via execnet appdirs==1.4.4 # via fs attrs==21.2.0 @@ -23,10 +21,6 @@ cffi==1.14.5 # via cryptography chardet==4.0.0 # via requests - # via celery -click-plugins==1.1.1 - # via celery - # via celery # via # -c requirements/constraints.txt # celery @@ -34,6 +28,10 @@ click-plugins==1.1.1 # click-plugins # click-repl # code-annotations + # via celery +click-plugins==1.1.1 + # via celery + # via celery code-annotations==1.1.2 # via -r requirements/test.in coverage==5.5 @@ -42,6 +40,20 @@ cryptography==3.4.7 # via pyjwt ddt==1.4.2 # via -r requirements/test.in + # via + # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt + # -r requirements/base.in + # django-crum + # django-model-utils + # djangorestframework + # drf-jwt + # edx-django-utils + # edx-drf-extensions + # edx-i18n-tools + # edx-when + # event-tracking + # jsonfield2 + # rest-condition django-crum==0.7.9 # via # -r requirements/base.in @@ -57,22 +69,10 @@ django-waffle==2.2.0 # -r requirements/base.in # edx-django-utils # edx-drf-extensions -django-webpack-loader==1.0.0 - # via -r requirements/base.in +django-webpack-loader==0.7.0 # via - # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt + # -c requirements/constraints.txt # -r requirements/base.in - # django-crum - # django-model-utils - # djangorestframework - # drf-jwt - # edx-django-utils - # edx-drf-extensions - # edx-i18n-tools - # edx-when - # event-tracking - # jsonfield2 - # rest-condition # via # -r requirements/base.in # drf-jwt @@ -93,7 +93,7 @@ edx-drf-extensions==6.5.0 # edx-when edx-i18n-tools==0.5.3 # via -r requirements/test.in -edx-opaque-keys==2.2.1 +edx-opaque-keys==2.2.2 # via # -r requirements/base.in # edx-drf-extensions @@ -104,7 +104,7 @@ edx-when==2.0.0 # via -r requirements/base.in event-tracking==1.0.4 # via -r requirements/base.in -execnet==1.8.1 +execnet==1.9.0 # via pytest-xdist freezegun==1.1.0 # via -r requirements/test.in @@ -141,14 +141,14 @@ mock==4.0.3 # via -r requirements/test.in mypy-extensions==0.4.3 # via logilab-common -newrelic==6.4.1.158 +newrelic==6.4.4.161 # via edx-django-utils -packaging==20.9 +packaging==21.0 # via pytest +path==16.0.0 + # via path.py path.py==12.5.0 # via edx-i18n-tools -path==15.1.2 - # via path.py pbr==5.6.0 # via stevedore pluggy==0.13.1 @@ -182,20 +182,20 @@ pymongo==3.11.4 # event-tracking pyparsing==2.4.7 # via packaging +pytest==6.2.4 + # via + # pytest-cov + # pytest-django + # pytest-forked + # pytest-xdist pytest-cov==2.12.1 # via -r requirements/test.in pytest-django==4.4.0 # via -r requirements/test.in pytest-forked==1.3.0 # via pytest-xdist -pytest-xdist==2.2.1 +pytest-xdist==2.3.0 # via -r requirements/test.in -pytest==6.2.4 - # via - # pytest-cov - # pytest-django - # pytest-forked - # pytest-xdist python-dateutil==2.8.1 # via # -r requirements/base.in @@ -269,7 +269,7 @@ toml==0.10.2 # pytest-cov typing-extensions==3.10.0.0 # via logilab-common -urllib3==1.26.5 +urllib3==1.26.6 # via # requests # responses