diff --git a/AUTHORS b/AUTHORS index e4bc6c3777..b5ac7c421e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -129,3 +129,4 @@ Alison Hodges Jane Manning Toddi Norum Xavier Antoviaque +Ali Reza Sharafat diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f541a43e8d..f63b3b5b55 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,8 @@ Blades: Add view for field type Dict in Studio. BLD-658. Blades: Refactor stub implementation of LTI Provider. BLD-601. +Studio: Added ability to edit course short descriptions that appear on the course catalog page. + LMS: In left accordion and progress page, due dates are now displayed in time zone specified by settings.TIME_ZONE, instead of UTC always diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index 9b33654b70..b3ef6bd80a 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -76,6 +76,11 @@ class CourseDetailsTestCase(CourseTestCase): CourseDetails.update_from_json(self.course_locator, jsondetails.__dict__, self.user).syllabus, jsondetails.syllabus, "After set syllabus" ) + jsondetails.short_description = "Short Description" + self.assertEqual( + CourseDetails.update_from_json(self.course_locator, jsondetails.__dict__, self.user).short_description, + jsondetails.short_description, "After set short_description" + ) jsondetails.overview = "Overview" self.assertEqual( CourseDetails.update_from_json(self.course_locator, jsondetails.__dict__, self.user).overview, @@ -120,10 +125,19 @@ class CourseDetailsTestCase(CourseTestCase): self.assertContains(response, "Introducing Your Course") self.assertContains(response, "Course Image") + self.assertContains(response, "Course Short Description") self.assertNotContains(response, "Course Overview") self.assertNotContains(response, "Course Introduction Video") self.assertNotContains(response, "Requirements") + def test_editable_short_description_fetch(self): + settings_details_url = self.course_locator.url_reverse('settings/details/') + + with mock.patch.dict('django.conf.settings.FEATURES', {'EDITABLE_SHORT_DESCRIPTION': False}): + response = self.client.get_html(settings_details_url) + self.assertNotContains(response, "Course Short Description") + + def test_regular_site_fetch(self): settings_details_url = self.course_locator.url_reverse('settings/details/') @@ -141,6 +155,7 @@ class CourseDetailsTestCase(CourseTestCase): self.assertContains(response, "Introducing Your Course") self.assertContains(response, "Course Image") + self.assertContains(response, "Course Short Description") self.assertContains(response, "Course Overview") self.assertContains(response, "Course Introduction Video") self.assertContains(response, "Requirements") @@ -186,6 +201,7 @@ class CourseDetailsViewTest(CourseTestCase): self.alter_field(url, details, 'enrollment_start', datetime.datetime(2012, 10, 12, 1, 30, tzinfo=utc)) self.alter_field(url, details, 'enrollment_end', datetime.datetime(2012, 11, 15, 1, 30, tzinfo=utc)) + self.alter_field(url, details, 'short_description', "Short Description") self.alter_field(url, details, 'overview', "Overview") self.alter_field(url, details, 'intro_video', "intro_video") self.alter_field(url, details, 'effort', "effort") @@ -199,6 +215,7 @@ class CourseDetailsViewTest(CourseTestCase): self.compare_date_fields(details, encoded, context, 'end_date') self.compare_date_fields(details, encoded, context, 'enrollment_start') self.compare_date_fields(details, encoded, context, 'enrollment_end') + self.assertEqual(details['short_description'], encoded['short_description'], context + " short_description not ==") self.assertEqual(details['overview'], encoded['overview'], context + " overviews not ==") self.assertEqual(details['intro_video'], encoded.get('intro_video', None), context + " intro_video not ==") self.assertEqual(details['effort'], encoded['effort'], context + " efforts not ==") diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 782ea03c3b..5816982e57 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -459,6 +459,8 @@ def settings_handler(request, tag=None, package_id=None, branch=None, version_gu settings.FEATURES.get('ENABLE_MKTG_SITE', False) ) + short_description_editable = settings.FEATURES.get('EDITABLE_SHORT_DESCRIPTION', True) + return render_to_response('settings.html', { 'context_course': course_module, 'course_locator': locator, @@ -466,6 +468,7 @@ def settings_handler(request, tag=None, package_id=None, branch=None, version_gu 'course_image_url': utils.course_image_url(course_module), 'details_url': locator.url_reverse('/settings/details/'), 'about_page_editable': about_page_editable, + 'short_description_editable': short_description_editable, 'upload_asset_url': upload_asset_url }) elif 'application/json' in request.META.get('HTTP_ACCEPT', ''): diff --git a/cms/djangoapps/models/settings/course_details.py b/cms/djangoapps/models/settings/course_details.py index eeb82c54a5..41d5ba98fe 100644 --- a/cms/djangoapps/models/settings/course_details.py +++ b/cms/djangoapps/models/settings/course_details.py @@ -23,6 +23,7 @@ class CourseDetails(object): self.enrollment_start = None self.enrollment_end = None self.syllabus = None # a pdf file asset + self.short_description = "" self.overview = "" # html to render as the overview self.intro_video = None # a video pointer self.effort = None # int hours/week @@ -51,6 +52,12 @@ class CourseDetails(object): except ItemNotFoundError: pass + temploc = course_old_location.replace(category='about', name='short_description') + try: + course.short_description = get_modulestore(temploc).get_item(temploc).data + except ItemNotFoundError: + pass + temploc = temploc.replace(name='overview') try: course.overview = get_modulestore(temploc).get_item(temploc).data @@ -150,7 +157,7 @@ class CourseDetails(object): # NOTE: below auto writes to the db w/o verifying that any of the fields actually changed # to make faster, could compare against db or could have client send over a list of which fields changed. - for about_type in ['syllabus', 'overview', 'effort']: + for about_type in ['syllabus', 'overview', 'effort', 'short_description']: cls.update_about_item(course_old_location, about_type, jsondict[about_type], descriptor, user) recomposed_video_tag = CourseDetails.recompose_video_tag(jsondict['intro_video']) diff --git a/cms/envs/common.py b/cms/envs/common.py index ea80031611..2c98ceda17 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -73,6 +73,9 @@ FEATURES = { # Turn off account locking if failed login attempts exceeds a limit 'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': False, + + # Allow editing of short description in course settings in cms + 'EDITABLE_SHORT_DESCRIPTION': True, } ENABLE_JASMINE = False diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js index 058cacadd7..4cf2851903 100644 --- a/cms/static/js/models/settings/course_details.js +++ b/cms/static/js/models/settings/course_details.js @@ -10,6 +10,7 @@ var CourseDetails = Backbone.Model.extend({ enrollment_start: null, enrollment_end: null, syllabus: null, + short_description: "", overview: "", intro_video: null, effort: null, // an int or null, diff --git a/cms/static/js/views/settings/main.js b/cms/static/js/views/settings/main.js index 63776829c3..08aeabb83c 100644 --- a/cms/static/js/views/settings/main.js +++ b/cms/static/js/views/settings/main.js @@ -50,6 +50,8 @@ var DetailsView = ValidatingView.extend({ this.$el.find('#' + this.fieldToSelectorMap['overview']).val(this.model.get('overview')); this.codeMirrorize(null, $('#course-overview')[0]); + this.$el.find('#' + this.fieldToSelectorMap['short_description']).val(this.model.get('short_description')); + this.$el.find('.current-course-introduction-video iframe').attr('src', this.model.videosourceSample()); this.$el.find('#' + this.fieldToSelectorMap['intro_video']).val(this.model.get('intro_video') || ''); if (this.model.has('intro_video')) { @@ -71,6 +73,7 @@ var DetailsView = ValidatingView.extend({ 'enrollment_start' : 'enrollment-start', 'enrollment_end' : 'enrollment-end', 'overview' : 'course-overview', + 'short_description' : 'course-short-description', 'intro_video' : 'course-introduction-video', 'effort' : "course-effort", 'course_image_asset_path': 'course-image-url' @@ -148,6 +151,9 @@ var DetailsView = ValidatingView.extend({ case 'course-effort': this.setField(event); break; + case 'course-short-description': + this.setField(event); + break; // Don't make the user reload the page to check the Youtube ID. // Wait for a second to load the video, avoiding egregious AJAX calls. case 'course-introduction-video': diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 2c27c2562d..e78ed54151 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -67,19 +67,19 @@ require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/s
  1. -
  2. -
  3. -
@@ -93,7 +93,7 @@ require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/s
  • - ${_("Invite your students")}
  • @@ -198,6 +198,14 @@ require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/s ${_("Information for prospective students")}
      + % if short_description_editable: +
    1. + + + ${_("Appears on the course catalog page when students roll over the course name. Limit to ~150 characters")} +
    2. + % endif + % if about_page_editable: