diff --git a/lms/djangoapps/instructor/tests/test_legacy_anon_csv.py b/lms/djangoapps/instructor/tests/test_legacy_anon_csv.py
deleted file mode 100644
index 2f298f76dd..0000000000
--- a/lms/djangoapps/instructor/tests/test_legacy_anon_csv.py
+++ /dev/null
@@ -1,67 +0,0 @@
-"""
-Unit tests for instructor dashboard
-
-Based on (and depends on) unit tests for courseware.
-
-Notes for running by hand:
-
-./manage.py lms --settings test test 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 User
-
-from django.core.urlresolvers import reverse
-
-from courseware.tests.helpers import LoginEnrollmentTestCase
-from xmodule.modulestore.tests.django_utils import TEST_DATA_MOCK_MODULESTORE
-import instructor.views.legacy
-from student.roles import CourseStaffRole
-from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
-from xmodule.modulestore.tests.factories import CourseFactory
-
-from mock import Mock, patch
-
-
-@override_settings(MODULESTORE=TEST_DATA_MOCK_MODULESTORE)
-class TestInstructorDashboardAnonCSV(ModuleStoreTestCase, LoginEnrollmentTestCase):
- '''
- Check for download of csv
- '''
-
- # Note -- I copied this setUp from a similar test
- def setUp(self):
- # clear_existing_modulestores()
- self.toy = CourseFactory.create(org='edX', course='toy', display_name='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)
-
- CourseStaffRole(self.toy.id).add_users(User.objects.get(email=self.instructor))
-
- self.logout()
- self.login(self.instructor, self.password)
- self.enroll(self.toy)
-
- @patch.object(instructor.views.legacy, 'anonymous_id_for_user', Mock(return_value='42'))
- @patch.object(instructor.views.legacy, 'unique_id_for_user', Mock(return_value='41'))
- def test_download_anon_csv(self):
- course = self.toy
- url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id.to_deprecated_string()})
- response = self.client.post(url, {'action': 'Download CSV of all student anonymized IDs'})
-
- self.assertEqual(response['Content-Type'], 'text/csv')
- body = response.content.replace('\r', '')
- self.assertEqual(
- body,
- ('"User ID","Anonymized User ID","Course Specific Anonymized User ID"'
- '\n"2","41","42"\n')
- )
diff --git a/lms/djangoapps/instructor/tests/test_legacy_download_csv.py b/lms/djangoapps/instructor/tests/test_legacy_download_csv.py
deleted file mode 100644
index 94f1648e76..0000000000
--- a/lms/djangoapps/instructor/tests/test_legacy_download_csv.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""
-Unit tests for instructor dashboard
-
-Based on (and depends on) unit tests for courseware.
-
-Notes for running by hand:
-
-./manage.py lms --settings test test 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 User
-
-from django.core.urlresolvers import reverse
-
-from courseware.tests.helpers import LoginEnrollmentTestCase
-from xmodule.modulestore.tests.django_utils import TEST_DATA_MOCK_MODULESTORE
-from student.roles import CourseStaffRole
-from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
-from xmodule.modulestore.tests.factories import CourseFactory
-
-
-@override_settings(MODULESTORE=TEST_DATA_MOCK_MODULESTORE)
-class TestInstructorDashboardGradeDownloadCSV(ModuleStoreTestCase, LoginEnrollmentTestCase):
- '''
- Check for download of csv
- '''
-
- def setUp(self):
- # clear_existing_modulestores()
- self.toy = CourseFactory.create(org='edX', course='toy', display_name='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)
-
- CourseStaffRole(self.toy.id).add_users(User.objects.get(email=self.instructor))
-
- 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_legacy', kwargs={'course_id': course.id.to_deprecated_string()})
- 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.to_deprecated_string()), 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","username","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/test_legacy_email.py b/lms/djangoapps/instructor/tests/test_legacy_email.py
deleted file mode 100644
index a740a2ef15..0000000000
--- a/lms/djangoapps/instructor/tests/test_legacy_email.py
+++ /dev/null
@@ -1,144 +0,0 @@
-"""
-Unit tests for email feature flag in legacy instructor dashboard.
-Additionally tests that bulk email is always disabled for non-Mongo
-backed courses, regardless of email feature flag, and that the
-view is conditionally available when Course Auth is turned on.
-"""
-from django.test.utils import override_settings
-from django.conf import settings
-from django.core.urlresolvers import reverse
-from mock import patch
-
-from xmodule.modulestore.tests.django_utils import TEST_DATA_MOCK_MODULESTORE
-from student.tests.factories import AdminFactory
-from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
-from xmodule.modulestore.tests.factories import CourseFactory
-from xmodule.modulestore import ModuleStoreEnum
-
-from bulk_email.models import CourseAuthorization
-
-
-@override_settings(MODULESTORE=TEST_DATA_MOCK_MODULESTORE)
-class TestInstructorDashboardEmailView(ModuleStoreTestCase):
- """
- Check for email view displayed with flag
- """
- def setUp(self):
- self.course = CourseFactory.create()
-
- # Create instructor account
- instructor = AdminFactory.create()
- self.client.login(username=instructor.username, password="test")
-
- # URL for instructor dash
- self.url = reverse('instructor_dashboard_legacy', kwargs={'course_id': self.course.id.to_deprecated_string()})
- # URL for email view
- self.email_link = 'Email '
-
- def tearDown(self):
- """
- Undo all patches.
- """
- patch.stopall()
-
- @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True, 'REQUIRE_COURSE_EMAIL_AUTH': False})
- def test_email_flag_true(self):
- # Assert that the URL for the email view is in the response
- response = self.client.get(self.url)
- self.assertTrue(self.email_link in response.content)
-
- # Select the Email view of the instructor dash
- session = self.client.session
- session[u'idash_mode:{0}'.format(self.course.location.course_key.to_deprecated_string())] = 'Email'
- session.save()
- response = self.client.get(self.url)
-
- # Ensure we've selected the view properly and that the send_to field is present.
- selected_email_link = 'Email '
- self.assertTrue(selected_email_link in response.content)
- send_to_label = 'Send to: '
- self.assertTrue(send_to_label in response.content)
-
- @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True, 'REQUIRE_COURSE_EMAIL_AUTH': True})
- def test_email_flag_unauthorized(self):
- # Assert that the URL for the email view is not in the response
- # email is enabled, but this course is not authorized to send email
- response = self.client.get(self.url)
- self.assertFalse(self.email_link in response.content)
-
- @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True, 'REQUIRE_COURSE_EMAIL_AUTH': True})
- def test_email_flag_authorized(self):
- # Assert that the URL for the email view is in the response
- # email is enabled, and this course is authorized to send email
-
- # Assert that instructor email is not enabled for this course
- self.assertFalse(CourseAuthorization.instructor_email_enabled(self.course.id))
- response = self.client.get(self.url)
- self.assertFalse(self.email_link in response.content)
-
- # Authorize the course to use email
- cauth = CourseAuthorization(course_id=self.course.id, email_enabled=True)
- cauth.save()
-
- # Assert that instructor email is enabled for this course
- self.assertTrue(CourseAuthorization.instructor_email_enabled(self.course.id))
- response = self.client.get(self.url)
- self.assertTrue(self.email_link in response.content)
-
- @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': False})
- def test_email_flag_false(self):
- # Assert that the URL for the email view is not in the response
- response = self.client.get(self.url)
- self.assertFalse(self.email_link in response.content)
-
- @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True})
- def test_email_flag_true_xml_store(self):
- # If the enable email setting is enabled, but this is an XML backed course,
- # the email view shouldn't be available on the instructor dashboard.
-
- # The course factory uses a MongoModuleStore backing, so patch the
- # `get_modulestore_type` method to pretend to be XML-backed.
- # This is OK; we're simply testing that the `is_mongo_modulestore_type` flag
- # in `instructor/views/legacy.py` is doing the correct thing.
-
- with patch('xmodule.modulestore.mongo.base.MongoModuleStore.get_modulestore_type') as mock_modulestore:
- mock_modulestore.return_value = ModuleStoreEnum.Type.xml
-
- # Assert that the URL for the email view is not in the response
- response = self.client.get(self.url)
- self.assertFalse(self.email_link in response.content)
-
- @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True})
- def test_send_mail_unauthorized(self):
- """ Test 'Send email' action returns an error if course is not authorized to send email. """
-
- response = self.client.post(
- self.url, {
- 'action': 'Send email',
- 'to_option': 'all',
- 'subject': "Welcome to the course!",
- 'message': "Lets start with an introduction!"
- }
- )
- self.assertContains(response, "Email is not enabled for this course.")
-
- @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True})
- def test_send_mail_authorized(self):
- """ Test 'Send email' action when course is authorized to send email. """
-
- course_authorization = CourseAuthorization(course_id=self.course.id, email_enabled=True)
- course_authorization.save()
-
- session = self.client.session
- session[u'idash_mode:{0}'.format(self.course.location.course_key.to_deprecated_string())] = 'Email'
- session.save()
-
- response = self.client.post(
- self.url, {
- 'action': 'Send email',
- 'to_option': 'all',
- 'subject': 'Welcome to the course!',
- 'message': 'Lets start with an introduction!',
- }
- )
- self.assertContains(response, "Your email was successfully queued for sending.")
diff --git a/lms/djangoapps/instructor/tests/test_legacy_forum_admin.py b/lms/djangoapps/instructor/tests/test_legacy_forum_admin.py
deleted file mode 100644
index be323c9eac..0000000000
--- a/lms/djangoapps/instructor/tests/test_legacy_forum_admin.py
+++ /dev/null
@@ -1,144 +0,0 @@
-"""
-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
-from django.contrib.auth.models import User
-
-from django.core.urlresolvers import reverse
-from django_comment_common.models import Role, FORUM_ROLE_ADMINISTRATOR, \
- FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_STUDENT
-from django_comment_client.utils import has_forum_access
-
-from courseware.tests.helpers import LoginEnrollmentTestCase
-from xmodule.modulestore.tests.django_utils import TEST_DATA_MOCK_MODULESTORE
-from student.roles import CourseStaffRole
-from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
-from xmodule.modulestore.tests.factories import CourseFactory
-
-
-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'}
-
-
-def action_name(operation, rolename):
- if operation == 'List':
- return '{0} course forum {1}s'.format(operation, FORUM_ADMIN_ACTION_SUFFIX[rolename])
- else:
- return '{0} forum {1}'.format(operation, FORUM_ADMIN_ACTION_SUFFIX[rolename])
-
-
-@override_settings(MODULESTORE=TEST_DATA_MOCK_MODULESTORE)
-class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTestCase):
- '''
- Check for change in forum admin role memberships
- '''
-
- def setUp(self):
- self.toy = CourseFactory.create(org='edX', course='toy', display_name='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)
-
- CourseStaffRole(self.toy.id).add_users(User.objects.get(email=self.instructor))
-
- self.logout()
- self.login(self.instructor, self.password)
- self.enroll(self.toy)
-
- def initialize_roles(self, course_id):
- 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.community_ta_role = Role.objects.get_or_create(name=FORUM_ROLE_COMMUNITY_TA, course_id=course_id)[0]
-
- def test_add_forum_admin_users_for_unknown_user(self):
- course = self.toy
- url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id.to_deprecated_string()})
- username = 'unknown'
- for action in ['Add', 'Remove']:
- for rolename in FORUM_ROLES:
- response = self.client.post(url, {'action': action_name(action, rolename), FORUM_ADMIN_USER[rolename]: username})
- self.assertTrue(response.content.find('Error: unknown username "{0}"'.format(username)) >= 0)
-
- def test_add_forum_admin_users_for_missing_roles(self):
- course = self.toy
- url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id.to_deprecated_string()})
- username = 'u1'
- for action in ['Add', 'Remove']:
- for rolename in FORUM_ROLES:
- response = self.client.post(url, {'action': action_name(action, rolename), FORUM_ADMIN_USER[rolename]: username})
- self.assertTrue(response.content.find('Error: unknown rolename "{0}"'.format(rolename)) >= 0)
-
- def test_remove_forum_admin_users_for_missing_users(self):
- course = self.toy
- self.initialize_roles(course.id)
- url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id.to_deprecated_string()})
- username = 'u1'
- action = 'Remove'
- for rolename in FORUM_ROLES:
- response = self.client.post(url, {'action': action_name(action, rolename), FORUM_ADMIN_USER[rolename]: username})
- self.assertTrue(response.content.find('Error: user "{0}" does not have rolename "{1}"'.format(username, rolename)) >= 0)
-
- def test_add_and_remove_forum_admin_users(self):
- course = self.toy
- self.initialize_roles(course.id)
- url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id.to_deprecated_string()})
- username = 'u2'
- for rolename in FORUM_ROLES:
- response = self.client.post(url, {'action': action_name('Add', rolename), FORUM_ADMIN_USER[rolename]: username})
- self.assertContains(response, 'Added "{0}" to "{1}" forum role = "{2}"'.format(username, course.id.to_deprecated_string(), rolename))
- self.assertTrue(has_forum_access(username, course.id, rolename))
- response = self.client.post(url, {'action': action_name('Remove', rolename), FORUM_ADMIN_USER[rolename]: username})
- self.assertContains(response, 'Removed "{0}" from "{1}" forum role = "{2}"'.format(username, course.id.to_deprecated_string(), rolename))
- self.assertFalse(has_forum_access(username, course.id, rolename))
-
- def test_add_and_read_forum_admin_users(self):
- course = self.toy
- self.initialize_roles(course.id)
- url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id.to_deprecated_string()})
- username = 'u2'
- for rolename in FORUM_ROLES:
- # perform an add, and follow with a second identical add:
- self.client.post(url, {'action': action_name('Add', rolename), FORUM_ADMIN_USER[rolename]: username})
- response = self.client.post(url, {'action': action_name('Add', rolename), FORUM_ADMIN_USER[rolename]: username})
- self.assertTrue(response.content.find('Error: user "{0}" already has rolename "{1}", cannot add'.format(username, rolename)) >= 0)
- self.assertTrue(has_forum_access(username, course.id, rolename))
-
- def test_add_nonstaff_forum_admin_users(self):
- course = self.toy
- self.initialize_roles(course.id)
- url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id.to_deprecated_string()})
- username = 'u1'
- rolename = FORUM_ROLE_ADMINISTRATOR
- response = self.client.post(url, {'action': action_name('Add', rolename), FORUM_ADMIN_USER[rolename]: username})
- self.assertTrue(response.content.find('Error: user "{0}" should first be added as staff'.format(username)) >= 0)
-
- def test_list_forum_admin_users(self):
- course = self.toy
- self.initialize_roles(course.id)
- url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id.to_deprecated_string()})
- username = 'u2'
- added_roles = [FORUM_ROLE_STUDENT] # u2 is already added as a student to the discussion forums
- self.assertTrue(has_forum_access(username, course.id, 'Student'))
- for rolename in FORUM_ROLES:
- response = self.client.post(url, {'action': action_name('Add', rolename), FORUM_ADMIN_USER[rolename]: username})
- self.assertTrue(has_forum_access(username, course.id, rolename))
- response = self.client.post(url, {'action': action_name('List', rolename), FORUM_ADMIN_USER[rolename]: username})
- for header in ['Username', 'Full name', 'Roles']:
- self.assertTrue(response.content.find('
{0} '.format(header)) > 0)
- self.assertTrue(response.content.find('{0} '.format(username)) >= 0)
- # concatenate all roles for user, in sorted order:
- 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))
diff --git a/lms/djangoapps/instructor/tests/test_legacy_reset.py b/lms/djangoapps/instructor/tests/test_legacy_reset.py
deleted file mode 100644
index de2d51851a..0000000000
--- a/lms/djangoapps/instructor/tests/test_legacy_reset.py
+++ /dev/null
@@ -1,70 +0,0 @@
-"""
-View-level tests for resetting student state in legacy instructor dash.
-"""
-
-import json
-from django.core.urlresolvers import reverse
-from django.test.utils import override_settings
-
-from courseware.tests.helpers import LoginEnrollmentTestCase
-from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
-from xmodule.modulestore.tests.django_utils import TEST_DATA_MOCK_MODULESTORE
-from xmodule.modulestore.tests.factories import CourseFactory
-from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentFactory
-
-from courseware.models import StudentModule
-
-from submissions import api as sub_api
-from student.models import anonymous_id_for_user
-
-
-@override_settings(MODULESTORE=TEST_DATA_MOCK_MODULESTORE)
-class InstructorResetStudentStateTest(ModuleStoreTestCase, LoginEnrollmentTestCase):
- """
- Reset student state from the legacy instructor dash.
- """
-
- def setUp(self):
- """
- Log in as an instructor, and create a course/student to reset.
- """
- instructor = AdminFactory.create()
- self.client.login(username=instructor.username, password='test')
- self.student = UserFactory.create(username='test', email='test@example.com')
- self.course = CourseFactory.create()
- CourseEnrollmentFactory.create(user=self.student, course_id=self.course.id)
-
- def test_delete_student_state_resets_scores(self):
- problem_location = self.course.id.make_usage_key('dummy', 'module')
-
- # Create a student module for the user
- StudentModule.objects.create(
- student=self.student,
- course_id=self.course.id,
- module_state_key=problem_location,
- state=json.dumps({})
- )
-
- # Create a submission and score for the student using the submissions API
- student_item = {
- 'student_id': anonymous_id_for_user(self.student, self.course.id),
- 'course_id': self.course.id.to_deprecated_string(),
- 'item_id': problem_location.to_deprecated_string(),
- 'item_type': 'openassessment'
- }
- submission = sub_api.create_submission(student_item, 'test answer')
- sub_api.set_score(submission['uuid'], 1, 2)
-
- # Delete student state using the instructor dash
- url = reverse('instructor_dashboard_legacy', kwargs={'course_id': self.course.id.to_deprecated_string()})
- response = self.client.post(url, {
- 'action': 'Delete student state for module',
- 'unique_student_identifier': self.student.email,
- 'problem_for_student': problem_location.to_deprecated_string(),
- })
-
- self.assertEqual(response.status_code, 200)
-
- # Verify that the student's scores have been reset in the submissions API
- score = sub_api.get_score(student_item)
- self.assertIs(score, None)
diff --git a/lms/djangoapps/instructor/tests/test_legacy_xss.py b/lms/djangoapps/instructor/tests/test_legacy_xss.py
index fb5302edf6..e8218dcea4 100644
--- a/lms/djangoapps/instructor/tests/test_legacy_xss.py
+++ b/lms/djangoapps/instructor/tests/test_legacy_xss.py
@@ -63,6 +63,3 @@ class TestXss(ModuleStoreTestCase):
def test_dump_list_of_enrolled(self):
self._test_action("Dump list of enrolled students")
-
- def test_dump_grades(self):
- self._test_action("Dump Grades for all students in this course")
diff --git a/lms/djangoapps/instructor/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py
index 8e8a48513a..afcd5c8c4d 100644
--- a/lms/djangoapps/instructor/views/legacy.py
+++ b/lms/djangoapps/instructor/views/legacy.py
@@ -3,7 +3,6 @@ Instructor Views
"""
## NOTE: This is the code for the legacy instructor dashboard
## We are no longer supporting this file or accepting changes into it.
-# pylint: skip-file
from contextlib import contextmanager
import csv
import json
@@ -27,39 +26,22 @@ from django.core.urlresolvers import reverse
from django.core.mail import send_mail
from django.utils import timezone
-from xmodule_modifiers import wrap_xblock, request_token
import xmodule.graders as xmgraders
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from opaque_keys.edx.locations import SlashSeparatedCourseKey
-from xmodule.modulestore.exceptions import ItemNotFoundError
-from xmodule.html_module import HtmlDescriptor
-from opaque_keys import InvalidKeyError
-from lms.lib.xblock.runtime import quote_slashes
-from submissions import api as sub_api # installed from the edx-submissions repository
-
-from bulk_email.models import CourseEmail, CourseAuthorization
from courseware import grades
from courseware.access import has_access
from courseware.courses import get_course_with_access, get_cms_course_link
-from student.roles import (
- CourseStaffRole, CourseInstructorRole, CourseBetaTesterRole, GlobalStaff
-)
from courseware.models import StudentModule
-from django_comment_common.models import (
- Role, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA
-)
+from django_comment_common.models import FORUM_ROLE_ADMINISTRATOR
from django_comment_client.utils import has_forum_access
from instructor.offline_gradecalc import student_grades, offline_grades_available
from instructor.views.tools import strip_if_string, bulk_email_is_enabled_for_course, add_block_ids
from instructor_task.api import (
get_running_instructor_tasks,
get_instructor_task_history,
- submit_rescore_problem_for_all_students,
- submit_rescore_problem_for_student,
- submit_reset_problem_attempts_for_all_students,
- submit_bulk_course_email
)
from instructor_task.views import get_task_completion_info
from edxmako.shortcuts import render_to_response, render_to_string
@@ -68,12 +50,8 @@ from psychometrics import psychoanalyze
from student.models import (
CourseEnrollment,
CourseEnrollmentAllowed,
- unique_id_for_user,
- anonymous_id_for_user
)
import track.views
-from xblock.field_data import DictFieldData
-from xblock.fields import ScopeIds
from django.utils.translation import ugettext as _
from microsite_configuration import microsite
@@ -108,10 +86,6 @@ def instructor_dashboard(request, course_id):
forum_admin_access = has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR)
msg = ''
- email_msg = ''
- email_to_option = None
- email_subject = None
- html_message = ''
show_email_tab = False
problems = []
plots = []
@@ -172,23 +146,6 @@ def instructor_dashboard(request, course_id):
writer.writerow(encoded_row)
return response
- def get_student_from_identifier(unique_student_identifier):
- """Gets a student object using either an email address or username"""
- unique_student_identifier = strip_if_string(unique_student_identifier)
- msg = ""
- try:
- if "@" in unique_student_identifier:
- student = User.objects.get(email=unique_student_identifier)
- else:
- student = User.objects.get(username=unique_student_identifier)
- msg += _("Found a single student. ")
- except User.DoesNotExist:
- student = None
- msg += "{text} ".format(
- text=_("Couldn't find student with that email or username.")
- )
- return msg, student
-
# process actions from form POST
action = request.POST.get('action', '')
use_offline = request.POST.get('use_offline_grades', False)
@@ -227,12 +184,6 @@ def instructor_dashboard(request, course_id):
datatable['title'] = _('List of students enrolled in {course_key}').format(course_key=course_key.to_deprecated_string())
track.views.server_track(request, "list-students", {}, page="idashboard")
- elif 'Dump Grades' in action:
- log.debug(action)
- datatable = get_student_grade_summary_data(request, course, get_grades=True, use_offline=use_offline)
- datatable['title'] = _('Summary Grades of students enrolled in {course_key}').format(course_key=course_key.to_deprecated_string())
- track.views.server_track(request, "dump-grades", {}, page="idashboard")
-
elif 'Dump all RAW grades' in action:
log.debug(action)
datatable = get_student_grade_summary_data(request, course, get_grades=True,
@@ -240,11 +191,6 @@ def instructor_dashboard(request, course_id):
datatable['title'] = _('Raw Grades of students enrolled in {course_key}').format(course_key=course_key)
track.views.server_track(request, "dump-grades-raw", {}, page="idashboard")
- elif 'Download CSV of all student grades' in action:
- track.views.server_track(request, "dump-grades-csv", {}, page="idashboard")
- return return_csv('grades_{0}.csv'.format(course_key.to_deprecated_string()),
- get_student_grade_summary_data(request, course, use_offline=use_offline))
-
elif 'Download CSV of all RAW grades' in action:
track.views.server_track(request, "dump-grades-csv-raw", {}, page="idashboard")
return return_csv('grades_{0}_raw.csv'.format(course_key.to_deprecated_string()),
@@ -254,279 +200,6 @@ def instructor_dashboard(request, course_id):
track.views.server_track(request, "dump-answer-dist-csv", {}, page="idashboard")
return return_csv('answer_dist_{0}.csv'.format(course_key.to_deprecated_string()), get_answers_distribution(request, course_key))
- elif 'Dump description of graded assignments configuration' in action:
- # what is "graded assignments configuration"?
- track.views.server_track(request, "dump-graded-assignments-config", {}, page="idashboard")
- msg += dump_grading_context(course)
-
- elif "Rescore ALL students' problem submissions" in action:
- problem_location_str = strip_if_string(request.POST.get('problem_for_all_students', ''))
- try:
- problem_location = course_key.make_usage_key_from_deprecated_string(problem_location_str)
- instructor_task = submit_rescore_problem_for_all_students(request, problem_location)
- if instructor_task is None:
- msg += '{text} '.format(
- text=_('Failed to create a background task for rescoring "{problem_url}".').format(
- problem_url=problem_location_str
- )
- )
- else:
- track.views.server_track(
- request,
- "rescore-all-submissions",
- {
- "problem": problem_location_str,
- "course": course_key.to_deprecated_string()
- },
- page="idashboard"
- )
-
- except (InvalidKeyError, ItemNotFoundError) as err:
- msg += '{text} '.format(
- text=_('Failed to create a background task for rescoring "{problem_url}": problem not found.').format(
- problem_url=problem_location_str
- )
- )
- except Exception as err: # pylint: disable=broad-except
- log.error("Encountered exception from rescore: {0}".format(err))
- msg += '{text} '.format(
- text=_('Failed to create a background task for rescoring "{url}": {message}.').format(
- url=problem_location_str, message=err.message
- )
- )
-
- elif "Reset ALL students' attempts" in action:
- problem_location_str = strip_if_string(request.POST.get('problem_for_all_students', ''))
- try:
- problem_location = course_key.make_usage_key_from_deprecated_string(problem_location_str)
- instructor_task = submit_reset_problem_attempts_for_all_students(request, problem_location)
- if instructor_task is None:
- msg += '{text} '.format(
- text=_('Failed to create a background task for resetting "{problem_url}".').format(problem_url=problem_location_str)
- )
- else:
- track.views.server_track(
- request,
- "reset-all-attempts",
- {
- "problem": problem_location_str,
- "course": course_key.to_deprecated_string()
- },
- page="idashboard"
- )
- except (InvalidKeyError, ItemNotFoundError) as err:
- log.error('Failure to reset: unknown problem "{0}"'.format(err))
- msg += '{text} '.format(
- text=_('Failed to create a background task for resetting "{problem_url}": problem not found.').format(
- problem_url=problem_location_str
- )
- )
- except Exception as err: # pylint: disable=broad-except
- log.error("Encountered exception from reset: {0}".format(err))
- msg += '{text} '.format(
- text=_('Failed to create a background task for resetting "{url}": {message}.').format(
- url=problem_location_str, message=err.message
- )
- )
-
- elif "Show Background Task History for Student" in action:
- # put this before the non-student case, since the use of "in" will cause this to be missed
- unique_student_identifier = request.POST.get('unique_student_identifier', '')
- message, student = get_student_from_identifier(unique_student_identifier)
- if student is None:
- msg += message
- else:
- problem_location_str = strip_if_string(request.POST.get('problem_for_student', ''))
- try:
- problem_location = course_key.make_usage_key_from_deprecated_string(problem_location_str)
- except InvalidKeyError:
- msg += '{text} '.format(
- text=_('Could not find problem location "{url}".').format(
- url=problem_location_str
- )
- )
- else:
- message, datatable = get_background_task_table(course_key, problem_location, student)
- msg += message
-
- elif "Show Background Task History" in action:
- problem_location_str = strip_if_string(request.POST.get('problem_for_all_students', ''))
- try:
- problem_location = course_key.make_usage_key_from_deprecated_string(problem_location_str)
- except InvalidKeyError:
- msg += '{text} '.format(
- text=_('Could not find problem location "{url}".').format(
- url=problem_location_str
- )
- )
- else:
- message, datatable = get_background_task_table(course_key, problem_location)
- msg += message
-
- elif ("Reset student's attempts" in action or
- "Delete student state for module" in action or
- "Rescore student's problem submission" in action):
- # get the form data
- unique_student_identifier = request.POST.get(
- 'unique_student_identifier', ''
- )
- problem_location_str = strip_if_string(request.POST.get('problem_for_student', ''))
- try:
- module_state_key = course_key.make_usage_key_from_deprecated_string(problem_location_str)
- except InvalidKeyError:
- msg += '{text} '.format(
- text=_('Could not find problem location "{url}".').format(
- url=problem_location_str
- )
- )
- else:
- # try to uniquely id student by email address or username
- message, student = get_student_from_identifier(unique_student_identifier)
- msg += message
- student_module = None
- if student is not None:
- # Reset the student's score in the submissions API
- # Currently this is used only by open assessment (ORA 2)
- # We need to do this *before* retrieving the `StudentModule` model,
- # because it's possible for a score to exist even if no student module exists.
- if "Delete student state for module" in action:
- try:
- sub_api.reset_score(
- anonymous_id_for_user(student, course_key),
- course_key.to_deprecated_string(),
- module_state_key.to_deprecated_string(),
- )
- except sub_api.SubmissionError:
- # Trust the submissions API to log the error
- error_msg = _("An error occurred while deleting the score.")
- msg += "{err} ".format(err=error_msg)
-
- # find the module in question
- try:
- student_module = StudentModule.objects.get(
- student_id=student.id,
- course_id=course_key,
- module_state_key=module_state_key
- )
- msg += _("Found module. ")
-
- except StudentModule.DoesNotExist as err:
- error_msg = _("Couldn't find module with that urlname: {url}. ").format(url=problem_location_str)
- msg += "{err_msg} ({err}) ".format(err_msg=error_msg, err=err)
- log.debug(error_msg)
-
- if student_module is not None:
- if "Delete student state for module" in action:
- # delete the state
- try:
- student_module.delete()
-
- msg += "{text} ".format(
- text=_("Deleted student module state for {state}!").format(state=module_state_key)
- )
- event = {
- "problem": problem_location_str,
- "student": unique_student_identifier,
- "course": course_key.to_deprecated_string()
- }
- track.views.server_track(
- request,
- "delete-student-module-state",
- event,
- page="idashboard"
- )
- except Exception as err: # pylint: disable=broad-except
- error_msg = _("Failed to delete module state for {id}/{url}. ").format(
- id=unique_student_identifier, url=problem_location_str
- )
- msg += "{err_msg} ({err}) ".format(err_msg=error_msg, err=err)
- log.exception(error_msg)
- elif "Reset student's attempts" in action:
- # modify the problem's state
- try:
- # load the state json
- problem_state = json.loads(student_module.state)
- old_number_of_attempts = problem_state["attempts"]
- problem_state["attempts"] = 0
- # save
- student_module.state = json.dumps(problem_state)
- student_module.save()
- event = {
- "old_attempts": old_number_of_attempts,
- "student": unicode(student),
- "problem": student_module.module_state_key,
- "instructor": unicode(request.user),
- "course": course_key.to_deprecated_string()
- }
- track.views.server_track(request, "reset-student-attempts", event, page="idashboard")
- msg += "{text} ".format(
- text=_("Module state successfully reset!")
- )
- except Exception as err: # pylint: disable=broad-except
- error_msg = _("Couldn't reset module state for {id}/{url}. ").format(
- id=unique_student_identifier, url=problem_location_str
- )
- msg += "{err_msg} ({err}) ".format(err_msg=error_msg, err=err)
- log.exception(error_msg)
- else:
- # "Rescore student's problem submission" case
- try:
- instructor_task = submit_rescore_problem_for_student(request, module_state_key, student)
- if instructor_task is None:
- msg += '{text} '.format(
- text=_('Failed to create a background task for rescoring "{key}" for student {id}.').format(
- key=module_state_key, id=unique_student_identifier
- )
- )
- else:
- track.views.server_track(
- request,
- "rescore-student-submission",
- {
- "problem": module_state_key,
- "student": unique_student_identifier,
- "course": course_key.to_deprecated_string()
- },
- page="idashboard"
- )
- except Exception as err: # pylint: disable=broad-except
- msg += '{text} '.format(
- text=_('Failed to create a background task for rescoring "{key}": {id}.').format(
- key=module_state_key, id=err.message
- )
- )
- log.exception("Encountered exception from rescore: student '{0}' problem '{1}'".format(
- unique_student_identifier, module_state_key
- )
- )
-
- elif "Get link to student's progress page" in action:
- unique_student_identifier = request.POST.get('unique_student_identifier', '')
- # try to uniquely id student by email address or username
- message, student = get_student_from_identifier(unique_student_identifier)
- msg += message
- if student is not None:
- progress_url = reverse('student_progress', kwargs={
- 'course_id': course_key.to_deprecated_string(),
- 'student_id': student.id
- })
- track.views.server_track(
- request,
- "get-student-progress-page",
- {
- "student": unicode(student),
- "instructor": unicode(request.user),
- "course": course_key.to_deprecated_string()
- },
- page="idashboard"
- )
- msg += "{text} .".format(
- url=progress_url,
- text=_("Progress page for username: {username} with email address: {email}").format(
- username=student.username, email=student.email
- )
- )
-
#----------------------------------------
# export grades to remote gradebook
@@ -551,8 +224,9 @@ def instructor_dashboard(request, course_id):
datatable = {'header': ['Student email', 'Match?']}
rg_students = [x['email'] for x in rg_stud_data['retdata']]
- def domatch(x):
- return 'yes' if x.email in rg_students else 'No'
+ def domatch(student):
+ """Returns 'yes' if student is pressent in the remote gradebook student list, else returns 'No'"""
+ return 'yes' if student.email in rg_students else 'No'
datatable['data'] = [[x.email, domatch(x)] for x in stud_data['students']]
datatable['title'] = action
@@ -597,67 +271,9 @@ def instructor_dashboard(request, course_id):
msg2, __ = _do_remote_gradebook(request.user, course, 'post-grades', files=files)
msg += msg2
- #----------------------------------------
- # Admin
-
- elif 'List course staff' in action:
- role = CourseStaffRole(course.id)
- datatable = _role_members_table(role, _("List of Staff"), course_key)
- track.views.server_track(request, "list-staff", {}, page="idashboard")
-
- elif 'List course instructors' in action and GlobalStaff().has_user(request.user):
- role = CourseInstructorRole(course.id)
- datatable = _role_members_table(role, _("List of Instructors"), course_key)
- track.views.server_track(request, "list-instructors", {}, page="idashboard")
-
- elif action == 'Add course staff':
- uname = request.POST['staffuser']
- role = CourseStaffRole(course.id)
- msg += add_user_to_role(request, uname, role, 'staff', 'staff')
-
- elif action == 'Add instructor' and request.user.is_staff:
- uname = request.POST['instructor']
- role = CourseInstructorRole(course.id)
- msg += add_user_to_role(request, uname, role, 'instructor', 'instructor')
-
- elif action == 'Remove course staff':
- uname = request.POST['staffuser']
- role = CourseStaffRole(course.id)
- msg += remove_user_from_role(request, uname, role, 'staff', 'staff')
-
- elif action == 'Remove instructor' and request.user.is_staff:
- uname = request.POST['instructor']
- role = CourseInstructorRole(course.id)
- msg += remove_user_from_role(request, uname, role, 'instructor', 'instructor')
-
#----------------------------------------
# DataDump
- elif 'Download CSV of all student profile data' in action:
- enrolled_students = User.objects.filter(
- courseenrollment__course_id=course_key,
- courseenrollment__is_active=1,
- ).order_by('username').select_related("profile")
- profkeys = ['name', 'language', 'location', 'year_of_birth', 'gender', 'level_of_education',
- 'mailing_address', 'goals']
- datatable = {'header': ['username', 'email'] + profkeys}
-
- def getdat(user):
- """
- Return a list of profile data for the given user.
- """
- profile = user.profile
- return [user.username, user.email] + [getattr(profile, xkey, '') for xkey in profkeys]
-
- datatable['data'] = [getdat(u) for u in enrolled_students]
- datatable['title'] = _('Student profile data for course {course_id}').format(
- course_id=course_key.to_deprecated_string()
- )
- return return_csv(
- 'profiledata_{course_id}.csv'.format(course_id=course_key.to_deprecated_string()),
- datatable
- )
-
elif 'Download CSV of all responses to problem' in action:
problem_to_dump = request.POST.get('problem_to_dump', '')
@@ -684,118 +300,6 @@ def instructor_dashboard(request, course_id):
datatable['title'] = _('Student state for problem {problem}').format(problem=problem_to_dump)
return return_csv('student_state_from_{problem}.csv'.format(problem=problem_to_dump), datatable)
- elif 'Download CSV of all student anonymized IDs' in action:
- students = User.objects.filter(
- courseenrollment__course_id=course_key,
- ).order_by('id')
-
- datatable = {'header': ['User ID', 'Anonymized User ID', 'Course Specific Anonymized User ID']}
- datatable['data'] = [[s.id, unique_id_for_user(s, save=False), anonymous_id_for_user(s, course_key, save=False)] for s in students]
- return return_csv(course_key.to_deprecated_string().replace('/', '-') + '-anon-ids.csv', datatable)
-
- #----------------------------------------
- # Group management
-
- elif 'List beta testers' in action:
- role = CourseBetaTesterRole(course.id)
- datatable = _role_members_table(role, _("List of Beta Testers"), course_key)
- track.views.server_track(request, "list-beta-testers", {}, page="idashboard")
-
- elif action == 'Add beta testers':
- users = request.POST['betausers']
- log.debug("users: {0!r}".format(users))
- role = CourseBetaTesterRole(course.id)
- for username_or_email in split_by_comma_and_whitespace(users):
- msg += "{0}
".format(
- add_user_to_role(request, username_or_email, role, 'beta testers', 'beta-tester'))
-
- elif action == 'Remove beta testers':
- users = request.POST['betausers']
- role = CourseBetaTesterRole(course.id)
- for username_or_email in split_by_comma_and_whitespace(users):
- msg += "{0}
".format(
- remove_user_from_role(request, username_or_email, role, 'beta testers', 'beta-tester'))
-
- #----------------------------------------
- # forum administration
-
- elif action == 'List course forum admins':
- rolename = FORUM_ROLE_ADMINISTRATOR
- datatable = {}
- msg += _list_course_forum_members(course_key, rolename, datatable)
- track.views.server_track(
- request, "list-forum-admins", {"course": course_key.to_deprecated_string()}, page="idashboard"
- )
-
- elif action == 'Remove forum admin':
- uname = request.POST['forumadmin']
- msg += _update_forum_role_membership(uname, course, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_REMOVE)
- track.views.server_track(
- request, "remove-forum-admin", {"username": uname, "course": course_key.to_deprecated_string()},
- page="idashboard"
- )
-
- elif action == 'Add forum admin':
- uname = request.POST['forumadmin']
- msg += _update_forum_role_membership(uname, course, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_ADD)
- track.views.server_track(
- request, "add-forum-admin", {"username": uname, "course": course_key.to_deprecated_string()},
- page="idashboard"
- )
-
- elif action == 'List course forum moderators':
- rolename = FORUM_ROLE_MODERATOR
- datatable = {}
- msg += _list_course_forum_members(course_key, rolename, datatable)
- track.views.server_track(
- request, "list-forum-mods", {"course": course_key.to_deprecated_string()}, page="idashboard"
- )
-
- elif action == 'Remove forum moderator':
- uname = request.POST['forummoderator']
- msg += _update_forum_role_membership(uname, course, FORUM_ROLE_MODERATOR, FORUM_ROLE_REMOVE)
- track.views.server_track(
- request, "remove-forum-mod", {"username": uname, "course": course_key.to_deprecated_string()},
- page="idashboard"
- )
-
- elif action == 'Add forum moderator':
- uname = request.POST['forummoderator']
- msg += _update_forum_role_membership(uname, course, FORUM_ROLE_MODERATOR, FORUM_ROLE_ADD)
- track.views.server_track(
- request, "add-forum-mod", {"username": uname, "course": course_key.to_deprecated_string()},
- page="idashboard"
- )
-
- elif action == 'List course forum community TAs':
- rolename = FORUM_ROLE_COMMUNITY_TA
- datatable = {}
- msg += _list_course_forum_members(course_key, rolename, datatable)
- track.views.server_track(
- request, "list-forum-community-TAs", {"course": course_key.to_deprecated_string()},
- page="idashboard"
- )
-
- elif action == 'Remove forum community TA':
- uname = request.POST['forummoderator']
- msg += _update_forum_role_membership(uname, course, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_REMOVE)
- track.views.server_track(
- request, "remove-forum-community-TA", {
- "username": uname, "course": course_key.to_deprecated_string()
- },
- page="idashboard"
- )
-
- elif action == 'Add forum community TA':
- uname = request.POST['forummoderator']
- msg += _update_forum_role_membership(uname, course, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_ADD)
- track.views.server_track(
- request, "add-forum-community-TA", {
- "username": uname, "course": course_key.to_deprecated_string()
- },
- page="idashboard"
- )
-
#----------------------------------------
# enrollment
@@ -842,55 +346,6 @@ def instructor_dashboard(request, course_id):
ret = _do_enroll_students(course, course_key, students, secure=secure, overload=overload)
datatable = ret['datatable']
- #----------------------------------------
- # email
-
- elif action == 'Send email':
- email_to_option = request.POST.get("to_option")
- email_subject = request.POST.get("subject")
- html_message = request.POST.get("message")
-
- if bulk_email_is_enabled_for_course(course_key):
- try:
- # Create the CourseEmail object. This is saved immediately, so that
- # any transaction that has been pending up to this point will also be
- # committed.
- email = CourseEmail.create(
- course_key.to_deprecated_string(), request.user, email_to_option, email_subject, html_message
- )
-
- # Submit the task, so that the correct InstructorTask object gets created (for monitoring purposes)
- submit_bulk_course_email(request, course_key, email.id) # pylint: disable=no-member
-
- except Exception as err: # pylint: disable=broad-except
- # Catch any errors and deliver a message to the user
- error_msg = "Failed to send email! ({0})".format(err)
- msg += "" + error_msg + " "
- log.exception(error_msg)
-
- else:
- # If sending the task succeeds, deliver a success message to the user.
- if email_to_option == "all":
- text = _(
- "Your email was successfully queued for sending. "
- "Please note that for large classes, it may take up to an hour "
- "(or more, if other courses are simultaneously sending email) "
- "to send all emails."
- )
- else:
- text = _('Your email was successfully queued for sending.')
- email_msg = ''.format(text=text)
- else:
- msg += "Email is not enabled for this course. "
-
- elif "Show Background Email Task History" in action:
- message, datatable = get_background_task_table(course_key, task_type='bulk_course_email')
- msg += message
-
- elif "Show Background Email Task History" in action:
- message, datatable = get_background_task_table(course_key, task_type='bulk_course_email')
- msg += message
-
#----------------------------------------
# psychometrics
@@ -933,7 +388,7 @@ def instructor_dashboard(request, course_id):
analytics_results = {}
if idash_mode == 'Analytics':
- DASHBOARD_ANALYTICS = [
+ dashboard_analytics = [
# "StudentsAttemptedProblems", # num students who tried given problem
"StudentsDailyActivity", # active students by day
"StudentsDropoffPerDay", # active students dropoff by day
@@ -942,7 +397,7 @@ def instructor_dashboard(request, course_id):
"ProblemGradeDistribution", # foreach problem, grade distribution
]
- for analytic_name in DASHBOARD_ANALYTICS:
+ for analytic_name in dashboard_analytics:
analytics_results[analytic_name] = get_analytics_result(analytic_name)
#----------------------------------------
@@ -975,27 +430,6 @@ def instructor_dashboard(request, course_id):
if is_studio_course:
studio_url = get_cms_course_link(course)
- email_editor = None
- # HTML editor for email
- if idash_mode == 'Email' and is_studio_course:
- html_module = HtmlDescriptor(
- course.system,
- DictFieldData({'data': html_message}),
- ScopeIds(None, None, None, course_key.make_usage_key('html', 'dummy'))
- )
- fragment = html_module.render('studio_view')
- fragment = wrap_xblock(
- 'LmsRuntime', html_module, 'studio_view', fragment, None,
- extra_data={"course-id": course_key.to_deprecated_string()},
- usage_id_serializer=lambda usage_id: quote_slashes(usage_id.to_deprecated_string()),
- request_token=request_token(request),
- )
- email_editor = fragment.content
-
- # Enable instructor email only if the following conditions are met:
- # 1. Feature flag is on
- # 2. We have explicitly enabled email for the given course via django-admin
- # 3. It is NOT an XML course
if bulk_email_is_enabled_for_course(course_key):
show_email_tab = True
@@ -1025,10 +459,6 @@ def instructor_dashboard(request, course_id):
'modeflag': {idash_mode: 'selectedmode'},
'studio_url': studio_url,
- 'to_option': email_to_option, # email
- 'subject': email_subject, # email
- 'editor': email_editor, # email
- 'email_msg': email_msg, # email
'show_email_tab': show_email_tab, # email
'problems': problems, # psychometrics
@@ -1036,7 +466,6 @@ def instructor_dashboard(request, course_id):
'course_errors': modulestore().get_course_errors(course.id),
'instructor_tasks': instructor_tasks,
'offline_grade_log': offline_grades_available(course_key),
- 'cohorts_ajax_url': reverse('cohorts', kwargs={'course_key_string': course_key.to_deprecated_string()}),
'analytics_results': analytics_results,
'disable_buttons': disable_buttons,
@@ -1045,38 +474,38 @@ def instructor_dashboard(request, course_id):
context['standard_dashboard_url'] = reverse('instructor_dashboard', kwargs={'course_id': course_key.to_deprecated_string()})
- return render_to_response('courseware/instructor_dashboard.html', context)
+ return render_to_response('courseware/legacy_instructor_dashboard.html', context)
def _do_remote_gradebook(user, course, action, args=None, files=None):
'''
Perform remote gradebook action. Returns msg, datatable.
'''
- rg = course.remote_gradebook
- if not rg:
+ rgb = course.remote_gradebook
+ if not rgb:
msg = _("No remote gradebook defined in course metadata")
return msg, {}
- rgurl = settings.FEATURES.get('REMOTE_GRADEBOOK_URL', '')
- if not rgurl:
+ rgburl = settings.FEATURES.get('REMOTE_GRADEBOOK_URL', '')
+ if not rgburl:
msg = _("No remote gradebook url defined in settings.FEATURES")
return msg, {}
- rgname = rg.get('name', '')
- if not rgname:
+ rgbname = rgb.get('name', '')
+ if not rgbname:
msg = _("No gradebook name defined in course remote_gradebook metadata")
return msg, {}
if args is None:
args = {}
- data = dict(submit=action, gradebook=rgname, user=user.email)
+ data = dict(submit=action, gradebook=rgbname, user=user.email)
data.update(args)
try:
- resp = requests.post(rgurl, data=data, verify=False, files=files)
+ resp = requests.post(rgburl, data=data, verify=False, files=files)
retdict = json.loads(resp.content)
except Exception as err: # pylint: disable=broad-except
- msg = _("Failed to communicate with gradebook server at {url}").format(url=rgurl) + " "
+ msg = _("Failed to communicate with gradebook server at {url}").format(url=rgburl) + " "
msg += _("Error: {err}").format(err=err)
msg += " resp={resp}".format(resp=resp.content)
msg += " data={data}".format(data=data)
@@ -1096,80 +525,6 @@ def _do_remote_gradebook(user, course, action, args=None, files=None):
return msg, datatable
-def _list_course_forum_members(course_key, rolename, datatable):
- """
- Fills in datatable with forum membership information, for a given role,
- so that it will be displayed on instructor dashboard.
-
- course_ID = the CourseKey for a course
- rolename = one of "Administrator", "Moderator", "Community TA"
-
- Returns message status string to append to displayed message, if role is unknown.
- """
- # make sure datatable is set up properly for display first, before checking for errors
- datatable['header'] = [_('Username'), _('Full name'), _('Roles')]
- datatable['title'] = _('List of Forum {name}s in course {id}').format(
- name=rolename, id=course_key.to_deprecated_string()
- )
- datatable['data'] = []
- try:
- role = Role.objects.get(name=rolename, course_id=course_key)
- except Role.DoesNotExist:
- return '' + _('Error: unknown rolename "{rolename}"').format(rolename=rolename) + ' '
- uset = role.users.all().order_by('username')
- msg = 'Role = {0}'.format(rolename)
- log.debug('role={0}'.format(rolename))
- datatable['data'] = [[x.username, x.profile.name, ', '.join([
- r.name for r in x.roles.filter(course_id=course_key).order_by('name')
- ])] for x in uset]
- return msg
-
-
-def _update_forum_role_membership(uname, course, rolename, add_or_remove):
- '''
- Supports adding a user to a course's forum role
-
- uname = username string for user
- course = course object
- rolename = one of "Administrator", "Moderator", "Community TA"
- add_or_remove = one of "add" or "remove"
-
- Returns message status string to append to displayed message, Status is returned if user
- or role is unknown, or if entry already exists when adding, or if entry doesn't exist when removing.
- '''
- # check that username and rolename are valid:
- try:
- user = User.objects.get(username=uname)
- except User.DoesNotExist:
- return '' + _('Error: unknown username "{username}"').format(username=uname) + ' '
- try:
- role = Role.objects.get(name=rolename, course_id=course.id)
- except Role.DoesNotExist:
- return '' + _('Error: unknown rolename "{rolename}"').format(rolename=rolename) + ' '
-
- # check whether role already has the specified user:
- alreadyexists = role.users.filter(username=uname).exists()
- msg = ''
- log.debug('rolename={0}'.format(rolename))
- if add_or_remove == FORUM_ROLE_REMOVE:
- if not alreadyexists:
- msg = '' + _('Error: user "{username}" does not have rolename "{rolename}", cannot remove').format(username=uname, rolename=rolename) + ' '
- else:
- user.roles.remove(role)
- msg = '' + _('Removed "{username}" from "{course_id}" forum role = "{rolename}"').format(username=user, course_id=course.id.to_deprecated_string(), rolename=rolename) + ' '
- else:
- if alreadyexists:
- msg = '' + _('Error: user "{username}" already has rolename "{rolename}", cannot add').format(username=uname, rolename=rolename) + ' '
- else:
- if (rolename == FORUM_ROLE_ADMINISTRATOR and not has_access(user, 'staff', course)):
- msg = '' + _('Error: user "{username}" should first be added as staff before adding as a forum administrator, cannot add').format(username=uname) + ' '
- else:
- user.roles.add(role)
- msg = '' + _('Added "{username}" to "{course_id}" forum role = "{rolename}"').format(username=user, course_id=course.id.to_deprecated_string(), rolename=rolename) + ' '
-
- return msg
-
-
def _role_members_table(role, title, course_key):
"""
Return a data table of usernames and names of users in group_name.
@@ -1389,7 +744,7 @@ def get_student_grade_summary_data(request, course, get_grades=True, get_raw_sco
datarow = [student.id, student.username, student.profile.name, student.email]
try:
datarow.append(student.externalauthmap.external_email)
- except: # ExternalAuthMap.DoesNotExist
+ except Exception: # pylint: disable=broad-except
datarow.append('')
if get_grades:
@@ -1448,12 +803,12 @@ def _do_enroll_students(course, course_key, students, secure=False, overload=Fal
if overload: # delete all but staff
todelete = CourseEnrollment.objects.filter(course_id=course_key)
- for ce in todelete:
- if not has_access(ce.user, 'staff', course) and ce.user.email.lower() not in new_students_lc:
- status[ce.user.email] = 'deleted'
- ce.deactivate()
+ for enrollee in todelete:
+ if not has_access(enrollee.user, 'staff', course) and enrollee.user.email.lower() not in new_students_lc:
+ status[enrollee.user.email] = 'deleted'
+ enrollee.deactivate()
else:
- status[ce.user.email] = 'is staff'
+ status[enrollee.user.email] = 'is staff'
ceaset = CourseEnrollmentAllowed.objects.filter(course_id=course_key)
for cea in ceaset:
status[cea.email] = 'removed from pending enrollment list'
@@ -1487,7 +842,7 @@ def _do_enroll_students(course, course_key, students, secure=False, overload=Fal
)
# Composition of email
- d = {
+ email_data = {
'site_name': stripped_site_name,
'registration_url': registration_url,
'course': course,
@@ -1502,11 +857,11 @@ def _do_enroll_students(course, course_key, students, secure=False, overload=Fal
user = User.objects.get(email=student)
except User.DoesNotExist:
- #Student not signed up yet, put in pending enrollment allowed table
+ # Student not signed up yet, put in pending enrollment allowed table
cea = CourseEnrollmentAllowed.objects.filter(email=student, course_id=course_key)
- #If enrollmentallowed already exists, update auto_enroll flag to however it was set in UI
- #Will be 0 or 1 records as there is a unique key on email + course_id
+ # If enrollmentallowed already exists, update auto_enroll flag to however it was set in UI
+ # Will be 0 or 1 records as there is a unique key on email + course_id
if cea:
cea[0].auto_enroll = auto_enroll
cea[0].save()
@@ -1514,7 +869,7 @@ def _do_enroll_students(course, course_key, students, secure=False, overload=Fal
+ ('on' if auto_enroll else 'off')
continue
- #EnrollmentAllowed doesn't exist so create it
+ # EnrollmentAllowed doesn't exist so create it
cea = CourseEnrollmentAllowed(email=student, course_id=course_key, auto_enroll=auto_enroll)
cea.save()
@@ -1523,9 +878,9 @@ def _do_enroll_students(course, course_key, students, secure=False, overload=Fal
if email_students:
# User is allowed to enroll but has not signed up yet
- d['email_address'] = student
- d['message'] = 'allowed_enroll'
- send_mail_ret = send_mail_to_student(student, d)
+ email_data['email_address'] = student
+ email_data['message'] = 'allowed_enroll'
+ send_mail_ret = send_mail_to_student(student, email_data)
status[student] += (', email sent' if send_mail_ret else '')
continue
@@ -1541,13 +896,13 @@ def _do_enroll_students(course, course_key, students, secure=False, overload=Fal
if email_students:
# User enrolled for first time, populate dict with user specific info
- d['email_address'] = student
- d['full_name'] = user.profile.name
- d['message'] = 'enrolled_enroll'
- send_mail_ret = send_mail_to_student(student, d)
+ email_data['email_address'] = student
+ email_data['full_name'] = user.profile.name
+ email_data['message'] = 'enrolled_enroll'
+ send_mail_ret = send_mail_to_student(student, email_data)
status[student] += (', email sent' if send_mail_ret else '')
- except:
+ except Exception: # pylint: disable=broad-except
status[student] = 'rejected'
datatable = {'header': ['StudentEmail', 'action']}
@@ -1582,15 +937,17 @@ def _do_unenroll_students(course_key, students, email_students=False):
)
if email_students:
course = modulestore().get_course(course_key)
- #Composition of email
- d = {'site_name': stripped_site_name,
- 'course': course}
+ # Composition of email
+ data = {
+ 'site_name': stripped_site_name,
+ 'course': course
+ }
for student in old_students:
isok = False
cea = CourseEnrollmentAllowed.objects.filter(course_id=course_key, email=student)
- #Will be 0 or 1 records as there is a unique key on email + course_id
+ # Will be 0 or 1 records as there is a unique key on email + course_id
if cea:
cea[0].delete()
status[student] = "un-enrolled"
@@ -1601,25 +958,25 @@ def _do_unenroll_students(course_key, students, email_students=False):
except User.DoesNotExist:
if isok and email_students:
- #User was allowed to join but had not signed up yet
- d['email_address'] = student
- d['message'] = 'allowed_unenroll'
- send_mail_ret = send_mail_to_student(student, d)
+ # User was allowed to join but had not signed up yet
+ data['email_address'] = student
+ data['message'] = 'allowed_unenroll'
+ send_mail_ret = send_mail_to_student(student, data)
status[student] += (', email sent' if send_mail_ret else '')
continue
- #Will be 0 or 1 records as there is a unique key on user + course_id
+ # Will be 0 or 1 records as there is a unique key on user + course_id
if CourseEnrollment.is_enrolled(user, course_key):
try:
CourseEnrollment.unenroll(user, course_key)
status[student] = "un-enrolled"
if email_students:
- #User was enrolled
- d['email_address'] = student
- d['full_name'] = user.profile.name
- d['message'] = 'enrolled_unenroll'
- send_mail_ret = send_mail_to_student(student, d)
+ # User was enrolled
+ data['email_address'] = student
+ data['full_name'] = user.profile.name
+ data['message'] = 'enrolled_unenroll'
+ send_mail_ret = send_mail_to_student(student, data)
status[student] += (', email sent' if send_mail_ret else '')
except Exception: # pylint: disable=broad-except
@@ -1630,8 +987,7 @@ def _do_unenroll_students(course_key, students, email_students=False):
datatable['data'] = [[x, status[x]] for x in sorted(status)]
datatable['title'] = _('Un-enrollment of students')
- data = dict(datatable=datatable)
- return data
+ return dict(datatable=datatable)
def send_mail_to_student(student, param_dict):
@@ -1728,15 +1084,15 @@ def get_answers_distribution(request, course_key):
dist = grades.answer_distributions(course.id)
- d = {}
- d['header'] = ['url_name', 'display name', 'answer id', 'answer', 'count']
+ dist = {}
+ dist['header'] = ['url_name', 'display name', 'answer id', 'answer', 'count']
- d['data'] = [
+ dist['data'] = [
[url_name, display_name, answer_id, a, answers[a]]
for (url_name, display_name, answer_id), answers in sorted(dist.items())
for a in answers
]
- return d
+ return dist
#-----------------------------------------------------------------------------
@@ -1758,8 +1114,8 @@ def compute_course_stats(course):
children = module.get_children()
category = module.__class__.__name__ # HtmlDescriptor, CapaDescriptor, ...
counts[category] += 1
- for c in children:
- walk(c)
+ for child in children:
+ walk(child)
walk(course)
stats = dict(counts) # number of each kind of module
diff --git a/lms/static/sass/base/_base.scss b/lms/static/sass/base/_base.scss
index 4a9fbcc1fc..f5437d302e 100644
--- a/lms/static/sass/base/_base.scss
+++ b/lms/static/sass/base/_base.scss
@@ -315,6 +315,11 @@ mark {
display: none;
}
+// UI - is deprecated
+.is-deprecated {
+ @extend %ui-deprecated;
+}
+
// UI - semantically hide text
.sr {
@extend %text-sr;
diff --git a/lms/static/sass/base/_extends.scss b/lms/static/sass/base/_extends.scss
index abfdaa0d5c..7048196f98 100644
--- a/lms/static/sass/base/_extends.scss
+++ b/lms/static/sass/base/_extends.scss
@@ -113,3 +113,10 @@
padding: 0;
}
}
+
+%ui-deprecated {
+ @extend %t-weight4;
+ background: tint($warning-color, 85%);
+ padding: ($baseline/5) ($baseline/2);
+ color: shade($warning-color, 45%);
+}
diff --git a/lms/templates/course_groups/cohort_management.html b/lms/templates/course_groups/cohort_management.html
deleted file mode 100644
index 4c9ca9e5a2..0000000000
--- a/lms/templates/course_groups/cohort_management.html
+++ /dev/null
@@ -1,42 +0,0 @@
-<%! from django.utils.translation import ugettext as _ %>
-
-${_("Cohort groups")}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/lms/templates/course_groups/debug.html b/lms/templates/course_groups/debug.html
deleted file mode 100644
index a70d4151ee..0000000000
--- a/lms/templates/course_groups/debug.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-<%! from django.utils.translation import ugettext as _ %>
-
-
- ## "edX" should not be translated
- <%block name="pagetitle">%block>
-
-
-
-
-
-
-
-<%include file="/course_groups/cohort_management.html" />
-
-
-
-
diff --git a/lms/templates/courseware/instructor_dashboard.html b/lms/templates/courseware/legacy_instructor_dashboard.html
similarity index 72%
rename from lms/templates/courseware/instructor_dashboard.html
rename to lms/templates/courseware/legacy_instructor_dashboard.html
index c3e5a34b48..cf0733c433 100644
--- a/lms/templates/courseware/instructor_dashboard.html
+++ b/lms/templates/courseware/legacy_instructor_dashboard.html
@@ -136,7 +136,7 @@ function goto( mode)
${_("Back To Instructor Dashboard")}
- ${_("Instructor Dashboard")}
+ ${_("Legacy Instructor Dashboard")}
%if settings.FEATURES.get('IS_EDX_DOMAIN', False):
## Only show this banner on the edx.org website (other sites may choose to show this if they wish)
@@ -201,19 +201,10 @@ function goto( mode)
% endif
-
- ${_("Gradebook")}
-
-
-
-
-
-
-
@@ -223,7 +214,12 @@ function goto( mode)
%if not settings.FEATURES.get('ENABLE_ASYNC_ANSWER_DISTRIBUTION'):
%endif
-
+
+ ${_("To download student grades and view the grading configuration for your course, visit the Data Download section of the Instructor Dashboard.")}
+
+
+ ${_("To view the Gradebook (only available for courses with a small number of enrolled students), visit the Student Admin section of the Instructor Dashboard.")}
+
@@ -263,67 +259,13 @@ function goto( mode)
%if settings.FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'):
${_("Course-specific grade adjustment")}
-
- ${_("Specify a problem in the course here with its complete location:")}
-
-
- ## Translators: A location (string of text) follows this sentence.
- ${_("You must provide the complete location of the problem. In the Staff Debug viewer, the location looks like this:")}
- i4x://edX/Open_DemoX/problem/78c98390884243b89f6023745231c525
-
- ${_("Then select an action:")}
-
-
-
-
-
${_("These actions run in the background, and status for active tasks will appear in a table below. To see status for all tasks submitted for this problem, click on this button:")}
-
-
-
-
+ ${_("To perform these actions, visit the Student Admin section of the Instructor Dashboard.")}
-
%endif
${_("Student-specific grade inspection and adjustment")}
-
- ${_("Specify the {platform_name} email address or username of a student here:").format(platform_name=settings.PLATFORM_NAME)}
-
-
-
- ${_("Click this, and a link to student's progress page will appear below:")}
-
-
-
- ${_("Specify a problem in the course here with its complete location:")}
-
-
- ## Translators: A location (string of text) follows this sentence.
- ${_("You must provide the complete location of the problem. In the Staff Debug viewer, the location looks like this:")}
- i4x://edX/Open_DemoX/problem/78c98390884243b89f6023745231c525
-
- ${_("Then select an action:")}
-
- %if settings.FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'):
-
- %endif
-
-
- %if instructor_access:
-
- ${_("You may also delete the entire state of a student for the specified module:")}
-
-
- %endif
- %if settings.FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'):
- ${_("Rescoring runs in the background, and status for active tasks will appear in a table below. "
- "To see status for all tasks submitted for this problem and student, click on this button:")}
-
-
-
-
- %endif
+ ${_("To perform these actions, visit the Student Admin section of the Instructor Dashboard.")}
%endif
@@ -351,25 +293,8 @@ function goto( mode)
##-----------------------------------------------------------------------------
%if modeflag.get('Admin'):
- %if instructor_access:
-
-
-
-
-
-
-
-
- %endif
-
- %if admin_access:
-
-
-
-
-
-
-
+ %if instructor_access or admin_access:
+ ${_("To add or remove course staff or instructors, visit the Membership section of the Instructor Dashboard.")}
%endif
%if settings.FEATURES['ENABLE_MANUAL_GIT_RELOAD'] and admin_access:
@@ -381,38 +306,7 @@ function goto( mode)
##-----------------------------------------------------------------------------
%if modeflag.get('Forum Admin'):
- %if instructor_access:
-
-
-
-
-
-
-
- %endif
-
- %if instructor_access or forum_admin_access:
-
-
-
-
-
-
-
-
-
-
- %else:
- ${_("User requires forum administrator privileges to perform administration tasks. See instructor.")}
- %endif
-
-
- ${_("Explanation of Roles:")}
- ${_("Forum Moderators: can edit or delete any post, remove misuse flags, close and re-open threads, endorse "
- "responses, and see posts from all cohorts (if the course is cohorted). Moderators' posts are marked as 'staff'.")}
- ${_("Forum Admins: have moderator privileges, as well as the ability to edit the list of forum moderators "
- "(e.g. to appoint a new moderator). Admins' posts are marked as 'staff'.")}
- ${_("Community TAs: have forum moderator privileges, and their posts are labelled 'Community TA'.")}
+ ${_("To manage forum roles, visit the Membership section of the Instructor Dashboard.")}
%endif
##-----------------------------------------------------------------------------
@@ -463,15 +357,13 @@ function goto( mode)
%if modeflag.get('Data'):
-
-
-
${_("Problem urlname:")}
-
-
+
+
+ ${_("To download student profile data and anonymized IDs, visit the Data Download section of the Instructor Dashboard.")}
%endif
@@ -480,103 +372,18 @@ function goto( mode)
%if modeflag.get('Manage Groups'):
%if instructor_access:
-
-
-
-
- ## Translators: days_early_for_beta should not be translated
- ${_("Enter usernames or emails for students who should be beta-testers, one per line, or separated by commas. They will get to "
- "see course materials early, as configured via the days_early_for_beta option in the course policy.")}
-
-
-
-
-
-
-
-
%if course.is_cohorted:
- <%include file="/course_groups/cohort_management.html" />
+ ${_("To manage beta tester roles and cohort groups, visit the Membership section of the Instructor Dashboard.")}
+ %else:
+ ${_("To manage beta tester roles, visit the Membership section of the Instructor Dashboard.")}
%endif
-
%endif
%endif
##-----------------------------------------------------------------------------
%if modeflag.get('Email'):
- %if email_msg:
-
${email_msg}
- %endif
-
-
-
-
-
${_("Please try not to email students more than once per week. Important things to consider before sending:")}
-
- ${_("Have you read over the email to make sure it says everything you want to say?")}
- ${_("Have you sent the email to yourself first to make sure you're happy with how it's displayed, and that embedded links and images work properly?")}
-
-
-
${_("CAUTION!")}
- ${_("Once the 'Send Email' button is clicked, your email will be queued for sending.")}
- ${_("A queued email CANNOT be cancelled.")}
-
-
-
-
-
-
-
- These email actions run in the background, and status for active email tasks will appear in a table below.
- To see status for all bulk email tasks submitted for this course, click on this button:
-
-
-
-
+ ${_("To send email, visit the Email section of the Instructor Dashboard.")}
%endif
@@ -848,8 +655,7 @@ function goto( mode)
##-----------------------------------------------------------------------------
-%if course_stats and modeflag.get('Psychometrics') is None:
-
+%if modeflag.get('Admin') and course_stats:
@@ -870,6 +676,13 @@ function goto( mode)
%endfor
+%else:
+
+
+ ${_("Course Statistics At A Glance")}
+
+ ${_("View course statistics in the Admin section of this legacy instructor dashboard.")}
+
%endif
##-----------------------------------------------------------------------------