@@ -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 = ''
|
||||
|
||||
100
common/djangoapps/course_modes/tests/test_views.py
Normal file
100
common/djangoapps/course_modes/tests/test_views.py
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -7,7 +7,6 @@ from urlparse import urlparse
|
||||
from os.path import splitext, basename
|
||||
from HTMLParser import HTMLParser
|
||||
|
||||
|
||||
def get_instructions(xmltree):
|
||||
""" Removes <instructions> from the xmltree and returns them as a string, otherwise None. """
|
||||
instructions = xmltree.find('instructions')
|
||||
|
||||
@@ -9,13 +9,19 @@ 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
|
||||
|
||||
# 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("""\
|
||||
<annotatable>
|
||||
<instructions>
|
||||
<p>
|
||||
@@ -36,28 +42,47 @@ class AnnotatableFields(object):
|
||||
</annotatable>
|
||||
"""))
|
||||
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',
|
||||
)
|
||||
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",
|
||||
)
|
||||
|
||||
|
||||
@@ -91,18 +116,23 @@ class ImageAnnotationModule(AnnotatableFields, XModule):
|
||||
""" Removes <instructions> 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,
|
||||
'instructions_html': self.instructions,
|
||||
'annotation_storage': self.annotation_storage_url,
|
||||
'token': retrieve_token(self.user, self.annotation_token_secret),
|
||||
'tag': self.instructor_tags,
|
||||
'openseadragonjson': self.openseadragonjson,
|
||||
'annotation_storage': self.annotation_storage_url,
|
||||
'default_tab': self.default_tab,
|
||||
'instructor_email': self.instructor_email,
|
||||
'annotation_mode': self.annotation_mode,
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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("""\
|
||||
<annotatable>
|
||||
<instructions>
|
||||
<p>
|
||||
@@ -47,8 +49,43 @@ class AnnotatableFields(object):
|
||||
scope=Scope.settings,
|
||||
default='None',
|
||||
)
|
||||
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"))
|
||||
diacritics = String(
|
||||
display_name=_("Diacritic Marks"),
|
||||
help=_("Add diacritic marks to be added to a text using the comma-separated form, i.e. markname;urltomark;baseline,markname2;urltomark2;baseline2"),
|
||||
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")
|
||||
)
|
||||
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, XModule):
|
||||
@@ -73,7 +110,7 @@ class TextAnnotationModule(AnnotatableFields, XModule):
|
||||
""" Removes <instructions> 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,
|
||||
@@ -82,10 +119,17 @@ class TextAnnotationModule(AnnotatableFields, XModule):
|
||||
'source': self.source,
|
||||
'instructions_html': self.instructions,
|
||||
'content_html': self.content,
|
||||
'annotation_storage': self.annotation_storage_url,
|
||||
'token': retrieve_token(self.user_email, self.annotation_token_secret),
|
||||
'diacritic_marks': self.diacritics,
|
||||
'annotation_storage': self.annotation_storage_url,
|
||||
'default_tab': self.default_tab,
|
||||
'instructor_email': self.instructor_email,
|
||||
'annotation_mode': self.annotation_mode,
|
||||
}
|
||||
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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,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("""\
|
||||
<annotatable>
|
||||
<instructions>
|
||||
<p>
|
||||
@@ -31,12 +34,51 @@ class AnnotatableFields(object):
|
||||
display_name=_("Display Name"),
|
||||
help=_("Display name for this module"),
|
||||
scope=Scope.settings,
|
||||
default='Video Annotation',
|
||||
default=_('Video Annotation'),
|
||||
)
|
||||
sourceurl = String(help=_("The external source URL for the video."), display_name=_("Source URL"), scope=Scope.settings, default="http://video-js.zencoder.com/oceans-clip.mp4")
|
||||
poster_url = String(help=_("Poster Image URL"), display_name=_("Poster URL"), 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"))
|
||||
sourceurl = String(
|
||||
help=_("The external source URL for the video."),
|
||||
display_name=_("Source URL"),
|
||||
scope=Scope.settings, default="http://video-js.zencoder.com/oceans-clip.mp4"
|
||||
)
|
||||
poster_url = String(
|
||||
help=_("Poster Image URL"),
|
||||
display_name=_("Poster URL"),
|
||||
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")
|
||||
)
|
||||
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, XModule):
|
||||
'''Video Annotation Module'''
|
||||
@@ -72,7 +114,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)
|
||||
|
||||
@@ -84,11 +126,16 @@ class VideoAnnotationModule(AnnotatableFields, XModule):
|
||||
'typeSource': extension,
|
||||
'poster': self.poster_url,
|
||||
'content_html': self.content,
|
||||
'annotation_storage': self.annotation_storage_url,
|
||||
'token': retrieve_token(self.user_email, self.annotation_token_secret),
|
||||
'annotation_storage': self.annotation_storage_url,
|
||||
'default_tab': self.default_tab,
|
||||
'instructor_email': self.instructor_email,
|
||||
'annotation_mode': self.annotation_mode,
|
||||
}
|
||||
|
||||
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):
|
||||
|
||||
@@ -380,19 +380,20 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
clickTimeThreshold: viewer.clickTimeThreshold,
|
||||
clickDistThreshold: viewer.clickDistThreshold
|
||||
});
|
||||
|
||||
/* Set elements to the control menu */
|
||||
viewer.annotatorControl = viewer.wrapperAnnotation.element;
|
||||
if( viewer.toolbar ){
|
||||
viewer.toolbar.addControl(
|
||||
viewer.annotatorControl,
|
||||
{anchor: $.ControlAnchor.BOTTOM_RIGHT}
|
||||
);
|
||||
}else{
|
||||
viewer.addControl(
|
||||
viewer.annotatorControl,
|
||||
{anchor: $.ControlAnchor.TOP_LEFT}
|
||||
);
|
||||
if(this.options.viewer.annotation_mode == "everyone" || this.options.viewer.flags){
|
||||
/* Set elements to the control menu */
|
||||
viewer.annotatorControl = viewer.wrapperAnnotation.element;
|
||||
if( viewer.toolbar ){
|
||||
viewer.toolbar.addControl(
|
||||
viewer.annotatorControl,
|
||||
{anchor: $.ControlAnchor.BOTTOM_RIGHT}
|
||||
);
|
||||
}else{
|
||||
viewer.addControl(
|
||||
viewer.annotatorControl,
|
||||
{anchor: $.ControlAnchor.TOP_LEFT}
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
_reset: function(){
|
||||
|
||||
20
common/static/js/vendor/ova/catch/css/main.css
vendored
20
common/static/js/vendor/ova/catch/css/main.css
vendored
@@ -379,14 +379,14 @@
|
||||
display:inline-block;
|
||||
color:#302f2f;
|
||||
font-family:arial;
|
||||
font-size:15px;
|
||||
font-size:14px;
|
||||
font-weight:bold;
|
||||
padding:6px 24px;
|
||||
text-decoration:none;
|
||||
|
||||
margin: 0px 0px 10px 0px;
|
||||
cursor:pointer;
|
||||
width:140px;
|
||||
width:115px;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
@@ -468,7 +468,7 @@
|
||||
#mainCatch .searchbox input{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 60%;
|
||||
width: 50%;
|
||||
margin-left: 10px;
|
||||
display: inline;
|
||||
float: left;
|
||||
@@ -493,19 +493,28 @@
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
#mainCatch .searchbox .clear-search-icon{
|
||||
font-size: 12px;
|
||||
text-decoration: underline;
|
||||
float: right;
|
||||
margin-top: 10px;
|
||||
padding-right: 3px;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
#mainCatch .searchbox .search-icon:hover{
|
||||
opacity:0.5;
|
||||
box-shadow: 2px 4px 5px #888888;
|
||||
}
|
||||
|
||||
#mainCatch .selectors{
|
||||
width:40%;
|
||||
width:45%;
|
||||
position:relative;
|
||||
float:left;
|
||||
}
|
||||
|
||||
#mainCatch .searchbox{
|
||||
width:60%;
|
||||
width:52%;
|
||||
position:relative;
|
||||
float:right;
|
||||
}
|
||||
@@ -515,6 +524,7 @@
|
||||
position:relative;
|
||||
padding-right:5px;
|
||||
margin-top:8px;
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
#mainCatch .replies .replyItem .deleteReply{
|
||||
|
||||
171
common/static/js/vendor/ova/catch/js/catch.js
vendored
171
common/static/js/vendor/ova/catch/js/catch.js
vendored
@@ -42,29 +42,29 @@ annotationList:
|
||||
'</div>'+
|
||||
|
||||
'<div class="annotatedBy field">'+
|
||||
'User'+
|
||||
gettext('User')+
|
||||
'</div>'+
|
||||
|
||||
'<div class="body field">'+
|
||||
'Annotation'+
|
||||
gettext('Annotation')+
|
||||
'</div>'+
|
||||
|
||||
'{{#if videoFormat}}'+
|
||||
'<div class="start field">'+
|
||||
'Start'+
|
||||
gettext('Start')+
|
||||
'</div>'+
|
||||
|
||||
'<div class="end field">'+
|
||||
'End'+
|
||||
gettext('End')+
|
||||
'</div>'+
|
||||
'{{/if}}'+
|
||||
|
||||
'<div class="totalreplies field">'+
|
||||
'#Replies'+
|
||||
gettext('#Replies')+
|
||||
'</div>'+
|
||||
|
||||
'<div class="annotatedAt field">'+
|
||||
'Date posted'+
|
||||
gettext('Date posted')+
|
||||
'</div>'+
|
||||
'</div>'+
|
||||
'</div>'+
|
||||
@@ -73,30 +73,41 @@ annotationList:
|
||||
'{{/each}}'+
|
||||
'</div>'+
|
||||
'<div class="annotationListButtons">'+
|
||||
'<div class="moreButtonCatch">More</div>'+
|
||||
'<div class="moreButtonCatch">'+gettext('More')+'</div>'+
|
||||
'</div>',
|
||||
|
||||
//Main->PublicPrivateInstructor
|
||||
annotationPublicPrivateInstructor:
|
||||
'<div class="selectors"><div class="PublicPrivate myNotes active">'+gettext('My Notes')+'<span class="action">myNotes</span></div>'+
|
||||
'<div class="PublicPrivate instructor"> '+gettext('Instructor')+'<span class="action">instructor</span></div>'+
|
||||
'<div class="PublicPrivate public"> '+gettext('Public')+'<span class="action">public</span></div></div>'+
|
||||
'<div class="searchbox"><div class="searchinst">'+gettext('Search')+'</div><select class="dropdown-list">'+
|
||||
'<option>'+gettext('Users')+'</option>'+
|
||||
'<option>'+gettext('Tags')+'</option>'+
|
||||
'<option>'+gettext('Annotation Text')+'</option>'+
|
||||
'</select><input type="text" name="search"/><div class="search-icon" alt="Run search."></div><div class="clear-search-icon" alt="Clear search.">'+gettext('Clear')+'</div></div>',
|
||||
|
||||
//Main->PublicPrivate
|
||||
annotationPublicPrivate:
|
||||
'<div class="selectors"><div class="PublicPrivate myNotes active">My Notes<span class="action">myNotes</span></div>'+
|
||||
'<div class="PublicPrivate public"> Public<span class="action">public</span></div></div>'+
|
||||
'<div class="searchbox"><div class="searchinst">Search</div><select class="dropdown-list">'+
|
||||
'<option>Users</option>'+
|
||||
'<option>Tags</option>'+
|
||||
'<option>Annotation Text</option>'+
|
||||
'</select><input type="text" name="search"/><div class="search-icon" alt="Run search."></div></div>',
|
||||
'<div class="selectors"><div class="PublicPrivate myNotes active">'+gettext('My Notes')+'<span class="action">myNotes</span></div>'+
|
||||
'<div class="PublicPrivate public"> '+gettext('Public')+'<span class="action">public</span></div></div>'+
|
||||
'<div class="searchbox"><div class="searchinst">'+gettext('Search')+'</div><select class="dropdown-list">'+
|
||||
'<option>'+gettext('Users')+'</option>'+
|
||||
'<option>'+gettext('Tags')+'</option>'+
|
||||
'<option>'+gettext('Annotation Text')+'</option>'+
|
||||
'</select><input type="text" name="search"/><div class="search-icon" alt="Run search."></div><div class="clear-search-icon" alt="Clear search.">'+gettext('Clear')+'</div></div>',
|
||||
|
||||
//Main->MediaSelector
|
||||
annotationMediaSelector:
|
||||
'<ul class="ui-tabs-nav">'+
|
||||
'<li class="ui-state-default" media="text">'+
|
||||
'Text'+
|
||||
gettext('Text')+
|
||||
'</li>'+
|
||||
'<li class="ui-state-default" media="video">'+
|
||||
'Video'+
|
||||
gettext('Video')+
|
||||
'</li>'+
|
||||
'li class="ui-state-default" media="image">'+
|
||||
'Image'+
|
||||
'<li class="ui-state-default" media="image">'+
|
||||
gettext('Image')+
|
||||
'</li>'+
|
||||
'</ul>',
|
||||
|
||||
@@ -126,7 +137,7 @@ annotationReply:
|
||||
'<div class="map"></div>'+
|
||||
'</div>'+
|
||||
'{{/if}}'+
|
||||
'<div class="deleteReply">Delete</div>'+
|
||||
'<div class="deleteReply">'+gettext('Delete')+'</div>'+
|
||||
'</p>'+
|
||||
'<p>'+
|
||||
'{{#if this.text}}'+
|
||||
@@ -233,13 +244,13 @@ annotationDetail:
|
||||
'</div>'+
|
||||
|
||||
'<div class="controlReplies">'+
|
||||
'<div class="newReply" style="text-decoration:underline">Reply</div> '+
|
||||
'<div class="newReply" style="text-decoration:underline">'+gettext('Reply')+'</div> '+
|
||||
'<div class="hideReplies" style="text-decoration:underline;display:{{#if hasReplies}}block{{else}}none{{/if}}">Show Replies</div> '+
|
||||
'{{#if authToEditButton}}'+
|
||||
'<div class="editAnnotation" style="text-decoration:underline">Edit</div>'+
|
||||
'<div class="editAnnotation" style="text-decoration:underline">'+gettext('Edit')+'</div>'+
|
||||
'{{/if}}'+
|
||||
'{{#if authToDeleteButton}}'+
|
||||
'<div class="deleteAnnotation" style="text-decoration:underline">Delete</div>'+
|
||||
'<div class="deleteAnnotation" style="text-decoration:underline">'+gettext('Delete')+'</div>'+
|
||||
'{{/if}}'+
|
||||
'</div>'+
|
||||
|
||||
@@ -248,7 +259,7 @@ annotationDetail:
|
||||
|
||||
'{{#if tags}}'+
|
||||
'<div class="tags">'+
|
||||
'<h3>Tags:</h3>'+
|
||||
'<h3>'+gettext('Tags:')+'</h3>'+
|
||||
'{{#each tags}}'+
|
||||
'<div class="tag">'+
|
||||
'{{this}}'+
|
||||
@@ -290,14 +301,14 @@ CatchAnnotation = function (element, options) {
|
||||
|
||||
//Reset element an create a new element div
|
||||
element.html('<div id="mainCatch" class="annotationListContainer"></div>');
|
||||
|
||||
this.current_tab = this.options.default_tab;
|
||||
//INIT
|
||||
var self = this;
|
||||
$( document ).ready(function() {
|
||||
self.init();
|
||||
self.refreshCatch(true);
|
||||
var moreBut = self.element.find('.annotationListButtons .moreButtonCatch');
|
||||
moreBut.hide();
|
||||
var moreBut = self.element.find('.annotationListButtons .moreButtonCatch');
|
||||
moreBut.hide();
|
||||
});
|
||||
|
||||
return this;
|
||||
@@ -310,6 +321,7 @@ CatchAnnotation.prototype = {
|
||||
this.TEMPLATENAMES = [
|
||||
"annotationList", //Main
|
||||
"annotationPublicPrivate", //Main->PublicPrivate
|
||||
"annotationPublicPrivateInstructor", //Main->PublicPrivateInstructor
|
||||
"annotationMediaSelector", //Main->MediaSelector
|
||||
"annotationItem", //Main->ContainerRow
|
||||
"annotationReply",//Main->ContainerRow->Reply
|
||||
@@ -317,8 +329,8 @@ CatchAnnotation.prototype = {
|
||||
"annotationDetail",//Main->ContainerRow->DetailRow
|
||||
];
|
||||
//annotator
|
||||
var wrapper = $('.annotator-wrapper').parent()[0],
|
||||
annotator = $.data(wrapper, 'annotator');
|
||||
var wrapper = $('.annotator-wrapper').parent()[0];
|
||||
var annotator = $.data(wrapper, 'annotator');
|
||||
this.annotator = annotator;
|
||||
|
||||
//Subscribe to annotator
|
||||
@@ -336,10 +348,22 @@ CatchAnnotation.prototype = {
|
||||
this.HTMLTEMPLATES = CatchSources.HTMLTEMPLATES(this.options.imageUrlRoot);
|
||||
this.TEMPLATES = {};
|
||||
this._compileTemplates();
|
||||
|
||||
// the default annotations are the user's personal ones instead of instructor
|
||||
// if the default tab is instructor, we must refresh the catch to pull the ones
|
||||
// under the instructor's email. passing empty strings as arguments will default
|
||||
// to pulling the annotations for the email within this.options.userId.
|
||||
if(this.options.default_tab.toLowerCase() == 'instructor'){
|
||||
this.options.userId = this.options.instructor_email;
|
||||
this._refresh('','');
|
||||
}
|
||||
},
|
||||
//
|
||||
// GLOBAL UTILITIES
|
||||
//
|
||||
getTemplate: function(templateName){
|
||||
return this.TEMPLATES[templateName]() || '';
|
||||
},
|
||||
refreshCatch: function(newInstance) {
|
||||
var mediaType = this.options.media || 'text',
|
||||
annotationItems = [],
|
||||
@@ -380,10 +404,16 @@ CatchAnnotation.prototype = {
|
||||
|
||||
if (newInstance){
|
||||
var videoFormat = (mediaType === "video") ? true:false;
|
||||
var publicPrivateTemplate = '';
|
||||
if (self.options.showPublicPrivate) {
|
||||
var templateName = this.options.instructor_email ?
|
||||
"annotationPublicPrivateInstructor" :
|
||||
"annotationPublicPrivate";
|
||||
}
|
||||
el.html(self.TEMPLATES.annotationList({
|
||||
annotationItems: annotationItems,
|
||||
videoFormat: videoFormat,
|
||||
PublicPrivate: self.options.showPublicPrivate?self.TEMPLATES.annotationPublicPrivate():'',
|
||||
PublicPrivate: this.getTemplate(templateName),
|
||||
MediaSelector: self.options.showMediaSelector?self.TEMPLATES.annotationMediaSelector():'',
|
||||
}));
|
||||
}else{
|
||||
@@ -402,10 +432,21 @@ CatchAnnotation.prototype = {
|
||||
//Set PublicPrivate
|
||||
var PublicPrivateButtons = el.find('.annotationListButtons .PublicPrivate').removeClass('active'); //reset
|
||||
for (var index=0;index<PublicPrivateButtons.length;index++) {
|
||||
var span = $(PublicPrivateButtons[index]).find('span'),
|
||||
isUser = (typeof self.options.userId!='undefined' && self.options.userId!='' && self.options.userId!=null);
|
||||
if (isUser && span.html()=="myNotes") $(PublicPrivateButtons[index]).addClass('active');
|
||||
else if (!isUser && span.html()=="public") $(PublicPrivateButtons[index]).addClass('active');
|
||||
var span = $(PublicPrivateButtons[index]).find('span');
|
||||
if (span.html().toLowerCase()==self.current_tab.toLowerCase()) {
|
||||
switch (self.current_tab.toLowerCase()){
|
||||
case 'public':
|
||||
self.options.userId = '';
|
||||
break;
|
||||
case 'instructor':
|
||||
self.options.userId = this.options.instructor_email;
|
||||
break;
|
||||
default:
|
||||
self.options.userId = this.annotator.plugins.Permissions.user.id;
|
||||
break;
|
||||
}
|
||||
$(PublicPrivateButtons[index]).addClass('active');
|
||||
}
|
||||
}
|
||||
|
||||
//reset all old events
|
||||
@@ -423,6 +464,7 @@ CatchAnnotation.prototype = {
|
||||
onControlRepliesClick = this.__bind(this._onControlRepliesClick, this),
|
||||
onMoreButtonClick = this.__bind(this._onMoreButtonClick, this),
|
||||
onSearchButtonClick = this.__bind(this._onSearchButtonClick, this),
|
||||
onClearSearchButtonClick = this.__bind(this._onClearSearchButtonClick, this),
|
||||
onDeleteReplyButtonClick = this.__bind(this._onDeleteReplyButtonClick, this),
|
||||
onZoomToImageBoundsButtonClick = this.__bind(this._onZoomToImageBoundsButtonClick, this);
|
||||
|
||||
@@ -463,6 +505,9 @@ CatchAnnotation.prototype = {
|
||||
|
||||
//Search Button
|
||||
el.on("click",".searchbox .search-icon", onSearchButtonClick);
|
||||
|
||||
//Clear Search Button
|
||||
el.on("click",".searchbox .clear-search-icon", onClearSearchButtonClick);
|
||||
|
||||
//Delete Reply Button
|
||||
el.on("click", ".replies .replyItem .deleteReply", onDeleteReplyButtonClick);
|
||||
@@ -471,16 +516,16 @@ CatchAnnotation.prototype = {
|
||||
changeMedia: function(media) {
|
||||
var media = media || 'text';
|
||||
this.options.media = media;
|
||||
this._refresh();
|
||||
this._refresh();
|
||||
this.refreshCatch(true);
|
||||
this.checkTotAnnotations();
|
||||
this.checkTotAnnotations();
|
||||
},
|
||||
changeUserId: function(userId) {
|
||||
var userId = userId || '';
|
||||
this.options.userId = userId;
|
||||
this._refresh();
|
||||
this.refreshCatch(true);
|
||||
this.checkTotAnnotations();
|
||||
this.checkTotAnnotations();
|
||||
},
|
||||
loadAnnotations: function() {
|
||||
var annotator = this.annotator,
|
||||
@@ -568,21 +613,26 @@ CatchAnnotation.prototype = {
|
||||
var annotations = annotator.plugins['Store'].annotations,
|
||||
tot = typeof annotations !='undefined'?annotations.length:0,
|
||||
attempts = 0; // max 100
|
||||
if(annotation.media == "image"){
|
||||
self.refreshCatch(true);
|
||||
self.checkTotAnnotations();
|
||||
} else {
|
||||
//This is to watch the annotations object, to see when is deleted the annotation
|
||||
var ischanged = function(){
|
||||
var new_tot = annotator.plugins['Store'].annotations.length;
|
||||
if (attempts<100)
|
||||
setTimeout(function(){
|
||||
if (new_tot != tot){
|
||||
self.refreshCatch(true);
|
||||
self.checkTotAnnotations();
|
||||
}else{
|
||||
attempts++;
|
||||
ischanged();
|
||||
}
|
||||
},100); //wait for the change in the annotations
|
||||
};
|
||||
ischanged();
|
||||
var ischanged = function(){
|
||||
var new_tot = annotator.plugins['Store'].annotations.length;
|
||||
if (attempts<100)
|
||||
setTimeout(function(){
|
||||
if (new_tot != tot){
|
||||
self.refreshCatch(true);
|
||||
self.checkTotAnnotations();
|
||||
}else{
|
||||
attempts++;
|
||||
ischanged();
|
||||
}
|
||||
},100); //wait for the change in the annotations
|
||||
};
|
||||
ischanged();
|
||||
}
|
||||
});
|
||||
annotator.subscribe("annotationCreated", function (annotation){
|
||||
var attempts = 0; // max 100
|
||||
@@ -770,6 +820,10 @@ CatchAnnotation.prototype = {
|
||||
var allannotations = this.annotator.plugins['Store'].annotations,
|
||||
osda = this.annotator.osda;
|
||||
|
||||
if(this.options.externalLink){
|
||||
uri += (uri.indexOf('?') >= 0)?'&osdaId='+osdaId:'?osdaId='+osdaId;
|
||||
location.href = uri;
|
||||
}
|
||||
for(var item in allannotations){
|
||||
var an = allannotations[item];
|
||||
if (typeof an.id!='undefined' && an.id == osdaId){//this is the annotation
|
||||
@@ -1011,8 +1065,18 @@ CatchAnnotation.prototype = {
|
||||
userId = '';
|
||||
|
||||
//Get userI
|
||||
userId = (action.html()=="myNotes")? this.annotator.plugins.Permissions.user.id : '';
|
||||
|
||||
switch (action.html()){
|
||||
case 'public':
|
||||
userId = '';
|
||||
break;
|
||||
case 'instructor':
|
||||
userId = this.options.instructor_email;
|
||||
break;
|
||||
default:
|
||||
userId = this.annotator.plugins.Permissions.user.id;
|
||||
break;
|
||||
}
|
||||
this.current_tab = action.html();
|
||||
//Change userid and refresh
|
||||
this.changeUserId(userId);
|
||||
},
|
||||
@@ -1069,6 +1133,9 @@ CatchAnnotation.prototype = {
|
||||
this._refresh(searchtype,searchInput);
|
||||
|
||||
},
|
||||
_onClearSearchButtonClick: function(evt){
|
||||
this._refresh('','');
|
||||
},
|
||||
_clearAnnotator: function(){
|
||||
var annotator = this.annotator,
|
||||
store = annotator.plugins.Store,
|
||||
|
||||
4
common/static/js/vendor/ova/openseadragon.js
vendored
4
common/static/js/vendor/ova/openseadragon.js
vendored
@@ -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('/');
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -11,9 +11,7 @@ from xmodule.modulestore.locations import SlashSeparatedCourseKey
|
||||
@login_required
|
||||
def notes(request, course_id):
|
||||
''' Displays the student's notes. '''
|
||||
|
||||
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
|
||||
|
||||
course = get_course_with_access(request.user, 'load', course_key)
|
||||
if not notes_enabled_for_course(course):
|
||||
raise Http404
|
||||
@@ -28,6 +26,7 @@ def notes(request, course_id):
|
||||
'student': student,
|
||||
'storage': storage,
|
||||
'token': retrieve_token(student.email, course.annotation_token_secret),
|
||||
'default_tab': 'myNotes',
|
||||
}
|
||||
|
||||
return render_to_response('notes.html', context)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -9,72 +9,69 @@
|
||||
</style>
|
||||
|
||||
<div class="annotatable-wrapper">
|
||||
<div class="annotatable-header">
|
||||
% if display_name is not UNDEFINED and display_name is not None:
|
||||
<div class="annotatable-title">${display_name}</div>
|
||||
% endif
|
||||
</div>
|
||||
% if instructions_html is not UNDEFINED and instructions_html is not None:
|
||||
<div class="annotatable-section shaded">
|
||||
<div class="annotatable-section-title">
|
||||
${_('Instructions')}
|
||||
<a class="annotatable-toggle annotatable-toggle-instructions expanded" href="javascript:void(0)">${_('Collapse Instructions')}</a>
|
||||
</div>
|
||||
<div class="annotatable-section-body annotatable-instructions">
|
||||
${instructions_html}
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<div class="annotatable-section">
|
||||
<div class="annotatable-content">
|
||||
<div id="imageHolder" class="openseadragon1">
|
||||
<div class="annotatable-header">
|
||||
% if display_name is not UNDEFINED and display_name is not None:
|
||||
<div class="annotatable-title">${display_name}</div>
|
||||
% endif
|
||||
</div>
|
||||
% if instructions_html is not UNDEFINED and instructions_html is not None:
|
||||
<div class="annotatable-section shaded">
|
||||
<div class="annotatable-section-title">
|
||||
${_('Instructions')}
|
||||
<a class="annotatable-toggle annotatable-toggle-instructions expanded" href="javascript:void(0)">${_('Collapse Instructions')}</a>
|
||||
</div>
|
||||
<div class="annotatable-section-body annotatable-instructions">
|
||||
${instructions_html}
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<div class="annotatable-section">
|
||||
<div class="annotatable-content">
|
||||
<div id="imageHolder" class="openseadragon1">
|
||||
<%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)}
|
||||
<script type="text/javascript" src="${static.url('js/vendor/tinymce/js/tinymce/tinymce.full.min.js', raw=True)}" />
|
||||
<script type="text/javascript" src="${static.url('js/vendor/tinymce/js/tinymce/jquery.tinymce.min.js', raw=True)}" />
|
||||
</div>
|
||||
<div id="catchDIV">
|
||||
## Translators: Notes below refer to annotations. They wil later be put under a "Notes" section.
|
||||
<div class="annotationListContainer">${_('You do not have any notes.')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="catchDIV">
|
||||
<div class="annotationListContainer">${_('Note: only instructors may annotate.')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
function onClickHideInstructions(){
|
||||
//Reset function if there is more than one event handler
|
||||
$(this).off();
|
||||
$(this).on('click',onClickHideInstructions);
|
||||
var hide = $(this).html()=='Collapse Instructions'?true:false,
|
||||
cls, txt,slideMethod;
|
||||
txt = (hide ? 'Expand' : 'Collapse') + ' Instructions';
|
||||
cls = (hide ? ['expanded', 'collapsed'] : ['collapsed', 'expanded']);
|
||||
slideMethod = (hide ? 'slideUp' : 'slideDown');
|
||||
$(this).text(txt).removeClass(cls[0]).addClass(cls[1]);
|
||||
$(this).parents('.annotatable-section:first').find('.annotatable-instructions')[slideMethod]();
|
||||
}
|
||||
$('.annotatable-toggle-instructions').on('click', onClickHideInstructions);
|
||||
|
||||
//Grab uri of the course
|
||||
var parts = window.location.href.split("/"),
|
||||
uri = '',
|
||||
courseid;
|
||||
for (var index = 0; index <= 9; index += 1) uri += parts[index]+"/"; //Get the unit url
|
||||
courseid = parts[4] + "/" + parts[5] + "/" + parts[6];
|
||||
//Change uri in cms
|
||||
var lms_location = $('.sidebar .preview-button').attr('href');
|
||||
if (typeof lms_location!='undefined'){
|
||||
courseid = parts[4].split(".").join("/");
|
||||
uri = window.location.protocol;
|
||||
for (var index = 0; index <= 9; index += 1) uri += lms_location.split("/")[index]+"/"; //Get the unit url
|
||||
}
|
||||
var unit_id = $('#sequence-list').find('.active').attr("data-element");
|
||||
function onClickHideInstructions(){
|
||||
//Reset function if there is more than one event handler
|
||||
$(this).off();
|
||||
$(this).on('click',onClickHideInstructions);
|
||||
var hide = $(this).html()=='Collapse Instructions'?true:false,
|
||||
cls, txt,slideMethod;
|
||||
txt = (hide ? 'Expand' : 'Collapse') + ' Instructions';
|
||||
cls = (hide ? ['expanded', 'collapsed'] : ['collapsed', 'expanded']);
|
||||
slideMethod = (hide ? 'slideUp' : 'slideDown');
|
||||
$(this).text(txt).removeClass(cls[0]).addClass(cls[1]);
|
||||
$(this).parents('.annotatable-section:first').find('.annotatable-instructions')[slideMethod]();
|
||||
}
|
||||
$('.annotatable-toggle-instructions').on('click', onClickHideInstructions);
|
||||
|
||||
//Grab uri of the course
|
||||
var parts = window.location.href.split("/"),
|
||||
uri = '',
|
||||
courseid;
|
||||
for (var index = 0; index <= 9; index += 1) uri += parts[index]+"/"; //Get the unit url
|
||||
courseid = parts[4] + "/" + parts[5] + "/" + parts[6];
|
||||
//Change uri in cms
|
||||
var lms_location = $('.sidebar .preview-button').attr('href');
|
||||
if (typeof lms_location!='undefined'){
|
||||
courseid = parts[4].split(".").join("/");
|
||||
uri = window.location.protocol;
|
||||
for (var index = 0; index <= 9; index += 1) uri += lms_location.split("/")[index]+"/"; //Get the unit url
|
||||
}
|
||||
var unit_id = $('#sequence-list').find('.active').attr("data-element");
|
||||
uri += unit_id;
|
||||
var pagination = 100,
|
||||
is_staff = !('${user.is_staff}'=='False'),
|
||||
var pagination = 100,
|
||||
is_staff = ('${user.email}'=='${instructor_email}'),
|
||||
options = {
|
||||
optionsAnnotator: {
|
||||
permissions:{
|
||||
@@ -105,7 +102,7 @@
|
||||
if (annotation.permissions) {
|
||||
tokens = annotation.permissions[action] || [];
|
||||
if (is_staff){
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
if (tokens.length === 0) {
|
||||
return true;
|
||||
@@ -155,7 +152,7 @@
|
||||
offset:0,
|
||||
uri:uri,
|
||||
media:'image',
|
||||
userid:'${user.email}',
|
||||
userid:'${user.email}',
|
||||
}
|
||||
},
|
||||
highlightTags:{
|
||||
@@ -174,37 +171,52 @@
|
||||
},
|
||||
optionsOpenSeadragon:{
|
||||
id: "imageHolder",
|
||||
annotation_mode: "${annotation_mode}",
|
||||
flags: is_staff,
|
||||
prefixUrl: "${settings.STATIC_URL}" + "js/vendor/ova/images/",
|
||||
${openseadragonjson}
|
||||
},
|
||||
optionsOSDA:{},
|
||||
|
||||
};
|
||||
tinymce.baseURL = "${settings.STATIC_URL}" + "js/vendor/tinymce/js/tinymce";
|
||||
var imgURLRoot = "${settings.STATIC_URL}" + "js/vendor/ova/catch/img/";
|
||||
|
||||
|
||||
if (typeof Annotator != 'undefined'){
|
||||
//remove old instances
|
||||
if (Annotator._instances.length !== 0) {
|
||||
$('#imageHolder').annotator("destroy");
|
||||
}
|
||||
delete osda;
|
||||
//Load the plugin Image/Text Annotation
|
||||
var osda = new OpenSeadragonAnnotation($('#imageHolder'),options);
|
||||
|
||||
//Catch
|
||||
var annotator = osda.annotator,
|
||||
catchOptions = {
|
||||
media:'image',
|
||||
externalLink:false,
|
||||
imageUrlRoot:imgURLRoot,
|
||||
showMediaSelector: false,
|
||||
showPublicPrivate: true,
|
||||
userId:'${user.email}',
|
||||
pagination:pagination,//Number of Annotations per load in the pagination,
|
||||
flags:is_staff
|
||||
},
|
||||
Catch = new CatchAnnotation($('#catchDIV'),catchOptions);
|
||||
//remove old instances
|
||||
if (Annotator._instances.length !== 0) {
|
||||
$('#imageHolder').annotator("destroy");
|
||||
}
|
||||
delete osda;
|
||||
//Load the plugin Image/Text Annotation
|
||||
var osda = new OpenSeadragonAnnotation($('#imageHolder'),options);
|
||||
|
||||
var userId = ('${default_tab}'.toLowerCase() === 'instructor') ?
|
||||
'${instructor_email}':
|
||||
'${user.email}';
|
||||
|
||||
//Catch
|
||||
var annotator = osda.annotator;
|
||||
var catchOptions = {
|
||||
media:'image',
|
||||
externalLink:false,
|
||||
imageUrlRoot:imgURLRoot,
|
||||
showMediaSelector: false,
|
||||
showPublicPrivate: true,
|
||||
userId:userId,
|
||||
pagination:pagination,//Number of Annotations per load in the pagination,
|
||||
flags:is_staff,
|
||||
default_tab: "${default_tab}",
|
||||
instructor_email: "${instructor_email}",
|
||||
annotation_mode: "${annotation_mode}",
|
||||
};
|
||||
|
||||
// if annotations are opened to everyone (2) or if they want to create no annotations (1 with no instructor)
|
||||
// then the table at the bottom of the source should be displayed
|
||||
if ("${annotation_mode}" == "everyone" || ("${annotation_mode}" == "instructor" && "${instructor_email}" != ""))
|
||||
var Catch = new CatchAnnotation($('#catchDIV'),catchOptions);
|
||||
|
||||
// if it is in instructor mode only (1), the annotator should be destroyed for all except the instructor
|
||||
if ("${annotation_mode}" == "instructor" && "${instructor_email}" == "" && !is_staff)
|
||||
osda.annotator.destroy();
|
||||
}
|
||||
</script>
|
||||
@@ -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)}
|
||||
<script type="text/javascript" src="${static.url('js/vendor/tinymce/js/tinymce/tinymce.full.min.js', raw=True)}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/tinymce/js/tinymce/jquery.tinymce.min.js', raw=True)}" ></script>
|
||||
<%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);
|
||||
</script>
|
||||
|
||||
@@ -2,8 +2,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)}
|
||||
<script type="text/javascript" src="${static.url('js/vendor/tinymce/js/tinymce/tinymce.full.min.js', raw=True)}" />
|
||||
<script type="text/javascript" src="${static.url('js/vendor/tinymce/js/tinymce/jquery.tinymce.min.js', raw=True)}" />
|
||||
|
||||
<div class="annotatable-wrapper">
|
||||
<div class="annotatable-header">
|
||||
@@ -167,7 +165,6 @@ ${static.css(group='style-vendor-tinymce-skin', raw=True)}
|
||||
};
|
||||
|
||||
var imgURLRoot = "${settings.STATIC_URL}" + "js/vendor/ova/catch/img/";
|
||||
tinymce.baseURL = "${settings.STATIC_URL}" + "js/vendor/tinymce/js/tinymce";
|
||||
|
||||
//remove old instances
|
||||
if (Annotator._instances.length !== 0) {
|
||||
@@ -177,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);
|
||||
</script>
|
||||
|
||||
@@ -2,9 +2,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)}
|
||||
<script type="text/javascript" src="${static.url('js/vendor/tinymce/js/tinymce/tinymce.full.min.js', raw=True)}" />
|
||||
<script type="text/javascript" src="${static.url('js/vendor/tinymce/js/tinymce/jquery.tinymce.min.js', raw=True)}" />
|
||||
|
||||
|
||||
<div class="annotatable-wrapper">
|
||||
<div class="annotatable-header">
|
||||
@@ -168,7 +165,6 @@ ${static.css(group='style-vendor-tinymce-skin', raw=True)}
|
||||
};
|
||||
|
||||
var imgURLRoot = "${settings.STATIC_URL}" + "js/vendor/ova/catch/img/";
|
||||
tinymce.baseURL = "${settings.STATIC_URL}" + "js/vendor/tinymce/js/tinymce";
|
||||
|
||||
//remove old instances
|
||||
if (Annotator._instances.length !== 0) {
|
||||
@@ -179,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);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user