Define initial celery task on instructor dash.

Add CourseTaskLog, and make calls to regrading.

Add regrading implementation, and hack the marshalling of request.
This commit is contained in:
Brian Wilson
2013-04-25 18:22:00 -04:00
parent 94238238e7
commit 91ac6e68e8
7 changed files with 738 additions and 6 deletions

View File

@@ -0,0 +1,162 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'CourseTaskLog'
db.create_table('courseware_coursetasklog', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('course_id', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
('student', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', null=True, to=orm['auth.User'])),
('task_name', self.gf('django.db.models.fields.CharField')(max_length=50, db_index=True)),
('task_args', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
('task_id', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
('task_status', self.gf('django.db.models.fields.CharField')(max_length=50, null=True, db_index=True)),
('requester', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['auth.User'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, null=True, db_index=True, blank=True)),
('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, db_index=True, blank=True)),
))
db.send_create_signal('courseware', ['CourseTaskLog'])
def backwards(self, orm):
# Deleting model 'CourseTaskLog'
db.delete_table('courseware_coursetasklog')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'courseware.coursetasklog': {
'Meta': {'object_name': 'CourseTaskLog'},
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'requester': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['auth.User']"}),
'student': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['auth.User']"}),
'task_args': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'task_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'task_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
'task_status': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'db_index': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'})
},
'courseware.offlinecomputedgrade': {
'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'OfflineComputedGrade'},
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
'gradeset': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'courseware.offlinecomputedgradelog': {
'Meta': {'ordering': "['-created']", 'object_name': 'OfflineComputedGradeLog'},
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'nstudents': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'seconds': ('django.db.models.fields.IntegerField', [], {'default': '0'})
},
'courseware.studentmodule': {
'Meta': {'unique_together': "(('student', 'module_state_key', 'course_id'),)", 'object_name': 'StudentModule'},
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
'done': ('django.db.models.fields.CharField', [], {'default': "'na'", 'max_length': '8', 'db_index': 'True'}),
'grade': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'max_grade': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
'module_state_key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'module_id'", 'db_index': 'True'}),
'module_type': ('django.db.models.fields.CharField', [], {'default': "'problem'", 'max_length': '32', 'db_index': 'True'}),
'state': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'courseware.studentmodulehistory': {
'Meta': {'object_name': 'StudentModuleHistory'},
'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
'grade': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'max_grade': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'student_module': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['courseware.StudentModule']"}),
'version': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'null': 'True', 'blank': 'True'})
},
'courseware.xmodulecontentfield': {
'Meta': {'unique_together': "(('definition_id', 'field_name'),)", 'object_name': 'XModuleContentField'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
'definition_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'field_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
'value': ('django.db.models.fields.TextField', [], {'default': "'null'"})
},
'courseware.xmodulesettingsfield': {
'Meta': {'unique_together': "(('usage_id', 'field_name'),)", 'object_name': 'XModuleSettingsField'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
'field_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
'usage_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'value': ('django.db.models.fields.TextField', [], {'default': "'null'"})
},
'courseware.xmodulestudentinfofield': {
'Meta': {'unique_together': "(('student', 'field_name'),)", 'object_name': 'XModuleStudentInfoField'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
'field_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'value': ('django.db.models.fields.TextField', [], {'default': "'null'"})
},
'courseware.xmodulestudentprefsfield': {
'Meta': {'unique_together': "(('student', 'module_type', 'field_name'),)", 'object_name': 'XModuleStudentPrefsField'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
'field_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
'module_type': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'value': ('django.db.models.fields.TextField', [], {'default': "'null'"})
}
}
complete_apps = ['courseware']

View File

@@ -17,6 +17,7 @@ from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
class StudentModule(models.Model):
"""
Keeps student state for a particular module in a particular course.
@@ -262,3 +263,20 @@ class OfflineComputedGradeLog(models.Model):
def __unicode__(self):
return "[OCGLog] %s: %s" % (self.course_id, self.created)
class CourseTaskLog(models.Model):
"""
Stores information about background tasks that have been submitted to
perform course-specific work.
Examples include grading and regrading.
"""
course_id = models.CharField(max_length=255, db_index=True)
student = models.ForeignKey(User, null=True, db_index=True, related_name='+') # optional: None = task applies to all students
task_name = models.CharField(max_length=50, db_index=True)
task_args = models.CharField(max_length=255, db_index=True)
task_id = models.CharField(max_length=255, db_index=True) # max_length from celery_taskmeta
task_status = models.CharField(max_length=50, null=True, db_index=True) # max_length from celery_taskmeta
requester = models.ForeignKey(User, db_index=True, related_name='+')
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
updated = models.DateTimeField(auto_now=True, db_index=True)

View File

@@ -0,0 +1,352 @@
import json
import logging
from django.contrib.auth.models import User
from courseware.models import StudentModule, CourseTaskLog
from courseware.model_data import ModelDataCache
from courseware.module_render import get_module
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError,\
InvalidLocationError
import track.views
from celery import task, current_task
from celery.utils.log import get_task_logger
from time import sleep
from django.core.handlers.wsgi import WSGIRequest
logger = get_task_logger(__name__)
# celery = Celery('tasks', broker='django://')
log = logging.getLogger(__name__)
@task
def add(x, y):
return x + y
@task
def echo(value):
if value == 'ping':
result = 'pong'
else:
result = 'got: {0}'.format(value)
return result
@task
def waitawhile(value):
for i in range(value):
sleep(1) # in seconds
logger.info('Waited {0} seconds...'.format(i))
current_task.update_state(state='PROGRESS',
meta={'current': i, 'total': value})
result = 'Yeah!'
return result
class UpdateProblemModuleStateError(Exception):
pass
def _update_problem_module_state(request, course_id, problem_url, student, update_fcn, action_name, filter_fcn):
'''
Performs generic update by visiting StudentModule instances with the update_fcn provided
If student is None, performs update on modules for all students on the specified problem
'''
module_state_key = problem_url
# TODO: store this in the task state, not as a separate return value.
# (Unless that's not what the task state is intended to mean. The task can successfully
# complete, as far as celery is concerned, but have an internal status of failed.)
succeeded = False
# find the problem descriptor, if any:
try:
module_descriptor = modulestore().get_instance(course_id, module_state_key)
succeeded = True
except ItemNotFoundError:
msg = "Couldn't find problem with that urlname."
except InvalidLocationError:
msg = "Couldn't find problem with that urlname."
if module_descriptor is None:
msg = "Couldn't find problem with that urlname."
# if not succeeded:
# current_task.update_state(
# meta={'attempted': num_attempted, 'updated': num_updated, 'total': num_total})
# The task should still succeed, but should have metadata indicating
# that the result of the successful task was a failure. (It's not
# the queue that failed, but the task put on the queue.)
# find the module in question
succeeded = False
modules_to_update = StudentModule.objects.filter(course_id=course_id,
module_state_key=module_state_key)
# give the option of regrading an individual student. If not specified,
# then regrades all students who have responded to a problem so far
if student is not None:
modules_to_update = modules_to_update.filter(student_id=student.id)
if filter_fcn is not None:
modules_to_update = filter_fcn(modules_to_update)
# perform the main loop
num_updated = 0
num_attempted = 0
num_total = len(modules_to_update) # TODO: make this more efficient. Count()?
for module_to_update in modules_to_update:
num_attempted += 1
# try:
if update_fcn(request, module_to_update, module_descriptor):
num_updated += 1
# if there's an error, just let it throw, and the task will
# be marked as FAILED, with a stack trace.
# except UpdateProblemModuleStateError as e:
# something bad happened, so exit right away
# return (succeeded, e.message)
# update task status:
current_task.update_state(state='PROGRESS',
meta={'attempted': num_attempted, 'updated': num_updated, 'total': num_total})
# done with looping through all modules, so just return final statistics:
if student is not None:
if num_attempted == 0:
msg = "Unable to find submission to be {action} for student '{student}' and problem '{problem}'."
elif num_updated == 0:
msg = "Problem failed to be {action} for student '{student}' and problem '{problem}'!"
else:
succeeded = True
msg = "Problem successfully {action} for student '{student}' and problem '{problem}'"
elif num_attempted == 0:
msg = "Unable to find any students with submissions to be {action} for problem '{problem}'."
elif num_updated == 0:
msg = "Problem failed to be {action} for any of {attempted} students for problem '{problem}'!"
elif num_updated == num_attempted:
succeeded = True
msg = "Problem successfully {action} for {attempted} students for problem '{problem}'!"
elif num_updated < num_attempted:
msg = "Problem {action} for {updated} of {attempted} students for problem '{problem}'!"
msg = msg.format(action=action_name, updated=num_updated, attempted=num_attempted, student=student, problem=module_state_key)
# update status in task result object itself:
current_task.update_state(state='DONE',
meta={'attempted': num_attempted, 'updated': num_updated, 'total': num_total,
'succeeded': succeeded, 'message': msg})
# and update status in course task table as well:
# TODO: figure out how this is legal. The actual task result
# status is updated by celery when this task completes, and is
# not
# course_task_log_entry = CourseTaskLog.objects.get(task_id=current_task.id)
# course_task_log_entry.task_status = ...
# return (succeeded, msg)
return succeeded
def _update_problem_module_state_for_student(request, course_id, problem_url, student_identifier,
update_fcn, action_name, filter_fcn=None):
msg = ''
success = False
# try to uniquely id student by email address or username
try:
if "@" in student_identifier:
student_to_update = User.objects.get(email=student_identifier)
elif student_identifier is not None:
student_to_update = User.objects.get(username=student_identifier)
return _update_problem_module_state(request, course_id, problem_url, student_to_update, update_fcn, action_name, filter_fcn)
except User.DoesNotExist:
msg = "Couldn't find student with that email or username."
return (success, msg)
def _update_problem_module_state_for_all_students(request, course_id, problem_url, update_fcn, action_name, filter_fcn=None):
return _update_problem_module_state(request, course_id, problem_url, None, update_fcn, action_name, filter_fcn)
def _regrade_problem_module_state(request, module_to_regrade, module_descriptor):
'''
Takes an XModule descriptor and a corresponding StudentModule object, and
performs regrading on the student's problem submission.
Throws exceptions if the regrading is fatal and should be aborted if in a loop.
'''
# unpack the StudentModule:
course_id = module_to_regrade.course_id
student = module_to_regrade.student
module_state_key = module_to_regrade.module_state_key
# reconstitute the problem's corresponding XModule:
model_data_cache = ModelDataCache.cache_for_descriptor_descendents(course_id, student,
module_descriptor)
# Note that the request is passed to get_module() to provide xqueue-related URL information
instance = get_module(student, request, module_state_key, model_data_cache,
course_id, grade_bucket_type='regrade')
if instance is None:
# Either permissions just changed, or someone is trying to be clever
# and load something they shouldn't have access to.
msg = "No module {loc} for student {student}--access denied?".format(loc=module_state_key,
student=student)
log.debug(msg)
raise UpdateProblemModuleStateError(msg)
if not hasattr(instance, 'regrade_problem'):
# if the first instance doesn't have a regrade method, we should
# probably assume that no other instances will either.
msg = "Specified problem does not support regrading."
raise UpdateProblemModuleStateError(msg)
result = instance.regrade_problem()
if 'success' not in result:
# don't consider these fatal, but false means that the individual call didn't complete:
log.debug("error processing regrade call for problem {loc} and student {student}: "
"unexpected response {msg}".format(msg=result, loc=module_state_key, student=student))
return False
elif result['success'] != 'correct' and result['success'] != 'incorrect':
log.debug("error processing regrade call for problem {loc} and student {student}: "
"{msg}".format(msg=result['success'], loc=module_state_key, student=student))
return False
else:
track.views.server_track(request,
'{instructor} regrade problem {problem} for student {student} '
'in {course}'.format(student=student.id,
problem=module_to_regrade.module_state_key,
instructor=request.user,
course=course_id),
{},
page='idashboard')
return True
def filter_problem_module_state_for_done(modules_to_update):
return modules_to_update.filter(state__contains='"done": true')
@task
def _regrade_problem_for_student(request, course_id, problem_url, student_identifier):
action_name = 'regraded'
update_fcn = _regrade_problem_module_state
filter_fcn = filter_problem_module_state_for_done
return _update_problem_module_state_for_student(request, course_id, problem_url, student_identifier,
update_fcn, action_name, filter_fcn)
def regrade_problem_for_student(request, course_id, problem_url, student_identifier):
# First submit task. Then put stuff into table with the resulting task_id.
result = _regrade_problem_for_student.apply_async(request, course_id, problem_url, student_identifier)
task_id = result.id
# TODO: for log, would want student_identifier to already be mapped to the student
tasklog_args = {'course_id': course_id,
'task_name': 'regrade',
'task_args': problem_url,
'task_id': task_id,
'task_status': result.state,
'requester': request.user}
CourseTaskLog.objects.create(**tasklog_args)
return result
@task
def _regrade_problem_for_all_students(request_environ, course_id, problem_url):
# request = dummy_request
request = WSGIRequest(request_environ)
action_name = 'regraded'
update_fcn = _regrade_problem_module_state
filter_fcn = filter_problem_module_state_for_done
return _update_problem_module_state_for_all_students(request, course_id, problem_url,
update_fcn, action_name, filter_fcn)
def regrade_problem_for_all_students(request, course_id, problem_url):
# Figure out (for now) how to serialize what we need of the request. The actual
# request will not successfully serialize with json or with pickle.
request_environ = {'HTTP_USER_AGENT': request.META['HTTP_USER_AGENT'],
'REMOTE_ADDR': request.META['REMOTE_ADDR'],
'SERVER_NAME': request.META['SERVER_NAME'],
'REQUEST_METHOD': 'GET',
# 'HTTP_X_FORWARDED_PROTO': request.META['HTTP_X_FORWARDED_PROTO'],
}
# Submit task. Then put stuff into table with the resulting task_id.
task_args = [request_environ, course_id, problem_url]
result = _regrade_problem_for_all_students.apply_async(task_args)
task_id = result.id
tasklog_args = {'course_id': course_id,
'task_name': 'regrade',
'task_args': problem_url,
'task_id': task_id,
'task_status': result.state,
'requester': request.user}
course_task_log = CourseTaskLog.objects.create(**tasklog_args)
return course_task_log
def _reset_problem_attempts_module_state(request, module_to_reset, module_descriptor):
# modify the problem's state
# load the state json and change state
problem_state = json.loads(module_to_reset.state)
if 'attempts' in problem_state:
old_number_of_attempts = problem_state["attempts"]
if old_number_of_attempts > 0:
problem_state["attempts"] = 0
# convert back to json and save
module_to_reset.state = json.dumps(problem_state)
module_to_reset.save()
# write out tracking info
track.views.server_track(request,
'{instructor} reset attempts from {old_attempts} to 0 for {student} '
'on problem {problem} in {course}'.format(old_attempts=old_number_of_attempts,
student=module_to_reset.student,
problem=module_to_reset.module_state_key,
instructor=request.user,
course=module_to_reset.course_id),
{},
page='idashboard')
# consider the reset to be successful, even if no update was performed. (It's just "optimized".)
return True
def _reset_problem_attempts_for_student(request, course_id, problem_url, student_identifier):
action_name = 'reset'
update_fcn = _reset_problem_attempts_module_state
return _update_problem_module_state_for_student(request, course_id, problem_url, student_identifier,
update_fcn, action_name)
def _reset_problem_attempts_for_all_students(request, course_id, problem_url):
action_name = 'reset'
update_fcn = _reset_problem_attempts_module_state
return _update_problem_module_state_for_all_students(request, course_id, problem_url,
update_fcn, action_name)
def _delete_problem_module_state(request, module_to_delete, module_descriptor):
'''
delete the state
'''
module_to_delete.delete()
return True
def _delete_problem_state_for_student(request, course_id, problem_url, student_ident):
action_name = 'deleted'
update_fcn = _delete_problem_module_state
return _update_problem_module_state_for_student(request, course_id, problem_url,
update_fcn, action_name)
def _delete_problem_state_for_all_students(request, course_id, problem_url):
action_name = 'deleted'
update_fcn = _delete_problem_module_state
return _update_problem_module_state_for_all_students(request, course_id, problem_url,
update_fcn, action_name)