From 2a1c0217f6681b693542d17d391f0b781486166b Mon Sep 17 00:00:00 2001 From: Dmitry Viskov Date: Thu, 9 Feb 2017 00:42:21 +0300 Subject: [PATCH] Open Response Assessment tab for Instructor Dashboard --- .../tests/views/test_instructor_dashboard.py | 24 ++++++++++ .../instructor/views/instructor_dashboard.py | 47 +++++++++++++++++++ .../instructor_dashboard.js | 3 ++ .../open_response_assessment.js | 40 ++++++++++++++++ .../sass/course/instructor/_instructor_2.scss | 8 ++++ .../instructor_dashboard_2.html | 14 ++++++ .../open_response_assessment.html | 6 +++ 7 files changed, 142 insertions(+) create mode 100644 lms/static/js/instructor_dashboard/open_response_assessment.js create mode 100644 lms/templates/instructor/instructor_dashboard_2/open_response_assessment.html diff --git a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py index 7a61c2903f..cf49ef2018 100644 --- a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py +++ b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py @@ -321,6 +321,30 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT # Max number of student per page is one. Patched setting MAX_STUDENTS_PER_PAGE_GRADE_BOOK = 1 self.assertEqual(len(response.mako_context['students']), 1) # pylint: disable=no-member + def test_open_response_assessment_page(self): + """ + Test that Open Responses is available only if course contains at least one ORA block + """ + ora_section = ( + '' + ) + + response = self.client.get(self.url) + self.assertNotIn(ora_section, response.content) + + course = ItemFactory.create( + parent_location=self.course.location, + category="course", + display_name="Test course", + ) + ItemFactory.create(parent_location=course.location, category="openassessment") + response = self.client.get(self.url) + self.assertIn(ora_section, response.content) + @ddt.ddt class TestInstructorDashboardPerformance(ModuleStoreTestCase, LoginEnrollmentTestCase, XssTestMixin): diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index fdb241f5c1..c1d70f1aef 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -38,6 +38,7 @@ from student.models import CourseEnrollment from shoppingcart.models import Coupon, PaidCourseRegistration, CourseRegCodeItem from course_modes.models import CourseMode, CourseModesArchive from student.roles import CourseFinanceAdminRole, CourseSalesAdminRole +from lms.djangoapps.courseware.module_render import get_module_by_usage_id from certificates.models import ( CertificateGenerationConfiguration, CertificateWhitelist, @@ -190,6 +191,10 @@ def instructor_dashboard_2(request, course_id): if certs_enabled and access['admin']: sections.append(_section_certificates(course)) + openassessment_blocks = modulestore().get_items(course_key, qualifiers={'category': 'openassessment'}) + if len(openassessment_blocks) > 0: + sections.append(_section_open_response_assessment(request, course, openassessment_blocks, access)) + disable_buttons = not _is_small_course(course_key) certificate_white_list = CertificateWhitelist.get_certificate_white_list(course_key) @@ -698,6 +703,48 @@ def _section_metrics(course, access): return section_data +def _section_open_response_assessment(request, course, openassessment_blocks, access): + """Provide data for the corresponding dashboard section """ + course_key = course.id + + ora_items = [] + parents = {} + + for block in openassessment_blocks: + block_parent_id = unicode(block.parent) + result_item_id = unicode(block.location) + if block_parent_id not in parents: + parents[block_parent_id] = modulestore().get_item(block.parent) + + ora_items.append({ + 'id': result_item_id, + 'name': block.display_name, + 'parent_id': block_parent_id, + 'parent_name': parents[block_parent_id].display_name, + 'staff_assessment': 'staff-assessment' in block.assessment_steps, + 'url_base': reverse('xblock_view', args=[course.id, block.location, 'student_view']), + 'url_grade_available_responses': reverse('xblock_view', args=[course.id, block.location, + 'grade_available_responses_view']), + }) + + openassessment_block = openassessment_blocks[0] + block, __ = get_module_by_usage_id( + request, unicode(course_key), unicode(openassessment_block.location), + disable_staff_debug_info=True, course=course + ) + section_data = { + 'fragment': block.render('ora_blocks_listing_view', context={ + 'ora_items': ora_items, + 'ora_item_view_enabled': settings.FEATURES.get('ENABLE_XBLOCK_VIEW_ENDPOINT', False) + }), + 'section_key': 'open_response_assessment', + 'section_display_name': _('Open Responses'), + 'access': access, + 'course_id': unicode(course_key), + } + return section_data + + def is_ecommerce_course(course_key): """ Checks if the given course is an e-commerce course or not, by checking its SKU value from diff --git a/lms/static/js/instructor_dashboard/instructor_dashboard.js b/lms/static/js/instructor_dashboard/instructor_dashboard.js index c075ee2680..272e241855 100644 --- a/lms/static/js/instructor_dashboard/instructor_dashboard.js +++ b/lms/static/js/instructor_dashboard/instructor_dashboard.js @@ -190,6 +190,9 @@ such that the value can be defined later than this assignment (file load order). }, { constructor: window.InstructorDashboard.sections.Certificates, $element: idashContent.find('.' + CSS_IDASH_SECTION + '#certificates') + }, { + constructor: window.InstructorDashboard.sections.OpenResponseAssessment, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#open_response_assessment') } ]; if (edx.instructor_dashboard.proctoring !== void 0) { diff --git a/lms/static/js/instructor_dashboard/open_response_assessment.js b/lms/static/js/instructor_dashboard/open_response_assessment.js new file mode 100644 index 0000000000..c1290f7e29 --- /dev/null +++ b/lms/static/js/instructor_dashboard/open_response_assessment.js @@ -0,0 +1,40 @@ +/* globals _ */ + +(function(_) { + 'use strict'; + + var OpenResponseAssessment = (function() { + function OpenResponseAssessmentBlock($section) { + this.$section = $section; + this.$section.data('wrapper', this); + } + + OpenResponseAssessmentBlock.prototype.onClickTitle = function() { + var block = this.$section.find('.open-response-assessment'); + XBlock.initializeBlock($(block).find('.xblock')[0]); + }; + + return OpenResponseAssessmentBlock; + }()); + + if (typeof window.setup_debug === 'undefined') { + // eslint-disable-next-line no-unused-vars, camelcase + window.setup_debug = function(element_id, edit_link, staff_context) { + // stub function. + }; + } + + _.defaults(window, { + InstructorDashboard: {} + }); + + _.defaults(window.InstructorDashboard, { + sections: {} + }); + + _.defaults(window.InstructorDashboard.sections, { + OpenResponseAssessment: OpenResponseAssessment + }); + + this.OpenResponseAssessment = OpenResponseAssessment; +}).call(this, _); diff --git a/lms/static/sass/course/instructor/_instructor_2.scss b/lms/static/sass/course/instructor/_instructor_2.scss index 32f6540973..55caeeb5f2 100644 --- a/lms/static/sass/course/instructor/_instructor_2.scss +++ b/lms/static/sass/course/instructor/_instructor_2.scss @@ -1482,6 +1482,14 @@ } } +// view - student admin +// -------------------- +.instructor-dashboard-wrapper-2 section.idash-section#open_response_assessment { + .open-response-assessment { + padding-top: 20px; + } +} + input[name="subject"] { width:600px; } diff --git a/lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html b/lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html index 9df3aa8daf..bdd6cde429 100644 --- a/lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html +++ b/lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html @@ -5,6 +5,7 @@ <%! from django.utils.translation import ugettext as _ from django.core.urlresolvers import reverse +from openedx.core.djangolib.markup import HTML %> <%block name="bodyclass">view-in-course view-instructordash @@ -63,6 +64,19 @@ from django.core.urlresolvers import reverse + % for section_data in sections: + % if 'fragment' in section_data: + ${HTML(section_data['fragment'].head_html())} + % endif + % endfor + + +<%block name="js_extra"> + % for section_data in sections: + % if 'fragment' in section_data: + ${HTML(section_data['fragment'].foot_html())} + % endif + % endfor ## Include Underscore templates diff --git a/lms/templates/instructor/instructor_dashboard_2/open_response_assessment.html b/lms/templates/instructor/instructor_dashboard_2/open_response_assessment.html new file mode 100644 index 0000000000..24b71e92f9 --- /dev/null +++ b/lms/templates/instructor/instructor_dashboard_2/open_response_assessment.html @@ -0,0 +1,6 @@ +<%page args="section_data" expression_filter="h"/> +<%! from openedx.core.djangolib.markup import HTML %> + +
+ ${HTML(section_data['fragment'].body_html())} +