diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index d186bcc39c..8e2d12d5e9 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -509,6 +509,9 @@ class CapaModule(XModule): event_info['success'] = success self.system.track_function('save_problem_check', event_info) + if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback + self.system.psychometrics_handler(self.get_instance_state()) + # render problem into HTML html = self.get_problem_html(encapsulate=False) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 88368c4a80..b033660c17 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -16,6 +16,7 @@ from capa.xqueue_interface import XQueueInterface from courseware.access import has_access from mitxmako.shortcuts import render_to_string from models import StudentModule, StudentModuleCache +from psychometrics.psychoanalyze import make_psychometrics_data_update_handler from static_replace import replace_urls from xmodule.errortracker import exc_info_to_str from xmodule.exceptions import NotFoundError @@ -230,6 +231,9 @@ def _get_module(user, request, location, student_module_cache, course_id, positi # pass position specified in URL to module through ModuleSystem system.set('position', position) system.set('DEBUG', settings.DEBUG) + if settings.MITX_FEATURES.get('ENABLE_PSYCHOMETRICS') and instance_module is not None: + system.set('psychometrics_handler', # set callback for updating PsychometricsData + make_psychometrics_data_update_handler(instance_module)) try: module = descriptor.xmodule_constructor(system)(instance_state, shared_state) diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index f4e9c27991..74f186b689 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -27,6 +27,7 @@ from django.views.decorators.cache import cache_control from courseware import grades from courseware.access import has_access, get_access_group_name from courseware.courses import (get_course_with_access, get_courses_by_university) +from psychometrics import psychoanalyze from student.models import UserProfile from student.models import UserTestGroup, CourseEnrollment @@ -51,7 +52,18 @@ def instructor_dashboard(request, course_id): instructor_access = has_access(request.user, course, 'instructor') # an instructor can manage staff lists msg = '' - # msg += ('POST=%s' % dict(request.POST)).replace('<','<') + #msg += ('POST=%s' % dict(request.POST)).replace('<','<') + + problems = [] + plots = [] + + # the instructor dashboard page is modal: grades, psychometrics, admin + # keep that state in request.session (defaults to grades mode) + idash_mode = request.POST.get('idash_mode','') + if idash_mode: + request.session['idash_mode'] = idash_mode + else: + idash_mode = request.session.get('idash_mode','Grades') def escape(s): """escape HTML special characters in string""" @@ -149,6 +161,9 @@ def instructor_dashboard(request, course_id): track.views.server_track(request, 'dump-answer-dist-csv', {}, page='idashboard') return return_csv('answer_dist_%s.csv' % course_id, get_answers_distribution(request, course_id)) + #---------------------------------------- + # Admin + elif 'List course staff' in action: group = get_staff_group(course) msg += 'Staff group = %s' % group.name @@ -187,13 +202,28 @@ def instructor_dashboard(request, course_id): user.groups.remove(group) track.views.server_track(request, 'remove-staff %s' % user, {}, page='idashboard') - # For now, mostly a static page + #---------------------------------------- + # psychometrics + + elif action == 'Generate Histogram and IRT Plot': + problem = request.POST['Problem'] + nmsg, plots = psychoanalyze.generate_plots_for_problem(problem) + msg += nmsg + + if idash_mode=='Psychometrics': + problems = psychoanalyze.problems_with_psychometric_data(course_id) + + #---------------------------------------- + # context for rendering context = {'course': course, 'staff_access': True, 'admin_access': request.user.is_staff, 'instructor_access': instructor_access, 'datatable': datatable, 'msg': msg, + 'modeflag': {idash_mode: 'selectedmode'}, + 'problems': problems, # psychometrics + 'plots': plots, # psychometrics 'course_errors': modulestore().get_item_errors(course.location), } diff --git a/lms/envs/common.py b/lms/envs/common.py index 63301d420b..3cfaae940d 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -71,6 +71,8 @@ MITX_FEATURES = { 'ENABLE_DISCUSSION' : False, 'ENABLE_DISCUSSION_SERVICE': True, + 'ENABLE_PSYCHOMETRICS': False, # real-time psychometrics (eg item response theory analysis in instructor dashboard) + 'ENABLE_SQL_TRACKING_LOGS': False, 'ENABLE_LMS_MIGRATION': False, 'ENABLE_MANUAL_GIT_RELOAD': False, @@ -619,6 +621,7 @@ INSTALLED_APPS = ( 'util', 'certificates', 'instructor', + 'psychometrics', #For the wiki 'wiki', # The new django-wiki from benjaoming diff --git a/lms/envs/dev.py b/lms/envs/dev.py index 50befeb875..5a7e019e55 100644 --- a/lms/envs/dev.py +++ b/lms/envs/dev.py @@ -20,6 +20,8 @@ MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False # Enable to test subdomains- MITX_FEATURES['SUBDOMAIN_BRANDING'] = True MITX_FEATURES['FORCE_UNIVERSITY_DOMAIN'] = None # show all university courses if in dev (ie don't use HTTP_HOST) MITX_FEATURES['ENABLE_MANUAL_GIT_RELOAD'] = True +MITX_FEATURES['ENABLE_PSYCHOMETRICS'] = True # real-time psychometrics (eg item response theory analysis in instructor dashboard) + WIKI_ENABLED = True diff --git a/lms/templates/courseware/instructor_dashboard.html b/lms/templates/courseware/instructor_dashboard.html index 8568490e5e..d73eda1ed7 100644 --- a/lms/templates/courseware/instructor_dashboard.html +++ b/lms/templates/courseware/instructor_dashboard.html @@ -4,6 +4,8 @@ <%block name="headextra"> <%static:css group='course'/> + + <%include file="/courseware/course_navigation.html" args="active_page='instructor'" /> @@ -31,37 +33,89 @@ table.stat_table td { border-color: #666666; background-color: #ffffff; } + +a.selectedmode { background-color: yellow; } + + +
+

Instructor Dashboard

-
- +

[ Grades | + %if settings.MITX_FEATURES.get('ENABLE_PSYCHOMETRICS'): + Psychometrics | + %endif + Admin ] +

+ + + + +##----------------------------------------------------------------------------- +%if modeflag.get('Grades'):

Gradebook +

Grade summary +

+

+

+

+

+ %endif -%if instructor_access: +##----------------------------------------------------------------------------- +%if modeflag.get('Psychometrics'): + +

Select a problem and an action: +

+ +

+ +

+

+ +

+ +

+ +%endif + +##----------------------------------------------------------------------------- +%if modeflag.get('Admin'): + %if instructor_access:

@@ -69,16 +123,20 @@ table.stat_table td {


- %endif + %endif -%if settings.MITX_FEATURES['ENABLE_MANUAL_GIT_RELOAD'] and admin_access: + %if settings.MITX_FEATURES['ENABLE_MANUAL_GIT_RELOAD'] and admin_access:

+ %endif %endif

+##----------------------------------------------------------------------------- +%if modeflag.get('Psychometrics') is None: +

@@ -99,14 +157,45 @@ table.stat_table td { %endfor

+%endif + +##----------------------------------------------------------------------------- +%if modeflag.get('Psychometrics'): + + %for plot in plots: +
+

${plot['title']}

+
+

${plot['info']}

+
+
+ +
+
+ %endfor + +%endif + +##----------------------------------------------------------------------------- +## always show msg %if msg:

${msg}

%endif -% if course_errors is not UNDEFINED: +##----------------------------------------------------------------------------- +%if modeflag.get('Admin'): + % if course_errors is not UNDEFINED:

Course errors

+ %if not course_errors: + None + %else:
    % for (summary, err) in course_errors:
  • ${summary | h} @@ -118,8 +207,10 @@ table.stat_table td {
  • % endfor
+ %endif
-% endif + % endif +%endif