diff --git a/.pylintrc b/.pylintrc
index 49fcf80eb9..792079ce03 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -34,10 +34,13 @@ load-plugins=
# multiple time (only on the command line, not in the configuration file where
# it should appear only once).
disable=
+# Never going to use these
# C0301: Line too long
-# C0302: Too many lines in module
-# W0141: Used builtin function 'map'
# W0142: Used * or ** magic
+# W0141: Used builtin function 'map'
+
+# Might use these when the code is in better shape
+# C0302: Too many lines in module
# R0201: Method could be a function
# R0901: Too many ancestors
# R0902: Too many instance attributes
@@ -96,7 +99,18 @@ zope=no
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
-generated-members=REQUEST,acl_users,aq_parent,objects,DoesNotExist,can_read,can_write,get_url,size,content
+generated-members=
+ REQUEST,
+ acl_users,
+ aq_parent,
+ objects,
+ DoesNotExist,
+ can_read,
+ can_write,
+ get_url,
+ size,
+ content,
+ status_code
[BASIC]
diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py
index c5fb0a231b..e40d7c57b9 100644
--- a/cms/djangoapps/contentstore/tests/test_contentstore.py
+++ b/cms/djangoapps/contentstore/tests/test_contentstore.py
@@ -15,8 +15,9 @@ from datetime import timedelta
from django.contrib.auth.models import User
from django.dispatch import Signal
from contentstore.utils import get_modulestore
+from contentstore.tests.utils import parse_json
-from .utils import ModuleStoreTestCase, parse_json
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore import Location
@@ -48,6 +49,7 @@ class MongoCollectionFindWrapper(object):
self.counter = self.counter+1
return self.original(query, *args, **kwargs)
+
@override_settings(MODULESTORE=TEST_DATA_MODULESTORE)
class ContentStoreToyCourseTest(ModuleStoreTestCase):
"""
@@ -187,27 +189,33 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
def test_get_depth_with_drafts(self):
import_from_xml(modulestore(), 'common/test/data/', ['simple'])
- course = modulestore('draft').get_item(Location(['i4x', 'edX', 'simple',
- 'course', '2012_Fall', None]), depth=None)
+ course = modulestore('draft').get_item(
+ Location(['i4x', 'edX', 'simple', 'course', '2012_Fall', None]),
+ depth=None
+ )
# make sure no draft items have been returned
num_drafts = self._get_draft_counts(course)
self.assertEqual(num_drafts, 0)
- problem = modulestore('draft').get_item(Location(['i4x', 'edX', 'simple',
- 'problem', 'ps01-simple', None]))
+ problem = modulestore('draft').get_item(
+ Location(['i4x', 'edX', 'simple', 'problem', 'ps01-simple', None])
+ )
# put into draft
modulestore('draft').clone_item(problem.location, problem.location)
# make sure we can query that item and verify that it is a draft
- draft_problem = modulestore('draft').get_item(Location(['i4x', 'edX', 'simple',
- 'problem', 'ps01-simple', None]))
+ draft_problem = modulestore('draft').get_item(
+ Location(['i4x', 'edX', 'simple', 'problem', 'ps01-simple', None])
+ )
self.assertTrue(getattr(draft_problem, 'is_draft', False))
#now requery with depth
- course = modulestore('draft').get_item(Location(['i4x', 'edX', 'simple',
- 'course', '2012_Fall', None]), depth=None)
+ course = modulestore('draft').get_item(
+ Location(['i4x', 'edX', 'simple', 'course', '2012_Fall', None]),
+ depth=None
+ )
# make sure just one draft item have been returned
num_drafts = self._get_draft_counts(course)
@@ -266,10 +274,11 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
# make sure the parent no longer points to the child object which was deleted
self.assertTrue(sequential.location.url() in chapter.children)
- self.client.post(reverse('delete_item'),
- json.dumps({'id': sequential.location.url(), 'delete_children': 'true',
- 'delete_all_versions': 'true'}),
- "application/json")
+ self.client.post(
+ reverse('delete_item'),
+ json.dumps({'id': sequential.location.url(), 'delete_children': 'true', 'delete_all_versions': 'true'}),
+ "application/json"
+ )
found = False
try:
@@ -537,15 +546,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
print 'Exporting to tempdir = {0}'.format(root_dir)
# export out to a tempdir
- exported = False
- try:
- export_to_xml(module_store, content_store, location, root_dir, 'test_export')
- exported = True
- except Exception:
- print 'Exception thrown: {0}'.format(traceback.format_exc())
- pass
-
- self.assertTrue(exported)
+ export_to_xml(module_store, content_store, location, root_dir, 'test_export')
class ContentStoreTest(ModuleStoreTestCase):
@@ -625,10 +626,12 @@ class ContentStoreTest(ModuleStoreTestCase):
"""Test viewing the index page with no courses"""
# Create a course so there is something to view
resp = self.client.get(reverse('index'))
- self.assertContains(resp,
- '
My Courses
',
- status_code=200,
- html=True)
+ self.assertContains(
+ resp,
+ 'My Courses
',
+ status_code=200,
+ html=True
+ )
def test_course_factory(self):
"""Test that the course factory works correctly."""
@@ -645,10 +648,12 @@ class ContentStoreTest(ModuleStoreTestCase):
"""Test viewing the index page with an existing course"""
CourseFactory.create(display_name='Robot Super Educational Course')
resp = self.client.get(reverse('index'))
- self.assertContains(resp,
- 'Robot Super Educational Course',
- status_code=200,
- html=True)
+ self.assertContains(
+ resp,
+ 'Robot Super Educational Course',
+ status_code=200,
+ html=True
+ )
def test_course_overview_view_with_course(self):
"""Test viewing the course overview page with an existing course"""
@@ -661,10 +666,12 @@ class ContentStoreTest(ModuleStoreTestCase):
}
resp = self.client.get(reverse('course_index', kwargs=data))
- self.assertContains(resp,
- '',
- status_code=200,
- html=True)
+ self.assertContains(
+ resp,
+ '',
+ status_code=200,
+ html=True
+ )
def test_clone_item(self):
"""Test cloning an item. E.g. creating a new section"""
@@ -680,7 +687,10 @@ class ContentStoreTest(ModuleStoreTestCase):
self.assertEqual(resp.status_code, 200)
data = parse_json(resp)
- self.assertRegexpMatches(data['id'], '^i4x:\/\/MITx\/999\/chapter\/([0-9]|[a-f]){32}$')
+ self.assertRegexpMatches(
+ data['id'],
+ r"^i4x://MITx/999/chapter/([0-9]|[a-f]){32}$"
+ )
def test_capa_module(self):
"""Test that a problem treats markdown specially."""
diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py
index c3fa665b2c..c9f6b2053e 100644
--- a/cms/djangoapps/contentstore/tests/test_course_settings.py
+++ b/cms/djangoapps/contentstore/tests/test_course_settings.py
@@ -12,7 +12,7 @@ from models.settings.course_details import (CourseDetails, CourseSettingsEncoder
from models.settings.course_grading import CourseGradingModel
from contentstore.utils import get_modulestore
-from .utils import ModuleStoreTestCase
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from models.settings.course_metadata import CourseMetadata
@@ -47,12 +47,8 @@ class CourseTestCase(ModuleStoreTestCase):
self.client = Client()
self.client.login(username=uname, password=password)
- t = 'i4x://edx/templates/course/Empty'
- o = 'MITx'
- n = '999'
- dn = 'Robot Super Course'
- self.course_location = Location('i4x', o, n, 'course', 'Robot_Super_Course')
- CourseFactory.create(template=t, org=o, number=n, display_name=dn)
+ course = CourseFactory.create(template='i4x://edx/templates/course/Empty', org='MITx', number='999', display_name='Robot Super Course')
+ self.course_location = course.location
class CourseDetailsTestCase(CourseTestCase):
@@ -86,17 +82,25 @@ class CourseDetailsTestCase(CourseTestCase):
jsondetails = CourseDetails.fetch(self.course_location)
jsondetails.syllabus = "bar"
# encode - decode to convert date fields and other data which changes form
- self.assertEqual(CourseDetails.update_from_json(jsondetails.__dict__).syllabus,
- jsondetails.syllabus, "After set syllabus")
+ self.assertEqual(
+ CourseDetails.update_from_json(jsondetails.__dict__).syllabus,
+ jsondetails.syllabus, "After set syllabus"
+ )
jsondetails.overview = "Overview"
- self.assertEqual(CourseDetails.update_from_json(jsondetails.__dict__).overview,
- jsondetails.overview, "After set overview")
+ self.assertEqual(
+ CourseDetails.update_from_json(jsondetails.__dict__).overview,
+ jsondetails.overview, "After set overview"
+ )
jsondetails.intro_video = "intro_video"
- self.assertEqual(CourseDetails.update_from_json(jsondetails.__dict__).intro_video,
- jsondetails.intro_video, "After set intro_video")
+ self.assertEqual(
+ CourseDetails.update_from_json(jsondetails.__dict__).intro_video,
+ jsondetails.intro_video, "After set intro_video"
+ )
jsondetails.effort = "effort"
- self.assertEqual(CourseDetails.update_from_json(jsondetails.__dict__).effort,
- jsondetails.effort, "After set effort")
+ self.assertEqual(
+ CourseDetails.update_from_json(jsondetails.__dict__).effort,
+ jsondetails.effort, "After set effort"
+ )
class CourseDetailsViewTest(CourseTestCase):
@@ -150,9 +154,7 @@ class CourseDetailsViewTest(CourseTestCase):
@staticmethod
def struct_to_datetime(struct_time):
- return datetime.datetime(struct_time.tm_year, struct_time.tm_mon,
- struct_time.tm_mday, struct_time.tm_hour,
- struct_time.tm_min, struct_time.tm_sec, tzinfo=UTC())
+ return datetime.datetime(*struct_time[:6], tzinfo=UTC())
def compare_date_fields(self, details, encoded, context, field):
if details[field] is not None:
@@ -271,18 +273,20 @@ class CourseMetadataEditingTest(CourseTestCase):
self.assertIn('xqa_key', test_model, 'xqa_key field ')
def test_update_from_json(self):
- test_model = CourseMetadata.update_from_json(self.course_location,
- {"advertised_start": "start A",
- "testcenter_info": {"c": "test"},
- "days_early_for_beta": 2})
+ test_model = CourseMetadata.update_from_json(self.course_location, {
+ "advertised_start": "start A",
+ "testcenter_info": {"c": "test"},
+ "days_early_for_beta": 2
+ })
self.update_check(test_model)
# try fresh fetch to ensure persistence
test_model = CourseMetadata.fetch(self.course_location)
self.update_check(test_model)
# now change some of the existing metadata
- test_model = CourseMetadata.update_from_json(self.course_location,
- {"advertised_start": "start B",
- "display_name": "jolly roger"})
+ test_model = CourseMetadata.update_from_json(self.course_location, {
+ "advertised_start": "start B",
+ "display_name": "jolly roger"}
+ )
self.assertIn('display_name', test_model, 'Missing editable metadata field')
self.assertEqual(test_model['display_name'], 'jolly roger', "not expected value")
self.assertIn('advertised_start', test_model, 'Missing revised advertised_start metadata field')
diff --git a/cms/djangoapps/contentstore/tests/test_utils.py b/cms/djangoapps/contentstore/tests/test_utils.py
index 6b1b6b7f6f..eb7bfb6db9 100644
--- a/cms/djangoapps/contentstore/tests/test_utils.py
+++ b/cms/djangoapps/contentstore/tests/test_utils.py
@@ -3,7 +3,7 @@ from contentstore import utils
import mock
from django.test import TestCase
from xmodule.modulestore.tests.factories import CourseFactory
-from .utils import ModuleStoreTestCase
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
class LMSLinksTestCase(TestCase):
@@ -30,7 +30,7 @@ class LMSLinksTestCase(TestCase):
class UrlReverseTestCase(ModuleStoreTestCase):
""" Tests for get_url_reverse """
- def test_CoursePageNames(self):
+ def test_course_page_names(self):
""" Test the defined course pages. """
course = CourseFactory.create(org='mitX', number='666', display_name='URL Reverse Course')
@@ -70,4 +70,3 @@ class UrlReverseTestCase(ModuleStoreTestCase):
'https://edge.edx.org/courses/edX/edX101/How_to_Create_an_edX_Course/about',
utils.get_url_reverse('https://edge.edx.org/courses/edX/edX101/How_to_Create_an_edX_Course/about', course)
)
-
\ No newline at end of file
diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py
index 72e30dca64..34e5da4b4d 100644
--- a/cms/djangoapps/contentstore/tests/tests.py
+++ b/cms/djangoapps/contentstore/tests/tests.py
@@ -1,7 +1,8 @@
from django.test.client import Client
from django.core.urlresolvers import reverse
-from .utils import ModuleStoreTestCase, parse_json, user, registration
+from .utils import parse_json, user, registration
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
class ContentStoreTestCase(ModuleStoreTestCase):
diff --git a/cms/djangoapps/contentstore/tests/utils.py b/cms/djangoapps/contentstore/tests/utils.py
index bb7ac2bf06..3135e49a08 100644
--- a/cms/djangoapps/contentstore/tests/utils.py
+++ b/cms/djangoapps/contentstore/tests/utils.py
@@ -2,112 +2,11 @@
Utilities for contentstore tests
'''
-#pylint: disable=W0603
-
import json
-import copy
-from uuid import uuid4
-from django.test import TestCase
-from django.conf import settings
from student.models import Registration
from django.contrib.auth.models import User
-import xmodule.modulestore.django
-from xmodule.templates import update_templates
-
-
-class ModuleStoreTestCase(TestCase):
- """ Subclass for any test case that uses the mongodb
- module store. This populates a uniquely named modulestore
- collection with templates before running the TestCase
- and drops it they are finished. """
-
- @staticmethod
- def flush_mongo_except_templates():
- '''
- Delete everything in the module store except templates
- '''
- modulestore = xmodule.modulestore.django.modulestore()
-
- # This query means: every item in the collection
- # that is not a template
- query = {"_id.course": {"$ne": "templates"}}
-
- # Remove everything except templates
- modulestore.collection.remove(query)
-
- @staticmethod
- def load_templates_if_necessary():
- '''
- Load templates into the modulestore only if they do not already exist.
- We need the templates, because they are copied to create
- XModules such as sections and problems
- '''
- modulestore = xmodule.modulestore.django.modulestore()
-
- # Count the number of templates
- query = {"_id.course": "templates"}
- num_templates = modulestore.collection.find(query).count()
-
- if num_templates < 1:
- update_templates()
-
- @classmethod
- def setUpClass(cls):
- '''
- Flush the mongo store and set up templates
- '''
-
- # Use a uuid to differentiate
- # the mongo collections on jenkins.
- cls.orig_modulestore = copy.deepcopy(settings.MODULESTORE)
- test_modulestore = cls.orig_modulestore
- test_modulestore['default']['OPTIONS']['collection'] = 'modulestore_%s' % uuid4().hex
- test_modulestore['direct']['OPTIONS']['collection'] = 'modulestore_%s' % uuid4().hex
- xmodule.modulestore.django._MODULESTORES = {}
-
- settings.MODULESTORE = test_modulestore
-
- TestCase.setUpClass()
-
- @classmethod
- def tearDownClass(cls):
- '''
- Revert to the old modulestore settings
- '''
-
- # Clean up by dropping the collection
- modulestore = xmodule.modulestore.django.modulestore()
- modulestore.collection.drop()
-
- # Restore the original modulestore settings
- settings.MODULESTORE = cls.orig_modulestore
-
- def _pre_setup(self):
- '''
- Remove everything but the templates before each test
- '''
-
- # Flush anything that is not a template
- ModuleStoreTestCase.flush_mongo_except_templates()
-
- # Check that we have templates loaded; if not, load them
- ModuleStoreTestCase.load_templates_if_necessary()
-
- # Call superclass implementation
- super(ModuleStoreTestCase, self)._pre_setup()
-
- def _post_teardown(self):
- '''
- Flush everything we created except the templates
- '''
- # Flush anything that is not a template
- ModuleStoreTestCase.flush_mongo_except_templates()
-
- # Call superclass implementation
- super(ModuleStoreTestCase, self)._post_teardown()
-
def parse_json(response):
"""Parse response, which is assumed to be json"""
diff --git a/cms/envs/test.py b/cms/envs/test.py
index 99869bc869..f11ff9b56c 100644
--- a/cms/envs/test.py
+++ b/cms/envs/test.py
@@ -28,7 +28,7 @@ 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
+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 = [
@@ -41,7 +41,7 @@ STATICFILES_DIRS += [
if os.path.isdir(COMMON_TEST_DATA_ROOT / course_dir)
]
-modulestore_options = {
+MODULESTORE_OPTIONS = {
'default_class': 'xmodule.raw_module.RawDescriptor',
'host': 'localhost',
'db': 'test_xmodule',
@@ -53,15 +53,15 @@ modulestore_options = {
MODULESTORE = {
'default': {
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
- 'OPTIONS': modulestore_options
+ 'OPTIONS': MODULESTORE_OPTIONS
},
'direct': {
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
- 'OPTIONS': modulestore_options
+ 'OPTIONS': MODULESTORE_OPTIONS
},
'draft': {
'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore',
- 'OPTIONS': modulestore_options
+ 'OPTIONS': MODULESTORE_OPTIONS
}
}
@@ -76,7 +76,7 @@ CONTENTSTORE = {
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': ENV_ROOT / "db" / "cms.db",
+ 'NAME': TEST_ROOT / "db" / "cms.db",
},
}
diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py
index f74188725a..adb51954e8 100644
--- a/common/djangoapps/student/tests/factories.py
+++ b/common/djangoapps/student/tests/factories.py
@@ -2,7 +2,7 @@ from student.models import (User, UserProfile, Registration,
CourseEnrollmentAllowed, CourseEnrollment)
from django.contrib.auth.models import Group
from datetime import datetime
-from factory import Factory, SubFactory
+from factory import Factory, SubFactory, post_generation
from uuid import uuid4
@@ -44,6 +44,17 @@ class UserFactory(Factory):
last_login = datetime(2012, 1, 1)
date_joined = datetime(2011, 1, 1)
+ @post_generation
+ def set_password(self, create, extracted, **kwargs):
+ self._raw_password = self.password
+ self.set_password(self.password)
+ if create:
+ self.save()
+
+
+class AdminFactory(UserFactory):
+ is_staff = True
+
class CourseEnrollmentFactory(Factory):
FACTORY_FOR = CourseEnrollment
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index 5d3f576119..1ea51bd7f1 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -232,6 +232,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
# 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.grading_policy)
self.test_center_exams = []
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py
new file mode 100644
index 0000000000..753cbfac4f
--- /dev/null
+++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py
@@ -0,0 +1,104 @@
+
+import copy
+from uuid import uuid4
+from django.test import TestCase
+
+from django.conf import settings
+import xmodule.modulestore.django
+from xmodule.templates import update_templates
+
+
+class ModuleStoreTestCase(TestCase):
+ """ Subclass for any test case that uses the mongodb
+ module store. This populates a uniquely named modulestore
+ collection with templates before running the TestCase
+ and drops it they are finished. """
+
+ @staticmethod
+ def flush_mongo_except_templates():
+ '''
+ Delete everything in the module store except templates
+ '''
+ modulestore = xmodule.modulestore.django.modulestore()
+
+ # This query means: every item in the collection
+ # that is not a template
+ query = {"_id.course": {"$ne": "templates"}}
+
+ # Remove everything except templates
+ modulestore.collection.remove(query)
+
+ @staticmethod
+ def load_templates_if_necessary():
+ '''
+ Load templates into the modulestore only if they do not already exist.
+ We need the templates, because they are copied to create
+ XModules such as sections and problems
+ '''
+ modulestore = xmodule.modulestore.django.modulestore()
+
+ # Count the number of templates
+ query = {"_id.course": "templates"}
+ num_templates = modulestore.collection.find(query).count()
+
+ if num_templates < 1:
+ update_templates()
+
+ @classmethod
+ def setUpClass(cls):
+ '''
+ Flush the mongo store and set up templates
+ '''
+
+ # Use a uuid to differentiate
+ # the mongo collections on jenkins.
+ cls.orig_modulestore = copy.deepcopy(settings.MODULESTORE)
+ if 'direct' not in settings.MODULESTORE:
+ settings.MODULESTORE['direct'] = settings.MODULESTORE['default']
+
+ settings.MODULESTORE['default']['OPTIONS']['collection'] = 'modulestore_%s' % uuid4().hex
+ settings.MODULESTORE['direct']['OPTIONS']['collection'] = 'modulestore_%s' % uuid4().hex
+ xmodule.modulestore.django._MODULESTORES.clear()
+
+ print settings.MODULESTORE
+
+ TestCase.setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ '''
+ Revert to the old modulestore settings
+ '''
+
+ # Clean up by dropping the collection
+ modulestore = xmodule.modulestore.django.modulestore()
+ modulestore.collection.drop()
+
+ xmodule.modulestore.django._MODULESTORES.clear()
+
+ # Restore the original modulestore settings
+ settings.MODULESTORE = cls.orig_modulestore
+
+ def _pre_setup(self):
+ '''
+ Remove everything but the templates before each test
+ '''
+
+ # Flush anything that is not a template
+ ModuleStoreTestCase.flush_mongo_except_templates()
+
+ # Check that we have templates loaded; if not, load them
+ ModuleStoreTestCase.load_templates_if_necessary()
+
+ # Call superclass implementation
+ super(ModuleStoreTestCase, self)._pre_setup()
+
+ def _post_teardown(self):
+ '''
+ Flush everything we created except the templates
+ '''
+ # Flush anything that is not a template
+ ModuleStoreTestCase.flush_mongo_except_templates()
+
+ # Call superclass implementation
+ super(ModuleStoreTestCase, self)._post_teardown()
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py
index 1a82e1b708..e49972a305 100644
--- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py
+++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py
@@ -1,4 +1,4 @@
-from factory import Factory
+from factory import Factory, lazy_attribute_sequence, lazy_attribute
from time import gmtime
from uuid import uuid4
from xmodule.modulestore import Location
@@ -7,21 +7,12 @@ from xmodule.timeparse import stringify_time
from xmodule.modulestore.inheritance import own_metadata
-def XMODULE_COURSE_CREATION(class_to_create, **kwargs):
- return XModuleCourseFactory._create(class_to_create, **kwargs)
-
-
-def XMODULE_ITEM_CREATION(class_to_create, **kwargs):
- return XModuleItemFactory._create(class_to_create, **kwargs)
-
-
class XModuleCourseFactory(Factory):
"""
Factory for XModule courses.
"""
ABSTRACT_FACTORY = True
- _creation_function = (XMODULE_COURSE_CREATION,)
@classmethod
def _create(cls, target_class, *args, **kwargs):
@@ -33,7 +24,10 @@ class XModuleCourseFactory(Factory):
location = Location('i4x', org, number,
'course', Location.clean(display_name))
- store = modulestore('direct')
+ try:
+ store = modulestore('direct')
+ except KeyError:
+ store = modulestore()
# Write the data to the mongo datastore
new_course = store.clone_item(template, location)
@@ -52,6 +46,11 @@ class XModuleCourseFactory(Factory):
# Update the data in the mongo datastore
store.update_metadata(new_course.location.url(), own_metadata(new_course))
+ data = kwargs.get('data')
+ if data is not None:
+ store.update_item(new_course.location, data)
+
+
return new_course
@@ -74,7 +73,19 @@ class XModuleItemFactory(Factory):
"""
ABSTRACT_FACTORY = True
- _creation_function = (XMODULE_ITEM_CREATION,)
+
+ display_name = None
+
+ @lazy_attribute
+ def category(attr):
+ template = Location(attr.template)
+ return template.category
+
+ @lazy_attribute
+ def location(attr):
+ parent = Location(attr.parent_location)
+ dest_name = attr.display_name.replace(" ", "_") if attr.display_name is not None else uuid4().hex
+ return parent._replace(category=attr.category, name=dest_name)
@classmethod
def _create(cls, target_class, *args, **kwargs):
@@ -110,12 +121,7 @@ class XModuleItemFactory(Factory):
# This code was based off that in cms/djangoapps/contentstore/views.py
parent = store.get_item(parent_location)
- # If a display name is set, use that
- dest_name = display_name.replace(" ", "_") if display_name is not None else uuid4().hex
- dest_location = parent_location._replace(category=template.category,
- name=dest_name)
-
- new_item = store.clone_item(template, dest_location)
+ new_item = store.clone_item(template, kwargs.get('location'))
# replace the display name with an optional parameter passed in from the caller
if display_name is not None:
@@ -145,4 +151,7 @@ class ItemFactory(XModuleItemFactory):
parent_location = 'i4x://MITx/999/course/Robot_Super_Course'
template = 'i4x://edx/templates/chapter/Empty'
- display_name = 'Section One'
+
+ @lazy_attribute_sequence
+ def display_name(attr, n):
+ return "{} {}".format(attr.category.title(), n)
\ No newline at end of file
diff --git a/jenkins/test.sh b/jenkins/test.sh
index edcca840c8..b554e7a708 100755
--- a/jenkins/test.sh
+++ b/jenkins/test.sh
@@ -36,7 +36,7 @@ export PIP_DOWNLOAD_CACHE=/mnt/pip-cache
source /mnt/virtualenvs/"$JOB_NAME"/bin/activate
pip install -q -r pre-requirements.txt
-yes w | pip install -q -r test-requirements.txt -r requirements.txt
+yes w | pip install -q -r requirements.txt
rake clobber
rake pep8 > pep8.log || cat pep8.log
diff --git a/lms/djangoapps/courseware/features/common.py b/lms/djangoapps/courseware/features/common.py
index f6256adfa1..e81568ae4b 100644
--- a/lms/djangoapps/courseware/features/common.py
+++ b/lms/djangoapps/courseware/features/common.py
@@ -1,6 +1,8 @@
#pylint: disable=C0111
#pylint: disable=W0621
+from __future__ import absolute_import
+
from lettuce import world, step
from nose.tools import assert_equals, assert_in
from lettuce.django import django_url
diff --git a/lms/djangoapps/courseware/tests/factories.py b/lms/djangoapps/courseware/tests/factories.py
index a84b2b8475..df072c015c 100644
--- a/lms/djangoapps/courseware/tests/factories.py
+++ b/lms/djangoapps/courseware/tests/factories.py
@@ -1,6 +1,7 @@
import factory
from student.models import (User, UserProfile, Registration,
CourseEnrollmentAllowed)
+from courseware.models import StudentModule
from django.contrib.auth.models import Group
from datetime import datetime
import uuid
@@ -47,3 +48,15 @@ class CourseEnrollmentAllowedFactory(factory.Factory):
email = 'test@edx.org'
course_id = 'edX/test/2012_Fall'
+
+
+class StudentModuleFactory(factory.Factory):
+ FACTORY_FOR = StudentModule
+
+ module_type = "problem"
+ student = factory.SubFactory(UserFactory)
+ course_id = "MITx/999/Robot_Super_Course"
+ state = None
+ grade = None
+ max_grade = None
+ done = 'na'
diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py
index 5613f8831f..4c9f592797 100644
--- a/lms/djangoapps/courseware/tests/tests.py
+++ b/lms/djangoapps/courseware/tests/tests.py
@@ -55,7 +55,7 @@ def mongo_store_config(data_dir):
Use of this config requires mongo to be running
'''
- return {
+ store = {
'default': {
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
'OPTIONS': {
@@ -68,6 +68,8 @@ def mongo_store_config(data_dir):
}
}
}
+ store['direct'] = store['default']
+ return store
def draft_mongo_store_config(data_dir):
@@ -83,6 +85,17 @@ def draft_mongo_store_config(data_dir):
'fs_root': data_dir,
'render_template': 'mitxmako.shortcuts.render_to_string',
}
+ },
+ 'direct': {
+ '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',
+ }
}
}
diff --git a/lms/djangoapps/django_comment_client/models.py b/lms/djangoapps/django_comment_client/models.py
index 023b355a29..e06aed1281 100644
--- a/lms/djangoapps/django_comment_client/models.py
+++ b/lms/djangoapps/django_comment_client/models.py
@@ -38,7 +38,7 @@ class Role(models.Model):
def inherit_permissions(self, role): # TODO the name of this method is a little bit confusing,
# since it's one-off and doesn't handle inheritance later
if role.course_id and role.course_id != self.course_id:
- logging.warning("{0} cannot inherit permissions from {1} due to course_id inconsistency", \
+ logging.warning("%s cannot inherit permissions from %s due to course_id inconsistency", \
self, role)
for per in role.permissions.all():
self.add_permission(per)
diff --git a/lms/djangoapps/instructor/tests/__init__.py b/lms/djangoapps/instructor/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/lms/djangoapps/instructor/tests/test_download_csv.py b/lms/djangoapps/instructor/tests/test_download_csv.py
new file mode 100644
index 0000000000..8e4c175faa
--- /dev/null
+++ b/lms/djangoapps/instructor/tests/test_download_csv.py
@@ -0,0 +1,81 @@
+"""
+Unit tests for instructor dashboard
+
+Based on (and depends on) unit tests for courseware.
+
+Notes for running by hand:
+
+django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/instructor
+"""
+
+from django.test.utils import override_settings
+
+# Need access to internal func to put users in the right group
+from django.contrib.auth.models import Group
+
+from django.core.urlresolvers import reverse
+
+from courseware.access import _course_staff_group_name
+from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE, get_user
+from xmodule.modulestore.django import modulestore
+import xmodule.modulestore.django
+
+
+@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
+class TestInstructorDashboardGradeDownloadCSV(LoginEnrollmentTestCase):
+ '''
+ Check for download of csv
+ '''
+
+ def setUp(self):
+ xmodule.modulestore.django._MODULESTORES = {}
+
+ self.full = modulestore().get_course("edX/full/6.002_Spring_2012")
+ self.toy = modulestore().get_course("edX/toy/2012_Fall")
+
+ # Create two accounts
+ self.student = 'view@test.com'
+ self.instructor = 'view2@test.com'
+ self.password = 'foo'
+ self.create_account('u1', self.student, self.password)
+ self.create_account('u2', self.instructor, self.password)
+ self.activate_user(self.student)
+ self.activate_user(self.instructor)
+
+ def make_instructor(course):
+ group_name = _course_staff_group_name(course.location)
+ g = Group.objects.create(name=group_name)
+ g.user_set.add(get_user(self.instructor))
+
+ make_instructor(self.toy)
+
+ self.logout()
+ self.login(self.instructor, self.password)
+ self.enroll(self.toy)
+
+ def test_download_grades_csv(self):
+ course = self.toy
+ url = reverse('instructor_dashboard', kwargs={'course_id': course.id})
+ msg = "url = {0}\n".format(url)
+ response = self.client.post(url, {'action': 'Download CSV of all student grades for this course'})
+ msg += "instructor dashboard download csv grades: response = '{0}'\n".format(response)
+
+ self.assertEqual(response['Content-Type'], 'text/csv', msg)
+
+ cdisp = response['Content-Disposition']
+ msg += "Content-Disposition = '%s'\n" % cdisp
+ self.assertEqual(cdisp, 'attachment; filename=grades_{0}.csv'.format(course.id), msg)
+
+ body = response.content.replace('\r', '')
+ msg += "body = '{0}'\n".format(body)
+
+ # All the not-actually-in-the-course hw and labs come from the
+ # default grading policy string in graders.py
+ expected_body = '''"ID","Username","Full Name","edX email","External email","HW 01","HW 02","HW 03","HW 04","HW 05","HW 06","HW 07","HW 08","HW 09","HW 10","HW 11","HW 12","HW Avg","Lab 01","Lab 02","Lab 03","Lab 04","Lab 05","Lab 06","Lab 07","Lab 08","Lab 09","Lab 10","Lab 11","Lab 12","Lab Avg","Midterm","Final"
+"2","u2","Fred Weasley","view2@test.com","","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"
+'''
+
+ self.assertEqual(body, expected_body, msg)
+
+
+
diff --git a/lms/djangoapps/instructor/tests.py b/lms/djangoapps/instructor/tests/test_forum_admin.py
similarity index 73%
rename from lms/djangoapps/instructor/tests.py
rename to lms/djangoapps/instructor/tests/test_forum_admin.py
index fd8e652997..d2d58fb61c 100644
--- a/lms/djangoapps/instructor/tests.py
+++ b/lms/djangoapps/instructor/tests/test_forum_admin.py
@@ -1,13 +1,8 @@
"""
-Unit tests for instructor dashboard
-
-Based on (and depends on) unit tests for courseware.
-
-Notes for running by hand:
-
-django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/instructor
+Unit tests for instructor dashboard forum administration
"""
+
from django.test.utils import override_settings
# Need access to internal func to put users in the right group
@@ -24,63 +19,6 @@ from xmodule.modulestore.django import modulestore
import xmodule.modulestore.django
-@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
-class TestInstructorDashboardGradeDownloadCSV(LoginEnrollmentTestCase):
- '''
- Check for download of csv
- '''
-
- def setUp(self):
- xmodule.modulestore.django._MODULESTORES = {}
-
- self.full = modulestore().get_course("edX/full/6.002_Spring_2012")
- self.toy = modulestore().get_course("edX/toy/2012_Fall")
-
- # Create two accounts
- self.student = 'view@test.com'
- self.instructor = 'view2@test.com'
- self.password = 'foo'
- self.create_account('u1', self.student, self.password)
- self.create_account('u2', self.instructor, self.password)
- self.activate_user(self.student)
- self.activate_user(self.instructor)
-
- def make_instructor(course):
- group_name = _course_staff_group_name(course.location)
- g = Group.objects.create(name=group_name)
- g.user_set.add(get_user(self.instructor))
-
- make_instructor(self.toy)
-
- self.logout()
- self.login(self.instructor, self.password)
- self.enroll(self.toy)
-
- def test_download_grades_csv(self):
- course = self.toy
- url = reverse('instructor_dashboard', kwargs={'course_id': course.id})
- msg = "url = {0}\n".format(url)
- response = self.client.post(url, {'action': 'Download CSV of all student grades for this course'})
- msg += "instructor dashboard download csv grades: response = '{0}'\n".format(response)
-
- self.assertEqual(response['Content-Type'], 'text/csv', msg)
-
- cdisp = response['Content-Disposition']
- msg += "Content-Disposition = '%s'\n" % cdisp
- self.assertEqual(cdisp, 'attachment; filename=grades_{0}.csv'.format(course.id), msg)
-
- body = response.content.replace('\r', '')
- msg += "body = '{0}'\n".format(body)
-
- # All the not-actually-in-the-course hw and labs come from the
- # default grading policy string in graders.py
- expected_body = '''"ID","Username","Full Name","edX email","External email","HW 01","HW 02","HW 03","HW 04","HW 05","HW 06","HW 07","HW 08","HW 09","HW 10","HW 11","HW 12","HW Avg","Lab 01","Lab 02","Lab 03","Lab 04","Lab 05","Lab 06","Lab 07","Lab 08","Lab 09","Lab 10","Lab 11","Lab 12","Lab Avg","Midterm","Final"
-"2","u2","Fred Weasley","view2@test.com","","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"
-'''
-
- self.assertEqual(body, expected_body, msg)
-
-
FORUM_ROLES = [FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA]
FORUM_ADMIN_ACTION_SUFFIX = {FORUM_ROLE_ADMINISTRATOR: 'admin', FORUM_ROLE_MODERATOR: 'moderator', FORUM_ROLE_COMMUNITY_TA: 'community TA'}
FORUM_ADMIN_USER = {FORUM_ROLE_ADMINISTRATOR: 'forumadmin', FORUM_ROLE_MODERATOR: 'forummoderator', FORUM_ROLE_COMMUNITY_TA: 'forummoderator'}
@@ -208,4 +146,4 @@ class TestInstructorDashboardForumAdmin(LoginEnrollmentTestCase):
added_roles.append(rolename)
added_roles.sort()
roles = ', '.join(added_roles)
- self.assertTrue(response.content.find('{0} | '.format(roles)) >= 0, 'not finding roles "{0}"'.format(roles))
+ self.assertTrue(response.content.find('{0} | '.format(roles)) >= 0, 'not finding roles "{0}"'.format(roles))
\ No newline at end of file
diff --git a/lms/djangoapps/instructor/tests/test_gradebook.py b/lms/djangoapps/instructor/tests/test_gradebook.py
new file mode 100644
index 0000000000..2de5c18bcd
--- /dev/null
+++ b/lms/djangoapps/instructor/tests/test_gradebook.py
@@ -0,0 +1,154 @@
+"""
+Tests of the instructor dashboard gradebook
+"""
+
+from django.test import TestCase
+from django.test.utils import override_settings
+from django.core.urlresolvers import reverse
+from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
+from student.tests.factories import UserFactory, CourseEnrollmentFactory, UserProfileFactory, AdminFactory
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
+from mock import patch, DEFAULT
+from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
+from capa.tests.response_xml_factory import StringResponseXMLFactory
+from courseware.tests.factories import StudentModuleFactory
+from xmodule.modulestore import Location
+from xmodule.modulestore.django import modulestore
+
+
+USER_COUNT = 11
+
+@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
+class TestGradebook(ModuleStoreTestCase):
+ grading_policy = None
+
+ def setUp(self):
+ instructor = AdminFactory.create()
+ self.client.login(username=instructor.username, password='test')
+
+ modulestore().request_cache = modulestore().metadata_inheritance_cache_subsystem = None
+
+ course_data = {}
+ if self.grading_policy is not None:
+ course_data['grading_policy'] = self.grading_policy
+
+ self.course = CourseFactory.create(data=course_data)
+ chapter = ItemFactory.create(
+ parent_location=self.course.location,
+ template="i4x://edx/templates/sequential/Empty",
+ )
+ section = ItemFactory.create(
+ parent_location=chapter.location,
+ template="i4x://edx/templates/sequential/Empty",
+ metadata={'graded': True, 'format': 'Homework'}
+ )
+
+ self.users = [
+ UserFactory.create(username='robot%d' % i, email='robot+test+%d@edx.org' % i)
+ for i in xrange(USER_COUNT)
+ ]
+
+ for user in self.users:
+ UserProfileFactory.create(user=user)
+ CourseEnrollmentFactory.create(user=user, course_id=self.course.id)
+
+ for i in xrange(USER_COUNT-1):
+ template_name = "i4x://edx/templates/problem/Blank_Common_Problem"
+ item = ItemFactory.create(
+ parent_location=section.location,
+ template=template_name,
+ data=StringResponseXMLFactory().build_xml(answer='foo'),
+ metadata={'rerandomize': 'always'}
+ )
+
+ for j, user in enumerate(self.users):
+ StudentModuleFactory.create(
+ grade=1 if i < j else 0,
+ max_grade=1,
+ student=user,
+ course_id=self.course.id,
+ module_state_key=Location(item.location).url()
+ )
+
+ self.response = self.client.get(reverse('gradebook', args=(self.course.id,)))
+
+ def test_response_code(self):
+ self.assertEquals(self.response.status_code, 200)
+
+class TestDefaultGradingPolicy(TestGradebook):
+ def test_all_users_listed(self):
+ for user in self.users:
+ self.assertIn(user.username, self.response.content)
+
+ def test_default_policy(self):
+ # Default >= 50% passes, so Users 5-10 should be passing for Homework 1 [6]
+ # One use at the top of the page [1]
+ self.assertEquals(7, self.response.content.count('grade_Pass'))
+
+ # Users 1-5 attempted Homework 1 (and get Fs) [4]
+ # Users 1-10 attempted any homework (and get Fs) [10]
+ # Users 4-10 scored enough to not get rounded to 0 for the class (and get Fs) [7]
+ # One use at top of the page [1]
+ self.assertEquals(22, self.response.content.count('grade_F'))
+
+ # All other grades are None [29 categories * 11 users - 27 non-empty grades = 292]
+ # One use at the top of the page [1]
+ self.assertEquals(293, self.response.content.count('grade_None'))
+
+class TestLetterCutoffPolicy(TestGradebook):
+ grading_policy = {
+ "GRADER": [
+ {
+ "type": "Homework",
+ "min_count": 1,
+ "drop_count": 0,
+ "short_label": "HW",
+ "weight": 1
+ },
+ ],
+ "GRADE_CUTOFFS": {
+ 'A': .9,
+ 'B': .8,
+ 'C': .7,
+ 'D': .6,
+ }
+ }
+
+ def test_styles(self):
+
+ self.assertIn("grade_A {color:green;}", self.response.content)
+ self.assertIn("grade_B {color:Chocolate;}", self.response.content)
+ self.assertIn("grade_C {color:DarkSlateGray;}", self.response.content)
+ self.assertIn("grade_D {color:DarkSlateGray;}", self.response.content)
+
+ def test_assigned_grades(self):
+ print self.response.content
+ # Users 9-10 have >= 90% on Homeworks [2]
+ # Users 9-10 have >= 90% on the class [2]
+ # One use at the top of the page [1]
+ self.assertEquals(5, self.response.content.count('grade_A'))
+
+ # User 8 has 80 <= Homeworks < 90 [1]
+ # User 8 has 80 <= class < 90 [1]
+ # One use at the top of the page [1]
+ self.assertEquals(3, self.response.content.count('grade_B'))
+
+ # User 7 has 70 <= Homeworks < 80 [1]
+ # User 7 has 70 <= class < 80 [1]
+ # One use at the top of the page [1]
+ self.assertEquals(3, self.response.content.count('grade_C'))
+
+ # User 6 has 60 <= Homeworks < 70 [1]
+ # User 6 has 60 <= class < 70 [1]
+ # One use at the top of the page [1]
+ self.assertEquals(3, self.response.content.count('grade_C'))
+
+ # Users 1-5 have 60% > grades > 0 on Homeworks [5]
+ # Users 1-5 have 60% > grades > 0 on the class [5]
+ # One use at top of the page [1]
+ self.assertEquals(11, self.response.content.count('grade_F'))
+
+ # User 0 has 0 on Homeworks [1]
+ # User 0 has 0 on the class [1]
+ # One use at the top of the page [1]
+ self.assertEquals(3, self.response.content.count('grade_None'))
\ No newline at end of file
diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py
index a3b4f42bf7..dd6748e691 100644
--- a/lms/djangoapps/instructor/views.py
+++ b/lms/djangoapps/instructor/views.py
@@ -961,11 +961,14 @@ def gradebook(request, course_id):
}
for student in enrolled_students]
- return render_to_response('courseware/gradebook.html', {'students': student_info,
- 'course': course,
- 'course_id': course_id,
- # Checked above
- 'staff_access': True, })
+ return render_to_response('courseware/gradebook.html', {
+ 'students': student_info,
+ 'course': course,
+ 'course_id': course_id,
+ # Checked above
+ 'staff_access': True,
+ 'ordered_grades': sorted(course.grade_cutoffs.items(), key=lambda i: i[1], reverse=True),
+ })
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
diff --git a/lms/djangoapps/licenses/models.py b/lms/djangoapps/licenses/models.py
index 06f777f611..db24126a8e 100644
--- a/lms/djangoapps/licenses/models.py
+++ b/lms/djangoapps/licenses/models.py
@@ -73,7 +73,7 @@ def _create_license(user, software):
license.save()
except IndexError:
# there are no free licenses
- log.error('No serial numbers available for {0}', software)
+ log.error('No serial numbers available for %s', software)
license = None
# TODO [rocha]look if someone has unenrolled from the class
# and already has a serial number
diff --git a/lms/djangoapps/licenses/tests.py b/lms/djangoapps/licenses/tests.py
index 5289c31bc6..5cf5e44dde 100644
--- a/lms/djangoapps/licenses/tests.py
+++ b/lms/djangoapps/licenses/tests.py
@@ -8,10 +8,14 @@ from tempfile import NamedTemporaryFile
from factory import Factory, SubFactory
from django.test import TestCase
+from django.test.utils import override_settings
from django.core.management import call_command
from django.core.urlresolvers import reverse
+from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
from licenses.models import CourseSoftware, UserLicense
from courseware.tests.tests import LoginEnrollmentTestCase, get_user
+from xmodule.modulestore.tests.factories import CourseFactory
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
COURSE_1 = 'edX/toy/2012_Fall'
@@ -130,20 +134,24 @@ class LicenseTestCase(LoginEnrollmentTestCase):
self.assertEqual(302, response.status_code)
-class CommandTest(TestCase):
+@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
+class CommandTest(ModuleStoreTestCase):
'''Test management command for importing serial numbers'''
+ def setUp(self):
+ course = CourseFactory.create()
+ self.course_id = course.id
def test_import_serial_numbers(self):
size = 20
log.debug('Adding one set of serials for {0}'.format(SOFTWARE_1))
with generate_serials_file(size) as temp_file:
- args = [COURSE_1, SOFTWARE_1, temp_file.name]
+ args = [self.course_id, SOFTWARE_1, temp_file.name]
call_command('import_serial_numbers', *args)
log.debug('Adding one set of serials for {0}'.format(SOFTWARE_2))
with generate_serials_file(size) as temp_file:
- args = [COURSE_1, SOFTWARE_2, temp_file.name]
+ args = [self.course_id, SOFTWARE_2, temp_file.name]
call_command('import_serial_numbers', *args)
log.debug('There should be only 2 course-software entries')
@@ -156,7 +164,7 @@ class CommandTest(TestCase):
log.debug('Adding more serial numbers to {0}'.format(SOFTWARE_1))
with generate_serials_file(size) as temp_file:
- args = [COURSE_1, SOFTWARE_1, temp_file.name]
+ args = [self.course_id, SOFTWARE_1, temp_file.name]
call_command('import_serial_numbers', *args)
log.debug('There should be still only 2 course-software entries')
@@ -179,7 +187,7 @@ class CommandTest(TestCase):
with NamedTemporaryFile() as tmpfile:
tmpfile.write('\n'.join(known_serials))
tmpfile.flush()
- args = [COURSE_1, SOFTWARE_1, tmpfile.name]
+ args = [self.course_id, SOFTWARE_1, tmpfile.name]
call_command('import_serial_numbers', *args)
log.debug('Check if we added only the new ones')
diff --git a/lms/envs/test.py b/lms/envs/test.py
index 5eb96c8df0..58b49f744b 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -91,7 +91,7 @@ MODULESTORE = {
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': PROJECT_ROOT / "db" / "mitx.db",
+ 'NAME': TEST_ROOT / 'db' / 'mitx.db'
},
}
@@ -122,7 +122,7 @@ CACHES = {
'LOCATION': '/var/tmp/mongo_metadata_inheritance',
'TIMEOUT': 300,
'KEY_FUNCTION': 'util.memcache.safe_key',
- }
+ }
}
# Dummy secret key for dev
diff --git a/lms/templates/courseware/gradebook.html b/lms/templates/courseware/gradebook.html
index fb750aed19..015004ee1c 100644
--- a/lms/templates/courseware/gradebook.html
+++ b/lms/templates/courseware/gradebook.html
@@ -13,9 +13,12 @@
<%static:css group='course'/>
@@ -78,8 +81,8 @@
letter_grade = 'None'
if fraction > 0:
letter_grade = 'F'
- for grade in ['A', 'B', 'C']:
- if fraction >= course.grade_cutoffs[grade]:
+ for (grade, cutoff) in ordered_grades:
+ if fraction >= cutoff:
letter_grade = grade
break
@@ -90,11 +93,11 @@
%for student in students:
-
+
%for section in student['grade_summary']['section_breakdown']:
${percent_data( section['percent'] )}
%endfor
- | ${percent_data( student['grade_summary']['percent'])} |
+ ${percent_data( student['grade_summary']['percent'])}
%endfor
diff --git a/requirements.txt b/requirements.txt
index 1a383e6cc0..a626ac1944 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,9 +4,7 @@ beautifulsoup==3.2.1
boto==2.6.0
django-celery==3.0.11
django-countries==1.5
-django-debug-toolbar-mongo
django-followit==0.0.3
-django-jasmine==0.3.2
django-keyedcache==1.4-6
django-kombu==0.9.4
django-mako==0.1.5pre
@@ -19,29 +17,20 @@ django-ses==0.4.1
django-storages==1.1.5
django-threaded-multihost==1.4-1
django==1.4.3
-django_debug_toolbar
-django_nose==1.1
-dogapi==1.2.1
-dogstatsd-python==0.2.1
-factory_boy
feedparser==5.1.3
fs==0.4.0
GitPython==0.3.2.RC1
glob2==0.3
http://sympy.googlecode.com/files/sympy-0.7.1.tar.gz
-ipython==0.13.1
lxml==3.0.1
mako==0.7.3
Markdown==2.2.1
-mock==0.8.0
MySQL-python==1.2.4c1
networkx==1.7
-newrelic==1.8.0.13
nltk==2.0.4
-nosexcover==1.0.7
numpy==1.6.2
paramiko==1.9.0
-path.py
+path.py==3.0.1
Pillow==1.7.8
pip
pygments==1.5
@@ -51,11 +40,37 @@ python-memcached==1.48
python-openid==2.2.5
pytz==2012h
PyYAML==3.10
-rednose==0.3
requests==0.14.2
scipy==0.11.0
Shapely==1.2.16
sorl-thumbnail==11.12
South==0.7.6
-sphinx==1.1.3
xmltodict==0.4.1
+
+# Used for debugging
+ipython==0.13.1
+
+
+# Metrics gathering and monitoring
+dogapi==1.2.1
+dogstatsd-python==0.2.1
+newrelic==1.8.0.13
+
+# Used for documentation gathering
+sphinx==1.1.3
+
+# Used for testing
+coverage==3.6
+factory_boy==1.3.0
+lettuce==0.2.15
+mock==0.8.0
+nosexcover==1.0.7
+pep8==1.4.5
+pylint==0.27.0
+rednose==0.3
+selenium==2.31.0
+splinter==0.5.0
+django_nose==1.1
+django-jasmine==0.3.2
+django_debug_toolbar
+django-debug-toolbar-mongo
diff --git a/test-requirements.txt b/test-requirements.txt
deleted file mode 100644
index d9db89f107..0000000000
--- a/test-requirements.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-coverage==3.6
-pylint==0.27.0
-pep8==1.4.5
-lettuce==0.2.15
-selenium==2.31.0
-splinter==0.5.0
-