Merge pull request #6321 from edx/feature/edxnotes

TNL-213: Student Notes
This commit is contained in:
Tim Babych
2015-01-15 02:02:13 +02:00
125 changed files with 9458 additions and 358 deletions

View File

@@ -0,0 +1,15 @@
"""
Utilities related to edXNotes.
"""
import sys
def edxnotes(cls):
"""
Conditional decorator that loads edxnotes only when they exist.
"""
if "edxnotes" in sys.modules:
from edxnotes.decorators import edxnotes as notes # pylint: disable=import-error
return notes(cls)
else:
return cls

View File

@@ -16,6 +16,8 @@ from xmodule.xml_module import XmlDescriptor, name_to_pathname
import textwrap
from xmodule.contentstore.content import StaticContent
from xblock.core import XBlock
from xmodule.edxnotes_utils import edxnotes
log = logging.getLogger("edx.courseware")
@@ -51,7 +53,10 @@ class HtmlFields(object):
)
class HtmlModule(HtmlFields, XModule):
class HtmlModuleMixin(HtmlFields, XModule):
"""
Attributes and methods used by HtmlModules internally.
"""
js = {
'coffee': [
resource_string(__name__, 'js/src/javascript_loader.coffee'),
@@ -72,6 +77,14 @@ class HtmlModule(HtmlFields, XModule):
return self.data
@edxnotes
class HtmlModule(HtmlModuleMixin):
"""
Module for putting raw html in a course
"""
pass
class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor):
"""
Module for putting raw html in a course
@@ -255,7 +268,7 @@ class AboutFields(object):
@XBlock.tag("detached")
class AboutModule(AboutFields, HtmlModule):
class AboutModule(AboutFields, HtmlModuleMixin):
"""
Overriding defaults but otherwise treated as HtmlModule.
"""
@@ -292,7 +305,7 @@ class StaticTabFields(object):
@XBlock.tag("detached")
class StaticTabModule(StaticTabFields, HtmlModule):
class StaticTabModule(StaticTabFields, HtmlModuleMixin):
"""
Supports the field overrides
"""
@@ -326,7 +339,7 @@ class CourseInfoFields(object):
@XBlock.tag("detached")
class CourseInfoModule(CourseInfoFields, HtmlModule):
class CourseInfoModule(CourseInfoFields, HtmlModuleMixin):
"""
Just to support xblock field overrides
"""

View File

@@ -35,7 +35,7 @@ src_paths:
lib_paths:
- common_static/js/test/i18n.js
- common_static/coffee/src/ajax_prefix.js
- common_static/coffee/src/logger.js
- common_static/js/src/logger.js
- common_static/js/vendor/jasmine-jquery.js
- common_static/js/vendor/jasmine-imagediff.js
- common_static/js/vendor/require.js

View File

@@ -34,7 +34,7 @@ describe 'Crowdsourced hinter', ->
response =
success: 'incorrect'
contents: 'mock grader response'
settings.success(response)
settings.success(response) if settings
)
@problem.answers = 'test answer'
@problem.check_fd()

View File

@@ -172,6 +172,19 @@ class InheritanceMixin(XBlockMixin):
scope=Scope.settings,
default=default_reset_button
)
edxnotes = Boolean(
display_name=_("Enable Student Notes"),
help=_("Enter true or false. If true, students can use the Student Notes feature."),
default=False,
scope=Scope.settings
)
edxnotes_visibility = Boolean(
display_name="Student Notes Visibility",
help=_("Indicates whether Student Notes are visible in the course. "
"Students can also show or hide their notes in the courseware."),
default=True,
scope=Scope.user_info
)
def compute_inherited_metadata(descriptor):

View File

@@ -69,6 +69,7 @@ class CourseTab(object): # pylint: disable=incomplete-protocol
settings: The configuration settings, including values for:
WIKI_ENABLED
FEATURES['ENABLE_DISCUSSION_SERVICE']
FEATURES['ENABLE_EDXNOTES']
FEATURES['ENABLE_STUDENT_NOTES']
FEATURES['ENABLE_TEXTBOOK']
@@ -195,6 +196,7 @@ class CourseTab(object): # pylint: disable=incomplete-protocol
'staff_grading': StaffGradingTab,
'open_ended': OpenEndedGradingTab,
'notes': NotesTab,
'edxnotes': EdxNotesTab,
'syllabus': SyllabusTab,
'instructor': InstructorTab, # not persisted
}
@@ -694,6 +696,27 @@ class NotesTab(AuthenticatedCourseTab):
return super(NotesTab, cls).validate(tab_dict, raise_error) and need_name(tab_dict, raise_error)
class EdxNotesTab(AuthenticatedCourseTab):
"""
A tab for the course student notes.
"""
type = 'edxnotes'
def can_display(self, course, settings, is_user_authenticated, is_user_staff, is_user_enrolled):
return settings.FEATURES.get('ENABLE_EDXNOTES')
def __init__(self, tab_dict=None):
super(EdxNotesTab, self).__init__(
name=tab_dict['name'] if tab_dict else _('Notes'),
tab_id=self.type,
link_func=link_reverse_func(self.type),
)
@classmethod
def validate(cls, tab_dict, raise_error=True):
return super(EdxNotesTab, cls).validate(tab_dict, raise_error) and need_name(tab_dict, raise_error)
class InstructorTab(StaffTab):
"""
A tab for the course instructors.
@@ -854,13 +877,13 @@ class CourseTabList(List):
# the following tabs should appear only once
for tab_type in [
CoursewareTab.type,
CourseInfoTab.type,
NotesTab.type,
TextbookTabs.type,
PDFTextbookTabs.type,
HtmlTextbookTabs.type,
]:
CoursewareTab.type,
CourseInfoTab.type,
NotesTab.type,
TextbookTabs.type,
PDFTextbookTabs.type,
HtmlTextbookTabs.type,
EdxNotesTab.type]:
cls._validate_num_tabs_of_type(tabs, tab_type, 1)
@staticmethod

View File

@@ -412,6 +412,40 @@ class InstructorTestCase(TabTestCase):
self.check_can_display_results(tab, for_staff_only=True)
class EdxNotesTestCase(TabTestCase):
"""
Test cases for Notes Tab.
"""
def check_edxnotes_tab(self):
"""
Helper function for verifying the edxnotes tab.
"""
return self.check_tab(
tab_class=tabs.EdxNotesTab,
dict_tab={'type': tabs.EdxNotesTab.type, 'name': 'same'},
expected_link=self.reverse('edxnotes', args=[self.course.id.to_deprecated_string()]),
expected_tab_id=tabs.EdxNotesTab.type,
invalid_dict_tab=self.fake_dict_tab,
)
def test_edxnotes_tabs_enabled(self):
"""
Tests that edxnotes tab is shown when feature is enabled.
"""
self.settings.FEATURES['ENABLE_EDXNOTES'] = True
tab = self.check_edxnotes_tab()
self.check_can_display_results(tab, for_authenticated_users_only=True)
def test_edxnotes_tabs_disabled(self):
"""
Tests that edxnotes tab is not shown when feature is disabled.
"""
self.settings.FEATURES['ENABLE_EDXNOTES'] = False
tab = self.check_edxnotes_tab()
self.check_can_display_results(tab, expected_value=False)
class KeyCheckerTestCase(unittest.TestCase):
"""Test cases for KeyChecker class"""
@@ -473,6 +507,7 @@ class TabListTestCase(TabTestCase):
tabs.TextbookTabs.type,
tabs.PDFTextbookTabs.type,
tabs.HtmlTextbookTabs.type,
tabs.EdxNotesTab.type,
]
for unique_tab_type in unique_tab_types:
@@ -505,6 +540,7 @@ class TabListTestCase(TabTestCase):
{'type': tabs.OpenEndedGradingTab.type},
{'type': tabs.NotesTab.type, 'name': 'fake_name'},
{'type': tabs.SyllabusTab.type},
{'type': tabs.EdxNotesTab.type, 'name': 'fake_name'},
],
# with external discussion
[
@@ -565,6 +601,7 @@ class CourseTabListTestCase(TabListTestCase):
self.settings.FEATURES['ENABLE_TEXTBOOK'] = True
self.settings.FEATURES['ENABLE_DISCUSSION_SERVICE'] = True
self.settings.FEATURES['ENABLE_STUDENT_NOTES'] = True
self.settings.FEATURES['ENABLE_EDXNOTES'] = True
self.course.hide_progress_tab = False
# create 1 book per textbook type