From bf7b3eab1aa7754ea4b9a672d14c98d01ee5bd83 Mon Sep 17 00:00:00 2001 From: Sanford Student Date: Thu, 27 Jul 2017 12:05:02 -0400 Subject: [PATCH] add cert available date to CMS settings --- .../js/models/settings/course_details.js | 11 ++++- .../js/spec/views/settings/main_spec.js | 1 + cms/static/js/views/settings/main.js | 48 ++++++++++--------- cms/templates/settings.html | 13 +++++ lms/djangoapps/certificates/signals.py | 2 +- .../certificates/tests/test_signals.py | 2 +- .../core/djangoapps/certificates}/__init__.py | 0 .../certificates/config/__init__.py | 0 .../djangoapps/certificates/config/waffle.py | 0 .../core/djangoapps/models/course_details.py | 10 ++++ .../models/tests/test_course_details.py | 11 +++++ 11 files changed, 72 insertions(+), 26 deletions(-) rename {lms/djangoapps/certificates/config => openedx/core/djangoapps/certificates}/__init__.py (100%) create mode 100644 openedx/core/djangoapps/certificates/config/__init__.py rename {lms => openedx/core}/djangoapps/certificates/config/waffle.py (100%) diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js index 683200d7d6..a6a81bfa10 100644 --- a/cms/static/js/models/settings/course_details.js +++ b/cms/static/js/models/settings/course_details.js @@ -8,6 +8,7 @@ define(['backbone', 'underscore', 'gettext', 'js/models/validation_helpers', 'js language: '', start_date: null, // maps to 'start' end_date: null, // maps to 'end' + certificate_available_date: null, enrollment_start: null, enrollment_end: null, syllabus: null, @@ -38,7 +39,7 @@ define(['backbone', 'underscore', 'gettext', 'js/models/validation_helpers', 'js // A bit funny in that the video key validation is asynchronous; so, it won't stop the validation. var errors = {}; newattrs = DateUtils.convertDateStringsToObjects( - newattrs, ['start_date', 'end_date', 'enrollment_start', 'enrollment_end'] + newattrs, ['start_date', 'end_date', 'certificate_available_date', 'enrollment_start', 'enrollment_end'] ); if (newattrs.start_date === null) { @@ -51,6 +52,14 @@ define(['backbone', 'underscore', 'gettext', 'js/models/validation_helpers', 'js if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) { errors.enrollment_start = gettext('The course start date must be later than the enrollment start date.'); } + if ( + newattrs.start_date && newattrs.certificate_available_date && newattrs.start_date > + newattrs.certificate_available_date + ) { + errors.enrollment_start = gettext( + 'The certificate available date must be later than the enrollment start date.' + ); + } if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) { errors.enrollment_end = gettext('The enrollment start date cannot be after the enrollment end date.'); } diff --git a/cms/static/js/spec/views/settings/main_spec.js b/cms/static/js/spec/views/settings/main_spec.js index 4fbebcc84d..e33e845465 100644 --- a/cms/static/js/spec/views/settings/main_spec.js +++ b/cms/static/js/spec/views/settings/main_spec.js @@ -21,6 +21,7 @@ define([ end_date: '2014-11-05T20:00:00Z', enrollment_start: '2014-10-00T00:00:00Z', enrollment_end: '2014-11-05T00:00:00Z', + certificate_available_date: '2014-11-05T20:00:00Z', org: '', course_id: '', run: '', diff --git a/cms/static/js/views/settings/main.js b/cms/static/js/views/settings/main.js index 8125c0a936..0b9204a8e1 100644 --- a/cms/static/js/views/settings/main.js +++ b/cms/static/js/views/settings/main.js @@ -79,6 +79,7 @@ define(['js/views/validation', 'codemirror', 'underscore', 'jquery', 'jquery.ui' DateUtils.setupDatePicker('start_date', this); DateUtils.setupDatePicker('end_date', this); + DateUtils.setupDatePicker('certificate_available_date', this); DateUtils.setupDatePicker('enrollment_start', this); DateUtils.setupDatePicker('enrollment_end', this); @@ -154,29 +155,30 @@ define(['js/views/validation', 'codemirror', 'underscore', 'jquery', 'jquery.ui' return this; }, fieldToSelectorMap: { - 'language': 'course-language', - 'start_date': 'course-start', - 'end_date': 'course-end', - 'enrollment_start': 'enrollment-start', - 'enrollment_end': 'enrollment-end', - 'overview': 'course-overview', - 'title': 'course-title', - 'subtitle': 'course-subtitle', - 'duration': 'course-duration', - 'description': 'course-description', - 'short_description': 'course-short-description', - 'intro_video': 'course-introduction-video', - 'effort': 'course-effort', - 'course_image_asset_path': 'course-image-url', - 'banner_image_asset_path': 'banner-image-url', - 'video_thumbnail_image_asset_path': 'video-thumbnail-image-url', - 'pre_requisite_courses': 'pre-requisite-course', - 'entrance_exam_enabled': 'entrance-exam-enabled', - 'entrance_exam_minimum_score_pct': 'entrance-exam-minimum-score-pct', - 'course_settings_learning_fields': 'course-settings-learning-fields', - 'add_course_learning_info': 'add-course-learning-info', - 'add_course_instructor_info': 'add-course-instructor-info', - 'course_learning_info': 'course-learning-info' + language: 'course-language', + start_date: 'course-start', + end_date: 'course-end', + enrollment_start: 'enrollment-start', + enrollment_end: 'enrollment-end', + certificate_available_date: 'certificate-available', + overview: 'course-overview', + title: 'course-title', + subtitle: 'course-subtitle', + duration: 'course-duration', + description: 'course-description', + short_description: 'course-short-description', + intro_video: 'course-introduction-video', + effort: 'course-effort', + course_image_asset_path: 'course-image-url', + banner_image_asset_path: 'banner-image-url', + video_thumbnail_image_asset_path: 'video-thumbnail-image-url', + pre_requisite_courses: 'pre-requisite-course', + entrance_exam_enabled: 'entrance-exam-enabled', + entrance_exam_minimum_score_pct: 'entrance-exam-minimum-score-pct', + course_settings_learning_fields: 'course-settings-learning-fields', + add_course_learning_info: 'add-course-learning-info', + add_course_instructor_info: 'add-course-instructor-info', + course_learning_info: 'course-learning-info' }, addLearningFields: function() { diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 2a5c51b887..420b6975f8 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -9,6 +9,7 @@ import urllib from django.utils.translation import ugettext as _ from contentstore import utils + from openedx.core.djangoapps.certificates.config import waffle from openedx.core.djangolib.js_utils import ( dump_js_escaped_json, js_escaped_string ) @@ -213,6 +214,18 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url | n, js_escaped_string}' + % if waffle.waffle().is_enabled(waffle.INSTRUCTOR_PACED_ONLY): +
    +
  1. +
    + + + ${_("By default, 48 hours after course end date")} +
    +
  2. +
+ % endif +
  1. diff --git a/lms/djangoapps/certificates/signals.py b/lms/djangoapps/certificates/signals.py index b41f156c3e..572bd3cf58 100644 --- a/lms/djangoapps/certificates/signals.py +++ b/lms/djangoapps/certificates/signals.py @@ -6,7 +6,6 @@ import logging from django.db.models.signals import post_save from django.dispatch import receiver -from .config import waffle from certificates.models import \ CertificateGenerationCourseSetting, \ CertificateWhitelist, \ @@ -14,6 +13,7 @@ from certificates.models import \ from certificates.tasks import generate_certificate from courseware import courses from lms.djangoapps.grades.new.course_grade_factory import CourseGradeFactory +from openedx.core.djangoapps.certificates.config import waffle from openedx.core.djangoapps.signals.signals import COURSE_GRADE_NOW_PASSED, LEARNER_NOW_VERIFIED from student.models import CourseEnrollment diff --git a/lms/djangoapps/certificates/tests/test_signals.py b/lms/djangoapps/certificates/tests/test_signals.py index 31dd1ec0a0..8bbe406478 100644 --- a/lms/djangoapps/certificates/tests/test_signals.py +++ b/lms/djangoapps/certificates/tests/test_signals.py @@ -5,7 +5,6 @@ and disabling for instructor-paced courses. import mock from certificates import api as certs_api -from certificates.config import waffle from certificates.models import \ CertificateGenerationConfiguration, \ CertificateWhitelist, \ @@ -15,6 +14,7 @@ from openedx.core.djangoapps.signals.handlers import _listen_for_course_pacing_c from lms.djangoapps.grades.new.course_grade_factory import CourseGradeFactory from lms.djangoapps.grades.tests.utils import mock_passing_grade from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification +from openedx.core.djangoapps.certificates.config import waffle from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration from student.tests.factories import CourseEnrollmentFactory, UserFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase diff --git a/lms/djangoapps/certificates/config/__init__.py b/openedx/core/djangoapps/certificates/__init__.py similarity index 100% rename from lms/djangoapps/certificates/config/__init__.py rename to openedx/core/djangoapps/certificates/__init__.py diff --git a/openedx/core/djangoapps/certificates/config/__init__.py b/openedx/core/djangoapps/certificates/config/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/certificates/config/waffle.py b/openedx/core/djangoapps/certificates/config/waffle.py similarity index 100% rename from lms/djangoapps/certificates/config/waffle.py rename to openedx/core/djangoapps/certificates/config/waffle.py diff --git a/openedx/core/djangoapps/models/course_details.py b/openedx/core/djangoapps/models/course_details.py index 2102033a73..dee667849a 100644 --- a/openedx/core/djangoapps/models/course_details.py +++ b/openedx/core/djangoapps/models/course_details.py @@ -106,6 +106,7 @@ class CourseDetails(object): course_details = cls(course_key.org, course_key.course, course_key.run) course_details.start_date = course_descriptor.start course_details.end_date = course_descriptor.end + course_details.certificate_available_date = course_descriptor.certificate_available_date course_details.enrollment_start = course_descriptor.enrollment_start course_details.enrollment_end = course_descriptor.enrollment_end course_details.pre_requisite_courses = course_descriptor.pre_requisite_courses @@ -233,6 +234,15 @@ class CourseDetails(object): dirty = True descriptor.enrollment_end = converted + if 'certificate_available_date' in jsondict: + converted = date.from_json(jsondict['certificate_available_date']) + else: + converted = None + + if converted != descriptor.certificate_available_date: + dirty = True + descriptor.certificate_available_date = converted + if 'course_image_name' in jsondict and jsondict['course_image_name'] != descriptor.course_image: descriptor.course_image = jsondict['course_image_name'] dirty = True diff --git a/openedx/core/djangoapps/models/tests/test_course_details.py b/openedx/core/djangoapps/models/tests/test_course_details.py index 17cb3455f1..74ad8eb8b2 100644 --- a/openedx/core/djangoapps/models/tests/test_course_details.py +++ b/openedx/core/djangoapps/models/tests/test_course_details.py @@ -39,6 +39,10 @@ class CourseDetailsTestCase(ModuleStoreTestCase): self.assertIsNone( details.enrollment_end, "enrollment_end date somehow initialized " + str(details.enrollment_end) ) + self.assertIsNone( + details.certificate_available_date, + "certificate_available_date date somehow initialized " + str(details.certificate_available_date) + ) self.assertIsNone(details.syllabus, "syllabus somehow initialized" + str(details.syllabus)) self.assertIsNone(details.intro_video, "intro_video somehow initialized" + str(details.intro_video)) self.assertIsNone(details.effort, "effort somehow initialized" + str(details.effort)) @@ -90,6 +94,13 @@ class CourseDetailsTestCase(ModuleStoreTestCase): CourseDetails.update_from_json(self.course.id, jsondetails.__dict__, self.user).end_date, jsondetails.end_date ) + jsondetails.certificate_available_date = datetime.datetime(2010, 10, 1, 0, tzinfo=UTC()) + self.assertEqual( + CourseDetails.update_from_json( + self.course.id, jsondetails.__dict__, self.user + ).certificate_available_date, + jsondetails.certificate_available_date + ) jsondetails.course_image_name = "an_image.jpg" self.assertEqual( CourseDetails.update_from_json(self.course.id, jsondetails.__dict__, self.user).course_image_name,