Commit a7590e85 by Sarina Canelake

Merge pull request #2865 from edx/sarina/change-default-dashboard

WIP Switch default instructor dashboard to new dash
parents ea11fd07 a5381db0
...@@ -9,6 +9,10 @@ Studio: Add drag-and-drop support to the container page. STUD-1309. ...@@ -9,6 +9,10 @@ Studio: Add drag-and-drop support to the container page. STUD-1309.
Common: Add extensible third-party auth module. Common: Add extensible third-party auth module.
LMS: Switch default instructor dashboard to the new (formerly "beta")
instructor dashboard. Puts the old (now "legacy") dash behind a feature flag.
LMS-1296
Blades: Handle situation if no response were sent from XQueue to LMS in Matlab Blades: Handle situation if no response were sent from XQueue to LMS in Matlab
problem after Run Code button press. BLD-994. problem after Run Code button press. BLD-994.
......
...@@ -38,6 +38,12 @@ class TestOptoutCourseEmails(ModuleStoreTestCase): ...@@ -38,6 +38,12 @@ class TestOptoutCourseEmails(ModuleStoreTestCase):
self.client.login(username=self.student.username, password="test") self.client.login(username=self.student.username, password="test")
self.send_mail_url = reverse('send_email', kwargs={'course_id': self.course.id})
self.success_content = {
'course_id': self.course.id,
'success': True,
}
def tearDown(self): def tearDown(self):
""" """
Undo all patches. Undo all patches.
...@@ -48,18 +54,12 @@ class TestOptoutCourseEmails(ModuleStoreTestCase): ...@@ -48,18 +54,12 @@ class TestOptoutCourseEmails(ModuleStoreTestCase):
"""Navigate to the instructor dash's email view""" """Navigate to the instructor dash's email view"""
# Pull up email view on instructor dashboard # Pull up email view on instructor dashboard
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
# Response loads the whole instructor dashboard, so no need to explicitly
# navigate to a particular email section
response = self.client.get(url) response = self.client.get(url)
email_link = '<a href="#" onclick="goto(\'Email\')" class="None">Email</a>' email_section = '<div class="vert-left send-email" id="section-send-email">'
# If this fails, it is likely because ENABLE_INSTRUCTOR_EMAIL is set to False # If this fails, it is likely because ENABLE_INSTRUCTOR_EMAIL is set to False
self.assertTrue(email_link in response.content) self.assertTrue(email_section 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_id)] = 'Email'
session.save()
response = self.client.get(url)
selected_email_link = '<a href="#" onclick="goto(\'Email\')" class="selectedmode">Email</a>'
self.assertTrue(selected_email_link in response.content)
@patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True, 'REQUIRE_COURSE_EMAIL_AUTH': False}) @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True, 'REQUIRE_COURSE_EMAIL_AUTH': False})
def test_optout_course(self): def test_optout_course(self):
...@@ -77,15 +77,14 @@ class TestOptoutCourseEmails(ModuleStoreTestCase): ...@@ -77,15 +77,14 @@ class TestOptoutCourseEmails(ModuleStoreTestCase):
self.client.login(username=self.instructor.username, password="test") self.client.login(username=self.instructor.username, password="test")
self.navigate_to_email_view() self.navigate_to_email_view()
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
test_email = { test_email = {
'action': 'Send email', 'action': 'Send email',
'to_option': 'all', 'send_to': 'all',
'subject': 'test subject for all', 'subject': 'test subject for all',
'message': 'test message for all' 'message': 'test message for all'
} }
response = self.client.post(url, test_email) response = self.client.post(self.send_mail_url, test_email)
self.assertContains(response, "Your email was successfully queued for sending.") self.assertEquals(json.loads(response.content), self.success_content)
# Assert that self.student.email not in mail.to, outbox should be empty # Assert that self.student.email not in mail.to, outbox should be empty
self.assertEqual(len(mail.outbox), 0) self.assertEqual(len(mail.outbox), 0)
...@@ -106,16 +105,14 @@ class TestOptoutCourseEmails(ModuleStoreTestCase): ...@@ -106,16 +105,14 @@ class TestOptoutCourseEmails(ModuleStoreTestCase):
self.client.login(username=self.instructor.username, password="test") self.client.login(username=self.instructor.username, password="test")
self.navigate_to_email_view() self.navigate_to_email_view()
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
test_email = { test_email = {
'action': 'Send email', 'action': 'Send email',
'to_option': 'all', 'send_to': 'all',
'subject': 'test subject for all', 'subject': 'test subject for all',
'message': 'test message for all' 'message': 'test message for all'
} }
response = self.client.post(url, test_email) response = self.client.post(self.send_mail_url, test_email)
self.assertEquals(json.loads(response.content), self.success_content)
self.assertContains(response, "Your email was successfully queued for sending.")
# Assert that self.student.email in mail.to # Assert that self.student.email in mail.to
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
""" """
Unit tests for sending course email Unit tests for sending course email
""" """
import json
from mock import patch from mock import patch
from django.conf import settings from django.conf import settings
...@@ -70,18 +72,17 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): ...@@ -70,18 +72,17 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase):
# Pull up email view on instructor dashboard # Pull up email view on instructor dashboard
self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
# Response loads the whole instructor dashboard, so no need to explicitly
# navigate to a particular email section
response = self.client.get(self.url) response = self.client.get(self.url)
email_link = '<a href="#" onclick="goto(\'Email\')" class="None">Email</a>' email_section = '<div class="vert-left send-email" id="section-send-email">'
# If this fails, it is likely because ENABLE_INSTRUCTOR_EMAIL is set to False # If this fails, it is likely because ENABLE_INSTRUCTOR_EMAIL is set to False
self.assertTrue(email_link in response.content) self.assertTrue(email_section in response.content)
self.send_mail_url = reverse('send_email', kwargs={'course_id': self.course.id})
# Select the Email view of the instructor dash self.success_content = {
session = self.client.session 'course_id': self.course.id,
session[u'idash_mode:{0}'.format(self.course.location.course_id)] = 'Email' 'success': True,
session.save() }
response = self.client.get(self.url)
selected_email_link = '<a href="#" onclick="goto(\'Email\')" class="selectedmode">Email</a>'
self.assertTrue(selected_email_link in response.content)
def tearDown(self): def tearDown(self):
""" """
...@@ -96,12 +97,13 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): ...@@ -96,12 +97,13 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase):
""" """
test_email = { test_email = {
'action': 'Send email', 'action': 'Send email',
'to_option': 'myself', 'send_to': 'myself',
'subject': 'test subject for myself', 'subject': 'test subject for myself',
'message': 'test message for myself' 'message': 'test message for myself'
} }
response = self.client.post(self.url, test_email) response = self.client.post(self.send_mail_url, test_email)
self.assertContains(response, "Email is not enabled for this course.") # We should get back a HttpResponseForbidden (status code 403)
self.assertContains(response, "Email is not enabled for this course.", status_code=403)
def test_send_to_self(self): def test_send_to_self(self):
""" """
...@@ -110,15 +112,16 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): ...@@ -110,15 +112,16 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase):
# Now we know we have pulled up the instructor dash's email view # Now we know we have pulled up the instructor dash's email view
# (in the setUp method), we can test sending an email. # (in the setUp method), we can test sending an email.
test_email = { test_email = {
'action': 'Send email', 'action': 'send',
'to_option': 'myself', 'send_to': 'myself',
'subject': 'test subject for myself', 'subject': 'test subject for myself',
'message': 'test message for myself' 'message': 'test message for myself'
} }
response = self.client.post(self.url, test_email) # Post the email to the instructor dashboard API
response = self.client.post(self.send_mail_url, test_email)
self.assertContains(response, "Your email was successfully queued for sending.") self.assertEquals(json.loads(response.content), self.success_content)
# Check that outbox is as expected
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
self.assertEqual(len(mail.outbox[0].to), 1) self.assertEqual(len(mail.outbox[0].to), 1)
self.assertEquals(mail.outbox[0].to[0], self.instructor.email) self.assertEquals(mail.outbox[0].to[0], self.instructor.email)
...@@ -135,13 +138,13 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): ...@@ -135,13 +138,13 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase):
# (in the setUp method), we can test sending an email. # (in the setUp method), we can test sending an email.
test_email = { test_email = {
'action': 'Send email', 'action': 'Send email',
'to_option': 'staff', 'send_to': 'staff',
'subject': 'test subject for staff', 'subject': 'test subject for staff',
'message': 'test message for subject' 'message': 'test message for subject'
} }
response = self.client.post(self.url, test_email) # Post the email to the instructor dashboard API
response = self.client.post(self.send_mail_url, test_email)
self.assertContains(response, "Your email was successfully queued for sending.") self.assertEquals(json.loads(response.content), self.success_content)
# the 1 is for the instructor in this test and others # the 1 is for the instructor in this test and others
self.assertEquals(len(mail.outbox), 1 + len(self.staff)) self.assertEquals(len(mail.outbox), 1 + len(self.staff))
...@@ -159,13 +162,13 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): ...@@ -159,13 +162,13 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase):
test_email = { test_email = {
'action': 'Send email', 'action': 'Send email',
'to_option': 'all', 'send_to': 'all',
'subject': 'test subject for all', 'subject': 'test subject for all',
'message': 'test message for all' 'message': 'test message for all'
} }
response = self.client.post(self.url, test_email) # Post the email to the instructor dashboard API
response = self.client.post(self.send_mail_url, test_email)
self.assertContains(response, "Your email was successfully queued for sending.") self.assertEquals(json.loads(response.content), self.success_content)
self.assertEquals(len(mail.outbox), 1 + len(self.staff) + len(self.students)) self.assertEquals(len(mail.outbox), 1 + len(self.staff) + len(self.students))
self.assertItemsEqual( self.assertItemsEqual(
...@@ -183,13 +186,13 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): ...@@ -183,13 +186,13 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase):
uni_subject = u'téśt śúbjéćt főŕ áĺĺ' uni_subject = u'téśt śúbjéćt főŕ áĺĺ'
test_email = { test_email = {
'action': 'Send email', 'action': 'Send email',
'to_option': 'all', 'send_to': 'all',
'subject': uni_subject, 'subject': uni_subject,
'message': 'test message for all' 'message': 'test message for all'
} }
response = self.client.post(self.url, test_email) # Post the email to the instructor dashboard API
response = self.client.post(self.send_mail_url, test_email)
self.assertContains(response, "Your email was successfully queued for sending.") self.assertEquals(json.loads(response.content), self.success_content)
self.assertEquals(len(mail.outbox), 1 + len(self.staff) + len(self.students)) self.assertEquals(len(mail.outbox), 1 + len(self.staff) + len(self.students))
self.assertItemsEqual( self.assertItemsEqual(
...@@ -211,13 +214,13 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): ...@@ -211,13 +214,13 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase):
uni_message = u'ẗëṡẗ ṁëṡṡäġë ḟöṛ äḷḷ イ乇丂イ ᄊ乇丂丂ムg乇 キo尺 ムレレ тэѕт мэѕѕаБэ fоѓ аll' uni_message = u'ẗëṡẗ ṁëṡṡäġë ḟöṛ äḷḷ イ乇丂イ ᄊ乇丂丂ムg乇 キo尺 ムレレ тэѕт мэѕѕаБэ fоѓ аll'
test_email = { test_email = {
'action': 'Send email', 'action': 'Send email',
'to_option': 'all', 'send_to': 'all',
'subject': 'test subject for all', 'subject': 'test subject for all',
'message': uni_message 'message': uni_message
} }
response = self.client.post(self.url, test_email) # Post the email to the instructor dashboard API
response = self.client.post(self.send_mail_url, test_email)
self.assertContains(response, "Your email was successfully queued for sending.") self.assertEquals(json.loads(response.content), self.success_content)
self.assertEquals(len(mail.outbox), 1 + len(self.staff) + len(self.students)) self.assertEquals(len(mail.outbox), 1 + len(self.staff) + len(self.students))
self.assertItemsEqual( self.assertItemsEqual(
...@@ -242,13 +245,13 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): ...@@ -242,13 +245,13 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase):
test_email = { test_email = {
'action': 'Send email', 'action': 'Send email',
'to_option': 'all', 'send_to': 'all',
'subject': 'test subject for all', 'subject': 'test subject for all',
'message': 'test message for all' 'message': 'test message for all'
} }
response = self.client.post(self.url, test_email) # Post the email to the instructor dashboard API
response = self.client.post(self.send_mail_url, test_email)
self.assertContains(response, "Your email was successfully queued for sending.") self.assertEquals(json.loads(response.content), self.success_content)
self.assertEquals(len(mail.outbox), 1 + len(self.staff) + len(self.students)) self.assertEquals(len(mail.outbox), 1 + len(self.staff) + len(self.students))
...@@ -280,12 +283,14 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase): ...@@ -280,12 +283,14 @@ class TestEmailSendFromDashboard(ModuleStoreTestCase):
test_email = { test_email = {
'action': 'Send email', 'action': 'Send email',
'to_option': 'all', 'send_to': 'all',
'subject': 'test subject for all', 'subject': 'test subject for all',
'message': 'test message for all' 'message': 'test message for all'
} }
response = self.client.post(self.url, test_email) # Post the email to the instructor dashboard API
self.assertContains(response, "Your email was successfully queued for sending.") response = self.client.post(self.send_mail_url, test_email)
self.assertEquals(json.loads(response.content), self.success_content)
self.assertEquals(mock_factory.emails_sent, self.assertEquals(mock_factory.emails_sent,
1 + len(self.staff) + len(self.students) + LARGE_NUM_EMAILS - len(optouts)) 1 + len(self.staff) + len(self.students) + LARGE_NUM_EMAILS - len(optouts))
outbox_contents = [e.to[0] for e in mail.outbox] outbox_contents = [e.to[0] for e in mail.outbox]
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
""" """
Unit tests for handling email sending errors Unit tests for handling email sending errors
""" """
import json
from itertools import cycle from itertools import cycle
from mock import patch from mock import patch
from smtplib import SMTPDataError, SMTPServerDisconnected, SMTPConnectError from smtplib import SMTPDataError, SMTPServerDisconnected, SMTPConnectError
...@@ -53,6 +55,11 @@ class TestEmailErrors(ModuleStoreTestCase): ...@@ -53,6 +55,11 @@ class TestEmailErrors(ModuleStoreTestCase):
# load initial content (since we don't run migrations as part of tests): # load initial content (since we don't run migrations as part of tests):
call_command("loaddata", "course_email_template.json") call_command("loaddata", "course_email_template.json")
self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
self.send_mail_url = reverse('send_email', kwargs={'course_id': self.course.id})
self.success_content = {
'course_id': self.course.id,
'success': True,
}
def tearDown(self): def tearDown(self):
patch.stopall() patch.stopall()
...@@ -66,15 +73,16 @@ class TestEmailErrors(ModuleStoreTestCase): ...@@ -66,15 +73,16 @@ class TestEmailErrors(ModuleStoreTestCase):
get_conn.return_value.send_messages.side_effect = SMTPDataError(455, "Throttling: Sending rate exceeded") get_conn.return_value.send_messages.side_effect = SMTPDataError(455, "Throttling: Sending rate exceeded")
test_email = { test_email = {
'action': 'Send email', 'action': 'Send email',
'to_option': 'myself', 'send_to': 'myself',
'subject': 'test subject for myself', 'subject': 'test subject for myself',
'message': 'test message for myself' 'message': 'test message for myself'
} }
self.client.post(self.url, test_email) response = self.client.post(self.send_mail_url, test_email)
self.assertEquals(json.loads(response.content), self.success_content)
# Test that we retry upon hitting a 4xx error # Test that we retry upon hitting a 4xx error
self.assertTrue(retry.called) self.assertTrue(retry.called)
(_, kwargs) = retry.call_args (__, kwargs) = retry.call_args
exc = kwargs['exc'] exc = kwargs['exc']
self.assertIsInstance(exc, SMTPDataError) self.assertIsInstance(exc, SMTPDataError)
...@@ -94,11 +102,12 @@ class TestEmailErrors(ModuleStoreTestCase): ...@@ -94,11 +102,12 @@ class TestEmailErrors(ModuleStoreTestCase):
test_email = { test_email = {
'action': 'Send email', 'action': 'Send email',
'to_option': 'all', 'send_to': 'all',
'subject': 'test subject for all', 'subject': 'test subject for all',
'message': 'test message for all' 'message': 'test message for all'
} }
self.client.post(self.url, test_email) response = self.client.post(self.send_mail_url, test_email)
self.assertEquals(json.loads(response.content), self.success_content)
# We shouldn't retry when hitting a 5xx error # We shouldn't retry when hitting a 5xx error
self.assertFalse(retry.called) self.assertFalse(retry.called)
...@@ -118,14 +127,15 @@ class TestEmailErrors(ModuleStoreTestCase): ...@@ -118,14 +127,15 @@ class TestEmailErrors(ModuleStoreTestCase):
get_conn.return_value.open.side_effect = SMTPServerDisconnected(425, "Disconnecting") get_conn.return_value.open.side_effect = SMTPServerDisconnected(425, "Disconnecting")
test_email = { test_email = {
'action': 'Send email', 'action': 'Send email',
'to_option': 'myself', 'send_to': 'myself',
'subject': 'test subject for myself', 'subject': 'test subject for myself',
'message': 'test message for myself' 'message': 'test message for myself'
} }
self.client.post(self.url, test_email) response = self.client.post(self.send_mail_url, test_email)
self.assertEquals(json.loads(response.content), self.success_content)
self.assertTrue(retry.called) self.assertTrue(retry.called)
(_, kwargs) = retry.call_args (__, kwargs) = retry.call_args
exc = kwargs['exc'] exc = kwargs['exc']
self.assertIsInstance(exc, SMTPServerDisconnected) self.assertIsInstance(exc, SMTPServerDisconnected)
...@@ -139,14 +149,15 @@ class TestEmailErrors(ModuleStoreTestCase): ...@@ -139,14 +149,15 @@ class TestEmailErrors(ModuleStoreTestCase):
test_email = { test_email = {
'action': 'Send email', 'action': 'Send email',
'to_option': 'myself', 'send_to': 'myself',
'subject': 'test subject for myself', 'subject': 'test subject for myself',
'message': 'test message for myself' 'message': 'test message for myself'
} }
self.client.post(self.url, test_email) response = self.client.post(self.send_mail_url, test_email)
self.assertEquals(json.loads(response.content), self.success_content)
self.assertTrue(retry.called) self.assertTrue(retry.called)
(_, kwargs) = retry.call_args (__, kwargs) = retry.call_args
exc = kwargs['exc'] exc = kwargs['exc']
self.assertIsInstance(exc, SMTPConnectError) self.assertIsInstance(exc, SMTPConnectError)
...@@ -162,7 +173,7 @@ class TestEmailErrors(ModuleStoreTestCase): ...@@ -162,7 +173,7 @@ class TestEmailErrors(ModuleStoreTestCase):
task_input = {"email_id": -1} task_input = {"email_id": -1}
with self.assertRaises(CourseEmail.DoesNotExist): with self.assertRaises(CourseEmail.DoesNotExist):
perform_delegate_email_batches(entry.id, course_id, task_input, "action_name") # pylint: disable=E1101 perform_delegate_email_batches(entry.id, course_id, task_input, "action_name") # pylint: disable=E1101
((log_str, _, email_id), _) = mock_log.warning.call_args ((log_str, __, email_id), __) = mock_log.warning.call_args
self.assertTrue(mock_log.warning.called) self.assertTrue(mock_log.warning.called)
self.assertIn('Failed to get CourseEmail with id', log_str) self.assertIn('Failed to get CourseEmail with id', log_str)
self.assertEqual(email_id, -1) self.assertEqual(email_id, -1)
......
...@@ -253,17 +253,6 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase): ...@@ -253,17 +253,6 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase):
b_section_has_problem = get_array_section_has_problem(self.course.id) b_section_has_problem = get_array_section_has_problem(self.course.id)
self.assertEquals(b_section_has_problem[0], True) self.assertEquals(b_section_has_problem[0], True)
def test_dashboard(self):
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
response = self.client.post(
url,
{
'idash_mode': 'Metrics'
}
)
self.assertContains(response, '<h2>Course Statistics At A Glance</h2>')
def test_has_instructor_access_for_class(self): def test_has_instructor_access_for_class(self):
""" """
Test for instructor access Test for instructor access
......
# pylint: disable=C0111 # pylint: disable=C0111
# pylint: disable=W0621 # pylint: disable=W0621
# pylint: disable=unused-argument
from lettuce import world, step from lettuce import world, step
...@@ -14,6 +15,11 @@ def i_click_on_the_tab(step, tab_text): ...@@ -14,6 +15,11 @@ def i_click_on_the_tab(step, tab_text):
world.click_link(tab_text) world.click_link(tab_text)
@step('I click on the "([^"]*)" link$')
def i_click_on_the_link(step, link_text):
world.click_link(link_text)
@step('I visit the courseware URL$') @step('I visit the courseware URL$')
def i_visit_the_course_info_url(step): def i_visit_the_course_info_url(step):
world.visit('/courses/MITx/6.002x/2012_Fall/courseware') world.visit('/courses/MITx/6.002x/2012_Fall/courseware')
......
...@@ -51,7 +51,8 @@ Feature: LMS.LTI component ...@@ -51,7 +51,8 @@ Feature: LMS.LTI component
Then I see text "Problem Scores: 5/10" Then I see text "Problem Scores: 5/10"
And I see graph with total progress "5%" And I see graph with total progress "5%"
Then I click on the "Instructor" tab Then I click on the "Instructor" tab
And I click on the "Gradebook" tab And I click on the "Student Admin" tab
And I click on the "View Gradebook" link
And I see in the gradebook table that "HW" is "50" And I see in the gradebook table that "HW" is "50"
And I see in the gradebook table that "Total" is "5" And I see in the gradebook table that "Total" is "5"
...@@ -88,7 +89,8 @@ Feature: LMS.LTI component ...@@ -88,7 +89,8 @@ Feature: LMS.LTI component
Then I see text "Problem Scores: 8/10" Then I see text "Problem Scores: 8/10"
And I see graph with total progress "8%" And I see graph with total progress "8%"
Then I click on the "Instructor" tab Then I click on the "Instructor" tab
And I click on the "Gradebook" tab And I click on the "Student Admin" tab
And I click on the "View Gradebook" link
And I see in the gradebook table that "HW" is "80" And I see in the gradebook table that "HW" is "80"
And I see in the gradebook table that "Total" is "8" And I see in the gradebook table that "Total" is "8"
And I visit the LTI component And I visit the LTI component
...@@ -113,7 +115,8 @@ Feature: LMS.LTI component ...@@ -113,7 +115,8 @@ Feature: LMS.LTI component
Then I see text "Problem Scores: 0/10" Then I see text "Problem Scores: 0/10"
And I see graph with total progress "0%" And I see graph with total progress "0%"
Then I click on the "Instructor" tab Then I click on the "Instructor" tab
And I click on the "Gradebook" tab And I click on the "Student Admin" tab
And I click on the "View Gradebook" link
And I see in the gradebook table that "HW" is "0" And I see in the gradebook table that "HW" is "0"
And I see in the gradebook table that "Total" is "0" And I see in the gradebook table that "Total" is "0"
......
...@@ -117,7 +117,6 @@ def when_i_send_an_email(step, recipient): # pylint: disable=unused-argument ...@@ -117,7 +117,6 @@ def when_i_send_an_email(step, recipient): # pylint: disable=unused-argument
# Go to the email section of the instructor dash # Go to the email section of the instructor dash
world.visit('/courses/edx/888/Bulk_Email_Test_Course') world.visit('/courses/edx/888/Bulk_Email_Test_Course')
world.css_click('a[href="/courses/edx/888/Bulk_Email_Test_Course/instructor"]') world.css_click('a[href="/courses/edx/888/Bulk_Email_Test_Course/instructor"]')
world.css_click('div.beta-button-wrapper>a.beta-button')
world.css_click('a[data-section="send_email"]') world.css_click('a[data-section="send_email"]')
# Select the recipient # Select the recipient
......
...@@ -77,7 +77,6 @@ def go_to_section(section_name): ...@@ -77,7 +77,6 @@ def go_to_section(section_name):
# course_info, membership, student_admin, data_download, analytics, send_email # course_info, membership, student_admin, data_download, analytics, send_email
world.visit('/courses/edx/999/Test_Course') world.visit('/courses/edx/999/Test_Course')
world.css_click('a[href="/courses/edx/999/Test_Course/instructor"]') world.css_click('a[href="/courses/edx/999/Test_Course/instructor"]')
world.css_click('div.beta-button-wrapper>a.beta-button')
world.css_click('a[data-section="{0}"]'.format(section_name)) world.css_click('a[data-section="{0}"]'.format(section_name))
......
...@@ -34,7 +34,7 @@ class TestNewInstructorDashboardEmailViewMongoBacked(ModuleStoreTestCase): ...@@ -34,7 +34,7 @@ class TestNewInstructorDashboardEmailViewMongoBacked(ModuleStoreTestCase):
self.client.login(username=instructor.username, password="test") self.client.login(username=instructor.username, password="test")
# URL for instructor dash # URL for instructor dash
self.url = reverse('instructor_dashboard_2', kwargs={'course_id': self.course.id}) self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
# URL for email view # URL for email view
self.email_link = '<a href="" data-section="send_email">Email</a>' self.email_link = '<a href="" data-section="send_email">Email</a>'
...@@ -122,7 +122,7 @@ class TestNewInstructorDashboardEmailViewXMLBacked(ModuleStoreTestCase): ...@@ -122,7 +122,7 @@ class TestNewInstructorDashboardEmailViewXMLBacked(ModuleStoreTestCase):
self.client.login(username=instructor.username, password="test") self.client.login(username=instructor.username, password="test")
# URL for instructor dash # URL for instructor dash
self.url = reverse('instructor_dashboard_2', kwargs={'course_id': self.course_name}) self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course_name})
# URL for email view # URL for email view
self.email_link = '<a href="" data-section="send_email">Email</a>' self.email_link = '<a href="" data-section="send_email">Email</a>'
......
...@@ -52,7 +52,7 @@ class TestInstructorDashboardAnonCSV(ModuleStoreTestCase, LoginEnrollmentTestCas ...@@ -52,7 +52,7 @@ class TestInstructorDashboardAnonCSV(ModuleStoreTestCase, LoginEnrollmentTestCas
def test_download_anon_csv(self): def test_download_anon_csv(self):
course = self.toy course = self.toy
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
with patch('instructor.views.legacy.unique_id_for_user') as mock_unique: with patch('instructor.views.legacy.unique_id_for_user') as mock_unique:
mock_unique.return_value = 42 mock_unique.return_value = 42
......
...@@ -49,7 +49,7 @@ class TestInstructorDashboardGradeDownloadCSV(ModuleStoreTestCase, LoginEnrollme ...@@ -49,7 +49,7 @@ class TestInstructorDashboardGradeDownloadCSV(ModuleStoreTestCase, LoginEnrollme
def test_download_grades_csv(self): def test_download_grades_csv(self):
course = self.toy course = self.toy
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
msg = "url = {0}\n".format(url) msg = "url = {0}\n".format(url)
response = self.client.post(url, {'action': 'Download CSV of all student grades for this course'}) 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) msg += "instructor dashboard download csv grades: response = '{0}'\n".format(response)
......
...@@ -32,7 +32,7 @@ class TestInstructorDashboardEmailView(ModuleStoreTestCase): ...@@ -32,7 +32,7 @@ class TestInstructorDashboardEmailView(ModuleStoreTestCase):
self.client.login(username=instructor.username, password="test") self.client.login(username=instructor.username, password="test")
# URL for instructor dash # URL for instructor dash
self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) self.url = reverse('instructor_dashboard_legacy', kwargs={'course_id': self.course.id})
# URL for email view # URL for email view
self.email_link = '<a href="#" onclick="goto(\'Email\')" class="None">Email</a>' self.email_link = '<a href="#" onclick="goto(\'Email\')" class="None">Email</a>'
......
...@@ -52,7 +52,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -52,7 +52,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
course = self.course course = self.course
# Run the Un-enroll students command # Run the Un-enroll students command
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
response = self.client.post( response = self.client.post(
url, url,
{ {
...@@ -84,7 +84,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -84,7 +84,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
course = self.course course = self.course
# Run the Enroll students command # Run the Enroll students command
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'student1_1@test.com, student1_2@test.com', 'auto_enroll': 'on'}) response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'student1_1@test.com, student1_2@test.com', 'auto_enroll': 'on'})
# Check the page output # Check the page output
...@@ -129,7 +129,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -129,7 +129,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
course = self.course course = self.course
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'student0@test.com', 'auto_enroll': 'on'}) response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'student0@test.com', 'auto_enroll': 'on'})
self.assertContains(response, '<td>student0@test.com</td>') self.assertContains(response, '<td>student0@test.com</td>')
self.assertContains(response, '<td>already enrolled</td>') self.assertContains(response, '<td>already enrolled</td>')
...@@ -142,7 +142,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -142,7 +142,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
course = self.course course = self.course
# Run the Enroll students command # Run the Enroll students command
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'student2_1@test.com, student2_2@test.com'}) response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'student2_1@test.com, student2_2@test.com'})
# Check the page output # Check the page output
...@@ -199,7 +199,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -199,7 +199,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
# Create activated, but not enrolled, user # Create activated, but not enrolled, user
UserFactory.create(username="student3_0", email="student3_0@test.com", first_name='Autoenrolled') UserFactory.create(username="student3_0", email="student3_0@test.com", first_name='Autoenrolled')
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'student3_0@test.com, student3_1@test.com, student3_2@test.com', 'auto_enroll': 'on', 'email_students': 'on'}) response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'student3_0@test.com, student3_1@test.com, student3_2@test.com', 'auto_enroll': 'on', 'email_students': 'on'})
# Check the page output # Check the page output
...@@ -254,7 +254,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -254,7 +254,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
cea = CourseEnrollmentAllowed(email='student4_0@test.com', course_id=course.id) cea = CourseEnrollmentAllowed(email='student4_0@test.com', course_id=course.id)
cea.save() cea.save()
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
response = self.client.post(url, {'action': 'Unenroll multiple students', 'multiple_students': 'student4_0@test.com, student2@test.com, student3@test.com', 'email_students': 'on'}) response = self.client.post(url, {'action': 'Unenroll multiple students', 'multiple_students': 'student4_0@test.com, student2@test.com, student3@test.com', 'email_students': 'on'})
# Check the page output # Check the page output
...@@ -301,7 +301,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -301,7 +301,7 @@ class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
# Create activated, but not enrolled, user # Create activated, but not enrolled, user
UserFactory.create(username="student5_0", email="student5_0@test.com", first_name="ShibTest", last_name="Enrolled") UserFactory.create(username="student5_0", email="student5_0@test.com", first_name="ShibTest", last_name="Enrolled")
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'student5_0@test.com, student5_1@test.com', 'auto_enroll': 'on', 'email_students': 'on'}) response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'student5_0@test.com, student5_1@test.com', 'auto_enroll': 'on', 'email_students': 'on'})
# Check the page output # Check the page output
......
...@@ -67,7 +67,7 @@ class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTest ...@@ -67,7 +67,7 @@ class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTest
def test_add_forum_admin_users_for_unknown_user(self): def test_add_forum_admin_users_for_unknown_user(self):
course = self.toy course = self.toy
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
username = 'unknown' username = 'unknown'
for action in ['Add', 'Remove']: for action in ['Add', 'Remove']:
for rolename in FORUM_ROLES: for rolename in FORUM_ROLES:
...@@ -76,7 +76,7 @@ class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTest ...@@ -76,7 +76,7 @@ class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTest
def test_add_forum_admin_users_for_missing_roles(self): def test_add_forum_admin_users_for_missing_roles(self):
course = self.toy course = self.toy
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
username = 'u1' username = 'u1'
for action in ['Add', 'Remove']: for action in ['Add', 'Remove']:
for rolename in FORUM_ROLES: for rolename in FORUM_ROLES:
...@@ -86,7 +86,7 @@ class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTest ...@@ -86,7 +86,7 @@ class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTest
def test_remove_forum_admin_users_for_missing_users(self): def test_remove_forum_admin_users_for_missing_users(self):
course = self.toy course = self.toy
self.initialize_roles(course.id) self.initialize_roles(course.id)
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
username = 'u1' username = 'u1'
action = 'Remove' action = 'Remove'
for rolename in FORUM_ROLES: for rolename in FORUM_ROLES:
...@@ -96,7 +96,7 @@ class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTest ...@@ -96,7 +96,7 @@ class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTest
def test_add_and_remove_forum_admin_users(self): def test_add_and_remove_forum_admin_users(self):
course = self.toy course = self.toy
self.initialize_roles(course.id) self.initialize_roles(course.id)
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
username = 'u2' username = 'u2'
for rolename in FORUM_ROLES: for rolename in FORUM_ROLES:
response = 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})
...@@ -109,7 +109,7 @@ class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTest ...@@ -109,7 +109,7 @@ class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTest
def test_add_and_read_forum_admin_users(self): def test_add_and_read_forum_admin_users(self):
course = self.toy course = self.toy
self.initialize_roles(course.id) self.initialize_roles(course.id)
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
username = 'u2' username = 'u2'
for rolename in FORUM_ROLES: for rolename in FORUM_ROLES:
# perform an add, and follow with a second identical add: # perform an add, and follow with a second identical add:
...@@ -121,7 +121,7 @@ class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTest ...@@ -121,7 +121,7 @@ class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTest
def test_add_nonstaff_forum_admin_users(self): def test_add_nonstaff_forum_admin_users(self):
course = self.toy course = self.toy
self.initialize_roles(course.id) self.initialize_roles(course.id)
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
username = 'u1' username = 'u1'
rolename = FORUM_ROLE_ADMINISTRATOR rolename = FORUM_ROLE_ADMINISTRATOR
response = 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})
...@@ -130,7 +130,7 @@ class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTest ...@@ -130,7 +130,7 @@ class TestInstructorDashboardForumAdmin(ModuleStoreTestCase, LoginEnrollmentTest
def test_list_forum_admin_users(self): def test_list_forum_admin_users(self):
course = self.toy course = self.toy
self.initialize_roles(course.id) self.initialize_roles(course.id)
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': course.id})
username = 'u2' username = 'u2'
added_roles = [FORUM_ROLE_STUDENT] # u2 is already added as a student to the discussion forums 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')) self.assertTrue(has_forum_access(username, course.id, 'Student'))
......
...@@ -67,7 +67,7 @@ class TestGradebook(ModuleStoreTestCase): ...@@ -67,7 +67,7 @@ class TestGradebook(ModuleStoreTestCase):
module_state_key=Location(item.location).url() module_state_key=Location(item.location).url()
) )
self.response = self.client.get(reverse('gradebook', args=(self.course.id,))) self.response = self.client.get(reverse('gradebook_legacy', args=(self.course.id,)))
def test_response_code(self): def test_response_code(self):
self.assertEquals(self.response.status_code, 200) self.assertEquals(self.response.status_code, 200)
......
...@@ -45,7 +45,7 @@ class TestRawGradeCSV(TestSubmittingProblems): ...@@ -45,7 +45,7 @@ class TestRawGradeCSV(TestSubmittingProblems):
resp = self.submit_question_answer('p2', {'2_1': 'Correct'}) resp = self.submit_question_answer('p2', {'2_1': 'Correct'})
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': self.course.id})
msg = "url = {0}\n".format(url) msg = "url = {0}\n".format(url)
response = self.client.post(url, {'action': 'Download CSV of all RAW grades'}) response = self.client.post(url, {'action': 'Download CSV of all RAW grades'})
msg += "instructor dashboard download raw csv grades: response = '{0}'\n".format(response) msg += "instructor dashboard download raw csv grades: response = '{0}'\n".format(response)
......
...@@ -53,7 +53,7 @@ class InstructorResetStudentStateTest(ModuleStoreTestCase, LoginEnrollmentTestCa ...@@ -53,7 +53,7 @@ class InstructorResetStudentStateTest(ModuleStoreTestCase, LoginEnrollmentTestCa
sub_api.set_score(submission['uuid'], 1, 2) sub_api.set_score(submission['uuid'], 1, 2)
# Delete student state using the instructor dash # Delete student state using the instructor dash
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) url = reverse('instructor_dashboard_legacy', kwargs={'course_id': self.course.id})
response = self.client.post(url, { response = self.client.post(url, {
'action': 'Delete student state for module', 'action': 'Delete student state for module',
'unique_student_identifier': self.student.email, 'unique_student_identifier': self.student.email,
......
...@@ -1053,7 +1053,10 @@ def send_email(request, course_id): ...@@ -1053,7 +1053,10 @@ def send_email(request, course_id):
# Submit the task, so that the correct InstructorTask object gets created (for monitoring purposes) # Submit the task, so that the correct InstructorTask object gets created (for monitoring purposes)
instructor_task.api.submit_bulk_course_email(request, course_id, email.id) # pylint: disable=E1101 instructor_task.api.submit_bulk_course_email(request, course_id, email.id) # pylint: disable=E1101
response_payload = {'course_id': course_id} response_payload = {
'course_id': course_id,
'success': True,
}
return JsonResponse(response_payload) return JsonResponse(response_payload)
......
...@@ -6,6 +6,7 @@ from django.utils.translation import ugettext as _ ...@@ -6,6 +6,7 @@ from django.utils.translation import ugettext as _
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
from django.views.decorators.cache import cache_control from django.views.decorators.cache import cache_control
from edxmako.shortcuts import render_to_response from edxmako.shortcuts import render_to_response
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.html import escape from django.utils.html import escape
from django.http import Http404 from django.http import Http404
...@@ -18,9 +19,10 @@ from xmodule.modulestore.django import modulestore ...@@ -18,9 +19,10 @@ from xmodule.modulestore.django import modulestore
from xblock.field_data import DictFieldData from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds from xblock.fields import ScopeIds
from courseware.access import has_access from courseware.access import has_access
from courseware.courses import get_course_by_id, get_cms_course_link from courseware.courses import get_course_by_id, get_cms_course_link, get_course_with_access
from django_comment_client.utils import has_forum_access from django_comment_client.utils import has_forum_access
from django_comment_common.models import FORUM_ROLE_ADMINISTRATOR from django_comment_common.models import FORUM_ROLE_ADMINISTRATOR
from instructor.offline_gradecalc import student_grades
from student.models import CourseEnrollment from student.models import CourseEnrollment
from bulk_email.models import CourseAuthorization from bulk_email.models import CourseAuthorization
from class_dashboard.dashboard_data import get_section_display_name, get_array_section_has_problem from class_dashboard.dashboard_data import get_section_display_name, get_array_section_has_problem
...@@ -79,7 +81,7 @@ def instructor_dashboard_2(request, course_id): ...@@ -79,7 +81,7 @@ def instructor_dashboard_2(request, course_id):
context = { context = {
'course': course, 'course': course,
'old_dashboard_url': reverse('instructor_dashboard', kwargs={'course_id': course_id}), 'old_dashboard_url': reverse('instructor_dashboard_legacy', kwargs={'course_id': course_id}),
'studio_url': studio_url, 'studio_url': studio_url,
'sections': sections, 'sections': sections,
'disable_buttons': disable_buttons, 'disable_buttons': disable_buttons,
...@@ -156,15 +158,23 @@ def _section_membership(course_id, access): ...@@ -156,15 +158,23 @@ def _section_membership(course_id, access):
def _section_student_admin(course_id, access): def _section_student_admin(course_id, access):
""" Provide data for the corresponding dashboard section """ """ Provide data for the corresponding dashboard section """
is_small_course = False
enrollment_count = CourseEnrollment.num_enrolled_in(course_id)
max_enrollment_for_buttons = settings.FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS")
if max_enrollment_for_buttons is not None:
is_small_course = enrollment_count <= max_enrollment_for_buttons
section_data = { section_data = {
'section_key': 'student_admin', 'section_key': 'student_admin',
'section_display_name': _('Student Admin'), 'section_display_name': _('Student Admin'),
'access': access, 'access': access,
'is_small_course': is_small_course,
'get_student_progress_url_url': reverse('get_student_progress_url', kwargs={'course_id': course_id}), 'get_student_progress_url_url': reverse('get_student_progress_url', kwargs={'course_id': course_id}),
'enrollment_url': reverse('students_update_enrollment', kwargs={'course_id': course_id}), 'enrollment_url': reverse('students_update_enrollment', kwargs={'course_id': course_id}),
'reset_student_attempts_url': reverse('reset_student_attempts', kwargs={'course_id': course_id}), 'reset_student_attempts_url': reverse('reset_student_attempts', kwargs={'course_id': course_id}),
'rescore_problem_url': reverse('rescore_problem', kwargs={'course_id': course_id}), 'rescore_problem_url': reverse('rescore_problem', kwargs={'course_id': course_id}),
'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': course_id}), 'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': course_id}),
'spoc_gradebook_url': reverse('spoc_gradebook', kwargs={'course_id': course_id}),
} }
return section_data return section_data
...@@ -246,3 +256,43 @@ def _section_metrics(course_id, access): ...@@ -246,3 +256,43 @@ def _section_metrics(course_id, access):
'get_students_problem_grades_url': reverse('get_students_problem_grades'), 'get_students_problem_grades_url': reverse('get_students_problem_grades'),
} }
return section_data return section_data
#---- Gradebook (shown to small courses only) ----
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def spoc_gradebook(request, course_id):
"""
Show the gradebook for this course:
- Only shown for courses with enrollment < settings.FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS")
- Only displayed to course staff
"""
course = get_course_with_access(request.user, course_id, 'staff', depth=None)
enrolled_students = User.objects.filter(
courseenrollment__course_id=course_id,
courseenrollment__is_active=1
).order_by('username').select_related("profile")
# TODO (vshnayder): implement pagination to show to large courses
max_num_students = settings.FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS")
enrolled_students = enrolled_students[:max_num_students] # HACK!
student_info = [
{
'username': student.username,
'id': student.id,
'email': student.email,
'grade_summary': student_grades(student, request, course),
'realname': student.profile.name,
}
for student in enrolled_students
]
return render_to_response('courseware/gradebook.html', {
'students': student_info,
'course': course,
'course_id': course_id,
# Checked above
'staff_access': True,
'ordered_grades': sorted(course.grade_cutoffs.items(), key=lambda i: i[1], reverse=True),
})
""" """
Instructor Views Instructor Views
""" """
## NOTE: This is the code for the legacy instructor dashboard
## We are no longer supporting this file or accepting changes into it.
from contextlib import contextmanager from contextlib import contextmanager
import csv import csv
import json import json
...@@ -946,8 +949,7 @@ def instructor_dashboard(request, course_id): ...@@ -946,8 +949,7 @@ def instructor_dashboard(request, course_id):
'metrics_results': metrics_results, 'metrics_results': metrics_results,
} }
if settings.FEATURES.get('ENABLE_INSTRUCTOR_BETA_DASHBOARD'): context['standard_dashboard_url'] = reverse('instructor_dashboard', kwargs={'course_id': course_id})
context['beta_dashboard_url'] = reverse('instructor_dashboard_2', kwargs={'course_id': course_id})
return render_to_response('courseware/instructor_dashboard.html', context) return render_to_response('courseware/instructor_dashboard.html', context)
......
...@@ -169,8 +169,10 @@ FEATURES = { ...@@ -169,8 +169,10 @@ FEATURES = {
# Enable instructor to assign individual due dates # Enable instructor to assign individual due dates
'INDIVIDUAL_DUE_DATES': False, 'INDIVIDUAL_DUE_DATES': False,
# Enable instructor dash beta version link # Enable legacy instructor dashboard
'ENABLE_INSTRUCTOR_BETA_DASHBOARD': True, 'ENABLE_INSTRUCTOR_LEGACY_DASHBOARD': True,
# Is this an edX-owned domain? (used on instructor dashboard)
'IS_EDX_DOMAIN': False,
# Toggle to enable certificates of courses on dashboard # Toggle to enable certificates of courses on dashboard
'ENABLE_VERIFIED_CERTIFICATES': False, 'ENABLE_VERIFIED_CERTIFICATES': False,
......
...@@ -31,11 +31,13 @@ FEATURES['ENABLE_SERVICE_STATUS'] = True ...@@ -31,11 +31,13 @@ FEATURES['ENABLE_SERVICE_STATUS'] = True
FEATURES['ENABLE_INSTRUCTOR_EMAIL'] = True # Enable email for all Studio courses FEATURES['ENABLE_INSTRUCTOR_EMAIL'] = True # Enable email for all Studio courses
FEATURES['REQUIRE_COURSE_EMAIL_AUTH'] = False # Give all courses email (don't require django-admin perms) FEATURES['REQUIRE_COURSE_EMAIL_AUTH'] = False # Give all courses email (don't require django-admin perms)
FEATURES['ENABLE_HINTER_INSTRUCTOR_VIEW'] = True FEATURES['ENABLE_HINTER_INSTRUCTOR_VIEW'] = True
FEATURES['ENABLE_INSTRUCTOR_BETA_DASHBOARD'] = True FEATURES['ENABLE_INSTRUCTOR_LEGACY_DASHBOARD'] = True
FEATURES['MULTIPLE_ENROLLMENT_ROLES'] = True FEATURES['MULTIPLE_ENROLLMENT_ROLES'] = True
FEATURES['ENABLE_SHOPPING_CART'] = True FEATURES['ENABLE_SHOPPING_CART'] = True
FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True
FEATURES['ENABLE_S3_GRADE_DOWNLOADS'] = True FEATURES['ENABLE_S3_GRADE_DOWNLOADS'] = True
FEATURES['IS_EDX_DOMAIN'] = True # Is this an edX-owned domain? (used on instructor dashboard)
FEEDBACK_SUBMISSION_EMAIL = "dummy@example.com" FEEDBACK_SUBMISSION_EMAIL = "dummy@example.com"
......
...@@ -33,7 +33,7 @@ FEATURES['ENABLE_SERVICE_STATUS'] = True ...@@ -33,7 +33,7 @@ FEATURES['ENABLE_SERVICE_STATUS'] = True
FEATURES['ENABLE_HINTER_INSTRUCTOR_VIEW'] = True FEATURES['ENABLE_HINTER_INSTRUCTOR_VIEW'] = True
FEATURES['ENABLE_INSTRUCTOR_BETA_DASHBOARD'] = True FEATURES['ENABLE_INSTRUCTOR_LEGACY_DASHBOARD'] = True
FEATURES['ENABLE_SHOPPING_CART'] = True FEATURES['ENABLE_SHOPPING_CART'] = True
......
...@@ -99,7 +99,7 @@ setup_instructor_dashboard = (idash_content) => ...@@ -99,7 +99,7 @@ setup_instructor_dashboard = (idash_content) =>
e.preventDefault() e.preventDefault()
# deactivate all link & section styles # deactivate all link & section styles
idash_content.find(".#{CSS_INSTRUCTOR_NAV}").children().removeClass CSS_ACTIVE_SECTION idash_content.find(".#{CSS_INSTRUCTOR_NAV} li").children().removeClass CSS_ACTIVE_SECTION
idash_content.find(".#{CSS_IDASH_SECTION}").removeClass CSS_ACTIVE_SECTION idash_content.find(".#{CSS_IDASH_SECTION}").removeClass CSS_ACTIVE_SECTION
# discover section paired to link # discover section paired to link
......
if $('.instructor-dashboard-wrapper').length == 1 if $('.instructor-dashboard-wrapper').length == 1
analytics.track "Loaded an Instructor Dashboard Page", analytics.track "Loaded a Legacy Instructor Dashboard Page",
location: window.location.pathname location: window.location.pathname
dashboard_page: $('.navbar .selectedmode').text() dashboard_page: $('.navbar .selectedmode').text()
...@@ -6,7 +6,7 @@ describe('StaffDebugActions', function() { ...@@ -6,7 +6,7 @@ describe('StaffDebugActions', function() {
describe('get_url ', function() { describe('get_url ', function() {
it('defines url to courseware ajax entry point', function() { it('defines url to courseware ajax entry point', function() {
spyOn(StaffDebug, "get_current_url").andReturn("/courses/edX/Open_DemoX/edx_demo_course/courseware/stuff"); spyOn(StaffDebug, "get_current_url").andReturn("/courses/edX/Open_DemoX/edx_demo_course/courseware/stuff");
expect(StaffDebug.get_url('rescore_problem')).toBe('/courses/edX/Open_DemoX/edx_demo_course/instructor_dashboard/api/rescore_problem'); expect(StaffDebug.get_url('rescore_problem')).toBe('/courses/edX/Open_DemoX/edx_demo_course/instructor/api/rescore_problem');
}); });
}); });
...@@ -40,7 +40,7 @@ describe('StaffDebugActions', function() { ...@@ -40,7 +40,7 @@ describe('StaffDebugActions', function() {
'delete_module': false 'delete_module': false
}); });
expect($.ajax.mostRecentCall.args[0]['url']).toEqual( expect($.ajax.mostRecentCall.args[0]['url']).toEqual(
'/instructor_dashboard/api/reset_student_attempts' '/instructor/api/reset_student_attempts'
); );
$('#' + fixture_id).remove(); $('#' + fixture_id).remove();
}); });
...@@ -59,7 +59,7 @@ describe('StaffDebugActions', function() { ...@@ -59,7 +59,7 @@ describe('StaffDebugActions', function() {
'delete_module': true 'delete_module': true
}); });
expect($.ajax.mostRecentCall.args[0]['url']).toEqual( expect($.ajax.mostRecentCall.args[0]['url']).toEqual(
'/instructor_dashboard/api/reset_student_attempts' '/instructor/api/reset_student_attempts'
); );
$('#' + fixture_id).remove(); $('#' + fixture_id).remove();
...@@ -79,7 +79,7 @@ describe('StaffDebugActions', function() { ...@@ -79,7 +79,7 @@ describe('StaffDebugActions', function() {
'delete_module': false 'delete_module': false
}); });
expect($.ajax.mostRecentCall.args[0]['url']).toEqual( expect($.ajax.mostRecentCall.args[0]['url']).toEqual(
'/instructor_dashboard/api/rescore_problem' '/instructor/api/rescore_problem'
); );
$('#' + fixture_id).remove(); $('#' + fixture_id).remove();
}); });
......
...@@ -7,7 +7,7 @@ var StaffDebug = (function(){ ...@@ -7,7 +7,7 @@ var StaffDebug = (function(){
get_url = function(action){ get_url = function(action){
var pathname = this.get_current_url(); var pathname = this.get_current_url();
var url = pathname.substr(0,pathname.indexOf('/courseware')) + '/instructor_dashboard/api/' + action; var url = pathname.substr(0,pathname.indexOf('/courseware')) + '/instructor/api/' + action;
return url; return url;
} }
......
...@@ -121,9 +121,9 @@ ...@@ -121,9 +121,9 @@
} }
} }
} }
//Metrics tab //Metrics tab
.metrics-container { .metrics-container {
position: relative; position: relative;
width: 100%; width: 100%;
...@@ -170,6 +170,18 @@ ...@@ -170,6 +170,18 @@
border-radius: 5px; border-radius: 5px;
margin-top: 25px; margin-top: 25px;
} }
.wrapper-msg {
margin-bottom: ($baseline*1.5);
.msg {
margin-bottom: 0;
}
.note {
margin: 0;
}
}
} }
...@@ -34,6 +34,11 @@ ...@@ -34,6 +34,11 @@
} }
// system feedback - messages // system feedback - messages
.wrapper-msg {
margin-bottom: ($baseline*1.5);
}
.msg { .msg {
border-radius: 1px; border-radius: 1px;
padding: $baseline/2 $baseline*0.75; padding: $baseline/2 $baseline*0.75;
...@@ -43,6 +48,10 @@ ...@@ -43,6 +48,10 @@
.copy { .copy {
font-weight: 600; font-weight: 600;
} }
&.is-shown {
display: block;
}
} }
// TYPE: warning // TYPE: warning
...@@ -51,6 +60,10 @@ ...@@ -51,6 +60,10 @@
background: tint($warning-color,95%); background: tint($warning-color,95%);
display: none; display: none;
color: $warning-color; color: $warning-color;
&.is-shown {
display: block;
}
} }
// TYPE: confirm // TYPE: confirm
...@@ -59,6 +72,10 @@ ...@@ -59,6 +72,10 @@
background: tint($confirm-color,95%); background: tint($confirm-color,95%);
display: none; display: none;
color: $confirm-color; color: $confirm-color;
&.is-shown {
display: block;
}
} }
// TYPE: confirm // TYPE: confirm
...@@ -69,6 +86,10 @@ ...@@ -69,6 +86,10 @@
.copy { .copy {
color: $error-color; color: $error-color;
} }
&.is-shown {
display: block;
}
} }
// inline copy // inline copy
...@@ -110,6 +131,11 @@ section.instructor-dashboard-content-2 { ...@@ -110,6 +131,11 @@ section.instructor-dashboard-content-2 {
// border: 1px solid blue; // border: 1px solid blue;
// } // }
.wrap-instructor-info {
display: inline;
top: 0;
}
.request-response-error { .request-response-error {
margin: 0; margin: 0;
padding-bottom: ($baseline); padding-bottom: ($baseline);
...@@ -141,8 +167,10 @@ section.instructor-dashboard-content-2 { ...@@ -141,8 +167,10 @@ section.instructor-dashboard-content-2 {
h1 { h1 {
@extend .top-header; @extend .top-header;
display: inline-block;
padding-bottom: 0; padding-bottom: 0;
border-bottom: 0; border-bottom: 0;
margin-bottom: ($baseline*.75);
} }
input[type="button"] { input[type="button"] {
...@@ -163,20 +191,29 @@ section.instructor-dashboard-content-2 { ...@@ -163,20 +191,29 @@ section.instructor-dashboard-content-2 {
} }
.instructor-nav { .instructor-nav {
padding-bottom: 1em; @extend %ui-no-list;
border-top: 1px solid $gray-l3;
border-bottom: 1px solid $gray-l3;
border-bottom: 1px solid #C8C8C8; .nav-item {
a { @extend %t-copy-base;
margin-right: 1.2em; display: inline-block;
} margin: ($baseline/2) $baseline;
.active-section { a {
color: #551A8B; display: block;
text-transform: uppercase;
&.active-section {
color: $black;
}
}
} }
} }
section.idash-section { section.idash-section {
display: none; display: none;
margin-top: ($baseline*1.5);
// background-color: #0f0; // background-color: #0f0;
&.active-section { &.active-section {
...@@ -560,14 +597,14 @@ section.instructor-dashboard-content-2 { ...@@ -560,14 +597,14 @@ section.instructor-dashboard-content-2 {
float: left; float: left;
clear: both; clear: both;
margin-top: 25px; margin-top: 25px;
.metrics-left { .metrics-left {
position: relative; position: relative;
width: 30%; width: 30%;
height: 640px; height: 640px;
float: left; float: left;
margin-right: 2.5%; margin-right: 2.5%;
svg { svg {
width: 100%; width: 100%;
} }
...@@ -579,33 +616,33 @@ section.instructor-dashboard-content-2 { ...@@ -579,33 +616,33 @@ section.instructor-dashboard-content-2 {
float: left; float: left;
margin-left: 2.5%; margin-left: 2.5%;
margin-bottom: 25px; margin-bottom: 25px;
svg { svg {
width: 100%; width: 100%;
} }
} }
svg { svg {
.stacked-bar { .stacked-bar {
cursor: pointer; cursor: pointer;
} }
} }
.metrics-tooltip { .metrics-tooltip {
width: 250px; width: 250px;
background-color: lightgray; background-color: lightgray;
padding: 3px; padding: 3px;
} }
.metrics-overlay { .metrics-overlay {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
background-color: rgba(255,255,255, .75); background-color: rgba(255,255,255, .75);
display: none; display: none;
.metrics-overlay-content-wrapper { .metrics-overlay-content-wrapper {
position: relative; position: relative;
display: block; display: block;
...@@ -616,23 +653,23 @@ section.instructor-dashboard-content-2 { ...@@ -616,23 +653,23 @@ section.instructor-dashboard-content-2 {
border: 1px solid #000; border: 1px solid #000;
border-radius: 25px; border-radius: 25px;
padding: 2.5%; padding: 2.5%;
.metrics-overlay-title { .metrics-overlay-title {
display: block; display: block;
height: 50px; height: 50px;
margin-bottom: 10px; margin-bottom: 10px;
font-weight: bold; font-weight: bold;
} }
.metrics-overlay-content { .metrics-overlay-content {
width: 100%; width: 100%;
height: 370px; height: 370px;
overflow: auto; overflow: auto;
border: 1px solid #000; border: 1px solid #000;
table { table {
width: 100%; width: 100%;
.header { .header {
background-color: #ddd; background-color: #ddd;
} }
...@@ -641,18 +678,18 @@ section.instructor-dashboard-content-2 { ...@@ -641,18 +678,18 @@ section.instructor-dashboard-content-2 {
} }
} }
} }
.overflow-message { .overflow-message {
padding-top: 20px; padding-top: 20px;
} }
.download-csv { .download-csv {
position: absolute; position: absolute;
display: none; display: none;
right: 2%; right: 2%;
bottom: 2%; bottom: 2%;
} }
.close-button { .close-button {
position: absolute; position: absolute;
right: 1.5%; right: 1.5%;
...@@ -661,27 +698,27 @@ section.instructor-dashboard-content-2 { ...@@ -661,27 +698,27 @@ section.instructor-dashboard-content-2 {
} }
} }
} }
.stacked-bar-graph-legend { .stacked-bar-graph-legend {
fill: white; fill: white;
} }
p.loading { p.loading {
padding-top: 100px; padding-top: 100px;
text-align: center; text-align: center;
} }
p.nothing { p.nothing {
padding-top: 25px; padding-top: 25px;
} }
h3.attention { h3.attention {
padding: 10px; padding: 10px;
border: 1px solid #999; border: 1px solid #999;
border-radius: 5px; border-radius: 5px;
margin-top: 25px; margin-top: 25px;
} }
input#graph_reload { input#graph_reload {
display: none; display: none;
} }
......
## NOTE: This is the template for the LEGACY instructor dashboard ##
## We are no longer supporting this file or accepting changes into it. ##
## Please see lms/templates/instructor for instructor dashboard templates ##
<%! from django.utils.translation import ugettext as _ %> <%! from django.utils.translation import ugettext as _ %>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%inherit file="../main.html" /> <%inherit file="../main.html" />
<%namespace name='static' file='/static_content.html'/> <%namespace name='static' file='/static_content.html'/>
<%block name="pagetitle">${_("Instructor Dashboard")}</%block> <%block name="pagetitle">${_("Legacy Instructor Dashboard")}</%block>
<%block name="nav_skip">#instructor-dashboard-content</%block> <%block name="nav_skip">#instructor-dashboard-content</%block>
<%block name="headextra"> <%block name="headextra">
...@@ -122,13 +126,19 @@ function goto( mode) ...@@ -122,13 +126,19 @@ function goto( mode)
%if studio_url: %if studio_url:
<a class="instructor-info-action" href="${studio_url}">${_("View Course in Studio")}</a> <a class="instructor-info-action" href="${studio_url}">${_("View Course in Studio")}</a>
%endif %endif
%if settings.FEATURES.get('ENABLE_INSTRUCTOR_BETA_DASHBOARD'): <a class="instructor-info-action beta-button" href="${ standard_dashboard_url }">${_("Back To Instructor Dashboard")}</a>
<a class="instructor-info-action beta-button" href="${ beta_dashboard_url }">${_("Try New Beta Dashboard")}</a>
%endif
</div> </div>
<h1>${_("Instructor Dashboard")}</h1> <h1>${_("Instructor Dashboard")}</h1>
%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)
<div class="wrapper-msg urgency-low msg-warning is-shown">
<p>${_("You are using the legacy instructor dashboard, which we will retire in the near future.")} <a href="${ standard_dashboard_url }">${_("Return to the Instructor Dashboard")} <i class="icon-double-angle-right"></i></a></p>
<p class="note">${_("If the Instructor Dashboard is missing functionality, please contact your PM to let us know.")}</p>
</div>
%endif
<h2 class="navbar">[ <a href="#" onclick="goto('Grades');" class="${modeflag.get('Grades')}">Grades</a> | <h2 class="navbar">[ <a href="#" onclick="goto('Grades');" class="${modeflag.get('Grades')}">Grades</a> |
%if settings.FEATURES.get('ENABLE_PSYCHOMETRICS'): %if settings.FEATURES.get('ENABLE_PSYCHOMETRICS'):
<a href="#" onclick="goto('Psychometrics');" class="${modeflag.get('Psychometrics')}">${_("Psychometrics")}</a> | <a href="#" onclick="goto('Psychometrics');" class="${modeflag.get('Psychometrics')}">${_("Psychometrics")}</a> |
...@@ -187,11 +197,11 @@ function goto( mode) ...@@ -187,11 +197,11 @@ function goto( mode)
% endif % endif
<p> <p>
<a href="${reverse('gradebook', kwargs=dict(course_id=course.id))}" class="${'is-disabled' if disable_buttons else ''}">${_("Gradebook")}</a> <a href="${reverse('gradebook_legacy', kwargs=dict(course_id=course.id))}" class="${'is-disabled' if disable_buttons else ''}">${_("Gradebook")}</a>
</p> </p>
<p> <p>
<a href="${reverse('grade_summary', kwargs=dict(course_id=course.id))}" class="${'is-disabled' if disable_buttons else ''}">${_("Grade summary")}</a> <a href="${reverse('grade_summary_legacy', kwargs=dict(course_id=course.id))}" class="${'is-disabled' if disable_buttons else ''}">${_("Grade summary")}</a>
</p> </p>
<p> <p>
...@@ -401,7 +411,7 @@ function goto( mode) ...@@ -401,7 +411,7 @@ function goto( mode)
%else: %else:
<p>${_("User requires forum administrator privileges to perform administration tasks. See instructor.")}</p> <p>${_("User requires forum administrator privileges to perform administration tasks. See instructor.")}</p>
%endif %endif
<br /> <br />
<h2>${_("Explanation of Roles:")}</h2> <h2>${_("Explanation of Roles:")}</h2>
<p>${_("Forum Moderators: can edit or delete any post, remove misuse flags, close and re-open threads, endorse " <p>${_("Forum Moderators: can edit or delete any post, remove misuse flags, close and re-open threads, endorse "
...@@ -686,7 +696,7 @@ function goto( mode) ...@@ -686,7 +696,7 @@ function goto( mode)
</script> </script>
<div id="metrics"></div> <div id="metrics"></div>
<h3 class="attention">${_("Loading the latest graphs for you; depending on your class size, this may take a few minutes.")}</h3> <h3 class="attention">${_("Loading the latest graphs for you; depending on your class size, this may take a few minutes.")}</h3>
%for i in range(0,len(metrics_results['section_display_name'])): %for i in range(0,len(metrics_results['section_display_name'])):
......
...@@ -53,35 +53,39 @@ ...@@ -53,35 +53,39 @@
<script language="JavaScript" type="text/javascript"></script> <script language="JavaScript" type="text/javascript"></script>
<section class="container"> <section class="container">
<div class="instructor-dashboard-wrapper-2"> <div class="instructor-dashboard-wrapper-2">
<section class="instructor-dashboard-content-2" id="instructor-dashboard-content"> <section class="instructor-dashboard-content-2" id="instructor-dashboard-content">
<div class="wrap-instructor-info studio-view"> <div class="wrap-instructor-info studio-view">
%if studio_url: %if studio_url:
<a class="instructor-info-action" href="${studio_url}">${_("View Course in Studio")}</a> <a class="instructor-info-action" href="${studio_url}">${_("View Course in Studio")}</a>
%endif %endif
<a class="instructor-info-action" href="${ old_dashboard_url }"> ${_("Back to Standard Dashboard")} </a> %if settings.FEATURES.get('ENABLE_INSTRUCTOR_LEGACY_DASHBOARD'):
</div> <a class="instructor-info-action" href="${ old_dashboard_url }"> ${_("Revert to Legacy Dashboard")} </a>
%endif
<h1>${_("Instructor Dashboard")}</h1> </div>
<hr />
## links which are tied to idash-sections below. <h1>${_("Instructor Dashboard")}</h1>
## the links are acativated and handled in instructor_dashboard.coffee <div class="wrapper-msg urgency-low msg-warning is-shown">
## when the javascript loads, it clicks on the first section <p>${_("We've changed the look and feel of the Instructor Dashboard. During this transition time, you can still access the old Instructor Dashboard by clicking the 'Revert to Legacy Dashboard' button above.")}</p>
<h2 class="instructor-nav"> </div>
% for section_data in sections:
<a href="" data-section="${ section_data['section_key'] }">${_(section_data['section_display_name'])}</a> ## links which are tied to idash-sections below.
% endfor ## the links are activated and handled in instructor_dashboard.coffee
</h2> ## when the javascript loads, it clicks on the first section
<ul class="instructor-nav">
## each section corresponds to a section_data sub-dictionary provided by the view % for section_data in sections:
## to keep this short, sections can be pulled out into their own files <li class="nav-item"><a href="" data-section="${ section_data['section_key'] }">${_(section_data['section_display_name'])}</a></li>
% endfor
% for section_data in sections: </ul>
<section id="${ section_data['section_key'] }" class="idash-section">
<%include file="${ section_data['section_key'] }.html" args="section_data=section_data" /> ## each section corresponds to a section_data sub-dictionary provided by the view
</section> ## to keep this short, sections can be pulled out into their own files
% endfor
% for section_data in sections:
<section id="${ section_data['section_key'] }" class="idash-section">
<%include file="${ section_data['section_key'] }.html" args="section_data=section_data" />
</section> </section>
</div> % endfor
</section>
</div>
</section> </section>
<%! from django.utils.translation import ugettext as _ %> <%! from django.utils.translation import ugettext as _ %>
<%page args="section_data"/> <%page args="section_data"/>
<div>
%if section_data['is_small_course']:
## Show the gradebook for small courses
<h2>${_("Student Gradebook")}</h2>
<p>
${_("Click here to view the gradebook for enrolled students. This feature is only visible to courses with a small number of total enrolled students.")}
</p>
<br>
<p>
<a href="${ section_data['spoc_gradebook_url'] }" class="progress-link"> ${_("View Gradebook")} </a>
</p>
<hr>
%endif
</div>
<div class="student-specific-container action-type-container"> <div class="student-specific-container action-type-container">
<h2>${_("Student-specific grade inspection")}</h2> <h2>${_("Student-specific grade inspection")}</h2>
<div class="request-response-error"></div> <div class="request-response-error"></div>
......
...@@ -270,14 +270,15 @@ if settings.COURSEWARE_ENABLED: ...@@ -270,14 +270,15 @@ if settings.COURSEWARE_ENABLED:
# For the instructor # For the instructor
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor$',
'instructor.views.legacy.instructor_dashboard', name="instructor_dashboard"), 'instructor.views.instructor_dashboard.instructor_dashboard_2', name="instructor_dashboard"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor/api/',
include('instructor.views.api_urls')),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/gradebook$',
'instructor.views.instructor_dashboard.spoc_gradebook', name='spoc_gradebook'),
# see ENABLE_INSTRUCTOR_BETA_DASHBOARD section for more urls # see ENABLE_INSTRUCTOR_LEGACY_DASHBOARD section for legacy dash urls
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/gradebook$', # Open Ended grading views
'instructor.views.legacy.gradebook', name='gradebook'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/grade_summary$',
'instructor.views.legacy.grade_summary', name='grade_summary'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading$',
'open_ended_grading.views.staff_grading', name='staff_grading'), 'open_ended_grading.views.staff_grading', name='staff_grading'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading/get_next$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading/get_next$',
...@@ -364,13 +365,14 @@ if settings.COURSEWARE_ENABLED: ...@@ -364,13 +365,14 @@ if settings.COURSEWARE_ENABLED:
) )
if settings.COURSEWARE_ENABLED and settings.FEATURES.get('ENABLE_INSTRUCTOR_BETA_DASHBOARD'): if settings.COURSEWARE_ENABLED and settings.FEATURES.get('ENABLE_INSTRUCTOR_LEGACY_DASHBOARD'):
urlpatterns += ( urlpatterns += (
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor_dashboard$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/legacy_gradebook$',
'instructor.views.instructor_dashboard.instructor_dashboard_2', name="instructor_dashboard_2"), 'instructor.views.legacy.gradebook', name='gradebook_legacy'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/legacy_grade_summary$',
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor_dashboard/api/', 'instructor.views.legacy.grade_summary', name='grade_summary_legacy'),
include('instructor.views.api_urls')) url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/legacy_instructor_dash$',
'instructor.views.legacy.instructor_dashboard', name="instructor_dashboard_legacy"),
) )
if settings.FEATURES.get('CLASS_DASHBOARD'): if settings.FEATURES.get('CLASS_DASHBOARD'):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment