diff --git a/cms/djangoapps/contentstore/api/tests/test_quality.py b/cms/djangoapps/contentstore/api/tests/test_quality.py index e7dba1c4586773186b0599499111cc6116ef0589..0ef6720db9dae17811b21cbd402bc19d37761e2a 100644 --- a/cms/djangoapps/contentstore/api/tests/test_quality.py +++ b/cms/djangoapps/contentstore/api/tests/test_quality.py @@ -13,7 +13,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory class CourseQualityViewTest(SharedModuleStoreTestCase, APITestCase): """ - Test importing courses via a RESTful API (POST method only) + Test course quality view via a RESTful API """ MODULESTORE = TEST_DATA_SPLIT_MODULESTORE diff --git a/cms/djangoapps/contentstore/api/tests/test_validation.py b/cms/djangoapps/contentstore/api/tests/test_validation.py index 582072e3abbd8baa2f666c26cf5db2bd64e9edb4..87b580d7bc43a680c33cc2dd0fc99100849da74b 100644 --- a/cms/djangoapps/contentstore/api/tests/test_validation.py +++ b/cms/djangoapps/contentstore/api/tests/test_validation.py @@ -14,7 +14,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory class CourseValidationViewTest(SharedModuleStoreTestCase, APITestCase): """ - Test importing courses via a RESTful API (POST method only) + Test course validation view via a RESTful API """ MODULESTORE = TEST_DATA_SPLIT_MODULESTORE @@ -96,6 +96,7 @@ class CourseValidationViewTest(SharedModuleStoreTestCase, APITestCase): 'has_certificate': False, }, 'grades': { + 'has_grading_policy': False, 'sum_of_weights': 1.0, }, 'is_self_paced': True, diff --git a/cms/djangoapps/contentstore/api/views/course_validation.py b/cms/djangoapps/contentstore/api/views/course_validation.py index 27ada22b478f2884fcd60aa81b7e0a0846e87341..b69b414561449e52bacc41cbd0fdef20b0959aff 100644 --- a/cms/djangoapps/contentstore/api/views/course_validation.py +++ b/cms/djangoapps/contentstore/api/views/course_validation.py @@ -9,6 +9,7 @@ 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 +from xmodule.course_metadata_utils import DEFAULT_GRADING_POLICY from xmodule.modulestore.django import modulestore from .utils import get_bool_param, course_author_access_required @@ -195,8 +196,10 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView): ) def _grades_validation(self, course): + has_grading_policy = self._has_grading_policy(course) sum_of_weights = course.grader.sum_of_weights return dict( + has_grading_policy=has_grading_policy, sum_of_weights=sum_of_weights, ) @@ -276,3 +279,48 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView): def _has_start_date(self, course): return not course.start_date_is_still_default + + def _has_grading_policy(self, course): + grading_policy_formatted = {} + default_grading_policy_formatted = {} + + for grader, assignment_type, weight in course.grader.subgraders: + grading_policy_formatted[assignment_type] = { + 'type': assignment_type, + 'short_label': grader.short_label, + 'min_count': grader.min_count, + 'drop_count': grader.drop_count, + 'weight': weight, + } + + # the default grading policy Lab assignment type does not have a short-label, + # but courses with the default grading policy do return a short-label for Lab + # assignments, so we ignore the Lab short-label + if 'Lab' in grading_policy_formatted: + grading_policy_formatted['Lab'].pop('short_label') + + for assignment in DEFAULT_GRADING_POLICY['GRADER']: + default_assignment_grading_policy_formatted = { + 'type': assignment['type'], + 'min_count': assignment['min_count'], + 'drop_count': assignment['drop_count'], + 'weight': assignment['weight'], + } + + # the default grading policy Lab assignment type does not have a short-label, so only + # add short_label to dictionary when the assignment has one + if 'short_label' in assignment: + default_assignment_grading_policy_formatted['short_label'] = assignment['short_label'] + + default_grading_policy_formatted[assignment['type']] = default_assignment_grading_policy_formatted + + # check for equality + if len(grading_policy_formatted) != len(default_grading_policy_formatted): + return True + else: + for assignment_type in grading_policy_formatted: + if (assignment_type not in default_grading_policy_formatted or + grading_policy_formatted[assignment_type] != default_grading_policy_formatted[assignment_type]): + return True + + return False diff --git a/common/lib/xmodule/xmodule/course_metadata_utils.py b/common/lib/xmodule/xmodule/course_metadata_utils.py index fee7a2d058f9b7ef29755b67cb62f2c436db4d7c..317f18d961b92218db9cfcb2492af121a8740c08 100644 --- a/common/lib/xmodule/xmodule/course_metadata_utils.py +++ b/common/lib/xmodule/xmodule/course_metadata_utils.py @@ -14,6 +14,44 @@ from pytz import utc DEFAULT_START_DATE = datetime(2030, 1, 1, tzinfo=utc) +""" +Default grading policy for a course run. +""" +DEFAULT_GRADING_POLICY = { + "GRADER": [ + { + "type": "Homework", + "short_label": "HW", + "min_count": 12, + "drop_count": 2, + "weight": 0.15, + }, + { + "type": "Lab", + "min_count": 12, + "drop_count": 2, + "weight": 0.15, + }, + { + "type": "Midterm Exam", + "short_label": "Midterm", + "min_count": 1, + "drop_count": 0, + "weight": 0.3, + }, + { + "type": "Final Exam", + "short_label": "Final", + "min_count": 1, + "drop_count": 0, + "weight": 0.4, + } + ], + "GRADE_CUTOFFS": { + "Pass": 0.5, + }, +} + def clean_course_key(course_key, padding_char): """ diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index c79007241b188ae18bbe911d45681830acdf6000..76f4718360c3cba2bb703b42da64499c03ab1b25 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -20,7 +20,7 @@ from six import text_type from xblock.fields import Scope, List, String, Dict, Boolean, Integer, Float from xmodule import course_metadata_utils -from xmodule.course_metadata_utils import DEFAULT_START_DATE +from xmodule.course_metadata_utils import DEFAULT_START_DATE, DEFAULT_GRADING_POLICY from xmodule.graders import grader_from_conf from xmodule.seq_module import SequenceDescriptor, SequenceModule from xmodule.tabs import CourseTabList, InvalidTabsException @@ -229,40 +229,7 @@ class CourseFields(object): ) grading_policy = Dict( help=_("Grading policy definition for this class"), - default={ - "GRADER": [ - { - "type": "Homework", - "min_count": 12, - "drop_count": 2, - "short_label": "HW", - "weight": 0.15, - }, - { - "type": "Lab", - "min_count": 12, - "drop_count": 2, - "weight": 0.15, - }, - { - "type": "Midterm Exam", - "short_label": "Midterm", - "min_count": 1, - "drop_count": 0, - "weight": 0.3, - }, - { - "type": "Final Exam", - "short_label": "Final", - "min_count": 1, - "drop_count": 0, - "weight": 0.4, - } - ], - "GRADE_CUTOFFS": { - "Pass": 0.5, - }, - }, + default=DEFAULT_GRADING_POLICY, scope=Scope.content ) show_calculator = Boolean( diff --git a/package-lock.json b/package-lock.json index c1658d940984ddab76f7b070f1ab39ed9fa3ff0c..1cc91ece890d79fe7d8e41ecd36e05bf9dca3205 100644 --- a/package-lock.json +++ b/package-lock.json @@ -106,9 +106,9 @@ } }, "@edx/studio-frontend": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@edx/studio-frontend/-/studio-frontend-1.16.1.tgz", - "integrity": "sha512-JP+C2PAvnDNurOPFMAQ5R1yKNK2YXLJIvtccsXMDQP9/lUgE1h9ZItD0dYhQM7P4tpeLqRVQ0O/lqh9A43rFDw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/@edx/studio-frontend/-/studio-frontend-1.16.3.tgz", + "integrity": "sha512-KcfH8KbjZ8FGj6X9OMPf8HHmzaXTDD3c1L1rnSvLgHLLm624CeY1khbPFVN1a/HBQ8tNqjPDtgERvzyfnnAGDQ==", "requires": { "@edx/edx-bootstrap": "1.0.0", "@edx/paragon": "3.1.2", @@ -121,7 +121,7 @@ "js-cookie": "2.2.0", "popper.js": "1.12.9", "prop-types": "15.6.0", - "react": "16.4.1", + "react": "16.4.2", "react-dom": "16.1.0", "react-dropzone": "4.2.13", "react-intl": "2.4.0", @@ -158,7 +158,7 @@ "font-awesome": "4.7.0", "mailto-link": "1.0.0", "prop-types": "15.6.0", - "react": "16.4.1", + "react": "16.4.2", "react-dom": "16.1.0", "react-element-proptypes": "1.0.0", "react-proptype-conditional-require": "1.0.4" @@ -175,9 +175,9 @@ "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" }, "react": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/react/-/react-16.4.1.tgz", - "integrity": "sha512-3GEs0giKp6E0Oh/Y9ZC60CmYgUPnp7voH9fbjWsvXtYFb4EWtgQub0ADSq0sJR0BbHc4FThLLtzlcFaFXIorwg==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/react/-/react-16.4.2.tgz", + "integrity": "sha512-dMv7YrbxO4y2aqnvA7f/ik9ibeLSHQJTI6TrYAenPSaQ6OXfb+Oti+oJiy8WBxgRzlKatYqtCjphTgDSCEiWFg==", "requires": { "fbjs": "0.8.16", "loose-envify": "1.3.1", diff --git a/package.json b/package.json index 926816ffd56ba6e4dabeafa1058f72e5a41ed164..e11d1fbc60d5eccae4d5fff7c0ae14837f108de3 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.1", + "@edx/studio-frontend": "1.16.3", "babel-core": "6.26.0", "babel-loader": "6.4.1", "babel-plugin-transform-class-properties": "6.24.1",