diff --git a/cms/djangoapps/contentstore/course_info_model.py b/cms/djangoapps/contentstore/course_info_model.py
index 570763bd28..c5632b3373 100644
--- a/cms/djangoapps/contentstore/course_info_model.py
+++ b/cms/djangoapps/contentstore/course_info_model.py
@@ -160,25 +160,13 @@ def _get_index(passed_id=None):
return 0
-def _get_html(course_updates_items):
- """
- Method to create course_updates_html from course_updates items
- """
- list_items = []
- for update in reversed(course_updates_items):
- # filter course update items which have status "deleted".
- if update.get("status") != CourseInfoModule.STATUS_DELETED:
- list_items.append(u"{date}
{content}".format(**update))
- return u"".format(list_items="".join(list_items))
-
-
def save_course_update_items(location, course_updates, course_update_items, user=None):
"""
Save list of course_updates data dictionaries in new field ("course_updates.items")
and html related to course update in 'data' ("course_updates.data") field.
"""
course_updates.items = course_update_items
- course_updates.data = _get_html(course_update_items)
+ course_updates.data = ""
# update db record
modulestore().update_item(course_updates, user.id)
diff --git a/cms/djangoapps/contentstore/views/tests/test_course_updates.py b/cms/djangoapps/contentstore/views/tests/test_course_updates.py
index 94f92fe637..f9199c005a 100644
--- a/cms/djangoapps/contentstore/views/tests/test_course_updates.py
+++ b/cms/djangoapps/contentstore/views/tests/test_course_updates.py
@@ -173,9 +173,8 @@ class CourseUpdateTest(CourseTestCase):
self.assertHTMLEqual(update_content, json.loads(resp.content)['content'])
course_updates = modulestore().get_item(location)
self.assertEqual(course_updates.items, [{u'date': update_date, u'content': update_content, u'id': 1}])
- # course_updates 'data' field should update accordingly
- update_data = u"".format(date=update_date, content=update_content)
- self.assertEqual(course_updates.data, update_data)
+ # course_updates 'data' field should not update automatically
+ self.assertEqual(course_updates.data, '')
# test delete course update item (soft delete)
course_updates = modulestore().get_item(location)
diff --git a/cms/templates/course_info.html b/cms/templates/course_info.html
index 7fa84c264e..dc2d4640ce 100644
--- a/cms/templates/course_info.html
+++ b/cms/templates/course_info.html
@@ -27,7 +27,7 @@ from openedx.core.lib.js_utils import escape_json_dumps
"${handouts_locator | escapejs}",
"${base_asset_url}",
${escape_json_dumps(push_notification_enabled) | n}
- );
+ );
});
%block>
diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py
index 14b6b19280..62fce42e3f 100644
--- a/common/lib/xmodule/xmodule/html_module.py
+++ b/common/lib/xmodule/xmodule/html_module.py
@@ -1,13 +1,14 @@
-import os
-import sys
-import re
import copy
-import logging
-import textwrap
-from lxml import etree
-from path import Path as path
+from datetime import datetime
from fs.errors import ResourceNotFoundError
+import logging
+from lxml import etree
+import os
+from path import Path as path
from pkg_resources import resource_string
+import re
+import sys
+import textwrap
import dogstats_wrapper as dog_stats_api
from xmodule.util.misc import escape_html_characters
@@ -75,10 +76,10 @@ class HtmlBlock(object):
return Fragment(self.get_html())
def get_html(self):
- """
- When we switch this to an XBlock, we can merge this with student_view,
- but for now the XModule mixin requires that this method be defined.
- """
+ """ Returns html required for rendering XModule. """
+
+ # When we switch this to an XBlock, we can merge this with student_view,
+ # but for now the XModule mixin requires that this method be defined.
# pylint: disable=no-member
if self.system.anonymous_student_id:
return self.data.replace("%%USER_ID%%", self.system.anonymous_student_id)
@@ -417,6 +418,35 @@ class CourseInfoModule(CourseInfoFields, HtmlModuleMixin):
# statuses
STATUS_VISIBLE = 'visible'
STATUS_DELETED = 'deleted'
+ TEMPLATE_DIR = 'courseware'
+
+ @XBlock.supports("multi_device")
+ def student_view(self, _context):
+ """
+ Return a fragment that contains the html for the student view
+ """
+ return Fragment(self.get_html())
+
+ def get_html(self):
+ """ Returns html required for rendering XModule. """
+
+ # When we switch this to an XBlock, we can merge this with student_view,
+ # but for now the XModule mixin requires that this method be defined.
+ # pylint: disable=no-member
+ if self.data != "":
+ if self.system.anonymous_student_id:
+ return self.data.replace("%%USER_ID%%", self.system.anonymous_student_id)
+ return self.data
+ else:
+ course_updates = [item for item in self.items if item.get('status') == self.STATUS_VISIBLE]
+ course_updates.sort(key=lambda item: datetime.strptime(item['date'], '%B %d, %Y'), reverse=True)
+
+ context = {
+ 'visible_updates': course_updates[:3],
+ 'hidden_updates': course_updates[3:],
+ }
+
+ return self.system.render_template("{0}/course_updates.html".format(self.TEMPLATE_DIR), context)
@XBlock.tag("detached")
diff --git a/lms/static/js/courseware/toggle_element_visibility.js b/lms/static/js/courseware/toggle_element_visibility.js
new file mode 100644
index 0000000000..b5dc438b0a
--- /dev/null
+++ b/lms/static/js/courseware/toggle_element_visibility.js
@@ -0,0 +1,42 @@
+;(function (define) {
+ 'use strict';
+
+ define(["jquery"],
+ function ($) {
+
+ return function () {
+ // define variables for code legibility
+ var toggleActionElements = $('.toggle-visibility-button');
+
+ var updateToggleActionText = function (targetElement, actionElement) {
+ var show_text = actionElement.data('show');
+ var hide_text = actionElement.data('hide');
+
+ if (targetElement.is(":visible")) {
+ if (hide_text) {
+ actionElement.html(actionElement.data('hide'));
+ } else {
+ actionElement.hide();
+ }
+ } else {
+ if (show_text) {
+ actionElement.html(actionElement.data('show'));
+ }
+ }
+ };
+
+ $.each(toggleActionElements, function (i, elem) {
+ var toggleActionElement = $(elem);
+ var toggleTargetElement = toggleActionElement.siblings('.toggle-visibility-element');
+
+ updateToggleActionText(toggleTargetElement, toggleActionElement);
+
+ toggleActionElement.on('click', function (event) {
+ event.preventDefault();
+ toggleTargetElement.toggleClass('hidden');
+ updateToggleActionText(toggleTargetElement, toggleActionElement);
+ });
+ });
+ };
+ });
+})(define || RequireJS.define);
diff --git a/lms/static/js/fixtures/courseware/course_updates.html b/lms/static/js/fixtures/courseware/course_updates.html
new file mode 100644
index 0000000000..51935ba02f
--- /dev/null
+++ b/lms/static/js/fixtures/courseware/course_updates.html
@@ -0,0 +1,45 @@
+
+
+ December 1, 2015
+ Hide
+
+
Assignment 1
+
Please submit your first assignment before due date.
+
+
+
+ December 1, 2015
+ Show
+
+
Quiz 1
+
You have a quiz due on coming friday.
+
+
+
+ November 26, 2015
+ Show
+
+
Assignment update
+
Please submit your first assignment before due date.
+
+
+
+
+
+ November 20, 2015
+ Show
+ Orientation
+
Orientation will held on monday
+
+
+
+ November 19, 2015
+ Show
+ Course starting
+
Starting info about course.
+
+
+
+
+ Show Earlier Course Updates
+
diff --git a/lms/static/js/spec/courseware/updates_visibility.js b/lms/static/js/spec/courseware/updates_visibility.js
new file mode 100644
index 0000000000..dc03954502
--- /dev/null
+++ b/lms/static/js/spec/courseware/updates_visibility.js
@@ -0,0 +1,36 @@
+define(['jquery', 'js/courseware/toggle_element_visibility'],
+ function ($, ToggleElementVisibility) {
+ 'use strict';
+
+ describe('show/hide with mouse click', function () {
+
+ beforeEach(function() {
+ loadFixtures('js/fixtures/courseware/course_updates.html');
+ /*jshint newcap: false */
+ ToggleElementVisibility();
+ /*jshint newcap: true */
+ });
+
+ it('ensures update will hide on hide button click', function () {
+ var $shownUpdate = $('.toggle-visibility-element:not(.hidden)').first();
+ $shownUpdate.siblings('.toggle-visibility-button').trigger('click');
+ expect($shownUpdate).toHaveClass('hidden');
+ });
+
+ it('ensures update will show on show button click', function () {
+ var $hiddenUpdate = $('.toggle-visibility-element.hidden').first();
+ $hiddenUpdate.siblings('.toggle-visibility-button').trigger('click');
+ expect($hiddenUpdate).not.toHaveClass('hidden');
+ });
+
+ it('ensures old updates will show on button click', function () {
+ // on page load old updates will be hidden
+ var $oldUpdates = $('.toggle-visibility-element.old-updates');
+ expect($oldUpdates).toHaveClass('hidden');
+
+ // on click on show earlier update button old updates will be shown
+ $('.toggle-visibility-button.show-older-updates').trigger('click');
+ expect($oldUpdates).not.toHaveClass('hidden');
+ });
+ });
+ });
diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js
index 427b066f55..3d0ae676d1 100644
--- a/lms/static/js/spec/main.js
+++ b/lms/static/js/spec/main.js
@@ -708,6 +708,7 @@
'lms/include/js/spec/edxnotes/collections/notes_spec.js',
'lms/include/js/spec/search/search_spec.js',
'lms/include/js/spec/navigation_spec.js',
+ 'lms/include/js/spec/courseware/updates_visibility.js',
'lms/include/js/spec/discovery/collections/filters_spec.js',
'lms/include/js/spec/discovery/models/course_card_spec.js',
'lms/include/js/spec/discovery/models/course_directory_spec.js',
diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss
index afa626fa34..59f0fdfbfe 100644
--- a/lms/static/sass/course/_info.scss
+++ b/lms/static/sass/course/_info.scss
@@ -50,6 +50,8 @@ div.info-wrapper {
@extend .content;
@include padding-left($baseline);
line-height: lh();
+ width: 100%;
+ display: block;
h1 {
@include text-align(left);
@@ -69,6 +71,25 @@ div.info-wrapper {
margin-bottom: lh();
padding-left: 0;
+ .updates-article {
+ border-radius:3px;
+ background-color: $white;
+ border:1px solid transparent;
+ &:hover {
+ border: 1px solid $gray-l3;
+ }
+ }
+
+ .show-older-updates {
+ @extend %btn-pl-white-base;
+ padding: ($baseline/2);
+ @include font-size(14);
+ width: 100%;
+ display: block;
+ text-align: center;
+ cursor: pointer;
+ }
+
> li,article {
@extend .clearfix;
padding: $baseline;
@@ -81,12 +102,25 @@ div.info-wrapper {
}
}
- h2 {
+ h2.date {
@extend %t-title9;
margin-bottom: ($baseline/4);
text-transform: none;
background: url('#{$static-path}/images/calendar-icon.png') 0 center no-repeat;
@include padding-left($baseline);
+ @include float(left);
+ }
+
+ .toggle-visibility-button {
+ @extend %t-title9;
+ @include float(right);
+ cursor: pointer;
+ }
+
+ .toggle-visibility-element {
+ content:'';
+ display:block;
+ clear: both;
}
section.update-description {
diff --git a/lms/templates/courseware/course_updates.html b/lms/templates/courseware/course_updates.html
new file mode 100644
index 0000000000..6fc936b753
--- /dev/null
+++ b/lms/templates/courseware/course_updates.html
@@ -0,0 +1,29 @@
+<%! from django.utils.translation import ugettext as _ %>
+
+
+ % for index, update in enumerate(visible_updates):
+
+ % if not update.get("is_error"):
+ ${update.get("date")}
+
+ % endif
+
+ ${update.get("content")}
+
+
+ % endfor
+
+
+ % for update in hidden_updates:
+
+ ${update.get("date")}
+
+ ${update.get("content")}
+
+ % endfor
+
+
+% if len(hidden_updates) > 0:
+
+% endif
+
diff --git a/lms/templates/courseware/info.html b/lms/templates/courseware/info.html
index e51d1b7c0f..dbc4b8d41b 100644
--- a/lms/templates/courseware/info.html
+++ b/lms/templates/courseware/info.html
@@ -34,6 +34,10 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
<%include file="/courseware/course_navigation.html" args="active_page='info'" />
+<%static:require_module module_name="js/courseware/toggle_element_visibility" class_name="ToggleElementVisibility">
+ ToggleElementVisibility();
+%static:require_module>
+
<%block name="bodyclass">view-in-course view-course-info ${course.css_class or ''}%block>
diff --git a/openedx/core/djangoapps/user_api/accounts/image_helpers.py b/openedx/core/djangoapps/user_api/accounts/image_helpers.py
index 00de36609c..b204d6daf6 100644
--- a/openedx/core/djangoapps/user_api/accounts/image_helpers.py
+++ b/openedx/core/djangoapps/user_api/accounts/image_helpers.py
@@ -92,13 +92,18 @@ def get_profile_image_urls_for_user(user, request=None):
dictionary of {size_display_name: url} for each image.
"""
- if user.profile.has_profile_image:
- urls = _get_profile_image_urls(
- _make_profile_image_name(user.username),
- get_profile_image_storage(),
- version=user.profile.profile_image_uploaded_at.strftime("%s"),
- )
- else:
+ try:
+ if user.profile.has_profile_image:
+ urls = _get_profile_image_urls(
+ _make_profile_image_name(user.username),
+ get_profile_image_storage(),
+ version=user.profile.profile_image_uploaded_at.strftime("%s"),
+ )
+ else:
+ urls = _get_default_profile_image_urls()
+ except UserProfile.DoesNotExist:
+ # when user does not have profile it raises exception, when exception
+ # occur we can simply get default image.
urls = _get_default_profile_image_urls()
if request: