From 3ff31892609bcb15f88f35c46a86d8872c173f6a Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Tue, 15 Jan 2013 15:39:05 -0500 Subject: [PATCH 01/98] Finished tests for access.py (as far as able) --- .../courseware/tests/test_access.py | 126 ++++++++++++++++++ .../courseware/tests/test_progress.py | 67 ++++++++++ 2 files changed, 193 insertions(+) create mode 100644 lms/djangoapps/courseware/tests/test_access.py create mode 100644 lms/djangoapps/courseware/tests/test_progress.py diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py new file mode 100644 index 0000000000..ebdad9e6a8 --- /dev/null +++ b/lms/djangoapps/courseware/tests/test_access.py @@ -0,0 +1,126 @@ +import unittest +import logging +import time +from mock import MagicMock, patch + +from django.conf import settings +from django.test import TestCase + +from xmodule.course_module import CourseDescriptor +from xmodule.error_module import ErrorDescriptor +from xmodule.modulestore import Location +from xmodule.timeparse import parse_time +from xmodule.x_module import XModule, XModuleDescriptor +import courseware.access as access + +class Stub: + def __init__(self): + pass + +class AccessTestCase(TestCase): + def setUp(self): + pass + + def test__has_global_staff_access(self): + # Only 2 branches? + mock_user = MagicMock() + mock_user.is_staff = True + self.assertTrue(access._has_global_staff_access(mock_user)) + mock_user_2 = MagicMock() + mock_user.is_staff = False + self.assertFalse(access._has_global_staff_access(mock_user)) + + def test__has_access_to_location(self): + mock_user = MagicMock() + mock_user.is_authenticated.return_value = False + self.assertFalse(access._has_access_to_location(mock_user, "dummy", + "dummy")) + mock_user_2 = MagicMock() + mock_user_2.groups.all.return_value = ['instructor_toy'] + location = MagicMock(spec=Location) + location.course = 'toy' + self.assertTrue(access._has_access_to_location(mock_user_2, location, + "instructor")) + mock_user_3 = MagicMock() + mock_user_3.is_staff = False + self.assertFalse(access._has_access_to_location(mock_user_3, 'dummy', + 'dummy')) + + def test__dispatch(self): + self.assertRaises(ValueError, access._dispatch,{}, 'action', 'dummy', + 'dummy') + + def test__has_access_string(self): + mock_user = MagicMock() + mock_user.is_staff = True + self.assertTrue(access._has_access_string(mock_user, 'global', 'staff')) + self.assertFalse(access._has_access_string(mock_user, 'dummy', 'staff')) + + def test__has_access_descriptor(self): + mock_descriptor = MagicMock() + mock_descriptor.start = 0 + # test has dependency on time.gmtime() > 0 + self.assertTrue(access._has_access_descriptor("dummy", mock_descriptor, + 'load')) + mock_descriptor_2 = MagicMock() + mock_descriptor_2.start = None + self.assertTrue(access._has_access_descriptor("dummy", mock_descriptor_2, + 'load')) + + def test__has_access_error_desc(self): + mock_user = None + mock_descriptor = MagicMock() + mock_descriptor.location = None + # Just want to make sure function goes through path. + self.assertFalse(access._has_access_error_desc(mock_user, mock_descriptor, + 'load')) + + def test__get_access_group_name_course_desc(self): + self.assertEquals(access._get_access_group_name_course_desc('dummy', + 'notstaff'), + []) + # Problem: Can't use a Mock for location because needs to be a valid + # input to Location + # Getting "IndentationError: expected an indented block" +## tag, org, course, category, name = [MagicMock()]*5 +## #mock_course.location = ['tag', 'org', 'course', 'category', 'name'] +## L = Location([tag, org, course, category, name]) +## print L.course_id() +## assert False + #mock_course.location.course = 'toy' + #access._get_access_group_name_course_desc(mock_course, 'staff') + + def test__has_access_course_desc(self): + # This is more of a test for see_exists + mock_course = MagicMock() + mock_course.metadata.get = 'is_public' + self.assertTrue(access._has_access_course_desc('dummy', mock_course, + 'see_exists')) + mock_course_2 = MagicMock() + mock_course_2.metadata.get = 'private' + # Is there a way to see all the functions that have been called on a mock? + # Basically, I want to see if _has_staff_access_to_descriptor is called on + # the mock user and course + # This actually doesn't seem possible, according to the API + # None user can see course even if not 'is_public'? + self.assertTrue(access._has_access_course_desc(None, mock_course_2, + 'see_exists')) + def test_get_access_group_name(self): + # Need to create an instance of CourseDescriptor + # Is it necessary to test? basically "testing" python + self.assertRaises(TypeError, access.get_access_group_name, + 'notCourseDescriptor', 'dummy_action') + + def test_has_access(self): + magic = MagicMock() + error = ErrorDescriptor(magic) + mock_user = MagicMock() + self.assertFalse(access.has_access(None, error, 'load')) + self.assertFalse(access.has_access(mock_user, 'dummy', 'staff')) + self.assertRaises(TypeError, access.has_access,'dummyuser', {}, 'dummy') + +# How do decorators work? I think that is the correct +## def test_patches(self): +## user = Stub() +## @patch.object(Stub, "is_staff", True) +## self.assertTrue(access._has_global_staff_access(mock_user)) diff --git a/lms/djangoapps/courseware/tests/test_progress.py b/lms/djangoapps/courseware/tests/test_progress.py new file mode 100644 index 0000000000..480d594863 --- /dev/null +++ b/lms/djangoapps/courseware/tests/test_progress.py @@ -0,0 +1,67 @@ +from django.test import TestCase +from courseware import progress +from mock import MagicMock + + + +class ProgessTests(TestCase): + def setUp(self): + + self.d = dict({'duration_total': 0, + 'duration_watched': 0, + 'done': True, + 'questions_correct': 4, + 'questions_incorrect': 0, + 'questions_total': 0}) + + self.c = progress.completion() + self.c2= progress.completion() + self.c2.dict = dict({'duration_total': 0, + 'duration_watched': 0, + 'done': True, + 'questions_correct': 2, + 'questions_incorrect': 1, + 'questions_total': 0}) + + self.cplusc2 = dict({'duration_total': 0, + 'duration_watched': 0, + 'done': True, + 'questions_correct': 2, + 'questions_incorrect': 1, + 'questions_total': 0}) + + + + self.oth = dict({'duration_total': 0, + 'duration_watched': 0, + 'done': True, + 'questions_correct': 4, + 'questions_incorrect': 0, + 'questions_total': 7}) + + self.x = MagicMock() + self.x.dict = self.oth + + self.d_oth = {'duration_total': 0, + 'duration_watched': 0, + 'done': True, + 'questions_correct': 4, + 'questions_incorrect': 0, + 'questions_total': 7} + def test_getitem(self): + self.assertEqual(self.c.__getitem__('duration_watched'), 0) + + def test_setitem(self): + self.c.__setitem__('questions_correct', 4) + self.assertEqual(str(self.c),str(self.d)) + + # def test_add(self): + # self.assertEqual(self.c.__add__(self.c2), self.cplusc2) + + def test_contains(self): + + return self.c.__contains__('meow') + #self.assertEqual(self.c.__contains__('done'), True) + + def test_repr(self): + self.assertEqual(self.c.__repr__(), str(progress.completion())) From 1565cd5bd364caa2d478ae3bd27095462e46a5ec Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Fri, 18 Jan 2013 14:44:17 -0500 Subject: [PATCH 02/98] Test cases for lms/djangoapps/courseware/module_render in test_module_render --- .../courseware/tests/test_module_render.py | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 lms/djangoapps/courseware/tests/test_module_render.py diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py new file mode 100644 index 0000000000..8340d1fda2 --- /dev/null +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -0,0 +1,164 @@ +from unittest import TestCase +import logging +from mock import MagicMock, patch +import json +import factory + +from django.http import Http404, HttpResponse, HttpRequest +from django.conf import settings +from django.contrib.auth.models import User +from django.test.client import Client +from django.conf import settings + +from courseware.models import StudentModule +from xmodule.modulestore.exceptions import ItemNotFoundError +from xmodule.modulestore import Location +import courseware.module_render as render + +class Stub: + def __init__(self): + pass + +class ModuleRenderTestCase(TestCase): + def setUp(self): + self.location = ['tag', 'org', 'course', 'category', 'name'] + + def test_toc_for_course(self): + mock_course = MagicMock() + mock_course.id = 'dummy' + mock_course.location = Location(self.location) + mock_course.get_children.return_value = [] + mock_user = MagicMock() + mock_user.is_authenticated.return_value = False + self.assertIsNone(render.toc_for_course(mock_user,'dummy', + mock_course, 'dummy', 'dummy')) + + def test_get_module(self): + self.assertIsNone(render.get_module('dummyuser',None,\ + 'invalid location',None,None)) + + + def test__get_module(self): + mock_user = MagicMock() + mock_user.is_authenticated.return_value = True + location = ['tag', 'org', 'course', 'category', 'name'] + #render._get_module(mock_user, + + def test_get_instance_module(self): + mock_user = MagicMock() + mock_user.is_authenticated.return_value = False + self.assertIsNone(render.get_instance_module('dummy', mock_user, 'dummy', + 'dummy')) + mock_user_2 = MagicMock() + mock_user_2.is_authenticated.return_value = True + mock_module = MagicMock() + mock_module.descriptor.stores_state = False + self.assertIsNone(render.get_instance_module('dummy', mock_user_2, + mock_module,'dummy')) + + def test_get_shared_instance_module(self): +## class MockUserFactory(factory.Factory): +## FACTORY_FOR = MagicMock +## is_authenticated.return_value = + mock_user = MagicMock(User) + mock_user.is_authenticated.return_value = False + self.assertIsNone(render.get_shared_instance_module('dummy', mock_user, 'dummy', + 'dummy')) + mock_user_2 = MagicMock(User) + mock_user_2.is_authenticated.return_value = True + mock_module = MagicMock() + mock_module.shared_state_key = 'key' + self.assertIsInstance(render.get_shared_instance_module('dummy', mock_user, + mock_module, 'dummy'), StudentModule) + + + + def test_xqueue_callback(self): + mock_request = MagicMock() + mock_request.POST.copy.return_value = {} + # 339 + self.assertRaises(Http404, render.xqueue_callback,mock_request, + 'dummy', 'dummy', 'dummy', 'dummy') + mock_request_2 = MagicMock() + xpackage = {'xqueue_header': json.dumps({}), + 'xqueue_body' : 'Message from grader'} + mock_request_2.POST.copy.return_value = xpackage + # 342 + self.assertRaises(Http404, render.xqueue_callback,mock_request_2, + 'dummy', 'dummy', 'dummy', 'dummy') + mock_request_3 = MagicMock() + xpackage_2 = {'xqueue_header': json.dumps({'lms_key':'secretkey'}), + 'xqueue_body' : 'Message from grader'} + mock_request_3.POST.copy.return_value = xpackage_2 +## self.assertRaises(Http404, render.xqueue_callback, mock_request_3, +## 'dummy', 0, 'dummy', 'dummy') + # continue later + + def test_modx_dispatch(self): + self.assertRaises(Http404, render.modx_dispatch, 'dummy', 'dummy', + 'invalid Location', 'dummy') + mock_request = MagicMock() + mock_request.FILES.keys.return_value = ['file_id'] + mock_request.FILES.getlist.return_value = ['file']*(settings.MAX_FILEUPLOADS_PER_INPUT + 1) + self.assertEquals(render.modx_dispatch(mock_request, 'dummy', self.location, + 'dummy').content, + json.dumps({'success': 'Submission aborted! Maximum %d files may be submitted at once' %\ + settings.MAX_FILEUPLOADS_PER_INPUT})) + mock_request_2 = MagicMock() + mock_request_2.FILES.keys.return_value = ['file_id'] + inputfile = Stub() + inputfile.size = 1 + settings.STUDENT_FILEUPLOAD_MAX_SIZE + inputfile.name = 'name' + filelist = [inputfile] + mock_request_2.FILES.getlist.return_value = filelist + self.assertEquals(render.modx_dispatch(mock_request_2, 'dummy', self.location, + 'dummy').content, + json.dumps({'success': 'Submission aborted! Your file "%s" is too large (max size: %d MB)' %\ + (inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE/(1000**2))})) + mock_request_3 = MagicMock() + mock_request_3.POST.copy.return_value = {} + inputfile_2 = Stub() + inputfile_2.size = 1 + inputfile_2.name = 'name' + self.assertRaises(ItemNotFoundError, render.modx_dispatch, + mock_request_3, 'dummy', self.location, 'toy') + # Deadend + + def test_preview_chemcalc(self): + mock_request = MagicMock() + mock_request.method = 'notGET' + self.assertRaises(Http404, render.preview_chemcalc, mock_request) + mock_request_2 = MagicMock() + mock_request_2.method = 'GET' + mock_request_2.GET.get.return_value = None + self.assertEquals(render.preview_chemcalc(mock_request_2).content, + json.dumps({'preview':'', + 'error':'No formula specified.'})) + + mock_request_3 = MagicMock() + mock_request_3.method = 'GET' + # Test fails because chemcalc.render_to_html always parses strings? + mock_request_3.GET.get.return_value = unicode('\x12400', errors="strict") +## self.assertEquals(render.preview_chemcalc(mock_request_3).content, +## json.dumps({'preview':'', +## 'error':"Couldn't parse formula: formula"})) +## + mock_request_3 = MagicMock() + mock_request_3.method = 'GET' + mock_request_3.GET.get.return_value = Stub() + self.assertEquals(render.preview_chemcalc(mock_request_3).content, + json.dumps({'preview':'', + 'error':"Error while rendering preview"})) + + + def test_get_score_bucket(self): + self.assertEquals(render.get_score_bucket(0, 10), 'incorrect') + self.assertEquals(render.get_score_bucket(1, 10), 'partial') + self.assertEquals(render.get_score_bucket(10, 10), 'correct') + # get_score_bucket calls error cases 'incorrect' + self.assertEquals(render.get_score_bucket(11, 10), 'incorrect') + self.assertEquals(render.get_score_bucket(-1, 10), 'incorrect') + + +class MagicMockFactory(factory.Factory): + FACTORY_FOR = MagicMock From 6e773909e48b84475d239c76ee8d9431eb80d71b Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Tue, 22 Jan 2013 10:36:05 -0500 Subject: [PATCH 03/98] added test_views --- .../courseware/tests/test_module_render.py | 4 +- lms/djangoapps/courseware/tests/test_views.py | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 lms/djangoapps/courseware/tests/test_views.py diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index 8340d1fda2..d4748ebb04 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -70,8 +70,6 @@ class ModuleRenderTestCase(TestCase): mock_module.shared_state_key = 'key' self.assertIsInstance(render.get_shared_instance_module('dummy', mock_user, mock_module, 'dummy'), StudentModule) - - def test_xqueue_callback(self): mock_request = MagicMock() @@ -162,3 +160,5 @@ class ModuleRenderTestCase(TestCase): class MagicMockFactory(factory.Factory): FACTORY_FOR = MagicMock + v = factory.LazyAttribute(i for i in [True, False, False]) + diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py new file mode 100644 index 0000000000..1acf175bbd --- /dev/null +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -0,0 +1,37 @@ +from unittest import TestCase +import logging +from mock import MagicMock, patch + +from django.conf import settings +from django.test.utils import override_settings + +import courseware.views as views + +class Stub(): + pass + +class ViewsTestCase(TestCase): + def setUp(self): + pass + + + def test_user_groups(self): + mock_user = MagicMock() + mock_user.is_authenticated.return_value = False + self.assertEquals(views.user_groups(mock_user),[]) + + @override_settings(DEBUG = True) + def test_user_groups_debug(self): + mock_user = MagicMock() + mock_user.is_authenticated.return_value = True + pass + #views.user_groups(mock_user) + #Keep going later + + def test_get_current_child(self): + self.assertIsNone(views.get_current_child(Stub())) + mock_xmodule = MagicMock() + mock_xmodule.position = -1 + mock_xmodule.get_display_items.return_value = ['one','two'] + print views.user_groups(mock_xmodule) + self.assertEquals(views.user_groups(mock_xmodule), 'one') From 35650233891b668d7c74570d59fd030dd49b0485 Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Tue, 22 Jan 2013 14:01:01 -0500 Subject: [PATCH 04/98] created test_courses.py --- .../courseware/tests/test_courses.py | 21 +++++++++ lms/djangoapps/courseware/tests/test_views.py | 47 +++++++++++++++++-- 2 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 lms/djangoapps/courseware/tests/test_courses.py diff --git a/lms/djangoapps/courseware/tests/test_courses.py b/lms/djangoapps/courseware/tests/test_courses.py new file mode 100644 index 0000000000..7e1456efec --- /dev/null +++ b/lms/djangoapps/courseware/tests/test_courses.py @@ -0,0 +1,21 @@ +from mock import MagicMock, patch +import datetime + +from django.test import TestCase +from django.contrib.auth.models import User + +from student.models import CourseEnrollment +import courseware.courses as courses + +class CoursesTestCase(TestCase): + def setUp(self): + self.user = User.objects.create(username='dummy', password='123456', + email='test@mit.edu') + self.date = datetime.datetime(2013,1,22) + self.course_id = 'edx/toy/Fall_2012' + self.enrollment = CourseEnrollment.objects.get_or_create(user = self.user, + course_id = self.course_id, + created = self.date)[0] + + def test_get_course_by_id(self): + courses.get_course_by_id(self.course_id) diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 1acf175bbd..ba7393f939 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -1,24 +1,37 @@ -from unittest import TestCase import logging from mock import MagicMock, patch +import datetime +from django.test import TestCase +from django.http import Http404 from django.conf import settings from django.test.utils import override_settings +from django.contrib.auth.models import User +from student.models import CourseEnrollment import courseware.views as views +from xmodule.modulestore.django import modulestore + class Stub(): pass class ViewsTestCase(TestCase): def setUp(self): - pass - + self.user = User.objects.create(username='dummy', password='123456', + email='test@mit.edu') + self.date = datetime.datetime(2013,1,22) + self.course_id = 'edx/toy/Fall_2012' + self.enrollment = CourseEnrollment.objects.get_or_create(user = self.user, + course_id = self.course_id, + created = self.date)[0] def test_user_groups(self): + # depreciated function? mock_user = MagicMock() mock_user.is_authenticated.return_value = False self.assertEquals(views.user_groups(mock_user),[]) + @override_settings(DEBUG = True) def test_user_groups_debug(self): @@ -33,5 +46,29 @@ class ViewsTestCase(TestCase): mock_xmodule = MagicMock() mock_xmodule.position = -1 mock_xmodule.get_display_items.return_value = ['one','two'] - print views.user_groups(mock_xmodule) - self.assertEquals(views.user_groups(mock_xmodule), 'one') + self.assertEquals(views.get_current_child(mock_xmodule), 'one') + mock_xmodule_2 = MagicMock() + mock_xmodule_2.position = 3 + mock_xmodule_2.get_display_items.return_value = [] + self.assertIsNone(views.get_current_child(mock_xmodule_2)) + + def test_redirect_to_course_position(self): + mock_module = MagicMock() + mock_module.descriptor.id = 'Underwater Basketweaving' + mock_module.position = 3 + mock_module.get_display_items.return_value = [] + self.assertRaises(Http404, views.redirect_to_course_position, + mock_module, True) + + def test_index(self): + print modulestore() + assert False + + def test_registered_for_course(self): + self.assertFalse(views.registered_for_course('Basketweaving', None)) + mock_user = MagicMock() + mock_user.is_authenticated.return_value = False + self.assertFalse(views.registered_for_course('dummy', mock_user)) + mock_course = MagicMock() + mock_course.id = self.course_id + self.assertTrue(views.registered_for_course(mock_course, self.user)) From 7884e2c6906b99c181ca70bd8d8c8e7b9d34c146 Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Tue, 22 Jan 2013 15:08:44 -0500 Subject: [PATCH 05/98] more tests --- lms/djangoapps/courseware/tests/test_courses.py | 2 ++ lms/djangoapps/courseware/tests/test_views.py | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_courses.py b/lms/djangoapps/courseware/tests/test_courses.py index 7e1456efec..7cb2f8b54b 100644 --- a/lms/djangoapps/courseware/tests/test_courses.py +++ b/lms/djangoapps/courseware/tests/test_courses.py @@ -6,6 +6,7 @@ from django.contrib.auth.models import User from student.models import CourseEnrollment import courseware.courses as courses +from xmodule.modulestore.xml import XMLModuleStore class CoursesTestCase(TestCase): def setUp(self): @@ -16,6 +17,7 @@ class CoursesTestCase(TestCase): self.enrollment = CourseEnrollment.objects.get_or_create(user = self.user, course_id = self.course_id, created = self.date)[0] + self.xml_modulestore = XMLModuleStore() def test_get_course_by_id(self): courses.get_course_by_id(self.course_id) diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index ba7393f939..9bca202761 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -61,8 +61,9 @@ class ViewsTestCase(TestCase): mock_module, True) def test_index(self): - print modulestore() - assert False + pass + #print modulestore() + #assert False def test_registered_for_course(self): self.assertFalse(views.registered_for_course('Basketweaving', None)) @@ -72,3 +73,7 @@ class ViewsTestCase(TestCase): mock_course = MagicMock() mock_course.id = self.course_id self.assertTrue(views.registered_for_course(mock_course, self.user)) + + def test_jump_to(self): + self.assertRaises(Http404, views.jump_to, 'foo', 'bar', ()) + From 26ee9a24ef72d035d7554e84b5cc161da8fb3f7d Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Tue, 22 Jan 2013 13:42:01 -0500 Subject: [PATCH 06/98] Add test for TOC rendering --- .../courseware/tests/test_module_render.py | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 lms/djangoapps/courseware/tests/test_module_render.py diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py new file mode 100644 index 0000000000..d5164d4903 --- /dev/null +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -0,0 +1,94 @@ +import unittest +import logging + +from django.conf import settings +from django.test import TestCase +from django.test.client import RequestFactory +from override_settings import override_settings + +import factory +from django.contrib.auth.models import User + +from xmodule.modulestore.django import modulestore, _MODULESTORES +from courseware import module_render + +def xml_store_config(data_dir): + return { + 'default': { + 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', + 'OPTIONS': { + 'data_dir': data_dir, + 'default_class': 'xmodule.hidden_module.HiddenDescriptor', + } + } +} + +TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT +TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) + +class UserFactory(factory.Factory): + first_name = 'Test' + last_name = 'Robot' + is_staff = True + is_active = True + +@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) +class TestTOC(TestCase): + """Check the Table of Contents for a course""" + def setUp(self): + self._MODULESTORES = {} + + # Toy courses should be loaded + self.course_name = 'edX/toy/2012_Fall' + self.toy_course = modulestore().get_course(self.course_name) + + self.portal_user = UserFactory() + + def test_toc_toy_from_chapter(self): + chapter = 'Overview' + chapter_url = '%s/%s/%s' % ('/courses', self.course_name, chapter) + factory = RequestFactory() + request = factory.get(chapter_url) + + expected = ([{'active': True, 'sections': + [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True, + 'format': u'Lecture Sequence', 'due': '', 'active': False}, + {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True, + 'format': '', 'due': '', 'active': False}, + {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True, + 'format': '', 'due': '', 'active': False}, + {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True, + 'format': '', 'due': '', 'active': False}], + 'url_name': 'Overview', 'display_name': u'Overview'}, + {'active': False, 'sections': + [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True, + 'format': '', 'due': '', 'active': False}], + 'url_name': 'secret:magic', 'display_name': 'secret:magic'}]) + + actual = module_render.toc_for_course(self.portal_user, request, self.toy_course, chapter, None) + self.assertEqual(expected, actual) + + def test_toc_toy_from_section(self): + chapter = 'Overview' + chapter_url = '%s/%s/%s' % ('/courses', self.course_name, chapter) + section = 'Welcome' + factory = RequestFactory() + request = factory.get(chapter_url) + + expected = ([{'active': True, 'sections': + [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True, + 'format': u'Lecture Sequence', 'due': '', 'active': False}, + {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True, + 'format': '', 'due': '', 'active': True}, + {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True, + 'format': '', 'due': '', 'active': False}, + {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True, + 'format': '', 'due': '', 'active': False}], + 'url_name': 'Overview', 'display_name': u'Overview'}, + {'active': False, 'sections': + [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True, + 'format': '', 'due': '', 'active': False}], + 'url_name': 'secret:magic', 'display_name': 'secret:magic'}]) + + actual = module_render.toc_for_course(self.portal_user, request, self.toy_course, chapter, section) + self.assertEqual(expected, actual) \ No newline at end of file From 3fd69bcae4f36405e4c5069867ac5c1d62d0b4cb Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Wed, 23 Jan 2013 10:41:12 -0500 Subject: [PATCH 07/98] more tests in test_views.py --- lms/djangoapps/courseware/tests/test_views.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 9bca202761..cf4c792ab7 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -11,7 +11,8 @@ from django.contrib.auth.models import User from student.models import CourseEnrollment import courseware.views as views from xmodule.modulestore.django import modulestore - +from xmodule.modulestore.exceptions import InvalidLocationError,\ + ItemNotFoundError, NoPathToItem class Stub(): pass @@ -25,6 +26,7 @@ class ViewsTestCase(TestCase): self.enrollment = CourseEnrollment.objects.get_or_create(user = self.user, course_id = self.course_id, created = self.date)[0] + self.location = ['tag', 'org', 'course', 'category', 'name'] def test_user_groups(self): # depreciated function? @@ -75,5 +77,8 @@ class ViewsTestCase(TestCase): self.assertTrue(views.registered_for_course(mock_course, self.user)) def test_jump_to(self): - self.assertRaises(Http404, views.jump_to, 'foo', 'bar', ()) + mock_request = MagicMock() + self.assertRaises(Http404, views.jump_to, mock_request, 'bar', ()) + self.assertRaises(ItemNotFoundError, views.jump_to, mock_request, 'dummy', + self.location) From 4d469fd9f7d0148eacb70823b8b44998de173142 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Thu, 24 Jan 2013 09:55:24 -0500 Subject: [PATCH 08/98] Add test for jump_to. --- lms/djangoapps/courseware/tests/test_views.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 lms/djangoapps/courseware/tests/test_views.py diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py new file mode 100644 index 0000000000..f3206004d6 --- /dev/null +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -0,0 +1,48 @@ +import unittest +import logging + +from django.conf import settings +from django.test import TestCase +from django.test.client import RequestFactory +from override_settings import override_settings + +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore, _MODULESTORES +from courseware import views + +def xml_store_config(data_dir): + return { + 'default': { + 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', + 'OPTIONS': { + 'data_dir': data_dir, + 'default_class': 'xmodule.hidden_module.HiddenDescriptor', + } + } +} + +TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT +TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) + +@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) +class TestJumpTo(TestCase): + """Check the jumpto link for a course""" + def setUp(self): + self._MODULESTORES = {} + + # Toy courses should be loaded + self.course_name = 'edX/toy/2012_Fall' + + def test_jumpto_invalid_location(self): + location = Location('i4x', 'edX', 'toy', 'NoSuchPlace', None) + jumpto_url = '%s/%s/jump_to/%s' % ('/courses', self.course_name, location) + expected = 'courses/edX/toy/2012_Fall/courseware/Overview/' + response = self.client.get(jumpto_url) + self.assertEqual(response.status_code, 404) + + def test_jumpto_from_chapter(self): + location = Location('i4x', 'edX', 'toy', 'chapter', 'Overview') + jumpto_url = '%s/%s/jump_to/%s' % ('/courses', self.course_name, location) + expected = 'courses/edX/toy/2012_Fall/courseware/Overview/' + response = self.client.get(jumpto_url) + self.assertRedirects(response, expected, status_code=302, target_status_code=302) From 61e12f57e7f64721c969ed4ad03e1ee03474be63 Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Thu, 24 Jan 2013 09:59:33 -0500 Subject: [PATCH 09/98] experimenting with creating a course --- .../courseware/tests/test_course_creation.py | 61 ++++++++++ .../courseware/tests/test_module_render.py | 13 +-- lms/djangoapps/courseware/tests/test_views.py | 106 ++++++++++++++++-- 3 files changed, 164 insertions(+), 16 deletions(-) create mode 100644 lms/djangoapps/courseware/tests/test_course_creation.py diff --git a/lms/djangoapps/courseware/tests/test_course_creation.py b/lms/djangoapps/courseware/tests/test_course_creation.py new file mode 100644 index 0000000000..af3e3ee0e1 --- /dev/null +++ b/lms/djangoapps/courseware/tests/test_course_creation.py @@ -0,0 +1,61 @@ +import logging +from mock import MagicMock, patch +import factory +import copy +from path import path + +from django.test import TestCase +from django.test.client import Client +from django.core.urlresolvers import reverse +from django.conf import settings +from override_settings import override_settings + +from xmodule.modulestore.xml_importer import import_from_xml +import xmodule.modulestore.django + +TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE) +TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data') + +@override_settings(MODULESTORE=TEST_DATA_MODULESTORE) +class CreateTest(TestCase): + def setUp(self): + xmodule.modulestore.django._MODULESTORES = {} + xmodule.modulestore.django.modulestore().collection.drop() + import_from_xml(modulestore(), 'common/test/data/', [test_course_name]) + + def check_edit_item(self, test_course_name): + import_from_xml(modulestore(), 'common/test/data/', [test_course_name]) + for descriptor in modulestore().get_items(Location(None, None, None, None, None)): + print "Checking ", descriptor.location.url() + print descriptor.__class__, descriptor.location + resp = self.client.get(reverse('edit_item'), {'id': descriptor.location.url()}) + self.assertEqual(resp.status_code, 200) + + def test_edit_item_toy(self): + self.check_edit_item('toy') + +## def setUp(self): +## self.client = Client() +## self.username = 'username' +## self.email = 'test@foo.com' +## self.pw = 'password' +## +## def create_account(self, username, email, pw): +## resp = self.client.post('/create_account', { +## 'username': username, +## 'email': email, +## 'password': pw, +## 'location': 'home', +## 'language': 'Franglish', +## 'name': 'Fred Weasley', +## 'terms_of_service': 'true', +## 'honor_code': 'true', +## }) +## return resp +## +## def registration(self, email): +## '''look up registration object by email''' +## return Registration.objects.get(user__email=email) +## +## def activate_user(self, email): +## activation_key = self.registration(email).activation_key diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index 24f21241f0..ae725638e4 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -1,26 +1,22 @@ -from unittest import TestCase import logging from mock import MagicMock, patch import json import factory +import unittest from django.http import Http404, HttpResponse, HttpRequest from django.conf import settings from django.contrib.auth.models import User from django.test.client import Client from django.conf import settings +from django.test import TestCase +from django.test.client import RequestFactory from courseware.models import StudentModule from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore import Location import courseware.module_render as render - -import unittest - -from django.test import TestCase -from django.test.client import RequestFactory from override_settings import override_settings - from xmodule.modulestore.django import modulestore, _MODULESTORES @@ -202,7 +198,8 @@ class TestTOC(TestCase): # Toy courses should be loaded self.course_name = 'edX/toy/2012_Fall' self.toy_course = modulestore().get_course(self.course_name) - + print type(self.toy_course) + assert False self.portal_user = UserFactory() def test_toc_toy_from_chapter(self): diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index cf4c792ab7..3ff14657b8 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -1,32 +1,84 @@ import logging from mock import MagicMock, patch import datetime +import factory from django.test import TestCase -from django.http import Http404 +from django.http import Http404, HttpResponse from django.conf import settings from django.test.utils import override_settings from django.contrib.auth.models import User +from django.test.client import RequestFactory from student.models import CourseEnrollment -import courseware.views as views from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import InvalidLocationError,\ - ItemNotFoundError, NoPathToItem + ItemNotFoundError, NoPathToItem +import courseware.views as views +from xmodule.modulestore import Location +#import mitx.common.djangoapps.mitxmako as mako class Stub(): pass +def render_to_response(template_name, dictionary, context_instance=None, + namespace='main', **kwargs): + # The original returns HttpResponse + print dir() + print template_name + print dictionary + return HttpResponse('foo') + +class UserFactory(factory.Factory): + first_name = 'Test' + last_name = 'Robot' + is_staff = True + is_active = True + +# This part is required for modulestore() to work properly +def xml_store_config(data_dir): + return { + 'default': { + 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', + 'OPTIONS': { + 'data_dir': data_dir, + 'default_class': 'xmodule.hidden_module.HiddenDescriptor', + } + } +} + +TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT +TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) + +class ModulestoreTest(TestCase): + def setUp(self): + self._MODULESTORES = {} + + # Toy courses should be loaded + self.course_name = 'edX/toy/2012_Fall' + self.toy_course = modulestore().get_course('edX/toy/2012_Fall') + + def test(self): + self.assertEquals(1,2) + class ViewsTestCase(TestCase): def setUp(self): self.user = User.objects.create(username='dummy', password='123456', email='test@mit.edu') self.date = datetime.datetime(2013,1,22) - self.course_id = 'edx/toy/Fall_2012' + self.course_id = 'edx/toy/2012_Fall' self.enrollment = CourseEnrollment.objects.get_or_create(user = self.user, course_id = self.course_id, created = self.date)[0] self.location = ['tag', 'org', 'course', 'category', 'name'] + self._MODULESTORES = {} + # This is a CourseDescriptor object + self.toy_course = modulestore().get_course('edX/toy/2012_Fall') + self.request_factory = RequestFactory() + # Many functions call upon render_to_response + # Problem is that we don't know what templates there are? + views.render_to_response = render_to_response + #m = mako.MakoMiddleware() def test_user_groups(self): # depreciated function? @@ -77,8 +129,46 @@ class ViewsTestCase(TestCase): self.assertTrue(views.registered_for_course(mock_course, self.user)) def test_jump_to(self): - mock_request = MagicMock() - self.assertRaises(Http404, views.jump_to, mock_request, 'bar', ()) + chapter = 'Overview' + chapter_url = '%s/%s/%s' % ('/courses', self.course_id, chapter) + request = self.request_factory.get(chapter_url) + self.assertRaisesRegexp(Http404, 'Invalid location', views.jump_to, + request, 'bar', ()) + self.assertRaisesRegexp(Http404, 'No data*', views.jump_to, request, + 'dummy', self.location) + print type(self.toy_course) + print dir(self.toy_course) + print self.toy_course.location + print self.toy_course.__dict__ + valid = ['i4x', 'edX', 'toy', 'chapter', 'overview'] + L = Location('i4x', 'edX', 'toy', 'chapter', 'Overview', None) - self.assertRaises(ItemNotFoundError, views.jump_to, mock_request, 'dummy', - self.location) + views.jump_to(request, 'dummy', L) + + def test_static_tab(self): + mock_request = MagicMock() + mock_request.user = self.user + # What is tab_slug? + #views.static_tab(mock_request, self.course_id, 'dummy') + + def test_university_profile(self): + chapter = 'Overview' + chapter_url = '%s/%s/%s' % ('/courses', self.course_id, chapter) + request = self.request_factory.get(chapter_url) + request.user = UserFactory() + self.assertRaisesRegexp(Http404, 'University Profile*', + views.university_profile, request, 'Harvard') + # Mocked out function render_to_response + self.assertIsInstance(views.university_profile(request, 'edX'), HttpResponse) + + def test_syllabus(self): + chapter = 'Overview' + chapter_url = '%s/%s/%s' % ('/courses', self.course_id, chapter) + request = self.request_factory.get(chapter_url) + request.user = UserFactory() + # course not found + views.syllabus(request, self.course_id) + + def test_render_notifications(self): + request = self.request_factory.get('foo') + views.render_notifications(request, self.course_id, 'dummy') From 9663973038a13e8cce1fd9eee0bd7ff212ca37bb Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Thu, 24 Jan 2013 14:59:46 -0500 Subject: [PATCH 10/98] more tests for courseware/views --- .../courseware/tests/test_module_render.py | 5 -- lms/djangoapps/courseware/tests/test_views.py | 70 +++++++++++++------ 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index ae725638e4..925601ba37 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -62,9 +62,6 @@ class ModuleRenderTestCase(TestCase): mock_module,'dummy')) def test_get_shared_instance_module(self): -## class MockUserFactory(factory.Factory): -## FACTORY_FOR = MagicMock -## is_authenticated.return_value = mock_user = MagicMock(User) mock_user.is_authenticated.return_value = False self.assertIsNone(render.get_shared_instance_module('dummy', mock_user, 'dummy', @@ -198,8 +195,6 @@ class TestTOC(TestCase): # Toy courses should be loaded self.course_name = 'edX/toy/2012_Fall' self.toy_course = modulestore().get_course(self.course_name) - print type(self.toy_course) - assert False self.portal_user = UserFactory() def test_toc_toy_from_chapter(self): diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 57ef91c79e..fda591b2e6 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -1,9 +1,9 @@ -<<<<<<< HEAD import logging from mock import MagicMock, patch import datetime import factory import unittest +import os from django.test import TestCase from django.http import Http404, HttpResponse @@ -25,13 +25,13 @@ from xmodule.modulestore import Location class Stub(): pass -def render_to_response(template_name, dictionary, context_instance=None, - namespace='main', **kwargs): - # The original returns HttpResponse - print dir() - print template_name - print dictionary - return HttpResponse('foo') +##def render_to_response(template_name, dictionary, context_instance=None, +## namespace='main', **kwargs): +## # The original returns HttpResponse +## print dir() +## print template_name +## print dictionary +## return HttpResponse('foo') class UserFactory(factory.Factory): first_name = 'Test' @@ -56,7 +56,7 @@ TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) -class ModulestoreTest(TestCase): +#class ModulestoreTest(TestCase): @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class TestJumpTo(TestCase): @@ -68,9 +68,6 @@ class TestJumpTo(TestCase): self.course_name = 'edX/toy/2012_Fall' self.toy_course = modulestore().get_course('edX/toy/2012_Fall') - def test(self): - self.assertEquals(1,2) - def test_jumpto_invalid_location(self): location = Location('i4x', 'edX', 'toy', 'NoSuchPlace', None) jumpto_url = '%s/%s/jump_to/%s' % ('/courses', self.course_name, location) @@ -101,11 +98,11 @@ class ViewsTestCase(TestCase): self.request_factory = RequestFactory() # Many functions call upon render_to_response # Problem is that we don't know what templates there are? - views.render_to_response = render_to_response + #views.render_to_response = render_to_response #m = mako.MakoMiddleware() def test_user_groups(self): - # depreciated function? + # depreciated function mock_user = MagicMock() mock_user.is_authenticated.return_value = False self.assertEquals(views.user_groups(mock_user),[]) @@ -160,10 +157,10 @@ class ViewsTestCase(TestCase): request, 'bar', ()) self.assertRaisesRegexp(Http404, 'No data*', views.jump_to, request, 'dummy', self.location) - print type(self.toy_course) - print dir(self.toy_course) - print self.toy_course.location - print self.toy_course.__dict__ +## print type(self.toy_course) +## print dir(self.toy_course) +## print self.toy_course.location +## print self.toy_course.__dict__ valid = ['i4x', 'edX', 'toy', 'chapter', 'overview'] L = Location('i4x', 'edX', 'toy', 'chapter', 'Overview', None) @@ -172,18 +169,32 @@ class ViewsTestCase(TestCase): def test_static_tab(self): mock_request = MagicMock() mock_request.user = self.user - # What is tab_slug? + # What is tab_slug? A string? #views.static_tab(mock_request, self.course_id, 'dummy') + def test_static_university_profile(self): + # TODO + # Can't test unless have a valid template file +## request = self.client.get('university_profile/edX') +## views.static_university_profile(request, 'edX') + pass + + def test_university_profile(self): + chapter = 'Overview' chapter_url = '%s/%s/%s' % ('/courses', self.course_id, chapter) request = self.request_factory.get(chapter_url) request.user = UserFactory() self.assertRaisesRegexp(Http404, 'University Profile*', views.university_profile, request, 'Harvard') - # Mocked out function render_to_response - self.assertIsInstance(views.university_profile(request, 'edX'), HttpResponse) + # Supposed to return a HttpResponse object + # Templates don't exist because not in database + # TODO + # assertTemplateUsed is called on an HttpResponse, but + #request_2 = self.client.get('/university_profile/edx') + #self.assertIsInstance(views.university_profile(request, 'edX'), HttpResponse) + # Can't continue testing unless have valid template file def test_syllabus(self): chapter = 'Overview' @@ -191,8 +202,23 @@ class ViewsTestCase(TestCase): request = self.request_factory.get(chapter_url) request.user = UserFactory() # course not found + # TODO views.syllabus(request, self.course_id) def test_render_notifications(self): request = self.request_factory.get('foo') - views.render_notifications(request, self.course_id, 'dummy') + #views.render_notifications(request, self.course_id, 'dummy') + # TODO + # Needs valid template + + def test_news(self): + #print settings.TEMPLATE_DIRS + #assert False + # I want news to get all the way to render_to_response + # Bug? get_notifications is actually in lms/lib/comment_client/legacy.py + request = self.client.get('/news') + self.user.id = 'foo' + request.user = self.user + course_id = 'edX/toy/2012_Fall' + views.news(request, course_id) + # TODO From f9b0ec37cd760750bdc2522d22184d1b0cb66a39 Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Thu, 24 Jan 2013 16:08:19 -0500 Subject: [PATCH 11/98] more tests in test_views, needs templates to continue --- lms/djangoapps/courseware/tests/test_views.py | 67 +++++++++++-------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index fda591b2e6..52496bcb6b 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -4,6 +4,7 @@ import datetime import factory import unittest import os +from nose.plugins.skip import SkipTest from django.test import TestCase from django.http import Http404, HttpResponse @@ -19,6 +20,12 @@ from xmodule.modulestore.exceptions import InvalidLocationError,\ import courseware.views as views from xmodule.modulestore import Location +def skipped(func): + from nose.plugins.skip import SkipTest + def _(): + raise SkipTest("Test %s is skipped" % func.__name__) + _.__name__ = func.__name__ + return _ #from override_settings import override_settings @@ -39,8 +46,14 @@ class UserFactory(factory.Factory): is_staff = True is_active = True -# This part is required for modulestore() to work properly +def skipped(func): + from nose.plugins.skip import SkipTest + def _(): + raise SkipTest("Test %s is skipped" % func.__name__) + _.__name__ = func.__name__ + return _ +# This part is required for modulestore() to work properly def xml_store_config(data_dir): return { 'default': { @@ -96,10 +109,6 @@ class ViewsTestCase(TestCase): # This is a CourseDescriptor object self.toy_course = modulestore().get_course('edX/toy/2012_Fall') self.request_factory = RequestFactory() - # Many functions call upon render_to_response - # Problem is that we don't know what templates there are? - #views.render_to_response = render_to_response - #m = mako.MakoMiddleware() def test_user_groups(self): # depreciated function @@ -161,49 +170,50 @@ class ViewsTestCase(TestCase): ## print dir(self.toy_course) ## print self.toy_course.location ## print self.toy_course.__dict__ - valid = ['i4x', 'edX', 'toy', 'chapter', 'overview'] - L = Location('i4x', 'edX', 'toy', 'chapter', 'Overview', None) - - views.jump_to(request, 'dummy', L) +## valid = ['i4x', 'edX', 'toy', 'chapter', 'overview'] +## L = Location('i4x', 'edX', 'toy', 'chapter', 'Overview', None) +## +## views.jump_to(request, 'dummy', L) def test_static_tab(self): - mock_request = MagicMock() - mock_request.user = self.user - # What is tab_slug? A string? - #views.static_tab(mock_request, self.course_id, 'dummy') - + request = self.request_factory.get('foo') + request.user = self.user + self.assertRaises(Http404, views.static_tab, request, 'edX/toy/2012_Fall', + 'dummy') + # What are valid tab_slugs? +## request_2 = self.request_factory.get('foo') +## request_2.user = UserFactory() + def test_static_university_profile(self): # TODO # Can't test unless have a valid template file -## request = self.client.get('university_profile/edX') -## views.static_university_profile(request, 'edX') - pass + raise SkipTest + request = self.client.get('university_profile/edX') + self.assertIsInstance(views.static_university_profile(request, 'edX'), HttpResponse) - def test_university_profile(self): - + raise SkipTest chapter = 'Overview' chapter_url = '%s/%s/%s' % ('/courses', self.course_id, chapter) request = self.request_factory.get(chapter_url) request.user = UserFactory() self.assertRaisesRegexp(Http404, 'University Profile*', views.university_profile, request, 'Harvard') - # Supposed to return a HttpResponse object - # Templates don't exist because not in database # TODO - # assertTemplateUsed is called on an HttpResponse, but #request_2 = self.client.get('/university_profile/edx') - #self.assertIsInstance(views.university_profile(request, 'edX'), HttpResponse) + self.assertIsInstance(views.university_profile(request, 'edX'), HttpResponse) # Can't continue testing unless have valid template file + def test_syllabus(self): + raise SkipTest chapter = 'Overview' chapter_url = '%s/%s/%s' % ('/courses', self.course_id, chapter) request = self.request_factory.get(chapter_url) request.user = UserFactory() - # course not found + # Can't find valid template # TODO - views.syllabus(request, self.course_id) + views.syllabus(request, 'edX/toy/2012_Fall') def test_render_notifications(self): request = self.request_factory.get('foo') @@ -212,13 +222,12 @@ class ViewsTestCase(TestCase): # Needs valid template def test_news(self): - #print settings.TEMPLATE_DIRS - #assert False - # I want news to get all the way to render_to_response + raise SkipTest # Bug? get_notifications is actually in lms/lib/comment_client/legacy.py request = self.client.get('/news') self.user.id = 'foo' request.user = self.user course_id = 'edX/toy/2012_Fall' - views.news(request, course_id) + self.assertIsInstance(views.news(request, course_id), HttpResponse) + # TODO From 583505b95ba8a2a803e4130829e9c19c8e586533 Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Fri, 25 Jan 2013 12:19:16 -0500 Subject: [PATCH 12/98] test_courses.py --- .../courseware/tests/test_courses.py | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_courses.py b/lms/djangoapps/courseware/tests/test_courses.py index 7cb2f8b54b..2f4868b27b 100644 --- a/lms/djangoapps/courseware/tests/test_courses.py +++ b/lms/djangoapps/courseware/tests/test_courses.py @@ -3,21 +3,59 @@ import datetime from django.test import TestCase from django.contrib.auth.models import User +from django.conf import settings +from django.test.utils import override_settings from student.models import CourseEnrollment import courseware.courses as courses from xmodule.modulestore.xml import XMLModuleStore +from xmodule.modulestore.django import modulestore +def xml_store_config(data_dir): + return { + 'default': { + 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', + 'OPTIONS': { + 'data_dir': data_dir, + 'default_class': 'xmodule.hidden_module.HiddenDescriptor', + } + } +} + +TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT +TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) + +@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class CoursesTestCase(TestCase): def setUp(self): self.user = User.objects.create(username='dummy', password='123456', email='test@mit.edu') self.date = datetime.datetime(2013,1,22) - self.course_id = 'edx/toy/Fall_2012' + self.course_id = 'edx/toy/2012_Fall' self.enrollment = CourseEnrollment.objects.get_or_create(user = self.user, course_id = self.course_id, created = self.date)[0] - self.xml_modulestore = XMLModuleStore() + self._MODULESTORES = {} + self.toy_course = modulestore().get_course('edX/toy/2012_Fall') def test_get_course_by_id(self): - courses.get_course_by_id(self.course_id) + courses.get_course_by_id("edx/toy/2012_Fall") + +@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) +class CoursesTests(TestCase): + def setUp(self): + self._MODULESTORES = {} + self.course_name = 'edX/toy/2012_Fall' + self.toy_course = modulestore().get_course('edX/toy/2012_Fall') + self.fake_user = User.objects.create(is_superuser=True) + + ''' + no test written for get_request_for_thread + ''' + + def test_get_course_by_id(self): + self.test_course_id = "edX/toy/2012_Fall" + # print modulestore().get_instance(test_course_id, Location('i4x', 'edx', 'toy', 'course', '2012_Fall')) + self.assertEqual(courses.get_course_by_id(self.test_course_id),modulestore().get_instance(self.test_course_id, Location('i4x', 'edX', 'toy', 'course', '2012_Fall'), None)) + + From 7918c35414ca2f6d9cfe7205b684902deaf43ae9 Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Mon, 28 Jan 2013 15:29:55 -0500 Subject: [PATCH 13/98] fixing up tests --- .../courseware/tests/test_courses.py | 30 ++-- .../courseware/tests/test_module_render.py | 130 ++++++++++++------ lms/djangoapps/courseware/tests/test_views.py | 44 +++--- 3 files changed, 126 insertions(+), 78 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_courses.py b/lms/djangoapps/courseware/tests/test_courses.py index 2f4868b27b..91b6af4dfc 100644 --- a/lms/djangoapps/courseware/tests/test_courses.py +++ b/lms/djangoapps/courseware/tests/test_courses.py @@ -10,6 +10,7 @@ from student.models import CourseEnrollment import courseware.courses as courses from xmodule.modulestore.xml import XMLModuleStore from xmodule.modulestore.django import modulestore +from xmodule.modulestore import Location def xml_store_config(data_dir): return { @@ -28,34 +29,35 @@ TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class CoursesTestCase(TestCase): def setUp(self): - self.user = User.objects.create(username='dummy', password='123456', - email='test@mit.edu') - self.date = datetime.datetime(2013,1,22) - self.course_id = 'edx/toy/2012_Fall' - self.enrollment = CourseEnrollment.objects.get_or_create(user = self.user, - course_id = self.course_id, - created = self.date)[0] +## self.user = User.objects.create(username='dummy', password='123456', +## email='test@mit.edu') +## self.date = datetime.datetime(2013,1,22) +## self.enrollment = CourseEnrollment.objects.get_or_create(user = self.user, +## course_id = self.course_id, +## created = self.date)[0] self._MODULESTORES = {} + #self.course_id = 'edx/toy/2012_Fall' self.toy_course = modulestore().get_course('edX/toy/2012_Fall') def test_get_course_by_id(self): - courses.get_course_by_id("edx/toy/2012_Fall") + courses.get_course_by_id("edX/toy/2012_Fall") + @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class CoursesTests(TestCase): + # runs def setUp(self): self._MODULESTORES = {} - self.course_name = 'edX/toy/2012_Fall' + #self.course_id = 'edX/toy/2012_Fall' self.toy_course = modulestore().get_course('edX/toy/2012_Fall') - self.fake_user = User.objects.create(is_superuser=True) +## self.fake_user = User.objects.create(is_superuser=True) ''' no test written for get_request_for_thread ''' def test_get_course_by_id(self): - self.test_course_id = "edX/toy/2012_Fall" + #self.test_course_id = "edX/toy/2012_Fall" + courses.get_course_by_id("edX/toy/2012_Fall") # print modulestore().get_instance(test_course_id, Location('i4x', 'edx', 'toy', 'course', '2012_Fall')) - self.assertEqual(courses.get_course_by_id(self.test_course_id),modulestore().get_instance(self.test_course_id, Location('i4x', 'edX', 'toy', 'course', '2012_Fall'), None)) - - + #self.assertEqual(courses.get_course_by_id(self.test_course_id),modulestore().get_instance(self.test_course_id, Location('i4x', 'edX', 'toy', 'course', '2012_Fall'), None)) diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index 925601ba37..3150450648 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -2,7 +2,9 @@ import logging from mock import MagicMock, patch import json import factory -import unittest +import unittest +from nose.tools import set_trace +from nose.plugins.skip import SkipTest from django.http import Http404, HttpResponse, HttpRequest from django.conf import settings @@ -11,22 +13,50 @@ from django.test.client import Client from django.conf import settings from django.test import TestCase from django.test.client import RequestFactory +from django.core.urlresolvers import reverse -from courseware.models import StudentModule +from courseware.models import StudentModule, StudentModuleCache from xmodule.modulestore.exceptions import ItemNotFoundError +from xmodule.exceptions import NotFoundError from xmodule.modulestore import Location import courseware.module_render as render from override_settings import override_settings from xmodule.modulestore.django import modulestore, _MODULESTORES - +from xmodule.seq_module import SequenceModule +from courseware.tests.tests import PageLoader +from student.models import Registration class Stub: def __init__(self): pass -class ModuleRenderTestCase(TestCase): +def xml_store_config(data_dir): + return { + 'default': { + 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', + 'OPTIONS': { + 'data_dir': data_dir, + 'default_class': 'xmodule.hidden_module.HiddenDescriptor', + } + } +} + +class UserFactory(factory.Factory): + first_name = 'Test' + last_name = 'Robot' + is_staff = True + is_active = True + +TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT +TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) + +@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) +class ModuleRenderTestCase(PageLoader): def setUp(self): - self.location = ['tag', 'org', 'course', 'category', 'name'] + self.location = ['i4x', 'edX', 'toy', 'chapter', 'Overview'] + self._MODULESTORES = {} + self.course_id = 'edX/toy/2012_Fall' + self.toy_course = modulestore().get_course(self.course_id) def test_toc_for_course(self): mock_course = MagicMock() @@ -37,19 +67,26 @@ class ModuleRenderTestCase(TestCase): mock_user.is_authenticated.return_value = False self.assertIsNone(render.toc_for_course(mock_user,'dummy', mock_course, 'dummy', 'dummy')) + # rest of tests are in class TestTOC def test_get_module(self): self.assertIsNone(render.get_module('dummyuser',None,\ 'invalid location',None,None)) - + #done def test__get_module(self): mock_user = MagicMock() - mock_user.is_authenticated.return_value = True - location = ['tag', 'org', 'course', 'category', 'name'] - #render._get_module(mock_user, + mock_user.is_authenticated.return_value = False + location = Location('i4x', 'edX', 'toy', 'chapter', 'Overview') + mock_request = MagicMock() + s = render._get_module(mock_user, mock_request, location, + 'dummy', 'edX/toy/2012_Fall') + self.assertIsInstance(s, SequenceModule) + # Don't know how to generate error in line 260 to test + # Can't tell if sequence module is an error? def test_get_instance_module(self): + # done mock_user = MagicMock() mock_user.is_authenticated.return_value = False self.assertIsNone(render.get_instance_module('dummy', mock_user, 'dummy', @@ -62,16 +99,28 @@ class ModuleRenderTestCase(TestCase): mock_module,'dummy')) def test_get_shared_instance_module(self): + raise SkipTest mock_user = MagicMock(User) mock_user.is_authenticated.return_value = False self.assertIsNone(render.get_shared_instance_module('dummy', mock_user, 'dummy', 'dummy')) mock_user_2 = MagicMock(User) mock_user_2.is_authenticated.return_value = True + mock_module = MagicMock() mock_module.shared_state_key = 'key' - self.assertIsInstance(render.get_shared_instance_module('dummy', mock_user, - mock_module, 'dummy'), StudentModule) + mock_module.location = Location('i4x', 'edX', 'toy', 'chapter', 'Overview') + mock_module.get_shared_state.return_value = '{}' + mock_cache = MagicMock() + mock_cache.lookup.return_value = False + #mock_cache._state = 'dummy' + #set_trace() + print mock_module.get_shared_state() + s = render.get_shared_instance_module(self.course_id, mock_user_2, + mock_module, mock_cache) + self.assertIsInstance(s, StudentModule) + # Problem: can't get code to take branch that creates StudentModule? + # Can't finish testing modx_dispatch def test_xqueue_callback(self): mock_request = MagicMock() @@ -90,9 +139,21 @@ class ModuleRenderTestCase(TestCase): xpackage_2 = {'xqueue_header': json.dumps({'lms_key':'secretkey'}), 'xqueue_body' : 'Message from grader'} mock_request_3.POST.copy.return_value = xpackage_2 -## self.assertRaises(Http404, render.xqueue_callback, mock_request_3, -## 'dummy', 0, 'dummy', 'dummy') - # continue later + # Roadblock: how to get user registered in class? + raise SkipTest + # + # trying alternate way of creating account in hopes of getting valid id + # Problem: Can't activate user + + self.student_name = '12' + self.password = 'foo' + self.email = 'test@mit.edu' + self.create_account(self.student_name, self.email, self.password) + self.activate_user(self.email) + request = RequestFactory().get('stuff') + # This doesn't work to install user + render.xqueue_callback(mock_request_3, self.course_id, + self.student_name, self.password, 'dummy') def test_modx_dispatch(self): self.assertRaises(Http404, render.modx_dispatch, 'dummy', 'dummy', @@ -117,12 +178,23 @@ class ModuleRenderTestCase(TestCase): (inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE/(1000**2))})) mock_request_3 = MagicMock() mock_request_3.POST.copy.return_value = {} + mock_request_3.FILES = False + mock_request_3.user = UserFactory() inputfile_2 = Stub() inputfile_2.size = 1 inputfile_2.name = 'name' self.assertRaises(ItemNotFoundError, render.modx_dispatch, mock_request_3, 'dummy', self.location, 'toy') - # Deadend + self.assertRaises(Http404,render.modx_dispatch, mock_request_3, 'dummy', + self.location, self.course_id) +## student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(self.course_id, +## mock_request_3.user, modulestore().get_instance(self.course_id, self.location)) +## get_shared_instance_module(course_id, request.user, instance, student_module_cache) + # 'goto_position' is the only dispatch that will work + mock_request_3.POST.copy.return_value = {'position':1} + self.assertIsInstance(render.modx_dispatch(mock_request_3, 'goto_position', + self.location, self.course_id), HttpResponse) + # keep going def test_preview_chemcalc(self): mock_request = MagicMock() @@ -159,33 +231,6 @@ class ModuleRenderTestCase(TestCase): self.assertEquals(render.get_score_bucket(11, 10), 'incorrect') self.assertEquals(render.get_score_bucket(-1, 10), 'incorrect') - -class MagicMockFactory(factory.Factory): - FACTORY_FOR = MagicMock - v = factory.LazyAttribute(i for i in [True, False, False]) - - - -def xml_store_config(data_dir): - return { - 'default': { - 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', - 'OPTIONS': { - 'data_dir': data_dir, - 'default_class': 'xmodule.hidden_module.HiddenDescriptor', - } - } -} - -TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT -TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) - -class UserFactory(factory.Factory): - first_name = 'Test' - last_name = 'Robot' - is_staff = True - is_active = True - @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class TestTOC(TestCase): """Check the Table of Contents for a course""" @@ -245,3 +290,4 @@ class TestTOC(TestCase): actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, section) self.assertEqual(expected, actual) + diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 52496bcb6b..c901f87720 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -32,14 +32,6 @@ def skipped(func): class Stub(): pass -##def render_to_response(template_name, dictionary, context_instance=None, -## namespace='main', **kwargs): -## # The original returns HttpResponse -## print dir() -## print template_name -## print dictionary -## return HttpResponse('foo') - class UserFactory(factory.Factory): first_name = 'Test' last_name = 'Robot' @@ -100,7 +92,7 @@ class ViewsTestCase(TestCase): self.user = User.objects.create(username='dummy', password='123456', email='test@mit.edu') self.date = datetime.datetime(2013,1,22) - self.course_id = 'edx/toy/2012_Fall' + self.course_id = 'edX/toy/2012_Fall' self.enrollment = CourseEnrollment.objects.get_or_create(user = self.user, course_id = self.course_id, created = self.date)[0] @@ -109,6 +101,9 @@ class ViewsTestCase(TestCase): # This is a CourseDescriptor object self.toy_course = modulestore().get_course('edX/toy/2012_Fall') self.request_factory = RequestFactory() + chapter = 'Overview' + self.chapter_url = '%s/%s/%s' % ('/courses', self.course_id, chapter) + def test_user_groups(self): # depreciated function @@ -145,9 +140,19 @@ class ViewsTestCase(TestCase): mock_module, True) def test_index(self): - pass - #print modulestore() - #assert False + assert SkipTest + request = self.request_factory.get(self.chapter_url) + request.user = UserFactory() + response = views.index(request, self.course_id) + self.assertIsInstance(response, HttpResponse) + self.assertEqual(response.status_code, 302) + # views.index does not throw 404 if chapter, section, or position are + # not valid, which doesn't match index's comments + views.index(request, self.course_id, chapter='foo', section='bar', + position='baz') + request_2 = self.request_factory.get(self.chapter_url) + request_2.user = self.user + response = views.index(request_2, self.course_id) def test_registered_for_course(self): self.assertFalse(views.registered_for_course('Basketweaving', None)) @@ -159,9 +164,7 @@ class ViewsTestCase(TestCase): self.assertTrue(views.registered_for_course(mock_course, self.user)) def test_jump_to(self): - chapter = 'Overview' - chapter_url = '%s/%s/%s' % ('/courses', self.course_id, chapter) - request = self.request_factory.get(chapter_url) + request = self.request_factory.get(self.chapter_url) self.assertRaisesRegexp(Http404, 'Invalid location', views.jump_to, request, 'bar', ()) self.assertRaisesRegexp(Http404, 'No data*', views.jump_to, request, @@ -186,16 +189,14 @@ class ViewsTestCase(TestCase): def test_static_university_profile(self): # TODO - # Can't test unless have a valid template file + # Can't test unless havehttp://toastdriven.com/blog/2011/apr/10/guide-to-testing-in-django/ a valid template file raise SkipTest request = self.client.get('university_profile/edX') self.assertIsInstance(views.static_university_profile(request, 'edX'), HttpResponse) def test_university_profile(self): raise SkipTest - chapter = 'Overview' - chapter_url = '%s/%s/%s' % ('/courses', self.course_id, chapter) - request = self.request_factory.get(chapter_url) + request = self.request_factory.get(self.chapter_url) request.user = UserFactory() self.assertRaisesRegexp(Http404, 'University Profile*', views.university_profile, request, 'Harvard') @@ -207,15 +208,14 @@ class ViewsTestCase(TestCase): def test_syllabus(self): raise SkipTest - chapter = 'Overview' - chapter_url = '%s/%s/%s' % ('/courses', self.course_id, chapter) - request = self.request_factory.get(chapter_url) + request = self.request_factory.get(self.chapter_url) request.user = UserFactory() # Can't find valid template # TODO views.syllabus(request, 'edX/toy/2012_Fall') def test_render_notifications(self): + raise SkipTest request = self.request_factory.get('foo') #views.render_notifications(request, self.course_id, 'dummy') # TODO From 74839663ff163576bef15ac951c6eba1a6d6dddf Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Tue, 29 Jan 2013 10:12:39 -0500 Subject: [PATCH 14/98] added cms/djangoapps/contentstore/test/tests.py --- .../contentstore/tests/test_views.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 cms/djangoapps/contentstore/tests/test_views.py diff --git a/cms/djangoapps/contentstore/tests/test_views.py b/cms/djangoapps/contentstore/tests/test_views.py new file mode 100644 index 0000000000..f513eef7f9 --- /dev/null +++ b/cms/djangoapps/contentstore/tests/test_views.py @@ -0,0 +1,50 @@ +import logging +from mock import MagicMock, patch +import json +import factory +import unittest +from nose.tools import set_trace +from nose.plugins.skip import SkipTest + +from django.http import Http404, HttpResponse, HttpRequest +from django.conf import settings +from django.contrib.auth.models import User +from django.test.client import Client +from django.conf import settings +from django.test import TestCase +from django.test.client import RequestFactory + +import cms.djangoapps.contentstore.views as views + +def xml_store_config(data_dir): + return { + 'default': { + 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', + 'OPTIONS': { + 'data_dir': data_dir, + 'default_class': 'xmodule.hidden_module.HiddenDescriptor', + } + } +} + +class UserFactory(factory.Factory): + first_name = 'Test' + last_name = 'Robot' + is_staff = True + is_active = True + is_authenticated = True + +TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT +TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) + +@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) +class ViewsTestCase(TestCase): + def setUp(self): + self.location = ['i4x', 'edX', 'toy', 'chapter', 'Overview'] + self._MODULESTORES = {} + self.course_id = 'edX/toy/2012_Fall' + self.toy_course = modulestore().get_course(self.course_id) + + def test_has_access(self): + user = UserFactory() + views.has_access(user, self.location) From 85c412fc2eafefd0240a3c0081532d633b045a37 Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Tue, 29 Jan 2013 11:06:32 -0500 Subject: [PATCH 15/98] changed cms/env/tests, added test_views.py --- cms/djangoapps/contentstore/tests/test_views.py | 10 +++++++--- cms/envs/test.py | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_views.py b/cms/djangoapps/contentstore/tests/test_views.py index f513eef7f9..ae6548bd9d 100644 --- a/cms/djangoapps/contentstore/tests/test_views.py +++ b/cms/djangoapps/contentstore/tests/test_views.py @@ -13,8 +13,11 @@ from django.test.client import Client from django.conf import settings from django.test import TestCase from django.test.client import RequestFactory +from override_settings import override_settings + +from xmodule.modulestore.django import modulestore, _MODULESTORES +import contentstore.views as views -import cms.djangoapps.contentstore.views as views def xml_store_config(data_dir): return { @@ -32,7 +35,6 @@ class UserFactory(factory.Factory): last_name = 'Robot' is_staff = True is_active = True - is_authenticated = True TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) @@ -47,4 +49,6 @@ class ViewsTestCase(TestCase): def test_has_access(self): user = UserFactory() - views.has_access(user, self.location) + user.is_authenticated = True + set_trace() + self.assertTrue(views.has_access(user, self.location)) diff --git a/cms/envs/test.py b/cms/envs/test.py index d55c309827..e31037b04f 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -25,6 +25,9 @@ STATIC_ROOT = TEST_ROOT / "staticfiles" GITHUB_REPO_ROOT = TEST_ROOT / "data" COMMON_TEST_DATA_ROOT = COMMON_ROOT / "test" / "data" +# Makes the tests run much faster... +SOUTH_TESTS_MIGRATE = False # To disable migrations and use syncdb instead + # TODO (cpennington): We need to figure out how envs/test.py can inject things into common.py so that we don't have to repeat this sort of thing STATICFILES_DIRS = [ COMMON_ROOT / "static", From d9fdccb567924f04ec420e95bfe5c37e5bee751a Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Tue, 29 Jan 2013 14:23:55 -0500 Subject: [PATCH 16/98] testing testing tests --- .../contentstore/tests/test_views.py | 45 ++++++++++++++++--- .../courseware/tests/test_module_render.py | 9 ++-- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_views.py b/cms/djangoapps/contentstore/tests/test_views.py index ae6548bd9d..c1fa19624c 100644 --- a/cms/djangoapps/contentstore/tests/test_views.py +++ b/cms/djangoapps/contentstore/tests/test_views.py @@ -6,7 +6,7 @@ import unittest from nose.tools import set_trace from nose.plugins.skip import SkipTest -from django.http import Http404, HttpResponse, HttpRequest +from django.http import Http404, HttpResponse, HttpRequest, HttpResponseRedirect from django.conf import settings from django.contrib.auth.models import User from django.test.client import Client @@ -14,9 +14,12 @@ from django.conf import settings from django.test import TestCase from django.test.client import RequestFactory from override_settings import override_settings +from django.core.exceptions import PermissionDenied from xmodule.modulestore.django import modulestore, _MODULESTORES import contentstore.views as views +import auth.authz as a +from contentstore.tests.factories import XModuleCourseFactory, CourseFactory def xml_store_config(data_dir): @@ -39,16 +42,46 @@ class UserFactory(factory.Factory): TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) -@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class ViewsTestCase(TestCase): def setUp(self): self.location = ['i4x', 'edX', 'toy', 'chapter', 'Overview'] + self.location_2 = ['i4x', 'edX', 'full', 'course', '6.002_Spring_2012'] + # empty Modulestore self._MODULESTORES = {} self.course_id = 'edX/toy/2012_Fall' + self.course_id_2 = 'edx/full/6.002_Spring_2012' self.toy_course = modulestore().get_course(self.course_id) def test_has_access(self): - user = UserFactory() - user.is_authenticated = True - set_trace() - self.assertTrue(views.has_access(user, self.location)) + user = MagicMock(is_staff = True, is_active = True, is_authenticated = True) + m = MagicMock() + m.count.return_value = 1 + user.groups.filter.return_value = m + self.assertTrue(views.has_access(user, self.location_2)) + user.is_authenticated = False + self.assertFalse(views.has_access(user, self.location_2)) + + def test_course_index(self): + # UserFactory doesn't work? + self.user = MagicMock(is_staff = False, is_active = False) + self.user.is_authenticated.return_value = False + request = MagicMock(user = self.user) + # Instead of raising exception when has_access is False, redirects + self.assertIsInstance(views.course_index(request, 'edX', + 'full', '6.002_Spring_2012'), HttpResponseRedirect) + self.user_2 = MagicMock(is_staff = True, is_active = True) + self.user_2.is_authenticated.return_value = True + request_2 = MagicMock(user = self.user_2) + # Bug? Raises error because calls modulestore().get_item(location) + #NotImplementedError: XMLModuleStores can't guarantee that definitions + #are unique. Use get_instance. + print views.course_index(request_2, 'edX', + 'full', '6.002_Spring_2012') + + def test_edit_subsection(self): + self.user = MagicMock(is_staff = False, is_active = False) + self.user.is_authenticated.return_value = False + self.request = MagicMock(user = self.user) + self.assertIsInstance(views.edit_subscription(self.request, self.location_2), + HttpResponseRedirect) + diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index 3150450648..f419e6f582 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -107,8 +107,7 @@ class ModuleRenderTestCase(PageLoader): mock_user_2 = MagicMock(User) mock_user_2.is_authenticated.return_value = True - mock_module = MagicMock() - mock_module.shared_state_key = 'key' + mock_module = MagicMock(shared_state_key = 'key') mock_module.location = Location('i4x', 'edX', 'toy', 'chapter', 'Overview') mock_module.get_shared_state.return_value = '{}' mock_cache = MagicMock() @@ -197,11 +196,9 @@ class ModuleRenderTestCase(PageLoader): # keep going def test_preview_chemcalc(self): - mock_request = MagicMock() - mock_request.method = 'notGET' + mock_request = MagicMock(method = 'notGET') self.assertRaises(Http404, render.preview_chemcalc, mock_request) - mock_request_2 = MagicMock() - mock_request_2.method = 'GET' + mock_request_2 = MagicMock(method = 'GET') mock_request_2.GET.get.return_value = None self.assertEquals(render.preview_chemcalc(mock_request_2).content, json.dumps({'preview':'', From 3858362157d1638f222fc5e8da893bdbe38262d9 Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Tue, 29 Jan 2013 15:53:20 -0500 Subject: [PATCH 17/98] more tests --- cms/djangoapps/contentstore/tests/test_views.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_views.py b/cms/djangoapps/contentstore/tests/test_views.py index c1fa19624c..ed18e1cc8c 100644 --- a/cms/djangoapps/contentstore/tests/test_views.py +++ b/cms/djangoapps/contentstore/tests/test_views.py @@ -50,8 +50,14 @@ class ViewsTestCase(TestCase): self._MODULESTORES = {} self.course_id = 'edX/toy/2012_Fall' self.course_id_2 = 'edx/full/6.002_Spring_2012' - self.toy_course = modulestore().get_course(self.course_id) + #self.toy_course = modulestore().get_course(self.course_id) + # Problem: Classes persist, need to delete stuff from modulestore + self.course = CourseFactory.create() + print dir(self.course) + def tearDown(self): + pass + def test_has_access(self): user = MagicMock(is_staff = True, is_active = True, is_authenticated = True) m = MagicMock() @@ -72,16 +78,15 @@ class ViewsTestCase(TestCase): self.user_2 = MagicMock(is_staff = True, is_active = True) self.user_2.is_authenticated.return_value = True request_2 = MagicMock(user = self.user_2) - # Bug? Raises error because calls modulestore().get_item(location) - #NotImplementedError: XMLModuleStores can't guarantee that definitions - #are unique. Use get_instance. + # Need to use XModuleStoreFactory? print views.course_index(request_2, 'edX', 'full', '6.002_Spring_2012') def test_edit_subsection(self): + # Redirects if request.user doesn't have access to location self.user = MagicMock(is_staff = False, is_active = False) self.user.is_authenticated.return_value = False self.request = MagicMock(user = self.user) - self.assertIsInstance(views.edit_subscription(self.request, self.location_2), + self.assertIsInstance(views.edit_subsection(self.request, self.location_2), HttpResponseRedirect) From cdbe9857d67b3c71255be512e47e440a5a0e10b2 Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Wed, 30 Jan 2013 12:30:48 -0500 Subject: [PATCH 18/98] tests in cms.djangoapps.contentstore/tests/test_views.py --- .../contentstore/tests/test_views.py | 64 ++++++++++++++++--- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_views.py b/cms/djangoapps/contentstore/tests/test_views.py index ed18e1cc8c..87dd7f7ac6 100644 --- a/cms/djangoapps/contentstore/tests/test_views.py +++ b/cms/djangoapps/contentstore/tests/test_views.py @@ -6,7 +6,7 @@ import unittest from nose.tools import set_trace from nose.plugins.skip import SkipTest -from django.http import Http404, HttpResponse, HttpRequest, HttpResponseRedirect +from django.http import Http404, HttpResponse, HttpRequest, HttpResponseRedirect, HttpResponseBadRequest from django.conf import settings from django.contrib.auth.models import User from django.test.client import Client @@ -18,8 +18,8 @@ from django.core.exceptions import PermissionDenied from xmodule.modulestore.django import modulestore, _MODULESTORES import contentstore.views as views -import auth.authz as a -from contentstore.tests.factories import XModuleCourseFactory, CourseFactory +from contentstore.tests.factories import CourseFactory, ItemFactory +from xmodule.modulestore import Location def xml_store_config(data_dir): @@ -46,17 +46,21 @@ class ViewsTestCase(TestCase): def setUp(self): self.location = ['i4x', 'edX', 'toy', 'chapter', 'Overview'] self.location_2 = ['i4x', 'edX', 'full', 'course', '6.002_Spring_2012'] + self.location_3 = ['i4x', 'MITx', '999', 'course', 'Robot_Super_Course'] # empty Modulestore self._MODULESTORES = {} self.course_id = 'edX/toy/2012_Fall' self.course_id_2 = 'edx/full/6.002_Spring_2012' #self.toy_course = modulestore().get_course(self.course_id) # Problem: Classes persist, need to delete stuff from modulestore + # is a CourseDescriptor object? self.course = CourseFactory.create() - print dir(self.course) + # is a sequence descriptor + self.item = ItemFactory.create(template = 'i4x://edx/templates/sequential/Empty') def tearDown(self): - pass + _MODULESTORES = {} + modulestore().collection.drop() def test_has_access(self): user = MagicMock(is_staff = True, is_active = True, is_authenticated = True) @@ -72,15 +76,15 @@ class ViewsTestCase(TestCase): self.user = MagicMock(is_staff = False, is_active = False) self.user.is_authenticated.return_value = False request = MagicMock(user = self.user) - # Instead of raising exception when has_access is False, redirects + # Redirects if request.user doesn't have access to location self.assertIsInstance(views.course_index(request, 'edX', 'full', '6.002_Spring_2012'), HttpResponseRedirect) self.user_2 = MagicMock(is_staff = True, is_active = True) self.user_2.is_authenticated.return_value = True request_2 = MagicMock(user = self.user_2) - # Need to use XModuleStoreFactory? - print views.course_index(request_2, 'edX', - 'full', '6.002_Spring_2012') + # Doesn't work unless we figure out render_to_response +## views.course_index(request_2, 'MITx', +## '999', 'Robot_Super_Course') def test_edit_subsection(self): # Redirects if request.user doesn't have access to location @@ -89,4 +93,44 @@ class ViewsTestCase(TestCase): self.request = MagicMock(user = self.user) self.assertIsInstance(views.edit_subsection(self.request, self.location_2), HttpResponseRedirect) - + # If location isn't for a "sequential", return Bad Request + self.user_2 = MagicMock(is_staff = True, is_active = True) + self.user_2.is_authenticated.return_value = True + self.request_2 = MagicMock(user = self.user_2) + self.assertIsInstance(views.edit_subsection(self.request_2, + self.location_3), HttpResponseBadRequest) + # Need render_to_response + #views.edit_subsection(self.request_2, self.item.location) + + def test_edit_unit(self): + # if user doesn't have access, should redirect + self.user = MagicMock(is_staff = False, is_active = False) + self.user.is_authenticated.return_value = False + self.request = MagicMock(user = self.user) + self.assertIsInstance(views.edit_unit(self.request, self.location_2), + HttpResponseRedirect) + + def test_assignment_type_update(self): + # If user doesn't have access, should redirect + self.user = MagicMock(is_staff = False, is_active = False) + self.user.is_authenticated.return_value = False + self.request = RequestFactory().get('foo') + self.request.user = self.user + self.assertIsInstance(views.assignment_type_update(self.request, + 'MITx', '999', 'course', 'Robot_Super_Course'), + HttpResponseRedirect) + # if user has access, then should return HttpResponse + self.user_2 = MagicMock(is_staff = True, is_active = True) + self.user_2.is_authenticated.return_value = True + self.request.user = self.user_2 + get_response = views.assignment_type_update(self.request,'MITx', '999', + 'course', 'Robot_Super_Course') + self.assertIsInstance(get_response,HttpResponse) + get_response_string = '{"id": 99, "location": ["i4x", "MITx", "999", "course", "Robot_Super_Course", null], "graderType": "Not Graded"}' + self.assertEquals(get_response.content, get_response_string) + self.request_2 = RequestFactory().post('foo') + self.request_2.user = self.user_2 + post_response = views.assignment_type_update(self.request_2,'MITx', '999', + 'course', 'Robot_Super_Course') + self.assertIsInstance(post_response,HttpResponse) + self.assertEquals(post_response.content, 'null') From 40ddaa99766244efb3f46b8b2841aa6f1a076724 Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Wed, 30 Jan 2013 16:03:16 -0500 Subject: [PATCH 19/98] more tests on test_views.py --- .../contentstore/tests/test_views.py | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/cms/djangoapps/contentstore/tests/test_views.py b/cms/djangoapps/contentstore/tests/test_views.py index 87dd7f7ac6..2cb00eac36 100644 --- a/cms/djangoapps/contentstore/tests/test_views.py +++ b/cms/djangoapps/contentstore/tests/test_views.py @@ -5,6 +5,7 @@ import factory import unittest from nose.tools import set_trace from nose.plugins.skip import SkipTest +from collections import defaultdict from django.http import Http404, HttpResponse, HttpRequest, HttpResponseRedirect, HttpResponseBadRequest from django.conf import settings @@ -20,7 +21,11 @@ from xmodule.modulestore.django import modulestore, _MODULESTORES import contentstore.views as views from contentstore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore import Location +from xmodule.x_module import ModuleSystem +from xmodule.error_module import ErrorModule +class Stub(): + pass def xml_store_config(data_dir): return { @@ -61,6 +66,7 @@ class ViewsTestCase(TestCase): def tearDown(self): _MODULESTORES = {} modulestore().collection.drop() + assert False def test_has_access(self): user = MagicMock(is_staff = True, is_active = True, is_authenticated = True) @@ -134,3 +140,78 @@ class ViewsTestCase(TestCase): 'course', 'Robot_Super_Course') self.assertIsInstance(post_response,HttpResponse) self.assertEquals(post_response.content, 'null') + + def test_load_preview_state(self): + # Tests that function creates empty defaultdict when request.session + # is empty + # location cannot be a list or other mutable type + self.request = RequestFactory().get('foo') + self.request.session = {} + instance_state, shared_state = views.load_preview_state(self.request, + 'foo', 'bar') + self.assertIsNone(instance_state) + self.assertIsNone(shared_state) + + def test_save_preview_state(self): + self.request = RequestFactory().get('foo') + self.request.session = {} + loc = Location(self.location_3) + result = {'preview_states': + {('id', loc):{'instance':None, + 'shared':None, + } + } + } + views.save_preview_state(self.request, 'id', loc, None, None) + self.assertEquals(self.request.session, result) + + def test_get_preview_module(self): + raise SkipTest + self.request = RequestFactory().get('foo') + self.request.user = UserFactory() + mock_descriptor = MagicMock() + mock_descriptor.get_sample_state.return_value = [('foo','bar')] + instance, shared = views.get_preview_module(self.request, 'id', mock_descriptor) + self.assertEquals(instance, 'foo') + + def test_preview_module_system(self): + # Returns a ModuleSystem + self.request = RequestFactory().get('foo') + self.request.user = UserFactory() + self.assertIsInstance(views.preview_module_system(self.request, + 'id', self.course), + ModuleSystem) + + def test_load_preview_module(self): + + self.request = RequestFactory().get('foo') + self.request.user = UserFactory() + self.request.session = {} + self.assertIsInstance(views.load_preview_module(self.request, 'id', + self.course, 'instance', 'shared'), + ErrorModule) + system = views.preview_module_system(self.request, 'id', self.course) + # is a functools.partial object? + # Not sure how to get a valid line 507 + print self.course.xmodule_constructor(system) + print self.course.xmodule_constructor(system).func + print self.course.xmodule_constructor(system).keywords + + def test__xmodule_recurse(self): + raise SkipTest +## mock_item = MagicMock() +## mock_item.get_children.return_value = [] + s = Stub() + s.children.append(Stub()) + views._xmodule_recurse(s, lambda x: return) + #views._xmodule_recurse(s, lambda x: x.n += 1) + self.assertEquals(s.n, 1) + self.assertEquals(s.children[0].n, 1) + +class Stub(): + def __init__(self): + self.n = 0 + self.children = [] + def get_children(self): + return self.children + From abc3e5b09d705d728522b6ffb79da85a5489fe1a Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Thu, 31 Jan 2013 14:47:59 -0500 Subject: [PATCH 20/98] more tests in test_views.py --- .../contentstore/tests/test_views.py | 119 ++++++++++++++++-- 1 file changed, 111 insertions(+), 8 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_views.py b/cms/djangoapps/contentstore/tests/test_views.py index 2cb00eac36..9fa16ad4b2 100644 --- a/cms/djangoapps/contentstore/tests/test_views.py +++ b/cms/djangoapps/contentstore/tests/test_views.py @@ -23,6 +23,7 @@ from contentstore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore import Location from xmodule.x_module import ModuleSystem from xmodule.error_module import ErrorModule +from contentstore.utils import get_course_for_item class Stub(): pass @@ -49,6 +50,7 @@ TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) class ViewsTestCase(TestCase): def setUp(self): + #modulestore().collection.drop() self.location = ['i4x', 'edX', 'toy', 'chapter', 'Overview'] self.location_2 = ['i4x', 'edX', 'full', 'course', '6.002_Spring_2012'] self.location_3 = ['i4x', 'MITx', '999', 'course', 'Robot_Super_Course'] @@ -56,8 +58,6 @@ class ViewsTestCase(TestCase): self._MODULESTORES = {} self.course_id = 'edX/toy/2012_Fall' self.course_id_2 = 'edx/full/6.002_Spring_2012' - #self.toy_course = modulestore().get_course(self.course_id) - # Problem: Classes persist, need to delete stuff from modulestore # is a CourseDescriptor object? self.course = CourseFactory.create() # is a sequence descriptor @@ -66,7 +66,7 @@ class ViewsTestCase(TestCase): def tearDown(self): _MODULESTORES = {} modulestore().collection.drop() - assert False + #assert False def test_has_access(self): user = MagicMock(is_staff = True, is_active = True, is_authenticated = True) @@ -196,18 +196,121 @@ class ViewsTestCase(TestCase): print self.course.xmodule_constructor(system) print self.course.xmodule_constructor(system).func print self.course.xmodule_constructor(system).keywords + print dir(self.course.xmodule_constructor(system).func) def test__xmodule_recurse(self): - raise SkipTest -## mock_item = MagicMock() -## mock_item.get_children.return_value = [] + #There shouldn't be a difference, but the code works with defined + # function f but not with lambda functions + mock_item = MagicMock() + mock_item.get_children.return_value = [] s = Stub() s.children.append(Stub()) - views._xmodule_recurse(s, lambda x: return) - #views._xmodule_recurse(s, lambda x: x.n += 1) + views._xmodule_recurse(s, f) self.assertEquals(s.n, 1) self.assertEquals(s.children[0].n, 1) + + def test_get_module_previews(self): + # needs a working render_to_string + raise SkipTest + self.request = RequestFactory().get('foo') + self.request.user = UserFactory() + self.request.session = {} + print views.get_module_previews(self.request, self.course) + + def test_delete_item(self): + # If user doesn't have permission, redirect + self.no_permit_user = MagicMock(is_staff = False, is_active = False) + self.no_permit_user.is_authenticated.return_value = True + self.request = RequestFactory().post('i4x://MITx/999/course/Robot_Super_Course') + self.request.POST = self.request.POST.copy() + self.request.POST.update({'id':'i4x://MITx/999/course/Robot_Super_Course'}) + self.request.user = self.no_permit_user + self.assertRaises(PermissionDenied, views.delete_item, self.request) + # Should return an HttpResponse + self.permit_user =MagicMock(is_staff = True, is_active = True) + self.permit_user.is_authenticated.return_value = True + self.request_2 = RequestFactory().post(self.item.location.url()) + self.request_2.POST = self.request_2.POST.copy() + self.request_2.POST.update({'id':self.item.location.url()}) + self.request_2.user = self.permit_user + response = views.delete_item(self.request_2) + self.assertIsInstance(response, HttpResponse) + self.assertEquals(modulestore().get_items(self.item.location.url()), []) + # Set delete_children to True to delete all children + # Create children + self.item_2 = ItemFactory.create() + child_item = ItemFactory.create() +## print type(self.item_2) +## print self.item_2.__dict__ + # Is there better way of adding children? What format are children in? + self.item_2.definition['children'] = [child_item.location.url()] + self.request_3 = RequestFactory().post(self.item_2.location.url()) + self.request_3.POST = self.request_3.POST.copy() + self.request_3.POST.update({'id':self.item_2.location.url(), + 'delete_children':True, + 'delete_all_versions':True}) + self.request_3.user = self.permit_user + print self.item_2.get_children() + self.assertIsInstance(views.delete_item(self.request_3), HttpResponse) + self.assertEquals(modulestore().get_items(self.item_2.location.url()), []) + # Problem: Function doesn't delete child item? + # child_item can be manually deleted, but can't delete it using function + # Not sure if problem with _xmodule_recurse and lambda functions + #store = views.get_modulestore(child_item.location.url()) + #store.delete_item(child_item.location) + self.assertEquals(modulestore().get_items(child_item.location.url()), []) + # Check delete_item on 'vertical' + self.item_3 = ItemFactory.create(template = 'i4x://edx/templates/vertical/Empty') + self.request_4 = RequestFactory().post(self.item_3.location.url()) + self.request_4.POST = self.request_4.POST.copy() + self.request_4.POST.update({'id':self.item_3.location.url(), + 'delete_children':True, + 'delete_all_versions':True}) + self.request_4.user = self.permit_user + self.assertIsInstance(views.delete_item(self.request_4), HttpResponse) + self.assertEquals(modulestore().get_items(self.item_3.location.url()), []) + + def test_save_item(self): + # Test that user with no permissions gets redirected + self.no_permit_user = MagicMock(is_staff = False, is_active = False) + self.no_permit_user.is_authenticated.return_value = True + self.request = RequestFactory().post(self.item.location.url()) + self.request.POST = self.request.POST.copy() + self.request.POST.update({'id':self.item.location.url()}) + self.request.user = self.no_permit_user + self.assertRaises(PermissionDenied, views.save_item, self.request) + # Test user with permissions but nothing in request.POST + self.item_2 = ItemFactory.create() + self.permit_user =MagicMock(is_staff = True, is_active = True) + self.permit_user.is_authenticated.return_value = True + self.request_2 = RequestFactory().post(self.item_2.location.url()) + self.request_2.POST = self.request.POST.copy() + self.request_2.POST.update({'id':self.item_2.location.url()}) + self.request_2.user = self.permit_user + self.assertIsInstance(views.save_item(self.request_2), HttpResponse) + # Test updating data + self.request_3 = RequestFactory().post(self.item_2.location.url()) + self.request_3.POST = self.request.POST.copy() + self.request_3.POST.update({'id':self.item_2.location.url(), + 'data':{'foo':'bar'}}) + self.request_3.user = self.permit_user + self.assertIsInstance(views.save_item(self.request_3), HttpResponse) + self.assertEquals(modulestore().get_item(self.item_2.location.dict()).definition['data'], + {u'foo': u'bar'}) + # Test metadata, which is a dictionary? + self.request_4 = RequestFactory().post(self.item_2.location.url()) + self.request_4.POST = self.request.POST.copy() + self.request_4.POST.update({'id':self.item_2.location.url(), + 'metadata':{'foo':'bar'}}) + self.request_4.user = self.permit_user + self.assertIsInstance(views.save_item(self.request_4), HttpResponse) + self.assertEquals(modulestore().get_item(self.item_2.location.dict()).metadata['foo'], + 'bar') + +def f(x): + x.n += 1 + class Stub(): def __init__(self): self.n = 0 From 7908d87902d0e89faa674eb9383c6ecb00e10f6f Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Thu, 31 Jan 2013 15:58:14 -0500 Subject: [PATCH 21/98] more tests for views.py --- .../contentstore/tests/test_views.py | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_views.py b/cms/djangoapps/contentstore/tests/test_views.py index 9fa16ad4b2..d5a15a10e3 100644 --- a/cms/djangoapps/contentstore/tests/test_views.py +++ b/cms/djangoapps/contentstore/tests/test_views.py @@ -6,6 +6,7 @@ import unittest from nose.tools import set_trace from nose.plugins.skip import SkipTest from collections import defaultdict +import re from django.http import Http404, HttpResponse, HttpRequest, HttpResponseRedirect, HttpResponseBadRequest from django.conf import settings @@ -24,6 +25,7 @@ from xmodule.modulestore import Location from xmodule.x_module import ModuleSystem from xmodule.error_module import ErrorModule from contentstore.utils import get_course_for_item +from xmodule.templates import update_templates class Stub(): pass @@ -50,12 +52,14 @@ TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) class ViewsTestCase(TestCase): def setUp(self): - #modulestore().collection.drop() + self._MODULESTORES = {} + modulestore().collection.drop() + update_templates() self.location = ['i4x', 'edX', 'toy', 'chapter', 'Overview'] self.location_2 = ['i4x', 'edX', 'full', 'course', '6.002_Spring_2012'] self.location_3 = ['i4x', 'MITx', '999', 'course', 'Robot_Super_Course'] # empty Modulestore - self._MODULESTORES = {} + self.course_id = 'edX/toy/2012_Fall' self.course_id_2 = 'edx/full/6.002_Spring_2012' # is a CourseDescriptor object? @@ -210,6 +214,7 @@ class ViewsTestCase(TestCase): self.assertEquals(s.children[0].n, 1) def test_get_module_previews(self): + raise SkipTest # needs a working render_to_string raise SkipTest self.request = RequestFactory().get('foo') @@ -218,6 +223,7 @@ class ViewsTestCase(TestCase): print views.get_module_previews(self.request, self.course) def test_delete_item(self): + raise SkipTest # If user doesn't have permission, redirect self.no_permit_user = MagicMock(is_staff = False, is_active = False) self.no_permit_user.is_authenticated.return_value = True @@ -297,7 +303,7 @@ class ViewsTestCase(TestCase): self.assertIsInstance(views.save_item(self.request_3), HttpResponse) self.assertEquals(modulestore().get_item(self.item_2.location.dict()).definition['data'], {u'foo': u'bar'}) - # Test metadata, which is a dictionary? + # Test updating metadata self.request_4 = RequestFactory().post(self.item_2.location.url()) self.request_4.POST = self.request.POST.copy() self.request_4.POST.update({'id':self.item_2.location.url(), @@ -306,7 +312,25 @@ class ViewsTestCase(TestCase): self.assertIsInstance(views.save_item(self.request_4), HttpResponse) self.assertEquals(modulestore().get_item(self.item_2.location.dict()).metadata['foo'], 'bar') - + + def test_clone_item(self): + # Test that user with no permissions gets redirected + self.no_permit_user = MagicMock(is_staff = False, is_active = False) + self.no_permit_user.is_authenticated.return_value = True + self.request = RequestFactory().post(self.item.location.url()) + self.request.POST = self.request.POST.copy() + self.request.POST.update({'id':self.item.location.url(), + 'parent_location':self.course.location.url(), + 'template':self.location_3, + 'display_name':'bar'}) + self.request.user = self.no_permit_user + self.assertRaises(PermissionDenied, views.clone_item, self.request) + self.permit_user = MagicMock(is_staff = True, is_active = True) + self.permit_user.is_authenticated.return_value = True + self.request.user = self.permit_user + response = views.clone_item(self.request) + self.assertIsInstance(response, HttpResponse) + self.assertRegexpMatches(response.content, '{"id": "i4x://MITx/999/course/') def f(x): x.n += 1 From c144b2519a6cad068e88df0f4c50e0937d036b59 Mon Sep 17 00:00:00 2001 From: Deena Wang Date: Fri, 1 Feb 2013 15:57:11 -0500 Subject: [PATCH 22/98] more tests in test_views.py --- .../contentstore/tests/factories.py | 53 ++- .../contentstore/tests/test_views.py | 369 +++++++++++++++++- 2 files changed, 401 insertions(+), 21 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/factories.py b/cms/djangoapps/contentstore/tests/factories.py index cb9f451d38..ab83c52438 100644 --- a/cms/djangoapps/contentstore/tests/factories.py +++ b/cms/djangoapps/contentstore/tests/factories.py @@ -1,10 +1,17 @@ from factory import Factory -from xmodule.modulestore import Location -from xmodule.modulestore.django import modulestore +from datetime import datetime +import uuid from time import gmtime from uuid import uuid4 -from xmodule.timeparse import stringify_time +from django.contrib.auth.models import Group + +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore +from xmodule.timeparse import stringify_time +from student.models import (User, UserProfile, Registration, + CourseEnrollmentAllowed) +from django.contrib.auth.models import Group def XMODULE_COURSE_CREATION(class_to_create, **kwargs): return XModuleCourseFactory._create(class_to_create, **kwargs) @@ -114,4 +121,42 @@ class ItemFactory(XModuleItemFactory): parent_location = 'i4x://MITx/999/course/Robot_Super_Course' template = 'i4x://edx/templates/chapter/Empty' - display_name = 'Section One' \ No newline at end of file + display_name = 'Section One' + +class UserProfileFactory(Factory): + FACTORY_FOR = UserProfile + + user = None + name = 'Robot Studio' + courseware = 'course.xml' + +class RegistrationFactory(Factory): + FACTORY_FOR = Registration + + user = None + activation_key = uuid.uuid4().hex + +class UserFactory(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_FOR = Group + + name = 'test_group' + +class CourseEnrollmentAllowedFactory(Factory): + FACTORY_FOR = CourseEnrollmentAllowed + + email = 'test@edx.org' + course_id = 'edX/test/2012_Fall' diff --git a/cms/djangoapps/contentstore/tests/test_views.py b/cms/djangoapps/contentstore/tests/test_views.py index ae6548bd9d..f4f7fcd964 100644 --- a/cms/djangoapps/contentstore/tests/test_views.py +++ b/cms/djangoapps/contentstore/tests/test_views.py @@ -5,19 +5,32 @@ import factory import unittest from nose.tools import set_trace from nose.plugins.skip import SkipTest +from collections import defaultdict +import re -from django.http import Http404, HttpResponse, HttpRequest +from django.http import (Http404, HttpResponse, HttpRequest, + HttpResponseRedirect, HttpResponseBadRequest, + HttpResponseForbidden) from django.conf import settings from django.contrib.auth.models import User -from django.test.client import Client -from django.conf import settings +from django.test.client import Client, RequestFactory from django.test import TestCase -from django.test.client import RequestFactory +from django.core.exceptions import PermissionDenied from override_settings import override_settings from xmodule.modulestore.django import modulestore, _MODULESTORES +from xmodule.modulestore import Location +from xmodule.x_module import ModuleSystem +from xmodule.error_module import ErrorModule +from xmodule.seq_module import SequenceModule +from xmodule.templates import update_templates +from contentstore.utils import get_course_for_item +from contentstore.tests.factories import UserFactory +from contentstore.tests.factories import CourseFactory, ItemFactory import contentstore.views as views +class Stub(): + pass def xml_store_config(data_dir): return { @@ -30,25 +43,347 @@ def xml_store_config(data_dir): } } -class UserFactory(factory.Factory): - first_name = 'Test' - last_name = 'Robot' - is_staff = True - is_active = True - TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) -@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class ViewsTestCase(TestCase): def setUp(self): - self.location = ['i4x', 'edX', 'toy', 'chapter', 'Overview'] + # empty Modulestore self._MODULESTORES = {} + modulestore().collection.drop() + update_templates() + self.location = ['i4x', 'edX', 'toy', 'chapter', 'Overview'] + self.location_2 = ['i4x', 'edX', 'full', 'course', '6.002_Spring_2012'] + self.location_3 = ['i4x', 'MITx', '999', 'course', 'Robot_Super_Course'] self.course_id = 'edX/toy/2012_Fall' - self.toy_course = modulestore().get_course(self.course_id) + self.course_id_2 = 'edx/full/6.002_Spring_2012' + # is a CourseDescriptor object? + self.course = CourseFactory.create() + # is a sequence descriptor + self.item = ItemFactory.create(template = 'i4x://edx/templates/sequential/Empty') + self.no_permit_user = UserFactory() + self.permit_user = UserFactory(is_staff = True, username = 'Wizardly Herbert') + def tearDown(self): + _MODULESTORES = {} + modulestore().collection.drop() + assert False + def test_has_access(self): - user = UserFactory() - user.is_authenticated = True - set_trace() - self.assertTrue(views.has_access(user, self.location)) + self.assertTrue(views.has_access(self.permit_user, self.location_2)) + self.assertFalse(views.has_access(self.no_permit_user, self.location_2)) + # done + + def test_course_index(self): + request = RequestFactory().get('foo') + request.user = self.no_permit_user + # Redirects if request.user doesn't have access to location + self.assertRaises(PermissionDenied, views.course_index, request, 'edX', + 'full', '6.002_Spring_2012') + request_2 = RequestFactory().get('foo') + request.user = self.permit_user + # Doesn't work unless we figure out render_to_response +## views.course_index(request_2, 'MITx', +## '999', 'Robot_Super_Course') + + def test_edit_subsection(self): + # Redirects if request.user doesn't have access to location + self.request = RequestFactory().get('foo') + self.request.user = self.no_permit_user + self.assertRaises(PermissionDenied, views.edit_subsection, self.request, + self.location_2) + # If location isn't for a "sequential", return Bad Request + self.request_2 = RequestFactory().get('foo') + self.request_2.user = self.permit_user + self.assertIsInstance(views.edit_subsection(self.request_2, + self.location_3), HttpResponseBadRequest) + # Need render_to_response + #views.edit_subsection(self.request_2, self.item.location) + + def test_edit_unit(self): + raise SkipTest + # if user doesn't have access, should redirect + self.request = RequestFactory().get('foo') + self.request.user = self.no_permit_user + self.assertRaises(PermissionDenied, views.edit_unit, self.request, + self.location_2) + self.request_2 = RequestFactory().get('foo') + self.request_2.user = self.permit_user + # Problem: no parent locations, so IndexError + #print modulestore().get_parent_locations(self.location_3, None) + views.edit_unit(self.request_2, self.location_3) + # Needs render_to_response + + def test_assignment_type_update(self): + raise SkipTest + # If user doesn't have access, should return HttpResponseForbidden() + self.request = RequestFactory().get('foo') + self.request.user = self.no_permit_user + self.assertIsInstance(views.assignment_type_update(self.request, + 'MITx', '999', 'course', 'Robot_Super_Course'), + HttpResponseForbidden) +## views.assignment_type_update(self.request, 'MITx', '999', 'course', 'Robot_Super_Course') + # if user has access, then should return HttpResponse + self.request.user = self.permit_user + get_response = views.assignment_type_update(self.request,'MITx', '999', + 'course', 'Robot_Super_Course') + self.assertIsInstance(get_response,HttpResponse) + get_response_string = '{"id": 99, "location": ["i4x", "MITx", "999", "course", "Robot_Super_Course", null], "graderType": "Not Graded"}' + self.assertEquals(get_response.content, get_response_string) + self.request_2 = RequestFactory().post('foo') + self.request_2.user = self.permit_user + post_response = views.assignment_type_update(self.request_2,'MITx', '999', + 'course', 'Robot_Super_Course') + self.assertIsInstance(post_response,HttpResponse) + self.assertEquals(post_response.content, 'null') + + def test_load_preview_state(self): + # Tests that function creates empty defaultdict when request.session + # is empty + # location cannot be a list or other mutable type + self.request = RequestFactory().get('foo') + self.request.session = {} + instance_state, shared_state = views.load_preview_state(self.request, + 'foo', 'bar') + self.assertIsNone(instance_state) + self.assertIsNone(shared_state) + # Done + + def test_save_preview_state(self): + self.request = RequestFactory().get('foo') + self.request.session = {} + loc = Location(self.location_3) + result = {'preview_states': + {('id', loc):{'instance':None, + 'shared':None, + } + } + } + views.save_preview_state(self.request, 'id', loc, None, None) + self.assertEquals(self.request.session, result) + # Done + + def test_get_preview_module(self): + self.request = RequestFactory().get('foo') + self.request.user = self.permit_user + self.request.session = {} + module = views.get_preview_module(self.request, 'id', self.course) + self.assertIsInstance(module, SequenceModule) + # Done + + def test_preview_module_system(self): + # Returns a ModuleSystem + self.request = RequestFactory().get('foo') + self.request.user = self.no_permit_user + self.assertIsInstance(views.preview_module_system(self.request, + 'id', self.course), + ModuleSystem) + # done + + def test_load_preview_module(self): + # if error in getting module, return ErrorModule + self.request = RequestFactory().get('foo') + self.request.user = self.no_permit_user + self.request.session = {} + self.assertIsInstance(views.load_preview_module(self.request, 'id', + self.course, 'instance', 'shared'), + ErrorModule) + instance_state, shared_state = self.course.get_sample_state()[0] + module = views.load_preview_module(self.request,'id', self.course, + instance_state, shared_state) + self.assertIsInstance(module, SequenceModule) + # I'd like to test module.get_html, but it relies on render_to_string + # Test static_tab + self.course_2 = CourseFactory(display_name = 'Intro_to_intros', location = Location('i4x', 'MITx', '666', 'static_tab', 'Intro_to_intros')) + module_2 = views.load_preview_module(self.request,'id', self.course_2, + instance_state, shared_state) + self.assertIsInstance(module, SequenceModule) + # needs render_to_string + + def test__xmodule_recurse(self): + #There shouldn't be a difference, but the code works with defined + # function f but not with lambda functions + mock_item = MagicMock() + mock_item.get_children.return_value = [] + s = Stub() + s.children.append(Stub()) + views._xmodule_recurse(s, f) + self.assertEquals(s.n, 1) + self.assertEquals(s.children[0].n, 1) + + def test_get_module_previews(self): + raise SkipTest + # needs a working render_to_string + self.request = RequestFactory().get('foo') + self.request.user = UserFactory() + self.request.session = {} + print views.get_module_previews(self.request, self.course) + + def test_delete_item(self): + raise SkipTest + # If user doesn't have permission, redirect + self.request = RequestFactory().post('i4x://MITx/999/course/Robot_Super_Course') + self.request.POST = self.request.POST.copy() + self.request.POST.update({'id':'i4x://MITx/999/course/Robot_Super_Course'}) + self.request.user = self.no_permit_user + self.assertRaises(PermissionDenied, views.delete_item, self.request) + # Should return an HttpResponse + self.request_2 = RequestFactory().post(self.item.location.url()) + self.request_2.POST = self.request_2.POST.copy() + self.request_2.POST.update({'id':self.item.location.url()}) + self.request_2.user = self.permit_user + response = views.delete_item(self.request_2) + self.assertIsInstance(response, HttpResponse) + self.assertEquals(modulestore().get_items(self.item.location.url()), []) + # Set delete_children to True to delete all children + # Create children + self.item_2 = ItemFactory.create() + child_item = ItemFactory.create() +## print type(self.item_2) +## print self.item_2.__dict__ + # Is there better way of adding children? What format are children in? + self.item_2.definition['children'] = [child_item.location.url()] + self.request_3 = RequestFactory().post(self.item_2.location.url()) + self.request_3.POST = self.request_3.POST.copy() + self.request_3.POST.update({'id':self.item_2.location.url(), + 'delete_children':True, + 'delete_all_versions':True}) + self.request_3.user = self.permit_user + print self.item_2.get_children() + self.assertIsInstance(views.delete_item(self.request_3), HttpResponse) + self.assertEquals(modulestore().get_items(self.item_2.location.url()), []) + # Problem: Function doesn't delete child item? + # child_item can be manually deleted, but can't delete it using function + # Not sure if problem with _xmodule_recurse and lambda functions + #store = views.get_modulestore(child_item.location.url()) + #store.delete_item(child_item.location) + self.assertEquals(modulestore().get_items(child_item.location.url()), []) + + # Check delete_item on 'vertical' + self.item_3 = ItemFactory.create(template = 'i4x://edx/templates/vertical/Empty') + self.request_4 = RequestFactory().post(self.item_3.location.url()) + self.request_4.POST = self.request_4.POST.copy() + self.request_4.POST.update({'id':self.item_3.location.url(), + 'delete_children':True, + 'delete_all_versions':True}) + self.request_4.user = self.permit_user + self.assertIsInstance(views.delete_item(self.request_4), HttpResponse) + self.assertEquals(modulestore().get_items(self.item_3.location.url()), []) + + def test_save_item(self): + # Test that user with no permissions gets redirected + self.request = RequestFactory().post(self.item.location.url()) + self.request.POST = self.request.POST.copy() + self.request.POST.update({'id':self.item.location.url()}) + self.request.user = self.no_permit_user + self.assertRaises(PermissionDenied, views.save_item, self.request) + # Test user with permissions but nothing in request.POST + self.item_2 = ItemFactory.create() + self.request_2 = RequestFactory().post(self.item_2.location.url()) + self.request_2.POST = self.request.POST.copy() + self.request_2.POST.update({'id':self.item_2.location.url()}) + self.request_2.user = self.permit_user + self.assertIsInstance(views.save_item(self.request_2), HttpResponse) + # Test updating data + self.request_3 = RequestFactory().post(self.item_2.location.url()) + self.request_3.POST = self.request.POST.copy() + self.request_3.POST.update({'id':self.item_2.location.url(), + 'data':{'foo':'bar'}}) + self.request_3.user = self.permit_user + self.assertIsInstance(views.save_item(self.request_3), HttpResponse) + self.assertEquals(modulestore().get_item(self.item_2.location.dict()).definition['data'], + {u'foo': u'bar'}) + # Test updating metadata + self.request_4 = RequestFactory().post(self.item_2.location.url()) + self.request_4.POST = self.request.POST.copy() + self.request_4.POST.update({'id':self.item_2.location.url(), + 'metadata':{'foo':'bar'}}) + self.request_4.user = self.permit_user + self.assertIsInstance(views.save_item(self.request_4), HttpResponse) + self.assertEquals(modulestore().get_item(self.item_2.location.dict()).metadata['foo'], + 'bar') + #done + + def test_clone_item(self): + # Test that user with no permissions gets redirected + self.request = RequestFactory().post(self.item.location.url()) + self.request.POST = self.request.POST.copy() + self.request.POST.update({'id':self.item.location.url(), + 'parent_location':self.course.location.url(), + 'template':self.location_3, + 'display_name':'bar'}) + self.request.user = self.no_permit_user + self.assertRaises(PermissionDenied, views.clone_item, self.request) + self.request.user = self.permit_user + response = views.clone_item(self.request) + self.assertIsInstance(response, HttpResponse) + self.assertRegexpMatches(response.content, '{"id": "i4x://MITx/999/course/') + # Done + + def test_upload_asset(self): + # Test get request + self.request = RequestFactory().get('foo') + self.assertIsInstance(views.upload_asset(self.request,'org', 'course', + 'coursename'), HttpResponseBadRequest) + # Test no permissions + self.request_2 = RequestFactory().post('foo') + self.request_2.user = self.no_permit_user + self.assertIsInstance(views.upload_asset(self.request_2, 'MITx', '999', + 'Robot_Super_Course'), HttpResponseForbidden) + # Test if course exists + + self.request_3 = RequestFactory().post('foo') + self.request_3.user = self.permit_user + # Throws error because of improperly formatted log +## self.assertIsInstance(views.upload_asset(self.request_3,'org', 'course', +## 'coursename'),HttpResponseBadRequest) + # Test response with fake file attached + # Not sure how to create fake file for testing purposes because + # can't override request.FILES +## print self.request_3.FILES +## print type(self.request_3.FILES) +## f = open('file.txt') +## self.request_4 = RequestFactory().post('foo', f) +## print self.request_3.FILES +## mock_file = MagicMock(name = 'Secrets', content_type = 'foo') +## mock_file.read.return_value = 'stuff' +## file_dict = {'file':mock_file} +## self.request_3.FILES = file_dict +## print views.upload_asset(self.request_3, 'MITx', '999', +## 'Robot_Super_Course') + + def test_manage_users(self): + self.request = RequestFactory().get('foo') + self.request.user = self.no_permit_user + self.assertRaises(PermissionDenied, views.manage_users, self.request, + self.location_3) + # Needs render_to_response + + def test_create_json_response(self): + ok_response = views.create_json_response() + self.assertIsInstance(ok_response, HttpResponse) + self.assertEquals(ok_response.content, '{"Status": "OK"}') + bad_response = views.create_json_response('Spacetime collapsing') + self.assertIsInstance(bad_response, HttpResponse) + self.assertEquals(bad_response.content, '{"Status": "Failed", "ErrMsg": "Spacetime collapsing"}') + + def test_reorder_static_tabs(self): + self.request = RequestFactory().get('foo') + self.request.POST = {'tabs':[self.location_3]} + self.request.user = self.no_permit_user + self.assertRaises(PermissionDenied, views.reorder_static_tabs, self.request) + self.request.user = self.permit_user + self.assertIsInstance(views.reorder_static_tabs(self.request), + HttpResponseBadRequest) + # to be continued ... + +def f(x): + x.n += 1 + +class Stub(): + def __init__(self): + self.n = 0 + self.children = [] + def get_children(self): + return self.children + From 6b8edde601a5b89753a11879d37c82371cfe87fb Mon Sep 17 00:00:00 2001 From: John Hess Date: Mon, 4 Feb 2013 19:13:01 -0500 Subject: [PATCH 23/98] removed un-enroll all button for fear of disaster --- lms/djangoapps/instructor/views.py | 5 ----- lms/templates/courseware/instructor_dashboard.html | 1 - 2 files changed, 6 deletions(-) diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index ddb31bf871..8a61d78b3b 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -494,11 +494,6 @@ def instructor_dashboard(request, course_id): msg += "Error! Failed to un-enroll student with email '%s'\n" % student msg += str(err) + '\n' - elif action == 'Un-enroll ALL students': - - ret = _do_enroll_students(course, course_id, '', overload=True) - datatable = ret['datatable'] - elif action == 'Enroll multiple students': students = request.POST.get('enroll_multiple','') diff --git a/lms/templates/courseware/instructor_dashboard.html b/lms/templates/courseware/instructor_dashboard.html index 4d46505705..0eb10c9e02 100644 --- a/lms/templates/courseware/instructor_dashboard.html +++ b/lms/templates/courseware/instructor_dashboard.html @@ -237,7 +237,6 @@ function goto( mode)

Student Email: -


%if settings.MITX_FEATURES.get('REMOTE_GRADEBOOK_URL','') and instructor_access: From 8f17e6ae9ed76fa75b3caf867b65ccb632cb6870 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Fri, 15 Feb 2013 01:30:14 -0500 Subject: [PATCH 24/98] First pass at implementing problem history. --- .../0006_create_student_module_history.py | 109 ++++++++++++++++++ .../0007_allow_null_version_in_history.py | 100 ++++++++++++++++ lms/djangoapps/courseware/models.py | 33 +++++- lms/djangoapps/courseware/views.py | 49 +++++++- lms/envs/common.py | 4 + lms/static/sass/shared/_modal.scss | 2 + .../courseware/submission_history.html | 13 +++ lms/templates/courseware/xqa_interface.html | 21 ++++ lms/templates/staff_problem_info.html | 25 +++- lms/urls.py | 9 +- 10 files changed, 360 insertions(+), 5 deletions(-) create mode 100644 lms/djangoapps/courseware/migrations/0006_create_student_module_history.py create mode 100644 lms/djangoapps/courseware/migrations/0007_allow_null_version_in_history.py create mode 100644 lms/templates/courseware/submission_history.html diff --git a/lms/djangoapps/courseware/migrations/0006_create_student_module_history.py b/lms/djangoapps/courseware/migrations/0006_create_student_module_history.py new file mode 100644 index 0000000000..8bf40cfb20 --- /dev/null +++ b/lms/djangoapps/courseware/migrations/0006_create_student_module_history.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'StudentModuleHistory' + db.create_table('courseware_studentmodulehistory', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('student_module', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['courseware.StudentModule'])), + ('version', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)), + ('created', self.gf('django.db.models.fields.DateTimeField')(db_index=True)), + ('state', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('grade', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), + ('max_grade', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), + )) + db.send_create_signal('courseware', ['StudentModuleHistory']) + + + def backwards(self, orm): + # Deleting model 'StudentModuleHistory' + db.delete_table('courseware_studentmodulehistory') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'courseware.offlinecomputedgrade': { + 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'OfflineComputedGrade'}, + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'gradeset': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'courseware.offlinecomputedgradelog': { + 'Meta': {'ordering': "['-created']", 'object_name': 'OfflineComputedGradeLog'}, + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'nstudents': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'seconds': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'courseware.studentmodule': { + 'Meta': {'unique_together': "(('student', 'module_state_key', 'course_id'),)", 'object_name': 'StudentModule'}, + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'done': ('django.db.models.fields.CharField', [], {'default': "'na'", 'max_length': '8', 'db_index': 'True'}), + 'grade': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'max_grade': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'module_state_key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'module_id'", 'db_index': 'True'}), + 'module_type': ('django.db.models.fields.CharField', [], {'default': "'problem'", 'max_length': '32', 'db_index': 'True'}), + 'state': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'courseware.studentmodulehistory': { + 'Meta': {'object_name': 'StudentModuleHistory'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'grade': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'max_grade': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'state': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'student_module': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['courseware.StudentModule']"}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + } + } + + complete_apps = ['courseware'] \ No newline at end of file diff --git a/lms/djangoapps/courseware/migrations/0007_allow_null_version_in_history.py b/lms/djangoapps/courseware/migrations/0007_allow_null_version_in_history.py new file mode 100644 index 0000000000..f6204294c4 --- /dev/null +++ b/lms/djangoapps/courseware/migrations/0007_allow_null_version_in_history.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Changing field 'StudentModuleHistory.version' + db.alter_column('courseware_studentmodulehistory', 'version', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)) + + def backwards(self, orm): + + # User chose to not deal with backwards NULL issues for 'StudentModuleHistory.version' + raise RuntimeError("Cannot reverse this migration. 'StudentModuleHistory.version' and its values cannot be restored.") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'courseware.offlinecomputedgrade': { + 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'OfflineComputedGrade'}, + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'gradeset': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'courseware.offlinecomputedgradelog': { + 'Meta': {'ordering': "['-created']", 'object_name': 'OfflineComputedGradeLog'}, + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'nstudents': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'seconds': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'courseware.studentmodule': { + 'Meta': {'unique_together': "(('student', 'module_state_key', 'course_id'),)", 'object_name': 'StudentModule'}, + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'done': ('django.db.models.fields.CharField', [], {'default': "'na'", 'max_length': '8', 'db_index': 'True'}), + 'grade': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'max_grade': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'module_state_key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'module_id'", 'db_index': 'True'}), + 'module_type': ('django.db.models.fields.CharField', [], {'default': "'problem'", 'max_length': '32', 'db_index': 'True'}), + 'state': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'courseware.studentmodulehistory': { + 'Meta': {'object_name': 'StudentModuleHistory'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'grade': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'max_grade': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'state': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'student_module': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['courseware.StudentModule']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'db_index': 'True'}) + } + } + + complete_apps = ['courseware'] \ No newline at end of file diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py index ac9bde77cd..58ede38d58 100644 --- a/lms/djangoapps/courseware/models.py +++ b/lms/djangoapps/courseware/models.py @@ -12,8 +12,10 @@ file and check it in at the same time as your model changes. To do that, ASSUMPTIONS: modules have unique IDs, even across different module_types """ -from django.db import models from django.contrib.auth.models import User +from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver class StudentModule(models.Model): """ @@ -60,6 +62,35 @@ class StudentModule(models.Model): self.student.username, self.module_state_key, str(self.state)[:20]]) +class StudentModuleHistory(models.Model): + """Keeps a complete history of state changes for a given XModule for a given + Student. Right now, we restrict this to problems so that the table doesn't + explode in size.""" + + class Meta: + get_latest_by = "created" + + student_module = models.ForeignKey(StudentModule, db_index=True) + version = models.CharField(max_length=255, null=True, blank=True, db_index=True) + + # This should be populated from the modified field in StudentModule + created = models.DateTimeField(db_index=True) + state = models.TextField(null=True, blank=True) + grade = models.FloatField(null=True, blank=True) + max_grade = models.FloatField(null=True, blank=True) + + @receiver(post_save, sender=StudentModule) + def save_history(sender, instance, **kwargs): + if instance.module_type == 'problem': + history_entry = StudentModuleHistory(student_module=instance, + version=None, + created=instance.modified, + state=instance.state, + grade=instance.grade, + max_grade=instance.max_grade) + history_entry.save() + + # TODO (cpennington): Remove these once the LMS switches to using XModuleDescriptors diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index fb351e1c01..b6fb31fc25 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -5,10 +5,11 @@ from functools import partial from django.conf import settings from django.core.context_processors import csrf +from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required -from django.http import Http404, HttpResponseRedirect +from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import redirect from mitxmako.shortcuts import render_to_response, render_to_string #from django.views.decorators.csrf import ensure_csrf_cookie @@ -20,7 +21,7 @@ from courseware.access import has_access from courseware.courses import (get_courses, get_course_with_access, get_courses_by_university, sort_by_announcement) import courseware.tabs as tabs -from courseware.models import StudentModule, StudentModuleCache +from courseware.models import StudentModule, StudentModuleCache, StudentModuleHistory from module_render import toc_for_course, get_module, get_instance_module, get_module_for_descriptor from django_comment_client.utils import get_discussion_title @@ -606,3 +607,47 @@ def progress(request, course_id, student_id=None): context.update() return render_to_response('courseware/progress.html', context) + + +@login_required +def submission_history(request, course_id, student_username, location): + """Render an HTML fragment (meant for inclusion elsewhere) that renders a + history of all state changes made by this user for this problem location. + Right now this only works for problems because that's all + StudentModuleHistory records. + """ + # Make sure our has_access check uses the course_id, eh? or is ourself + course = get_course_with_access(request.user, course_id, 'load') + staff_access = has_access(request.user, course, 'staff') + + if (student_username != request.user.username) and (not staff_access): + raise PermissionDenied + + try: + student = User.objects.get(username=student_username) + student_module = StudentModule.objects.get(course_id=course_id, + module_state_key=location, + student_id=student.id) + except User.DoesNotExist: + return HttpResponse("User {0} does not exist.".format(student_username)) + except StudentModule.DoesNotExist: + return HttpResponse("{0} has never accessed problem {1}" + .format(student_username, location)) + + history_entries = StudentModuleHistory.objects \ + .filter(student_module=student_module).order_by('-created') + + # If no history records exist, let's force a save to get history started. + if not history_entries: + student_module.save() + history_entries = StudentModuleHistory.objects \ + .filter(student_module=student_module).order_by('-created') + + context = { + 'history_entries': history_entries, + 'username': student.username, + 'location': location, + 'course_id': course_id + } + + return render_to_response('courseware/submission_history.html', context) diff --git a/lms/envs/common.py b/lms/envs/common.py index eb8c9989f0..371b7d9dcd 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -84,6 +84,10 @@ MITX_FEATURES = { # Flip to True when the YouTube iframe API breaks (again) 'USE_YOUTUBE_OBJECT_API': False, + + # Give a UI to show a student's submission history in a problem by the + # Staff Debug tool. + 'ENABLE_STUDENT_HISTORY_VIEW': True } # Used for A/B testing diff --git a/lms/static/sass/shared/_modal.scss b/lms/static/sass/shared/_modal.scss index 1685487b89..bfa803fee2 100644 --- a/lms/static/sass/shared/_modal.scss +++ b/lms/static/sass/shared/_modal.scss @@ -57,6 +57,8 @@ border: 1px solid rgba(0, 0, 0, 0.9); @include box-shadow(inset 0 1px 0 0 rgba(255, 255, 255, 0.7)); overflow: hidden; + padding-left: 10px; + padding-right: 10px; padding-bottom: 30px; position: relative; z-index: 2; diff --git a/lms/templates/courseware/submission_history.html b/lms/templates/courseware/submission_history.html new file mode 100644 index 0000000000..cd14e9b2ab --- /dev/null +++ b/lms/templates/courseware/submission_history.html @@ -0,0 +1,13 @@ +<% import json %> +

${username} > ${course_id} > ${location}

+ +% for i, entry in enumerate(history_entries): +
+
+#${len(history_entries) - i}: ${entry.created} EST
+Score: ${entry.grade} / ${entry.max_grade} +
+${json.dumps(json.loads(entry.state), indent=2, sort_keys=True) | h}
+
+
+% endfor diff --git a/lms/templates/courseware/xqa_interface.html b/lms/templates/courseware/xqa_interface.html index c314cc7fb0..89ec77b31f 100644 --- a/lms/templates/courseware/xqa_interface.html +++ b/lms/templates/courseware/xqa_interface.html @@ -5,6 +5,27 @@ function setup_debug(element_id, edit_link, staff_context){ $('#' + element_id + '_trig').leanModal(); $('#' + element_id + '_xqa_log').leanModal(); $('#' + element_id + '_xqa_form').submit(function () {sendlog(element_id, edit_link, staff_context);}); + + $("#" + element_id + "_history_trig").leanModal(); + + $('#' + element_id + '_history_form').submit( + function () { + var username = $("#" + element_id + "_history_student_username").val(); + var location = $("#" + element_id + "_history_location").val(); + + // This is a ridiculous way to get the course_id, but I'm not sure + // how to do it sensibly from within the staff debug code. + // staff_problem_info.html is rendered through a wrapper to get_html + // that's injected by the code that adds the histogram -- it's all + // kinda bizarre, and it remains awkward to simply ask "what course + // is this problem being shown in the context of." + var path_parts = window.location.pathname.split('/'); + var course_id = path_parts[2] + "/" + path_parts[3] + "/" + path_parts[4]; + $("#" + element_id + "_history_text").load('/courses/' + course_id + + "/submission_history/" + username + "/" + location); + return false; + } + ); } function sendlog(element_id, edit_link, staff_context){ diff --git a/lms/templates/staff_problem_info.html b/lms/templates/staff_problem_info.html index 9324445dd1..f427709bce 100644 --- a/lms/templates/staff_problem_info.html +++ b/lms/templates/staff_problem_info.html @@ -1,3 +1,4 @@ +## The JS for this is defined in xqa_interface.html ${module_content} %if location.category in ['problem','video','html']: % if edit_link: @@ -13,6 +14,10 @@ ${module_content} % endif +% if settings.MITX_FEATURES.get('ENABLE_STUDENT_HISTORY_VIEW'): + +% endif + -
+ + +
+ + """) + + # Create the problem + problem = LoncapaProblem(xml_str, '1', system=test_system) + + # Render the HTML + rendered_html = etree.XML(problem.get_html()) + + # Expect that the script element has been removed from the rendered HTML + script_element = rendered_html.find('script') + self.assertEqual(None, script_element) + + def test_render_response_xml(self): + # Generate some XML for a string response + kwargs = {'question_text': "Test question", + 'explanation_text': "Test explanation", + 'answer': 'Test answer', + 'hints': [('test prompt', 'test_hint', 'test hint text')]} + xml_str = StringResponseXMLFactory().build_xml(**kwargs) + + # Mock out the template renderer + test_system.render_template = mock.Mock() + test_system.render_template.return_value = "
Input Template Render
" + + # Create the problem and render the HTML + problem = LoncapaProblem(xml_str, '1', system=test_system) + rendered_html = etree.XML(problem.get_html()) + + # Expect problem has been turned into a
+ self.assertEqual(rendered_html.tag, "div") + + # Expect question text is in a

child + question_element = rendered_html.find("p") + self.assertEqual(question_element.text, "Test question") + + # Expect that the response has been turned into a + response_element = rendered_html.find("span") + self.assertEqual(response_element.tag, "span") + + # Expect that the response + # that contains a

for the textline + textline_element = response_element.find("div") + self.assertEqual(textline_element.text, 'Input Template Render') + + # Expect a child
for the solution + # with the rendered template + solution_element = rendered_html.find("div") + self.assertEqual(solution_element.text, 'Input Template Render') + + # Expect that the template renderer was called with the correct + # arguments, once for the textline input and once for + # the solution + expected_textline_context = {'status': 'unsubmitted', + 'value': '', + 'preprocessor': None, + 'msg': '', + 'inline': False, + 'hidden': False, + 'do_math': False, + 'id': '1_2_1', + 'size': None} + + expected_solution_context = {'id': '1_solution_1'} + + expected_calls = [mock.call('textline.html', expected_textline_context), + mock.call('solutionspan.html', expected_solution_context)] + + self.assertEqual(test_system.render_template.call_args_list, + expected_calls) + + + def test_render_response_with_overall_msg(self): + # CustomResponse script that sets an overall_message + script=textwrap.dedent(""" + def check_func(*args): + return {'overall_message': 'Test message', + 'input_list': [ {'ok': True, 'msg': '' } ] } + """) + + # Generate some XML for a CustomResponse + kwargs = {'script':script, 'cfn': 'check_func'} + xml_str = CustomResponseXMLFactory().build_xml(**kwargs) + + # Create the problem and render the html + problem = LoncapaProblem(xml_str, '1', system=test_system) + + # Grade the problem + correctmap = problem.grade_answers({'1_2_1': 'test'}) + + # Render the html + rendered_html = etree.XML(problem.get_html()) + + + # Expect that there is a
within the response
+ # with css class response_message + msg_div_element = rendered_html.find(".//div[@class='response_message']") + self.assertEqual(msg_div_element.tag, "div") + self.assertEqual(msg_div_element.get('class'), "response_message") + + + def test_substitute_python_vars(self): + # Generate some XML with Python variables defined in a script + # and used later as attributes + xml_str = textwrap.dedent(""" + + + + + """) + + # Create the problem and render the HTML + problem = LoncapaProblem(xml_str, '1', system=test_system) + rendered_html = etree.XML(problem.get_html()) + + # Expect that the variable $test has been replaced with its value + span_element = rendered_html.find('span') + self.assertEqual(span_element.get('attr'), "TEST") + + def _create_test_file(self, path, content_str): + test_fp = test_system.filestore.open(path, "w") + test_fp.write(content_str) + test_fp.close() + + self.addCleanup(lambda: os.remove(test_fp.name)) From 952716af13fa3365bf1952c929354787cc315f7d Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 1 Mar 2013 09:02:04 -0500 Subject: [PATCH 39/98] Added clean-up code for message HTML. If overall message is a parseable XHTML tree, it is inserted as a tree rather than text. --- common/lib/capa/capa/responsetypes.py | 47 ++++++++++++------- .../lib/capa/capa/tests/test_html_render.py | 7 ++- .../lib/capa/capa/tests/test_responsetypes.py | 4 +- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 897f922e93..698ec41a0a 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -201,7 +201,17 @@ class LoncapaResponse(object): if response_msg: response_msg_div = etree.SubElement(tree, 'div') response_msg_div.set("class", "response_message") - response_msg_div.text = response_msg + + # If the response message can be represented as an XHTML tree, + # create the tree and append it to the message
+ try: + response_tree = etree.XML(response_msg) + response_msg_div.append(response_tree) + + # Otherwise, assume that the message is text (not XHTML) + # and insert it as the text of the message
+ except: + response_msg_div.text = response_msg return tree @@ -1069,20 +1079,7 @@ def sympy_check2(): if 'ok' in ret: correct = ['correct'] * len(idset) if ret['ok'] else ['incorrect'] * len(idset) msg = ret.get('msg', None) - - if 1: - # try to clean up message html - msg = '' + msg + '' - msg = msg.replace('<', '<') - #msg = msg.replace('<','<') - msg = etree.tostring(fromstring_bs(msg, convertEntities=None), - pretty_print=True) - #msg = etree.tostring(fromstring_bs(msg),pretty_print=True) - msg = msg.replace(' ', '') - #msg = re.sub('(.*)','\\1',msg,flags=re.M|re.DOTALL) # python 2.7 - msg = re.sub('(?ms)(.*)', '\\1', msg) - - messages[0] = msg + messages[0] = self.clean_message_html(msg) # Another kind of dictionary the check function can return has @@ -1101,7 +1098,8 @@ def sympy_check2(): messages = [] for input_dict in input_list: correct.append('correct' if input_dict['ok'] else 'incorrect') - messages.append(input_dict['msg'] if 'msg' in input_dict else None) + msg = self.clean_message_html(input_dict['msg']) if 'msg' in input_dict else None + messages.append(msg) # Otherwise, we do not recognize the dictionary # Raise an exception @@ -1117,13 +1115,30 @@ def sympy_check2(): # build map giving "correct"ness of the answer(s) correct_map = CorrectMap() + + overall_message = self.clean_message_html(overall_message) correct_map.set_overall_message(overall_message) + for k in range(len(idset)): npoints = self.maxpoints[idset[k]] if correct[k] == 'correct' else 0 correct_map.set(idset[k], correct[k], msg=messages[k], npoints=npoints) return correct_map + def clean_message_html(self, msg): + # try to clean up message html + msg = '' + msg + '' + msg = msg.replace('<', '<') + #msg = msg.replace('<','<') + msg = etree.tostring(fromstring_bs(msg, convertEntities=None), + pretty_print=True) + #msg = etree.tostring(fromstring_bs(msg),pretty_print=True) + msg = msg.replace(' ', '') + #msg = re.sub('(.*)','\\1',msg,flags=re.M|re.DOTALL) # python 2.7 + msg = re.sub('(?ms)(.*)', '\\1', msg) + + return msg.strip() + def get_answers(self): ''' Give correct answer expected for this response. diff --git a/common/lib/capa/capa/tests/test_html_render.py b/common/lib/capa/capa/tests/test_html_render.py index aa5312aa14..257e63b611 100644 --- a/common/lib/capa/capa/tests/test_html_render.py +++ b/common/lib/capa/capa/tests/test_html_render.py @@ -135,7 +135,7 @@ class CapaHtmlRenderTest(unittest.TestCase): # CustomResponse script that sets an overall_message script=textwrap.dedent(""" def check_func(*args): - return {'overall_message': 'Test message', + return {'overall_message': '

Test message

', 'input_list': [ {'ok': True, 'msg': '' } ] } """) @@ -159,6 +159,11 @@ class CapaHtmlRenderTest(unittest.TestCase): self.assertEqual(msg_div_element.tag, "div") self.assertEqual(msg_div_element.get('class'), "response_message") + # Expect that the
contains our message (as part of the XML tree) + msg_p_element = msg_div_element.find('p') + self.assertEqual(msg_p_element.tag, "p") + self.assertEqual(msg_p_element.text, "Test message") + def test_substitute_python_vars(self): # Generate some XML with Python variables defined in a script diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 451e6ed14b..538ee6fe50 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -712,7 +712,7 @@ class CustomResponseTest(ResponseTest): msg = correct_map.get_msg('1_2_1') self.assertEqual(correctness, 'correct') - self.assertEqual(msg, "Message text\n") + self.assertEqual(msg, "Message text") # Incorrect answer input_dict = {'1_2_1': '0'} @@ -722,7 +722,7 @@ class CustomResponseTest(ResponseTest): msg = correct_map.get_msg('1_2_1') self.assertEqual(correctness, 'incorrect') - self.assertEqual(msg, "Message text\n") + self.assertEqual(msg, "Message text") def test_function_code_multiple_input_no_msg(self): From 9ac8ef542f4ced63fb7b8cf1769d0f6f8036c486 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 1 Mar 2013 09:35:37 -0500 Subject: [PATCH 40/98] Overall message HTMl now correctly renders when multiple HTML tags are passed as the message (even when there's no root tag) --- common/lib/capa/capa/responsetypes.py | 37 ++++++++++++------- .../lib/capa/capa/tests/test_html_render.py | 12 ++++-- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 698ec41a0a..6b57895013 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -199,19 +199,7 @@ class LoncapaResponse(object): # Add a
for the message at the end of the response if response_msg: - response_msg_div = etree.SubElement(tree, 'div') - response_msg_div.set("class", "response_message") - - # If the response message can be represented as an XHTML tree, - # create the tree and append it to the message
- try: - response_tree = etree.XML(response_msg) - response_msg_div.append(response_tree) - - # Otherwise, assume that the message is text (not XHTML) - # and insert it as the text of the message
- except: - response_msg_div.text = response_msg + tree.append(self._render_response_msg_html(response_msg)) return tree @@ -337,6 +325,29 @@ class LoncapaResponse(object): def __unicode__(self): return u'LoncapaProblem Response %s' % self.xml.tag + def _render_response_msg_html(self, response_msg): + """ Render a
for a message that applies to the entire response. + + *response_msg* is a string, which may contain XHTML markup + + Returns an etree element representing the response message
""" + # First try wrapping the text in a
and parsing + # it as an XHTML tree + try: + response_msg_div = etree.XML('
%s
' % str(response_msg)) + + # If we can't do that, create the
and set the message + # as the text of the
+ except: + response_msg_div = etree.Element('div') + response_msg_div.text = str(response_msg) + + + # Set the css class of the message
+ response_msg_div.set("class", "response_message") + + return response_msg_div + #----------------------------------------------------------------------------- diff --git a/common/lib/capa/capa/tests/test_html_render.py b/common/lib/capa/capa/tests/test_html_render.py index 257e63b611..64f031ea59 100644 --- a/common/lib/capa/capa/tests/test_html_render.py +++ b/common/lib/capa/capa/tests/test_html_render.py @@ -135,7 +135,8 @@ class CapaHtmlRenderTest(unittest.TestCase): # CustomResponse script that sets an overall_message script=textwrap.dedent(""" def check_func(*args): - return {'overall_message': '

Test message

', + msg = '

Test message 1

Test message 2

' + return {'overall_message': msg, 'input_list': [ {'ok': True, 'msg': '' } ] } """) @@ -160,9 +161,12 @@ class CapaHtmlRenderTest(unittest.TestCase): self.assertEqual(msg_div_element.get('class'), "response_message") # Expect that the
contains our message (as part of the XML tree) - msg_p_element = msg_div_element.find('p') - self.assertEqual(msg_p_element.tag, "p") - self.assertEqual(msg_p_element.text, "Test message") + msg_p_elements = msg_div_element.findall('p') + self.assertEqual(msg_p_elements[0].tag, "p") + self.assertEqual(msg_p_elements[0].text, "Test message 1") + + self.assertEqual(msg_p_elements[1].tag, "p") + self.assertEqual(msg_p_elements[1].text, "Test message 2") def test_substitute_python_vars(self): From c3da73ed1eea6be8afb24d5505190b2d4fa65ce9 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 1 Mar 2013 10:57:57 -0500 Subject: [PATCH 41/98] Changed title of custom response doc. Added custom response doc to index --- .../course_data_formats/custom_response.rst | 142 ++++++++++++++++++ doc/public/index.rst | 1 + 2 files changed, 143 insertions(+) create mode 100644 doc/public/course_data_formats/custom_response.rst diff --git a/doc/public/course_data_formats/custom_response.rst b/doc/public/course_data_formats/custom_response.rst new file mode 100644 index 0000000000..1ca8214915 --- /dev/null +++ b/doc/public/course_data_formats/custom_response.rst @@ -0,0 +1,142 @@ +#################################### +CustomResponse XML and Python Script +#################################### + +This document explains how to write a CustomResponse problem. CustomResponse +problems execute Python script to check student answers and provide hints. + +There are two general ways to create a CustomResponse problem: + + +***************** +Answer tag format +***************** +One format puts the Python code in an ```` tag: + +.. code-block:: xml + + +

What is the sum of 2 and 3?

+ + + + + + + # Python script goes here + +
+ + +The Python script interacts with these variables in the global context: + * ``answers``: An ordered list of answers the student provided. + For example, if the student answered ``6``, then ``answers[0]`` would + equal ``6``. + * ``expect``: The value of the ``expect`` attribute of ```` + (if provided). + * ``correct``: An ordered list of strings indicating whether the + student answered the question correctly. Valid values are + ``"correct"``, ``"incorrect"``, and ``"unknown"``. You can set these + values in the script. + * ``messages``: An ordered list of message strings that will be displayed + beneath each input. You can use this to provide hints to users. + For example ``messages[0] = "The capital of California is Sacramento"`` + would display that message beneath the first input of the response. + * ``overall_message``: A string that will be displayed beneath the + entire problem. You can use this to provide a hint that applies + to the entire problem rather than a particular input. + +Example of a checking script: + +.. code-block:: python + + if answers[0] == expect: + correct[0] = 'correct' + overall_message = 'Good job!' + else: + correct[0] = 'incorrect' + messages[0] = 'This answer is incorrect' + overall_message = 'Please try again' + +**Important**: Python is picky about indentation. Within the ```` tag, +you must begin your script with no indentation. + +***************** +Script tag format +***************** +The other way to create a CustomResponse is to put a "checking function" +in a `` + + + +**Important**: Python is picky about indentation. Within the `` From 88a30cb7330a079238b00ea68b6ae619dd3e20da Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Mon, 4 Mar 2013 13:21:19 -0500 Subject: [PATCH 76/98] Fixes in response to Victor's comments. --- lms/djangoapps/courseware/models.py | 4 +++- lms/djangoapps/courseware/views.py | 3 ++- lms/templates/courseware/submission_history.html | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py index 58ede38d58..3ad850f066 100644 --- a/lms/djangoapps/courseware/models.py +++ b/lms/djangoapps/courseware/models.py @@ -67,6 +67,8 @@ class StudentModuleHistory(models.Model): Student. Right now, we restrict this to problems so that the table doesn't explode in size.""" + HISTORY_SAVING_TYPES = {'problem'} + class Meta: get_latest_by = "created" @@ -81,7 +83,7 @@ class StudentModuleHistory(models.Model): @receiver(post_save, sender=StudentModule) def save_history(sender, instance, **kwargs): - if instance.module_type == 'problem': + if instance.module_type in StudentModuleHistory.HISTORY_SAVING_TYPES: history_entry = StudentModuleHistory(student_module=instance, version=None, created=instance.modified, diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index b6fb31fc25..d2da893776 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -616,10 +616,11 @@ def submission_history(request, course_id, student_username, location): Right now this only works for problems because that's all StudentModuleHistory records. """ - # Make sure our has_access check uses the course_id, eh? or is ourself course = get_course_with_access(request.user, course_id, 'load') staff_access = has_access(request.user, course, 'staff') + # Permission Denied if they don't have staff access and are trying to see + # somebody else's submission history. if (student_username != request.user.username) and (not staff_access): raise PermissionDenied diff --git a/lms/templates/courseware/submission_history.html b/lms/templates/courseware/submission_history.html index cd14e9b2ab..3d78cbd4f0 100644 --- a/lms/templates/courseware/submission_history.html +++ b/lms/templates/courseware/submission_history.html @@ -4,7 +4,7 @@ % for i, entry in enumerate(history_entries):
-#${len(history_entries) - i}: ${entry.created} EST
+#${len(history_entries) - i}: ${entry.created} UTC
Score: ${entry.grade} / ${entry.max_grade}
 ${json.dumps(json.loads(entry.state), indent=2, sort_keys=True) | h}

From a81e9a673c45d58088387f932d929d20c710c444 Mon Sep 17 00:00:00 2001
From: Chris Dodge 
Date: Mon, 4 Mar 2013 14:59:45 -0500
Subject: [PATCH 77/98] additional courseware view optimizations. Do a 'depth'
 fetch on the selected section so that it does a more efficient set of queries
 to the database. Also, in the CachingDescriptorSystem, if we have a 'cache
 miss', when we do the actual fetch (which creates a new 'system'), keep that
 fetched data around in our own collection, in case it is queried again

---
 common/lib/xmodule/xmodule/modulestore/mongo.py | 5 ++++-
 lms/djangoapps/courseware/views.py              | 4 ++++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py
index 8068129559..b46c29b2bc 100644
--- a/common/lib/xmodule/xmodule/modulestore/mongo.py
+++ b/common/lib/xmodule/xmodule/modulestore/mongo.py
@@ -64,7 +64,10 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
         location = Location(location)
         json_data = self.module_data.get(location)
         if json_data is None:
-            return self.modulestore.get_item(location)
+            module = self.modulestore.get_item(location)
+            if module is not None:
+                self.module_data.update(module.system.module_data)
+            return module
         else:
             # load the module and apply the inherited metadata
             try:
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index 8b48572818..a9e8298db7 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -306,6 +306,10 @@ def index(request, course_id, chapter=None, section=None,
                 # Specifically asked-for section doesn't exist
                 raise Http404
 
+            # cdodge: this looks silly, but let's refetch the section_descriptor with depth=None
+            # which will prefetch the children more efficiently than doing a recursive load
+            section_descriptor = modulestore().get_instance(course.id, section_descriptor.location, depth=None)
+
             # 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(

From 01a1bf6b6f3a3adb07a249e767c3d8a6b56d03d0 Mon Sep 17 00:00:00 2001
From: David Ormsbee 
Date: Mon, 4 Mar 2013 15:19:24 -0500
Subject: [PATCH 78/98] It's not UTC time, it's whatever the local settings are
 set to (New York in prod)

---
 lms/templates/courseware/submission_history.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lms/templates/courseware/submission_history.html b/lms/templates/courseware/submission_history.html
index 3d78cbd4f0..683c61c5a0 100644
--- a/lms/templates/courseware/submission_history.html
+++ b/lms/templates/courseware/submission_history.html
@@ -4,7 +4,7 @@
 % for i, entry in enumerate(history_entries):
 
-#${len(history_entries) - i}: ${entry.created} UTC
+#${len(history_entries) - i}: ${entry.created} (${TIME_ZONE} time)
Score: ${entry.grade} / ${entry.max_grade}
 ${json.dumps(json.loads(entry.state), indent=2, sort_keys=True) | h}

From 020e1e94fbf7b6977b8d34c872f9a09a635910ae Mon Sep 17 00:00:00 2001
From: Don Mitchell 
Date: Mon, 4 Mar 2013 15:38:47 -0500
Subject: [PATCH 79/98] Move side effecting of definition for grader to the
 course_module and don't make caller responsible.

---
 cms/djangoapps/models/settings/course_grading.py |  2 --
 common/lib/xmodule/xmodule/course_module.py      | 13 ++++---------
 2 files changed, 4 insertions(+), 11 deletions(-)

diff --git a/cms/djangoapps/models/settings/course_grading.py b/cms/djangoapps/models/settings/course_grading.py
index f1f68b5f5b..3d0b8f78af 100644
--- a/cms/djangoapps/models/settings/course_grading.py
+++ b/cms/djangoapps/models/settings/course_grading.py
@@ -118,8 +118,6 @@ class CourseGradingModel(object):
             descriptor.raw_grader[index] = grader
         else:
             descriptor.raw_grader.append(grader)
-        # make definition notice the update
-        descriptor.raw_grader = descriptor.raw_grader
 
         get_modulestore(course_location).update_item(course_location, descriptor.definition['data'])
 
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index 2ed780fcae..86ae673ae8 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -127,6 +127,7 @@ class CourseDescriptor(SequenceDescriptor):
         # NOTE (THK): This is a last-minute addition for Fall 2012 launch to dynamically
         #   disable the syllabus content for courses that do not provide a syllabus
         self.syllabus_present = self.system.resources_fs.exists(path('syllabus'))
+        self._grading_policy = {}
         self.set_grading_policy(self.definition['data'].get('grading_policy', None))
 
         self.test_center_exams = []
@@ -196,11 +197,9 @@ class CourseDescriptor(SequenceDescriptor):
         grading_policy.update(course_policy)
 
         # Here is where we should parse any configurations, so that we can fail early
-        grading_policy['RAW_GRADER'] = grading_policy['GRADER']  # used for cms access
-        grading_policy['GRADER'] = grader_from_conf(grading_policy['GRADER'])
-        self._grading_policy = grading_policy
-
-
+        # Use setters so that side effecting to .definitions works
+        self.raw_grader = grading_policy['GRADER']  # used for cms access
+        self.grade_cutoffs = grading_policy['GRADE_CUTOFFS']
 
     @classmethod
     def read_grading_policy(cls, paths, system):
@@ -317,10 +316,6 @@ class CourseDescriptor(SequenceDescriptor):
         if isinstance(value, time.struct_time):
             self.metadata['enrollment_end'] = stringify_time(value)
 
-    @property
-    def grader(self):
-        return self._grading_policy['GRADER']
-
     @property
     def raw_grader(self):
         return self._grading_policy['RAW_GRADER']

From 0c4a52567e036fd27d1142ae29fc98ab96d5e1a0 Mon Sep 17 00:00:00 2001
From: Don Mitchell 
Date: Mon, 4 Mar 2013 16:13:29 -0500
Subject: [PATCH 80/98] Provide convenience converter of the grading policy but
 don't save it on the object.

---
 common/lib/xmodule/xmodule/course_module.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index 86ae673ae8..72196f92a2 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -316,6 +316,10 @@ class CourseDescriptor(SequenceDescriptor):
         if isinstance(value, time.struct_time):
             self.metadata['enrollment_end'] = stringify_time(value)
 
+    @property
+    def grader(self):
+        return grader_from_conf(self.raw_grader)
+
     @property
     def raw_grader(self):
         return self._grading_policy['RAW_GRADER']

From b0106a41c796310294cfac103a590b6399dd71d9 Mon Sep 17 00:00:00 2001
From: cahrens 
Date: Mon, 4 Mar 2013 16:42:04 -0500
Subject: [PATCH 81/98] make branch

---
 .../contentstore/features/section.feature     |  8 +++
 .../contentstore/features/section.py          | 45 ++++++++++++++---
 .../contentstore/features/subsection.feature  |  8 +++
 .../contentstore/features/subsection.py       | 49 ++++++++++++++++---
 4 files changed, 96 insertions(+), 14 deletions(-)

diff --git a/cms/djangoapps/contentstore/features/section.feature b/cms/djangoapps/contentstore/features/section.feature
index ad00ba2911..75e7a4af10 100644
--- a/cms/djangoapps/contentstore/features/section.feature
+++ b/cms/djangoapps/contentstore/features/section.feature
@@ -11,6 +11,14 @@ Feature: Create Section
     And I see a release date for my section
     And I see a link to create a new subsection
 
+  Scenario: Add a new section (with a quote in the name) to a course (bug #216)
+    Given I have opened a new course in Studio
+    When I click the New Section link
+    And I enter a section name with a quote and click save
+    Then I see my section name with a quote on the Courseware page
+    And I click to edit the section name
+    Then I see the complete section name with a quote in the editor
+
   Scenario: Edit section release date
     Given I have opened a new course in Studio
     And I have added a new section
diff --git a/cms/djangoapps/contentstore/features/section.py b/cms/djangoapps/contentstore/features/section.py
index ca67c477fb..cfa4e4bb52 100644
--- a/cms/djangoapps/contentstore/features/section.py
+++ b/cms/djangoapps/contentstore/features/section.py
@@ -1,5 +1,6 @@
 from lettuce import world, step
 from common import *
+from nose.tools import assert_equal
 
 ############### ACTIONS ####################
 
@@ -12,10 +13,12 @@ def i_click_new_section_link(step):
 
 @step('I enter the section name and click save$')
 def i_save_section_name(step):
-    name_css = '.new-section-name'
-    save_css = '.new-section-name-save'
-    css_fill(name_css, 'My Section')
-    css_click(save_css)
+    save_section_name('My Section')
+
+
+@step('I enter a section name with a quote and click save$')
+def i_save_section_name_with_quote(step):
+    save_section_name('Section with "Quote"')
 
 
 @step('I have added a new section$')
@@ -45,8 +48,24 @@ def i_save_a_new_section_release_date(step):
 
 @step('I see my section on the Courseware page$')
 def i_see_my_section_on_the_courseware_page(step):
-    section_css = 'span.section-name-span'
-    assert_css_with_text(section_css, 'My Section')
+    see_my_section_on_the_courseware_page('My Section')
+
+
+@step('I see my section name with a quote on the Courseware page$')
+def i_see_my_section_name_with_quote_on_the_courseware_page(step):
+    see_my_section_on_the_courseware_page('Section with "Quote"')
+
+
+@step('I click to edit the section name$')
+def i_click_to_edit_section_name(step):
+    css_click('span.section-name-span')
+
+
+@step('I see the complete section name with a quote in the editor$')
+def i_see_complete_section_name_with_quote_in_editor(step):
+    css = '.edit-section-name'
+    assert world.browser.is_element_present_by_css(css, 5)
+    assert_equal(world.browser.find_by_css(css).value, 'Section with "Quote"')
 
 
 @step('the section does not exist$')
@@ -88,3 +107,17 @@ def the_section_release_date_is_updated(step):
     css = 'span.published-status'
     status_text = world.browser.find_by_css(css).text
     assert status_text == 'Will Release: 12/25/2013 at 12:00am'
+
+
+############ HELPER METHODS ###################
+
+def save_section_name(name):
+    name_css = '.new-section-name'
+    save_css = '.new-section-name-save'
+    css_fill(name_css, name)
+    css_click(save_css)
+
+
+def see_my_section_on_the_courseware_page(name):
+    section_css = 'span.section-name-span'
+    assert_css_with_text(section_css, name)
\ No newline at end of file
diff --git a/cms/djangoapps/contentstore/features/subsection.feature b/cms/djangoapps/contentstore/features/subsection.feature
index 5acb5bfe44..4b5f5b869d 100644
--- a/cms/djangoapps/contentstore/features/subsection.feature
+++ b/cms/djangoapps/contentstore/features/subsection.feature
@@ -9,6 +9,14 @@ Feature: Create Subsection
     And I enter the subsection name and click save
     Then I see my subsection on the Courseware page
 
+    Scenario: Add a new subsection (with a name containing a quote) to a section (bug #216)
+    Given I have opened a new course section in Studio
+    When I click the New Subsection link
+    And I enter a subsection name with a quote and click save
+    Then I see my subsection name with a quote on the Courseware page
+    And I click to edit the subsection name
+    Then I see the complete subsection name with a quote in the editor
+
     Scenario: Delete a subsection
     Given I have opened a new course section in Studio
     And I have added a new subsection
diff --git a/cms/djangoapps/contentstore/features/subsection.py b/cms/djangoapps/contentstore/features/subsection.py
index e2041b8dbf..88e1424898 100644
--- a/cms/djangoapps/contentstore/features/subsection.py
+++ b/cms/djangoapps/contentstore/features/subsection.py
@@ -1,5 +1,6 @@
 from lettuce import world, step
 from common import *
+from nose.tools import assert_equal
 
 ############### ACTIONS ####################
 
@@ -20,28 +21,60 @@ def i_click_the_new_subsection_link(step):
 
 @step('I enter the subsection name and click save$')
 def i_save_subsection_name(step):
-    name_css = 'input.new-subsection-name-input'
-    save_css = 'input.new-subsection-name-save'
-    css_fill(name_css, 'Subsection One')
-    css_click(save_css)
+    save_subsection_name('Subsection One')
+
+
+@step('I enter a subsection name with a quote and click save$')
+def i_save_subsection_name_with_quote(step):
+    save_subsection_name('Subsection With "Quote"')
+
+
+@step('I click to edit the subsection name$')
+def i_click_to_edit_subsection_name(step):
+    css_click('span.subsection-name-value')
+
+
+@step('I see the complete subsection name with a quote in the editor$')
+def i_see_complete_subsection_name_with_quote_in_editor(step):
+    css = '.subsection-display-name-input'
+    assert world.browser.is_element_present_by_css(css, 5)
+    assert_equal(world.browser.find_by_css(css).value, 'Subsection With "Quote"')
 
 
 @step('I have added a new subsection$')
 def i_have_added_a_new_subsection(step):
     add_subsection()
 
+
 ############ ASSERTIONS ###################
 
 
 @step('I see my subsection on the Courseware page$')
 def i_see_my_subsection_on_the_courseware_page(step):
-    css = 'span.subsection-name'
-    assert world.browser.is_element_present_by_css(css)
-    css = 'span.subsection-name-value'
-    assert_css_with_text(css, 'Subsection One')
+    see_subsection_name('Subsection One')
+
+
+@step('I see my subsection name with a quote on the Courseware page$')
+def i_see_my_subsection_name_with_quote_on_the_courseware_page(step):
+    see_subsection_name('Subsection With "Quote"')
 
 
 @step('the subsection does not exist$')
 def the_subsection_does_not_exist(step):
     css = 'span.subsection-name'
     assert world.browser.is_element_not_present_by_css(css)
+
+
+############ HELPER METHODS ###################
+
+def save_subsection_name(name):
+    name_css = 'input.new-subsection-name-input'
+    save_css = 'input.new-subsection-name-save'
+    css_fill(name_css, name)
+    css_click(save_css)
+
+def see_subsection_name(name):
+    css = 'span.subsection-name'
+    assert world.browser.is_element_present_by_css(css)
+    css = 'span.subsection-name-value'
+    assert_css_with_text(css, name)

From cc3b9557f97fc39bf44dd01371f168e792dc1216 Mon Sep 17 00:00:00 2001
From: Brian Talbot 
Date: Mon, 4 Mar 2013 17:02:59 -0500
Subject: [PATCH 82/98] studio - open ended and annotations: added in component
 button icon assets and styling

---
 cms/static/img/large-annotations-icon.png | Bin 0 -> 275 bytes
 cms/static/img/large-openended-icon.png   | Bin 0 -> 379 bytes
 cms/static/sass/_graphics.scss            |  10 +++++++++-
 3 files changed, 9 insertions(+), 1 deletion(-)
 create mode 100644 cms/static/img/large-annotations-icon.png
 create mode 100644 cms/static/img/large-openended-icon.png

diff --git a/cms/static/img/large-annotations-icon.png b/cms/static/img/large-annotations-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..249193521fe862d0514b212b00634639363b9db2
GIT binary patch
literal 275
zcmeAS@N?(olHy`uVBq!ia0vp^av;pX3?zBp#Z3TGx&b~RuK$SxK;=M%0p(F!fGoa}
zAirP*gZTmX6AIP~yx;Fo&v19k4m+SsyQhm|NX4z*OOAX;6gV6L-Bvq@zN^3EmNeNc
zbjJ2C`67&xD;_N5XBXR)uyb==!Q7bv(f_*sSwvhCvbw&`AuQu6{1-oD!M<{-}kn

literal 0
HcmV?d00001

diff --git a/cms/static/img/large-openended-icon.png b/cms/static/img/large-openended-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d31815413fe0a7809f8d6eb5be505a4b53f858a
GIT binary patch
literal 379
zcmeAS@N?(olHy`uVBq!ia0vp^av;pX3?zBp#Z3TG#sNMduK!5^Kutj9qFbB30a*ei
zL4LvO@AscKm>*D}kRb5hVShct#I0+e0_C22x;TbZ#4Wvav+uA0N83f6zTV!(|9|I~
zNx6tDo4HE-bF8YB(WL1|gBEi#UpleQ$lPs7{Rc)5d9zncZywJzUvT_+$_Z)z>njYt
zm~->Jek5wl+JA;cB&p&vYtOu$Nn6`y^XHsL5os~Z}UJ*Uu6(*r;Y{EBH?dQU#-$!?!=z19*)E?QZYFqJ3aazejnYyAQ
zZ>#vEdu{JnPRa^@c(py=oJ%ZpYSAyw2yvU)*B>+1KlmVb`28jOz&F;SGPZ5@JI`Ke
i{%`n4`nB<{pX`
Date: Mon, 4 Mar 2013 18:19:24 -0500
Subject: [PATCH 83/98] Cast cursor responses as lists.  MySQL returns them as
 tuples.

Removed print statemetns
Moved import call to top of file
---
 lms/djangoapps/dashboard/views.py | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/lms/djangoapps/dashboard/views.py b/lms/djangoapps/dashboard/views.py
index e74d462432..266e769db5 100644
--- a/lms/djangoapps/dashboard/views.py
+++ b/lms/djangoapps/dashboard/views.py
@@ -3,6 +3,7 @@ import json
 from datetime import datetime
 from django.http import Http404
 from mitxmako.shortcuts import render_to_response
+from django.db import connection
 
 from student.models import CourseEnrollment, CourseEnrollmentAllowed
 from django.contrib.auth.models import User
@@ -12,16 +13,18 @@ def dictfetchall(cursor):
     '''Returns a list of all rows from a cursor as a column: result dict.
     Borrowed from Django documentation'''
     desc = cursor.description
-    table=[]
+    table = []
     table.append([col[0] for col in desc])
-    table = table + cursor.fetchall()
-    print "Table: " + str(table)
+    
+    # ensure response from db is a list, not a tuple (which is returned
+    # by MySQL backed django instances)
+    rows_from_cursor=cursor.fetchall()
+    table = table + [list(row) for row in rows_from_cursor]
     return table
 
 def SQL_query_to_list(cursor, query_string):
     cursor.execute(query_string)
     raw_result=dictfetchall(cursor)
-    print raw_result
     return raw_result
 
 def dashboard(request):
@@ -50,7 +53,6 @@ def dashboard(request):
     results["scalars"]["Total Enrollments Across All Courses"]=CourseEnrollment.objects.count()
 
     # establish a direct connection to the database (for executing raw SQL)
-    from django.db import connection
     cursor = connection.cursor()
 
     # define the queries that will generate our user-facing tables

From 6ce3493f00a5c7395923273c50d0094cc2732d4a Mon Sep 17 00:00:00 2001
From: Chris Dodge 
Date: Mon, 4 Mar 2013 19:59:18 -0500
Subject: [PATCH 84/98] add comment

---
 common/lib/xmodule/xmodule/modulestore/mongo.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py
index b46c29b2bc..e2a4524188 100644
--- a/common/lib/xmodule/xmodule/modulestore/mongo.py
+++ b/common/lib/xmodule/xmodule/modulestore/mongo.py
@@ -66,6 +66,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
         if json_data is None:
             module = self.modulestore.get_item(location)
             if module is not None:
+                # update our own cache after going to the DB to get cache miss
                 self.module_data.update(module.system.module_data)
             return module
         else:

From faf5c3f0a2e3927637285abb4e5bc8c9a16365d8 Mon Sep 17 00:00:00 2001
From: Will Daly 
Date: Tue, 5 Mar 2013 09:10:02 -0500
Subject: [PATCH 85/98] Updated Deena's tests after merge with master to ensure
 that none of the tests fail.  Deleted some tests.

---
 .../contentstore/tests/factories.py           | 150 ------
 .../contentstore/tests/test_views.py          | 447 ------------------
 .../courseware/tests/test_course_creation.py  |  61 ---
 .../courseware/tests/test_module_render.py    |  26 +-
 .../courseware/tests/test_progress.py         |   8 -
 lms/djangoapps/courseware/tests/test_views.py |  73 ---
 6 files changed, 1 insertion(+), 764 deletions(-)
 delete mode 100644 cms/djangoapps/contentstore/tests/factories.py
 delete mode 100644 cms/djangoapps/contentstore/tests/test_views.py
 delete mode 100644 lms/djangoapps/courseware/tests/test_course_creation.py

diff --git a/cms/djangoapps/contentstore/tests/factories.py b/cms/djangoapps/contentstore/tests/factories.py
deleted file mode 100644
index cb01cb447e..0000000000
--- a/cms/djangoapps/contentstore/tests/factories.py
+++ /dev/null
@@ -1,150 +0,0 @@
-from factory import Factory
-from datetime import datetime
-from uuid import uuid4
-from student.models import (User, UserProfile, Registration,
-                            CourseEnrollmentAllowed)
-from django.contrib.auth.models import Group
-
-from django.contrib.auth.models import Group
-
-from xmodule.modulestore import Location
-from xmodule.modulestore.django import modulestore
-from xmodule.timeparse import stringify_time
-from student.models import (User, UserProfile, Registration,
-                            CourseEnrollmentAllowed)
-from django.contrib.auth.models import Group
-
-class UserProfileFactory(Factory):
-    FACTORY_FOR = UserProfile
-
-    user = None
-    name = 'Robot Studio'
-    courseware = 'course.xml'
-
-
-class RegistrationFactory(Factory):
-    FACTORY_FOR = Registration
-
-    user = None
-    activation_key = uuid4().hex
-
-
-class UserFactory(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_FOR = Group
-
-    name = 'test_group'
-
-
-class CourseEnrollmentAllowedFactory(Factory):
-    FACTORY_FOR = CourseEnrollmentAllowed
-
-class CourseFactory(XModuleCourseFactory):
-    FACTORY_FOR = Course
-
-    template = 'i4x://edx/templates/course/Empty'
-    org = 'MITx'
-    number = '999'
-    display_name = 'Robot Super Course'
-
-class XModuleItemFactory(Factory):
-    """
-    Factory for XModule items.
-    """
-
-    ABSTRACT_FACTORY = True
-    _creation_function = (XMODULE_ITEM_CREATION,)
-
-    @classmethod
-    def _create(cls, target_class, *args, **kwargs):
-        """
-        kwargs must include parent_location, template. Can contain display_name
-        target_class is ignored
-        """
-
-        DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info']
-        
-        parent_location = Location(kwargs.get('parent_location'))
-        template = Location(kwargs.get('template'))
-        display_name = kwargs.get('display_name')
-
-        store = modulestore('direct')
-
-        # This code was based off that in cms/djangoapps/contentstore/views.py
-        parent = store.get_item(parent_location)
-        dest_location = parent_location._replace(category=template.category, name=uuid4().hex)
-
-        new_item = store.clone_item(template, dest_location)
-
-        # TODO: This needs to be deleted when we have proper storage for static content
-        new_item.metadata['data_dir'] = parent.metadata['data_dir']
-
-        # replace the display name with an optional parameter passed in from the caller
-        if display_name is not None:
-            new_item.metadata['display_name'] = display_name
-
-        store.update_metadata(new_item.location.url(), new_item.own_metadata)
-
-        if new_item.location.category not in DETACHED_CATEGORIES:
-            store.update_children(parent_location, parent.definition.get('children', []) + [new_item.location.url()])
-
-        return new_item
-
-class Item:
-    pass
-
-class ItemFactory(XModuleItemFactory):
-    FACTORY_FOR = Item
-
-    parent_location = 'i4x://MITx/999/course/Robot_Super_Course'
-    template = 'i4x://edx/templates/chapter/Empty'
-    display_name = 'Section One'
-
-class UserProfileFactory(Factory):
-    FACTORY_FOR = UserProfile
-
-    user = None
-    name = 'Robot Studio'
-    courseware = 'course.xml'
-
-class RegistrationFactory(Factory):
-    FACTORY_FOR = Registration
-
-    user = None
-    activation_key = uuid.uuid4().hex
-
-class UserFactory(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_FOR = Group
-
-    name = 'test_group'
-
-class CourseEnrollmentAllowedFactory(Factory):
-    FACTORY_FOR = CourseEnrollmentAllowed
diff --git a/cms/djangoapps/contentstore/tests/test_views.py b/cms/djangoapps/contentstore/tests/test_views.py
deleted file mode 100644
index 85f960e7d5..0000000000
--- a/cms/djangoapps/contentstore/tests/test_views.py
+++ /dev/null
@@ -1,447 +0,0 @@
-import logging
-from mock import MagicMock, patch
-import json
-import factory
-import unittest
-from nose.tools import set_trace
-from nose.plugins.skip import SkipTest
-from collections import defaultdict
-import re
-
-from django.http import (Http404, HttpResponse, HttpRequest,
-                         HttpResponseRedirect, HttpResponseBadRequest,
-                         HttpResponseForbidden)
-from django.conf import settings
-from django.contrib.auth.models import User
-from django.test.client import Client, RequestFactory
-from django.test import TestCase
-from django.core.exceptions import PermissionDenied
-from override_settings import override_settings
-from django.core.exceptions import PermissionDenied
-
-from xmodule.modulestore.django import modulestore, _MODULESTORES
-from xmodule.modulestore import Location
-from xmodule.x_module import ModuleSystem
-from xmodule.error_module import ErrorModule
-from xmodule.seq_module import SequenceModule
-from xmodule.templates import update_templates
-from contentstore.utils import get_course_for_item
-from contentstore.tests.factories import UserFactory
-from contentstore.tests.factories import CourseFactory, ItemFactory
-import contentstore.views as views
-from contentstore.tests.factories import CourseFactory, ItemFactory
-from xmodule.modulestore import Location
-from xmodule.x_module import ModuleSystem
-from xmodule.error_module import ErrorModule
-from contentstore.utils import get_course_for_item
-from xmodule.templates import update_templates
-
-class Stub():
-    pass
-
-def xml_store_config(data_dir):
-    return {
-    'default': {
-        'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore',
-        'OPTIONS': {
-            'data_dir': data_dir,
-            'default_class': 'xmodule.hidden_module.HiddenDescriptor',
-        }
-    }
-}
-
-TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
-TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
-
-class ViewsTestCase(TestCase):
-    def setUp(self):
-        # empty Modulestore
-        self._MODULESTORES = {}
-        modulestore().collection.drop()
-        update_templates()
-        self.location = ['i4x', 'edX', 'toy', 'chapter', 'Overview']
-        self.location_2 = ['i4x', 'edX', 'full', 'course', '6.002_Spring_2012']
-        self.location_3 = ['i4x', 'MITx', '999', 'course', 'Robot_Super_Course']
-        self.course_id = 'edX/toy/2012_Fall'
-        self.course_id_2 = 'edx/full/6.002_Spring_2012'
-        # is a CourseDescriptor object?
-        self.course = CourseFactory.create()
-        # is a sequence descriptor
-        self.item = ItemFactory.create(template = 'i4x://edx/templates/sequential/Empty')
-        self.no_permit_user = UserFactory()
-        self.permit_user = UserFactory(is_staff = True, username = 'Wizardly Herbert')
-
-    def tearDown(self):
-        _MODULESTORES = {}
-        modulestore().collection.drop()
-        
-    def test_has_access(self):
-        self.assertTrue(views.has_access(self.permit_user, self.location_2))
-        self.assertFalse(views.has_access(self.no_permit_user, self.location_2))
-        # done
-
-    def test_course_index(self):
-        request = RequestFactory().get('foo')
-        request.user = self.no_permit_user
-        # Redirects if request.user doesn't have access to location
-        self.assertRaises(PermissionDenied, views.course_index, request, 'edX',
-                          'full', '6.002_Spring_2012')
-        request_2 = RequestFactory().get('foo')
-        request.user = self.permit_user
-        
-    def test_has_access(self):
-        user = MagicMock(is_staff = True, is_active = True, is_authenticated = True)
-        m = MagicMock()
-        m.count.return_value = 1
-        user.groups.filter.return_value = m
-        self.assertTrue(views.has_access(user, self.location_2))
-        user.is_authenticated = False
-        self.assertFalse(views.has_access(user, self.location_2))
-
-    def test_course_index(self):
-        # UserFactory doesn't work?
-        self.user = MagicMock(is_staff = False, is_active = False)
-        self.user.is_authenticated.return_value = False
-        request = MagicMock(user = self.user)
-        # Redirects if request.user doesn't have access to location
-        self.assertIsInstance(views.course_index(request, 'edX',
-                          'full', '6.002_Spring_2012'), HttpResponseRedirect)
-        self.user_2 = MagicMock(is_staff = True, is_active = True)
-        self.user_2.is_authenticated.return_value = True
-        request_2 = MagicMock(user = self.user_2)
-        # Doesn't work unless we figure out render_to_response
-##        views.course_index(request_2, 'MITx',
-##                          '999', 'Robot_Super_Course')
-
-    def test_edit_subsection(self):
-        # Redirects if request.user doesn't have access to location
-        self.request = RequestFactory().get('foo')
-        self.request.user = self.no_permit_user
-        self.assertRaises(PermissionDenied, views.edit_subsection, self.request,
-                          self.location_2)
-        # If location isn't for a "sequential", return Bad Request
-        self.request_2 = RequestFactory().get('foo')
-        self.request_2.user = self.permit_user
-        self.user = MagicMock(is_staff = False, is_active = False)
-        self.user.is_authenticated.return_value = False
-        self.request = MagicMock(user = self.user)
-        self.assertIsInstance(views.edit_subsection(self.request, self.location_2),
-                              HttpResponseRedirect)
-        # If location isn't for a "sequential", return Bad Request
-        self.user_2 = MagicMock(is_staff = True, is_active = True)
-        self.user_2.is_authenticated.return_value = True
-        self.request_2 = MagicMock(user = self.user_2)
-        self.assertIsInstance(views.edit_subsection(self.request_2,
-                                        self.location_3), HttpResponseBadRequest)
-        # Need render_to_response
-        #views.edit_subsection(self.request_2, self.item.location)
-    
-    def test_edit_unit(self):
-        raise SkipTest
-        # if user doesn't have access, should redirect
-        self.request = RequestFactory().get('foo')
-        self.request.user = self.no_permit_user
-        self.assertRaises(PermissionDenied, views.edit_unit, self.request,
-                          self.location_2)
-        self.request_2 = RequestFactory().get('foo')
-        self.request_2.user = self.permit_user
-        # Problem: no parent locations, so IndexError
-        #print modulestore().get_parent_locations(self.location_3, None)
-        views.edit_unit(self.request_2, self.location_3)
-        # Needs render_to_response
-
-    def test_assignment_type_update(self):
-        raise SkipTest
-        # If user doesn't have access, should return HttpResponseForbidden()
-        self.request = RequestFactory().get('foo')
-        self.request.user = self.no_permit_user
-        self.assertIsInstance(views.assignment_type_update(self.request,
-                                    'MITx', '999', 'course', 'Robot_Super_Course'),
-                              HttpResponseForbidden)
-##        views.assignment_type_update(self.request, 'MITx', '999', 'course', 'Robot_Super_Course')
-        # if user has access, then should return HttpResponse
-        self.request.user = self.permit_user
-        # if user doesn't have access, should redirect
-        self.user = MagicMock(is_staff = False, is_active = False)
-        self.user.is_authenticated.return_value = False
-        self.request = MagicMock(user = self.user)
-        self.assertIsInstance(views.edit_unit(self.request, self.location_2),
-                              HttpResponseRedirect)
-
-    def test_assignment_type_update(self):
-        # If user doesn't have access, should redirect
-        self.user = MagicMock(is_staff = False, is_active = False)
-        self.user.is_authenticated.return_value = False
-        self.request = RequestFactory().get('foo')
-        self.request.user = self.user
-        self.assertIsInstance(views.assignment_type_update(self.request,
-                                    'MITx', '999', 'course', 'Robot_Super_Course'),
-                              HttpResponseRedirect)
-        # if user has access, then should return HttpResponse
-        self.user_2 = MagicMock(is_staff = True, is_active = True)
-        self.user_2.is_authenticated.return_value = True
-        self.request.user = self.user_2
-        get_response = views.assignment_type_update(self.request,'MITx', '999',
-                                                    'course', 'Robot_Super_Course')
-        self.assertIsInstance(get_response,HttpResponse)
-        get_response_string = '{"id": 99, "location": ["i4x", "MITx", "999", "course", "Robot_Super_Course", null], "graderType": "Not Graded"}'
-        self.assertEquals(get_response.content, get_response_string)
-        self.request_2 = RequestFactory().post('foo')
-        self.request_2.user = self.permit_user
-        post_response = views.assignment_type_update(self.request_2,'MITx', '999',
-                                                    'course', 'Robot_Super_Course')
-        self.assertIsInstance(post_response,HttpResponse)
-        self.assertEquals(post_response.content, 'null')
-
-    def test_load_preview_state(self):
-        # Tests that function creates empty defaultdict when request.session
-        # is empty
-        # location cannot be a list or other mutable type
-        self.request = RequestFactory().get('foo')
-        self.request.session = {}
-        instance_state, shared_state = views.load_preview_state(self.request,
-                                                        'foo', 'bar')
-        self.assertIsNone(instance_state)
-        self.assertIsNone(shared_state)
-        # Done
-
-    def test_save_preview_state(self):
-        self.request = RequestFactory().get('foo')
-        self.request.session = {}
-        loc = Location(self.location_3)
-        result = {'preview_states':
-                                      {('id', loc):{'instance':None,
-                                                        'shared':None,
-                                                        }
-                                        }
-                }
-        views.save_preview_state(self.request, 'id', loc, None, None)
-        self.assertEquals(self.request.session, result)
-        # Done
-
-    def test_get_preview_module(self):
-        self.request = RequestFactory().get('foo')
-        self.request.user = self.permit_user
-        self.request.session = {}
-        module = views.get_preview_module(self.request, 'id', self.course)
-        self.assertIsInstance(module, SequenceModule)
-        # Done
-
-    def test_preview_module_system(self):
-        # Returns a ModuleSystem
-        self.request = RequestFactory().get('foo')
-        self.request.user = self.no_permit_user
-        self.assertIsInstance(views.preview_module_system(self.request,
-                                                          'id', self.course),
-                              ModuleSystem)
-        # done
-
-    def test_load_preview_module(self):
-        # if error in getting module, return ErrorModule
-        self.request = RequestFactory().get('foo')
-        self.request.user = self.no_permit_user
-        self.assertIsInstance(views.preview_module_system(self.request,
-                                                          'id', self.course),
-                              ModuleSystem)
-        self.request.session = {}
-        self.assertIsInstance(views.load_preview_module(self.request, 'id',
-                                        self.course, 'instance', 'shared'),
-                              ErrorModule)
-        instance_state, shared_state = self.course.get_sample_state()[0]
-        module = views.load_preview_module(self.request,'id', self.course,
-                                           instance_state, shared_state)
-        self.assertIsInstance(module, SequenceModule)
-        # I'd like to test module.get_html, but it relies on render_to_string
-        # Test static_tab
-        self.course_2 = CourseFactory(display_name = 'Intro_to_intros', location = Location('i4x', 'MITx', '666', 'static_tab', 'Intro_to_intros'))
-        module_2 = views.load_preview_module(self.request,'id', self.course_2,
-                                             instance_state, shared_state)
-        self.assertIsInstance(module, SequenceModule)
-        # needs render_to_string
-
-    def test__xmodule_recurse(self):
-        #There shouldn't be a difference, but the code works with defined
-        # function f but not with lambda functions
-        mock_item = MagicMock()
-        mock_item.get_children.return_value = []
-        s = Stub()
-        s.children.append(Stub())
-        views._xmodule_recurse(s, f)
-        self.assertEquals(s.n, 1)
-        self.assertEquals(s.children[0].n, 1)
-
-    def test_get_module_previews(self):
-        raise SkipTest
-        # needs a working render_to_string
-        self.request = RequestFactory().get('foo')
-        self.request.user = UserFactory()
-        self.request.session = {}
-        print views.get_module_previews(self.request, self.course)
-
-    def test_delete_item(self):
-        raise SkipTest
-        # If user doesn't have permission, redirect
-        self.request = RequestFactory().post('i4x://MITx/999/course/Robot_Super_Course')
-        self.request.POST = self.request.POST.copy()
-        self.request.POST.update({'id':'i4x://MITx/999/course/Robot_Super_Course'})
-        self.request.user = self.no_permit_user
-        self.assertRaises(PermissionDenied, views.delete_item, self.request)
-        # Should return an HttpResponse
-        self.request_2 = RequestFactory().post(self.item.location.url())
-        self.request_2.POST = self.request_2.POST.copy()
-        self.request_2.POST.update({'id':self.item.location.url()})
-        self.request_2.user = self.permit_user
-        response = views.delete_item(self.request_2)
-        self.assertIsInstance(response, HttpResponse)
-        self.assertEquals(modulestore().get_items(self.item.location.url()), [])
-        # Set delete_children to True to delete all children
-        # Create children
-        self.item_2 = ItemFactory.create()
-        child_item = ItemFactory.create()
-##        print type(self.item_2)
-##        print self.item_2.__dict__
-        # Is there better way of adding children? What format are children in?
-        self.item_2.definition['children'] = [child_item.location.url()]
-        self.request_3 = RequestFactory().post(self.item_2.location.url())
-        self.request_3.POST = self.request_3.POST.copy()
-        self.request_3.POST.update({'id':self.item_2.location.url(),
-                                    'delete_children':True,
-                                    'delete_all_versions':True})
-        self.request_3.user = self.permit_user
-        print self.item_2.get_children()
-        self.assertIsInstance(views.delete_item(self.request_3), HttpResponse)
-        self.assertEquals(modulestore().get_items(self.item_2.location.url()), [])
-        # Problem: Function doesn't delete child item?
-        # child_item can be manually deleted, but can't delete it using function
-        # Not sure if problem with _xmodule_recurse and lambda functions
-        #store = views.get_modulestore(child_item.location.url())
-        #store.delete_item(child_item.location)
-        self.assertEquals(modulestore().get_items(child_item.location.url()), [])
-        # Check delete_item on 'vertical'
-        self.item_3 = ItemFactory.create(template = 'i4x://edx/templates/vertical/Empty')
-        self.request_4 = RequestFactory().post(self.item_3.location.url())
-        self.request_4.POST = self.request_4.POST.copy()
-        self.request_4.POST.update({'id':self.item_3.location.url(),
-                                    'delete_children':True,
-                                    'delete_all_versions':True})
-        self.request_4.user = self.permit_user
-        self.assertIsInstance(views.delete_item(self.request_4), HttpResponse)
-        self.assertEquals(modulestore().get_items(self.item_3.location.url()), [])
-    
-    def test_save_item(self):
-        # Test that user with no permissions gets redirected
-        self.request = RequestFactory().post(self.item.location.url())
-        self.request.POST = self.request.POST.copy()
-        self.request.POST.update({'id':self.item.location.url()})
-        self.request.user = self.no_permit_user
-        self.assertRaises(PermissionDenied, views.save_item, self.request)
-        # Test user with permissions but nothing in request.POST
-        self.item_2 = ItemFactory.create()
-        self.request_2 = RequestFactory().post(self.item_2.location.url())
-        self.request_2.POST = self.request.POST.copy()
-        self.request_2.POST.update({'id':self.item_2.location.url()})
-        self.request_2.user = self.permit_user
-        self.assertIsInstance(views.save_item(self.request_2), HttpResponse)
-        # Test updating data
-        self.request_3 = RequestFactory().post(self.item_2.location.url())
-        self.request_3.POST = self.request.POST.copy()
-        self.request_3.POST.update({'id':self.item_2.location.url(),
-                                    'data':{'foo':'bar'}})
-        self.request_3.user = self.permit_user
-        self.assertIsInstance(views.save_item(self.request_3), HttpResponse)
-        self.assertEquals(modulestore().get_item(self.item_2.location.dict()).definition['data'],
-                          {u'foo': u'bar'})
-        # Test updating metadata
-        self.request_4 = RequestFactory().post(self.item_2.location.url())
-        self.request_4.POST = self.request.POST.copy()
-        self.request_4.POST.update({'id':self.item_2.location.url(),
-                                    'metadata':{'foo':'bar'}})
-        self.request_4.user = self.permit_user
-        self.assertIsInstance(views.save_item(self.request_4), HttpResponse)
-        self.assertEquals(modulestore().get_item(self.item_2.location.dict()).metadata['foo'],
-                          'bar')
-        #done
-
-    def test_clone_item(self):
-        # Test that user with no permissions gets redirected
-        self.request = RequestFactory().post(self.item.location.url())
-        self.request.POST = self.request.POST.copy()
-        self.request.POST.update({'id':self.item.location.url(),
-                                  'parent_location':self.course.location.url(),
-                                  'template':self.location_3,
-                                  'display_name':'bar'})
-        self.request.user = self.no_permit_user
-        self.assertRaises(PermissionDenied, views.clone_item, self.request)
-        self.request.user = self.permit_user
-        response = views.clone_item(self.request)
-        self.assertIsInstance(response, HttpResponse)
-        self.assertRegexpMatches(response.content, '{"id": "i4x://MITx/999/course/')
-        # Done
-
-    def test_upload_asset(self):
-        # Test get request
-        self.request = RequestFactory().get('foo')
-        self.assertIsInstance(views.upload_asset(self.request,'org', 'course',
-                                                 'coursename'), HttpResponseBadRequest)
-        # Test no permissions
-        self.request_2 = RequestFactory().post('foo')
-        self.request_2.user = self.no_permit_user
-        self.assertIsInstance(views.upload_asset(self.request_2, 'MITx', '999',
-                                    'Robot_Super_Course'), HttpResponseForbidden)
-        # Test if course exists
-        
-        self.request_3 = RequestFactory().post('foo')
-        self.request_3.user = self.permit_user
-        # Throws error because of improperly formatted log
-##        self.assertIsInstance(views.upload_asset(self.request_3,'org', 'course',
-##                                                 'coursename'),HttpResponseBadRequest)
-        # Test response with fake file attached
-        # Not sure how to create fake file for testing purposes because
-        # can't override request.FILES
-##        print self.request_3.FILES
-##        print type(self.request_3.FILES)
-##        f = open('file.txt')
-##        self.request_4 = RequestFactory().post('foo', f)
-##        print self.request_3.FILES
-##        mock_file = MagicMock(name = 'Secrets', content_type = 'foo')
-##        mock_file.read.return_value = 'stuff'
-##        file_dict = {'file':mock_file}
-##        self.request_3.FILES = file_dict
-##        print views.upload_asset(self.request_3, 'MITx', '999',
-##                                    'Robot_Super_Course')
-
-    def test_manage_users(self):
-        self.request = RequestFactory().get('foo')
-        self.request.user = self.no_permit_user
-        self.assertRaises(PermissionDenied, views.manage_users, self.request,
-                          self.location_3)
-        # Needs render_to_response
-
-    def test_create_json_response(self):
-        ok_response = views.create_json_response()
-        self.assertIsInstance(ok_response, HttpResponse)
-        self.assertEquals(ok_response.content, '{"Status": "OK"}')
-        bad_response = views.create_json_response('Spacetime collapsing')
-        self.assertIsInstance(bad_response, HttpResponse)
-        self.assertEquals(bad_response.content, '{"Status": "Failed", "ErrMsg": "Spacetime collapsing"}')
-
-    def test_reorder_static_tabs(self):
-        self.request = RequestFactory().get('foo')
-        self.request.POST = {'tabs':[self.location_3]}
-        self.request.user = self.no_permit_user
-        self.assertRaises(PermissionDenied, views.reorder_static_tabs, self.request)
-        self.request.user = self.permit_user
-        self.assertIsInstance(views.reorder_static_tabs(self.request),
-                              HttpResponseBadRequest)
-        # to be continued ...
-
-def f(x):
-    x.n += 1
-    
-class Stub():
-    def __init__(self):
-        self.n = 0
-        self.children = []
-    def get_children(self):
-        return self.children
-    
diff --git a/lms/djangoapps/courseware/tests/test_course_creation.py b/lms/djangoapps/courseware/tests/test_course_creation.py
deleted file mode 100644
index af3e3ee0e1..0000000000
--- a/lms/djangoapps/courseware/tests/test_course_creation.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import logging
-from mock import MagicMock, patch
-import factory
-import copy
-from path import path
-
-from django.test import TestCase
-from django.test.client import Client
-from django.core.urlresolvers import reverse
-from django.conf import settings
-from override_settings import override_settings
-
-from xmodule.modulestore.xml_importer import import_from_xml
-import xmodule.modulestore.django
-
-TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
-TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data')
-
-@override_settings(MODULESTORE=TEST_DATA_MODULESTORE)
-class CreateTest(TestCase):
-    def setUp(self):
-        xmodule.modulestore.django._MODULESTORES = {}
-        xmodule.modulestore.django.modulestore().collection.drop()
-        import_from_xml(modulestore(), 'common/test/data/', [test_course_name])
-
-    def check_edit_item(self, test_course_name):
-        import_from_xml(modulestore(), 'common/test/data/', [test_course_name])
-        for descriptor in modulestore().get_items(Location(None, None, None, None, None)):
-            print "Checking ", descriptor.location.url()
-            print descriptor.__class__, descriptor.location
-            resp = self.client.get(reverse('edit_item'), {'id': descriptor.location.url()})
-            self.assertEqual(resp.status_code, 200)
-
-    def test_edit_item_toy(self):
-        self.check_edit_item('toy')
-    
-##    def setUp(self):
-##        self.client = Client()
-##        self.username = 'username'
-##        self.email = 'test@foo.com'
-##        self.pw = 'password'
-##
-##    def create_account(self, username, email, pw):
-##        resp = self.client.post('/create_account', {
-##            'username': username,
-##            'email': email,
-##            'password': pw,
-##            'location': 'home',
-##            'language': 'Franglish',
-##            'name': 'Fred Weasley',
-##            'terms_of_service': 'true',
-##            'honor_code': 'true',
-##        })
-##        return resp
-##
-##    def registration(self, email):
-##    '''look up registration object by email'''
-##        return Registration.objects.get(user__email=email)
-##
-##    def activate_user(self, email):
-##        activation_key = self.registration(email).activation_key
diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py
index f419e6f582..d5f821e0bf 100644
--- a/lms/djangoapps/courseware/tests/test_module_render.py
+++ b/lms/djangoapps/courseware/tests/test_module_render.py
@@ -14,13 +14,13 @@ from django.conf import settings
 from django.test import TestCase
 from django.test.client import RequestFactory
 from django.core.urlresolvers import reverse 
+from django.test.utils import override_settings
 
 from courseware.models import StudentModule, StudentModuleCache 
 from xmodule.modulestore.exceptions import ItemNotFoundError
 from xmodule.exceptions import NotFoundError 
 from xmodule.modulestore import Location
 import courseware.module_render as render
-from override_settings import override_settings
 from xmodule.modulestore.django import modulestore, _MODULESTORES
 from xmodule.seq_module import SequenceModule
 from courseware.tests.tests import PageLoader
@@ -58,35 +58,11 @@ class ModuleRenderTestCase(PageLoader):
         self.course_id = 'edX/toy/2012_Fall'
         self.toy_course = modulestore().get_course(self.course_id)
 
-    def test_toc_for_course(self):
-        mock_course = MagicMock()
-        mock_course.id = 'dummy'
-        mock_course.location = Location(self.location)
-        mock_course.get_children.return_value = []
-        mock_user = MagicMock()
-        mock_user.is_authenticated.return_value = False
-        self.assertIsNone(render.toc_for_course(mock_user,'dummy',
-                                                mock_course, 'dummy', 'dummy'))
-        # rest of tests are in class TestTOC
-
     def test_get_module(self):
         self.assertIsNone(render.get_module('dummyuser',None,\
                                             'invalid location',None,None))
-        #done
-
-    def test__get_module(self):
-        mock_user = MagicMock()
-        mock_user.is_authenticated.return_value = False
-        location = Location('i4x', 'edX', 'toy', 'chapter', 'Overview')
-        mock_request = MagicMock()
-        s = render._get_module(mock_user, mock_request, location,
-                                'dummy', 'edX/toy/2012_Fall')
-        self.assertIsInstance(s, SequenceModule)
-        # Don't know how to generate error in line 260 to test
-        # Can't tell if sequence module is an error?
 
     def test_get_instance_module(self):
-        # done
         mock_user = MagicMock()
         mock_user.is_authenticated.return_value = False
         self.assertIsNone(render.get_instance_module('dummy', mock_user, 'dummy',
diff --git a/lms/djangoapps/courseware/tests/test_progress.py b/lms/djangoapps/courseware/tests/test_progress.py
index 480d594863..4e528f44a4 100644
--- a/lms/djangoapps/courseware/tests/test_progress.py
+++ b/lms/djangoapps/courseware/tests/test_progress.py
@@ -55,13 +55,5 @@ class ProgessTests(TestCase):
 		self.c.__setitem__('questions_correct', 4)
 		self.assertEqual(str(self.c),str(self.d))
 
-	# def test_add(self):
-	# 	self.assertEqual(self.c.__add__(self.c2), self.cplusc2)
-
-	def test_contains(self):
-
-		return self.c.__contains__('meow')
-		#self.assertEqual(self.c.__contains__('done'), True)
-
 	def test_repr(self):
 		self.assertEqual(self.c.__repr__(), str(progress.completion()))
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index c901f87720..de4d934ee7 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -4,7 +4,6 @@ import datetime
 import factory
 import unittest
 import os
-from nose.plugins.skip import SkipTest
 
 from django.test import TestCase
 from django.http import Http404, HttpResponse
@@ -20,13 +19,6 @@ from xmodule.modulestore.exceptions import InvalidLocationError,\
 import courseware.views as views
 from xmodule.modulestore import Location
 
-def skipped(func):
-    from nose.plugins.skip import SkipTest
-    def _():
-        raise SkipTest("Test %s is skipped" % func.__name__)
-    _.__name__ = func.__name__
-    return _
-
 #from override_settings import override_settings
 
 class Stub():
@@ -38,13 +30,6 @@ class UserFactory(factory.Factory):
     is_staff = True
     is_active = True
 
-def skipped(func):
-    from nose.plugins.skip import SkipTest
-    def _():
-        raise SkipTest("Test %s is skipped" % func.__name__)
-    _.__name__ = func.__name__
-    return _
-
 # This part is required for modulestore() to work properly
 def xml_store_config(data_dir):
     return {
@@ -139,20 +124,6 @@ class ViewsTestCase(TestCase):
         self.assertRaises(Http404, views.redirect_to_course_position,
                           mock_module, True)
 
-    def test_index(self):
-        assert SkipTest
-        request = self.request_factory.get(self.chapter_url)
-        request.user = UserFactory()
-        response = views.index(request, self.course_id)
-        self.assertIsInstance(response, HttpResponse)
-        self.assertEqual(response.status_code, 302)
-        # views.index does not throw 404 if chapter, section, or position are
-        # not valid, which doesn't match index's comments
-        views.index(request, self.course_id, chapter='foo', section='bar',
-                    position='baz')
-        request_2 = self.request_factory.get(self.chapter_url)
-        request_2.user = self.user
-        response = views.index(request_2, self.course_id)
 
     def test_registered_for_course(self):
         self.assertFalse(views.registered_for_course('Basketweaving', None))
@@ -187,47 +158,3 @@ class ViewsTestCase(TestCase):
 ##        request_2 = self.request_factory.get('foo')
 ##        request_2.user = UserFactory()
         
-    def test_static_university_profile(self):
-        # TODO
-        # Can't test unless havehttp://toastdriven.com/blog/2011/apr/10/guide-to-testing-in-django/ a valid template file
-        raise SkipTest
-        request = self.client.get('university_profile/edX')
-        self.assertIsInstance(views.static_university_profile(request, 'edX'), HttpResponse)
-        
-    def test_university_profile(self):
-        raise SkipTest
-        request = self.request_factory.get(self.chapter_url)
-        request.user = UserFactory()
-        self.assertRaisesRegexp(Http404, 'University Profile*',
-                                views.university_profile, request, 'Harvard')
-        # TODO
-        #request_2 = self.client.get('/university_profile/edx')
-        self.assertIsInstance(views.university_profile(request, 'edX'), HttpResponse)
-        # Can't continue testing unless have valid template file
-
-    
-    def test_syllabus(self):
-        raise SkipTest
-        request = self.request_factory.get(self.chapter_url)
-        request.user = UserFactory()
-        # Can't find valid template
-        # TODO
-        views.syllabus(request, 'edX/toy/2012_Fall')
-
-    def test_render_notifications(self):
-        raise SkipTest
-        request = self.request_factory.get('foo')
-        #views.render_notifications(request, self.course_id, 'dummy')
-        # TODO
-        # Needs valid template
-
-    def test_news(self):
-        raise SkipTest
-        # Bug? get_notifications is actually in lms/lib/comment_client/legacy.py
-        request = self.client.get('/news')
-        self.user.id = 'foo'
-        request.user = self.user
-        course_id = 'edX/toy/2012_Fall'
-        self.assertIsInstance(views.news(request, course_id), HttpResponse)
-        
-        # TODO

From 437aadb28e3ce110d1f75cd0f8af79c52a6dbf32 Mon Sep 17 00:00:00 2001
From: Will Daly 
Date: Tue, 5 Mar 2013 09:32:20 -0500
Subject: [PATCH 86/98] Cleaned up some tests; deleted skip tests

---
 .../courseware/tests/test_courses.py          | 63 --------------
 .../courseware/tests/test_module_render.py    | 87 -------------------
 lms/djangoapps/courseware/tests/test_views.py | 25 +-----
 3 files changed, 1 insertion(+), 174 deletions(-)
 delete mode 100644 lms/djangoapps/courseware/tests/test_courses.py

diff --git a/lms/djangoapps/courseware/tests/test_courses.py b/lms/djangoapps/courseware/tests/test_courses.py
deleted file mode 100644
index 91b6af4dfc..0000000000
--- a/lms/djangoapps/courseware/tests/test_courses.py
+++ /dev/null
@@ -1,63 +0,0 @@
-from mock import MagicMock, patch
-import datetime
-
-from django.test import TestCase
-from django.contrib.auth.models import User
-from django.conf import settings
-from django.test.utils import override_settings
-
-from student.models import CourseEnrollment
-import courseware.courses as courses
-from xmodule.modulestore.xml import XMLModuleStore
-from xmodule.modulestore.django import modulestore
-from xmodule.modulestore import Location
-
-def xml_store_config(data_dir):
-    return {
-    'default': {
-        'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore',
-        'OPTIONS': {
-            'data_dir': data_dir,
-            'default_class': 'xmodule.hidden_module.HiddenDescriptor',
-        }
-    }
-}
-
-TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
-TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
-
-@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
-class CoursesTestCase(TestCase):
-    def setUp(self):
-##        self.user = User.objects.create(username='dummy', password='123456',
-##                                        email='test@mit.edu')
-##        self.date = datetime.datetime(2013,1,22)
-##        self.enrollment = CourseEnrollment.objects.get_or_create(user = self.user,
-##                                                  course_id = self.course_id,
-##                                                  created = self.date)[0]
-        self._MODULESTORES = {}
-        #self.course_id = 'edx/toy/2012_Fall'
-        self.toy_course = modulestore().get_course('edX/toy/2012_Fall')
-    
-    def test_get_course_by_id(self):
-        courses.get_course_by_id("edX/toy/2012_Fall")
-
-
-@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
-class CoursesTests(TestCase):
-    # runs
-    def setUp(self):
-        self._MODULESTORES = {}
-        #self.course_id = 'edX/toy/2012_Fall'
-        self.toy_course = modulestore().get_course('edX/toy/2012_Fall')
-##        self.fake_user = User.objects.create(is_superuser=True)
-
-        '''
-        no test written for get_request_for_thread
-        '''
-
-    def test_get_course_by_id(self):
-        #self.test_course_id = "edX/toy/2012_Fall"
-        courses.get_course_by_id("edX/toy/2012_Fall")
-        # print modulestore().get_instance(test_course_id, Location('i4x', 'edx', 'toy', 'course', '2012_Fall'))
-        #self.assertEqual(courses.get_course_by_id(self.test_course_id),modulestore().get_instance(self.test_course_id, Location('i4x', 'edX', 'toy', 'course', '2012_Fall'), None))
diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py
index d5f821e0bf..9b9b3f6d2f 100644
--- a/lms/djangoapps/courseware/tests/test_module_render.py
+++ b/lms/djangoapps/courseware/tests/test_module_render.py
@@ -4,7 +4,6 @@ import json
 import factory
 import unittest
 from nose.tools import set_trace
-from nose.plugins.skip import SkipTest
 
 from django.http import Http404, HttpResponse, HttpRequest
 from django.conf import settings
@@ -74,62 +73,6 @@ class ModuleRenderTestCase(PageLoader):
         self.assertIsNone(render.get_instance_module('dummy', mock_user_2,
                                                      mock_module,'dummy'))
 
-    def test_get_shared_instance_module(self):
-        raise SkipTest
-        mock_user = MagicMock(User)
-        mock_user.is_authenticated.return_value = False
-        self.assertIsNone(render.get_shared_instance_module('dummy', mock_user, 'dummy',
-                     'dummy'))
-        mock_user_2 = MagicMock(User)
-        mock_user_2.is_authenticated.return_value = True
-        
-        mock_module = MagicMock(shared_state_key = 'key')
-        mock_module.location = Location('i4x', 'edX', 'toy', 'chapter', 'Overview')
-        mock_module.get_shared_state.return_value = '{}'
-        mock_cache = MagicMock()
-        mock_cache.lookup.return_value = False
-        #mock_cache._state = 'dummy'
-        #set_trace()
-        print mock_module.get_shared_state()
-        s = render.get_shared_instance_module(self.course_id, mock_user_2,
-                                              mock_module, mock_cache)
-        self.assertIsInstance(s, StudentModule)
-        # Problem: can't get code to take branch that creates StudentModule?
-        # Can't finish testing modx_dispatch
-
-    def test_xqueue_callback(self):
-        mock_request = MagicMock()
-        mock_request.POST.copy.return_value = {}
-        # 339
-        self.assertRaises(Http404, render.xqueue_callback,mock_request,
-                          'dummy', 'dummy', 'dummy', 'dummy')
-        mock_request_2 = MagicMock()
-        xpackage = {'xqueue_header': json.dumps({}), 
-        'xqueue_body'  : 'Message from grader'}
-        mock_request_2.POST.copy.return_value = xpackage
-        # 342
-        self.assertRaises(Http404, render.xqueue_callback,mock_request_2,
-                          'dummy', 'dummy', 'dummy', 'dummy')
-        mock_request_3 = MagicMock()
-        xpackage_2 = {'xqueue_header': json.dumps({'lms_key':'secretkey'}), 
-        'xqueue_body'  : 'Message from grader'}
-        mock_request_3.POST.copy.return_value = xpackage_2
-        # Roadblock: how to get user registered in class?
-        raise SkipTest
-        #
-        # trying alternate way of creating account in hopes of getting valid id
-        # Problem: Can't activate user
-        
-        self.student_name = '12'
-        self.password = 'foo'
-        self.email = 'test@mit.edu'
-        self.create_account(self.student_name, self.email, self.password)
-        self.activate_user(self.email)
-        request = RequestFactory().get('stuff')
-        # This doesn't work to install user
-        render.xqueue_callback(mock_request_3, self.course_id,
-                               self.student_name, self.password, 'dummy')
-
     def test_modx_dispatch(self):
         self.assertRaises(Http404, render.modx_dispatch, 'dummy', 'dummy',
                           'invalid Location', 'dummy')
@@ -162,40 +105,10 @@ class ModuleRenderTestCase(PageLoader):
                           mock_request_3, 'dummy', self.location, 'toy')
         self.assertRaises(Http404,render.modx_dispatch, mock_request_3, 'dummy',
                             self.location, self.course_id)
-##        student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(self.course_id, 
-##                mock_request_3.user, modulestore().get_instance(self.course_id, self.location))
-##        get_shared_instance_module(course_id, request.user, instance, student_module_cache)
-        # 'goto_position' is the only dispatch that will work
         mock_request_3.POST.copy.return_value = {'position':1}
         self.assertIsInstance(render.modx_dispatch(mock_request_3, 'goto_position',
                             self.location, self.course_id), HttpResponse)
-        # keep going
         
-    def test_preview_chemcalc(self):
-        mock_request = MagicMock(method = 'notGET')
-        self.assertRaises(Http404, render.preview_chemcalc, mock_request)
-        mock_request_2 = MagicMock(method = 'GET')
-        mock_request_2.GET.get.return_value = None
-        self.assertEquals(render.preview_chemcalc(mock_request_2).content,
-                          json.dumps({'preview':'',
-                                      'error':'No formula specified.'}))
-
-        mock_request_3 = MagicMock()
-        mock_request_3.method = 'GET'
-        # Test fails because chemcalc.render_to_html always parses strings?
-        mock_request_3.GET.get.return_value = unicode('\x12400', errors="strict")
-##        self.assertEquals(render.preview_chemcalc(mock_request_3).content,
-##                          json.dumps({'preview':'',
-##                                      'error':"Couldn't parse formula: formula"}))
-##        
-        mock_request_3 = MagicMock()
-        mock_request_3.method = 'GET'
-        mock_request_3.GET.get.return_value = Stub()
-        self.assertEquals(render.preview_chemcalc(mock_request_3).content,
-                          json.dumps({'preview':'',
-                                      'error':"Error while rendering preview"}))
-        
-
     def test_get_score_bucket(self):
         self.assertEquals(render.get_score_bucket(0, 10), 'incorrect')
         self.assertEquals(render.get_score_bucket(1, 10), 'partial')
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index de4d934ee7..76047aabda 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -19,8 +19,6 @@ from xmodule.modulestore.exceptions import InvalidLocationError,\
 import courseware.views as views
 from xmodule.modulestore import Location
 
-#from override_settings import override_settings
-
 class Stub():
     pass
 
@@ -45,9 +43,6 @@ def xml_store_config(data_dir):
 TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
 TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
 
-
-#class ModulestoreTest(TestCase):
-
 @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
 class TestJumpTo(TestCase):
     """Check the jumpto link for a course"""
@@ -134,27 +129,9 @@ class ViewsTestCase(TestCase):
         mock_course.id = self.course_id
         self.assertTrue(views.registered_for_course(mock_course, self.user))
 
-    def test_jump_to(self):
+    def test_jump_to_invalid(self):
         request = self.request_factory.get(self.chapter_url)
         self.assertRaisesRegexp(Http404, 'Invalid location', views.jump_to,
                                 request, 'bar', ())
         self.assertRaisesRegexp(Http404, 'No data*', views.jump_to, request,
                                 'dummy', self.location)
-##        print type(self.toy_course)
-##        print dir(self.toy_course)
-##        print self.toy_course.location
-##        print self.toy_course.__dict__
-##        valid = ['i4x', 'edX', 'toy', 'chapter', 'overview']
-##        L = Location('i4x', 'edX', 'toy', 'chapter', 'Overview', None)
-##        
-##        views.jump_to(request, 'dummy', L)
-        
-    def test_static_tab(self):
-        request = self.request_factory.get('foo')
-        request.user = self.user
-        self.assertRaises(Http404, views.static_tab, request, 'edX/toy/2012_Fall',
-                          'dummy')
-        # What are valid tab_slugs?
-##        request_2 = self.request_factory.get('foo')
-##        request_2.user = UserFactory()
-        

From 2fd9ccece1cb39b5234d905ae7f4ca2d4ae02aa6 Mon Sep 17 00:00:00 2001
From: Will Daly 
Date: Tue, 5 Mar 2013 09:34:52 -0500
Subject: [PATCH 87/98] Fixed whitespace issues; removed a test that made no
 assertions

---
 lms/djangoapps/courseware/tests/test_progress.py |  3 +--
 lms/djangoapps/courseware/tests/test_views.py    | 11 -----------
 2 files changed, 1 insertion(+), 13 deletions(-)

diff --git a/lms/djangoapps/courseware/tests/test_progress.py b/lms/djangoapps/courseware/tests/test_progress.py
index 4e528f44a4..44a0f0cb30 100644
--- a/lms/djangoapps/courseware/tests/test_progress.py
+++ b/lms/djangoapps/courseware/tests/test_progress.py
@@ -30,8 +30,6 @@ class ProgessTests(TestCase):
 		     'questions_incorrect': 1,
 		     'questions_total': 0})
 
-		
-
 		self.oth = dict({'duration_total': 0,
 		     'duration_watched': 0,
 		     'done': True,
@@ -48,6 +46,7 @@ class ProgessTests(TestCase):
 	     'questions_correct': 4,
 	     'questions_incorrect': 0,
 	     'questions_total': 7}
+
 	def test_getitem(self):
 		self.assertEqual(self.c.__getitem__('duration_watched'), 0)
 
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index 76047aabda..3ff845834a 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -84,22 +84,12 @@ class ViewsTestCase(TestCase):
         chapter = 'Overview'
         self.chapter_url = '%s/%s/%s' % ('/courses', self.course_id, chapter)
 
-
     def test_user_groups(self):
         # depreciated function
         mock_user = MagicMock()
         mock_user.is_authenticated.return_value = False
         self.assertEquals(views.user_groups(mock_user),[])
         
-
-    @override_settings(DEBUG = True)
-    def test_user_groups_debug(self):
-        mock_user = MagicMock()
-        mock_user.is_authenticated.return_value = True
-        pass
-        #views.user_groups(mock_user)
-        #Keep going later
-
     def test_get_current_child(self):
         self.assertIsNone(views.get_current_child(Stub()))
         mock_xmodule = MagicMock()
@@ -119,7 +109,6 @@ class ViewsTestCase(TestCase):
         self.assertRaises(Http404, views.redirect_to_course_position,
                           mock_module, True)
 
-
     def test_registered_for_course(self):
         self.assertFalse(views.registered_for_course('Basketweaving', None))
         mock_user = MagicMock()

From d03fe79e070caa980018acf17c012e372f465c88 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri 
Date: Tue, 5 Mar 2013 10:36:22 -0500
Subject: [PATCH 88/98] Small metadata naming fix

---
 .../xmodule/xmodule/templates/combinedopenended/default.yaml    | 2 +-
 common/lib/xmodule/xmodule/templates/peer_grading/default.yaml  | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml b/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml
index 60143d40e9..f2aba0e18b 100644
--- a/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml
+++ b/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml
@@ -1,7 +1,7 @@
 ---
 metadata:
     display_name: Open Ended Response
-    attempts: 1
+    max_attempts: 1
     max_score: 1
     is_graded: False
     version: 1
diff --git a/common/lib/xmodule/xmodule/templates/peer_grading/default.yaml b/common/lib/xmodule/xmodule/templates/peer_grading/default.yaml
index 46b5572bc8..cb8e29dfa2 100644
--- a/common/lib/xmodule/xmodule/templates/peer_grading/default.yaml
+++ b/common/lib/xmodule/xmodule/templates/peer_grading/default.yaml
@@ -5,6 +5,7 @@ metadata:
     use_for_single_location: False
     link_to_location: None
     is_graded: False
+    max_grade: 1
 data: |
   
   

From ef9f963f494ac3627d20c7f82616bfa867c68ce4 Mon Sep 17 00:00:00 2001
From: Brian Talbot 
Date: Tue, 5 Mar 2013 10:39:21 -0500
Subject: [PATCH 89/98] studio - advanced modules: added in advanced module
 button icon per artie/vik work

---
 cms/static/img/large-advanced-icon.png | Bin 0 -> 342 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 cms/static/img/large-advanced-icon.png

diff --git a/cms/static/img/large-advanced-icon.png b/cms/static/img/large-advanced-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..c6a19ea5a931e4a65daecb30ab6e741238db9b62
GIT binary patch
literal 342
zcmeAS@N?(olHy`uVBq!ia0vp^av;pX3?zBp#Z3TG#sNMduK!5^Kutj9qFbB30a*ei
zL4Lsn{R!(0?w=P>2$;YBy+b{NjoN-`pxhx(7srr_TWc?#Eo)KWX?s}mgO~Hi-Tzmw
zZF%L;yZO~_&h~V{zDs2$23ODDu77lE`Kj6K7j^JX%y@iEb^~L{^q(nZmJ4rYc*yZ*
zY>X65e)_e{;dQU{rA?_%?QCbf-BaXtV;xIn>ZaOo(FHTRmA
Date: Tue, 5 Mar 2013 10:51:59 -0500
Subject: [PATCH 90/98] merged openended and advanced categories per discussion
 at today's standup

---
 cms/djangoapps/contentstore/views.py | 29 ++++++++++++++--------------
 1 file changed, 15 insertions(+), 14 deletions(-)

diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index e14a3108d4..92cced6106 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -70,8 +70,7 @@ log = logging.getLogger(__name__)
 COMPONENT_TYPES = ['customtag', 'discussion', 'html', 'problem', 'video']
 
 ADVANCED_COMPONENT_TYPES = {
-    'openended' : ['combinedopenended', 'peergrading'],
-    'advanced' : ['annotatable'],
+    'advanced' : ['annotatable','combinedopenended', 'peergrading']
 }
 
 ADVANCED_COMPONENT_POLICY_KEY = 'advanced_modules'
@@ -293,18 +292,20 @@ def edit_unit(request, location):
     course_metadata = CourseMetadata.fetch(course.location)
     course_advanced_keys = course_metadata.get(ADVANCED_COMPONENT_POLICY_KEY, [])
 
-    #First try to parse with json
-    try:
-        course_advanced_keys = json.loads(course_advanced_keys)
-    except:
-        log.error("Cannot json decode course advanced policy: {0}".format(course_advanced_keys))
-    #It may be that it is not a json object, but can be evaluated as a python literal
-    try:
-        #This is a safe evaluation.  See docs for ast
-        course_advanced_keys = ast.literal_eval(course_advanced_keys)
-    except:
-        log.error("Cannot parse course advanced policy at all: {0}".format(course_advanced_keys))
-        course_advanced_keys=[]
+    # check if the keys are in JSON format, or perhaps a literal python expression
+    if isinstance(course_advanced_keys, basestring):
+        # Are you JSON?
+        try:
+            course_advanced_keys = json.loads(course_advanced_keys)
+        except:
+            log.error("Cannot JSON decode course advanced policy: {0}".format(course_advanced_keys))
+            # Not JSON? How about Python?
+        try:
+            #This is a safe evaluation.  See docs for ast
+            course_advanced_keys = ast.literal_eval(course_advanced_keys)
+        except:
+            log.error("Cannot parse course advanced policy at all: {0}".format(course_advanced_keys))
+            course_advanced_keys=[]
 
     #Set component types according to course policy file
     component_types = COMPONENT_TYPES

From c9b2a6a203921249b9cfafa92172db0ad5e3013e Mon Sep 17 00:00:00 2001
From: Arthur Barrett 
Date: Tue, 5 Mar 2013 10:52:39 -0500
Subject: [PATCH 91/98] updated CSS to use the new advanced icon

---
 cms/static/sass/_graphics.scss | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/cms/static/sass/_graphics.scss b/cms/static/sass/_graphics.scss
index 48d3453777..300cf3b692 100644
--- a/cms/static/sass/_graphics.scss
+++ b/cms/static/sass/_graphics.scss
@@ -275,7 +275,7 @@
 	width: 100px;
 	height: 60px;
 	margin-right: 5px;
-	background: url(../img/large-problem-icon.png) center no-repeat;
+	background: url(../img/large-advanced-icon.png) center no-repeat;
 }
 
 .large-textbook-icon {

From cc234d45e7fde00e06c23aecfb1b160931c18f00 Mon Sep 17 00:00:00 2001
From: cahrens 
Date: Tue, 5 Mar 2013 10:55:27 -0500
Subject: [PATCH 92/98] Don't collapse multiple underscores for asset names.
 #202

---
 common/lib/xmodule/xmodule/contentstore/content.py |  3 ++-
 common/lib/xmodule/xmodule/modulestore/__init__.py | 11 +++++++++++
 common/lib/xmodule/xmodule/tests/test_content.py   | 11 ++++++++---
 3 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/common/lib/xmodule/xmodule/contentstore/content.py b/common/lib/xmodule/xmodule/contentstore/content.py
index be33401bc8..9dc4b1367b 100644
--- a/common/lib/xmodule/xmodule/contentstore/content.py
+++ b/common/lib/xmodule/xmodule/contentstore/content.py
@@ -35,7 +35,8 @@ class StaticContent(object):
     @staticmethod
     def compute_location(org, course, name, revision=None, is_thumbnail=False):
         name = name.replace('/', '_')
-        return Location([XASSET_LOCATION_TAG, org, course, 'asset' if not is_thumbnail else 'thumbnail', Location.clean(name), revision])
+        return Location([XASSET_LOCATION_TAG, org, course, 'asset' if not is_thumbnail else 'thumbnail',
+                         Location.clean_keeping_underscores(name), revision])
 
     def get_id(self):
         return StaticContent.get_id_from_location(self.location)
diff --git a/common/lib/xmodule/xmodule/modulestore/__init__.py b/common/lib/xmodule/xmodule/modulestore/__init__.py
index 0ba7e36540..525527c93f 100644
--- a/common/lib/xmodule/xmodule/modulestore/__init__.py
+++ b/common/lib/xmodule/xmodule/modulestore/__init__.py
@@ -71,6 +71,17 @@ class Location(_LocationBase):
         """
         return Location._clean(value, INVALID_CHARS)
 
+
+    @staticmethod
+    def clean_keeping_underscores(value):
+        """
+        Return value, replacing INVALID_CHARS, but not collapsing multiple '_' chars.
+        This for cleaning asset names, as the YouTube ID's may have underscores in them, and we need the
+        transcript asset name to match. In the future we may want to change the behavior of _clean.
+        """
+        return INVALID_CHARS.sub('_', value)
+
+
     @staticmethod
     def clean_for_url_name(value):
         """
diff --git a/common/lib/xmodule/xmodule/tests/test_content.py b/common/lib/xmodule/xmodule/tests/test_content.py
index 1bcd2f4ebe..e73c33197c 100644
--- a/common/lib/xmodule/xmodule/tests/test_content.py
+++ b/common/lib/xmodule/xmodule/tests/test_content.py
@@ -19,9 +19,14 @@ class ContentTest(unittest.TestCase):
 
         content = StaticContent('loc', 'name', 'content_type', 'data')
         self.assertIsNone(content.thumbnail_location)
-    def test_generate_thumbnail_nonimage(self):
+    def test_generate_thumbnail_image(self):
         contentStore = ContentStore()
-        content = Content(Location(u'c4x', u'mitX', u'800', u'asset', u'monsters.jpg'), None)
+        content = Content(Location(u'c4x', u'mitX', u'800', u'asset', u'monsters__.jpg'), None)
         (thumbnail_content, thumbnail_file_location) = contentStore.generate_thumbnail(content)
         self.assertIsNone(thumbnail_content)
-        self.assertEqual(Location(u'c4x', u'mitX', u'800', u'thumbnail', u'monsters.jpg'), thumbnail_file_location)
+        self.assertEqual(Location(u'c4x', u'mitX', u'800', u'thumbnail', u'monsters__.jpg'), thumbnail_file_location)
+    def test_compute_location(self):
+        # We had a bug that __ got converted into a single _. Make sure that substitution of INVALID_CHARS (like space)
+        # still happen.
+        asset_location = StaticContent.compute_location('mitX', '400', 'subs__1eo_jXvZnE .srt.sjson')
+        self.assertEqual(Location(u'c4x', u'mitX', u'400', u'asset', u'subs__1eo_jXvZnE_.srt.sjson', None), asset_location)

From cee24c5cb23bdd077f195c291d705a50ca8f977b Mon Sep 17 00:00:00 2001
From: Jay Zoldak 
Date: Tue, 5 Mar 2013 11:18:07 -0500
Subject: [PATCH 93/98] Import existing UserFactory

---
 lms/djangoapps/courseware/tests/test_module_render.py | 8 ++------
 lms/djangoapps/courseware/tests/test_views.py         | 7 ++-----
 2 files changed, 4 insertions(+), 11 deletions(-)

diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py
index 9b9b3f6d2f..61b70d3656 100644
--- a/lms/djangoapps/courseware/tests/test_module_render.py
+++ b/lms/djangoapps/courseware/tests/test_module_render.py
@@ -25,6 +25,8 @@ from xmodule.seq_module import SequenceModule
 from courseware.tests.tests import PageLoader
 from student.models import Registration 
 
+from factories import UserFactory
+
 class Stub:
     def __init__(self):
         pass
@@ -40,12 +42,6 @@ def xml_store_config(data_dir):
     }
 }
 
-class UserFactory(factory.Factory):
-    first_name = 'Test'
-    last_name = 'Robot'
-    is_staff = True
-    is_active = True
-
 TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
 TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
 
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index 3ff845834a..cb15b5b7b6 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -19,14 +19,11 @@ from xmodule.modulestore.exceptions import InvalidLocationError,\
 import courseware.views as views
 from xmodule.modulestore import Location
 
+from factories import UserFactory
+
 class Stub():
     pass
 
-class UserFactory(factory.Factory):
-    first_name = 'Test'
-    last_name = 'Robot'
-    is_staff = True
-    is_active = True
 
 # This part is required for modulestore() to work properly
 def xml_store_config(data_dir):

From b0a018195ad78f180dc3e210e995798c9d7a1d31 Mon Sep 17 00:00:00 2001
From: Jay Zoldak 
Date: Tue, 5 Mar 2013 11:53:00 -0500
Subject: [PATCH 94/98] Pep8 fixes for tests

---
 .../courseware/tests/test_access.py           |  11 +-
 .../courseware/tests/test_module_render.py    |  97 +++++++++--------
 .../courseware/tests/test_progress.py         |  87 ++++++++-------
 lms/djangoapps/courseware/tests/test_views.py |  31 +++---
 lms/djangoapps/courseware/tests/tests.py      | 101 ++++++++----------
 5 files changed, 158 insertions(+), 169 deletions(-)

diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py
index 0b32da17a4..acb05d5d78 100644
--- a/lms/djangoapps/courseware/tests/test_access.py
+++ b/lms/djangoapps/courseware/tests/test_access.py
@@ -1,21 +1,20 @@
 import unittest
-import logging 
+import logging
 import time
 from mock import Mock, MagicMock, patch
 
 from django.conf import settings
 from django.test import TestCase
 
-from xmodule.course_module import CourseDescriptor 
-from xmodule.error_module import ErrorDescriptor 
-from xmodule.modulestore import Location 
-from xmodule.timeparse import parse_time 
+from xmodule.course_module import CourseDescriptor
+from xmodule.error_module import ErrorDescriptor
+from xmodule.modulestore import Location
+from xmodule.timeparse import parse_time
 from xmodule.x_module import XModule, XModuleDescriptor
 import courseware.access as access
 from factories import CourseEnrollmentAllowedFactory
 
 
-
 class AccessTestCase(TestCase):
     def test__has_global_staff_access(self):
         u = Mock(is_staff=False)
diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py
index 61b70d3656..81f95a85e4 100644
--- a/lms/djangoapps/courseware/tests/test_module_render.py
+++ b/lms/djangoapps/courseware/tests/test_module_render.py
@@ -12,39 +12,42 @@ from django.test.client import Client
 from django.conf import settings
 from django.test import TestCase
 from django.test.client import RequestFactory
-from django.core.urlresolvers import reverse 
+from django.core.urlresolvers import reverse
 from django.test.utils import override_settings
 
-from courseware.models import StudentModule, StudentModuleCache 
+from courseware.models import StudentModule, StudentModuleCache
 from xmodule.modulestore.exceptions import ItemNotFoundError
-from xmodule.exceptions import NotFoundError 
+from xmodule.exceptions import NotFoundError
 from xmodule.modulestore import Location
 import courseware.module_render as render
 from xmodule.modulestore.django import modulestore, _MODULESTORES
 from xmodule.seq_module import SequenceModule
 from courseware.tests.tests import PageLoader
-from student.models import Registration 
+from student.models import Registration
 
 from factories import UserFactory
 
+
 class Stub:
     def __init__(self):
         pass
 
+
 def xml_store_config(data_dir):
     return {
-    'default': {
-        'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore',
-        'OPTIONS': {
-            'data_dir': data_dir,
-            'default_class': 'xmodule.hidden_module.HiddenDescriptor',
+        'default': {
+            'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore',
+            'OPTIONS': {
+                'data_dir': data_dir,
+                'default_class': 'xmodule.hidden_module.HiddenDescriptor',
+            }
         }
     }
-}
 
 TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
 TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
 
+
 @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
 class ModuleRenderTestCase(PageLoader):
     def setUp(self):
@@ -54,8 +57,8 @@ class ModuleRenderTestCase(PageLoader):
         self.toy_course = modulestore().get_course(self.course_id)
 
     def test_get_module(self):
-        self.assertIsNone(render.get_module('dummyuser',None,\
-                                            'invalid location',None,None))
+        self.assertIsNone(render.get_module('dummyuser', None,
+                                            'invalid location', None, None))
 
     def test_get_instance_module(self):
         mock_user = MagicMock()
@@ -67,17 +70,17 @@ class ModuleRenderTestCase(PageLoader):
         mock_module = MagicMock()
         mock_module.descriptor.stores_state = False
         self.assertIsNone(render.get_instance_module('dummy', mock_user_2,
-                                                     mock_module,'dummy'))
+                                                     mock_module, 'dummy'))
 
     def test_modx_dispatch(self):
         self.assertRaises(Http404, render.modx_dispatch, 'dummy', 'dummy',
                           'invalid Location', 'dummy')
         mock_request = MagicMock()
         mock_request.FILES.keys.return_value = ['file_id']
-        mock_request.FILES.getlist.return_value = ['file']*(settings.MAX_FILEUPLOADS_PER_INPUT + 1)
+        mock_request.FILES.getlist.return_value = ['file'] * (settings.MAX_FILEUPLOADS_PER_INPUT + 1)
         self.assertEquals(render.modx_dispatch(mock_request, 'dummy', self.location,
                                           'dummy').content,
-                         json.dumps({'success': 'Submission aborted! Maximum %d files may be submitted at once' %\
+                         json.dumps({'success': 'Submission aborted! Maximum %d files may be submitted at once' %
                                      settings.MAX_FILEUPLOADS_PER_INPUT}))
         mock_request_2 = MagicMock()
         mock_request_2.FILES.keys.return_value = ['file_id']
@@ -88,8 +91,8 @@ class ModuleRenderTestCase(PageLoader):
         mock_request_2.FILES.getlist.return_value = filelist
         self.assertEquals(render.modx_dispatch(mock_request_2, 'dummy', self.location,
                                                'dummy').content,
-                          json.dumps({'success': 'Submission aborted! Your file "%s" is too large (max size: %d MB)' %\
-                                        (inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE/(1000**2))}))
+                          json.dumps({'success': 'Submission aborted! Your file "%s" is too large (max size: %d MB)' %
+                                        (inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE / (1000 ** 2))}))
         mock_request_3 = MagicMock()
         mock_request_3.POST.copy.return_value = {}
         mock_request_3.FILES = False
@@ -99,12 +102,12 @@ class ModuleRenderTestCase(PageLoader):
         inputfile_2.name = 'name'
         self.assertRaises(ItemNotFoundError, render.modx_dispatch,
                           mock_request_3, 'dummy', self.location, 'toy')
-        self.assertRaises(Http404,render.modx_dispatch, mock_request_3, 'dummy',
+        self.assertRaises(Http404, render.modx_dispatch, mock_request_3, 'dummy',
                             self.location, self.course_id)
-        mock_request_3.POST.copy.return_value = {'position':1}
+        mock_request_3.POST.copy.return_value = {'position': 1}
         self.assertIsInstance(render.modx_dispatch(mock_request_3, 'goto_position',
                             self.location, self.course_id), HttpResponse)
-        
+
     def test_get_score_bucket(self):
         self.assertEquals(render.get_score_bucket(0, 10), 'incorrect')
         self.assertEquals(render.get_score_bucket(1, 10), 'partial')
@@ -113,6 +116,7 @@ class ModuleRenderTestCase(PageLoader):
         self.assertEquals(render.get_score_bucket(11, 10), 'incorrect')
         self.assertEquals(render.get_score_bucket(-1, 10), 'incorrect')
 
+
 @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
 class TestTOC(TestCase):
     """Check the Table of Contents for a course"""
@@ -130,19 +134,19 @@ class TestTOC(TestCase):
         factory = RequestFactory()
         request = factory.get(chapter_url)
 
-        expected = ([{'active': True, 'sections': 
-                    [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True, 
-                    'format': u'Lecture Sequence', 'due': '', 'active': False}, 
-                    {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True, 
-                    'format': '', 'due': '', 'active': False}, 
-                    {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True, 
-                    'format': '', 'due': '', 'active': False}, 
-                    {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True, 
-                    'format': '', 'due': '', 'active': False}], 
-                    'url_name': 'Overview', 'display_name': u'Overview'}, 
-                    {'active': False, 'sections': 
-                    [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True, 
-                    'format': '', 'due': '', 'active': False}], 
+        expected = ([{'active': True, 'sections':
+                    [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True,
+                    'format': u'Lecture Sequence', 'due': '', 'active': False},
+                    {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True,
+                    'format': '', 'due': '', 'active': False},
+                    {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True,
+                    'format': '', 'due': '', 'active': False},
+                    {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True,
+                    'format': '', 'due': '', 'active': False}],
+                    'url_name': 'Overview', 'display_name': u'Overview'},
+                    {'active': False, 'sections':
+                    [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True,
+                    'format': '', 'due': '', 'active': False}],
                     'url_name': 'secret:magic', 'display_name': 'secret:magic'}])
 
         actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, None)
@@ -155,21 +159,20 @@ class TestTOC(TestCase):
         factory = RequestFactory()
         request = factory.get(chapter_url)
 
-        expected = ([{'active': True, 'sections': 
-                    [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True, 
-                    'format': u'Lecture Sequence', 'due': '', 'active': False}, 
-                    {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True, 
-                    'format': '', 'due': '', 'active': True}, 
-                    {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True, 
-                    'format': '', 'due': '', 'active': False}, 
-                    {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True, 
-                    'format': '', 'due': '', 'active': False}], 
-                    'url_name': 'Overview', 'display_name': u'Overview'}, 
-                    {'active': False, 'sections': 
-                    [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True, 
-                    'format': '', 'due': '', 'active': False}], 
+        expected = ([{'active': True, 'sections':
+                    [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True,
+                    'format': u'Lecture Sequence', 'due': '', 'active': False},
+                    {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True,
+                    'format': '', 'due': '', 'active': True},
+                    {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True,
+                    'format': '', 'due': '', 'active': False},
+                    {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True,
+                    'format': '', 'due': '', 'active': False}],
+                    'url_name': 'Overview', 'display_name': u'Overview'},
+                    {'active': False, 'sections':
+                    [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True,
+                    'format': '', 'due': '', 'active': False}],
                     'url_name': 'secret:magic', 'display_name': 'secret:magic'}])
 
         actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, section)
         self.assertEqual(expected, actual)
-
diff --git a/lms/djangoapps/courseware/tests/test_progress.py b/lms/djangoapps/courseware/tests/test_progress.py
index 44a0f0cb30..a70cbe4b9a 100644
--- a/lms/djangoapps/courseware/tests/test_progress.py
+++ b/lms/djangoapps/courseware/tests/test_progress.py
@@ -1,58 +1,57 @@
-from django.test import TestCase 
+from django.test import TestCase
 from courseware import progress
 from mock import MagicMock
 
 
-
 class ProgessTests(TestCase):
-	def setUp(self):
+        def setUp(self):
 
-		self.d = dict({'duration_total': 0,
-		     'duration_watched': 0,
-		     'done': True,
-		     'questions_correct': 4,
-		     'questions_incorrect': 0,
-		     'questions_total': 0})
+                self.d = dict({'duration_total': 0,
+            'duration_watched': 0,
+            'done': True,
+            'questions_correct': 4,
+            'questions_incorrect': 0,
+            'questions_total': 0})
 
-		self.c = progress.completion()
-		self.c2= progress.completion()
-		self.c2.dict = dict({'duration_total': 0,
-		     'duration_watched': 0,
-		     'done': True,
-		     'questions_correct': 2,
-		     'questions_incorrect': 1,
-		     'questions_total': 0})
+                self.c = progress.completion()
+                self.c2 = progress.completion()
+                self.c2.dict = dict({'duration_total': 0,
+            'duration_watched': 0,
+            'done': True,
+            'questions_correct': 2,
+            'questions_incorrect': 1,
+            'questions_total': 0})
 
-		self.cplusc2 = dict({'duration_total': 0,
-		     'duration_watched': 0,
-		     'done': True,
-		     'questions_correct': 2,
-		     'questions_incorrect': 1,
-		     'questions_total': 0})
+                self.cplusc2 = dict({'duration_total': 0,
+            'duration_watched': 0,
+            'done': True,
+            'questions_correct': 2,
+            'questions_incorrect': 1,
+            'questions_total': 0})
 
-		self.oth = dict({'duration_total': 0,
-		     'duration_watched': 0,
-		     'done': True,
-		     'questions_correct': 4,
-		     'questions_incorrect': 0,
-		     'questions_total': 7})
+                self.oth = dict({'duration_total': 0,
+            'duration_watched': 0,
+            'done': True,
+            'questions_correct': 4,
+            'questions_incorrect': 0,
+            'questions_total': 7})
 
-		self.x = MagicMock()
-		self.x.dict = self.oth
+                self.x = MagicMock()
+                self.x.dict = self.oth
 
-		self.d_oth = {'duration_total': 0,
-	     'duration_watched': 0,
-	     'done': True,
-	     'questions_correct': 4,
-	     'questions_incorrect': 0,
-	     'questions_total': 7}
+                self.d_oth = {'duration_total': 0,
+            'duration_watched': 0,
+            'done': True,
+            'questions_correct': 4,
+            'questions_incorrect': 0,
+            'questions_total': 7}
 
-	def test_getitem(self):
-		self.assertEqual(self.c.__getitem__('duration_watched'), 0)
+        def test_getitem(self):
+                self.assertEqual(self.c.__getitem__('duration_watched'), 0)
 
-	def test_setitem(self):
-		self.c.__setitem__('questions_correct', 4)
-		self.assertEqual(str(self.c),str(self.d))
+        def test_setitem(self):
+                self.c.__setitem__('questions_correct', 4)
+                self.assertEqual(str(self.c), str(self.d))
 
-	def test_repr(self):
-		self.assertEqual(self.c.__repr__(), str(progress.completion()))
+        def test_repr(self):
+                self.assertEqual(self.c.__repr__(), str(progress.completion()))
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index cb15b5b7b6..eeac999813 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -15,12 +15,13 @@ from django.test.client import RequestFactory
 from student.models import CourseEnrollment
 from xmodule.modulestore.django import modulestore, _MODULESTORES
 from xmodule.modulestore.exceptions import InvalidLocationError,\
-     ItemNotFoundError, NoPathToItem
+                                ItemNotFoundError, NoPathToItem
 import courseware.views as views
 from xmodule.modulestore import Location
 
 from factories import UserFactory
 
+
 class Stub():
     pass
 
@@ -28,18 +29,19 @@ class Stub():
 # This part is required for modulestore() to work properly
 def xml_store_config(data_dir):
     return {
-    'default': {
-        'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore',
-        'OPTIONS': {
-            'data_dir': data_dir,
-            'default_class': 'xmodule.hidden_module.HiddenDescriptor',
+        'default': {
+            'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore',
+            'OPTIONS': {
+                'data_dir': data_dir,
+                'default_class': 'xmodule.hidden_module.HiddenDescriptor',
+            }
         }
     }
-}
 
 TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
 TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
 
+
 @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
 class TestJumpTo(TestCase):
     """Check the jumpto link for a course"""
@@ -64,15 +66,16 @@ class TestJumpTo(TestCase):
         response = self.client.get(jumpto_url)
         self.assertRedirects(response, expected, status_code=302, target_status_code=302)
 
+
 class ViewsTestCase(TestCase):
     def setUp(self):
         self.user = User.objects.create(username='dummy', password='123456',
                                         email='test@mit.edu')
-        self.date = datetime.datetime(2013,1,22)
+        self.date = datetime.datetime(2013, 1, 22)
         self.course_id = 'edX/toy/2012_Fall'
-        self.enrollment = CourseEnrollment.objects.get_or_create(user = self.user,
-                                                  course_id = self.course_id,
-                                                  created = self.date)[0]
+        self.enrollment = CourseEnrollment.objects.get_or_create(user=self.user,
+                                                  course_id=self.course_id,
+                                                  created=self.date)[0]
         self.location = ['tag', 'org', 'course', 'category', 'name']
         self._MODULESTORES = {}
         # This is a CourseDescriptor object
@@ -85,13 +88,13 @@ class ViewsTestCase(TestCase):
         # depreciated function
         mock_user = MagicMock()
         mock_user.is_authenticated.return_value = False
-        self.assertEquals(views.user_groups(mock_user),[])
-        
+        self.assertEquals(views.user_groups(mock_user), [])
+
     def test_get_current_child(self):
         self.assertIsNone(views.get_current_child(Stub()))
         mock_xmodule = MagicMock()
         mock_xmodule.position = -1
-        mock_xmodule.get_display_items.return_value = ['one','two']
+        mock_xmodule.get_display_items.return_value = ['one', 'two']
         self.assertEquals(views.get_current_child(mock_xmodule), 'one')
         mock_xmodule_2 = MagicMock()
         mock_xmodule_2.position = 3
diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py
index fb6842d4a9..7e00baf61f 100644
--- a/lms/djangoapps/courseware/tests/tests.py
+++ b/lms/djangoapps/courseware/tests/tests.py
@@ -53,46 +53,46 @@ def registration(email):
 
 def mongo_store_config(data_dir):
     return {
-    'default': {
-        'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
-        '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',
+        'default': {
+            'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
+            '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 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',
+        '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': {
-        'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore',
-        'OPTIONS': {
-            'data_dir': data_dir,
-            'default_class': 'xmodule.hidden_module.HiddenDescriptor',
+        'default': {
+            'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore',
+            'OPTIONS': {
+                'data_dir': data_dir,
+                'default_class': 'xmodule.hidden_module.HiddenDescriptor',
+            }
         }
     }
-}
 
 TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
 TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
@@ -115,8 +115,7 @@ class ActivateLoginTestCase(TestCase):
                          'Response status code was {0} instead of 302'.format(response.status_code))
         url = response['Location']
 
-        e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(
-                                                              expected_url)
+        e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url)
         if not (e_scheme or e_netloc):
             expected_url = urlunsplit(('http', 'testserver', e_path,
                 e_query, e_fragment))
@@ -211,7 +210,7 @@ class PageLoader(ActivateLoginTestCase):
         resp = self.client.post('/change_enrollment', {
             'enrollment_action': 'enroll',
             'course_id': course.id,
-            })
+        })
         return parse_json(resp)
 
     def try_enroll(self, course):
@@ -230,11 +229,10 @@ class PageLoader(ActivateLoginTestCase):
         resp = self.client.post('/change_enrollment', {
             'enrollment_action': 'unenroll',
             'course_id': course.id,
-            })
+        })
         data = parse_json(resp)
         self.assertTrue(data['success'])
 
-
     def check_for_get_code(self, code, url):
         """
         Check that we got the expected code when accessing url via GET.
@@ -246,7 +244,6 @@ class PageLoader(ActivateLoginTestCase):
                          .format(resp.status_code, url, code))
         return resp
 
-
     def check_for_post_code(self, code, url, data={}):
         """
         Check that we got the expected code when accessing url via POST.
@@ -258,12 +255,8 @@ class PageLoader(ActivateLoginTestCase):
                          .format(resp.status_code, url, code))
         return resp
 
-
-
     def check_pages_load(self, module_store):
         """Make all locations in course load"""
-
-
        # enroll in the course before trying to access pages
         courses = module_store.get_courses()
         self.assertEqual(len(courses), 1)
@@ -316,7 +309,7 @@ class PageLoader(ActivateLoginTestCase):
                 msg = str(resp.status_code)
 
                 if resp.status_code != 200:
-                    msg = "ERROR " + msg  + ": " + descriptor.location.url()
+                    msg = "ERROR " + msg + ": " + descriptor.location.url()
                     all_ok = False
                     num_bad += 1
                 elif resp.redirect_chain[0][1] != 302:
@@ -344,7 +337,6 @@ class PageLoader(ActivateLoginTestCase):
         self.assertTrue(all_ok)
 
 
-
 @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
 class TestCoursesLoadTestCase_XmlModulestore(PageLoader):
     '''Check that all pages in test courses load properly'''
@@ -355,21 +347,21 @@ class TestCoursesLoadTestCase_XmlModulestore(PageLoader):
 
     def test_toy_course_loads(self):
         module_store = XMLModuleStore(
-                                      TEST_DATA_DIR,
-                                      default_class='xmodule.hidden_module.HiddenDescriptor',
-                                      course_dirs=['toy'],
-                                      load_error_modules=True,
-                                      )
+                                        TEST_DATA_DIR,
+                                        default_class='xmodule.hidden_module.HiddenDescriptor',
+                                        course_dirs=['toy'],
+                                        load_error_modules=True,
+        )
 
         self.check_pages_load(module_store)
 
     def test_full_course_loads(self):
         module_store = XMLModuleStore(
-                                      TEST_DATA_DIR,
-                                      default_class='xmodule.hidden_module.HiddenDescriptor',
-                                      course_dirs=['full'],
-                                      load_error_modules=True,
-                                      )
+                                        TEST_DATA_DIR,
+                                        default_class='xmodule.hidden_module.HiddenDescriptor',
+                                        course_dirs=['full'],
+                                        load_error_modules=True,
+        )
         self.check_pages_load(module_store)
 
 
@@ -525,7 +517,6 @@ class TestViewAuth(PageLoader):
             print 'checking for 404 on {0}'.format(url)
             self.check_for_get_code(404, url)
 
-
         # now also make the instructor staff
         u = user(self.instructor)
         u.is_staff = True
@@ -536,7 +527,6 @@ class TestViewAuth(PageLoader):
             print 'checking for 200 on {0}'.format(url)
             self.check_for_get_code(200, url)
 
-
     def run_wrapped(self, test):
         """
         test.py turns off start dates.  Enable them.
@@ -552,7 +542,6 @@ class TestViewAuth(PageLoader):
         finally:
             settings.MITX_FEATURES['DISABLE_START_DATES'] = oldDSD
 
-
     def test_dark_launch(self):
         """Make sure that before course start, students can't access course
         pages, but instructors can"""
@@ -646,7 +635,6 @@ class TestViewAuth(PageLoader):
             url = reverse_urls(['courseware'], course)[0]
             self.check_for_get_code(302, url)
 
-
         # First, try with an enrolled student
         print '=== Testing student access....'
         self.login(self.student, self.password)
@@ -761,7 +749,6 @@ class TestViewAuth(PageLoader):
         self.assertTrue(has_access(student_user, self.toy, 'load'))
 
 
-
 @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
 class TestCourseGrader(PageLoader):
     """Check that a course gets graded properly"""
@@ -832,13 +819,12 @@ class TestCourseGrader(PageLoader):
                             kwargs={
                                 'course_id': self.graded_course.id,
                                 'location': problem_location,
-                                'dispatch': 'problem_check', }
-                          )
+                                'dispatch': 'problem_check', })
 
         resp = self.client.post(modx_url, {
             'input_i4x-edX-graded-problem-{0}_2_1'.format(problem_url_name): responses[0],
             'input_i4x-edX-graded-problem-{0}_2_2'.format(problem_url_name): responses[1],
-            })
+        })
         print "modx_url", modx_url, "responses", responses
         print "resp", resp
 
@@ -854,8 +840,7 @@ class TestCourseGrader(PageLoader):
                             kwargs={
                                 'course_id': self.graded_course.id,
                                 'location': problem_location,
-                                'dispatch': 'problem_reset', }
-                          )
+                                'dispatch': 'problem_reset', })
 
         resp = self.client.post(modx_url)
         return resp

From 5bbb70ef8ace8228f4ae4fddd120effd50eaebff Mon Sep 17 00:00:00 2001
From: Arthur Barrett 
Date: Tue, 5 Mar 2013 13:47:02 -0500
Subject: [PATCH 95/98] Advanced modules must now be specified individually in
 order to enable them on the 'advanced' component menu.

---
 cms/djangoapps/contentstore/views.py | 32 ++++++++++++----------------
 1 file changed, 14 insertions(+), 18 deletions(-)

diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index 92cced6106..d189e18216 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -69,10 +69,8 @@ log = logging.getLogger(__name__)
 
 COMPONENT_TYPES = ['customtag', 'discussion', 'html', 'problem', 'video']
 
-ADVANCED_COMPONENT_TYPES = {
-    'advanced' : ['annotatable','combinedopenended', 'peergrading']
-}
-
+ADVANCED_COMPONENT_TYPES = ['annotatable','combinedopenended', 'peergrading']
+ADVANCED_COMPONENT_CATEGORY = 'advanced'
 ADVANCED_COMPONENT_POLICY_KEY = 'advanced_modules'
 
 # cdodge: these are categories which should not be parented, they are detached from the hierarchy
@@ -288,11 +286,13 @@ def edit_unit(request, location):
 
     component_templates = defaultdict(list)
 
-    # check if there are any advanced modules specified in the course policy
+    # Check if there are any advanced modules specified in the course policy. These modules
+    # should be specified as a list of strings, where the strings are the names of the modules
+    # in ADVANCED_COMPONENT_TYPES that should be enabled for the course.
     course_metadata = CourseMetadata.fetch(course.location)
     course_advanced_keys = course_metadata.get(ADVANCED_COMPONENT_POLICY_KEY, [])
 
-    # check if the keys are in JSON format, or perhaps a literal python expression
+    # We expect the advanced keys to be a *list* of strings, but if it is a JSON-encoded string, attempt to parse it.
     if isinstance(course_advanced_keys, basestring):
         # Are you JSON?
         try:
@@ -307,28 +307,24 @@ def edit_unit(request, location):
             log.error("Cannot parse course advanced policy at all: {0}".format(course_advanced_keys))
             course_advanced_keys=[]
 
-    #Set component types according to course policy file
-    component_types = COMPONENT_TYPES
+    # Set component types according to course policy file
+    component_types = list(COMPONENT_TYPES)
     if isinstance(course_advanced_keys, list):
-        #Generate a subset of the dictionary for just needed keys
         course_advanced_keys = [c for c in course_advanced_keys if c in ADVANCED_COMPONENT_TYPES]
-        advanced_component_type_mappings = {k: ADVANCED_COMPONENT_TYPES.get(k,[]) for k in course_advanced_keys}
-        #Let course staff defined keys be valid
-        component_types+=course_advanced_keys
+        if len(course_advanced_keys) > 0:
+            component_types.append(ADVANCED_COMPONENT_CATEGORY)
     else:
         log.error("Improper format for course advanced keys! {0}".format(course_advanced_keys))
 
     templates = modulestore().get_items(Location('i4x', 'edx', 'templates'))
     for template in templates:
         category = template.location.category
-        #Map subcategory to upper level category
-        for key in course_advanced_keys:
-            if category in advanced_component_type_mappings[key]:
-                category = key
-                break
+
+        if category in course_advanced_keys:
+            category = ADVANCED_COMPONENT_CATEGORY
 
         if category in component_types:
-			#This is a hack to create categories for different xmodules
+            #This is a hack to create categories for different xmodules
             component_templates[category].append((
                 template.display_name,
                 template.location.url(),

From 9475c6edacff878dbf3d56157dda6359b0d44312 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri 
Date: Tue, 5 Mar 2013 14:41:08 -0500
Subject: [PATCH 96/98] Address review comments

---
 common/lib/xmodule/xmodule/peer_grading_module.py | 2 --
 lms/djangoapps/open_ended_grading/views.py        | 7 ++++++-
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py
index 067834a7a0..3eb18300c3 100644
--- a/common/lib/xmodule/xmodule/peer_grading_module.py
+++ b/common/lib/xmodule/xmodule/peer_grading_module.py
@@ -456,7 +456,6 @@ class PeerGradingModule(XModule):
         try:
             problem_list_json = self.peer_gs.get_problem_list(self.system.course_id, self.system.anonymous_student_id)
             problem_list_dict = problem_list_json
-            log.debug(problem_list_dict)
             success = problem_list_dict['success']
             if 'error' in problem_list_dict:
                 error_text = problem_list_dict['error']
@@ -593,7 +592,6 @@ class PeerGradingDescriptor(XmlDescriptor, EditingDescriptor):
         'task_xml': dictionary of xml strings,
         }
         """
-        log.debug("In definition")
         expected_children = []
         for child in expected_children:
             if len(xml_object.xpath(child)) == 0:
diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py
index beae034105..55e8088c3f 100644
--- a/lms/djangoapps/open_ended_grading/views.py
+++ b/lms/djangoapps/open_ended_grading/views.py
@@ -89,18 +89,23 @@ def peer_grading(request, course_id):
     Show a peer grading interface
     '''
 
+    #Get the current course
     course = get_course_with_access(request.user, course_id, 'load')
     course_id_parts = course.id.split("/")
     false_dict = [False,"False", "false", "FALSE"]
 
+    #Reverse the base course url
     base_course_url  = reverse('courses')
     try:
         #TODO:  This will not work with multiple runs of a course.  Make it work.  The last key in the Location passed
         #to get_items is called revision.  Is this the same as run?
+        #Get the peer grading modules currently in the course
         items = modulestore().get_items(['i4x', None, course_id_parts[1], 'peergrading', None])
+        #See if any of the modules are centralized modules (ie display info from multiple problems)
         items = [i for i in items if i.metadata.get("use_for_single_location", True) in false_dict]
+        #Get the first one
         item_location = items[0].location
-        item_location_url = item_location.url()
+        #Generate a url for the first module and redirect the user to it
         problem_url_parts = search.path_to_location(modulestore(), course.id, item_location)
         problem_url = generate_problem_url(problem_url_parts, base_course_url)
 

From 102c594e7b9f24978c3854ac12971ed997713b19 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri 
Date: Tue, 5 Mar 2013 15:01:12 -0500
Subject: [PATCH 97/98] Remove string and old import

---
 cms/djangoapps/contentstore/views.py | 16 ----------------
 1 file changed, 16 deletions(-)

diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index d189e18216..14d1ee83ed 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -1,6 +1,5 @@
 from util.json_request import expect_json
 import json
-import ast
 import logging
 import os
 import sys
@@ -292,21 +291,6 @@ def edit_unit(request, location):
     course_metadata = CourseMetadata.fetch(course.location)
     course_advanced_keys = course_metadata.get(ADVANCED_COMPONENT_POLICY_KEY, [])
 
-    # We expect the advanced keys to be a *list* of strings, but if it is a JSON-encoded string, attempt to parse it.
-    if isinstance(course_advanced_keys, basestring):
-        # Are you JSON?
-        try:
-            course_advanced_keys = json.loads(course_advanced_keys)
-        except:
-            log.error("Cannot JSON decode course advanced policy: {0}".format(course_advanced_keys))
-            # Not JSON? How about Python?
-        try:
-            #This is a safe evaluation.  See docs for ast
-            course_advanced_keys = ast.literal_eval(course_advanced_keys)
-        except:
-            log.error("Cannot parse course advanced policy at all: {0}".format(course_advanced_keys))
-            course_advanced_keys=[]
-
     # Set component types according to course policy file
     component_types = list(COMPONENT_TYPES)
     if isinstance(course_advanced_keys, list):

From cde4cdf839ba7c04a74eadb4330ca08ea2a37a4f Mon Sep 17 00:00:00 2001
From: Brian Wilson 
Date: Tue, 5 Mar 2013 17:51:44 -0500
Subject: [PATCH 98/98] starting pdfbook doc

---
 doc/public/course_data_formats/course_xml.rst | 43 ++++++++++++++++++-
 1 file changed, 41 insertions(+), 2 deletions(-)

diff --git a/doc/public/course_data_formats/course_xml.rst b/doc/public/course_data_formats/course_xml.rst
index 51c5f2a872..fe25aa92f2 100644
--- a/doc/public/course_data_formats/course_xml.rst
+++ b/doc/public/course_data_formats/course_xml.rst
@@ -357,6 +357,8 @@ Supported fields at the course level
           * `cohorted_discussions`: list of discussions that should be cohorted.  Any not specified in this list are not cohorted.
           * `auto_cohort`: Truthy.
           * `auto_cohort_groups`: `["group name 1", "group name 2", ...]` If `cohorted` and `auto_cohort` is true, automatically put each student into a random group from the `auto_cohort_groups` list, creating the group if needed.  
+     * - `pdf_textbooks`
+       - have pdf-based textbooks on tabs in the courseware.  See below for details on config.
 
 
 Available metadata
@@ -508,13 +510,15 @@ If you want to customize the courseware tabs displayed for your course, specify
       "url_slug": "news",
       "name": "Exciting news"
     },
-    {"type": "textbooks"}
+    {"type": "textbooks"},
+    {"type": "pdf_textbooks"}
   ]
 
 * If you specify any tabs, you must specify all tabs.  They will appear in the order given.
 * The first two tabs must have types `"courseware"` and `"course_info"`, in that order, or the course will not load.
 * The `courseware` tab never has a name attribute -- it's always rendered as "Courseware" for consistency between courses.
 * The `textbooks` tab will actually generate one tab per textbook, using the textbook titles as names.
+* The `pdf_textbooks` tab will actually generate one tab per pdf_textbook.  The tab name is found in the pdf textbook definition.
 * For static tabs, the `url_slug` will be the url that points to the tab.  It can not be one of the existing courseware url types (even if those aren't used in your course).  The static content will come from `tabs/{course_url_name}/{url_slug}.html`, or `tabs/{url_slug}.html` if that doesn't exist.
 * An Instructor tab will be automatically added at the end for course staff users.
 
@@ -527,13 +531,15 @@ If you want to customize the courseware tabs displayed for your course, specify
    * - `course_info`
      - Parameter `name`.
    * - `wiki`
-     - arameter `name`.
+     - Parameter `name`.
    * - `discussion`
      - Parameter `name`.
    * - `external_link`
      - Parameters `name`, `link`.
    * - `textbooks`
      - No parameters--generates tab names from book titles.
+   * - `pdf_textbooks`
+     - No parameters--generates tab names from pdf book definition.  (See discussion below for configuration.)
    * - `progress`
      - Parameter `name`.
    * - `static_tab`
@@ -541,6 +547,39 @@ If you want to customize the courseware tabs displayed for your course, specify
    * - `staff_grading`
      - No parameters.  If specified, displays the staff grading tab for instructors.
 
+*********
+Textbooks
+*********
+Support is currently provided for image-based and PDF-based textbooks.  
+
+Image-based Textbooks
+^^^^^^^^^^^^^^^^^^^^^
+
+TBD.
+
+PDF-based Textbooks
+^^^^^^^^^^^^^^^^^^^
+
+PDF-based textbooks are configured at the course level in the policy file.  The JSON markup consists of an array of maps, with each map corresponding to a separate textbook.  There are two styles to presenting PDF-based material.  The first way is as a single PDF on a tab, which requires only a tab title and a URL for configuration.  A second way permits the display of multiple PDFs that should be displayed together on a single view. For this view, a side panel of links is available on the left, allowing selection of a particular PDF to view.  
+
+.. code-block:: json
+
+        "pdf_textbooks": [ 
+          {"tab_title": "Textbook 1", 
+	   "url": "https://www.example.com/book1.pdf" },
+          {"tab_title": "Textbook 2", 
+	   "chapters": [
+               { "title": "Chapter 1", "url": "https://www.example.com/Chapter1.pdf" },
+               { "title": "Chapter 2", "url": "https://www.example.com/Chapter2.pdf" },
+               { "title": "Chapter 3", "url": "https://www.example.com/Chapter3.pdf" },
+               { "title": "Chapter 4", "url": "https://www.example.com/Chapter4.pdf" },
+               { "title": "Chapter 5", "url": "https://www.example.com/Chapter5.pdf" },
+               { "title": "Chapter 6", "url": "https://www.example.com/Chapter6.pdf" },
+               { "title": "Chapter 7", "url": "https://www.example.com/Chapter7.pdf" }
+	       ]
+	  }
+        ]
+
 *************************************
 Other file locations (info and about)
 *************************************