diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index 4908b9e417..d6026fb57a 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 diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index c3fa665b2c..2209e695f5 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 diff --git a/cms/djangoapps/contentstore/tests/test_utils.py b/cms/djangoapps/contentstore/tests/test_utils.py index 6b1b6b7f6f..6fe85ce96d 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): @@ -70,4 +70,4 @@ 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..f41af4e097 100644 --- a/cms/djangoapps/contentstore/tests/utils.py +++ b/cms/djangoapps/contentstore/tests/utils.py @@ -5,109 +5,10 @@ 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/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/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py new file mode 100644 index 0000000000..7cbc591ff5 --- /dev/null +++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py @@ -0,0 +1,103 @@ + +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) + test_modulestore = cls.orig_modulestore + if 'direct' not in test_modulestore: + test_modulestore['direct'] = test_modulestore['default'] + + 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 + 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() + + # 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/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/instructor/test_download_csv.py b/lms/djangoapps/instructor/test_download_csv.py new file mode 100644 index 0000000000..8e4c175faa --- /dev/null +++ b/lms/djangoapps/instructor/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/__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('