diff --git a/cms/djangoapps/contentstore/api/tests/test_validation.py b/cms/djangoapps/contentstore/api/tests/test_validation.py index 87142cb3bec51ec4d9a9410945091cbeac0d4248..582072e3abbd8baa2f666c26cf5db2bd64e9edb4 100644 --- a/cms/djangoapps/contentstore/api/tests/test_validation.py +++ b/cms/djangoapps/contentstore/api/tests/test_validation.py @@ -81,6 +81,8 @@ class CourseValidationViewTest(SharedModuleStoreTestCase, APITestCase): 'total_visible': 1, 'assignments_with_dates_before_start': [], 'assignments_with_dates_after_end': [], + 'assignments_with_ora_dates_after_end': [], + 'assignments_with_ora_dates_before_start': [], }, 'dates': { 'has_start_date': True, diff --git a/cms/djangoapps/contentstore/api/views/course_validation.py b/cms/djangoapps/contentstore/api/views/course_validation.py index ef4bcc718f0eda4598b194a12591e83cdf3eec1b..27ada22b478f2884fcd60aa81b7e0a0846e87341 100644 --- a/cms/djangoapps/contentstore/api/views/course_validation.py +++ b/cms/djangoapps/contentstore/api/views/course_validation.py @@ -3,6 +3,9 @@ import logging from rest_framework.generics import GenericAPIView from rest_framework.response import Response +import dateutil +from pytz import UTC + from contentstore.course_info_model import get_course_updates from contentstore.views.certificates import CertificateManager from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes @@ -34,6 +37,7 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView): * certificates * updates * graded_only (boolean) - whether to included graded subsections only in the assignments information. + * validate_oras (boolean) - whether to check the dates in ORA problems in addition to assignment due dates. **GET Response Values** @@ -111,7 +115,6 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView): assignments_with_dates = [ a for a in visible_assignments if a.due ] - assignments_with_dates_before_start = ( [ {'id': unicode(a.location), 'display_name': a.display_name} @@ -138,11 +141,11 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView): for a in visible_assignments if a.due and a.graded ] - assignments_with_dates_before_start = ( [ {'id': unicode(a.location), 'display_name': a.display_name} - for a in assignments_with_dates if a.due < course.start + for a in assignments_with_dates + if a.due < course.start ] if self._has_start_date(course) else [] @@ -151,17 +154,44 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView): assignments_with_dates_after_end = ( [ {'id': unicode(a.location), 'display_name': a.display_name} - for a in assignments_with_dates if a.due > course.end + for a in assignments_with_dates + if a.due > course.end ] if course.end else [] ) + assignments_with_ora_dates_before_start = [] + assignments_with_ora_dates_after_end = [] + if get_bool_param(request, 'validate_oras', False): + # Iterate over all ORAs to find any with dates outside + # acceptable range + for ora in self._get_open_responses( + course, + get_bool_param(request, 'graded_only', False) + ): + if course.start and self._has_date_before_start(ora, course.start): + parent_unit = modulestore().get_item(ora.parent) + parent_assignment = modulestore().get_item(parent_unit.parent) + assignments_with_ora_dates_before_start.append({ + 'id': unicode(parent_assignment.location), + 'display_name': parent_assignment.display_name + }) + if course.end and self._has_date_after_end(ora, course.end): + parent_unit = modulestore().get_item(ora.parent) + parent_assignment = modulestore().get_item(parent_unit.parent) + assignments_with_ora_dates_after_end.append({ + 'id': unicode(parent_assignment.location), + 'display_name': parent_assignment.display_name + }) + return dict( total_number=len(assignments), total_visible=len(visible_assignments), assignments_with_dates_before_start=assignments_with_dates_before_start, assignments_with_dates_after_end=assignments_with_dates_after_end, + assignments_with_ora_dates_before_start=assignments_with_ora_dates_before_start, + assignments_with_ora_dates_after_end=assignments_with_ora_dates_after_end, ) def _grades_validation(self, course): @@ -207,5 +237,42 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView): ] return assignments, visible_assignments + def _get_open_responses(self, course, graded_only): + oras = modulestore().get_items(course.id, qualifiers={'category': 'openassessment'}) + return oras if not graded_only else [ora for ora in oras if ora.graded] + + def _has_date_before_start(self, ora, start): + if ora.submission_start: + if dateutil.parser.parse(ora.submission_start).replace(tzinfo=UTC) < start: + return True + if ora.submission_due: + if dateutil.parser.parse(ora.submission_due).replace(tzinfo=UTC) < start: + return True + for assessment in ora.rubric_assessments: + if assessment['start']: + if dateutil.parser.parse(assessment['start']).replace(tzinfo=UTC) < start: + return True + if assessment['due']: + if dateutil.parser.parse(assessment['due']).replace(tzinfo=UTC) < start: + return True + + return False + + def _has_date_after_end(self, ora, end): + if ora.submission_start: + if dateutil.parser.parse(ora.submission_start).replace(tzinfo=UTC) > end: + return True + if ora.submission_due: + if dateutil.parser.parse(ora.submission_due).replace(tzinfo=UTC) > end: + return True + for assessment in ora.rubric_assessments: + if assessment['start']: + if dateutil.parser.parse(assessment['start']).replace(tzinfo=UTC) > end: + return True + if assessment['due']: + if dateutil.parser.parse(assessment['due']).replace(tzinfo=UTC) > end: + return True + return False + def _has_start_date(self, course): return not course.start_date_is_still_default diff --git a/package-lock.json b/package-lock.json index 0e1363441acd63decbcdc1ceb69b082eb8233058..c1658d940984ddab76f7b070f1ab39ed9fa3ff0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -106,9 +106,9 @@ } }, "@edx/studio-frontend": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/@edx/studio-frontend/-/studio-frontend-1.16.0.tgz", - "integrity": "sha512-gZ0AZnbI+KlzXcmiSggMYcqeFqNafdIwuJPE4nPFrm93d4Zr2CydDTZEbgKswyKnKsFxEqtjz+ySP7NPSeFSzw==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@edx/studio-frontend/-/studio-frontend-1.16.1.tgz", + "integrity": "sha512-JP+C2PAvnDNurOPFMAQ5R1yKNK2YXLJIvtccsXMDQP9/lUgE1h9ZItD0dYhQM7P4tpeLqRVQ0O/lqh9A43rFDw==", "requires": { "@edx/edx-bootstrap": "1.0.0", "@edx/paragon": "3.1.2", diff --git a/package.json b/package.json index e99501c5e1891399ea57896820dac1e94669849d..926816ffd56ba6e4dabeafa1058f72e5a41ed164 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "@edx/cookie-policy-banner": "1.1.10", "@edx/edx-bootstrap": "0.4.3", "@edx/paragon": "2.6.4", - "@edx/studio-frontend": "1.16.0", + "@edx/studio-frontend": "1.16.1", "babel-core": "6.26.0", "babel-loader": "6.4.1", "babel-plugin-transform-class-properties": "6.24.1",