Commit 203df998 by Justin Riley

fix: imports, API changes, and baked-in course ids

The 'index' view still contains baked-in course ids - will fix this
later ideally by getting rid of 'index' and using the instructor API
instead which mostly does the same thing.
parent 39ed99b8
import datetime
import json import json
import datetime
import logging
from collections import OrderedDict
import pytz import pytz
from courseware.views import * from courseware.models import StudentModule
from edxmako.shortcuts import render_to_response
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from instructor.offline_gradecalc import student_grades from instructor.offline_gradecalc import student_grades
from collections import OrderedDict
from django.http import HttpResponse
from django.contrib.auth.models import User
log = logging.getLogger("mitx.module_tree_reset") log = logging.getLogger("mitx.module_tree_reset")
#-----------------------------------------------------------------------------
class TreeNode(object): class TreeNode(object):
def __init__(self, module, level, student): def __init__(self, module, level, student):
...@@ -18,12 +25,14 @@ class TreeNode(object): ...@@ -18,12 +25,14 @@ class TreeNode(object):
self.smstate = None self.smstate = None
if self.module.category in ['randomize', 'problem', 'problemset']: if self.module.category in ['randomize', 'problem', 'problemset']:
try: try:
self.smstate = StudentModule.objects.get(module_state_key=str(self.module.location), student=student) self.smstate = StudentModule.objects.get(
module_state_key=str(self.module.location),
student=student)
except StudentModule.DoesNotExist: except StudentModule.DoesNotExist:
pass pass
def __str__(self): def __str__(self):
s = "-"*self.level + ("> %s" % str(self.module.location)) s = "-" * self.level + ("> %s" % str(self.module.location))
s += ' (%s)' % self.module.display_name s += ' (%s)' % self.module.display_name
if self.smstate is not None: if self.smstate is not None:
...@@ -34,9 +43,9 @@ class TreeNode(object): ...@@ -34,9 +43,9 @@ class TreeNode(object):
class TreeNodeSet(list): class TreeNodeSet(list):
def __init__(self, course_id, module, ms, student):
def __init__(self, module, ms, student): super(TreeNodeSet, self).__init__(self)
list.__init__(self) self.course_id = course_id
self.parent_module = module self.parent_module = module
self.student = student self.student = student
self.get_tree(module, ms) self.get_tree(module, ms)
...@@ -45,82 +54,90 @@ class TreeNodeSet(list): ...@@ -45,82 +54,90 @@ class TreeNodeSet(list):
def get_tree(self, module, ms, level=1): def get_tree(self, module, ms, level=1):
self.append(TreeNode(module, level, self.student)) self.append(TreeNode(module, level, self.student))
for child in getattr(module, 'children', []): for child in getattr(module, 'children', []):
m = ms.get_item(child) m = ms.get_instance(self.course_id, child)
if m is not None: if m is not None:
self.get_tree(m, ms, level+1) self.get_tree(m, ms, level + 1)
def get_modules_to_reset(self): def get_modules_to_reset(self):
self.rset = [] self.rset = []
self.pset = [] self.pset = []
for tn in self: for tn in self:
if tn.module.category=='randomize': if tn.module.category == 'randomize':
self.rset.append(tn) self.rset.append(tn)
elif tn.module.category in ['problem', 'problemset']: elif tn.module.category in ['problem', 'problemset']:
self.pset.append(tn) self.pset.append(tn)
def reset_randomization(self): def reset_randomization(self):
''' """
Go through all <problem> and <randomize> modules in tree and reset their StudentModule state to empty. Go through all <problem> and <randomize> modules in tree and reset
''' their StudentModule state to empty.
msg = "Resetting all <problem> and <randomize> in tree of parent module %s\n" % self.parent_module """
msg = ("Resetting all <problem> and <randomize> in tree of parent "
"module %s\n" % self.parent_module)
for module in self.rset + self.pset: for module in self.rset + self.pset:
msg += " Resetting %s, old state=%s\n" % (module, (module.smstate.state if module.smstate is not None else {})) old_state = (module.smstate.state if module.smstate is not None
else {})
msg += " Resetting %s, old state=%s\n" % (module, old_state)
if module.smstate is not None: if module.smstate is not None:
module.smstate.state = '{}' module.smstate.state = '{}'
module.smstate.grade = None module.smstate.grade = None
module.smstate.save() module.smstate.save()
return msg return msg
#-----------------------------------------------------------------------------
class DummyRequest(object): class DummyRequest(object):
META = {} META = {}
def __init__(self): def __init__(self):
return return
def get_host(self): def get_host(self):
return 'edx.mit.edu' return 'edx.mit.edu'
def is_secure(self): def is_secure(self):
return False return False
#-----------------------------------------------------------------------------
class ProctorModuleInfo(object): class ProctorModuleInfo(object):
def __init__(self, course_id):
def __init__(self, course_loc=''):
if not course_loc:
course_loc = 'i4x://MITx/3.091r-exam/course/2013_Fall_residential_exam'
self.ms = modulestore() self.ms = modulestore()
self.course = self.ms.get_item(course_loc) self.course = self.ms.get_course(course_id)
self.get_released_proctor_modules() self.get_released_proctor_modules()
def get_released_proctor_modules(self): def get_released_proctor_modules(self):
chapters = [] chapters = []
for loc in self.course.children: for loc in self.course.children:
chapters.append(self.ms.get_item(loc)) chapters.append(self.ms.get_instance(self.course.id, loc))
#print "chapters:" # print "chapters:"
#print [c.id for c in chapters] # print [c.id for c in chapters]
pmods = [] pmods = []
for c in chapters: for c in chapters:
seq = self.ms.get_item(c.children[0]) seq = self.ms.get_instance(self.course.id, c.children[0])
if seq.category=='proctor': if seq.category == 'proctor':
pmods.append(seq) pmods.append(seq)
#print "proctor modules:" # print "proctor modules:"
#print [x.id for x in pmods] # print [x.id for x in pmods]
now = datetime.datetime.now(pytz.utc) now = datetime.datetime.now(pytz.utc)
rpmods = [p for p in pmods if p.lms.start < now] rpmods = [p for p in pmods if p.start < now]
for rpmod in rpmods: for rpmod in rpmods:
rpmod.ra_ps = self.ms.get_item(rpmod.children[0]) # the problemset # the problemset
rpmod.ra_rand = self.ms.get_item(rpmod.ra_ps.children[0]) # the randomize rpmod.ra_ps = self.ms.get_instance(self.course.id,
# rpmod.ra_prob = self.ms.get_item(rpmod.ra_rand.children[0]) # the problem rpmod.children[0])
# the randomize
#print "released pmods" rpmod.ra_rand = self.ms.get_instance(self.course.id,
#print [x.id for x in rpmods] rpmod.ra_ps.children[0])
# the problem
# rpmod.ra_prob = self.ms.get_instance(self.course.id,
# rpmod.ra_rand.children[0])
# print "released pmods"
# print [x.id for x in rpmods]
self.chapters = chapters self.chapters = chapters
self.pmods = pmods self.pmods = pmods
...@@ -137,114 +154,132 @@ class ProctorModuleInfo(object): ...@@ -137,114 +154,132 @@ class ProctorModuleInfo(object):
request.session = {} request.session = {}
try: try:
gradeset = student_grades(student, request, self.course, keep_raw_scores=False, use_offline=False) gradeset = student_grades(student, request, self.course,
except Exception as err: keep_raw_scores=False, use_offline=False)
#log.exception("Failed to get grades for %s" % student) except Exception:
# log.exception("Failed to get grades for %s" % student)
print("Failed to get grades for %s" % student) print("Failed to get grades for %s" % student)
gradeset = [] gradeset = []
self.gradeset = gradeset self.gradeset = gradeset
return gradeset return gradeset
def get_student_status(self, student): def get_student_status(self, student, debug=False):
''' """
For a given student, and for all released proctored modules, get StudentModule state for each, and For a given student, and for all released proctored modules, get
see which randomized problem was selected for a student (if any). StudentModule state for each, and see which randomized problem was
''' selected for a student (if any).
"""
if isinstance(student, str): if isinstance(student, str):
student = User.objects.get(username=student) student = User.objects.get(username=student)
self.student = student self.student = student
smstates = OrderedDict() smstates = OrderedDict()
# temporary - for debugging; flushes db cache # for debugging; flushes db cache
if False: if debug:
from django.db import transaction from django.db import transaction
try: try:
transaction.commit() transaction.commit()
except Exception as err: except Exception:
print "db cache flushed" print "db cache flushed"
class StateInfo(object): class StateInfo(object):
def __init__(self): def __init__(self):
self.state = '{}' self.state = '{}'
return return
# assume <proctor><problemset><randomized/></problemset></prcotor>
for rpmod in self.rpmods: # assume <proctor><problemset><randomized/></problemset></prcotor> structure # structure
for rpmod in self.rpmods:
try: try:
sm = StudentModule.objects.get(module_state_key=str(rpmod.ra_rand.location), student=student) # randomize state sm = StudentModule.objects.get(
module_state_key=str(rpmod.ra_rand.location),
student=student) # randomize state
except StudentModule.DoesNotExist: except StudentModule.DoesNotExist:
sm = StateInfo() sm = StateInfo()
sm.rpmod = rpmod sm.rpmod = rpmod
try: try:
ps_sm = StudentModule.objects.get(module_state_key=str(rpmod.ra_ps.location), student=student) # problemset state ps_sm = StudentModule.objects.get(
module_state_key=str(rpmod.ra_ps.location),
student=student) # problemset state
except StudentModule.DoesNotExist: except StudentModule.DoesNotExist:
ps_sm = StateInfo() ps_sm = StateInfo()
sm.ps_sm = ps_sm sm.ps_sm = ps_sm
sm.score = None sm.score = None
# get title (display_name) of problem assigned, if student had started a problem # get title (display_name) of problem assigned, if student had
# base this on the "choice" from the randmize module state # started a problem base this on the "choice" from the randmize
# module state
try: try:
sm.choice = int(json.loads(sm.state)['choice']) sm.choice = int(json.loads(sm.state)['choice'])
except Exception as err: except Exception:
sm.choice = None sm.choice = None
if sm.choice is not None: if sm.choice is not None:
try: try:
sm.problem = self.ms.get_item(rpmod.ra_rand.children[sm.choice]) sm.problem = self.ms.get_instance(
self.course.id, rpmod.ra_rand.children[sm.choice])
sm.problem_name = sm.problem.display_name sm.problem_name = sm.problem.display_name
except Exception as err: except Exception:
log.exception("Failed to get rand child choice=%s for %s student=%s" % (sm.choice, rpmod.ra_rand, student)) log.exception("Failed to get rand child "
"choice=%s for %s student=%s" %
(sm.choice, rpmod.ra_rand, student))
sm.problem = None sm.problem = None
sm.problem_name = None sm.problem_name = None
else: else:
sm.problem = None sm.problem = None
sm.problem_name = None sm.problem_name = None
# the url_name should be like 'LS1' and be the same key used in the
smstates[rpmod.url_name] = sm # the url_name should be like 'LS1' and be the same key used in the grade scores # grade scores
smstates[rpmod.url_name] = sm
self.smstates = smstates self.smstates = smstates
# get grades, match gradeset assignments with StudentModule states, and put grades there # get grades, match gradeset assignments with StudentModule states, and
# put grades there
self.get_grades() self.get_grades()
for score in self.gradeset['totaled_scores']['Assessment']: for score in self.gradeset['totaled_scores']['Assessment']:
if score.section in smstates: if score.section in smstates:
smstates[score.section].score = score smstates[score.section].score = score
s = 'State for student %s:\n' % student s = 'State for student %s:\n' % student
status = {} # this can be turned into a JSON string for the proctor panel status = {} # this can be turned into a JSON str for the proctor panel
status['student'] = dict(username=str(student), name=student.profile.name, id=student.id) status['student'] = dict(username=str(student),
name=student.profile.name, id=student.id)
status['assignments'] = [] status['assignments'] = []
for (name, sm) in smstates.iteritems(): for (name, sm) in smstates.iteritems():
# this doesn't work, since score will always appear?
# attempted = (sm.score is not None)
# attempted = (sm.score is not None) # this doesn't work, since score will always appear? # if student has visited the problemset then position will have
attempted = 'position' in sm.ps_sm.state # if student has visited the problemset then position will have been set # been set
attempted = 'position' in sm.ps_sm.state
if not attempted and sm.score is not None and sm.score.earned: if not attempted and sm.score is not None and sm.score.earned:
attempted = True attempted = True
stat = dict(name=name, assignment=sm.rpmod.ra_ps.display_name, pm_sm=sm.ps_sm.state, choice=sm.choice, earned=(sm.score.earned if sm.score is not None else None)
possible=(sm.score.possible if sm.score is not None else None)
stat = dict(name=name, assignment=sm.rpmod.ra_ps.display_name,
pm_sm=sm.ps_sm.state, choice=sm.choice,
problem=sm.problem_name, problem=sm.problem_name,
attempted=attempted, attempted=attempted,
earned=(sm.score.earned if sm.score is not None else None), earned=earned,
possible=(sm.score.possible if sm.score is not None else None), possible=possible)
)
status['assignments'].append(stat) status['assignments'].append(stat)
s += "[%s] %s -> %s (%s) %s [%s]\n" % (name, stat['assignment'], stat['pm_sm'], sm.choice, sm.problem_name, sm.score) s += "[%s] %s -> %s (%s) %s [%s]\n" % (name, stat['assignment'],
stat['pm_sm'], sm.choice,
sm.problem_name, sm.score)
self.status = status self.status = status
self.status_str = s self.status_str = s
return status return status
def get_student_grades(self, student): def get_student_grades(self, student):
''' """
Return student grades for assessments as a dict suitable for CSV file output, Return student grades for assessments as a dict suitable for CSV file
with id, name, username, prob1, grade1, prob2, grade2, ... output, with id, name, username, prob1, grade1, prob2, grade2, ...
where grade1 = points earned on assignment LS1, or '' if not attempted where grade1 = points earned on assignment LS1, or '' if not attempted
and prob1 = problem which was assigned or '' if not attempted and prob1 = problem which was assigned or '' if not attempted
''' """
status = self.get_student_status(student) status = self.get_student_status(student)
ret = OrderedDict() ret = OrderedDict()
ret['id'] = student.id ret['id'] = student.id
...@@ -263,69 +298,79 @@ class ProctorModuleInfo(object): ...@@ -263,69 +298,79 @@ class ProctorModuleInfo(object):
def get_assignments_attempted_and_failed(self, student, do_reset=False): def get_assignments_attempted_and_failed(self, student, do_reset=False):
status = self.get_student_status(student) status = self.get_student_status(student)
assignments = [] assignments = []
for stat in status['assignments']: for stat in status['assignments']:
if stat['attempted']: if stat['attempted']:
if not stat['earned'] == stat['possible']: if not stat['earned'] == stat['possible']:
s = "Student %s Assignment %s attempted '%s' but failed (%s/%s)" % (student, stat['name'], stat['problem'], stat['earned'], stat['possible']) log.info(
assignments.append(OrderedDict(id=student.id, "Student %s Assignment %s attempted '%s' but failed "
name=student.profile.name, "(%s/%s)" % (student, stat['name'], stat['problem'],
username=student.username, stat['earned'], stat['possible']))
assignment=stat['name'], assignments.append(OrderedDict(
problem=stat['problem'], id=student.id,
date=str(datetime.datetime.now()), name=student.profile.name,
earned=stat['earned'], username=student.username,
possible=stat['possible'], assignment=stat['name'],
) problem=stat['problem'],
) date=str(datetime.datetime.now()),
earned=stat['earned'],
possible=stat['possible'],
))
if do_reset: if do_reset:
aaf = assignments[-1] aaf = assignments[-1]
try: try:
log.debug('resetting %s for student %s' % (aaf['assignment'], aaf['username'])) log.debug('resetting %s for student %s' %
pmod = self.ms.get_item('i4x://MITx/3.091r-exam/proctor/%s' % aaf['assignment']) (aaf['assignment'], aaf['username']))
tnset = TreeNodeSet(pmod, self.ms, student) cloc = self.course.location
assi_url = Location(cloc.tag, cloc.org,
cloc.course, 'proctor',
aaf['assignment'])
pmod = self.ms.get_instance(self.course.id,
assi_url.url())
tnset = TreeNodeSet(self.course.id, pmod, self.ms,
student)
msg = tnset.reset_randomization() msg = tnset.reset_randomization()
log.debug(str(msg)) log.debug(str(msg))
except Exception as err: except Exception:
log.exception("Failed to do reset of %s for %s" % (aaf['assignment'], student)) log.exception("Failed to do reset of %s for %s" %
(aaf['assignment'], student))
return assignments return assignments
#-----------------------------------------------------------------------------
def getip(request): def getip(request):
''' """
Extract IP address of requester from header, even if behind proxy Extract IP address of requester from header, even if behind proxy
''' """
ip = request.META.get('HTTP_X_REAL_IP', '') # nginx reverse proxy ip = request.META.get('HTTP_X_REAL_IP', '') # nginx reverse proxy
if not ip: if not ip:
ip = request.META.get('REMOTE_ADDR', 'None') ip = request.META.get('REMOTE_ADDR', 'None')
return ip return ip
#-----------------------------------------------------------------------------
ALLOWED_IPS = [ '173.48.139.155', '10.152.159.162', '54.235.195.90' ] ALLOWED_IPS = ['173.48.139.155', '10.152.159.162', '54.235.195.90']
#ALLOWED_IPS = [ ] # ALLOWED_IPS = [ ]
ALLOWED_STAFF = 'staff_MITx/3.091r-exam/2013_Fall_residential_exam' ALLOWED_STAFF = 'staff_MITx/3.091r-exam/2013_Fall_residential_exam'
def index(request):
def index(request):
ip = getip(request) ip = getip(request)
if ip not in ALLOWED_IPS:
if not ip in ALLOWED_IPS:
if request.user and request.user.is_staff and False: if request.user and request.user.is_staff and False:
log.debug('request allowed because user=%s is staff' % request.user) log.debug('request allowed because user=%s is staff' %
request.user)
elif request.user is not None and request.user: elif request.user is not None and request.user:
groups = [g.name for g in request.user.groups.all()] groups = [g.name for g in request.user.groups.all()]
if ALLOWED_STAFF in groups: if ALLOWED_STAFF in groups:
log.debug('request allowed because user=%s is in group %s' % (request.user, ALLOWED_STAFF)) log.debug('request allowed because user=%s is in group %s' %
(request.user, ALLOWED_STAFF))
else: else:
log.debug('request denied, user=%s, groups %s' % (request.user, groups)) log.debug('request denied, user=%s, groups %s' %
(request.user, groups))
# return HttpResponse('permission denied', status=403) # return HttpResponse('permission denied', status=403)
else: else:
log.debug('request denied, user=%s, groups %s' % (request.user, groups)) log.debug('request denied, user=%s, groups %s' %
(request.user, groups))
# return HttpResponse('permission denied', status=403) # return HttpResponse('permission denied', status=403)
else: else:
log.debug('request allowed, in ALLOWED_IPS') log.debug('request allowed, in ALLOWED_IPS')
...@@ -335,13 +380,14 @@ def index(request): ...@@ -335,13 +380,14 @@ def index(request):
try: try:
student = User.objects.get(username=username) student = User.objects.get(username=username)
except User.DoesNotExist: except User.DoesNotExist:
return HttpResponse(json.dumps({'msg':'User does not exist', 'error': True})) return HttpResponse(
json.dumps({'msg': 'User does not exist', 'error': True}))
cmd = request.GET.get('cmd') cmd = request.GET.get('cmd')
if cmd=='reset': if cmd == 'reset':
location = request.GET.get('location') # eg proctor/Assessment_2 location = request.GET.get('location') # e.g. proctor/Assessment_2
location = location.replace(' ','_') location = location.replace(' ', '_')
ms = modulestore() ms = modulestore()
pmod = ms.get_item('i4x://MITx/3.091r-exam/proctor/%s' % location) pmod = ms.get_item('i4x://MITx/3.091r-exam/proctor/%s' % location)
...@@ -351,33 +397,43 @@ def index(request): ...@@ -351,33 +397,43 @@ def index(request):
for r in tnset.rset + tnset.pset: for r in tnset.rset + tnset.pset:
s += str(r) + '\n' s += str(r) + '\n'
msg = tnset.reset_randomization() # msg = tnset.reset_randomization()
tnset.reset_randomization()
# return render_to_response("module_tree_reset.html", {'status': s, 'msg': msg}) # return render_to_response("module_tree_reset.html",
#cmd = 'grades' # {'status': s, 'msg': msg})
# cmd = 'grades'
cmd = 'status' cmd = 'status'
if cmd=='status': if cmd == 'status':
try: try:
pminfo = ProctorModuleInfo() pminfo = ProctorModuleInfo()
status = pminfo.get_student_status(student) status = pminfo.get_student_status(student)
except Exception as err: except Exception as err:
log.exception("Failed to get status for %s" % student) log.exception("Failed to get status for %s" % student)
return HttpResponse(json.dumps({'msg':'Error getting grades for %s' % student, 'error': True, 'errstr': str(err)})) return HttpResponse(json.dumps(
{'msg': 'Error getting grades for %s' % student,
'error': True,
'errstr': str(err)}))
return HttpResponse(json.dumps(status)) return HttpResponse(json.dumps(status))
if cmd=='grades': if cmd == 'grades':
# from instructor.offline_gradecalc import student_grades # from instructor.offline_gradecalc import student_grades
ms = modulestore() ms = modulestore()
course = ms.get_item('i4x://MITx/3.091r-exam/course/2013_Fall_residential_exam') course = ms.get_item(
'i4x://MITx/3.091r-exam/course/2013_Fall_residential_exam')
try: try:
gradeset = student_grades(student, request, course, keep_raw_scores=False, use_offline=False) gradeset = student_grades(student, request, course,
keep_raw_scores=False, use_offline=False)
except Exception as err: except Exception as err:
log.exception("Failed to get grades for %s" % student) log.exception("Failed to get grades for %s" % student)
return HttpResponse(json.dumps({'msg':'Error getting grades for %s' % student, 'error': True})) return HttpResponse(json.dumps(
{'msg': 'Error getting grades for %s' % student,
'error': True}))
grades = gradeset['totaled_scores'] grades = gradeset['totaled_scores']
grades['student_id'] = student.id grades['student_id'] = student.id
return HttpResponse(json.dumps(grades)) return HttpResponse(json.dumps(grades))
return render_to_response("module_tree_reset.html", {'status': 'unknown command', 'msg': ''}) return render_to_response("module_tree_reset.html",
{'status': 'unknown command', 'msg': ''})
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