Commit 2cfeb34f by Sarina Canelake

Merge pull request #10825 from edx/kill-psycho

Remove psychometrics app
parents 7429a32b e932632b
...@@ -1125,9 +1125,6 @@ class CapaMixin(CapaFields): ...@@ -1125,9 +1125,6 @@ class CapaMixin(CapaFields):
self.attempts, self.attempts,
) )
if hasattr(self.runtime, 'psychometrics_handler'): # update PsychometricsData using callback
self.runtime.psychometrics_handler(self.get_state_for_lcp())
# render problem into HTML # render problem into HTML
html = self.get_problem_html(encapsulate=False) html = self.get_problem_html(encapsulate=False)
...@@ -1375,10 +1372,6 @@ class CapaMixin(CapaFields): ...@@ -1375,10 +1372,6 @@ class CapaMixin(CapaFields):
event_info['attempts'] = self.attempts event_info['attempts'] = self.attempts
self.track_function_unmask('problem_rescore', event_info) self.track_function_unmask('problem_rescore', event_info)
# psychometrics should be called on rescoring requests in the same way as check-problem
if hasattr(self.runtime, 'psychometrics_handler'): # update PsychometricsData using callback
self.runtime.psychometrics_handler(self.get_state_for_lcp())
return {'success': success} return {'success': success}
def save_problem(self, data): def save_problem(self, data):
......
...@@ -2528,20 +2528,6 @@ CREATE TABLE `programs_programsapiconfig` ( ...@@ -2528,20 +2528,6 @@ CREATE TABLE `programs_programsapiconfig` (
CONSTRAINT `programs_programsa_changed_by_id_b7c3b49d5c0dcd3_fk_auth_user_id` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`) CONSTRAINT `programs_programsa_changed_by_id_b7c3b49d5c0dcd3_fk_auth_user_id` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `psychometrics_psychometricdata`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `psychometrics_psychometricdata` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`done` tinyint(1) NOT NULL,
`attempts` int(11) NOT NULL,
`checktimes` longtext,
`studentmodule_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `studentmodule_id` (`studentmodule_id`),
CONSTRAINT `D758b867e6fa9161734bd9cb58b9a485` FOREIGN KEY (`studentmodule_id`) REFERENCES `courseware_studentmodule` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `self_paced_selfpacedconfiguration`; DROP TABLE IF EXISTS `self_paced_selfpacedconfiguration`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */; /*!40101 SET character_set_client = utf8 */;
......
...@@ -56,7 +56,6 @@ from openedx.core.lib.xblock_utils import ( ...@@ -56,7 +56,6 @@ from openedx.core.lib.xblock_utils import (
wrap_xblock, wrap_xblock,
request_token as xblock_request_token, request_token as xblock_request_token,
) )
from psychometrics.psychoanalyze import make_psychometrics_data_update_handler
from student.models import anonymous_id_for_user, user_by_anonymous_id from student.models import anonymous_id_for_user, user_by_anonymous_id
from student.roles import CourseBetaTesterRole from student.roles import CourseBetaTesterRole
from xblock.core import XBlock from xblock.core import XBlock
...@@ -760,11 +759,6 @@ def get_module_system_for_user(user, student_data, # TODO # pylint: disable=to ...@@ -760,11 +759,6 @@ def get_module_system_for_user(user, student_data, # TODO # pylint: disable=to
position = None position = None
system.set('position', position) system.set('position', position)
if settings.FEATURES.get('ENABLE_PSYCHOMETRICS') and user.is_authenticated():
system.set(
'psychometrics_handler', # set callback for updating PsychometricsData
make_psychometrics_data_update_handler(course_id, user, descriptor.location)
)
system.set(u'user_is_staff', user_is_staff) system.set(u'user_is_staff', user_is_staff)
system.set(u'user_is_admin', bool(has_access(user, u'staff', 'global'))) system.set(u'user_is_admin', bool(has_access(user, u'staff', 'global')))
......
...@@ -1767,17 +1767,6 @@ class TestRebindModule(TestSubmittingProblems): ...@@ -1767,17 +1767,6 @@ class TestRebindModule(TestSubmittingProblems):
self.assertEqual(module.scope_ids.user_id, user2.id) self.assertEqual(module.scope_ids.user_id, user2.id)
self.assertEqual(module.descriptor.scope_ids.user_id, user2.id) self.assertEqual(module.descriptor.scope_ids.user_id, user2.id)
@patch('courseware.module_render.make_psychometrics_data_update_handler')
@patch.dict(settings.FEATURES, {'ENABLE_PSYCHOMETRICS': True})
def test_psychometrics_anonymous(self, psycho_handler):
"""
Make sure that noauth modules with anonymous users don't have
the psychometrics callback bound.
"""
module = self.get_module_for_user(self.anon_user)
module.system.rebind_noauth_module_to_user(module, self.anon_user)
self.assertFalse(psycho_handler.called)
@attr('shard_1') @attr('shard_1')
@ddt.ddt @ddt.ddt
......
...@@ -48,7 +48,6 @@ from instructor_task.api import ( ...@@ -48,7 +48,6 @@ from instructor_task.api import (
from instructor_task.views import get_task_completion_info from instructor_task.views import get_task_completion_info
from edxmako.shortcuts import render_to_response, render_to_string from edxmako.shortcuts import render_to_response, render_to_string
from class_dashboard import dashboard_data from class_dashboard import dashboard_data
from psychometrics import psychoanalyze
from student.models import ( from student.models import (
CourseEnrollment, CourseEnrollment,
CourseEnrollmentAllowed, CourseEnrollmentAllowed,
...@@ -97,7 +96,7 @@ def instructor_dashboard(request, course_id): ...@@ -97,7 +96,7 @@ def instructor_dashboard(request, course_id):
plots = [] plots = []
datatable = {} datatable = {}
# the instructor dashboard page is modal: grades, psychometrics, admin # the instructor dashboard page is modal: grades, admin
# keep that state in request.session (defaults to grades mode) # keep that state in request.session (defaults to grades mode)
idash_mode = request.POST.get('idash_mode', '') idash_mode = request.POST.get('idash_mode', '')
idash_mode_key = u'idash_mode:{0}'.format(course_id) idash_mode_key = u'idash_mode:{0}'.format(course_id)
...@@ -320,18 +319,6 @@ def instructor_dashboard(request, course_id): ...@@ -320,18 +319,6 @@ def instructor_dashboard(request, course_id):
datatable = ret['datatable'] datatable = ret['datatable']
#---------------------------------------- #----------------------------------------
# psychometrics
elif action == 'Generate Histogram and IRT Plot':
problem = request.POST['Problem']
nmsg, plots = psychoanalyze.generate_plots_for_problem(problem)
msg += nmsg
track.views.server_track(request, "psychometrics-histogram-generation", {"problem": unicode(problem)}, page="idashboard")
if idash_mode == 'Psychometrics':
problems = psychoanalyze.problems_with_psychometric_data(course_key)
#----------------------------------------
# analytics # analytics
def get_analytics_result(analytics_name): def get_analytics_result(analytics_name):
"""Return data for an Analytic piece, or None if it doesn't exist. It """Return data for an Analytic piece, or None if it doesn't exist. It
...@@ -435,8 +422,6 @@ def instructor_dashboard(request, course_id): ...@@ -435,8 +422,6 @@ def instructor_dashboard(request, course_id):
'show_email_tab': show_email_tab, # email 'show_email_tab': show_email_tab, # email
'problems': problems, # psychometrics
'plots': plots, # psychometrics
'course_errors': modulestore().get_course_errors(course.id), 'course_errors': modulestore().get_course_errors(course.id),
'instructor_tasks': instructor_tasks, 'instructor_tasks': instructor_tasks,
'offline_grade_log': offline_grades_available(course_key), 'offline_grade_log': offline_grades_available(course_key),
......
'''
django admin pages for courseware model
'''
from psychometrics.models import PsychometricData
from django.contrib import admin
admin.site.register(PsychometricData)
#!/usr/bin/python
#
# generate pyschometrics data from tracking logs and student module data
import json
from courseware.models import StudentModule
from track.models import TrackingLog
from psychometrics.models import PsychometricData
from django.conf import settings
from django.core.management.base import BaseCommand
#db = "ocwtutor" # for debugging
#db = "default"
db = getattr(settings, 'DATABASE_FOR_PSYCHOMETRICS', 'default')
class Command(BaseCommand):
help = "initialize PsychometricData tables from StudentModule instances (and tracking data, if in SQL)."
help += "Note this is done for all courses for which StudentModule instances exist."
def handle(self, *args, **options):
# delete all pmd
#PsychometricData.objects.all().delete()
#PsychometricData.objects.using(db).all().delete()
smset = StudentModule.objects.using(db).exclude(max_grade=None)
for sm in smset:
usage_key = sm.module_state_key
if not usage_key.block_type == "problem":
continue
try:
state = json.loads(sm.state)
done = state['done']
except:
print "Oops, failed to eval state for %s (state=%s)" % (sm, sm.state)
continue
if done: # only keep if problem completed
try:
pmd = PsychometricData.objects.using(db).get(studentmodule=sm)
except PsychometricData.DoesNotExist:
pmd = PsychometricData(studentmodule=sm)
pmd.done = done
pmd.attempts = state['attempts']
# get attempt times from tracking log
uname = sm.student.username
tset = TrackingLog.objects.using(db).filter(username=uname, event_type__contains='problem_check')
tset = tset.filter(event_source='server')
tset = tset.filter(event__contains="'%s'" % usage_key)
checktimes = [x.dtcreated for x in tset]
pmd.checktimes = checktimes
if not len(checktimes) == pmd.attempts:
print "Oops, mismatch in number of attempts and check times for %s" % pmd
#print pmd
pmd.save(using=db)
print "%d PMD entries" % PsychometricData.objects.using(db).all().count()
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('courseware', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='PsychometricData',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('done', models.BooleanField(default=False)),
('attempts', models.IntegerField(default=0)),
('checktimes', models.TextField(null=True, blank=True)),
('studentmodule', models.OneToOneField(to='courseware.StudentModule')),
],
),
]
#
# db model for psychometrics data
#
# this data is collected in real time
#
from django.db import models
from courseware.models import StudentModule
class PsychometricData(models.Model):
"""
This data is a table linking student, module, and module performance,
including number of attempts, grade, max grade, and time of checks.
Links to instances of StudentModule, but only those for capa problems.
Note that StudentModule.module_state_key is a :class:`Location` instance.
checktimes is extracted from tracking logs, or added by capa module via psychometrics callback.
"""
class Meta(object):
app_label = "psychometrics"
studentmodule = models.OneToOneField(StudentModule, db_index=True) # contains student, module_state_key, course_id
done = models.BooleanField(default=False)
attempts = models.IntegerField(default=0) # extracted from studentmodule.state
checktimes = models.TextField(null=True, blank=True) # internally stored as list of datetime objects
# keep in mind
# grade = studentmodule.grade
# max_grade = studentmodule.max_grade
# student = studentmodule.student
# course_id = studentmodule.course_id
# location = studentmodule.module_state_key
def __unicode__(self):
sm = self.studentmodule
return "[PsychometricData] %s url=%s, grade=%s, max=%s, attempts=%s, ct=%s" % (sm.student,
sm.module_state_key,
sm.grade,
sm.max_grade,
self.attempts,
self.checktimes)
#
# File: psychometrics/psychoanalyze.py
#
# generate pyschometrics plots from PsychometricData
from __future__ import division
import datetime
import logging
import json
import math
import numpy as np
from opaque_keys.edx.locator import BlockUsageLocator
from scipy.optimize import curve_fit
from django.conf import settings
from django.db.models import Sum, Max
from psychometrics.models import PsychometricData
from courseware.models import StudentModule
from pytz import UTC
log = logging.getLogger("edx.psychometrics")
#db = "ocwtutor" # for debugging
#db = "default"
db = getattr(settings, 'DATABASE_FOR_PSYCHOMETRICS', 'default')
#-----------------------------------------------------------------------------
# fit functions
def func_2pl(x, a, b):
"""
2-parameter logistic function
"""
D = 1.7
edax = np.exp(D * a * (x - b))
return edax / (1 + edax)
#-----------------------------------------------------------------------------
# statistics class
class StatVar(object):
"""
Simple statistics on floating point numbers: avg, sdv, var, min, max
"""
def __init__(self, unit=1):
self.sum = 0
self.sum2 = 0
self.cnt = 0
self.unit = unit
self.min = None
self.max = None
def add(self, x):
if x is None:
return
if self.min is None:
self.min = x
else:
if x < self.min:
self.min = x
if self.max is None:
self.max = x
else:
if x > self.max:
self.max = x
self.sum += x
self.sum2 += x ** 2
self.cnt += 1
def avg(self):
if self.cnt is None:
return 0
return self.sum / 1.0 / self.cnt / self.unit
def var(self):
if self.cnt is None:
return 0
return (self.sum2 / 1.0 / self.cnt / (self.unit ** 2)) - (self.avg() ** 2)
def sdv(self):
v = self.var()
if v > 0:
return math.sqrt(v)
else:
return 0
def __str__(self):
return 'cnt=%d, avg=%f, sdv=%f' % (self.cnt, self.avg(), self.sdv())
def __add__(self, x):
self.add(x)
return self
#-----------------------------------------------------------------------------
# histogram generator
def make_histogram(ydata, bins=None):
'''
Generate histogram of ydata using bins provided, or by default bins
from 0 to 100 by 10. bins should be ordered in increasing order.
returns dict with keys being bins, and values being counts.
special: hist['bins'] = bins
'''
if bins is None:
bins = range(0, 100, 10)
nbins = len(bins)
hist = dict(zip(bins, [0] * nbins))
for y in ydata:
for b in bins[::-1]: # in reverse order
if y > b:
hist[b] += 1
break
# hist['bins'] = bins
return hist
#-----------------------------------------------------------------------------
def problems_with_psychometric_data(course_id):
'''
Return dict of {problems (location urls): count} for which psychometric data is available.
Does this for a given course_id.
'''
pmdset = PsychometricData.objects.using(db).filter(studentmodule__course_id=course_id)
plist = [p['studentmodule__module_state_key'] for p in pmdset.values('studentmodule__module_state_key').distinct()]
problems = dict(
(
p,
pmdset.filter(
studentmodule__module_state_key=BlockUsageLocator.from_string(p)
).count()
) for p in plist
)
return problems
#-----------------------------------------------------------------------------
def generate_plots_for_problem(problem):
pmdset = PsychometricData.objects.using(db).filter(
studentmodule__module_state_key=BlockUsageLocator.from_string(problem)
)
nstudents = pmdset.count()
msg = ""
plots = []
if nstudents < 2:
msg += "%s nstudents=%d --> skipping, too few" % (problem, nstudents)
return msg, plots
max_grade = pmdset[0].studentmodule.max_grade
agdat = pmdset.aggregate(Sum('attempts'), Max('attempts'))
max_attempts = agdat['attempts__max']
total_attempts = agdat['attempts__sum'] # not used yet
msg += "max attempts = %d" % max_attempts
xdat = range(1, max_attempts + 1)
dataset = {'xdat': xdat}
# compute grade statistics
grades = [pmd.studentmodule.grade for pmd in pmdset]
gsv = StatVar()
for g in grades:
gsv += g
msg += "<br><p><font color='blue'>Grade distribution: %s</font></p>" % gsv
# generate grade histogram
ghist = []
axisopts = """{
xaxes: [{
axisLabel: 'Grade'
}],
yaxes: [{
position: 'left',
axisLabel: 'Count'
}]
}"""
if gsv.max > max_grade:
msg += "<br/><p><font color='red'>Something is wrong: max_grade=%s, but max(grades)=%s</font></p>" % (max_grade, gsv.max)
max_grade = gsv.max
if max_grade > 1:
ghist = make_histogram(grades, np.linspace(0, max_grade, max_grade + 1))
ghist_json = json.dumps(ghist.items())
plot = {'title': "Grade histogram for %s" % problem,
'id': 'histogram',
'info': '',
'data': "var dhist = %s;\n" % ghist_json,
'cmd': '[ {data: dhist, bars: { show: true, align: "center" }} ], %s' % axisopts,
}
plots.append(plot)
else:
msg += "<br/>Not generating histogram: max_grade=%s" % max_grade
# histogram of time differences between checks
# Warning: this is inefficient - doesn't scale to large numbers of students
dtset = [] # time differences in minutes
dtsv = StatVar()
for pmd in pmdset:
try:
checktimes = eval(pmd.checktimes) # update log of attempt timestamps
except:
continue
if len(checktimes) < 2:
continue
ct0 = checktimes[0]
for ct in checktimes[1:]:
dt = (ct - ct0).total_seconds() / 60.0
if dt < 20: # ignore if dt too long
dtset.append(dt)
dtsv += dt
ct0 = ct
if dtsv.cnt > 2:
msg += "<br/><p><font color='brown'>Time differences between checks: %s</font></p>" % dtsv
bins = np.linspace(0, 1.5 * dtsv.sdv(), 30)
dbar = bins[1] - bins[0]
thist = make_histogram(dtset, bins)
thist_json = json.dumps(sorted(thist.items(), key=lambda(x): x[0]))
axisopts = """{ xaxes: [{ axisLabel: 'Time (min)'}], yaxes: [{position: 'left',axisLabel: 'Count'}]}"""
plot = {'title': "Histogram of time differences between checks",
'id': 'thistogram',
'info': '',
'data': "var thist = %s;\n" % thist_json,
'cmd': '[ {data: thist, bars: { show: true, align: "center", barWidth:%f }} ], %s' % (dbar, axisopts),
}
plots.append(plot)
# one IRT plot curve for each grade received (TODO: this assumes integer grades)
for grade in range(1, int(max_grade) + 1):
yset = {}
gset = pmdset.filter(studentmodule__grade=grade)
ngset = gset.count()
if ngset == 0:
continue
ydat = []
ylast = 0
for x in xdat:
y = gset.filter(attempts=x).count() / ngset
ydat.append(y + ylast)
ylast = y + ylast
yset['ydat'] = ydat
if len(ydat) > 3: # try to fit to logistic function if enough data points
try:
cfp = curve_fit(func_2pl, xdat, ydat, [1.0, max_attempts / 2.0])
yset['fitparam'] = cfp
yset['fitpts'] = func_2pl(np.array(xdat), *cfp[0])
yset['fiterr'] = [yd - yf for (yd, yf) in zip(ydat, yset['fitpts'])]
fitx = np.linspace(xdat[0], xdat[-1], 100)
yset['fitx'] = fitx
yset['fity'] = func_2pl(np.array(fitx), *cfp[0])
except Exception as err:
log.debug('Error in psychoanalyze curve fitting: %s', err)
dataset['grade_%d' % grade] = yset
axisopts = """{
xaxes: [{
axisLabel: 'Number of Attempts'
}],
yaxes: [{
max:1.0,
position: 'left',
axisLabel: 'Probability of correctness'
}]
}"""
# generate points for flot plot
for grade in range(1, int(max_grade) + 1):
jsdata = ""
jsplots = []
gkey = 'grade_%d' % grade
if gkey in dataset:
yset = dataset[gkey]
jsdata += "var d%d = %s;\n" % (grade, json.dumps(zip(xdat, yset['ydat'])))
jsplots.append('{ data: d%d, lines: { show: false }, points: { show: true}, color: "red" }' % grade)
if 'fitpts' in yset:
jsdata += 'var fit = %s;\n' % (json.dumps(zip(yset['fitx'], yset['fity'])))
jsplots.append('{ data: fit, lines: { show: true }, color: "blue" }')
(a, b) = yset['fitparam'][0]
irtinfo = "(2PL: D=1.7, a=%6.3f, b=%6.3f)" % (a, b)
else:
irtinfo = ""
plots.append({'title': 'IRT Plot for grade=%s %s' % (grade, irtinfo),
'id': "irt%s" % grade,
'info': '',
'data': jsdata,
'cmd': '[%s], %s' % (','.join(jsplots), axisopts),
})
#log.debug('plots = %s' % plots)
return msg, plots
#-----------------------------------------------------------------------------
def make_psychometrics_data_update_handler(course_id, user, module_state_key):
"""
Construct and return a procedure which may be called to update
the PsychometricData instance for the given StudentModule instance.
"""
sm, status = StudentModule.objects.get_or_create(
course_id=course_id,
student=user,
module_state_key=module_state_key,
defaults={'state': '{}', 'module_type': 'problem'},
)
try:
pmd = PsychometricData.objects.using(db).get(studentmodule=sm)
except PsychometricData.DoesNotExist:
pmd = PsychometricData(studentmodule=sm)
def psychometrics_data_update_handler(state):
"""
This function may be called each time a problem is successfully checked
(eg on save_problem_check events in capa_module).
state = instance state (a nice, uniform way to interface - for more future psychometric feature extraction)
"""
try:
state = json.loads(sm.state)
done = state['done']
except:
log.exception("Oops, failed to eval state for %s (state=%s)", sm, sm.state)
return
pmd.done = done
try:
pmd.attempts = state.get('attempts', 0)
except:
log.exception("no attempts for %s (state=%s)", sm, sm.state)
try:
checktimes = eval(pmd.checktimes) # update log of attempt timestamps
except:
checktimes = []
checktimes.append(datetime.datetime.now(UTC))
pmd.checktimes = checktimes
try:
pmd.save()
except:
log.exception("Error in updating psychometrics data for %s", sm)
return psychometrics_data_update_handler
...@@ -116,8 +116,6 @@ FEATURES = { ...@@ -116,8 +116,6 @@ FEATURES = {
# in their emails, and they will have no way to resubscribe. # in their emails, and they will have no way to resubscribe.
'ENABLE_DISCUSSION_EMAIL_DIGEST': False, 'ENABLE_DISCUSSION_EMAIL_DIGEST': False,
'ENABLE_PSYCHOMETRICS': False, # real-time psychometrics (eg item response theory analysis in instructor dashboard)
'ENABLE_DJANGO_ADMIN_SITE': True, # set true to enable django's admin site, even on prod (e.g. for course ops) 'ENABLE_DJANGO_ADMIN_SITE': True, # set true to enable django's admin site, even on prod (e.g. for course ops)
'ENABLE_SQL_TRACKING_LOGS': False, 'ENABLE_SQL_TRACKING_LOGS': False,
'ENABLE_LMS_MIGRATION': False, 'ENABLE_LMS_MIGRATION': False,
...@@ -1833,7 +1831,6 @@ INSTALLED_APPS = ( ...@@ -1833,7 +1831,6 @@ INSTALLED_APPS = (
'instructor', 'instructor',
'instructor_task', 'instructor_task',
'open_ended_grading', 'open_ended_grading',
'psychometrics',
'licenses', 'licenses',
'openedx.core.djangoapps.course_groups', 'openedx.core.djangoapps.course_groups',
'bulk_email', 'bulk_email',
......
...@@ -24,7 +24,6 @@ FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False # Enable to test subdomains--othe ...@@ -24,7 +24,6 @@ FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False # Enable to test subdomains--othe
FEATURES['SUBDOMAIN_BRANDING'] = True FEATURES['SUBDOMAIN_BRANDING'] = True
FEATURES['FORCE_UNIVERSITY_DOMAIN'] = None # show all university courses if in dev (ie don't use HTTP_HOST) FEATURES['FORCE_UNIVERSITY_DOMAIN'] = None # show all university courses if in dev (ie don't use HTTP_HOST)
FEATURES['ENABLE_MANUAL_GIT_RELOAD'] = True FEATURES['ENABLE_MANUAL_GIT_RELOAD'] = True
FEATURES['ENABLE_PSYCHOMETRICS'] = False # real-time psychometrics (eg item response theory analysis in instructor dashboard)
FEATURES['ENABLE_SERVICE_STATUS'] = True 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)
......
...@@ -148,9 +148,6 @@ function goto( mode) ...@@ -148,9 +148,6 @@ function goto( mode)
%endif %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'):
<a href="#" onclick="goto('Psychometrics');" class="${modeflag.get('Psychometrics')}">${_("Psychometrics")}</a> |
%endif
<a href="#" onclick="goto('Admin');" class="${modeflag.get('Admin')}">${_("Admin")}</a> | <a href="#" onclick="goto('Admin');" class="${modeflag.get('Admin')}">${_("Admin")}</a> |
<a href="#" onclick="goto('Forum Admin');" class="${modeflag.get('Forum Admin')}">${_("Forum Admin")}</a> | <a href="#" onclick="goto('Forum Admin');" class="${modeflag.get('Forum Admin')}">${_("Forum Admin")}</a> |
<a href="#" onclick="goto('Enrollment');" class="${modeflag.get('Enrollment')}">${_("Enrollment")}</a> | <a href="#" onclick="goto('Enrollment');" class="${modeflag.get('Enrollment')}">${_("Enrollment")}</a> |
...@@ -268,27 +265,6 @@ function goto( mode) ...@@ -268,27 +265,6 @@ function goto( mode)
%endif %endif
##----------------------------------------------------------------------------- ##-----------------------------------------------------------------------------
%if modeflag.get('Psychometrics'):
<p>${_("Select a problem and an action:")}
</p>
<p>
<select name="Problem">
%for problem, count in sorted(problems.items(), key=lambda x: x[0]):
<option value="${problem}">${problem} [${count}]</option>
%endfor
</select>
</p>
<p>
<input type="submit" name="action" value="Generate Histogram and IRT Plot">
</p>
<p></p>
%endif
##-----------------------------------------------------------------------------
%if modeflag.get('Admin'): %if modeflag.get('Admin'):
%if instructor_access or admin_access: %if instructor_access or admin_access:
...@@ -398,7 +374,7 @@ function goto( mode) ...@@ -398,7 +374,7 @@ function goto( mode)
##----------------------------------------------------------------------------- ##-----------------------------------------------------------------------------
%if datatable and modeflag.get('Psychometrics') is None: %if datatable:
<br/> <br/>
<br/> <br/>
...@@ -492,32 +468,6 @@ function goto( mode) ...@@ -492,32 +468,6 @@ function goto( mode)
%endif %endif
##----------------------------------------------------------------------------- ##-----------------------------------------------------------------------------
%if modeflag.get('Psychometrics'):
%for plot in plots:
<br/>
<h3>${plot['title']}</h3>
<br/>
<p>${plot['info']}</p>
<br/>
<div id="plot_${plot['id']}" style="width:600px;height:300px;"></div>
<script type="text/javascript">
$(function () {
${plot['data']}
$.plot($("#plot_${plot['id']}"), ${plot['cmd']} );
});
</script>
<br/>
<br/>
%endfor
%endif
##-----------------------------------------------------------------------------
## always show msg
##-----------------------------------------------------------------------------
%if modeflag.get('Admin'): %if modeflag.get('Admin'):
% if course_errors is not UNDEFINED: % if course_errors is not UNDEFINED:
<h2>${_("Course errors")}</h2> <h2>${_("Course errors")}</h2>
......
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