Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Waheed/openended problems commands #1946

Closed
wants to merge 10 commits into from
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,32 @@ def fix_invalid_state(self):
last_completed_child = next((i for i, child in reversed(list(enumerate(children))) if child['child_state'] == self.DONE), 0)
self.current_task_number = min(last_completed_child + 1, len(best_task_states) - 1)

def create_task(self, task_state, task_xml):
"""Create task object for given task state and task xml."""

tag_name = self.get_tag_name(task_xml)
children = self.child_modules()
task_descriptor = children['descriptors'][tag_name](self.system)
task_parsed_xml = task_descriptor.definition_from_xml(etree.fromstring(task_xml), self.system)
task = children['modules'][tag_name](
self.system,
self.location,
task_parsed_xml,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we call this variable definition for more clarity?

task_descriptor,
self.static_data,
instance_state=task_state,
)
return task

def get_current_task(self):
"""Return current task object."""

if len(self.task_states) > 0:
current_task_index = len(self.task_states) - 1
current_task_state = self.task_states[current_task_index]
current_task_xml = self.task_xml[current_task_index]
return self.create_task(current_task_state, current_task_xml)
return None

def reset_task_state(self, message=""):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,10 @@ def _parse_score_msg(self, score_msg, system, join_feedback=True):
'grader_ids': [0],
'submission_ids': [0],
}

if score_msg == "":
return fail

try:
score_result = json.loads(score_msg)
except (TypeError, ValueError):
Expand Down
55 changes: 55 additions & 0 deletions lms/djangoapps/instructor/management/commands/openended_post.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from xmodule.open_ended_grading_classes.openendedchild import OpenEndedChild
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for removing the hashbang, but why remove the module comment? A docstring at the top would be great.

from ...utils import get_descriptor, create_list_from_csv, get_users_from_ids, get_module_for_student
from django.core.management.base import BaseCommand


class Command(BaseCommand):
""" """

help = "Usage: openended_post <course_id> <problem_location> <student_ids.csv> \n"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would we get this list of students?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@feanil what's the best way to do that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I was wondering what the criteria is for this list. Any query run to get a list of affected students externally should probably just be run here in code instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the problem is is that we have to join over queries from LMS and the ORA dbs, and I don't know how to do that with the django orm

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that is what I was originally wondering about, what is an example query that would result in the list needed for this command? Is this query documented somewhere?


def handle(self, *args, **options):

if len(args) == 3:
course_id = args[0]
location = args[1]
students_ids = create_list_from_csv(args[2])
else:
print self.help
return

descriptor = get_descriptor(course_id, location)
if descriptor is None:
print "Location not found in course"
return

students = get_users_from_ids(students_ids)
print "Number of students: {0}".format(students.count())

for student in students:
print "------Student {0}:{1}------".format(student.id, student.username)
try:
module = get_module_for_student(student, course_id, location)
if module is None:
print "WARNING: No state found."
continue

latest_task = module._xmodule.child_module.get_current_task()
if latest_task is None:
print "WARNING: No state found."
continue

latest_task_state = latest_task.child_state
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is latest_task.child_state ok to call if latest_task is None?


if latest_task_state == OpenEndedChild.INITIAL:
print "WARNING: No submission."
elif latest_task_state == OpenEndedChild.POST_ASSESSMENT or latest_task_state == OpenEndedChild.DONE:
print "WARNING: Submission already graded."
elif latest_task_state == OpenEndedChild.ASSESSING:
latest_answer = latest_task.latest_answer()
latest_task.send_to_grader(latest_answer, latest_task.system)
print "Successfully sent to grader."
else:
print "WARNING: Invalid task_state: {0}".format(latest_task_state)
except Exception as err:
print err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it appropriate to continue with more students if an exception happens? Are you sure that print err will give you enough information? What kind of errors might happen here? What will the person running the script do with the error reports?

51 changes: 51 additions & 0 deletions lms/djangoapps/instructor/management/commands/openended_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from xmodule.open_ended_grading_classes.openendedchild import OpenEndedChild
from ...utils import get_descriptor, get_module_for_student, get_enrolled_students
from django.core.management.base import BaseCommand


class Command(BaseCommand):
"""Admin command for open ended problems."""

help = "Usage: openended_stats <course_id> <problem_location> \n"

def handle(self, *args, **options):
"""Handler for command."""

print "args = ", args

if len(args) == 2:
course_id = args[0]
location = args[1]
else:
print self.help
return

descriptor = get_descriptor(course_id, location)
if descriptor is None:
print "Location not found in course"
return

enrolled_students = get_enrolled_students(course_id)
print "Total students enrolled: {0}".format(enrolled_students.count())

self.get_state_counts(enrolled_students, course_id, location)

def get_state_counts(self, students, course_id, location):
"""Print stats of students."""

stats = {
OpenEndedChild.INITIAL: 0,
OpenEndedChild.ASSESSING: 0,
OpenEndedChild.POST_ASSESSMENT: 0,
OpenEndedChild.DONE: 0
}

for index, student in enumerate(students):
if index % 100 == 0:
print "{0} students processed".format(index)

module = get_module_for_student(student, course_id, location)
latest_task = module._xmodule.child_module.get_current_task()
stats[latest_task.child_state] += 1

print stats
121 changes: 121 additions & 0 deletions lms/djangoapps/instructor/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@

from courseware.model_data import FieldDataCache
from courseware.module_render import get_module_for_descriptor
from courseware.courses import get_course_by_id
import csv
from django.contrib.auth.models import User


def get_descriptor(course_id, location):
"""Find descriptor for the location in the course."""

course = get_course_by_id(course_id)
grading_context = course.grading_context
descriptor = None
for section_format, sections in grading_context['graded_sections'].iteritems():
try:
for section in sections:
section_descriptor = section['section_descriptor']
descriptor = find_descriptor_in_children(section_descriptor, location)
if descriptor:
break

if descriptor:
break

except Exception as err:
print err.message

return descriptor


def find_descriptor_in_children(descriptor, location):
"""Recursively look for location in descriptors children."""

try:
if descriptor.id == location:
return descriptor
children = descriptor.get_children()
except:
children = []
for module_descriptor in children:
child_descriptor = find_descriptor_in_children(module_descriptor, location)
if child_descriptor:
return child_descriptor
return None


def create_module(student, course, descriptor, request):
"""Create module for student from descriptor."""

field_data_cache = FieldDataCache([descriptor], course.id, student)
return get_module_for_descriptor(student, request, descriptor, field_data_cache, course.id)


def get_module_for_student(student, course, location, request=None):
"""Return module for student from location."""

if isinstance(student, str):
try:
student = User.objects.get(username=student)
except User.DoesNotExist:
return None

if isinstance(course, str):
course = get_course_by_id(course)
if course is None:
return None

if request is None:
request = DummyRequest()
request.user = student
request.session = {}

descriptor = get_descriptor(course.id, location)
module = create_module(student, course, descriptor, request)
return module


def get_enrolled_students(course_id):
"""Return enrolled students for course."""

enrolled_students = User.objects.filter(
courseenrollment__course_id=course_id,
courseenrollment__is_active=1
).prefetch_related("groups").order_by('username')
return enrolled_students


def get_users_from_ids(ids):
"""Return students from a list of ids."""

users = User.objects.filter(
id__in=ids,
).order_by('username')
return users

def create_list_from_csv(path_to_csv):
"""Read a csv and return items in a list."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function isn't properly named: it returns a list of the first column in the csv. Will this be used with csv files with more than one column?


items = []
with open(path_to_csv) as csv_file:
csv_reader = csv.reader(csv_file)
for row in csv_reader:
items.append(row[0])

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A list comprehension would be good here: items = [row[0] for row in csv_reader]

return items


class DummyRequest(object):
"""Dummy request"""

META = {}

def __init__(self):
return

def get_host(self):
return 'edx.mit.edu'

def is_secure(self):
return False