"
@answer_area.replaceWith(new_text)
+
+ # wrap this so that it can be mocked
+ reload: ->
+ location.reload()
diff --git a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py
index e0bf0ec1d3..3522b45718 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py
@@ -17,4 +17,26 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d
# export the static assets
contentstore.export_all_for_course(course_location, root_dir + '/' + course_dir + '/static/')
+ # export the static tabs
+ export_extra_content(export_fs, modulestore, course_location, 'static_tab', 'tabs', '.html')
+
+ # export the custom tags
+ export_extra_content(export_fs, modulestore, course_location, 'custom_tag_template', 'custom_tags')
+
+ # export the course updates
+ export_extra_content(export_fs, modulestore, course_location, 'course_info', 'info', '.html')
+
+
+def export_extra_content(export_fs, modulestore, course_location, category_type, dirname, file_suffix = ''):
+ query_loc = Location('i4x', course_location.org, course_location.course, category_type, None)
+ items = modulestore.get_items(query_loc)
+
+ if len(items) > 0:
+ item_dir = export_fs.makeopendir(dirname)
+ for item in items:
+ with item_dir.open(item.location.name + file_suffix, 'w') as item_file:
+ item_file.write(item.definition['data'].encode('utf8'))
+
+
+
\ No newline at end of file
diff --git a/common/lib/xmodule/xmodule/open_ended_image_submission.py b/common/lib/xmodule/xmodule/open_ended_image_submission.py
index cda6cb062a..abfb2d80ba 100644
--- a/common/lib/xmodule/xmodule/open_ended_image_submission.py
+++ b/common/lib/xmodule/xmodule/open_ended_image_submission.py
@@ -216,6 +216,10 @@ def upload_to_s3(file_to_upload, keyname):
Returns:
public_url: URL to access uploaded file
'''
+
+ #This commented out code is kept here in case we change the uploading method and require images to be
+ #converted before they are sent to S3.
+ #TODO: determine if commented code is needed and remove
#im = Image.open(file_to_upload)
#out_im = cStringIO.StringIO()
#im.save(out_im, 'PNG')
@@ -229,6 +233,9 @@ def upload_to_s3(file_to_upload, keyname):
k.key = keyname
k.set_metadata('filename', file_to_upload.name)
k.set_contents_from_file(file_to_upload)
+
+ #This commented out code is kept here in case we change the uploading method and require images to be
+ #converted before they are sent to S3.
#k.set_contents_from_string(out_im.getvalue())
#k.set_metadata("Content-Type", 'images/png')
diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py
index 4fd946e656..94d45d96e3 100644
--- a/common/lib/xmodule/xmodule/open_ended_module.py
+++ b/common/lib/xmodule/xmodule/open_ended_module.py
@@ -258,7 +258,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
"""
new_score_msg = self._parse_score_msg(score_msg, system)
if not new_score_msg['valid']:
- score_msg['feedback'] = 'Invalid grader reply. Please contact the course staff.'
+ new_score_msg['feedback'] = 'Invalid grader reply. Please contact the course staff.'
self.record_latest_score(new_score_msg['score'])
self.record_latest_post_assessment(score_msg)
@@ -405,6 +405,13 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
'score': Numeric value (floating point is okay) to assign to answer
'msg': grader_msg
'feedback' : feedback from grader
+ 'grader_type': what type of grader resulted in this score
+ 'grader_id': id of the grader
+ 'submission_id' : id of the submission
+ 'success': whether or not this submission was successful
+ 'rubric_scores': a list of rubric scores
+ 'rubric_scores_complete': boolean if rubric scores are complete
+ 'rubric_xml': the xml of the rubric in string format
}
Returns (valid_score_msg, correct, score, msg):
diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py
index 9db3388371..7151ac0723 100644
--- a/common/lib/xmodule/xmodule/openendedchild.py
+++ b/common/lib/xmodule/xmodule/openendedchild.py
@@ -117,7 +117,7 @@ class OpenEndedChild(object):
pass
def latest_answer(self):
- """None if not available"""
+ """Empty string if not available"""
if not self.history:
return ""
return self.history[-1].get('answer', "")
@@ -129,7 +129,7 @@ class OpenEndedChild(object):
return self.history[-1].get('score')
def latest_post_assessment(self, system):
- """None if not available"""
+ """Empty string if not available"""
if not self.history:
return ""
return self.history[-1].get('post_assessment', "")
diff --git a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
new file mode 100644
index 0000000000..c89f5ee848
--- /dev/null
+++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
@@ -0,0 +1,339 @@
+import json
+from mock import Mock, MagicMock, ANY
+import unittest
+
+from xmodule.openendedchild import OpenEndedChild
+from xmodule.open_ended_module import OpenEndedModule
+from xmodule.combined_open_ended_module import CombinedOpenEndedModule
+
+from xmodule.modulestore import Location
+from lxml import etree
+import capa.xqueue_interface as xqueue_interface
+from datetime import datetime
+
+from . import test_system
+"""
+Tests for the various pieces of the CombinedOpenEndedGrading system
+
+OpenEndedChild
+OpenEndedModule
+
+"""
+
+class OpenEndedChildTest(unittest.TestCase):
+ location = Location(["i4x", "edX", "sa_test", "selfassessment",
+ "SampleQuestion"])
+
+ metadata = json.dumps({'attempts': '10'})
+ prompt = etree.XML("This is a question prompt")
+ rubric = '''
+
+ Response Quality
+
+
+ '''
+ max_score = 4
+
+ static_data = {
+ 'max_attempts': 20,
+ 'prompt': prompt,
+ 'rubric': rubric,
+ 'max_score': max_score,
+ 'display_name': 'Name',
+ 'accept_file_upload' : False,
+ }
+ definition = Mock()
+ descriptor = Mock()
+
+ def setUp(self):
+ self.openendedchild = OpenEndedChild(test_system, self.location,
+ self.definition, self.descriptor, self.static_data, self.metadata)
+
+
+ def test_latest_answer_empty(self):
+ answer = self.openendedchild.latest_answer()
+ self.assertEqual(answer, "")
+
+
+ def test_latest_score_empty(self):
+ answer = self.openendedchild.latest_score()
+ self.assertEqual(answer, None)
+
+
+ def test_latest_post_assessment_empty(self):
+ answer = self.openendedchild.latest_post_assessment(test_system)
+ self.assertEqual(answer, "")
+
+
+ def test_new_history_entry(self):
+ new_answer = "New Answer"
+ self.openendedchild.new_history_entry(new_answer)
+ answer = self.openendedchild.latest_answer()
+ self.assertEqual(answer, new_answer)
+
+ new_answer = "Newer Answer"
+ self.openendedchild.new_history_entry(new_answer)
+ answer = self.openendedchild.latest_answer()
+ self.assertEqual(new_answer, answer)
+
+ def test_record_latest_score(self):
+ new_answer = "New Answer"
+ self.openendedchild.new_history_entry(new_answer)
+ new_score = 3
+ self.openendedchild.record_latest_score(new_score)
+ score = self.openendedchild.latest_score()
+ self.assertEqual(score, 3)
+
+ new_score = 4
+ self.openendedchild.new_history_entry(new_answer)
+ self.openendedchild.record_latest_score(new_score)
+ score = self.openendedchild.latest_score()
+ self.assertEqual(score, 4)
+
+
+ def test_record_latest_post_assessment(self):
+ new_answer = "New Answer"
+ self.openendedchild.new_history_entry(new_answer)
+
+ post_assessment = "Post assessment"
+ self.openendedchild.record_latest_post_assessment(post_assessment)
+ self.assertEqual(post_assessment,
+ self.openendedchild.latest_post_assessment(test_system))
+
+ def test_get_score(self):
+ new_answer = "New Answer"
+ self.openendedchild.new_history_entry(new_answer)
+
+ score = self.openendedchild.get_score()
+ self.assertEqual(score['score'], 0)
+ self.assertEqual(score['total'], self.static_data['max_score'])
+
+ new_score = 4
+ self.openendedchild.new_history_entry(new_answer)
+ self.openendedchild.record_latest_score(new_score)
+ score = self.openendedchild.get_score()
+ self.assertEqual(score['score'], new_score)
+ self.assertEqual(score['total'], self.static_data['max_score'])
+
+
+ def test_reset(self):
+ self.openendedchild.reset(test_system)
+ state = json.loads(self.openendedchild.get_instance_state())
+ self.assertEqual(state['state'], OpenEndedChild.INITIAL)
+
+
+ def test_is_last_response_correct(self):
+ new_answer = "New Answer"
+ self.openendedchild.new_history_entry(new_answer)
+ self.openendedchild.record_latest_score(self.static_data['max_score'])
+ self.assertEqual(self.openendedchild.is_last_response_correct(),
+ 'correct')
+
+ self.openendedchild.new_history_entry(new_answer)
+ self.openendedchild.record_latest_score(0)
+ self.assertEqual(self.openendedchild.is_last_response_correct(),
+ 'incorrect')
+
+class OpenEndedModuleTest(unittest.TestCase):
+ location = Location(["i4x", "edX", "sa_test", "selfassessment",
+ "SampleQuestion"])
+
+ metadata = json.dumps({'attempts': '10'})
+ prompt = etree.XML("This is a question prompt")
+ rubric = etree.XML('''
+
+ Response Quality
+
+
+ ''')
+ max_score = 4
+
+ static_data = {
+ 'max_attempts': 20,
+ 'prompt': prompt,
+ 'rubric': rubric,
+ 'max_score': max_score,
+ 'display_name': 'Name',
+ 'accept_file_upload': False,
+ }
+
+ oeparam = etree.XML('''
+
+ Enter essay here.
+ This is the answer.
+ {"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"}
+
+ ''')
+ definition = {'oeparam': oeparam}
+ descriptor = Mock()
+
+ def setUp(self):
+ test_system.location = self.location
+ self.mock_xqueue = MagicMock()
+ self.mock_xqueue.send_to_queue.return_value=(None, "Message")
+ test_system.xqueue = {'interface':self.mock_xqueue, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 1}
+ self.openendedmodule = OpenEndedModule(test_system, self.location,
+ self.definition, self.descriptor, self.static_data, self.metadata)
+
+ def test_message_post(self):
+ get = {'feedback': 'feedback text',
+ 'submission_id': '1',
+ 'grader_id': '1',
+ 'score': 3}
+ qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat)
+ student_info = {'anonymous_student_id': test_system.anonymous_student_id,
+ 'submission_time': qtime}
+ contents = {
+ 'feedback': get['feedback'],
+ 'submission_id': int(get['submission_id']),
+ 'grader_id': int(get['grader_id']),
+ 'score': get['score'],
+ 'student_info': json.dumps(student_info)
+ }
+
+ result = self.openendedmodule.message_post(get, test_system)
+ self.assertTrue(result['success'])
+ # make sure it's actually sending something we want to the queue
+ self.mock_xqueue.send_to_queue.assert_called_with(body = json.dumps(contents), header=ANY)
+
+ state = json.loads(self.openendedmodule.get_instance_state())
+ self.assertIsNotNone(state['state'], OpenEndedModule.DONE)
+
+ def test_send_to_grader(self):
+ submission = "This is a student submission"
+ qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat)
+ student_info = {'anonymous_student_id': test_system.anonymous_student_id,
+ 'submission_time': qtime}
+ contents = self.openendedmodule.payload.copy()
+ contents.update({
+ 'student_info': json.dumps(student_info),
+ 'student_response': submission,
+ 'max_score': self.max_score
+ })
+ result = self.openendedmodule.send_to_grader(submission, test_system)
+ self.assertTrue(result)
+ self.mock_xqueue.send_to_queue.assert_called_with(body = json.dumps(contents), header=ANY)
+
+ def update_score_single(self):
+ self.openendedmodule.new_history_entry("New Entry")
+ score_msg = {
+ 'correct': True,
+ 'score': 4,
+ 'msg' : 'Grader Message',
+ 'feedback': "Grader Feedback"
+ }
+ get = {'queuekey': "abcd",
+ 'xqueue_body': score_msg}
+ self.openendedmodule.update_score(get, test_system)
+
+ def update_score_single(self):
+ self.openendedmodule.new_history_entry("New Entry")
+ feedback = {
+ "success": True,
+ "feedback": "Grader Feedback"
+ }
+ score_msg = {
+ 'correct': True,
+ 'score': 4,
+ 'msg' : 'Grader Message',
+ 'feedback': json.dumps(feedback),
+ 'grader_type': 'IN',
+ 'grader_id': '1',
+ 'submission_id': '1',
+ 'success': True,
+ 'rubric_scores': [0],
+ 'rubric_scores_complete': True,
+ 'rubric_xml': etree.tostring(self.rubric)
+ }
+ get = {'queuekey': "abcd",
+ 'xqueue_body': json.dumps(score_msg)}
+ self.openendedmodule.update_score(get, test_system)
+
+ def test_latest_post_assessment(self):
+ self.update_score_single()
+ assessment = self.openendedmodule.latest_post_assessment(test_system)
+ self.assertFalse(assessment == '')
+ # check for errors
+ self.assertFalse('errors' in assessment)
+
+ def test_update_score(self):
+ self.update_score_single()
+ score = self.openendedmodule.latest_score()
+ self.assertEqual(score, 4)
+
+class CombinedOpenEndedModuleTest(unittest.TestCase):
+ location = Location(["i4x", "edX", "open_ended", "combinedopenended",
+ "SampleQuestion"])
+
+ prompt = "This is a question prompt"
+ rubric = '''
+
+ Response Quality
+
+
+ '''
+ max_score = 3
+
+ metadata = {'attempts': '10', 'max_score': max_score}
+
+ static_data = json.dumps({
+ 'max_attempts': 20,
+ 'prompt': prompt,
+ 'rubric': rubric,
+ 'max_score': max_score,
+ 'display_name': 'Name'
+ })
+
+ oeparam = etree.XML('''
+
+ Enter essay here.
+ This is the answer.
+ {"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"}
+
+ ''')
+
+ task_xml1 = '''
+
+
+ What hint about this problem would you give to someone?
+
+
+ Save Succcesful. Thanks for participating!
+
+
+ '''
+ task_xml2 = '''
+
+
+ Enter essay here.
+ This is the answer.
+ {"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"}
+
+ '''
+ definition = {'prompt': etree.XML(prompt), 'rubric': etree.XML(rubric), 'task_xml': [task_xml1, task_xml2]}
+ descriptor = Mock()
+
+ def setUp(self):
+ self.combinedoe = CombinedOpenEndedModule(test_system, self.location, self.definition, self.descriptor, self.static_data, metadata=self.metadata)
+
+ def test_get_tag_name(self):
+ name = self.combinedoe.get_tag_name("Tag")
+ self.assertEqual(name, "t")
+
+ def test_get_last_response(self):
+ response_dict = self.combinedoe.get_last_response(0)
+ self.assertEqual(response_dict['type'], "selfassessment")
+ self.assertEqual(response_dict['max_score'], self.max_score)
+ self.assertEqual(response_dict['state'], CombinedOpenEndedModule.INITIAL)
+
+ def test_update_task_states(self):
+ changed = self.combinedoe.update_task_states()
+ self.assertFalse(changed)
+
+ current_task = self.combinedoe.current_task
+ current_task.change_state(CombinedOpenEndedModule.DONE)
+ changed = self.combinedoe.update_task_states()
+
+ self.assertTrue(changed)
+
+
diff --git a/common/lib/xmodule/xmodule/tests/test_self_assessment.py b/common/lib/xmodule/xmodule/tests/test_self_assessment.py
index 9013794dbb..c5fb82e412 100644
--- a/common/lib/xmodule/xmodule/tests/test_self_assessment.py
+++ b/common/lib/xmodule/xmodule/tests/test_self_assessment.py
@@ -10,8 +10,16 @@ from . import test_system
class SelfAssessmentTest(unittest.TestCase):
- definition = {'rubric': 'A rubric',
- 'prompt': 'Who?',
+ rubric = '''
+
+ Response Quality
+
+
+ '''
+
+ prompt = etree.XML("This is sample prompt text.")
+ definition = {'rubric': rubric,
+ 'prompt': prompt,
'submitmessage': 'Shall we submit now?',
'hintprompt': 'Consider this...',
}
@@ -23,47 +31,47 @@ class SelfAssessmentTest(unittest.TestCase):
descriptor = Mock()
- def test_import(self):
+ def setUp(self):
state = json.dumps({'student_answers': ["Answer 1", "answer 2", "answer 3"],
'scores': [0, 1],
'hints': ['o hai'],
'state': SelfAssessmentModule.INITIAL,
'attempts': 2})
- rubric = '''
-
- Response Quality
-
-
- '''
-
- prompt = etree.XML("Text")
static_data = {
'max_attempts': 10,
- 'rubric': etree.XML(rubric),
- 'prompt': prompt,
+ 'rubric': etree.XML(self.rubric),
+ 'prompt': self.prompt,
'max_score': 1,
- 'display_name': "Name"
+ 'display_name': "Name",
+ 'accept_file_upload' : False,
}
- module = SelfAssessmentModule(test_system, self.location,
+ self.module = SelfAssessmentModule(test_system, self.location,
self.definition, self.descriptor,
static_data, state, metadata=self.metadata)
- self.assertEqual(module.get_score()['score'], 0)
+ def test_get_html(self):
+ html = self.module.get_html(test_system)
+ self.assertTrue("This is sample prompt text" in html)
+
+ def test_self_assessment_flow(self):
+
+ self.assertEqual(self.module.get_score()['score'], 0)
+
+ self.module.save_answer({'student_answer': "I am an answer"}, test_system)
+ self.assertEqual(self.module.state, self.module.ASSESSING)
+
+ self.module.save_assessment({'assessment': '0'}, test_system)
+ self.assertEqual(self.module.state, self.module.DONE)
- module.save_answer({'student_answer': "I am an answer"}, test_system)
- self.assertEqual(module.state, module.ASSESSING)
-
- module.save_assessment({'assessment': '0'}, test_system)
- self.assertEqual(module.state, module.DONE)
-
- d = module.reset({})
+ d = self.module.reset({})
self.assertTrue(d['success'])
- self.assertEqual(module.state, module.INITIAL)
+ self.assertEqual(self.module.state, self.module.INITIAL)
# if we now assess as right, skip the REQUEST_HINT state
- module.save_answer({'student_answer': 'answer 4'}, test_system)
- module.save_assessment({'assessment': '1'}, test_system)
- self.assertEqual(module.state, module.DONE)
+ self.module.save_answer({'student_answer': 'answer 4'}, test_system)
+ self.module.save_assessment({'assessment': '1'}, test_system)
+ self.assertEqual(self.module.state, self.module.DONE)
+
diff --git a/common/test/data/full/policies/6.002_Spring_2012.json b/common/test/data/full/policies/6.002_Spring_2012.json
index 345309ff5c..2f55528b7b 100644
--- a/common/test/data/full/policies/6.002_Spring_2012.json
+++ b/common/test/data/full/policies/6.002_Spring_2012.json
@@ -8,6 +8,7 @@
{"type": "courseware"},
{"type": "course_info", "name": "Course Info"},
{"type": "static_tab", "url_slug": "syllabus", "name": "Syllabus"},
+ {"type": "static_tab", "url_slug": "resources", "name": "Resources"},
{"type": "discussion", "name": "Discussion"},
{"type": "wiki", "name": "Wiki"},
{"type": "progress", "name": "Progress"}
diff --git a/common/test/data/full/tabs/resources.html b/common/test/data/full/tabs/resources.html
new file mode 100644
index 0000000000..bf78c92fb1
--- /dev/null
+++ b/common/test/data/full/tabs/resources.html
@@ -0,0 +1 @@
+
This is another sample tab
\ No newline at end of file
diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py
index 62ebeae9d7..0a7c723cb5 100644
--- a/lms/djangoapps/courseware/tabs.py
+++ b/lms/djangoapps/courseware/tabs.py
@@ -21,6 +21,16 @@ from fs.errors import ResourceNotFoundError
from courseware.access import has_access
from static_replace import replace_urls
+from lxml.html import rewrite_links
+from module_render import get_module
+from courseware.access import has_access
+from static_replace import replace_urls
+from xmodule.modulestore import Location
+from xmodule.modulestore.django import modulestore
+from xmodule.modulestore.xml import XMLModuleStore
+from xmodule.x_module import XModule
+from student.models import unique_id_for_user
+
from open_ended_grading import open_ended_notifications
log = logging.getLogger(__name__)
diff --git a/lms/djangoapps/courseware/tests/factories.py b/lms/djangoapps/courseware/tests/factories.py
new file mode 100644
index 0000000000..6950e28565
--- /dev/null
+++ b/lms/djangoapps/courseware/tests/factories.py
@@ -0,0 +1,44 @@
+import factory
+from student.models import (User, UserProfile, Registration,
+ CourseEnrollmentAllowed)
+from django.contrib.auth.models import Group
+from datetime import datetime
+import uuid
+
+class UserProfileFactory(factory.Factory):
+ FACTORY_FOR = UserProfile
+
+ user = None
+ name = 'Robot Studio'
+ courseware = 'course.xml'
+
+class RegistrationFactory(factory.Factory):
+ FACTORY_FOR = Registration
+
+ user = None
+ activation_key = uuid.uuid4().hex
+
+class UserFactory(factory.Factory):
+ FACTORY_FOR = User
+
+ username = 'robot'
+ email = 'robot@edx.org'
+ password = 'test'
+ first_name = 'Robot'
+ last_name = 'Tester'
+ is_staff = False
+ is_active = True
+ is_superuser = False
+ last_login = datetime.now()
+ date_joined = datetime.now()
+
+class GroupFactory(factory.Factory):
+ FACTORY_FOR = Group
+
+ name = 'test_group'
+
+class CourseEnrollmentAllowedFactory(factory.Factory):
+ FACTORY_FOR = CourseEnrollmentAllowed
+
+ email = 'test@edx.org'
+ course_id = 'edX/test/2012_Fall'
diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py
new file mode 100644
index 0000000000..ed9335d382
--- /dev/null
+++ b/lms/djangoapps/courseware/tests/test_access.py
@@ -0,0 +1,109 @@
+import unittest
+import time
+from mock import Mock
+from django.test import TestCase
+
+from xmodule.modulestore import Location
+from factories import CourseEnrollmentAllowedFactory
+import courseware.access as access
+
+class AccessTestCase(TestCase):
+ def test__has_global_staff_access(self):
+ u = Mock(is_staff=False)
+ self.assertFalse(access._has_global_staff_access(u))
+
+ u = Mock(is_staff=True)
+ self.assertTrue(access._has_global_staff_access(u))
+
+ def test__has_access_to_location(self):
+ location = Location('i4x://edX/toy/course/2012_Fall')
+
+ self.assertFalse(access._has_access_to_location(None, location,
+ 'staff', None))
+ u = Mock()
+ u.is_authenticated.return_value = False
+ self.assertFalse(access._has_access_to_location(u, location,
+ 'staff', None))
+ u = Mock(is_staff=True)
+ self.assertTrue(access._has_access_to_location(u, location,
+ 'instructor', None))
+ # A user has staff access if they are in the staff group
+ u = Mock(is_staff=False)
+ g = Mock()
+ g.name = 'staff_edX/toy/2012_Fall'
+ u.groups.all.return_value = [g]
+ self.assertTrue(access._has_access_to_location(u, location,
+ 'staff', None))
+ # A user has staff access if they are in the instructor group
+ g.name = 'instructor_edX/toy/2012_Fall'
+ self.assertTrue(access._has_access_to_location(u, location,
+ 'staff', None))
+
+ # A user has instructor access if they are in the instructor group
+ g.name = 'instructor_edX/toy/2012_Fall'
+ self.assertTrue(access._has_access_to_location(u, location,
+ 'instructor', None))
+
+ # A user does not have staff access if they are
+ # not in either the staff or the the instructor group
+ g.name = 'student_only'
+ self.assertFalse(access._has_access_to_location(u, location,
+ 'staff', None))
+
+ # A user does not have instructor access if they are
+ # not in the instructor group
+ g.name = 'student_only'
+ self.assertFalse(access._has_access_to_location(u, location,
+ 'instructor', None))
+
+ def test__has_access_string(self):
+ u = Mock(is_staff=True)
+ self.assertFalse(access._has_access_string(u, 'not_global', 'staff', None))
+
+ u._has_global_staff_access.return_value = True
+ self.assertTrue(access._has_access_string(u, 'global', 'staff', None))
+
+ self.assertRaises(ValueError, access._has_access_string, u, 'global', 'not_staff', None)
+
+ def test__has_access_descriptor(self):
+ # TODO: override DISABLE_START_DATES and test the start date branch of the method
+ u = Mock()
+ d = Mock()
+ d.start = time.gmtime(time.time() - 86400) # make sure the start time is in the past
+
+ # Always returns true because DISABLE_START_DATES is set in test.py
+ self.assertTrue(access._has_access_descriptor(u, d, 'load'))
+ self.assertRaises(ValueError, access._has_access_descriptor, u, d, 'not_load_or_staff')
+
+ def test__has_access_course_desc_can_enroll(self):
+ u = Mock()
+ yesterday = time.gmtime(time.time() - 86400)
+ tomorrow = time.gmtime(time.time() + 86400)
+ c = Mock(enrollment_start=yesterday, enrollment_end=tomorrow)
+ c.metadata.get = 'is_public'
+
+ # User can enroll if it is between the start and end dates
+ self.assertTrue(access._has_access_course_desc(u, c, 'enroll'))
+
+ # User can enroll if authenticated and specifically allowed for that course
+ # even outside the open enrollment period
+ u = Mock(email='test@edx.org', is_staff=False)
+ u.is_authenticated.return_value = True
+
+ c = Mock(enrollment_start=tomorrow, enrollment_end=tomorrow, id='edX/test/2012_Fall')
+ c.metadata.get = 'is_public'
+
+ allowed = CourseEnrollmentAllowedFactory(email=u.email, course_id=c.id)
+
+ self.assertTrue(access._has_access_course_desc(u, c, 'enroll'))
+
+ # Staff can always enroll even outside the open enrollment period
+ u = Mock(email='test@edx.org', is_staff=True)
+ u.is_authenticated.return_value = True
+
+ c = Mock(enrollment_start=tomorrow, enrollment_end=tomorrow, id='edX/test/Whenever')
+ c.metadata.get = 'is_public'
+ self.assertTrue(access._has_access_course_desc(u, c, 'enroll'))
+
+ # TODO:
+ # Non-staff cannot enroll outside the open enrollment period if not specifically allowed
diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py
index bf8a65126d..6c41cbac14 100644
--- a/lms/djangoapps/courseware/tests/tests.py
+++ b/lms/djangoapps/courseware/tests/tests.py
@@ -64,6 +64,21 @@ def mongo_store_config(data_dir):
}
}
+def draft_mongo_store_config(data_dir):
+ return {
+ 'default': {
+ 'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore',
+ 'OPTIONS': {
+ 'default_class': 'xmodule.raw_module.RawDescriptor',
+ 'host': 'localhost',
+ 'db': 'test_xmodule',
+ 'collection': 'modulestore',
+ 'fs_root': data_dir,
+ 'render_template': 'mitxmako.shortcuts.render_to_string',
+ }
+ }
+}
+
def xml_store_config(data_dir):
return {
'default': {
@@ -78,6 +93,7 @@ def xml_store_config(data_dir):
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR)
+TEST_DATA_DRAFT_MONGO_MODULESTORE = draft_mongo_store_config(TEST_DATA_DIR)
class ActivateLoginTestCase(TestCase):
'''Check that we can activate and log in'''
@@ -423,6 +439,16 @@ class TestNavigation(PageLoader):
kwargs={'course_id': self.toy.id, 'chapter': 'secret:magic'}))
+@override_settings(MODULESTORE=TEST_DATA_DRAFT_MONGO_MODULESTORE)
+class TestDraftModuleStore(TestCase):
+ def test_get_items_with_course_items(self):
+ store = modulestore()
+ # fix was to allow get_items() to take the course_id parameter
+ store.get_items(Location(None, None, 'vertical', None, None), course_id='abc', depth=0)
+ # test success is just getting through the above statement. The bug was that 'course_id' argument was
+ # not allowed to be passed in (i.e. was throwing exception)
+
+
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class TestViewAuth(PageLoader):
"""Check that view authentication works properly"""
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index 760ccb1d05..5d65d7c632 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -233,10 +233,13 @@ def index(request, course_id, chapter=None, section=None,
# Specifically asked-for section doesn't exist
raise Http404
- # Load all descendents of the section, because we're going to display it's
+ # Load all descendants of the section, because we're going to display its
# html, which in general will need all of its children
+ section_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
+ course.id, request.user, section_descriptor, depth=None)
+
section_module = get_module(request.user, request, section_descriptor.location,
- student_module_cache, course.id, position=position, depth=None)
+ section_module_cache, course.id, position=position, depth=None)
if section_module is None:
# User may be trying to be clever and access something
# they don't have access to.
diff --git a/lms/djangoapps/open_ended_grading/peer_grading_service.py b/lms/djangoapps/open_ended_grading/peer_grading_service.py
index 994ba0b2be..8a649d9017 100644
--- a/lms/djangoapps/open_ended_grading/peer_grading_service.py
+++ b/lms/djangoapps/open_ended_grading/peer_grading_service.py
@@ -31,6 +31,15 @@ This is a mock peer grading service that can be used for unit tests
without making actual service calls to the grading controller
"""
class MockPeerGradingService(object):
+ # TODO: get this rubric parsed and working
+ rubric = """
+
+ Description
+
+
+
+ """
+
def get_next_submission(self, problem_location, grader_id):
return json.dumps({'success': True,
'submission_id':1,
@@ -41,7 +50,7 @@ class MockPeerGradingService(object):
'max_score': 4})
def save_grade(self, location, grader_id, submission_id,
- score, feedback, submission_key):
+ score, feedback, submission_key, rubric_scores):
return json.dumps({'success': True})
def is_student_calibrated(self, problem_location, grader_id):
@@ -57,16 +66,16 @@ class MockPeerGradingService(object):
'max_score': 4})
def save_calibration_essay(self, problem_location, grader_id,
- calibration_essay_id, submission_key, score, feedback):
- return {'success': True, 'actual_score': 2}
+ calibration_essay_id, submission_key, score, feedback, rubric_scores):
+ return json.dumps({'success': True, 'actual_score': 2})
def get_problem_list(self, course_id, grader_id):
return json.dumps({'success': True,
'problem_list': [
json.dumps({'location': 'i4x://MITx/3.091x/problem/open_ended_demo1',
- 'problem_name': "Problem 1", 'num_graded': 3, 'num_pending': 5}),
+ 'problem_name': "Problem 1", 'num_graded': 3, 'num_pending': 5, 'num_required': 7}),
json.dumps({'location': 'i4x://MITx/3.091x/problem/open_ended_demo2',
- 'problem_name': "Problem 2", 'num_graded': 1, 'num_pending': 5})
+ 'problem_name': "Problem 2", 'num_graded': 1, 'num_pending': 5, 'num_required': 8})
]})
class PeerGradingService(GradingService):
@@ -377,4 +386,4 @@ def save_calibration_essay(request, course_id):
return HttpResponse(response, mimetype="application/json")
except GradingServiceError:
log.exception("Error saving calibration grade, location: {0}, submission_id: {1}, submission_key: {2}, grader_id: {3}".format(location, submission_id, submission_key, grader_id))
- return _err_response('Could not connect to grading service')
\ No newline at end of file
+ return _err_response('Could not connect to grading service')
diff --git a/lms/djangoapps/open_ended_grading/tests.py b/lms/djangoapps/open_ended_grading/tests.py
index 059a8939a2..57ea4f319c 100644
--- a/lms/djangoapps/open_ended_grading/tests.py
+++ b/lms/djangoapps/open_ended_grading/tests.py
@@ -6,6 +6,7 @@ django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/open
from django.test import TestCase
from open_ended_grading import staff_grading_service
+from open_ended_grading import peer_grading_service
from django.core.urlresolvers import reverse
from django.contrib.auth.models import Group
@@ -17,9 +18,10 @@ from nose import SkipTest
from mock import patch, Mock
import json
+import logging
+log = logging.getLogger(__name__)
from override_settings import override_settings
-_mock_service = staff_grading_service.MockStaffGradingService()
@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE)
class TestStaffGradingService(ct.PageLoader):
@@ -111,3 +113,144 @@ class TestStaffGradingService(ct.PageLoader):
d = json.loads(r.content)
self.assertTrue(d['success'], str(d))
self.assertIsNotNone(d['problem_list'])
+
+
+@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE)
+class TestPeerGradingService(ct.PageLoader):
+ '''
+ Check that staff grading service proxy works. Basically just checking the
+ access control and error handling logic -- all the actual work is on the
+ backend.
+ '''
+ def setUp(self):
+ xmodule.modulestore.django._MODULESTORES = {}
+
+ self.student = 'view@test.com'
+ self.instructor = 'view2@test.com'
+ self.password = 'foo'
+ self.location = 'TestLocation'
+ self.create_account('u1', self.student, self.password)
+ self.create_account('u2', self.instructor, self.password)
+ self.activate_user(self.student)
+ self.activate_user(self.instructor)
+
+ self.course_id = "edX/toy/2012_Fall"
+ self.toy = modulestore().get_course(self.course_id)
+
+ self.mock_service = peer_grading_service.peer_grading_service()
+
+ self.logout()
+
+ def test_get_next_submission_success(self):
+ self.login(self.student, self.password)
+
+ url = reverse('peer_grading_get_next_submission', kwargs={'course_id': self.course_id})
+ data = {'location': self.location}
+
+ r = self.check_for_post_code(200, url, data)
+ d = json.loads(r.content)
+ self.assertTrue(d['success'])
+ self.assertIsNotNone(d['submission_id'])
+ self.assertIsNotNone(d['prompt'])
+ self.assertIsNotNone(d['submission_key'])
+ self.assertIsNotNone(d['max_score'])
+
+ def test_get_next_submission_missing_location(self):
+ self.login(self.student, self.password)
+ url = reverse('peer_grading_get_next_submission', kwargs={'course_id': self.course_id})
+ data = {}
+ r = self.check_for_post_code(200, url, data)
+ d = json.loads(r.content)
+ self.assertFalse(d['success'])
+ self.assertEqual(d['error'], "Missing required keys: location")
+
+ def test_save_grade_success(self):
+ self.login(self.student, self.password)
+ url = reverse('peer_grading_save_grade', kwargs={'course_id': self.course_id})
+ data = {'location': self.location,
+ 'submission_id': '1',
+ 'submission_key': 'fake key',
+ 'score': '2',
+ 'feedback': 'This is feedback',
+ 'rubric_scores[]': [1, 2]}
+ r = self.check_for_post_code(200, url, data)
+ d = json.loads(r.content)
+ self.assertTrue(d['success'])
+
+ def test_save_grade_missing_keys(self):
+ self.login(self.student, self.password)
+ url = reverse('peer_grading_save_grade', kwargs={'course_id': self.course_id})
+ data = {}
+ r = self.check_for_post_code(200, url, data)
+ d = json.loads(r.content)
+ self.assertFalse(d['success'])
+ self.assertTrue(d['error'].find('Missing required keys:') > -1)
+
+ def test_is_calibrated_success(self):
+ self.login(self.student, self.password)
+ url = reverse('peer_grading_is_student_calibrated', kwargs={'course_id': self.course_id})
+ data = {'location': self.location}
+ r = self.check_for_post_code(200, url, data)
+ d = json.loads(r.content)
+ self.assertTrue(d['success'])
+ self.assertTrue('calibrated' in d)
+
+ def test_is_calibrated_failure(self):
+ self.login(self.student, self.password)
+ url = reverse('peer_grading_is_student_calibrated', kwargs={'course_id': self.course_id})
+ data = {}
+ r = self.check_for_post_code(200, url, data)
+ d = json.loads(r.content)
+ self.assertFalse(d['success'])
+ self.assertFalse('calibrated' in d)
+
+ def test_show_calibration_essay_success(self):
+ self.login(self.student, self.password)
+
+ url = reverse('peer_grading_show_calibration_essay', kwargs={'course_id': self.course_id})
+ data = {'location': self.location}
+
+ r = self.check_for_post_code(200, url, data)
+ d = json.loads(r.content)
+ self.assertTrue(d['success'])
+ self.assertIsNotNone(d['submission_id'])
+ self.assertIsNotNone(d['prompt'])
+ self.assertIsNotNone(d['submission_key'])
+ self.assertIsNotNone(d['max_score'])
+
+ def test_show_calibration_essay_missing_key(self):
+ self.login(self.student, self.password)
+
+ url = reverse('peer_grading_show_calibration_essay', kwargs={'course_id': self.course_id})
+ data = {}
+
+ r = self.check_for_post_code(200, url, data)
+ d = json.loads(r.content)
+
+ self.assertFalse(d['success'])
+ self.assertEqual(d['error'], "Missing required keys: location")
+
+ def test_save_calibration_essay_success(self):
+ self.login(self.student, self.password)
+ url = reverse('peer_grading_save_calibration_essay', kwargs={'course_id': self.course_id})
+ data = {'location': self.location,
+ 'submission_id': '1',
+ 'submission_key': 'fake key',
+ 'score': '2',
+ 'feedback': 'This is feedback',
+ 'rubric_scores[]': [1, 2]}
+ r = self.check_for_post_code(200, url, data)
+ d = json.loads(r.content)
+ self.assertTrue(d['success'])
+ self.assertTrue('actual_score' in d)
+
+ def test_save_calibration_essay_missing_keys(self):
+ self.login(self.student, self.password)
+ url = reverse('peer_grading_save_calibration_essay', kwargs={'course_id': self.course_id})
+ data = {}
+ r = self.check_for_post_code(200, url, data)
+ d = json.loads(r.content)
+ self.assertFalse(d['success'])
+ self.assertTrue(d['error'].find('Missing required keys:') > -1)
+ self.assertFalse('actual_score' in d)
+
diff --git a/lms/static/coffee/fixtures/staff_grading.html b/lms/static/coffee/fixtures/staff_grading.html
new file mode 100644
index 0000000000..2fe5a39a17
--- /dev/null
+++ b/lms/static/coffee/fixtures/staff_grading.html
@@ -0,0 +1,76 @@
+
+
+
+
Staff grading
+
+
+
+
+
+
+
+
+
+
Instructions
+
+
This is the list of problems that current need to be graded in order to train the machine learning models. Each problem needs to be trained separately, and we have indicated the number of student submissions that need to be graded in order for a model to be generated. You can grade more than the minimum required number of submissions--this will improve the accuracy of machine learning, though with diminishing returns. You can see the current accuracy of machine learning while grading.
@@ -41,12 +29,8 @@ describe 'Courseware', ->
"""
@courseware.render()
- it 'detect the video elements and convert them', ->
- expect(window.Video).toHaveBeenCalledWith('1', '1.0:abc1234')
- expect(window.Video).toHaveBeenCalledWith('2', '1.0:def5678')
-
- it 'detect the problem element and convert it', ->
- expect(window.Problem).toHaveBeenCalledWith(3, 'problem_3', '/example/url/')
+ it 'ensure that the XModules have been loaded', ->
+ expect(XModule.loadModules).toHaveBeenCalled()
it 'detect the histrogram element and convert it', ->
expect(window.Histogram).toHaveBeenCalledWith('3', [[0, 1]])
diff --git a/lms/static/coffee/spec/navigation_spec.coffee b/lms/static/coffee/spec/navigation_spec.coffee
index cb98c2b64c..1340984e52 100644
--- a/lms/static/coffee/spec/navigation_spec.coffee
+++ b/lms/static/coffee/spec/navigation_spec.coffee
@@ -16,6 +16,7 @@ describe 'Navigation', ->
active: 1
header: 'h3'
autoHeight: false
+ heightStyle: 'content'
describe 'when there is no active section', ->
beforeEach ->
@@ -23,11 +24,12 @@ describe 'Navigation', ->
$('#accordion').append('
')
new Navigation
- it 'activate the accordian with section 1 as active', ->
+ it 'activate the accordian with no section as active', ->
expect($('#accordion').accordion).toHaveBeenCalledWith
- active: 1
+ active: 0
header: 'h3'
autoHeight: false
+ heightStyle: 'content'
it 'binds the accordionchange event', ->
Navigation.bind()
diff --git a/lms/static/coffee/spec/staff_grading_spec.coffee b/lms/static/coffee/spec/staff_grading_spec.coffee
new file mode 100644
index 0000000000..595a9eb550
--- /dev/null
+++ b/lms/static/coffee/spec/staff_grading_spec.coffee
@@ -0,0 +1,11 @@
+describe 'StaffGrading', ->
+ beforeEach ->
+ spyOn Logger, 'log'
+ @mockBackend = new StaffGradingBackend('url', true)
+
+ describe 'constructor', ->
+ beforeEach ->
+ @staff_grading = new StaffGrading(@mockBackend)
+
+ it 'we are originally in the list view', ->
+ expect(@staff_grading.list_view).toBe(true)
diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee
index c7a8a01bab..005a8e682e 100644
--- a/lms/static/coffee/src/staff_grading/staff_grading.coffee
+++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee
@@ -9,9 +9,13 @@ state_graded = "graded"
state_no_data = "no_data"
state_error = "error"
-class StaffGradingBackend
+class @StaffGradingBackend
constructor: (ajax_url, mock_backend) ->
@ajax_url = ajax_url
+ # prevent this from trying to make requests when we don't have
+ # a proper url
+ if !ajax_url
+ mock_backend = true
@mock_backend = mock_backend
if @mock_backend
@mock_cnt = 0
@@ -142,7 +146,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
.error => callback({success: false, error: "Error occured while performing this operation"})
-class StaffGrading
+class @StaffGrading
constructor: (backend) ->
@backend = backend
diff --git a/lms/static/images/press/releases/eric-lander-lab.jpg b/lms/static/images/press/releases/eric-lander-lab.jpg
new file mode 100644
index 0000000000..54f027775d
Binary files /dev/null and b/lms/static/images/press/releases/eric-lander-lab.jpg differ
diff --git a/lms/static/images/press/releases/eric-lander-lab_x300.jpg b/lms/static/images/press/releases/eric-lander-lab_x300.jpg
new file mode 100644
index 0000000000..38c4a208ed
Binary files /dev/null and b/lms/static/images/press/releases/eric-lander-lab_x300.jpg differ
diff --git a/lms/static/images/press/releases/eric-lander_240x180.jpg b/lms/static/images/press/releases/eric-lander_240x180.jpg
new file mode 100644
index 0000000000..ee5b2d875b
Binary files /dev/null and b/lms/static/images/press/releases/eric-lander_240x180.jpg differ
diff --git a/lms/templates/feed.rss b/lms/templates/feed.rss
index 9d20ed67bc..8048c86cbd 100644
--- a/lms/templates/feed.rss
+++ b/lms/templates/feed.rss
@@ -6,7 +6,16 @@
##
EdX Blog
- 2013-01-21T14:00:12-07:00
+ 2013-01-30T14:00:12-07:00
+
+ tag:www.edx.org,2012:Post/13
+ 2013-01-30T10:00:00-07:00
+ 2013-01-30T10:00:00-07:00
+
+ New biology course from human genome pioneer Eric Lander
+ <img src="${static.url('images/press/releases/eric-lander_240x180.jpg')}" />
+ <p></p>
+ tag:www.edx.org,2012:Post/122013-01-29T10:00:00-07:00
diff --git a/lms/templates/problem_ajax.html b/lms/templates/problem_ajax.html
index 012e4276c3..42cd18c4e3 100644
--- a/lms/templates/problem_ajax.html
+++ b/lms/templates/problem_ajax.html
@@ -1 +1 @@
-
+
diff --git a/lms/templates/static_templates/faq.html b/lms/templates/static_templates/faq.html
index f6f5b07fe7..a0d8c6d577 100644
--- a/lms/templates/static_templates/faq.html
+++ b/lms/templates/static_templates/faq.html
@@ -39,7 +39,14 @@
Will certificates be awarded?
-
Yes. Online learners who demonstrate mastery of subjects can earn a certificate of mastery. Certificates will be issued by edX under the name of the underlying "X University" from where the course originated, i.e. HarvardX, MITx or BerkeleyX. For the courses in Fall 2012, those certificates will be free. There is a plan to charge a modest fee for certificates in the future.
+
Yes. Online learners who demonstrate mastery of subjects can earn a certificate
+ of mastery. Certificates will be issued at the discretion of edX and the underlying
+ X University that offered the course under the name of the underlying "X
+ University" from where the course originated, i.e. HarvardX, MITx or BerkeleyX.
+ For the courses in Fall 2012, those certificates will be free. There is a plan to
+ charge a modest fee for certificates in the future. Note: At this time, edX is
+ holding certificates for learners connected with Cuba, Iran, Syria and Sudan
+ pending confirmation that the issuance is in compliance with U.S. embargoes.
What will the scope of the online courses be? How many? Which faculty?
Will I get a certificate for taking an edX course?
-
-
Online learners who receive a passing grade for a course will receive a certificate of mastery from edX and the underlying X University that offered the course. For example, a certificate of mastery for MITx’s 6.002x Circuits & Electronics will come from edX and MITx.
+
+
Online learners who receive a passing grade for a course will receive a certificate
+ of mastery at the discretion of edX and the underlying X University that offered
+ the course. For example, a certificate of mastery for MITx’s 6.002x Circuits &
+ Electronics will come from edX and MITx.
+
If you passed the course, your certificate of mastery will be delivered online
+ through edx.org. So be sure to check your email in the weeks following the final
+ grading – you will be able to download and print your certificate. Note: At this
+ time, edX is holding certificates for learners connected with Cuba, Iran, Syria
+ and Sudan pending confirmation that the issuance is in compliance with U.S.
+ embargoes.
Pilot project offers online courses, educational support and jobs training through Boston community centers
CAMBRIDGE, MA – January 29, 2013 –
-EdX, the not-for-profit online learning initiative founded by Harvard University and the Massachusetts Institute of Technology (MIT), the not-for-profit online learning initiative founded by Harvard University and the Massachusetts Institute of Technology (MIT), announced today a pilot project with the City of Boston, Harvard and MIT to make online courses available through internet-connected Boston neighborhood community centers, high schools and libraries. A first-of-its-kind project, BostonX brings together innovators from the country’s center of higher education to offer Boston residents access to courses, internships, job training and placement services, and locations for edX students to gather, socialize and deepen learning.
+EdX, the not-for-profit online learning initiative founded by Harvard University and the Massachusetts Institute of Technology (MIT), announced today a pilot project with the City of Boston, Harvard and MIT to make online courses available through internet-connected Boston neighborhood community centers, high schools and libraries. A first-of-its-kind project, BostonX brings together innovators from the country’s center of higher education to offer Boston residents access to courses, internships, job training and placement services, and locations for edX students to gather, socialize and deepen learning.
“We must connect adults and youth in our neighborhoods with the opportunities of the knowledge economy,” said Mayor Tom Menino. “BostonX will help update our neighbors’ skills and our community centers. As a first step, I’m pleased to announce a pilot with Harvard, MIT and edX, their online learning initiative, which will bring free courses and training to our community centers.”
diff --git a/lms/templates/static_templates/press_releases/eric_lander_secret_of_life.html b/lms/templates/static_templates/press_releases/eric_lander_secret_of_life.html
new file mode 100644
index 0000000000..d91c8091d7
--- /dev/null
+++ b/lms/templates/static_templates/press_releases/eric_lander_secret_of_life.html
@@ -0,0 +1,92 @@
+<%! from django.core.urlresolvers import reverse %>
+<%inherit file="../../main.html" />
+
+<%namespace name='static' file='../../static_content.html'/>
+
+<%block name="title">Human Genome Pioneer Eric Lander to reveal “the secret of life”%block>
+
+
+
+
+
+
Human Genome Pioneer Eric Lander to reveal “the secret of life”
+
+
+
Broad Institute Director shares his MIT introductory biology course, covering topics in biochemistry, genetics and genomics, through edX.
+
+
+
+
+
Eric Lander, the founding director of the Broad Institute and a professor at MIT and Harvard Medical School.
CAMBRIDGE, MA – January 30, 2013 –
+In the past 10 years, the ability to decode or “sequence” DNA has grown by a million-fold, a stunning rate of progress that is producing a flood of information about human biology and disease. Because of these advances, the scientific community — and the world as a whole — stands on the verge of a revolution in biology. In the coming decades scientists will be able to understand how cells are “wired” and how that wiring is disrupted in human diseases ranging from diabetes to cancer to schizophrenia. Now, with his free online course, 7.00x Introductory Biology: “The Secret of Life”, genome pioneer Eric Lander, the founding director of the Broad Institute and a professor at MIT and Harvard Medical School, will explain to students around the world the basics of biology – the secret of life, so to speak – so that they can understand today’s revolution in biology.
+
+
EdX, the not-for-profit online learning initiative founded by Harvard University and the Massachusetts Institute of Technology (MIT), brings the best courses from the best faculty at the best institutions to anyone with an Internet connection. For the past 20 years, legendary teacher Lander has taught Introductory Biology to more than half of all MIT students. He has now adapted his course for online education, creating the newest course on the edX platform. The course, 7.00X, is now open for enrollment, with the first class slated for March 5th. This course will include innovative technology including a 3D molecule viewer and gene explorer tool to transform the learning experience. It is open to all levels and types of learners.
+
+
“Introducing the freshman class of MIT to the basics of biology is exhilarating,” said Lander. “Now, with this edX course, I look forward to teaching people around the world. There are no prerequisites for this course – other than curiosity and an interest in understanding some of the greatest scientific challenges of our time.”
+
+
Those taking the course will learn the fundamental ideas that underlie modern biology and medicine, including genetics, biochemistry, molecular biology, recombinant DNA, genomics and genomic medicine. They will become familiar with the structure and function of macromolecules such as DNA, RNA and proteins and understand how information flows within cells. Students will explore how mutations affect biological function and cause human disease. They will learn about modern molecular biological techniques and their wide-ranging impact.
+
+
“Eric Lander has created this remarkable digitally enhanced introduction to genetics and biology,” said Anant Agarwal, President of edX. “With this unique online version, he has brought the introductory biology course to a new level. It has been completely rethought and retooled, incorporating cutting-edge online interactive tools as well as community-building contests and milestone-based prizes.”
+
+
With online courses through edX like 7.00x, what matters isn’t what people have achieved or their transcripts, but their desire to learn. Students only need to come with a real interest in science and the desire to understand what's going on at the forefront of biology, and to learn the fundamental principles on which an amazing biomedical revolution is based – from one of the top scientist in the world. 7.00x Introductory Biology: The Secret of Life is now available for enrollment. Classes will start on March 5, 2013.
+
+
Dr. Eric Lander is President and Founding Director of the Broad Institute of Harvard and MIT, a new kind of collaborative biomedical research institution focused on genomic medicine. Dr. Lander is also Professor of Biology at MIT and Professor of Systems Biology at the Harvard Medical School. In addition, Dr. Lander serves as Co-Chair of the President’s Council of Advisors on Science and Technology, which advises the White House on science and technology. A geneticist, molecular biologist and mathematician, Dr. Lander has played a pioneering role in all aspects of the reading, understanding and medical application of the human genome. He was a principal leader of the international Human Genome Project (HGP) from 1990-2003, with his group being the largest contributor to the mapping and sequencing of the human genetic blueprint. Dr. Lander was an early pioneer in the free availability of genomic tools and information. Finally, he has mentored an extraordinary cadre of young scientists who have become the next generation of leaders in medical genomics. The recipient of numerous awards and honorary degrees, Dr. Lander was elected a member of the U.S. National Academy of Sciences in 1997 and of the U.S. Institute of Medicine in 1999.
The Eli and Edythe L. Broad Institute of MIT and Harvard was founded in 2003 to empower this generation of creative scientists to transform medicine with new genome-based knowledge. The Broad Institute seeks to describe all the molecular components of life and their connections; discover the molecular basis of major human diseases; develop effective new approaches to diagnostics and therapeutics; and disseminate discoveries, tools, methods and data openly to the entire scientific community.
+
+
Founded by MIT, Harvard and its affiliated hospitals, and the visionary Los Angeles philanthropists Eli and Edythe L. Broad, the Broad Institute includes faculty, professional staff and students from throughout the MIT and Harvard biomedical research communities and beyond, with collaborations spanning over a hundred private and public institutions in more than 40 countries worldwide. For further information about the Broad Institute, go to www.broadinstitute.org.
+
+
About edX
+
+
EdX is a not-for-profit enterprise of its founding partners Harvard University and the Massachusetts Institute of Technology focused on transforming online and on-campus learning through groundbreaking methodologies, game-like experiences and cutting-edge research. EdX provides inspirational and transformative knowledge to students of all ages, social status, and income who form worldwide communities of learners. EdX uses its open source technology to transcend physical and social borders. We’re focused on people, not profit. EdX is based in Cambridge, Massachusetts in the USA.
EdX is a not-for-profit enterprise of its founding partners Harvard University and the Massachusetts Institute of Technology focused on transforming online and on-campus learning through groundbreaking methodologies, game-like experiences and cutting-edge research. EdX provides inspirational and transformative knowledge to students of all ages, social status, and income who form worldwide communities of learners. EdX uses its open source technology to transcend physical and social borders. We’re focused on people, not profit. EdX is based in Cambridge, Massachusetts in the USA.