Commit a79a2907 by Victor Shnayder

Merge pull request #1755 from MITx/fix/brian/fix500v1

Fix/brian/fix500v1
parents 6d63d13f c639799a
...@@ -3,13 +3,11 @@ from django.test.utils import override_settings ...@@ -3,13 +3,11 @@ from django.test.utils import override_settings
import xmodule.modulestore.django import xmodule.modulestore.django
from courseware.tests.tests import PageLoader, TEST_DATA_XML_MODULESTORE from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.xml_importer import import_from_xml
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class WikiRedirectTestCase(PageLoader): class WikiRedirectTestCase(LoginEnrollmentTestCase):
def setUp(self): def setUp(self):
xmodule.modulestore.django._MODULESTORES = {} xmodule.modulestore.django._MODULESTORES = {}
courses = modulestore().get_courses() courses = modulestore().get_courses()
...@@ -30,8 +28,6 @@ class WikiRedirectTestCase(PageLoader): ...@@ -30,8 +28,6 @@ class WikiRedirectTestCase(PageLoader):
self.activate_user(self.student) self.activate_user(self.student)
self.activate_user(self.instructor) self.activate_user(self.instructor)
def test_wiki_redirect(self): def test_wiki_redirect(self):
""" """
Test that requesting wiki URLs redirect properly to or out of classes. Test that requesting wiki URLs redirect properly to or out of classes.
...@@ -69,7 +65,6 @@ class WikiRedirectTestCase(PageLoader): ...@@ -69,7 +65,6 @@ class WikiRedirectTestCase(PageLoader):
self.assertEqual(resp.status_code, 302) self.assertEqual(resp.status_code, 302)
self.assertEqual(resp['Location'], 'http://testserver' + destination) self.assertEqual(resp['Location'], 'http://testserver' + destination)
def create_course_page(self, course): def create_course_page(self, course):
""" """
Test that loading the course wiki page creates the wiki page. Test that loading the course wiki page creates the wiki page.
...@@ -98,7 +93,6 @@ class WikiRedirectTestCase(PageLoader): ...@@ -98,7 +93,6 @@ class WikiRedirectTestCase(PageLoader):
self.assertTrue("course info" in resp.content.lower()) self.assertTrue("course info" in resp.content.lower())
self.assertTrue("courseware" in resp.content.lower()) self.assertTrue("courseware" in resp.content.lower())
def test_course_navigator(self): def test_course_navigator(self):
"""" """"
Test that going from a course page to a wiki page contains the course navigator. Test that going from a course page to a wiki page contains the course navigator.
...@@ -108,7 +102,6 @@ class WikiRedirectTestCase(PageLoader): ...@@ -108,7 +102,6 @@ class WikiRedirectTestCase(PageLoader):
self.enroll(self.toy) self.enroll(self.toy)
self.create_course_page(self.toy) self.create_course_page(self.toy)
course_wiki_page = reverse('wiki:get', kwargs={'path': self.toy.wiki_slug + '/'}) course_wiki_page = reverse('wiki:get', kwargs={'path': self.toy.wiki_slug + '/'})
referer = reverse("courseware", kwargs={'course_id': self.toy.id}) referer = reverse("courseware", kwargs={'course_id': self.toy.id})
......
...@@ -2,16 +2,16 @@ from mock import MagicMock ...@@ -2,16 +2,16 @@ from mock import MagicMock
import json import json
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
from django.core.urlresolvers import reverse
from django.conf import settings from django.conf import settings
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from django.core.urlresolvers import reverse
from django.test.utils import override_settings from django.test.utils import override_settings
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
import courseware.module_render as render
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from courseware.tests.tests import PageLoader import courseware.module_render as render
from courseware.tests.tests import LoginEnrollmentTestCase
from courseware.model_data import ModelDataCache from courseware.model_data import ModelDataCache
from .factories import UserFactory from .factories import UserFactory
...@@ -38,7 +38,7 @@ TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) ...@@ -38,7 +38,7 @@ TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class ModuleRenderTestCase(PageLoader): class ModuleRenderTestCase(LoginEnrollmentTestCase):
def setUp(self): def setUp(self):
self.location = ['i4x', 'edX', 'toy', 'chapter', 'Overview'] self.location = ['i4x', 'edX', 'toy', 'chapter', 'Overview']
self.course_id = 'edX/toy/2012_Fall' self.course_id = 'edX/toy/2012_Fall'
...@@ -54,10 +54,9 @@ class ModuleRenderTestCase(PageLoader): ...@@ -54,10 +54,9 @@ class ModuleRenderTestCase(PageLoader):
mock_request = MagicMock() mock_request = MagicMock()
mock_request.FILES.keys.return_value = ['file_id'] 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, self.assertEquals(render.modx_dispatch(mock_request, 'dummy', self.location, 'dummy').content,
'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}))
settings.MAX_FILEUPLOADS_PER_INPUT}))
mock_request_2 = MagicMock() mock_request_2 = MagicMock()
mock_request_2.FILES.keys.return_value = ['file_id'] mock_request_2.FILES.keys.return_value = ['file_id']
inputfile = Stub() inputfile = Stub()
...@@ -68,7 +67,7 @@ class ModuleRenderTestCase(PageLoader): ...@@ -68,7 +67,7 @@ class ModuleRenderTestCase(PageLoader):
self.assertEquals(render.modx_dispatch(mock_request_2, 'dummy', self.location, self.assertEquals(render.modx_dispatch(mock_request_2, 'dummy', self.location,
'dummy').content, 'dummy').content,
json.dumps({'success': 'Submission aborted! Your file "%s" is too large (max size: %d MB)' % json.dumps({'success': 'Submission aborted! Your file "%s" is too large (max size: %d MB)' %
(inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE / (1000 ** 2))})) (inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE / (1000 ** 2))}))
mock_request_3 = MagicMock() mock_request_3 = MagicMock()
mock_request_3.POST.copy.return_value = {} mock_request_3.POST.copy.return_value = {}
mock_request_3.FILES = False mock_request_3.FILES = False
...@@ -79,10 +78,10 @@ class ModuleRenderTestCase(PageLoader): ...@@ -79,10 +78,10 @@ class ModuleRenderTestCase(PageLoader):
self.assertRaises(ItemNotFoundError, render.modx_dispatch, self.assertRaises(ItemNotFoundError, render.modx_dispatch,
mock_request_3, 'dummy', self.location, 'toy') 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) 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.assertIsInstance(render.modx_dispatch(mock_request_3, 'goto_position',
self.location, self.course_id), HttpResponse) self.location, self.course_id), HttpResponse)
def test_get_score_bucket(self): def test_get_score_bucket(self):
self.assertEquals(render.get_score_bucket(0, 10), 'incorrect') self.assertEquals(render.get_score_bucket(0, 10), 'incorrect')
...@@ -124,19 +123,19 @@ class TestTOC(TestCase): ...@@ -124,19 +123,19 @@ class TestTOC(TestCase):
self.toy_course.id, self.portal_user, self.toy_course, depth=2) self.toy_course.id, self.portal_user, self.toy_course, depth=2)
expected = ([{'active': True, 'sections': expected = ([{'active': True, 'sections':
[{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True, [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True,
'format': u'Lecture Sequence', 'due': '', 'active': False}, 'format': u'Lecture Sequence', 'due': '', 'active': False},
{'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True, {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True,
'format': '', 'due': '', 'active': False}, 'format': '', 'due': '', 'active': False},
{'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True, {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True,
'format': '', 'due': '', 'active': False}, 'format': '', 'due': '', 'active': False},
{'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True, {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True,
'format': '', 'due': '', 'active': False}], 'format': '', 'due': '', 'active': False}],
'url_name': 'Overview', 'display_name': u'Overview'}, 'url_name': 'Overview', 'display_name': u'Overview'},
{'active': False, 'sections': {'active': False, 'sections':
[{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True, [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True,
'format': '', 'due': '', 'active': False}], 'format': '', 'due': '', 'active': False}],
'url_name': 'secret:magic', 'display_name': 'secret:magic'}]) 'url_name': 'secret:magic', 'display_name': 'secret:magic'}])
actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, None, model_data_cache) actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, None, model_data_cache)
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
...@@ -151,19 +150,19 @@ class TestTOC(TestCase): ...@@ -151,19 +150,19 @@ class TestTOC(TestCase):
self.toy_course.id, self.portal_user, self.toy_course, depth=2) self.toy_course.id, self.portal_user, self.toy_course, depth=2)
expected = ([{'active': True, 'sections': expected = ([{'active': True, 'sections':
[{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True, [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True,
'format': u'Lecture Sequence', 'due': '', 'active': False}, 'format': u'Lecture Sequence', 'due': '', 'active': False},
{'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True, {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True,
'format': '', 'due': '', 'active': True}, 'format': '', 'due': '', 'active': True},
{'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True, {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True,
'format': '', 'due': '', 'active': False}, 'format': '', 'due': '', 'active': False},
{'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True, {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True,
'format': '', 'due': '', 'active': False}], 'format': '', 'due': '', 'active': False}],
'url_name': 'Overview', 'display_name': u'Overview'}, 'url_name': 'Overview', 'display_name': u'Overview'},
{'active': False, 'sections': {'active': False, 'sections':
[{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True, [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True,
'format': '', 'due': '', 'active': False}], 'format': '', 'due': '', 'active': False}],
'url_name': 'secret:magic', 'display_name': 'secret:magic'}]) 'url_name': 'secret:magic', 'display_name': 'secret:magic'}])
actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, section, model_data_cache) actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, section, model_data_cache)
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
import logging import logging
log = logging.getLogger("mitx." + __name__)
import json import json
import time import time
from urlparse import urlsplit, urlunsplit from urlparse import urlsplit, urlunsplit
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
...@@ -29,29 +26,30 @@ from xmodule.modulestore.django import modulestore ...@@ -29,29 +26,30 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.modulestore.xml import XMLModuleStore from xmodule.modulestore.xml import XMLModuleStore
from xmodule.timeparse import stringify_time
log = logging.getLogger("mitx." + __name__)
def parse_json(response): def parse_json(response):
"""Parse response, which is assumed to be json""" """Parse response, which is assumed to be json"""
return json.loads(response.content) return json.loads(response.content)
def user(email): def get_user(email):
'''look up a user by email''' '''look up a user by email'''
return User.objects.get(email=email) return User.objects.get(email=email)
def registration(email): def get_registration(email):
'''look up registration object by email''' '''look up registration object by email'''
return Registration.objects.get(user__email=email) return Registration.objects.get(user__email=email)
# A bit of a hack--want mongo modulestore for these tests, until
# jump_to works with the xmlmodulestore or we have an even better solution
# NOTE: this means this test requires mongo to be running.
def mongo_store_config(data_dir): def mongo_store_config(data_dir):
'''
Defines default module store using MongoModuleStore
Use of this config requires mongo to be running
'''
return { return {
'default': { 'default': {
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore', 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
...@@ -68,6 +66,7 @@ def mongo_store_config(data_dir): ...@@ -68,6 +66,7 @@ def mongo_store_config(data_dir):
def draft_mongo_store_config(data_dir): def draft_mongo_store_config(data_dir):
'''Defines default module store using DraftMongoModuleStore'''
return { return {
'default': { 'default': {
'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore', 'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore',
...@@ -84,6 +83,7 @@ def draft_mongo_store_config(data_dir): ...@@ -84,6 +83,7 @@ def draft_mongo_store_config(data_dir):
def xml_store_config(data_dir): def xml_store_config(data_dir):
'''Defines default module store using XMLModuleStore'''
return { return {
'default': { 'default': {
'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore',
...@@ -100,8 +100,8 @@ TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR) ...@@ -100,8 +100,8 @@ TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR)
TEST_DATA_DRAFT_MONGO_MODULESTORE = draft_mongo_store_config(TEST_DATA_DIR) TEST_DATA_DRAFT_MONGO_MODULESTORE = draft_mongo_store_config(TEST_DATA_DIR)
class ActivateLoginTestCase(TestCase): class LoginEnrollmentTestCase(TestCase):
'''Check that we can activate and log in''' '''Base TestCase providing support for user creation, activation, login, and course enrollment'''
def assertRedirectsNoFollow(self, response, expected_url): def assertRedirectsNoFollow(self, response, expected_url):
""" """
...@@ -117,32 +117,33 @@ class ActivateLoginTestCase(TestCase): ...@@ -117,32 +117,33 @@ class ActivateLoginTestCase(TestCase):
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): if not (e_scheme or e_netloc):
expected_url = urlunsplit(('http', 'testserver', e_path, expected_url = urlunsplit(('http', 'testserver', e_path, e_query, e_fragment))
e_query, e_fragment))
self.assertEqual(url, expected_url, "Response redirected to '{0}', expected '{1}'".format( self.assertEqual(url, expected_url, "Response redirected to '{0}', expected '{1}'".format(
url, expected_url)) url, expected_url))
def setUp(self): def setup_viewtest_user(self):
email = 'view@test.com' '''create a user account, activate, and log in'''
password = 'foo' self.viewtest_email = 'view@test.com'
self.create_account('viewtest', email, password) self.viewtest_password = 'foo'
self.activate_user(email) self.viewtest_username = 'viewtest'
self.login(email, password) self.create_account(self.viewtest_username, self.viewtest_email, self.viewtest_password)
self.activate_user(self.viewtest_email)
self.login(self.viewtest_email, self.viewtest_password)
# ============ User creation and login ============== # ============ User creation and login ==============
def _login(self, email, pw): def _login(self, email, password):
'''Login. View should always return 200. The success/fail is in the '''Login. View should always return 200. The success/fail is in the
returned json''' returned json'''
resp = self.client.post(reverse('login'), resp = self.client.post(reverse('login'),
{'email': email, 'password': pw}) {'email': email, 'password': password})
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
return resp return resp
def login(self, email, pw): def login(self, email, password):
'''Login, check that it worked.''' '''Login, check that it worked.'''
resp = self._login(email, pw) resp = self._login(email, password)
data = parse_json(resp) data = parse_json(resp)
self.assertTrue(data['success']) self.assertTrue(data['success'])
return resp return resp
...@@ -154,34 +155,34 @@ class ActivateLoginTestCase(TestCase): ...@@ -154,34 +155,34 @@ class ActivateLoginTestCase(TestCase):
self.assertEqual(resp.status_code, 302) self.assertEqual(resp.status_code, 302)
return resp return resp
def _create_account(self, username, email, pw): def _create_account(self, username, email, password):
'''Try to create an account. No error checking''' '''Try to create an account. No error checking'''
resp = self.client.post('/create_account', { resp = self.client.post('/create_account', {
'username': username, 'username': username,
'email': email, 'email': email,
'password': pw, 'password': password,
'name': 'Fred Weasley', 'name': 'Fred Weasley',
'terms_of_service': 'true', 'terms_of_service': 'true',
'honor_code': 'true', 'honor_code': 'true',
}) })
return resp return resp
def create_account(self, username, email, pw): def create_account(self, username, email, password):
'''Create the account and check that it worked''' '''Create the account and check that it worked'''
resp = self._create_account(username, email, pw) resp = self._create_account(username, email, password)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
data = parse_json(resp) data = parse_json(resp)
self.assertEqual(data['success'], True) self.assertEqual(data['success'], True)
# Check both that the user is created, and inactive # Check both that the user is created, and inactive
self.assertFalse(user(email).is_active) self.assertFalse(get_user(email).is_active)
return resp return resp
def _activate_user(self, email): def _activate_user(self, email):
'''Look up the activation key for the user, then hit the activate view. '''Look up the activation key for the user, then hit the activate view.
No error checking''' No error checking'''
activation_key = registration(email).activation_key activation_key = get_registration(email).activation_key
# and now we try to activate # and now we try to activate
resp = self.client.get(reverse('activate', kwargs={'key': activation_key})) resp = self.client.get(reverse('activate', kwargs={'key': activation_key}))
...@@ -191,19 +192,7 @@ class ActivateLoginTestCase(TestCase): ...@@ -191,19 +192,7 @@ class ActivateLoginTestCase(TestCase):
resp = self._activate_user(email) resp = self._activate_user(email)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
# Now make sure that the user is now actually activated # Now make sure that the user is now actually activated
self.assertTrue(user(email).is_active) self.assertTrue(get_user(email).is_active)
def test_activate_login(self):
'''The setup function does all the work'''
pass
def test_logout(self):
'''Setup function does login'''
self.logout()
class PageLoader(ActivateLoginTestCase):
''' Base class that adds a function to load all pages in a modulestore '''
def _enroll(self, course): def _enroll(self, course):
"""Post to the enrollment view, and return the parsed json response""" """Post to the enrollment view, and return the parsed json response"""
...@@ -240,8 +229,7 @@ class PageLoader(ActivateLoginTestCase): ...@@ -240,8 +229,7 @@ class PageLoader(ActivateLoginTestCase):
""" """
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, code, self.assertEqual(resp.status_code, code,
"got code {0} for url '{1}'. Expected code {2}" "got code {0} for url '{1}'. Expected code {2}".format(resp.status_code, url, code))
.format(resp.status_code, url, code))
return resp return resp
def check_for_post_code(self, code, url, data={}): def check_for_post_code(self, code, url, data={}):
...@@ -251,10 +239,27 @@ class PageLoader(ActivateLoginTestCase): ...@@ -251,10 +239,27 @@ class PageLoader(ActivateLoginTestCase):
""" """
resp = self.client.post(url, data) resp = self.client.post(url, data)
self.assertEqual(resp.status_code, code, self.assertEqual(resp.status_code, code,
"got code {0} for url '{1}'. Expected code {2}" "got code {0} for url '{1}'. Expected code {2}".format(resp.status_code, url, code))
.format(resp.status_code, url, code))
return resp return resp
class ActivateLoginTest(LoginEnrollmentTestCase):
'''Test logging in and logging out'''
def setUp(self):
self.setup_viewtest_user()
def test_activate_login(self):
'''Test login -- the setup function does all the work'''
pass
def test_logout(self):
'''Test logout -- setup function does login'''
self.logout()
class PageLoaderTestCase(LoginEnrollmentTestCase):
''' Base class that adds a function to load all pages in a modulestore '''
def check_pages_load(self, module_store): def check_pages_load(self, module_store):
"""Make all locations in course load""" """Make all locations in course load"""
# enroll in the course before trying to access pages # enroll in the course before trying to access pages
...@@ -264,14 +269,14 @@ class PageLoader(ActivateLoginTestCase): ...@@ -264,14 +269,14 @@ class PageLoader(ActivateLoginTestCase):
self.enroll(course) self.enroll(course)
course_id = course.id course_id = course.id
n = 0 num = 0
num_bad = 0 num_bad = 0
all_ok = True all_ok = True
for descriptor in module_store.get_items( for descriptor in module_store.get_items(
Location(None, None, None, None, None)): Location(None, None, None, None, None)):
n += 1 num += 1
print "Checking ", descriptor.location.url() print "Checking ", descriptor.location.url()
# We have ancillary course information now as modules and we can't simply use 'jump_to' to view them # We have ancillary course information now as modules and we can't simply use 'jump_to' to view them
...@@ -332,45 +337,43 @@ class PageLoader(ActivateLoginTestCase): ...@@ -332,45 +337,43 @@ class PageLoader(ActivateLoginTestCase):
print msg print msg
self.assertTrue(all_ok) # fail fast self.assertTrue(all_ok) # fail fast
print "{0}/{1} good".format(n - num_bad, n) print "{0}/{1} good".format(num - num_bad, num)
log.info("{0}/{1} good".format(n - num_bad, n)) log.info("{0}/{1} good".format(num - num_bad, num))
self.assertTrue(all_ok) self.assertTrue(all_ok)
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class TestCoursesLoadTestCase_XmlModulestore(PageLoader): class TestCoursesLoadTestCase_XmlModulestore(PageLoaderTestCase):
'''Check that all pages in test courses load properly''' '''Check that all pages in test courses load properly from XML'''
def setUp(self): def setUp(self):
ActivateLoginTestCase.setUp(self) self.setup_viewtest_user()
xmodule.modulestore.django._MODULESTORES = {} xmodule.modulestore.django._MODULESTORES = {}
def test_toy_course_loads(self): def test_toy_course_loads(self):
module_store = XMLModuleStore( module_store = XMLModuleStore(TEST_DATA_DIR,
TEST_DATA_DIR, default_class='xmodule.hidden_module.HiddenDescriptor',
default_class='xmodule.hidden_module.HiddenDescriptor', course_dirs=['toy'],
course_dirs=['toy'], load_error_modules=True,
load_error_modules=True,
) )
self.check_pages_load(module_store) self.check_pages_load(module_store)
def test_full_course_loads(self): def test_full_course_loads(self):
module_store = XMLModuleStore( module_store = XMLModuleStore(TEST_DATA_DIR,
TEST_DATA_DIR, default_class='xmodule.hidden_module.HiddenDescriptor',
default_class='xmodule.hidden_module.HiddenDescriptor', course_dirs=['full'],
course_dirs=['full'], load_error_modules=True,
load_error_modules=True,
) )
self.check_pages_load(module_store) self.check_pages_load(module_store)
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
class TestCoursesLoadTestCase_MongoModulestore(PageLoader): class TestCoursesLoadTestCase_MongoModulestore(PageLoaderTestCase):
'''Check that all pages in test courses load properly''' '''Check that all pages in test courses load properly from Mongo'''
def setUp(self): def setUp(self):
ActivateLoginTestCase.setUp(self) self.setup_viewtest_user()
xmodule.modulestore.django._MODULESTORES = {} xmodule.modulestore.django._MODULESTORES = {}
modulestore().collection.drop() modulestore().collection.drop()
...@@ -386,7 +389,7 @@ class TestCoursesLoadTestCase_MongoModulestore(PageLoader): ...@@ -386,7 +389,7 @@ class TestCoursesLoadTestCase_MongoModulestore(PageLoader):
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class TestNavigation(PageLoader): class TestNavigation(LoginEnrollmentTestCase):
"""Check that navigation state is saved properly""" """Check that navigation state is saved properly"""
def setUp(self): def setUp(self):
...@@ -447,7 +450,7 @@ class TestDraftModuleStore(TestCase): ...@@ -447,7 +450,7 @@ class TestDraftModuleStore(TestCase):
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class TestViewAuth(PageLoader): class TestViewAuth(LoginEnrollmentTestCase):
"""Check that view authentication works properly""" """Check that view authentication works properly"""
# NOTE: setUpClass() runs before override_settings takes effect, so # NOTE: setUpClass() runs before override_settings takes effect, so
...@@ -492,7 +495,7 @@ class TestViewAuth(PageLoader): ...@@ -492,7 +495,7 @@ class TestViewAuth(PageLoader):
'gradebook', 'gradebook',
'grade_summary',)] 'grade_summary',)]
urls.append(reverse('student_progress', kwargs={'course_id': course.id, urls.append(reverse('student_progress', kwargs={'course_id': course.id,
'student_id': user(self.student).id})) 'student_id': get_user(self.student).id}))
return urls return urls
# shouldn't be able to get to the instructor pages # shouldn't be able to get to the instructor pages
...@@ -502,8 +505,8 @@ class TestViewAuth(PageLoader): ...@@ -502,8 +505,8 @@ class TestViewAuth(PageLoader):
# Make the instructor staff in the toy course # Make the instructor staff in the toy course
group_name = _course_staff_group_name(self.toy.location) group_name = _course_staff_group_name(self.toy.location)
g = Group.objects.create(name=group_name) group = Group.objects.create(name=group_name)
g.user_set.add(user(self.instructor)) group.user_set.add(get_user(self.instructor))
self.logout() self.logout()
self.login(self.instructor, self.password) self.login(self.instructor, self.password)
...@@ -518,9 +521,9 @@ class TestViewAuth(PageLoader): ...@@ -518,9 +521,9 @@ class TestViewAuth(PageLoader):
self.check_for_get_code(404, url) self.check_for_get_code(404, url)
# now also make the instructor staff # now also make the instructor staff
u = user(self.instructor) instructor = get_user(self.instructor)
u.is_staff = True instructor.is_staff = True
u.save() instructor.save()
# and now should be able to load both # and now should be able to load both
for url in instructor_urls(self.toy) + instructor_urls(self.full): for url in instructor_urls(self.toy) + instructor_urls(self.full):
...@@ -627,7 +630,7 @@ class TestViewAuth(PageLoader): ...@@ -627,7 +630,7 @@ class TestViewAuth(PageLoader):
# to make access checking smarter and understand both the effective # to make access checking smarter and understand both the effective
# user (the student), and the requesting user (the prof) # user (the student), and the requesting user (the prof)
url = reverse('student_progress', kwargs={'course_id': course.id, url = reverse('student_progress', kwargs={'course_id': course.id,
'student_id': user(self.student).id}) 'student_id': get_user(self.student).id})
print 'checking for 404 on view-as-student: {0}'.format(url) print 'checking for 404 on view-as-student: {0}'.format(url)
self.check_for_get_code(404, url) self.check_for_get_code(404, url)
...@@ -648,8 +651,8 @@ class TestViewAuth(PageLoader): ...@@ -648,8 +651,8 @@ class TestViewAuth(PageLoader):
print '=== Testing course instructor access....' print '=== Testing course instructor access....'
# Make the instructor staff in the toy course # Make the instructor staff in the toy course
group_name = _course_staff_group_name(self.toy.location) group_name = _course_staff_group_name(self.toy.location)
g = Group.objects.create(name=group_name) group = Group.objects.create(name=group_name)
g.user_set.add(user(self.instructor)) group.user_set.add(get_user(self.instructor))
self.logout() self.logout()
self.login(self.instructor, self.password) self.login(self.instructor, self.password)
...@@ -663,9 +666,9 @@ class TestViewAuth(PageLoader): ...@@ -663,9 +666,9 @@ class TestViewAuth(PageLoader):
print '=== Testing staff access....' print '=== Testing staff access....'
# now also make the instructor staff # now also make the instructor staff
u = user(self.instructor) instructor = get_user(self.instructor)
u.is_staff = True instructor.is_staff = True
u.save() instructor.save()
# and now should be able to load both # and now should be able to load both
check_staff(self.toy) check_staff(self.toy)
...@@ -698,8 +701,8 @@ class TestViewAuth(PageLoader): ...@@ -698,8 +701,8 @@ class TestViewAuth(PageLoader):
print '=== Testing course instructor access....' print '=== Testing course instructor access....'
# Make the instructor staff in the toy course # Make the instructor staff in the toy course
group_name = _course_staff_group_name(self.toy.location) group_name = _course_staff_group_name(self.toy.location)
g = Group.objects.create(name=group_name) group = Group.objects.create(name=group_name)
g.user_set.add(user(self.instructor)) group.user_set.add(get_user(self.instructor))
print "logout/login" print "logout/login"
self.logout() self.logout()
...@@ -709,10 +712,10 @@ class TestViewAuth(PageLoader): ...@@ -709,10 +712,10 @@ class TestViewAuth(PageLoader):
print '=== Testing staff access....' print '=== Testing staff access....'
# now make the instructor global staff, but not in the instructor group # now make the instructor global staff, but not in the instructor group
g.user_set.remove(user(self.instructor)) group.user_set.remove(get_user(self.instructor))
u = user(self.instructor) instructor = get_user(self.instructor)
u.is_staff = True instructor.is_staff = True
u.save() instructor.save()
# unenroll and try again # unenroll and try again
self.unenroll(self.toy) self.unenroll(self.toy)
...@@ -726,8 +729,8 @@ class TestViewAuth(PageLoader): ...@@ -726,8 +729,8 @@ class TestViewAuth(PageLoader):
# Make courses start in the future # Make courses start in the future
tomorrow = time.time() + 24 * 3600 tomorrow = time.time() + 24 * 3600
nextday = tomorrow + 24 * 3600 # nextday = tomorrow + 24 * 3600
yesterday = time.time() - 24 * 3600 # yesterday = time.time() - 24 * 3600
# toy course's hasn't started # toy course's hasn't started
self.toy.lms.start = time.gmtime(tomorrow) self.toy.lms.start = time.gmtime(tomorrow)
...@@ -737,20 +740,20 @@ class TestViewAuth(PageLoader): ...@@ -737,20 +740,20 @@ class TestViewAuth(PageLoader):
self.toy.lms.days_early_for_beta = 2 self.toy.lms.days_early_for_beta = 2
# student user shouldn't see it # student user shouldn't see it
student_user = user(self.student) student_user = get_user(self.student)
self.assertFalse(has_access(student_user, self.toy, 'load')) self.assertFalse(has_access(student_user, self.toy, 'load'))
# now add the student to the beta test group # now add the student to the beta test group
group_name = course_beta_test_group_name(self.toy.location) group_name = course_beta_test_group_name(self.toy.location)
g = Group.objects.create(name=group_name) group = Group.objects.create(name=group_name)
g.user_set.add(student_user) group.user_set.add(student_user)
# now the student should see it # now the student should see it
self.assertTrue(has_access(student_user, self.toy, 'load')) self.assertTrue(has_access(student_user, self.toy, 'load'))
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class TestCourseGrader(PageLoader): class TestCourseGrader(LoginEnrollmentTestCase):
"""Check that a course gets graded properly""" """Check that a course gets graded properly"""
# NOTE: setUpClass() runs before override_settings takes effect, so # NOTE: setUpClass() runs before override_settings takes effect, so
...@@ -773,35 +776,39 @@ class TestCourseGrader(PageLoader): ...@@ -773,35 +776,39 @@ class TestCourseGrader(PageLoader):
self.activate_user(self.student) self.activate_user(self.student)
self.enroll(self.graded_course) self.enroll(self.graded_course)
self.student_user = user(self.student) self.student_user = get_user(self.student)
self.factory = RequestFactory() self.factory = RequestFactory()
def get_grade_summary(self): def get_grade_summary(self):
'''calls grades.grade for current user and course'''
model_data_cache = ModelDataCache.cache_for_descriptor_descendents( model_data_cache = ModelDataCache.cache_for_descriptor_descendents(
self.graded_course.id, self.student_user, self.graded_course) self.graded_course.id, self.student_user, self.graded_course)
fake_request = self.factory.get(reverse('progress', fake_request = self.factory.get(reverse('progress',
kwargs={'course_id': self.graded_course.id})) kwargs={'course_id': self.graded_course.id}))
return grades.grade(self.student_user, fake_request, return grades.grade(self.student_user, fake_request,
self.graded_course, model_data_cache) self.graded_course, model_data_cache)
def get_homework_scores(self): def get_homework_scores(self):
'''get scores for homeworks'''
return self.get_grade_summary()['totaled_scores']['Homework'] return self.get_grade_summary()['totaled_scores']['Homework']
def get_progress_summary(self): def get_progress_summary(self):
'''return progress summary structure for current user and course'''
model_data_cache = ModelDataCache.cache_for_descriptor_descendents( model_data_cache = ModelDataCache.cache_for_descriptor_descendents(
self.graded_course.id, self.student_user, self.graded_course) self.graded_course.id, self.student_user, self.graded_course)
fake_request = self.factory.get(reverse('progress', fake_request = self.factory.get(reverse('progress',
kwargs={'course_id': self.graded_course.id})) kwargs={'course_id': self.graded_course.id}))
progress_summary = grades.progress_summary(self.student_user, fake_request, progress_summary = grades.progress_summary(self.student_user, fake_request,
self.graded_course, model_data_cache) self.graded_course, model_data_cache)
return progress_summary return progress_summary
def check_grade_percent(self, percent): def check_grade_percent(self, percent):
'''assert that percent grade is as expected'''
grade_summary = self.get_grade_summary() grade_summary = self.get_grade_summary()
self.assertEqual(grade_summary['percent'], percent) self.assertEqual(grade_summary['percent'], percent)
...@@ -816,10 +823,9 @@ class TestCourseGrader(PageLoader): ...@@ -816,10 +823,9 @@ class TestCourseGrader(PageLoader):
problem_location = "i4x://edX/graded/problem/{0}".format(problem_url_name) problem_location = "i4x://edX/graded/problem/{0}".format(problem_url_name)
modx_url = reverse('modx_dispatch', modx_url = reverse('modx_dispatch',
kwargs={ kwargs={'course_id': self.graded_course.id,
'course_id': self.graded_course.id, 'location': problem_location,
'location': problem_location, 'dispatch': 'problem_check', })
'dispatch': 'problem_check', })
resp = self.client.post(modx_url, { 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_1'.format(problem_url_name): responses[0],
...@@ -831,16 +837,17 @@ class TestCourseGrader(PageLoader): ...@@ -831,16 +837,17 @@ class TestCourseGrader(PageLoader):
return resp return resp
def problem_location(self, problem_url_name): def problem_location(self, problem_url_name):
'''Get location string for problem, assuming hardcoded course_id'''
return "i4x://edX/graded/problem/{0}".format(problem_url_name) return "i4x://edX/graded/problem/{0}".format(problem_url_name)
def reset_question_answer(self, problem_url_name): def reset_question_answer(self, problem_url_name):
'''resets specified problem for current user'''
problem_location = self.problem_location(problem_url_name) problem_location = self.problem_location(problem_url_name)
modx_url = reverse('modx_dispatch', modx_url = reverse('modx_dispatch',
kwargs={ kwargs={'course_id': self.graded_course.id,
'course_id': self.graded_course.id, 'location': problem_location,
'location': problem_location, 'dispatch': 'problem_reset', })
'dispatch': 'problem_reset', })
resp = self.client.post(modx_url) resp = self.client.post(modx_url)
return resp return resp
...@@ -855,6 +862,7 @@ class TestCourseGrader(PageLoader): ...@@ -855,6 +862,7 @@ class TestCourseGrader(PageLoader):
return [s.earned for s in self.get_homework_scores()] return [s.earned for s in self.get_homework_scores()]
def score_for_hw(hw_url_name): def score_for_hw(hw_url_name):
"""returns list of scores for a given url"""
hw_section = [section for section hw_section = [section for section
in self.get_progress_summary()[0]['sections'] in self.get_progress_summary()[0]['sections']
if section.get('url_name') == hw_url_name][0] if section.get('url_name') == hw_url_name][0]
......
import json import json
import logging import logging
import xml.sax.saxutils as saxutils
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST from django.http import Http404
from django.http import HttpResponse, Http404
from django.utils import simplejson
from django.core.context_processors import csrf from django.core.context_processors import csrf
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User from django.contrib.auth.models import User
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
from courseware.courses import get_course_with_access from courseware.courses import get_course_with_access
from course_groups.cohorts import is_course_cohorted, get_cohort_id, is_commentable_cohorted, get_cohorted_commentables, get_cohort, get_course_cohorts, get_cohort_by_id from course_groups.cohorts import (is_course_cohorted, get_cohort_id, is_commentable_cohorted,
get_cohorted_commentables, get_course_cohorts, get_cohort_by_id)
from courseware.access import has_access from courseware.access import has_access
from urllib import urlencode from django_comment_client.permissions import cached_has_permission
from operator import methodcaller from django_comment_client.utils import (merge_dict, extract, strip_none, get_courseware_context)
from django_comment_client.permissions import check_permissions_by_view, cached_has_permission
from django_comment_client.utils import (merge_dict, extract, strip_none,
strip_blank, get_courseware_context)
import django_comment_client.utils as utils import django_comment_client.utils as utils
import comment_client as cc import comment_client as cc
import xml.sax.saxutils as saxutils
THREADS_PER_PAGE = 20 THREADS_PER_PAGE = 20
INLINE_THREADS_PER_PAGE = 20 INLINE_THREADS_PER_PAGE = 20
...@@ -31,6 +25,7 @@ escapedict = {'"': '"'} ...@@ -31,6 +25,7 @@ escapedict = {'"': '"'}
log = logging.getLogger("edx.discussions") log = logging.getLogger("edx.discussions")
@login_required
def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAGE): def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAGE):
""" """
This may raise cc.utils.CommentClientError or This may raise cc.utils.CommentClientError or
...@@ -60,7 +55,6 @@ def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAG ...@@ -60,7 +55,6 @@ def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAG
cc_user.default_sort_key = request.GET.get('sort_key') cc_user.default_sort_key = request.GET.get('sort_key')
cc_user.save() cc_user.save()
#there are 2 dimensions to consider when executing a search with respect to group id #there are 2 dimensions to consider when executing a search with respect to group id
#is user a moderator #is user a moderator
#did the user request a group #did the user request a group
...@@ -91,18 +85,17 @@ def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAG ...@@ -91,18 +85,17 @@ def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAG
#now add the group name if the thread has a group id #now add the group name if the thread has a group id
for thread in threads: for thread in threads:
if thread.get('group_id'): if thread.get('group_id'):
thread['group_name'] = get_cohort_by_id(course_id, thread.get('group_id')).name thread['group_name'] = get_cohort_by_id(course_id, thread.get('group_id')).name
thread['group_string'] = "This post visible only to Group %s." % (thread['group_name']) thread['group_string'] = "This post visible only to Group %s." % (thread['group_name'])
else: else:
thread['group_name'] = "" thread['group_name'] = ""
thread['group_string'] = "This post visible to everyone." thread['group_string'] = "This post visible to everyone."
#patch for backward compatibility to comments service #patch for backward compatibility to comments service
if not 'pinned' in thread: if not 'pinned' in thread:
thread['pinned'] = False thread['pinned'] = False
query_params['page'] = page query_params['page'] = page
query_params['num_pages'] = num_pages query_params['num_pages'] = num_pages
...@@ -110,6 +103,7 @@ def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAG ...@@ -110,6 +103,7 @@ def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAG
return threads, query_params return threads, query_params
@login_required
def inline_discussion(request, course_id, discussion_id): def inline_discussion(request, course_id, discussion_id):
""" """
Renders JSON for DiscussionModules Renders JSON for DiscussionModules
...@@ -142,14 +136,14 @@ def inline_discussion(request, course_id, discussion_id): ...@@ -142,14 +136,14 @@ def inline_discussion(request, course_id, discussion_id):
cohorts_list = list() cohorts_list = list()
if is_cohorted: if is_cohorted:
cohorts_list.append({'name':'All Groups','id':None}) cohorts_list.append({'name': 'All Groups', 'id': None})
#if you're a mod, send all cohorts and let you pick #if you're a mod, send all cohorts and let you pick
if is_moderator: if is_moderator:
cohorts = get_course_cohorts(course_id) cohorts = get_course_cohorts(course_id)
for c in cohorts: for c in cohorts:
cohorts_list.append({'name':c.name, 'id':c.id}) cohorts_list.append({'name': c.name, 'id': c.id})
else: else:
#students don't get to choose #students don't get to choose
...@@ -216,9 +210,6 @@ def forum_form_discussion(request, course_id): ...@@ -216,9 +210,6 @@ def forum_form_discussion(request, course_id):
user_cohort_id = get_cohort_id(request.user, course_id) user_cohort_id = get_cohort_id(request.user, course_id)
context = { context = {
'csrf': csrf(request)['csrf_token'], 'csrf': csrf(request)['csrf_token'],
'course': course, 'course': course,
...@@ -242,6 +233,7 @@ def forum_form_discussion(request, course_id): ...@@ -242,6 +233,7 @@ def forum_form_discussion(request, course_id):
return render_to_response('discussion/index.html', context) return render_to_response('discussion/index.html', context)
@login_required @login_required
def single_thread(request, course_id, discussion_id, thread_id): def single_thread(request, course_id, discussion_id, thread_id):
course = get_course_with_access(request.user, course_id, 'load') course = get_course_with_access(request.user, course_id, 'load')
...@@ -250,11 +242,11 @@ def single_thread(request, course_id, discussion_id, thread_id): ...@@ -250,11 +242,11 @@ def single_thread(request, course_id, discussion_id, thread_id):
try: try:
thread = cc.Thread.find(thread_id).retrieve(recursive=True, user_id=request.user.id) thread = cc.Thread.find(thread_id).retrieve(recursive=True, user_id=request.user.id)
#patch for backward compatibility with comments service #patch for backward compatibility with comments service
if not 'pinned' in thread.attributes: if not 'pinned' in thread.attributes:
thread['pinned'] = False thread['pinned'] = False
except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err: except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
log.error("Error loading single thread.") log.error("Error loading single thread.")
raise Http404 raise Http404
...@@ -352,7 +344,7 @@ def user_profile(request, course_id, user_id): ...@@ -352,7 +344,7 @@ def user_profile(request, course_id, user_id):
query_params = { query_params = {
'page': request.GET.get('page', 1), 'page': request.GET.get('page', 1),
'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities 'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities
} }
threads, page, num_pages = profiled_user.active_threads(query_params) threads, page, num_pages = profiled_user.active_threads(query_params)
query_params['page'] = page query_params['page'] = page
...@@ -369,8 +361,6 @@ def user_profile(request, course_id, user_id): ...@@ -369,8 +361,6 @@ def user_profile(request, course_id, user_id):
'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict), 'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict),
}) })
else: else:
context = { context = {
'course': course, 'course': course,
'user': request.user, 'user': request.user,
...@@ -426,5 +416,5 @@ def followed_threads(request, course_id, user_id): ...@@ -426,5 +416,5 @@ def followed_threads(request, course_id, user_id):
} }
return render_to_response('discussion/user_profile.html', context) return render_to_response('discussion/user_profile.html', context)
except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err: except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError):
raise Http404 raise Http404
from django.contrib.auth.models import User, Group
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.test.client import RequestFactory
from django.conf import settings
from mock import Mock
from django.test.utils import override_settings
import xmodule.modulestore.django
from student.models import CourseEnrollment
from django.db.models.signals import m2m_changed, pre_delete, pre_save, post_delete, post_save
from django.dispatch.dispatcher import _make_id
import string import string
import random import random
from .permissions import has_permission
from .models import Role, Permission
from xmodule.modulestore.django import modulestore
from xmodule.modulestore import Location
from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.modulestore.xml import XMLModuleStore
import comment_client
from courseware.tests.tests import PageLoader, TEST_DATA_XML_MODULESTORE from django.contrib.auth.models import User
from django.test import TestCase
#@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
#class TestCohorting(PageLoader):
# """Check that cohorting works properly"""
#
# def setUp(self):
# xmodule.modulestore.django._MODULESTORES = {}
#
# # Assume courses are there
# self.toy = modulestore().get_course("edX/toy/2012_Fall")
#
# # Create two accounts
# self.student = 'view@test.com'
# self.student2 = 'view2@test.com'
# self.password = 'foo'
# self.create_account('u1', self.student, self.password)
# self.create_account('u2', self.student2, self.password)
# self.activate_user(self.student)
# self.activate_user(self.student2)
#
# def test_create_thread(self):
# my_save = Mock()
# comment_client.perform_request = my_save
#
# resp = self.client.post(
# reverse('django_comment_client.base.views.create_thread',
# kwargs={'course_id': 'edX/toy/2012_Fall',
# 'commentable_id': 'General'}),
# {'some': "some",
# 'data': 'data'})
# self.assertTrue(my_save.called)
#
# #self.assertEqual(resp.status_code, 200)
# #self.assertEqual(my_save.something, "expected", "complaint if not true")
#
# self.toy.cohort_config = {"cohorted": True}
#
# # call the view again ...
#
# # assert that different things happened
from student.models import CourseEnrollment
from django_comment_client.permissions import has_permission
from django_comment_client.models import Role
class PermissionsTestCase(TestCase): class PermissionsTestCase(TestCase):
......
...@@ -8,13 +8,6 @@ Notes for running by hand: ...@@ -8,13 +8,6 @@ Notes for running by hand:
django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/instructor django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/instructor
""" """
import courseware.tests.tests as ct
import json
from nose import SkipTest
from mock import patch, Mock
from django.test.utils import override_settings from django.test.utils import override_settings
# Need access to internal func to put users in the right group # Need access to internal func to put users in the right group
...@@ -26,13 +19,13 @@ from django_comment_client.models import Role, FORUM_ROLE_ADMINISTRATOR, \ ...@@ -26,13 +19,13 @@ from django_comment_client.models import Role, FORUM_ROLE_ADMINISTRATOR, \
from django_comment_client.utils import has_forum_access from django_comment_client.utils import has_forum_access
from courseware.access import _course_staff_group_name from courseware.access import _course_staff_group_name
import courseware.tests.tests as ct from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE, get_user
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
import xmodule.modulestore.django import xmodule.modulestore.django
@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): class TestInstructorDashboardGradeDownloadCSV(LoginEnrollmentTestCase):
''' '''
Check for download of csv Check for download of csv
''' '''
...@@ -55,7 +48,7 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): ...@@ -55,7 +48,7 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader):
def make_instructor(course): def make_instructor(course):
group_name = _course_staff_group_name(course.location) group_name = _course_staff_group_name(course.location)
g = Group.objects.create(name=group_name) g = Group.objects.create(name=group_name)
g.user_set.add(ct.user(self.instructor)) g.user_set.add(get_user(self.instructor))
make_instructor(self.toy) make_instructor(self.toy)
...@@ -63,7 +56,6 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): ...@@ -63,7 +56,6 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader):
self.login(self.instructor, self.password) self.login(self.instructor, self.password)
self.enroll(self.toy) self.enroll(self.toy)
def test_download_grades_csv(self): def test_download_grades_csv(self):
course = self.toy course = self.toy
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': course.id})
...@@ -101,9 +93,8 @@ def action_name(operation, rolename): ...@@ -101,9 +93,8 @@ def action_name(operation, rolename):
return '{0} forum {1}'.format(operation, FORUM_ADMIN_ACTION_SUFFIX[rolename]) return '{0} forum {1}'.format(operation, FORUM_ADMIN_ACTION_SUFFIX[rolename])
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE) class TestInstructorDashboardForumAdmin(LoginEnrollmentTestCase):
class TestInstructorDashboardForumAdmin(ct.PageLoader):
''' '''
Check for change in forum admin role memberships Check for change in forum admin role memberships
''' '''
...@@ -112,7 +103,6 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): ...@@ -112,7 +103,6 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader):
xmodule.modulestore.django._MODULESTORES = {} xmodule.modulestore.django._MODULESTORES = {}
courses = modulestore().get_courses() courses = modulestore().get_courses()
self.course_id = "edX/toy/2012_Fall" self.course_id = "edX/toy/2012_Fall"
self.toy = modulestore().get_course(self.course_id) self.toy = modulestore().get_course(self.course_id)
...@@ -127,14 +117,12 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): ...@@ -127,14 +117,12 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader):
group_name = _course_staff_group_name(self.toy.location) group_name = _course_staff_group_name(self.toy.location)
g = Group.objects.create(name=group_name) g = Group.objects.create(name=group_name)
g.user_set.add(ct.user(self.instructor)) g.user_set.add(get_user(self.instructor))
self.logout() self.logout()
self.login(self.instructor, self.password) self.login(self.instructor, self.password)
self.enroll(self.toy) self.enroll(self.toy)
def initialize_roles(self, course_id): def initialize_roles(self, course_id):
self.admin_role = Role.objects.get_or_create(name=FORUM_ROLE_ADMINISTRATOR, course_id=course_id)[0] self.admin_role = Role.objects.get_or_create(name=FORUM_ROLE_ADMINISTRATOR, course_id=course_id)[0]
self.moderator_role = Role.objects.get_or_create(name=FORUM_ROLE_MODERATOR, course_id=course_id)[0] self.moderator_role = Role.objects.get_or_create(name=FORUM_ROLE_MODERATOR, course_id=course_id)[0]
......
"""Tests for License package"""
import logging import logging
import json
from uuid import uuid4 from uuid import uuid4
from random import shuffle from random import shuffle
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from factory import Factory, SubFactory
from django.test import TestCase from django.test import TestCase
from django.core.management import call_command from django.core.management import call_command
from django.core.urlresolvers import reverse
from .models import CourseSoftware, UserLicense from licenses.models import CourseSoftware, UserLicense
from courseware.tests.tests import LoginEnrollmentTestCase, get_user
COURSE_1 = 'edX/toy/2012_Fall' COURSE_1 = 'edX/toy/2012_Fall'
SOFTWARE_1 = 'matlab' SOFTWARE_1 = 'matlab'
SOFTWARE_2 = 'stata' SOFTWARE_2 = 'stata'
SERIAL_1 = '123456abcde'
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class CourseSoftwareFactory(Factory):
'''Factory for generating CourseSoftware objects in database'''
FACTORY_FOR = CourseSoftware
name = SOFTWARE_1
full_name = SOFTWARE_1
url = SOFTWARE_1
course_id = COURSE_1
class UserLicenseFactory(Factory):
'''
Factory for generating UserLicense objects in database
By default, the user assigned is null, indicating that the
serial number has not yet been assigned.
'''
FACTORY_FOR = UserLicense
software = SubFactory(CourseSoftwareFactory)
serial = SERIAL_1
class LicenseTestCase(LoginEnrollmentTestCase):
'''Tests for licenses.views'''
def setUp(self):
'''creates a user and logs in'''
self.setup_viewtest_user()
self.software = CourseSoftwareFactory()
def test_get_license(self):
UserLicenseFactory(user=get_user(self.viewtest_email), software=self.software)
response = self.client.post(reverse('user_software_license'),
{'software': SOFTWARE_1, 'generate': 'false'},
HTTP_X_REQUESTED_WITH='XMLHttpRequest',
HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1))
self.assertEqual(200, response.status_code)
json_returned = json.loads(response.content)
self.assertFalse('error' in json_returned)
self.assertTrue('serial' in json_returned)
self.assertEquals(json_returned['serial'], SERIAL_1)
def test_get_nonexistent_license(self):
response = self.client.post(reverse('user_software_license'),
{'software': SOFTWARE_1, 'generate': 'false'},
HTTP_X_REQUESTED_WITH='XMLHttpRequest',
HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1))
self.assertEqual(200, response.status_code)
json_returned = json.loads(response.content)
self.assertFalse('serial' in json_returned)
self.assertTrue('error' in json_returned)
def test_create_nonexistent_license(self):
'''Should not assign a license to an unlicensed user when none are available'''
response = self.client.post(reverse('user_software_license'),
{'software': SOFTWARE_1, 'generate': 'true'},
HTTP_X_REQUESTED_WITH='XMLHttpRequest',
HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1))
self.assertEqual(200, response.status_code)
json_returned = json.loads(response.content)
self.assertFalse('serial' in json_returned)
self.assertTrue('error' in json_returned)
def test_create_license(self):
'''Should assign a license to an unlicensed user if one is unassigned'''
# create an unassigned license
UserLicenseFactory(software=self.software)
response = self.client.post(reverse('user_software_license'),
{'software': SOFTWARE_1, 'generate': 'true'},
HTTP_X_REQUESTED_WITH='XMLHttpRequest',
HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1))
self.assertEqual(200, response.status_code)
json_returned = json.loads(response.content)
self.assertFalse('error' in json_returned)
self.assertTrue('serial' in json_returned)
self.assertEquals(json_returned['serial'], SERIAL_1)
def test_get_license_from_wrong_course(self):
response = self.client.post(reverse('user_software_license'),
{'software': SOFTWARE_1, 'generate': 'false'},
HTTP_X_REQUESTED_WITH='XMLHttpRequest',
HTTP_REFERER='/courses/{0}/some_page'.format('some/other/course'))
self.assertEqual(404, response.status_code)
def test_get_license_from_non_ajax(self):
response = self.client.post(reverse('user_software_license'),
{'software': SOFTWARE_1, 'generate': 'false'},
HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1))
self.assertEqual(404, response.status_code)
def test_get_license_without_software(self):
response = self.client.post(reverse('user_software_license'),
{'generate': 'false'},
HTTP_X_REQUESTED_WITH='XMLHttpRequest',
HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1))
self.assertEqual(404, response.status_code)
def test_get_license_without_login(self):
self.logout()
response = self.client.post(reverse('user_software_license'),
{'software': SOFTWARE_1, 'generate': 'false'},
HTTP_X_REQUESTED_WITH='XMLHttpRequest',
HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1))
# if we're not logged in, we should be referred to the login page
self.assertEqual(302, response.status_code)
class CommandTest(TestCase): class CommandTest(TestCase):
'''Test management command for importing serial numbers'''
def test_import_serial_numbers(self): def test_import_serial_numbers(self):
size = 20 size = 20
...@@ -51,31 +167,33 @@ class CommandTest(TestCase): ...@@ -51,31 +167,33 @@ class CommandTest(TestCase):
licenses_count = UserLicense.objects.all().count() licenses_count = UserLicense.objects.all().count()
self.assertEqual(3 * size, licenses_count) self.assertEqual(3 * size, licenses_count)
cs = CourseSoftware.objects.get(pk=1) software = CourseSoftware.objects.get(pk=1)
lics = UserLicense.objects.filter(software=cs)[:size] lics = UserLicense.objects.filter(software=software)[:size]
known_serials = list(l.serial for l in lics) known_serials = list(l.serial for l in lics)
known_serials.extend(generate_serials(10)) known_serials.extend(generate_serials(10))
shuffle(known_serials) shuffle(known_serials)
log.debug('Adding some new and old serials to {0}'.format(SOFTWARE_1)) log.debug('Adding some new and old serials to {0}'.format(SOFTWARE_1))
with NamedTemporaryFile() as f: with NamedTemporaryFile() as tmpfile:
f.write('\n'.join(known_serials)) tmpfile.write('\n'.join(known_serials))
f.flush() tmpfile.flush()
args = [COURSE_1, SOFTWARE_1, f.name] args = [COURSE_1, SOFTWARE_1, tmpfile.name]
call_command('import_serial_numbers', *args) call_command('import_serial_numbers', *args)
log.debug('Check if we added only the new ones') log.debug('Check if we added only the new ones')
licenses_count = UserLicense.objects.filter(software=cs).count() licenses_count = UserLicense.objects.filter(software=software).count()
self.assertEqual((2 * size) + 10, licenses_count) self.assertEqual((2 * size) + 10, licenses_count)
def generate_serials(size=20): def generate_serials(size=20):
'''generate a list of serial numbers'''
return [str(uuid4()) for _ in range(size)] return [str(uuid4()) for _ in range(size)]
def generate_serials_file(size=20): def generate_serials_file(size=20):
'''output list of generated serial numbers to a temp file'''
serials = generate_serials(size) serials = generate_serials(size)
temp_file = NamedTemporaryFile() temp_file = NamedTemporaryFile()
......
...@@ -7,12 +7,13 @@ from collections import namedtuple, defaultdict ...@@ -7,12 +7,13 @@ from collections import namedtuple, defaultdict
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.views.decorators.csrf import requires_csrf_token, csrf_protect from django.views.decorators.csrf import requires_csrf_token
from .models import CourseSoftware from licenses.models import CourseSoftware
from .models import get_courses_licenses, get_or_create_license, get_license from licenses.models import get_courses_licenses, get_or_create_license, get_license
log = logging.getLogger("mitx.licenses") log = logging.getLogger("mitx.licenses")
...@@ -44,6 +45,7 @@ def get_licenses_by_course(user, courses): ...@@ -44,6 +45,7 @@ def get_licenses_by_course(user, courses):
return data_by_course return data_by_course
@login_required
@requires_csrf_token @requires_csrf_token
def user_software_license(request): def user_software_license(request):
if request.method != 'POST' or not request.is_ajax(): if request.method != 'POST' or not request.is_ajax():
...@@ -65,19 +67,21 @@ def user_software_license(request): ...@@ -65,19 +67,21 @@ def user_software_license(request):
try: try:
software = CourseSoftware.objects.get(name=software_name, software = CourseSoftware.objects.get(name=software_name,
course_id=course_id) course_id=course_id)
print software
except CourseSoftware.DoesNotExist: except CourseSoftware.DoesNotExist:
raise Http404 raise Http404
user = User.objects.get(id=user_id) try:
user = User.objects.get(id=user_id)
except User.DoesNotExist:
raise Http404
if generate: if generate:
license = get_or_create_license(user, software) software_license = get_or_create_license(user, software)
else: else:
license = get_license(user, software) software_license = get_license(user, software)
if license: if software_license:
response = {'serial': license.serial} response = {'serial': software_license.serial}
else: else:
response = {'error': 'No serial number found'} response = {'error': 'No serial number found'}
......
...@@ -4,22 +4,22 @@ Tests for open ended grading interfaces ...@@ -4,22 +4,22 @@ Tests for open ended grading interfaces
django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/open_ended_grading django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/open_ended_grading
""" """
from django.test import TestCase import json
from open_ended_grading import staff_grading_service from mock import MagicMock
from xmodule.open_ended_grading_classes import peer_grading_service
from xmodule import peer_grading_module
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from mitxmako.shortcuts import render_to_string
from courseware.access import _course_staff_group_name from xmodule.open_ended_grading_classes import peer_grading_service
import courseware.tests.tests as ct from xmodule import peer_grading_module
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
import xmodule.modulestore.django import xmodule.modulestore.django
from nose import SkipTest
from mock import patch, Mock, MagicMock
import json
from xmodule.x_module import ModuleSystem from xmodule.x_module import ModuleSystem
from mitxmako.shortcuts import render_to_string
from open_ended_grading import staff_grading_service
from courseware.access import _course_staff_group_name
from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE, get_user
import logging import logging
...@@ -30,8 +30,8 @@ from django.http import QueryDict ...@@ -30,8 +30,8 @@ from django.http import QueryDict
from xmodule.tests import test_util_open_ended from xmodule.tests import test_util_open_ended
@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class TestStaffGradingService(ct.PageLoader): class TestStaffGradingService(LoginEnrollmentTestCase):
''' '''
Check that staff grading service proxy works. Basically just checking the Check that staff grading service proxy works. Basically just checking the
access control and error handling logic -- all the actual work is on the access control and error handling logic -- all the actual work is on the
...@@ -56,7 +56,7 @@ class TestStaffGradingService(ct.PageLoader): ...@@ -56,7 +56,7 @@ class TestStaffGradingService(ct.PageLoader):
def make_instructor(course): def make_instructor(course):
group_name = _course_staff_group_name(course.location) group_name = _course_staff_group_name(course.location)
g = Group.objects.create(name=group_name) g = Group.objects.create(name=group_name)
g.user_set.add(ct.user(self.instructor)) g.user_set.add(get_user(self.instructor))
make_instructor(self.toy) make_instructor(self.toy)
...@@ -126,8 +126,8 @@ class TestStaffGradingService(ct.PageLoader): ...@@ -126,8 +126,8 @@ class TestStaffGradingService(ct.PageLoader):
self.assertIsNotNone(d['problem_list']) self.assertIsNotNone(d['problem_list'])
@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class TestPeerGradingService(ct.PageLoader): class TestPeerGradingService(LoginEnrollmentTestCase):
''' '''
Check that staff grading service proxy works. Basically just checking the Check that staff grading service proxy works. Basically just checking the
access control and error handling logic -- all the actual work is on the access control and error handling logic -- all the actual work is on the
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment