From 0a5af2a38560aa9a5616ff176520e6ea0ea7149d Mon Sep 17 00:00:00 2001
From: Sarina Canelake
Date: Sun, 10 Aug 2014 23:20:31 -0400
Subject: [PATCH 01/13] Rename legacy dash template
---
lms/djangoapps/instructor/views/legacy.py | 2 +-
...structor_dashboard.html => legacy_instructor_dashboard.html} | 0
2 files changed, 1 insertion(+), 1 deletion(-)
rename lms/templates/courseware/{instructor_dashboard.html => legacy_instructor_dashboard.html} (100%)
diff --git a/lms/djangoapps/instructor/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py
index 8e8a48513a..ea3b6663c1 100644
--- a/lms/djangoapps/instructor/views/legacy.py
+++ b/lms/djangoapps/instructor/views/legacy.py
@@ -1045,7 +1045,7 @@ 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):
diff --git a/lms/templates/courseware/instructor_dashboard.html b/lms/templates/courseware/legacy_instructor_dashboard.html
similarity index 100%
rename from lms/templates/courseware/instructor_dashboard.html
rename to lms/templates/courseware/legacy_instructor_dashboard.html
From a15110f01c1d28ca7593efb4e4b35bc4ee02e0ea Mon Sep 17 00:00:00 2001
From: Sarina Canelake
Date: Sun, 10 Aug 2014 23:36:33 -0400
Subject: [PATCH 02/13] Remove Grade Adjustment code from legacy dash
---
.../instructor/tests/test_legacy_reset.py | 70 -----
lms/djangoapps/instructor/views/legacy.py | 268 ------------------
.../legacy_instructor_dashboard.html | 58 +---
3 files changed, 2 insertions(+), 394 deletions(-)
delete mode 100644 lms/djangoapps/instructor/tests/test_legacy_reset.py
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/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py
index ea3b6663c1..fac33108e6 100644
--- a/lms/djangoapps/instructor/views/legacy.py
+++ b/lms/djangoapps/instructor/views/legacy.py
@@ -259,274 +259,6 @@ def instructor_dashboard(request, course_id):
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
diff --git a/lms/templates/courseware/legacy_instructor_dashboard.html b/lms/templates/courseware/legacy_instructor_dashboard.html
index c3e5a34b48..00a792a07d 100644
--- a/lms/templates/courseware/legacy_instructor_dashboard.html
+++ b/lms/templates/courseware/legacy_instructor_dashboard.html
@@ -263,67 +263,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, please 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
${_("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, please visit the 'Student Admin' section of the instructor dashboard.")}
%endif
From d8904d9091ead6c5479652cb0da34f79d70627c6 Mon Sep 17 00:00:00 2001
From: Sarina Canelake
Date: Mon, 11 Aug 2014 19:21:04 -0400
Subject: [PATCH 03/13] Clean up Grade Downloads section of legacy dash
---
.../tests/test_legacy_download_csv.py | 72 -------------------
.../instructor/tests/test_legacy_xss.py | 3 -
lms/djangoapps/instructor/views/legacy.py | 16 -----
.../legacy_instructor_dashboard.html | 12 ++--
4 files changed, 6 insertions(+), 97 deletions(-)
delete mode 100644 lms/djangoapps/instructor/tests/test_legacy_download_csv.py
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_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 fac33108e6..c5a1122d39 100644
--- a/lms/djangoapps/instructor/views/legacy.py
+++ b/lms/djangoapps/instructor/views/legacy.py
@@ -227,12 +227,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 +234,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,11 +243,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)
-
#----------------------------------------
# export grades to remote gradebook
diff --git a/lms/templates/courseware/legacy_instructor_dashboard.html b/lms/templates/courseware/legacy_instructor_dashboard.html
index 00a792a07d..80f17164fe 100644
--- a/lms/templates/courseware/legacy_instructor_dashboard.html
+++ b/lms/templates/courseware/legacy_instructor_dashboard.html
@@ -202,16 +202,14 @@ function goto( mode)
% endif
- ${_("Gradebook")}
+ ${_("To view the Gradebook (small courses only), please visit the 'Student Admin' section of the instructor dashboard.")}
-
-
-
-
+
+ ${_("To download student grades, please visit the 'Data Download' section of the instructor dashboard.")}
@@ -223,7 +221,9 @@ function goto( mode)
%if not settings.FEATURES.get('ENABLE_ASYNC_ANSWER_DISTRIBUTION'):
%endif
-
+
+ ${_("To view the graded assignments configuration, please visit the 'Data Download' section of the instructor dashboard.")}
+
".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
@@ -778,81 +698,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.
diff --git a/lms/templates/courseware/legacy_instructor_dashboard.html b/lms/templates/courseware/legacy_instructor_dashboard.html
index 325bab5dee..b8edc06560 100644
--- a/lms/templates/courseware/legacy_instructor_dashboard.html
+++ b/lms/templates/courseware/legacy_instructor_dashboard.html
@@ -314,38 +314,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, please visit the 'Membership' section of the instructor dashboard.")}
%endif
##-----------------------------------------------------------------------------
From e3179c37cd1e43e0a89f5aede5e7dec5b0fa44da Mon Sep 17 00:00:00 2001
From: Sarina Canelake
Date: Wed, 13 Aug 2014 16:06:03 -0400
Subject: [PATCH 06/13] Clean up Email section of legacy dashboard
---
.../instructor/tests/test_legacy_email.py | 144 ------------------
lms/djangoapps/instructor/views/legacy.py | 77 +---------
.../legacy_instructor_dashboard.html | 73 +--------
3 files changed, 2 insertions(+), 292 deletions(-)
delete mode 100644 lms/djangoapps/instructor/tests/test_legacy_email.py
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 = ''
- 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/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py
index 4276a8c067..e5b7da7da8 100644
--- a/lms/djangoapps/instructor/views/legacy.py
+++ b/lms/djangoapps/instructor/views/legacy.py
@@ -58,8 +58,7 @@ from instructor_task.api import (
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
+ submit_reset_problem_attempts_for_all_students
)
from instructor_task.views import get_task_completion_info
from edxmako.shortcuts import render_to_response, render_to_string
@@ -445,55 +444,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 = '
{text}
'.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
@@ -578,27 +528,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
@@ -628,10 +557,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
diff --git a/lms/templates/courseware/legacy_instructor_dashboard.html b/lms/templates/courseware/legacy_instructor_dashboard.html
index b8edc06560..fad8fed6c5 100644
--- a/lms/templates/courseware/legacy_instructor_dashboard.html
+++ b/lms/templates/courseware/legacy_instructor_dashboard.html
@@ -407,78 +407,7 @@ function goto( mode)
##-----------------------------------------------------------------------------
%if modeflag.get('Email'):
- %if email_msg:
-
${_("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, please visit the 'Email' section of the instructor dashboard.")}
- ## 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.")}
-
-
-
-
-
-
-
+
${_("To manage beta tester roles, please visit the 'Membership' section of the instructor dashboard.")}
${_("To send email, please visit the 'Email' section of the instructor dashboard.")}
+
${_("To send email, please visit the 'Email' section of the instructor dashboard.")}
%endif
@@ -697,7 +697,7 @@ function goto( mode)
${_("Course Statistics At A Glance")}
-
+
${_("These statistics can be viewed under the 'Admin' tab of the legacy instructor dashboard.")}
%endif
From a1c8f8c4686c3b9513e98ac6eff1279fa0ae2077 Mon Sep 17 00:00:00 2001
From: Sarina Canelake
Date: Mon, 8 Dec 2014 21:35:49 -0500
Subject: [PATCH 13/13] Style cleanups: legacy instructor dashboard
---
lms/djangoapps/instructor/views/legacy.py | 155 +++++++-----------
.../legacy_instructor_dashboard.html | 49 ++----
2 files changed, 74 insertions(+), 130 deletions(-)
diff --git a/lms/djangoapps/instructor/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py
index 5b27aa0c0a..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,38 +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
)
from instructor_task.views import get_task_completion_info
from edxmako.shortcuts import render_to_response, render_to_string
@@ -67,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
@@ -107,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 = []
@@ -171,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)
@@ -266,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
@@ -429,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
@@ -438,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)
#----------------------------------------
@@ -522,31 +481,31 @@ 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)
@@ -565,6 +524,7 @@ def _do_remote_gradebook(user, course, action, args=None, files=None):
return msg, datatable
+
def _role_members_table(role, title, course_key):
"""
Return a data table of usernames and names of users in group_name.
@@ -784,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:
@@ -843,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'
@@ -882,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,
@@ -897,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()
@@ -909,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()
@@ -918,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
@@ -936,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']}
@@ -977,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"
@@ -996,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
@@ -1025,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):
@@ -1123,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
#-----------------------------------------------------------------------------
@@ -1153,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/templates/courseware/legacy_instructor_dashboard.html b/lms/templates/courseware/legacy_instructor_dashboard.html
index f6003e1885..cf0733c433 100644
--- a/lms/templates/courseware/legacy_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,21 +201,10 @@ function goto( mode)
% endif
-
- ${_("To view the Gradebook (small courses only), please visit the 'Student Admin' section of the instructor dashboard.")}
-
-
- ${_("To perform grade downloads, please visit the 'Data Download' section of the instructor dashboard.")}
-
-
-
- ${_("To download student grades, please visit the 'Data Download' section of the instructor dashboard.")}
-
-
@@ -226,7 +215,10 @@ function goto( mode)
%endif
- ${_("To view the graded assignments configuration, please visit the 'Data Download' section of the instructor dashboard.")}
+ ${_("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.")}
@@ -267,13 +259,13 @@ function goto( mode)
%if settings.FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'):
${_("Course-specific grade adjustment")}
-
${_("To perform these actions, please visit the 'Student Admin' section of the instructor dashboard.")}
+
${_("To perform these actions, visit the Student Admin section of the Instructor Dashboard.")}
%endif
${_("Student-specific grade inspection and adjustment")}
-
${_("To perform these actions, please visit the 'Student Admin' section of the instructor dashboard.")}
+
${_("To perform these actions, visit the Student Admin section of the Instructor Dashboard.")}
- ${_("To download student profile data, please visit the 'Data Download' section of the instructor dashboard.")}
-
-
${_("Problem urlname:")}
- ${_("To download student anonymized IDs, please visit the 'Data Download' section of the instructor dashboard.")}
+ ${_("To download student profile data and anonymized IDs, visit the Data Download section of the Instructor Dashboard.")}
${_("To send email, please visit the 'Email' section of the instructor dashboard.")}
+
${_("To send email, visit the Email section of the Instructor Dashboard.")}
%endif
@@ -698,7 +681,7 @@ function goto( mode)
${_("Course Statistics At A Glance")}
- ${_("These statistics can be viewed under the 'Admin' tab of the legacy instructor dashboard.")}
+ ${_("View course statistics in the Admin section of this legacy instructor dashboard.")}