From 0df7037650ca43c5bd134e88cb9c0b24a109c43e Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 10 Jun 2014 17:02:53 -0400 Subject: [PATCH 1/6] Send the serialized string to the track_function in split_test_module. --- common/lib/xmodule/xmodule/split_test_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/split_test_module.py b/common/lib/xmodule/xmodule/split_test_module.py index 40a772bcf2..15845978fb 100644 --- a/common/lib/xmodule/xmodule/split_test_module.py +++ b/common/lib/xmodule/xmodule/split_test_module.py @@ -226,7 +226,7 @@ class SplitTestModule(SplitTestFields, XModule): Record in the tracking logs which child was rendered """ # TODO: use publish instead, when publish is wired to the tracking logs - self.system.track_function('xblock.split_test.child_render', {'child-id': self.child.scope_ids.usage_id}) + self.system.track_function('xblock.split_test.child_render', {'child-id': self.child.scope_ids.usage_id.to_deprecated_string()}) return Response() def get_icon_class(self): From 643a7bfa95440c04cc0d5837f2d5fb086bc117a5 Mon Sep 17 00:00:00 2001 From: lduarte1991 Date: Wed, 28 May 2014 14:38:37 -0400 Subject: [PATCH 2/6] Annotation Tools: PR #3907 Annotation Tools: Load Tinymce in student_view Annotation Tools: Fixing tests for student_view Fixed typo from autocomplete Fixed dict for tests --- common/lib/xmodule/xmodule/imageannotation_module.py | 8 ++++++-- common/lib/xmodule/xmodule/tests/test_imageannotation.py | 4 ++-- common/lib/xmodule/xmodule/tests/test_textannotation.py | 4 ++-- common/lib/xmodule/xmodule/tests/test_videoannotation.py | 4 ++-- common/lib/xmodule/xmodule/textannotation_module.py | 9 ++++++--- common/lib/xmodule/xmodule/videoannotation_module.py | 9 ++++++--- lms/templates/imageannotation.html | 3 --- lms/templates/textannotation.html | 3 --- lms/templates/videoannotation.html | 4 ---- 9 files changed, 24 insertions(+), 24 deletions(-) diff --git a/common/lib/xmodule/xmodule/imageannotation_module.py b/common/lib/xmodule/xmodule/imageannotation_module.py index 9d5ab94083..14843f8885 100644 --- a/common/lib/xmodule/xmodule/imageannotation_module.py +++ b/common/lib/xmodule/xmodule/imageannotation_module.py @@ -9,6 +9,7 @@ from xmodule.raw_module import RawDescriptor from xblock.core import Scope, String from xmodule.annotator_mixin import get_instructions, html_to_text from xmodule.annotator_token import retrieve_token +from xblock.fragment import Fragment import textwrap @@ -91,7 +92,7 @@ class ImageAnnotationModule(AnnotatableFields, XModule): """ Removes from the xmltree and returns them as a string, otherwise None. """ return get_instructions(xmltree) - def get_html(self): + def student_view(self, context): """ Renders parameters to template. """ context = { 'display_name': self.display_name_with_default, @@ -102,7 +103,10 @@ class ImageAnnotationModule(AnnotatableFields, XModule): 'openseadragonjson': self.openseadragonjson, } - return self.system.render_template('imageannotation.html', context) + fragment = Fragment(self.system.render_template('imageannotation.html', context)) + fragment.add_javascript_url("/static/js/vendor/tinymce/js/tinymce/tinymce.full.min.js") + fragment.add_javascript_url("/static/js/vendor/tinymce/js/tinymce/jquery.tinymce.min.js") + return fragment class ImageAnnotationDescriptor(AnnotatableFields, RawDescriptor): # pylint: disable=abstract-method diff --git a/common/lib/xmodule/xmodule/tests/test_imageannotation.py b/common/lib/xmodule/xmodule/tests/test_imageannotation.py index c19ffc25d0..531d4784ac 100644 --- a/common/lib/xmodule/xmodule/tests/test_imageannotation.py +++ b/common/lib/xmodule/xmodule/tests/test_imageannotation.py @@ -69,10 +69,10 @@ class ImageAnnotationModuleTestCase(unittest.TestCase): actual = self.mod._extract_instructions(xmltree) # pylint: disable=protected-access self.assertIsNone(actual) - def test_get_html(self): + def test_student_view(self): """ Tests the function that passes in all the information in the context that will be used in templates/textannotation.html """ - context = self.mod.get_html() + context = self.mod.student_view({}).content for key in ['display_name', 'instructions_html', 'annotation_storage', 'token', 'tag', 'openseadragonjson']: self.assertIn(key, context) diff --git a/common/lib/xmodule/xmodule/tests/test_textannotation.py b/common/lib/xmodule/xmodule/tests/test_textannotation.py index 907eb78780..31d420dd08 100644 --- a/common/lib/xmodule/xmodule/tests/test_textannotation.py +++ b/common/lib/xmodule/xmodule/tests/test_textannotation.py @@ -54,10 +54,10 @@ class TextAnnotationModuleTestCase(unittest.TestCase): actual = self.mod._extract_instructions(xmltree) # pylint: disable=W0212 self.assertIsNone(actual) - def test_get_html(self): + def test_student_view(self): """ Tests the function that passes in all the information in the context that will be used in templates/textannotation.html """ - context = self.mod.get_html() + context = self.mod.student_view({}).content for key in ['display_name', 'tag', 'source', 'instructions_html', 'content_html', 'annotation_storage', 'token']: self.assertIn(key, context) diff --git a/common/lib/xmodule/xmodule/tests/test_videoannotation.py b/common/lib/xmodule/xmodule/tests/test_videoannotation.py index 410d39d4b1..0dc1a5d902 100644 --- a/common/lib/xmodule/xmodule/tests/test_videoannotation.py +++ b/common/lib/xmodule/xmodule/tests/test_videoannotation.py @@ -62,10 +62,10 @@ class VideoAnnotationModuleTestCase(unittest.TestCase): self.assertEqual(expectedyoutube, result2) self.assertEqual(expectednotyoutube, result1) - def test_get_html(self): + def test_student_view(self): """ Tests to make sure variables passed in truly exist within the html once it is all rendered. """ - context = self.mod.get_html() + context = self.mod.student_view({}).content for key in ['display_name', 'instructions_html', 'sourceUrl', 'typeSource', 'poster', 'annotation_storage']: self.assertIn(key, context) diff --git a/common/lib/xmodule/xmodule/textannotation_module.py b/common/lib/xmodule/xmodule/textannotation_module.py index 258f270787..f561dd2c2c 100644 --- a/common/lib/xmodule/xmodule/textannotation_module.py +++ b/common/lib/xmodule/xmodule/textannotation_module.py @@ -8,7 +8,7 @@ from xmodule.raw_module import RawDescriptor from xblock.core import Scope, String from xmodule.annotator_mixin import get_instructions from xmodule.annotator_token import retrieve_token - +from xblock.fragment import Fragment import textwrap # Make '_' a no-op so we can scrape strings @@ -73,7 +73,7 @@ class TextAnnotationModule(AnnotatableFields, XModule): """ Removes from the xmltree and returns them as a string, otherwise None. """ return get_instructions(xmltree) - def get_html(self): + def student_view(self, context): """ Renders parameters to template. """ context = { 'course_key': self.runtime.course_id, @@ -85,7 +85,10 @@ class TextAnnotationModule(AnnotatableFields, XModule): 'annotation_storage': self.annotation_storage_url, 'token': retrieve_token(self.user_email, self.annotation_token_secret), } - return self.system.render_template('textannotation.html', context) + fragment = Fragment(self.system.render_template('textannotation.html', context)) + fragment.add_javascript_url("/static/js/vendor/tinymce/js/tinymce/tinymce.full.min.js") + fragment.add_javascript_url("/static/js/vendor/tinymce/js/tinymce/jquery.tinymce.min.js") + return fragment class TextAnnotationDescriptor(AnnotatableFields, RawDescriptor): diff --git a/common/lib/xmodule/xmodule/videoannotation_module.py b/common/lib/xmodule/xmodule/videoannotation_module.py index f5bd7d3e93..d882d4e6a7 100644 --- a/common/lib/xmodule/xmodule/videoannotation_module.py +++ b/common/lib/xmodule/xmodule/videoannotation_module.py @@ -9,6 +9,7 @@ from xmodule.raw_module import RawDescriptor from xblock.core import Scope, String from xmodule.annotator_mixin import get_instructions, get_extension from xmodule.annotator_token import retrieve_token +from xblock.fragment import Fragment import textwrap @@ -72,7 +73,7 @@ class VideoAnnotationModule(AnnotatableFields, XModule): ''' get the extension of a given url ''' return get_extension(src_url) - def get_html(self): + def student_view(self, context): """ Renders parameters to template. """ extension = self._get_extension(self.sourceurl) @@ -87,8 +88,10 @@ class VideoAnnotationModule(AnnotatableFields, XModule): 'annotation_storage': self.annotation_storage_url, 'token': retrieve_token(self.user_email, self.annotation_token_secret), } - - return self.system.render_template('videoannotation.html', context) + fragment = Fragment(self.system.render_template('videoannotation.html', context)) + fragment.add_javascript_url("/static/js/vendor/tinymce/js/tinymce/tinymce.full.min.js") + fragment.add_javascript_url("/static/js/vendor/tinymce/js/tinymce/jquery.tinymce.min.js") + return fragment class VideoAnnotationDescriptor(AnnotatableFields, RawDescriptor): diff --git a/lms/templates/imageannotation.html b/lms/templates/imageannotation.html index 3342d296bb..e815c49506 100644 --- a/lms/templates/imageannotation.html +++ b/lms/templates/imageannotation.html @@ -31,8 +31,6 @@ <%namespace name='static' file='/static_content.html'/> ${static.css(group='style-vendor-tinymce-content', raw=True)} ${static.css(group='style-vendor-tinymce-skin', raw=True)} - \ No newline at end of file diff --git a/lms/templates/notes.html b/lms/templates/notes.html index 58f81be274..569393daaa 100644 --- a/lms/templates/notes.html +++ b/lms/templates/notes.html @@ -1,6 +1,10 @@ <%! from django.utils.translation import ugettext as _ %> <%namespace name='static' file='static_content.html'/> +${static.css(group='style-vendor-tinymce-content', raw=True)} +${static.css(group='style-vendor-tinymce-skin', raw=True)} + + <%inherit file="main.html" /> <%! from django.core.urlresolvers import reverse @@ -102,7 +106,7 @@ if (annotation.permissions) { tokens = annotation.permissions[action] || []; if (is_staff){ - return true; + return true; } if (tokens.length === 0) { return true; @@ -128,7 +132,7 @@ }, }, auth: { - tokenUrl: location.protocol+'//'+location.host+"/token?course_id=${course.id.to_deprecated_string()}" + token: "${token}" }, store: { // The endpoint of the store on your server. @@ -158,37 +162,34 @@ optionsRichText: { tinymce:{ selector: "li.annotator-item textarea", - plugins: "media image insertdatetime link code", + plugins: "media image codemirror", menubar: false, toolbar_items_size: 'small', extended_valid_elements : "iframe[src|frameborder|style|scrolling|class|width|height|name|align|id]", - toolbar: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image media rubric | code ", + toolbar: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | image rubric | code ", } - return true; - }, - }, - auth: { - token: "${token}" - }, - store: { - // The endpoint of the store on your server. - prefix: "${storage}", - - annotationData: {}, - - urls: { - // These are the default URLs. - create: '/create', - read: '/read/:id', - update: '/update/:id', - destroy: '/delete/:id', - search: '/search' }, + auth: { + token: "${token}" + }, + store: { + // The endpoint of the store on your server. + prefix: "${storage}", + + annotationData: {}, + + urls: { + // These are the default URLs. + create: '/create', + read: '/read/:id', + update: '/update/:id', + destroy: '/delete/:id', + search: '/search' + }, + } }; - tinyMCE.baseURL = "${settings.STATIC_URL}" + "js/vendor/ova"; var imgURLRoot = "${settings.STATIC_URL}" + "js/vendor/ova/catch/img/"; - //remove old instances if (Annotator._instances.length !== 0) { $('#notesHolder').annotator("destroy"); @@ -207,7 +208,8 @@ showMediaSelector: true, showPublicPrivate: true, pagination:pagination,//Number of Annotations per load in the pagination, - flags:is_staff + flags:is_staff, + default_tab: "${default_tab}", }, Catch = new CatchAnnotation($('#catchDIV'),catchOptions); diff --git a/lms/templates/textannotation.html b/lms/templates/textannotation.html index 5d97ac11c8..cb0cd23a35 100644 --- a/lms/templates/textannotation.html +++ b/lms/templates/textannotation.html @@ -174,17 +174,24 @@ ${static.css(group='style-vendor-tinymce-skin', raw=True)} //Load the plugin Video/Text Annotation var ova = new OpenVideoAnnotation.Annotator($('#textHolder'),options); + var userId = ('${default_tab}'.toLowerCase() === 'instructor') ? + '${instructor_email}': + '${user.email}'; + //Catch - var annotator = ova.annotator, - catchOptions = { + var annotator = ova.annotator; + var catchOptions = { media:'text', externalLink:false, imageUrlRoot:imgURLRoot, showMediaSelector: false, showPublicPrivate: true, - userId:'${user.email}', + userId:userId, pagination:pagination,//Number of Annotations per load in the pagination, - flags:is_staff - }, - Catch = new CatchAnnotation($('#catchDIV'),catchOptions); + flags:is_staff, + default_tab: "${default_tab}", + instructor_email: "${instructor_email}", + annotation_mode: "${annotation_mode}", + }; + var Catch = new CatchAnnotation($('#catchDIV'),catchOptions); diff --git a/lms/templates/videoannotation.html b/lms/templates/videoannotation.html index 539e86909a..07b61213c5 100644 --- a/lms/templates/videoannotation.html +++ b/lms/templates/videoannotation.html @@ -175,18 +175,24 @@ ${static.css(group='style-vendor-tinymce-skin', raw=True)} var ova = new OpenVideoAnnotation.Annotator($('#videoHolder'),options); ova.annotator.addPlugin('Tags'); + var userId = ('${default_tab}'.toLowerCase() === 'instructor') ? + '${instructor_email}': + '${user.email}'; //Catch - var annotator = ova.annotator, - catchOptions = { + var annotator = ova.annotator; + var catchOptions = { media:'video', externalLink:false, imageUrlRoot:imgURLRoot, showMediaSelector: false, showPublicPrivate: true, - userId:'${user.email}', + userId:userId, pagination:pagination,//Number of Annotations per load in the pagination, - flags:is_staff - }, - Catch = new CatchAnnotation($('#catchDIV'),catchOptions); + flags:is_staff, + default_tab: "${default_tab}", + instructor_email: "${instructor_email}", + annotation_mode: "${annotation_mode}", + }; + var Catch = new CatchAnnotation($('#catchDIV'),catchOptions); From 848c7165e3a2da42f11986846e0891c44a4f0a54 Mon Sep 17 00:00:00 2001 From: lduarte1991 Date: Fri, 6 Jun 2014 15:40:03 -0400 Subject: [PATCH 4/6] Annotation Tool: PR #4019 Reverting commit @3bfb633 and updating openseadragon.js for latest build Conflicts: common/lib/xmodule/xmodule/imageannotation_module.py common/lib/xmodule/xmodule/textannotation_module.py common/lib/xmodule/xmodule/videoannotation_module.py --- common/lib/xmodule/xmodule/annotator_mixin.py | 38 ------------------- .../xmodule/xmodule/imageannotation_module.py | 31 ++++++++++++--- .../xmodule/xmodule/textannotation_module.py | 37 ++++++++++++++++-- .../xmodule/xmodule/videoannotation_module.py | 37 ++++++++++++++++-- common/static/js/vendor/ova/openseadragon.js | 4 +- 5 files changed, 93 insertions(+), 54 deletions(-) diff --git a/common/lib/xmodule/xmodule/annotator_mixin.py b/common/lib/xmodule/xmodule/annotator_mixin.py index 93768f564c..aa0a19d4a8 100644 --- a/common/lib/xmodule/xmodule/annotator_mixin.py +++ b/common/lib/xmodule/xmodule/annotator_mixin.py @@ -6,10 +6,6 @@ from lxml import etree from urlparse import urlparse from os.path import splitext, basename from HTMLParser import HTMLParser -from xblock.core import Scope, String - -# Make '_' a no-op so we can scrape strings -_ = lambda text: text def get_instructions(xmltree): """ Removes from the xmltree and returns them as a string, otherwise None. """ @@ -56,37 +52,3 @@ def html_to_text(html): htmlstripper = MLStripper() htmlstripper.feed(html) return htmlstripper.get_data() - - -class CommonAnnotatorMixin(object): - annotation_storage_url = String( - help=_("Location of Annotation backend"), - scope=Scope.settings, - default="http://your_annotation_storage.com", - display_name=_("Url for Annotation Storage") - ) - annotation_token_secret = String( - help=_("Secret string for annotation storage"), - scope=Scope.settings, - default="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - display_name=_("Secret Token String for Annotation") - ) - default_tab = String( - display_name=_("Default Annotations Tab"), - help=_("Select which tab will be the default in the annotations table: myNotes, Instructor, or Public."), - scope=Scope.settings, - default="myNotes", - ) - # currently only supports one instructor, will build functionality for multiple later - instructor_email = String( - display_name=_("Email for 'Instructor' Annotations"), - help=_("Email of the user that will be attached to all annotations that will be found in 'Instructor' tab."), - scope=Scope.settings, - default="", - ) - annotation_mode = String( - display_name=_("Mode for Annotation Tool"), - help=_("Type in number corresponding to following modes: 'instructor' or 'everyone'"), - scope=Scope.settings, - default="everyone", - ) diff --git a/common/lib/xmodule/xmodule/imageannotation_module.py b/common/lib/xmodule/xmodule/imageannotation_module.py index e19f5a9100..dd01d9ae2e 100644 --- a/common/lib/xmodule/xmodule/imageannotation_module.py +++ b/common/lib/xmodule/xmodule/imageannotation_module.py @@ -7,7 +7,7 @@ from pkg_resources import resource_string from xmodule.x_module import XModule from xmodule.raw_module import RawDescriptor from xblock.core import Scope, String -from xmodule.annotator_mixin import CommonAnnotatorMixin, get_instructions, html_to_text +from xmodule.annotator_mixin import get_instructions, html_to_text from xmodule.annotator_token import retrieve_token from xblock.fragment import Fragment @@ -49,20 +49,39 @@ class AnnotatableFields(object): default='professor:green,teachingAssistant:blue', ) annotation_storage_url = String( - help="Location of Annotation backend", + help=_("Location of Annotation backend"), scope=Scope.settings, default="http://your_annotation_storage.com", - display_name="Url for Annotation Storage" + display_name=_("Url for Annotation Storage") ) annotation_token_secret = String( - help="Secret string for annotation storage", + help=_("Secret string for annotation storage"), scope=Scope.settings, default="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - display_name="Secret Token String for Annotation" + display_name=_("Secret Token String for Annotation") + ) + default_tab = String( + display_name=_("Default Annotations Tab"), + help=_("Select which tab will be the default in the annotations table: myNotes, Instructor, or Public."), + scope=Scope.settings, + default="myNotes", + ) + # currently only supports one instructor, will build functionality for multiple later + instructor_email = String( + display_name=_("Email for 'Instructor' Annotations"), + help=_("Email of the user that will be attached to all annotations that will be found in 'Instructor' tab."), + scope=Scope.settings, + default="", + ) + annotation_mode = String( + display_name=_("Mode for Annotation Tool"), + help=_("Type in number corresponding to following modes: 'instructor' or 'everyone'"), + scope=Scope.settings, + default="everyone", ) -class ImageAnnotationModule(AnnotatableFields, CommonAnnotatorMixin, XModule): +class ImageAnnotationModule(AnnotatableFields, XModule): '''Image Annotation Module''' js = { 'coffee': [ diff --git a/common/lib/xmodule/xmodule/textannotation_module.py b/common/lib/xmodule/xmodule/textannotation_module.py index 1224dc2524..4504ac5b3b 100644 --- a/common/lib/xmodule/xmodule/textannotation_module.py +++ b/common/lib/xmodule/xmodule/textannotation_module.py @@ -6,7 +6,7 @@ from pkg_resources import resource_string from xmodule.x_module import XModule from xmodule.raw_module import RawDescriptor from xblock.core import Scope, String -from xmodule.annotator_mixin import CommonAnnotatorMixin, get_instructions +from xmodule.annotator_mixin import get_instructions from xmodule.annotator_token import retrieve_token from xblock.fragment import Fragment import textwrap @@ -53,11 +53,40 @@ class AnnotatableFields(object): scope=Scope.settings, default='', ) - annotation_storage_url = String(help=_("Location of Annotation backend"), scope=Scope.settings, default="http://your_annotation_storage.com", display_name=_("Url for Annotation Storage")) - annotation_token_secret = String(help=_("Secret string for annotation storage"), scope=Scope.settings, default="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", display_name=_("Secret Token String for Annotation")) + annotation_storage_url = String( + help=_("Location of Annotation backend"), + scope=Scope.settings, + default="http://your_annotation_storage.com", + display_name=_("Url for Annotation Storage") + ) + annotation_token_secret = String( + help=_("Secret string for annotation storage"), + scope=Scope.settings, + default="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + display_name=_("Secret Token String for Annotation") + ) + default_tab = String( + display_name=_("Default Annotations Tab"), + help=_("Select which tab will be the default in the annotations table: myNotes, Instructor, or Public."), + scope=Scope.settings, + default="myNotes", + ) + # currently only supports one instructor, will build functionality for multiple later + instructor_email = String( + display_name=_("Email for 'Instructor' Annotations"), + help=_("Email of the user that will be attached to all annotations that will be found in 'Instructor' tab."), + scope=Scope.settings, + default="", + ) + annotation_mode = String( + display_name=_("Mode for Annotation Tool"), + help=_("Type in number corresponding to following modes: 'instructor' or 'everyone'"), + scope=Scope.settings, + default="everyone", + ) -class TextAnnotationModule(AnnotatableFields, CommonAnnotatorMixin, XModule): +class TextAnnotationModule(AnnotatableFields, XModule): ''' Text Annotation Module ''' js = {'coffee': [], 'js': []} diff --git a/common/lib/xmodule/xmodule/videoannotation_module.py b/common/lib/xmodule/xmodule/videoannotation_module.py index b07f9a700f..03fc2f75e3 100644 --- a/common/lib/xmodule/xmodule/videoannotation_module.py +++ b/common/lib/xmodule/xmodule/videoannotation_module.py @@ -7,7 +7,7 @@ from pkg_resources import resource_string from xmodule.x_module import XModule from xmodule.raw_module import RawDescriptor from xblock.core import Scope, String -from xmodule.annotator_mixin import CommonAnnotatorMixin, get_instructions, get_extension +from xmodule.annotator_mixin import get_instructions, get_extension from xmodule.annotator_token import retrieve_token from xblock.fragment import Fragment @@ -45,11 +45,40 @@ class AnnotatableFields(object): scope=Scope.settings, default="" ) - annotation_storage_url = String(help=_("Location of Annotation backend"), scope=Scope.settings, default="http://your_annotation_storage.com", display_name=_("Url for Annotation Storage")) - annotation_token_secret = String(help=_("Secret string for annotation storage"), scope=Scope.settings, default="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", display_name=_("Secret Token String for Annotation")) + annotation_storage_url = String( + help=_("Location of Annotation backend"), + scope=Scope.settings, + default="http://your_annotation_storage.com", + display_name=_("Url for Annotation Storage") + ) + annotation_token_secret = String( + help=_("Secret string for annotation storage"), + scope=Scope.settings, + default="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + display_name=_("Secret Token String for Annotation") + ) + default_tab = String( + display_name=_("Default Annotations Tab"), + help=_("Select which tab will be the default in the annotations table: myNotes, Instructor, or Public."), + scope=Scope.settings, + default="myNotes", + ) + # currently only supports one instructor, will build functionality for multiple later + instructor_email = String( + display_name=_("Email for 'Instructor' Annotations"), + help=_("Email of the user that will be attached to all annotations that will be found in 'Instructor' tab."), + scope=Scope.settings, + default="", + ) + annotation_mode = String( + display_name=_("Mode for Annotation Tool"), + help=_("Type in number corresponding to following modes: 'instructor' or 'everyone'"), + scope=Scope.settings, + default="everyone", + ) -class VideoAnnotationModule(AnnotatableFields, CommonAnnotatorMixin, XModule): +class VideoAnnotationModule(AnnotatableFields, XModule): '''Video Annotation Module''' js = { 'coffee': [ diff --git a/common/static/js/vendor/ova/openseadragon.js b/common/static/js/vendor/ova/openseadragon.js index 789c0ce403..559aa5bcd7 100644 --- a/common/static/js/vendor/ova/openseadragon.js +++ b/common/static/js/vendor/ova/openseadragon.js @@ -7872,14 +7872,14 @@ $.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, { uri; if ( level_width < this.tile_width && level_height < this.tile_height ){ - iiif_size = level_width + "," + level_height; + iiif_size = level_width + ","; iiif_region = 'full'; } else { iiif_tile_x = x * iiif_tile_size_width; iiif_tile_y = y * iiif_tile_size_height; iiif_tile_w = Math.min( iiif_tile_size_width, this.width - iiif_tile_x ); iiif_tile_h = Math.min( iiif_tile_size_height, this.height - iiif_tile_y ); - iiif_size = Math.ceil(iiif_tile_w * scale) + "," + Math.ceil(iiif_tile_h * scale); + iiif_size = Math.ceil(iiif_tile_w * scale) + ","; iiif_region = [ iiif_tile_x, iiif_tile_y, iiif_tile_w, iiif_tile_h ].join(','); } uri = [ this['@id'], iiif_region, iiif_size, IIIF_ROTATION, IIIF_QUALITY ].join('/'); From e48cc16d45ceda1ec8fe64b7092b2a1de25329c8 Mon Sep 17 00:00:00 2001 From: lduarte1991 Date: Mon, 9 Jun 2014 14:04:54 -0400 Subject: [PATCH 5/6] Annotations Tools: i18n Make stati strings extractable Original changes found in this commit https://github.com/edx/edx-platform/commit/a6bae4d238fdc6a60c8ee9f1b80ca 3512bb085eb Conflicts: common/lib/xmodule/xmodule/textannotation_module.py common/lib/xmodule/xmodule/videoannotation_module.py --- .../lib/xmodule/xmodule/imageannotation_module.py | 15 ++++++++++----- .../lib/xmodule/xmodule/textannotation_module.py | 4 +++- .../lib/xmodule/xmodule/videoannotation_module.py | 4 +++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/common/lib/xmodule/xmodule/imageannotation_module.py b/common/lib/xmodule/xmodule/imageannotation_module.py index dd01d9ae2e..7a37b35334 100644 --- a/common/lib/xmodule/xmodule/imageannotation_module.py +++ b/common/lib/xmodule/xmodule/imageannotation_module.py @@ -13,10 +13,15 @@ from xblock.fragment import Fragment import textwrap +# Make '_' a no-op so we can scrape strings +_ = lambda text: text + class AnnotatableFields(object): """ Fields for `ImageModule` and `ImageDescriptor`. """ - data = String(help="XML data for the annotation", scope=Scope.content, default=textwrap.dedent("""\ + data = String(help=_("XML data for the annotation"), + scope=Scope.content, + default=textwrap.dedent("""\

@@ -37,14 +42,14 @@ class AnnotatableFields(object): """)) display_name = String( - display_name="Display Name", - help="Display name for this module", + display_name=_("Display Name"), + help=_("Display name for this module"), scope=Scope.settings, default='Image Annotation', ) instructor_tags = String( - display_name="Tags for Assignments", - help="Add tags that automatically highlight in a certain color using the comma-separated form, i.e. imagery:red,parallelism:blue", + display_name=_("Tags for Assignments"), + help=_("Add tags that automatically highlight in a certain color using the comma-separated form, i.e. imagery:red,parallelism:blue"), scope=Scope.settings, default='professor:green,teachingAssistant:blue', ) diff --git a/common/lib/xmodule/xmodule/textannotation_module.py b/common/lib/xmodule/xmodule/textannotation_module.py index 4504ac5b3b..35b1fd8c96 100644 --- a/common/lib/xmodule/xmodule/textannotation_module.py +++ b/common/lib/xmodule/xmodule/textannotation_module.py @@ -17,7 +17,9 @@ _ = lambda text: text class AnnotatableFields(object): """Fields for `TextModule` and `TextDescriptor`.""" - data = String(help=_("XML data for the annotation"), scope=Scope.content, default=textwrap.dedent("""\ + data = String(help=_("XML data for the annotation"), + scope=Scope.content, + default=textwrap.dedent("""\

diff --git a/common/lib/xmodule/xmodule/videoannotation_module.py b/common/lib/xmodule/xmodule/videoannotation_module.py index 03fc2f75e3..6a8584505b 100644 --- a/common/lib/xmodule/xmodule/videoannotation_module.py +++ b/common/lib/xmodule/xmodule/videoannotation_module.py @@ -19,7 +19,9 @@ _ = lambda text: text class AnnotatableFields(object): """ Fields for `VideoModule` and `VideoDescriptor`. """ - data = String(help=_("XML data for the annotation"), scope=Scope.content, default=textwrap.dedent("""\ + data = String(help=_("XML data for the annotation"), + scope=Scope.content, + default=textwrap.dedent("""\

From f6ea16223b3db02eaaa414c750b02731a7380406 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 11 Jun 2014 12:06:37 -0400 Subject: [PATCH 6/6] Allow users that have unregistered from a verified course to re-register. Fixes LMS-2830 --- .../course_modes/tests/factories.py | 4 +- .../course_modes/tests/test_views.py | 100 ++++++++++++++++++ common/djangoapps/course_modes/views.py | 14 +-- common/djangoapps/student/models.py | 13 +-- common/djangoapps/student/tests/factories.py | 5 +- lms/djangoapps/certificates/queue.py | 2 +- lms/djangoapps/verify_student/views.py | 6 +- 7 files changed, 123 insertions(+), 21 deletions(-) create mode 100644 common/djangoapps/course_modes/tests/test_views.py diff --git a/common/djangoapps/course_modes/tests/factories.py b/common/djangoapps/course_modes/tests/factories.py index 4c32dea10f..0cfb439700 100644 --- a/common/djangoapps/course_modes/tests/factories.py +++ b/common/djangoapps/course_modes/tests/factories.py @@ -1,5 +1,6 @@ from course_modes.models import CourseMode from factory.django import DjangoModelFactory +from xmodule.modulestore.locations import SlashSeparatedCourseKey # Factories don't have __init__ methods, and are self documenting @@ -7,9 +8,10 @@ from factory.django import DjangoModelFactory class CourseModeFactory(DjangoModelFactory): FACTORY_FOR = CourseMode - course_id = u'MITx/999/Robot_Super_Course' + course_id = SlashSeparatedCourseKey('MITx', '999', 'Robot_Super_Course') mode_slug = 'audit' mode_display_name = 'audit course' min_price = 0 currency = 'usd' expiration_datetime = None + suggested_prices = '' diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py new file mode 100644 index 0000000000..9de6f3c09f --- /dev/null +++ b/common/djangoapps/course_modes/tests/test_views.py @@ -0,0 +1,100 @@ +import ddt +import unittest +from django.test import TestCase +from django.conf import settings +from django.core.urlresolvers import reverse +from mock import patch, Mock + +from course_modes.tests.factories import CourseModeFactory +from student.tests.factories import CourseEnrollmentFactory, UserFactory +from xmodule.modulestore.locations import SlashSeparatedCourseKey + + +@ddt.ddt +class CourseModeViewTest(TestCase): + + def setUp(self): + self.course_id = SlashSeparatedCourseKey('org', 'course', 'run') + + for mode in ('audit', 'verified', 'honor'): + CourseModeFactory(mode_slug=mode, course_id=self.course_id) + + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') + @ddt.data( + # is_active?, enrollment_mode, upgrade?, redirect? + (True, 'verified', True, True), # User is already verified + (True, 'verified', False, True), # User is already verified + (True, 'honor', True, False), # User isn't trying to upgrade + (True, 'honor', False, True), # User is trying to upgrade + (True, 'audit', True, False), # User isn't trying to upgrade + (True, 'audit', False, True), # User is trying to upgrade + (False, 'verified', True, False), # User isn't active + (False, 'verified', False, False), # User isn't active + (False, 'honor', True, False), # User isn't active + (False, 'honor', False, False), # User isn't active + (False, 'audit', True, False), # User isn't active + (False, 'audit', False, False), # User isn't active + ) + @ddt.unpack + @patch('course_modes.views.modulestore', Mock()) + def test_reregister_redirect(self, is_active, enrollment_mode, upgrade, redirect): + enrollment = CourseEnrollmentFactory( + is_active=is_active, + mode=enrollment_mode, + course_id=self.course_id + ) + + self.client.login( + username=enrollment.user.username, + password='test' + ) + + if upgrade: + get_params = {'upgrade': True} + else: + get_params = {} + + response = self.client.get( + reverse('course_modes_choose', args=[self.course_id.to_deprecated_string()]), + get_params, + follow=False, + ) + + if redirect: + self.assertEquals(response.status_code, 302) + self.assertTrue(response['Location'].endswith(reverse('dashboard'))) + else: + self.assertEquals(response.status_code, 200) + # TODO: Fix it so that response.templates works w/ mako templates, and then assert + # that the right template rendered + + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') + @ddt.data( + '', + '1,,2', + '1, ,2', + '1, 2, 3' + ) + @patch('course_modes.views.modulestore', Mock()) + def test_suggested_prices(self, price_list): + course_id = SlashSeparatedCourseKey('org', 'course', 'price_course') + user = UserFactory() + + for mode in ('audit', 'honor'): + CourseModeFactory(mode_slug=mode, course_id=course_id) + + CourseModeFactory(mode_slug='verified', course_id=course_id, suggested_prices=price_list) + + self.client.login( + username=user.username, + password='test' + ) + + response = self.client.get( + reverse('course_modes_choose', args=[self.course_id.to_deprecated_string()]), + follow=False, + ) + + self.assertEquals(response.status_code, 200) + # TODO: Fix it so that response.templates works w/ mako templates, and then assert + # that the right template rendered diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index 68b44e61cb..62855f65f6 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -36,16 +36,14 @@ class ChooseModeView(View): course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) - enrollment_mode = CourseEnrollment.enrollment_mode_for_user(request.user, course_key) + enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(request.user, course_key) upgrade = request.GET.get('upgrade', False) request.session['attempting_upgrade'] = upgrade + # Inactive users always need to re-register # verified users do not need to register or upgrade - if enrollment_mode == 'verified': - return redirect(reverse('dashboard')) - # registered users who are not trying to upgrade do not need to re-register - if enrollment_mode is not None and upgrade is False: + if is_active and (upgrade is False or enrollment_mode == 'verified'): return redirect(reverse('dashboard')) modes = CourseMode.modes_for_course_dict(course_key) @@ -64,7 +62,11 @@ class ChooseModeView(View): "upgrade": upgrade, } if "verified" in modes: - context["suggested_prices"] = [decimal.Decimal(x) for x in modes["verified"].suggested_prices.split(",")] + context["suggested_prices"] = [ + decimal.Decimal(x.strip()) + for x in modes["verified"].suggested_prices.split(",") + if x.strip() + ] context["currency"] = modes["verified"].currency.upper() context["min_price"] = modes["verified"].min_price diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 6faec29c0e..8654a32110 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -880,18 +880,15 @@ class CourseEnrollment(models.Model): `user` is a Django User object `course_id` is our usual course_id string (e.g. "edX/Test101/2013_Fall) - Returns the mode for both inactive and active users. - Returns None if the courseenrollment record does not exist. + Returns (mode, is_active) where mode is the enrollment mode of the student + and is_active is whether the enrollment is active. + Returns (None, None) if the courseenrollment record does not exist. """ try: record = CourseEnrollment.objects.get(user=user, course_id=course_id) - - if hasattr(record, 'mode'): - return record.mode - else: - return None + return (record.mode, record.is_active) except cls.DoesNotExist: - return None + return (None, None) @classmethod def enrollments_for_user(cls, user): diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py index d44ee95b80..6ad1962c4e 100644 --- a/common/djangoapps/student/tests/factories.py +++ b/common/djangoapps/student/tests/factories.py @@ -10,6 +10,7 @@ import factory from factory.django import DjangoModelFactory from uuid import uuid4 from pytz import UTC +from xmodule.modulestore.locations import SlashSeparatedCourseKey # Factories don't have __init__ methods, and are self documenting # pylint: disable=W0232, C0111 @@ -109,14 +110,14 @@ class CourseEnrollmentFactory(DjangoModelFactory): FACTORY_FOR = CourseEnrollment user = factory.SubFactory(UserFactory) - course_id = u'edX/toy/2012_Fall' + course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') class CourseEnrollmentAllowedFactory(DjangoModelFactory): FACTORY_FOR = CourseEnrollmentAllowed email = 'test@edx.org' - course_id = 'edX/test/2012_Fall' + course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') class PendingEmailChangeFactory(DjangoModelFactory): diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index 105eab04e4..cbcad9ea2b 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -181,7 +181,7 @@ class XQueueCertInterface(object): course_name = course.display_name or course_id.to_deprecated_string() is_whitelisted = self.whitelist.filter(user=student, course_id=course_id, whitelist=True).exists() grade = grades.grade(student, self.request, course) - enrollment_mode = CourseEnrollment.enrollment_mode_for_user(student, course_id) + enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(student, course_id) mode_is_verified = (enrollment_mode == GeneratedCertificate.MODES.verified) user_is_verified = SoftwareSecurePhotoVerification.user_is_verified(student) user_is_reverified = SoftwareSecurePhotoVerification.user_is_reverified_for_all(course_id, student) diff --git a/lms/djangoapps/verify_student/views.py b/lms/djangoapps/verify_student/views.py index 17d1513667..0d2754ff7c 100644 --- a/lms/djangoapps/verify_student/views.py +++ b/lms/djangoapps/verify_student/views.py @@ -65,7 +65,7 @@ class VerifyView(View): reverse('verify_student_verified', kwargs={'course_id': course_id.to_deprecated_string()}) + "?upgrade={}".format(upgrade) ) - elif CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == 'verified': + elif CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == ('verified', True): return redirect(reverse('dashboard')) else: # If they haven't completed a verification attempt, we have to @@ -119,7 +119,7 @@ class VerifiedView(View): """ upgrade = request.GET.get('upgrade', False) course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) - if CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == 'verified': + if CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == ('verified', True): return redirect(reverse('dashboard')) verify_mode = CourseMode.mode_for_course(course_id, "verified") @@ -284,7 +284,7 @@ def show_requirements(request, course_id): Show the requirements necessary for the verification flow. """ course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) - if CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == 'verified': + if CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == ('verified', True): return redirect(reverse('dashboard')) upgrade = request.GET.get('upgrade', False)