From 5f96420dd39f9fbbf1848b50fd2d722e3e50d45f Mon Sep 17 00:00:00 2001
From: Sanford Student <sstudent@edx.org>
Date: Fri, 20 Jul 2018 10:07:02 -0400
Subject: [PATCH] add validation of ORAs

---
 .../contentstore/api/tests/test_validation.py |  2 +
 .../api/views/course_validation.py            | 75 ++++++++++++++++++-
 package-lock.json                             |  6 +-
 package.json                                  |  2 +-
 4 files changed, 77 insertions(+), 8 deletions(-)

diff --git a/cms/djangoapps/contentstore/api/tests/test_validation.py b/cms/djangoapps/contentstore/api/tests/test_validation.py
index 87142cb3bec..582072e3abb 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 ef4bcc718f0..27ada22b478 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 0e1363441ac..c1658d94098 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 e99501c5e18..926816ffd56 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",
-- 
GitLab