Commit 4385cc50 by Sarina Canelake

Merge pull request #10942 from edx/kill-legacy-dash

Remove Legacy Instructor Dashboard
parents 5b9e0e9d a9fc7aca
#!/usr/bin/python
"""
django management command: dump grades to csv files
for use by batch processes
"""
import csv
from instructor.views.legacy import get_student_grade_summary_data
from courseware.courses import get_course_by_id
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from django.core.management.base import BaseCommand
from instructor.utils import DummyRequest
class Command(BaseCommand):
help = "dump grades to CSV file. Usage: dump_grades course_id_or_dir filename dump_type\n"
help += " course_id_or_dir: either course_id or course_dir\n"
help += " filename: where the output CSV is to be stored\n"
# help += " start_date: end date as M/D/Y H:M (defaults to end of available data)"
help += " dump_type: 'all' or 'raw' (see instructor dashboard)"
def handle(self, *args, **options):
# current grading logic and data schema doesn't handle dates
# datetime.strptime("21/11/06 16:30", "%m/%d/%y %H:%M")
print "args = ", args
course_id = 'MITx/8.01rq_MW/Classical_Mechanics_Reading_Questions_Fall_2012_MW_Section'
fn = "grades.csv"
get_raw_scores = False
if len(args) > 0:
course_id = args[0]
if len(args) > 1:
fn = args[1]
if len(args) > 2:
get_raw_scores = args[2].lower() == 'raw'
request = DummyRequest()
# parse out the course into a coursekey
try:
course_key = CourseKey.from_string(course_id)
# if it's not a new-style course key, parse it from an old-style
# course key
except InvalidKeyError:
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
try:
course = get_course_by_id(course_key)
# Ok with catching general exception here because this is run as a management command
# and the exception is exposed right away to the user.
except Exception as err: # pylint: disable=broad-except
print "-----------------------------------------------------------------------------"
print "Sorry, cannot find course with id {}".format(course_id)
print "Got exception {}".format(err)
print "Please provide a course ID or course data directory name, eg content-mit-801rq"
return
print "-----------------------------------------------------------------------------"
print "Dumping grades from {} to file {} (get_raw_scores={})".format(course.id, fn, get_raw_scores)
datatable = get_student_grade_summary_data(request, course, get_raw_scores=get_raw_scores)
fp = open(fn, 'w')
writer = csv.writer(fp, dialect='excel', quotechar='"', quoting=csv.QUOTE_ALL)
writer.writerow([unicode(s).encode('utf-8') for s in datatable['header']])
for datarow in datatable['data']:
encoded_row = [unicode(s).encode('utf-8') for s in datarow]
writer.writerow(encoded_row)
fp.close()
print "Done: {} records dumped".format(len(datatable['data']))
"""
Tests of various instructor dashboard features that include lists of students
"""
from django.conf import settings
from django.test.client import RequestFactory
from markupsafe import escape
from nose.plugins.attrib import attr
from student.tests.factories import UserFactory, CourseEnrollmentFactory
from edxmako.tests import mako_middleware_process_request
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from instructor.views import legacy
# pylint: disable=missing-docstring
@attr('shard_1')
class TestXss(SharedModuleStoreTestCase):
@classmethod
def setUpClass(cls):
super(TestXss, cls).setUpClass()
cls._course = CourseFactory.create()
def setUp(self):
super(TestXss, self).setUp()
self._request_factory = RequestFactory()
self._evil_student = UserFactory.create(
email="robot+evil@edx.org",
username="evil-robot",
profile__name='<span id="evil">Evil Robot</span>',
)
self._instructor = UserFactory.create(
email="robot+instructor@edx.org",
username="instructor",
is_staff=True
)
CourseEnrollmentFactory.create(
user=self._evil_student,
course_id=self._course.id
)
def _test_action(self, action):
"""
Test for XSS vulnerability in the given action
Build a request with the given action, call the instructor dashboard
view, and check that HTML code in a user's name is properly escaped.
"""
req = self._request_factory.post(
"dummy_url",
data={"action": action}
)
req.user = self._instructor
req.session = {}
mako_middleware_process_request(req)
resp = legacy.instructor_dashboard(req, self._course.id.to_deprecated_string())
respUnicode = resp.content.decode(settings.DEFAULT_CHARSET)
self.assertNotIn(self._evil_student.profile.name, respUnicode)
self.assertIn(escape(self._evil_student.profile.name), respUnicode)
def test_list_enrolled(self):
self._test_action("List enrolled students")
def test_dump_list_of_enrolled(self):
self._test_action("Dump list of enrolled students")
......@@ -195,8 +195,6 @@ def instructor_dashboard_2(request, course_id):
'generate_bulk_certificate_exceptions_url': generate_bulk_certificate_exceptions_url,
'certificate_exception_view_url': certificate_exception_view_url
}
if settings.FEATURES['ENABLE_INSTRUCTOR_LEGACY_DASHBOARD']:
context['old_dashboard_url'] = reverse('instructor_dashboard_legacy', kwargs={'course_id': unicode(course_key)})
return render_to_response('instructor/instructor_dashboard_2/instructor_dashboard_2.html', context)
......
......@@ -205,9 +205,6 @@ FEATURES = {
# Enable Custom Courses for EdX
'CUSTOM_COURSES_EDX': False,
# Enable legacy instructor dashboard
'ENABLE_INSTRUCTOR_LEGACY_DASHBOARD': False,
# Is this an edX-owned domain? (used for edX specific messaging and images)
'IS_EDX_DOMAIN': False,
......
......@@ -28,7 +28,6 @@ FEATURES['ENABLE_SERVICE_STATUS'] = True
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['ENABLE_HINTER_INSTRUCTOR_VIEW'] = True
FEATURES['ENABLE_INSTRUCTOR_LEGACY_DASHBOARD'] = False
FEATURES['MULTIPLE_ENROLLMENT_ROLES'] = True
FEATURES['ENABLE_SHOPPING_CART'] = True
FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True
......
......@@ -62,8 +62,6 @@ FEATURES['ENABLE_SERVICE_STATUS'] = True
FEATURES['ENABLE_HINTER_INSTRUCTOR_VIEW'] = True
FEATURES['ENABLE_INSTRUCTOR_LEGACY_DASHBOARD'] = True
FEATURES['ENABLE_SHOPPING_CART'] = True
FEATURES['ENABLE_VERIFIED_CERTIFICATES'] = True
......
......@@ -49,7 +49,6 @@
@import "views/teams";
// course - instructor-only views
@import "course/instructor/instructor";
@import "course/instructor/instructor_2";
@import "course/instructor/email";
@import "xmodule/descriptors/css/module-styles.scss";
......
.instructor-dashboard-wrapper {
display: table;
position: relative;
.beta-button-wrapper {
position: absolute;
top: 2em;
right: 2em;
}
.studio-edit-link{
position: absolute;
top: 3.5em;
right: 2em;
}
section.instructor-dashboard-content {
@extend .content;
padding: 40px;
width: 100%;
h1 {
@extend .top-header;
}
}
// form fields
.list-fields {
@extend %ui-no-list;
.field {
margin-bottom: $baseline;
&:last-child {
margin-bottom: 0;
}
.tip {
display: block;
margin-top: ($baseline/4);
color: tint(rgb(127,127,127),50%);
@include font-size(12);
}
}
}
// ====================
// system feedback - messages
.msg {
border-radius: 1px;
padding: 10px 15px;
margin-bottom: $baseline;
.copy {
font-weight: 600;
}
}
// TYPE: warning
.msg-warning {
border-top: 2px solid $warning-color;
background: tint($warning-color,95%);
.copy {
color: $warning-color;
}
}
// TYPE: confirm
.msg-confirm {
border-top: 2px solid $confirm-color;
background: tint($confirm-color,95%);
.copy {
color: $confirm-color;
}
}
// TYPE: confirm
.msg-error {
border-top: 2px solid $error-color;
background: tint($error-color,95%);
.copy {
color: $error-color;
}
}
// ====================
// inline copy
.copy-confirm {
color: $confirm-color;
}
.copy-warning {
color: $warning-color;
}
.copy-error {
color: $error-color;
}
.list-advice {
list-style: none;
padding: 0;
margin: 20px 0;
.item {
font-weight: 600;
margin-bottom: ($baseline/2);
&:last-child {
margin-bottom: 0;
}
}
}
//Metrics tab
.metrics-container {
position: relative;
width: 100%;
float: left;
clear: both;
margin-top: 25px;
}
.metrics-left {
position: relative;
width: 30%;
height: 640px;
float: left;
margin-right: 2.5%;
}
.metrics-right {
position: relative;
width: 65%;
height: 295px;
float: left;
margin-left: 2.5%;
margin-bottom: 25px;
}
.metrics-tooltip {
width: 250px;
background-color: lightgray;
padding: 3px;
}
.stacked-bar-graph-legend {
fill: white;
}
p.loading {
padding-top: 100px;
text-align: center;
}
p.nothing {
padding-top: 25px;
}
h3.attention {
padding: 10px;
border: 1px solid #999;
border-radius: 5px;
margin-top: 25px;
}
.wrapper-msg {
margin-bottom: ($baseline*1.5);
.msg {
margin-bottom: 0;
}
.note {
margin: 0;
}
}
}
.rtl .instructor-dashboard-wrapper .beta-button-wrapper,
.rtl .instructor-dashboard-wrapper .studio-edit-link {
left: 2em;
right: auto;
}
......@@ -91,9 +91,6 @@ from django.core.urlresolvers import reverse
%if studio_url:
<a class="instructor-info-action" href="${studio_url}">${_("View Course in Studio")}</a>
%endif
%if settings.FEATURES.get('ENABLE_INSTRUCTOR_LEGACY_DASHBOARD'):
<a class="instructor-info-action" href="${ old_dashboard_url }"> ${_("Revert to Legacy Dashboard")} </a>
%endif
</div>
<h1>${_("Instructor Dashboard")}</h1>
......
......@@ -547,7 +547,6 @@ urlpatterns += (
),
include(COURSE_URLS)
),
# see ENABLE_INSTRUCTOR_LEGACY_DASHBOARD section for legacy dash urls
# Cohorts management
url(
......@@ -753,13 +752,6 @@ if settings.FEATURES.get('ENABLE_STUDENT_HISTORY_VIEW'):
),
)
if settings.FEATURES.get('ENABLE_INSTRUCTOR_LEGACY_DASHBOARD'):
urlpatterns += (
url(r'^courses/{}/legacy_instructor_dash$'.format(settings.COURSE_ID_PATTERN),
'instructor.views.legacy.instructor_dashboard', name="instructor_dashboard_legacy"),
)
if settings.FEATURES.get('CLASS_DASHBOARD'):
urlpatterns += (
url(r'^class_dashboard/', include('class_dashboard.urls')),
......
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