Commit e3c64778 by kimth

Merge branch 'master' into kimth/fix-dynamath-6002x

parents 474b5377 87750f31
...@@ -53,7 +53,7 @@ def index(request): ...@@ -53,7 +53,7 @@ def index(request):
""" """
courses = modulestore().get_items(['i4x', None, None, 'course', None]) courses = modulestore().get_items(['i4x', None, None, 'course', None])
return render_to_response('index.html', { return render_to_response('index.html', {
'courses': [(course.metadata['display_name'], 'courses': [(course.metadata.get('display_name'),
reverse('course_index', args=[ reverse('course_index', args=[
course.location.org, course.location.org,
course.location.course, course.location.course,
......
...@@ -14,9 +14,11 @@ $yellow: #fff8af; ...@@ -14,9 +14,11 @@ $yellow: #fff8af;
$cream: #F6EFD4; $cream: #F6EFD4;
$border-color: #ddd; $border-color: #ddd;
// edX colors // edX colors
$blue: rgb(29,157,217); $blue: rgb(29,157,217);
$pink: rgb(182,37,104); $pink: rgb(182,37,104);
$error-red: rgb(253, 87, 87);
@mixin hide-text { @mixin hide-text {
background-color: transparent; background-color: transparent;
......
...@@ -330,11 +330,6 @@ section.cal { ...@@ -330,11 +330,6 @@ section.cal {
&:hover { &:hover {
opacity: 1; opacity: 1;
width: flex-grid(5) + flex-gutter();
+ section.main-content {
width: flex-grid(7);
}
} }
> header { > header {
......
...@@ -5,4 +5,8 @@ django admin pages for courseware model ...@@ -5,4 +5,8 @@ django admin pages for courseware model
from external_auth.models import * from external_auth.models import *
from django.contrib import admin from django.contrib import admin
admin.site.register(ExternalAuthMap) class ExternalAuthMapAdmin(admin.ModelAdmin):
search_fields = ['external_id','user__username']
date_hierarchy = 'dtcreated'
admin.site.register(ExternalAuthMap, ExternalAuthMapAdmin)
'''
django admin pages for courseware model
'''
from track.models import *
from django.contrib import admin
admin.site.register(TrackingLog)
...@@ -11,13 +11,11 @@ importAll("xproblem"); ...@@ -11,13 +11,11 @@ importAll("xproblem");
generatorModulePath = process.argv[2]; generatorModulePath = process.argv[2];
dependencies = JSON.parse(process.argv[3]); dependencies = JSON.parse(process.argv[3]);
seed = process.argv[4]; seed = JSON.parse(process.argv[4]);
params = JSON.parse(process.argv[5]); params = JSON.parse(process.argv[5]);
if(seed==null){ if(seed==null){
seed = 4; seed = 4;
}else{
seed = parseInt(seed);
} }
for(var i = 0; i < dependencies.length; i++){ for(var i = 0; i < dependencies.length; i++){
......
...@@ -408,7 +408,7 @@ class JavascriptResponse(LoncapaResponse): ...@@ -408,7 +408,7 @@ class JavascriptResponse(LoncapaResponse):
output = self.call_node([generator_file, output = self.call_node([generator_file,
self.generator, self.generator,
json.dumps(self.generator_dependencies), json.dumps(self.generator_dependencies),
json.dumps(str(self.system.seed)), json.dumps(str(self.context['the_lcp'].seed)),
json.dumps(self.params)]).strip() json.dumps(self.params)]).strip()
return json.loads(output) return json.loads(output)
......
...@@ -507,8 +507,12 @@ class CapaModule(XModule): ...@@ -507,8 +507,12 @@ class CapaModule(XModule):
# 'success' will always be incorrect # 'success' will always be incorrect
event_info['correct_map'] = correct_map.get_dict() event_info['correct_map'] = correct_map.get_dict()
event_info['success'] = success event_info['success'] = success
event_info['attempts'] = self.attempts
self.system.track_function('save_problem_check', event_info) 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 # render problem into HTML
html = self.get_problem_html(encapsulate=False) html = self.get_problem_html(encapsulate=False)
......
...@@ -32,7 +32,7 @@ nav.sequence-nav { ...@@ -32,7 +32,7 @@ nav.sequence-nav {
.sequence-list-wrapper { .sequence-list-wrapper {
position: relative; position: relative;
z-index: 9999; z-index: 99;
border: 1px solid #ccc; border: 1px solid #ccc;
height: 44px; height: 44px;
margin: 0 30px; margin: 0 30px;
......
...@@ -2023,7 +2023,16 @@ function add_schematic_handler(other_onload) { ...@@ -2023,7 +2023,16 @@ function add_schematic_handler(other_onload) {
update_schematics(); update_schematics();
} }
} }
window.onload = add_schematic_handler(window.onload); /*
* THK: Attaching update_schematic to window.onload is rather presumptuous...
* The function is called for EVERY page load, whether in courseware or in
* course info, in 6.002x or the public health course. It is also redundant
* because courseware includes an explicit call to update_schematic after
* each ajax exchange. In this case, calling update_schematic twice appears
* to contribute to a bug in Firefox that does not render the schematic
* properly depending on timing.
*/
//window.onload = add_schematic_handler(window.onload);
// ask each schematic input widget to update its value field for submission // ask each schematic input widget to update its value field for submission
function prepare_schematics() { function prepare_schematics() {
......
...@@ -75,7 +75,7 @@ class SequenceModule(XModule): ...@@ -75,7 +75,7 @@ class SequenceModule(XModule):
contents = [] contents = []
for child in self.get_display_items(): for child in self.get_display_items():
progress = child.get_progress() progress = child.get_progress()
contents.append({ childinfo = {
'content': child.get_html(), 'content': child.get_html(),
'title': "\n".join( 'title': "\n".join(
grand_child.display_name.strip() grand_child.display_name.strip()
...@@ -85,7 +85,10 @@ class SequenceModule(XModule): ...@@ -85,7 +85,10 @@ class SequenceModule(XModule):
'progress_status': Progress.to_js_status_str(progress), 'progress_status': Progress.to_js_status_str(progress),
'progress_detail': Progress.to_js_detail_str(progress), 'progress_detail': Progress.to_js_detail_str(progress),
'type': child.get_icon_class(), 'type': child.get_icon_class(),
}) }
if childinfo['title']=='':
childinfo['title'] = child.metadata.get('display_name','')
contents.append(childinfo)
params = {'items': contents, params = {'items': contents,
'element_id': self.location.html_id(), 'element_id': self.location.html_id(),
......
...@@ -16,6 +16,7 @@ from capa.xqueue_interface import XQueueInterface ...@@ -16,6 +16,7 @@ from capa.xqueue_interface import XQueueInterface
from courseware.access import has_access from courseware.access import has_access
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
from models import StudentModule, StudentModuleCache from models import StudentModule, StudentModuleCache
from psychometrics.psychoanalyze import make_psychometrics_data_update_handler
from static_replace import replace_urls from static_replace import replace_urls
from xmodule.errortracker import exc_info_to_str from xmodule.errortracker import exc_info_to_str
from xmodule.exceptions import NotFoundError from xmodule.exceptions import NotFoundError
...@@ -230,6 +231,9 @@ def _get_module(user, request, location, student_module_cache, course_id, positi ...@@ -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 # pass position specified in URL to module through ModuleSystem
system.set('position', position) system.set('position', position)
system.set('DEBUG', settings.DEBUG) 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: try:
module = descriptor.xmodule_constructor(system)(instance_state, shared_state) module = descriptor.xmodule_constructor(system)(instance_state, shared_state)
......
...@@ -27,6 +27,7 @@ from django.views.decorators.cache import cache_control ...@@ -27,6 +27,7 @@ from django.views.decorators.cache import cache_control
from courseware import grades from courseware import grades
from courseware.access import has_access, get_access_group_name from courseware.access import has_access, get_access_group_name
from courseware.courses import (get_course_with_access, get_courses_by_university) 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 UserProfile
from student.models import UserTestGroup, CourseEnrollment from student.models import UserTestGroup, CourseEnrollment
...@@ -51,7 +52,18 @@ def instructor_dashboard(request, course_id): ...@@ -51,7 +52,18 @@ def instructor_dashboard(request, course_id):
instructor_access = has_access(request.user, course, 'instructor') # an instructor can manage staff lists instructor_access = has_access(request.user, course, 'instructor') # an instructor can manage staff lists
msg = '' msg = ''
# msg += ('POST=%s' % dict(request.POST)).replace('<','&lt;') #msg += ('POST=%s' % dict(request.POST)).replace('<','&lt;')
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): def escape(s):
"""escape HTML special characters in string""" """escape HTML special characters in string"""
...@@ -149,6 +161,9 @@ def instructor_dashboard(request, course_id): ...@@ -149,6 +161,9 @@ def instructor_dashboard(request, course_id):
track.views.server_track(request, 'dump-answer-dist-csv', {}, page='idashboard') 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)) return return_csv('answer_dist_%s.csv' % course_id, get_answers_distribution(request, course_id))
#----------------------------------------
# Admin
elif 'List course staff' in action: elif 'List course staff' in action:
group = get_staff_group(course) group = get_staff_group(course)
msg += 'Staff group = %s' % group.name msg += 'Staff group = %s' % group.name
...@@ -187,14 +202,31 @@ def instructor_dashboard(request, course_id): ...@@ -187,14 +202,31 @@ def instructor_dashboard(request, course_id):
user.groups.remove(group) user.groups.remove(group)
track.views.server_track(request, 'remove-staff %s' % user, {}, page='idashboard') 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
track.views.server_track(request, 'psychometrics %s' % problem, {}, page='idashboard')
if idash_mode=='Psychometrics':
problems = psychoanalyze.problems_with_psychometric_data(course_id)
#----------------------------------------
# context for rendering
context = {'course': course, context = {'course': course,
'staff_access': True, 'staff_access': True,
'admin_access': request.user.is_staff, 'admin_access': request.user.is_staff,
'instructor_access': instructor_access, 'instructor_access': instructor_access,
'datatable': datatable, 'datatable': datatable,
'msg': msg, 'msg': msg,
'modeflag': {idash_mode: 'selectedmode'},
'problems': problems, # psychometrics
'plots': plots, # psychometrics
'course_errors': modulestore().get_item_errors(course.location), 'course_errors': modulestore().get_item_errors(course.location),
'djangopid' : os.getpid(),
} }
return render_to_response('courseware/instructor_dashboard.html', context) return render_to_response('courseware/instructor_dashboard.html', context)
......
...@@ -35,7 +35,17 @@ def getip(request): ...@@ -35,7 +35,17 @@ def getip(request):
ip = request.META.get('REMOTE_ADDR','None') ip = request.META.get('REMOTE_ADDR','None')
return ip return ip
def manage_modulestores(request,reload_dir=None):
def get_commit_id(course):
return course.metadata.get('GIT_COMMIT_ID','No commit id')
# getattr(def_ms.courses[reload_dir], 'GIT_COMMIT_ID','No commit id')
def set_commit_id(course,commit_id):
course.metadata['GIT_COMMIT_ID'] = commit_id
# setattr(def_ms.courses[reload_dir], 'GIT_COMMIT_ID', new_commit_id)
def manage_modulestores(request, reload_dir=None, commit_id=None):
''' '''
Manage the static in-memory modulestores. Manage the static in-memory modulestores.
...@@ -52,8 +62,9 @@ def manage_modulestores(request,reload_dir=None): ...@@ -52,8 +62,9 @@ def manage_modulestores(request,reload_dir=None):
ip = getip(request) ip = getip(request)
if LOCAL_DEBUG: if LOCAL_DEBUG:
html += '<h3>IP address: %s ' % ip html += '<h3>IP address: %s <h3>' % ip
html += '<h3>User: %s ' % request.user html += '<h3>User: %s </h3>' % request.user
html += '<h3>My pid: %s</h3>' % os.getpid()
log.debug('request from ip=%s, user=%s' % (ip,request.user)) log.debug('request from ip=%s, user=%s' % (ip,request.user))
if not (ip in ALLOWED_IPS or 'any' in ALLOWED_IPS): if not (ip in ALLOWED_IPS or 'any' in ALLOWED_IPS):
...@@ -66,14 +77,36 @@ def manage_modulestores(request,reload_dir=None): ...@@ -66,14 +77,36 @@ def manage_modulestores(request,reload_dir=None):
return HttpResponse(html, status=403) return HttpResponse(html, status=403)
#---------------------------------------- #----------------------------------------
# reload course if specified # reload course if specified; handle optional commit_id
if reload_dir is not None: if reload_dir is not None:
if reload_dir not in def_ms.courses: if reload_dir not in def_ms.courses:
html += '<h2 class="inline-error">Error: "%s" is not a valid course directory</h2>' % reload_dir html += '<h2 class="inline-error">Error: "%s" is not a valid course directory</h2>' % reload_dir
else: else:
html += '<h2>Reloaded course directory "%s"</h2>' % reload_dir # reloading based on commit_id is needed when running mutiple worker threads,
def_ms.try_load_course(reload_dir) # so that a given thread doesn't reload the same commit multiple times
current_commit_id = get_commit_id(def_ms.courses[reload_dir])
log.debug('commit_id="%s"' % commit_id)
log.debug('current_commit_id="%s"' % current_commit_id)
if (commit_id is not None) and (commit_id==current_commit_id):
html += "<h2>Already at commit id %s for %s</h2>" % (commit_id, reload_dir)
track.views.server_track(request,
'reload %s skipped already at %s (pid=%s)' % (reload_dir,
commit_id,
os.getpid(),
),
{}, page='migrate')
else:
html += '<h2>Reloaded course directory "%s"</h2>' % reload_dir
def_ms.try_load_course(reload_dir)
gdir = settings.DATA_DIR / reload_dir
new_commit_id = os.popen('cd %s; git log -n 1 | head -1' % gdir).read().strip().split(' ')[1]
set_commit_id(def_ms.courses[reload_dir], new_commit_id)
html += '<p>commit_id=%s</p>' % new_commit_id
track.views.server_track(request, 'reloaded %s now at %s (pid=%s)' % (reload_dir,
new_commit_id,
os.getpid()), {}, page='migrate')
#---------------------------------------- #----------------------------------------
...@@ -94,6 +127,8 @@ def manage_modulestores(request,reload_dir=None): ...@@ -94,6 +127,8 @@ def manage_modulestores(request,reload_dir=None):
html += '<hr width="100%"/>' html += '<hr width="100%"/>'
html += '<h2>Course: %s (%s)</h2>' % (course.display_name,cdir) html += '<h2>Course: %s (%s)</h2>' % (course.display_name,cdir)
html += '<p>commit_id=%s</p>' % get_commit_id(course)
for field in dumpfields: for field in dumpfields:
data = getattr(course,field) data = getattr(course,field)
html += '<h3>%s</h3>' % field html += '<h3>%s</h3>' % field
......
'''
django admin pages for courseware model
'''
from psychometrics.models import *
from django.contrib import admin
admin.site.register(PsychometricData)
#!/usr/bin/python
#
# generate pyschometrics data from tracking logs and student module data
import os, sys, string
import datetime
import json
from courseware.models import *
from track.models import *
from psychometrics.models import *
from xmodule.modulestore import Location
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:
url = sm.module_state_key
location = Location(url)
if not location.category=="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='save_problem_check')
tset = tset.filter(event_source='server')
tset = tset.filter(event__contains="'%s'" % url)
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()
#
# 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 nominally a Location instance (url string).
That means it is of the form {tag}://{org}/{course}/{category}/{name}[@{revision}]
and for capa problems, category = "problem".
checktimes is extracted from tracking logs, or added by capa module via psychometrics callback.
"""
studentmodule = models.ForeignKey(StudentModule, db_index=True, unique=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)
...@@ -71,6 +71,8 @@ MITX_FEATURES = { ...@@ -71,6 +71,8 @@ MITX_FEATURES = {
'ENABLE_DISCUSSION' : False, 'ENABLE_DISCUSSION' : False,
'ENABLE_DISCUSSION_SERVICE': True, 'ENABLE_DISCUSSION_SERVICE': True,
'ENABLE_PSYCHOMETRICS': False, # real-time psychometrics (eg item response theory analysis in instructor dashboard)
'ENABLE_SQL_TRACKING_LOGS': False, 'ENABLE_SQL_TRACKING_LOGS': False,
'ENABLE_LMS_MIGRATION': False, 'ENABLE_LMS_MIGRATION': False,
'ENABLE_MANUAL_GIT_RELOAD': False, 'ENABLE_MANUAL_GIT_RELOAD': False,
...@@ -619,6 +621,7 @@ INSTALLED_APPS = ( ...@@ -619,6 +621,7 @@ INSTALLED_APPS = (
'util', 'util',
'certificates', 'certificates',
'instructor', 'instructor',
'psychometrics',
#For the wiki #For the wiki
'wiki', # The new django-wiki from benjaoming 'wiki', # The new django-wiki from benjaoming
......
...@@ -20,6 +20,8 @@ MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False # Enable to test subdomains- ...@@ -20,6 +20,8 @@ MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False # Enable to test subdomains-
MITX_FEATURES['SUBDOMAIN_BRANDING'] = True 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['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_MANUAL_GIT_RELOAD'] = True
MITX_FEATURES['ENABLE_PSYCHOMETRICS'] = True # real-time psychometrics (eg item response theory analysis in instructor dashboard)
WIKI_ENABLED = True WIKI_ENABLED = True
......
...@@ -25,11 +25,6 @@ div.info-wrapper { ...@@ -25,11 +25,6 @@ div.info-wrapper {
margin-bottom: lh(); margin-bottom: lh();
padding-bottom: lh(.5); padding-bottom: lh(.5);
&:first-child {
margin: 0 (-(lh(.5))) lh();
padding: lh(.5);
}
ol, ul { ol, ul {
margin: 0; margin: 0;
list-style-type: disk; list-style-type: disk;
......
...@@ -80,7 +80,6 @@ div.course-wrapper { ...@@ -80,7 +80,6 @@ div.course-wrapper {
} }
.histogram { .histogram {
display: none;
width: 200px; width: 200px;
height: 150px; height: 150px;
} }
...@@ -117,6 +116,7 @@ div.course-wrapper { ...@@ -117,6 +116,7 @@ div.course-wrapper {
margin: 0; margin: 0;
@include clearfix(); @include clearfix();
padding: 0; padding: 0;
list-style: none;
li { li {
width: flex-grid(3, 9); width: flex-grid(3, 9);
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
## <script type="text/javascript" src="${static.url('js/vendor/CodeMirror-2.25/mode/xml/xml.js')}"></script> ## <script type="text/javascript" src="${static.url('js/vendor/CodeMirror-2.25/mode/xml/xml.js')}"></script>
## <script type="text/javascript" src="${static.url('js/vendor/CodeMirror-2.25/mode/python/python.js')}"></script> ## <script type="text/javascript" src="${static.url('js/vendor/CodeMirror-2.25/mode/python/python.js')}"></script>
<%static:js group='courseware'/> <%static:js group='courseware'/>
<%static:js group='discussion'/> <%static:js group='discussion'/>
...@@ -35,6 +36,22 @@ ...@@ -35,6 +36,22 @@
<script type="text/javascript"> <script type="text/javascript">
var $$course_id = "${course.id}"; var $$course_id = "${course.id}";
$(function(){
$(".ui-accordion-header a").each(function() {
var wordArray = $(this).text().split(" ");
var finalTitle = "";
for (i=0;i<=wordArray.length-1;i++) {
finalTitle += wordArray[i];
if (i == (wordArray.length-2)) {
finalTitle += "&nbsp;";
} else {
finalTitle += " ";
}
}
$(this).html(finalTitle);
});
});
</script> </script>
</%block> </%block>
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
<%block name="headextra"> <%block name="headextra">
<%static:css group='course'/> <%static:css group='course'/>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.axislabels.js')}"></script>
</%block> </%block>
<%include file="/courseware/course_navigation.html" args="active_page='instructor'" /> <%include file="/courseware/course_navigation.html" args="active_page='instructor'" />
...@@ -31,37 +33,91 @@ table.stat_table td { ...@@ -31,37 +33,91 @@ table.stat_table td {
border-color: #666666; border-color: #666666;
background-color: #ffffff; background-color: #ffffff;
} }
a.selectedmode { background-color: yellow; }
</style> </style>
<script language="JavaScript" type="text/javascript">
function goto( mode)
{
document.idashform.idash_mode.value = mode;
document.idashform.submit() ;
}
</script>
<section class="container"> <section class="container">
<div class="instructor-dashboard-wrapper"> <div class="instructor-dashboard-wrapper">
<section class="instructor-dashboard-content"> <section class="instructor-dashboard-content">
<h1>Instructor Dashboard</h1> <h1>Instructor Dashboard</h1>
<form method="POST"> <h2>[ <a href="#" onclick="goto('Grades');" class="${modeflag.get('Grades')}">Grades</a> |
%if settings.MITX_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> ]
</h2>
<div style="text-align:right" id="djangopid">${djangopid}</div>
<form name="idashform" method="POST">
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }"> <input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
<input type="hidden" name="idash_mode" value="">
##-----------------------------------------------------------------------------
%if modeflag.get('Grades'):
<p> <p>
<a href="${reverse('gradebook', kwargs=dict(course_id=course.id))}">Gradebook</a> <a href="${reverse('gradebook', kwargs=dict(course_id=course.id))}">Gradebook</a>
</p>
<p> <p>
<a href="${reverse('grade_summary', kwargs=dict(course_id=course.id))}">Grade summary</a> <a href="${reverse('grade_summary', kwargs=dict(course_id=course.id))}">Grade summary</a>
</p>
<p> <p>
<input type="submit" name="action" value="Dump list of enrolled students"> <input type="submit" name="action" value="Dump list of enrolled students">
</p>
<p> <p>
<input type="submit" name="action" value="Dump Grades for all students in this course"> <input type="submit" name="action" value="Dump Grades for all students in this course">
<input type="submit" name="action" value="Download CSV of all student grades for this course"> <input type="submit" name="action" value="Download CSV of all student grades for this course">
</p>
<p> <p>
<input type="submit" name="action" value="Dump all RAW grades for all students in this course"> <input type="submit" name="action" value="Dump all RAW grades for all students in this course">
<input type="submit" name="action" value="Download CSV of all RAW grades"> <input type="submit" name="action" value="Download CSV of all RAW grades">
</p>
<p> <p>
<input type="submit" name="action" value="Download CSV of answer distributions"> <input type="submit" name="action" value="Download CSV of answer distributions">
</p>
%endif
%if instructor_access: ##-----------------------------------------------------------------------------
%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 instructor_access:
<hr width="40%" style="align:left"> <hr width="40%" style="align:left">
<p> <p>
<input type="submit" name="action" value="List course staff members"> <input type="submit" name="action" value="List course staff members">
...@@ -69,16 +125,20 @@ table.stat_table td { ...@@ -69,16 +125,20 @@ table.stat_table td {
<input type="text" name="staffuser"> <input type="submit" name="action" value="Remove course staff"> <input type="text" name="staffuser"> <input type="submit" name="action" value="Remove course staff">
<input type="submit" name="action" value="Add course staff"> <input type="submit" name="action" value="Add course staff">
<hr width="40%" style="align:left"> <hr width="40%" style="align:left">
%endif %endif
%if settings.MITX_FEATURES['ENABLE_MANUAL_GIT_RELOAD'] and admin_access: %if settings.MITX_FEATURES['ENABLE_MANUAL_GIT_RELOAD'] and admin_access:
<p> <p>
<input type="submit" name="action" value="Reload course from XML files"> <input type="submit" name="action" value="Reload course from XML files">
<input type="submit" name="action" value="GIT pull and Reload course"> <input type="submit" name="action" value="GIT pull and Reload course">
%endif
%endif %endif
</form> </form>
##-----------------------------------------------------------------------------
%if modeflag.get('Psychometrics') is None:
<br/> <br/>
<br/> <br/>
<p> <p>
...@@ -99,14 +159,45 @@ table.stat_table td { ...@@ -99,14 +159,45 @@ table.stat_table td {
%endfor %endfor
</table> </table>
</p> </p>
%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 msg: %if msg:
<p>${msg}</p> <p>${msg}</p>
%endif %endif
% if course_errors is not UNDEFINED: ##-----------------------------------------------------------------------------
%if modeflag.get('Admin'):
% if course_errors is not UNDEFINED:
<h2>Course errors</h2> <h2>Course errors</h2>
<div id="course-errors"> <div id="course-errors">
%if not course_errors:
None
%else:
<ul> <ul>
% for (summary, err) in course_errors: % for (summary, err) in course_errors:
<li>${summary | h} <li>${summary | h}
...@@ -118,8 +209,10 @@ table.stat_table td { ...@@ -118,8 +209,10 @@ table.stat_table td {
</li> </li>
% endfor % endfor
</ul> </ul>
%endif
</div> </div>
% endif % endif
%endif
</section> </section>
</div> </div>
......
...@@ -76,7 +76,11 @@ ...@@ -76,7 +76,11 @@
<div id="register_message"></div> <div id="register_message"></div>
%endif %endif
%else: %else:
<a href="#signup-modal" class="register" rel="leanModal" data-notice='You must Sign Up or <a href="#login-modal" rel="leanModal">Log In</a> to enroll.'>Register for ${course.number}</a> <a href="#signup-modal" class="register" rel="leanModal" data-notice='You must Sign Up
% if not settings.MITX_FEATURES['DISABLE_LOGIN_BUTTON']:
or <a href="#login-modal" rel="leanModal">Log In</a>
% endif
to enroll.'>Register for ${course.number}</a>
%endif %endif
</div> </div>
......
[ [
{
"title": "Is MIT Giving Away the Farm?",
"url": "http://www.technologyreview.com/mitnews/428698/is-mit-giving-away-the-farm/",
"author": "Larry Hardesty",
"image": "techreview_logo_178x138.jpg",
"deck": "The surprising logic of MIT's free online education program.",
"publication": "Technology Review",
"publish_date": "September/October 2012"
},
{
"title": "School’s Out, Forever",
"url": "http://www.bostonmagazine.com/articles/2012/08/edx-online-classes-schools-ou
t-forever/",
"author": "Chris Vogel",
"image": "bostonmag_logo_178x138.jpg",
"deck": "A new online education program from Harvard and MIT is poised to transform what it means to go to college.",
"publication": "Boston Magazine",
"publish_date": "September 2012"
},
{
"title": "Q&A: Anant Agarwal, edX’s president and first professor",
"url": "http://www.bostonmagazine.com/articles/2012/08/edx-online-classes-schools-ou
t-forever/",
"author": " Molly Petrilla ",
"image": "smartplanet_logo_178x138.jpg",
"deck": "",
"publication": "Smart Planet",
"publish_date": "September 3, 2012"
},
{
"title": "EdX To Offer Proctored Final Exam For One Course",
"url": "http://www.thecrimson.com/article/2012/9/7/edx-offer-proctored-exams/",
"author": "Samuel Y. Weinstock",
"image": "harvardcrimson_logo_178x138.jpeg",
"deck": "",
"publication": "Harvard Crimson",
"publish_date": "September 7, 2012"
},
{
"title": "MOOCing On Site",
"url": "http://www.insidehighered.com/news/2012/09/07/site-based-testing-deals-strengthen-case-granting-credit-mooc-students",
"author": "Steve Kolowich ",
"image": "insidehighered_logo_178x138.jpg",
"deck": "",
"publication": "Inside Higher Education",
"publish_date": "September 7, 2012"
},
{
"title": "edX Curbs the Downfalls of Online Education By Announcing Supervised Final Exams",
"url": "http://bostinno.com/2012/09/07/edx-pearson-proctored-exams/",
"author": "Lauren Landry",
"image": "bostinno_logo_178x138.jpg",
"deck": "",
"publication": "Bostinno",
"publish_date": "September 7, 2012"
},
{
"title": "Harvard and MIT online courses get 'real world' exams",
"url": "http://www.bbc.co.uk/news/education-19505776",
"author": "Sean Coughlan",
"image": "bbc_logo_178x138.jpeg",
"deck": "",
"publication": "BBC",
"publish_date": "September 6, 2012"
},
{
"title": "Harvard-MIT Online School EdX to Offer Supervised Final Exams",
"url": "http://www.businessweek.com/news/2012-09-06/harvard-mit-online-school-edx-to-offer-supervised-final-exams",
"author": "Oliver Staley",
"image": "bloomberg_logo_178x138.jpeg",
"deck": "",
"publication": "Bloomberg Business Week",
"publish_date": "September 6, 2012"
},
{
"title": "Colorado State to Offer Credits for Online Class",
"url": "http://www.nytimes.com/2012/09/07/education/colorado-state-to-offer-credits-for-online-class.html?_r=3",
"author": "Tamar Lewin",
"image": "nyt_logo_178x138.jpeg",
"deck": "",
"publication": "New York Times",
"publish_date": "September 6, 2012"
},
{
"title": "edX Offers Proctored Exams for Open Online Course",
"url": "http://chronicle.com/blogs/wiredcampus/edx-offers-proctored-exams-for-open-online-course/39656",
"author": " Marc Parry",
"image": "chroniclehighered_logo_178x138.jpeg",
"deck": "",
"publication": "Chronicle of Higher Education",
"publish_date": "September 6, 2012"
},
{
"title": "edX Offers Proctored Exams for Open Online Course",
"url": "http://itbriefing.net/modules.php?op=modload&name=News&file=article&sid=323229&newlang=eng&topic=15&catid=37",
"author": "",
"image": "itbriefing_logo_178x138.jpg",
"deck": "",
"publication": "ITBriefing.net",
"publish_date": "September 6, 2012"
},
{
"title": "Student Loans: Debt for Life",
"url": "http://www.businessweek.com/articles/2012-09-06/student-loans-debt-for-life#p3",
"author": "Peter Coy",
"image": "bloomberg_logo_178x138.jpeg",
"deck": "",
"publication": "Bloomberg Business Week",
"publish_date": "September 6, 2012"
},
{
"title": "Straighterline wants to help professors expand reach, while students save",
"url": "http://www.baltimoresun.com/business/technology/blog/bs-bz-straighterline-college-professors-20120904,0,6114022.story",
"author": "Gus G. Sentementes",
"image": "baltsun_logo_178x138.jpg",
"deck": "",
"publication": "The Baltimore Sun",
"publish_date": "September 4, 2012"
},
{
"title": "Want to be a reporter? Learn to code",
"url": "http://gigaom.com/cloud/want-to-be-a-reporter-learn-to-code/",
"author": "Barb Darrow",
"image": "gigaom_logo_178x138.jpeg",
"deck": "",
"publication": "GigaOM",
"publish_date": "September 4, 2012"
},
{
"title": "MOOC Brigade: Will Massive, Open Online Courses Revolutionize Higher Education?",
"url": "http://nation.time.com/2012/09/04/mooc-brigade-will-massive-open-online-courses-revolutionize-higher-education/",
"author": "Kayla Webley",
"image": "time_logo_178x138.jpg",
"deck": "",
"publication": "Time",
"publish_date": "September 4, 2012"
},
{
"title": "Ivy walls lower with free online classes from Coursera and edX ",
"url": "http://www.csmonitor.com/Innovation/Pioneers/2012/0903/Ivy-walls-lower-with-free-online-classes-from-Coursera-and-edX",
"author": "Chris Gaylord",
"image": "csmonitor_logo_178x138.jpg",
"deck": "",
"publication": "Christian Science Monitor",
"publish_date": "September 3, 2012"
},
{
"title": "Summer recap. RLADs, new edX partner, Institute files amicus brief",
"url": "http://tech.mit.edu/V132/N34/summer.html",
"author": "",
"image": "thetech_logo_178x138.jpg",
"deck": "",
"publication": "The Tech",
"publish_date": "September 4, 2012"
},
{
"title": "Into the Future With MOOC's",
"url": "http://chronicle.com/article/Into-the-Future-With-MOOCs/134080/",
"author": "Kevin Carey",
"image": "chroniclehighered_logo_178x138.jpeg",
"deck": "",
"publication": "The Chronicle of Higher Education",
"publish_date": "September 3, 2012"
},
{
"title": "The Future Of Higher Education",
"url": "http://radioboston.wbur.org/2012/08/20/higher-education-online",
"author": "",
"image": "radioboston_logo_178x138.jpg",
"deck": "",
"publication": "NPR/Radio Boston",
"publish_date": "August 20, 2012"
},
{
"title": "Berkeley Joins edX",
"url": "http://www.insidehighered.com/quicktakes/2012/07/24/berkeley-joins-edx",
"author": "Tamar Lewin",
"image": "insidehighered_logo_178x138.jpg",
"deck": "",
"publication": "Inside Higher Ed",
"publish_date": "July 24, 2012"
},
{ {
"title": "Berkeley to Join the Free Online Learning Partnership EdX", "title": "Berkeley to Join the Free Online Learning Partnership EdX",
"url": "http://www.nytimes.com/2012/07/24/education/berkeley-to-offer-free-online-classes-on-edx.html?_r=1", "url": "http://www.nytimes.com/2012/07/24/education/berkeley-to-offer-free-online-classes-on-edx.html?_r=1",
......
...@@ -37,4 +37,3 @@ ...@@ -37,4 +37,3 @@
% endfor % endfor
</section> </section>
</section> </section>
...@@ -237,6 +237,7 @@ if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'): ...@@ -237,6 +237,7 @@ if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'):
urlpatterns += ( urlpatterns += (
url(r'^migrate/modules$', 'lms_migration.migrate.manage_modulestores'), url(r'^migrate/modules$', 'lms_migration.migrate.manage_modulestores'),
url(r'^migrate/reload/(?P<reload_dir>[^/]+)$', 'lms_migration.migrate.manage_modulestores'), url(r'^migrate/reload/(?P<reload_dir>[^/]+)$', 'lms_migration.migrate.manage_modulestores'),
url(r'^migrate/reload/(?P<reload_dir>[^/]+)/(?P<commit_id>[^/]+)$', 'lms_migration.migrate.manage_modulestores'),
url(r'^gitreload$', 'lms_migration.migrate.gitreload'), url(r'^gitreload$', 'lms_migration.migrate.gitreload'),
url(r'^gitreload/(?P<reload_dir>[^/]+)$', 'lms_migration.migrate.gitreload'), url(r'^gitreload/(?P<reload_dir>[^/]+)$', 'lms_migration.migrate.gitreload'),
) )
......
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