Commit 85058ec4 by Ali Reza Sharafat

Course short descriptions -- couple of more asserts and pep8 fixes

parent 89ac19e8
...@@ -129,3 +129,4 @@ Alison Hodges <ahodges@edx.org> ...@@ -129,3 +129,4 @@ Alison Hodges <ahodges@edx.org>
Jane Manning <jmanning@gmail.com> Jane Manning <jmanning@gmail.com>
Toddi Norum <toddi@edx.org> Toddi Norum <toddi@edx.org>
Xavier Antoviaque <xavier@antoviaque.org> Xavier Antoviaque <xavier@antoviaque.org>
Ali Reza Sharafat <ali.sharafat@gmail.com>
...@@ -14,6 +14,8 @@ Blades: Add view for field type Dict in Studio. BLD-658. ...@@ -14,6 +14,8 @@ Blades: Add view for field type Dict in Studio. BLD-658.
Blades: Refactor stub implementation of LTI Provider. BLD-601. 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 LMS: In left accordion and progress page, due dates are now displayed in time
zone specified by settings.TIME_ZONE, instead of UTC always zone specified by settings.TIME_ZONE, instead of UTC always
......
...@@ -76,6 +76,11 @@ class CourseDetailsTestCase(CourseTestCase): ...@@ -76,6 +76,11 @@ class CourseDetailsTestCase(CourseTestCase):
CourseDetails.update_from_json(self.course_locator, jsondetails.__dict__, self.user).syllabus, CourseDetails.update_from_json(self.course_locator, jsondetails.__dict__, self.user).syllabus,
jsondetails.syllabus, "After set 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" jsondetails.overview = "Overview"
self.assertEqual( self.assertEqual(
CourseDetails.update_from_json(self.course_locator, jsondetails.__dict__, self.user).overview, CourseDetails.update_from_json(self.course_locator, jsondetails.__dict__, self.user).overview,
...@@ -120,10 +125,19 @@ class CourseDetailsTestCase(CourseTestCase): ...@@ -120,10 +125,19 @@ class CourseDetailsTestCase(CourseTestCase):
self.assertContains(response, "Introducing Your Course") self.assertContains(response, "Introducing Your Course")
self.assertContains(response, "Course Image") self.assertContains(response, "Course Image")
self.assertContains(response, "Course Short Description")
self.assertNotContains(response, "Course Overview") self.assertNotContains(response, "Course Overview")
self.assertNotContains(response, "Course Introduction Video") self.assertNotContains(response, "Course Introduction Video")
self.assertNotContains(response, "Requirements") 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): def test_regular_site_fetch(self):
settings_details_url = self.course_locator.url_reverse('settings/details/') settings_details_url = self.course_locator.url_reverse('settings/details/')
...@@ -141,6 +155,7 @@ class CourseDetailsTestCase(CourseTestCase): ...@@ -141,6 +155,7 @@ class CourseDetailsTestCase(CourseTestCase):
self.assertContains(response, "Introducing Your Course") self.assertContains(response, "Introducing Your Course")
self.assertContains(response, "Course Image") self.assertContains(response, "Course Image")
self.assertContains(response, "Course Short Description")
self.assertContains(response, "Course Overview") self.assertContains(response, "Course Overview")
self.assertContains(response, "Course Introduction Video") self.assertContains(response, "Course Introduction Video")
self.assertContains(response, "Requirements") self.assertContains(response, "Requirements")
...@@ -186,6 +201,7 @@ class CourseDetailsViewTest(CourseTestCase): ...@@ -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_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, '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, 'overview', "Overview")
self.alter_field(url, details, 'intro_video', "intro_video") self.alter_field(url, details, 'intro_video', "intro_video")
self.alter_field(url, details, 'effort', "effort") self.alter_field(url, details, 'effort', "effort")
...@@ -199,6 +215,7 @@ class CourseDetailsViewTest(CourseTestCase): ...@@ -199,6 +215,7 @@ class CourseDetailsViewTest(CourseTestCase):
self.compare_date_fields(details, encoded, context, 'end_date') 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_start')
self.compare_date_fields(details, encoded, context, 'enrollment_end') 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['overview'], encoded['overview'], context + " overviews not ==")
self.assertEqual(details['intro_video'], encoded.get('intro_video', None), context + " intro_video not ==") self.assertEqual(details['intro_video'], encoded.get('intro_video', None), context + " intro_video not ==")
self.assertEqual(details['effort'], encoded['effort'], context + " efforts not ==") self.assertEqual(details['effort'], encoded['effort'], context + " efforts not ==")
......
...@@ -459,6 +459,8 @@ def settings_handler(request, tag=None, package_id=None, branch=None, version_gu ...@@ -459,6 +459,8 @@ def settings_handler(request, tag=None, package_id=None, branch=None, version_gu
settings.FEATURES.get('ENABLE_MKTG_SITE', False) settings.FEATURES.get('ENABLE_MKTG_SITE', False)
) )
short_description_editable = settings.FEATURES.get('EDITABLE_SHORT_DESCRIPTION', True)
return render_to_response('settings.html', { return render_to_response('settings.html', {
'context_course': course_module, 'context_course': course_module,
'course_locator': locator, 'course_locator': locator,
...@@ -466,6 +468,7 @@ def settings_handler(request, tag=None, package_id=None, branch=None, version_gu ...@@ -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), 'course_image_url': utils.course_image_url(course_module),
'details_url': locator.url_reverse('/settings/details/'), 'details_url': locator.url_reverse('/settings/details/'),
'about_page_editable': about_page_editable, 'about_page_editable': about_page_editable,
'short_description_editable': short_description_editable,
'upload_asset_url': upload_asset_url 'upload_asset_url': upload_asset_url
}) })
elif 'application/json' in request.META.get('HTTP_ACCEPT', ''): elif 'application/json' in request.META.get('HTTP_ACCEPT', ''):
......
...@@ -23,6 +23,7 @@ class CourseDetails(object): ...@@ -23,6 +23,7 @@ class CourseDetails(object):
self.enrollment_start = None self.enrollment_start = None
self.enrollment_end = None self.enrollment_end = None
self.syllabus = None # a pdf file asset self.syllabus = None # a pdf file asset
self.short_description = ""
self.overview = "" # html to render as the overview self.overview = "" # html to render as the overview
self.intro_video = None # a video pointer self.intro_video = None # a video pointer
self.effort = None # int hours/week self.effort = None # int hours/week
...@@ -51,6 +52,12 @@ class CourseDetails(object): ...@@ -51,6 +52,12 @@ class CourseDetails(object):
except ItemNotFoundError: except ItemNotFoundError:
pass 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') temploc = temploc.replace(name='overview')
try: try:
course.overview = get_modulestore(temploc).get_item(temploc).data course.overview = get_modulestore(temploc).get_item(temploc).data
...@@ -150,7 +157,7 @@ class CourseDetails(object): ...@@ -150,7 +157,7 @@ class CourseDetails(object):
# NOTE: below auto writes to the db w/o verifying that any of the fields actually changed # 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. # 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) cls.update_about_item(course_old_location, about_type, jsondict[about_type], descriptor, user)
recomposed_video_tag = CourseDetails.recompose_video_tag(jsondict['intro_video']) recomposed_video_tag = CourseDetails.recompose_video_tag(jsondict['intro_video'])
......
...@@ -73,6 +73,9 @@ FEATURES = { ...@@ -73,6 +73,9 @@ FEATURES = {
# Turn off account locking if failed login attempts exceeds a limit # Turn off account locking if failed login attempts exceeds a limit
'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': False, 'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': False,
# Allow editing of short description in course settings in cms
'EDITABLE_SHORT_DESCRIPTION': True,
} }
ENABLE_JASMINE = False ENABLE_JASMINE = False
......
...@@ -10,6 +10,7 @@ var CourseDetails = Backbone.Model.extend({ ...@@ -10,6 +10,7 @@ var CourseDetails = Backbone.Model.extend({
enrollment_start: null, enrollment_start: null,
enrollment_end: null, enrollment_end: null,
syllabus: null, syllabus: null,
short_description: "",
overview: "", overview: "",
intro_video: null, intro_video: null,
effort: null, // an int or null, effort: null, // an int or null,
......
...@@ -50,6 +50,8 @@ var DetailsView = ValidatingView.extend({ ...@@ -50,6 +50,8 @@ var DetailsView = ValidatingView.extend({
this.$el.find('#' + this.fieldToSelectorMap['overview']).val(this.model.get('overview')); this.$el.find('#' + this.fieldToSelectorMap['overview']).val(this.model.get('overview'));
this.codeMirrorize(null, $('#course-overview')[0]); 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('.current-course-introduction-video iframe').attr('src', this.model.videosourceSample());
this.$el.find('#' + this.fieldToSelectorMap['intro_video']).val(this.model.get('intro_video') || ''); this.$el.find('#' + this.fieldToSelectorMap['intro_video']).val(this.model.get('intro_video') || '');
if (this.model.has('intro_video')) { if (this.model.has('intro_video')) {
...@@ -71,6 +73,7 @@ var DetailsView = ValidatingView.extend({ ...@@ -71,6 +73,7 @@ var DetailsView = ValidatingView.extend({
'enrollment_start' : 'enrollment-start', 'enrollment_start' : 'enrollment-start',
'enrollment_end' : 'enrollment-end', 'enrollment_end' : 'enrollment-end',
'overview' : 'course-overview', 'overview' : 'course-overview',
'short_description' : 'course-short-description',
'intro_video' : 'course-introduction-video', 'intro_video' : 'course-introduction-video',
'effort' : "course-effort", 'effort' : "course-effort",
'course_image_asset_path': 'course-image-url' 'course_image_asset_path': 'course-image-url'
...@@ -148,6 +151,9 @@ var DetailsView = ValidatingView.extend({ ...@@ -148,6 +151,9 @@ var DetailsView = ValidatingView.extend({
case 'course-effort': case 'course-effort':
this.setField(event); this.setField(event);
break; break;
case 'course-short-description':
this.setField(event);
break;
// Don't make the user reload the page to check the Youtube ID. // 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. // Wait for a second to load the video, avoiding egregious AJAX calls.
case 'course-introduction-video': case 'course-introduction-video':
......
...@@ -67,19 +67,19 @@ require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/s ...@@ -67,19 +67,19 @@ require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/s
<ol class="list-input"> <ol class="list-input">
<li class="field text is-not-editable" id="field-course-organization"> <li class="field text is-not-editable" id="field-course-organization">
<label for="course-organization">${_("Organization")}</label> <label for="course-organization">${_("Organization")}</label>
<input title="${_('This field is disabled: this information cannot be changed.')}" type="text" <input title="${_('This field is disabled: this information cannot be changed.')}" type="text"
class="long" id="course-organization" readonly /> class="long" id="course-organization" readonly />
</li> </li>
<li class="field text is-not-editable" id="field-course-number"> <li class="field text is-not-editable" id="field-course-number">
<label for="course-number">${_("Course Number")}</label> <label for="course-number">${_("Course Number")}</label>
<input title="${_('This field is disabled: this information cannot be changed.')}" type="text" <input title="${_('This field is disabled: this information cannot be changed.')}" type="text"
class="short" id="course-number" readonly> class="short" id="course-number" readonly>
</li> </li>
<li class="field text is-not-editable" id="field-course-name"> <li class="field text is-not-editable" id="field-course-name">
<label for="course-name">${_("Course Name")}</label> <label for="course-name">${_("Course Name")}</label>
<input title="${_('This field is disabled: this information cannot be changed.')}" type="text" <input title="${_('This field is disabled: this information cannot be changed.')}" type="text"
class="long" id="course-name" readonly /> class="long" id="course-name" readonly />
</li> </li>
</ol> </ol>
...@@ -93,7 +93,7 @@ require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/s ...@@ -93,7 +93,7 @@ require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/s
<ul class="list-actions"> <ul class="list-actions">
<li class="action-item"> <li class="action-item">
<a title="${_('Send a note to students via email')}" <a title="${_('Send a note to students via email')}"
href="mailto:someone@domain.com?Subject=Enroll%20in%20${context_course.display_name_with_default}&body=The%20course%20&quot;${context_course.display_name_with_default}&quot;,%20provided%20by%20edX,%20is%20open%20for%20enrollment.%20Please%20navigate%20to%20this%20course%20at%20https:${lms_link_for_about_page}%20to%20enroll." class="action action-primary"> href="mailto:someone@domain.com?Subject=Enroll%20in%20${context_course.display_name_with_default}&body=The%20course%20&quot;${context_course.display_name_with_default}&quot;,%20provided%20by%20edX,%20is%20open%20for%20enrollment.%20Please%20navigate%20to%20this%20course%20at%20https:${lms_link_for_about_page}%20to%20enroll." class="action action-primary">
<i class="icon-envelope-alt icon-inline"></i>${_("Invite your students")}</a> <i class="icon-envelope-alt icon-inline"></i>${_("Invite your students")}</a>
</li> </li>
...@@ -198,6 +198,14 @@ require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/s ...@@ -198,6 +198,14 @@ require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/s
<span class="tip">${_("Information for prospective students")}</span> <span class="tip">${_("Information for prospective students")}</span>
</header> </header>
<ol class="list-input"> <ol class="list-input">
% if short_description_editable:
<li class="field text" id="field-course-short-description">
<label for="course-overview">${_("Course Short Description")}</label>
<textarea class="text" id="course-short-description"></textarea>
<span class="tip tip-stacked">${_("Appears on the course catalog page when students roll over the course name. Limit to ~150 characters")}</span>
</li>
% endif
% if about_page_editable: % if about_page_editable:
<li class="field text" id="field-course-overview"> <li class="field text" id="field-course-overview">
<label for="course-overview">${_("Course Overview")}</label> <label for="course-overview">${_("Course Overview")}</label>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment