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,29 @@ 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_latest_task(self):
"""Return latest task object."""

last_task_state = self.task_states[-1]
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 ever possible that self.task_states is None or []?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, this is done wrong.

last_task_xml = self.task_xml[-1]
return self.create_task(last_task_state, last_task_xml)

def reset_task_state(self, message=""):
"""
Expand Down
38 changes: 38 additions & 0 deletions lms/djangoapps/instructor/management/commands/openended_post.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/python
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be: #!/usr/bin/env python

#
# django management command: dump grades to csv files
# for use by batch processes

from ...utils import get_descriptor, read_csv, get_affected_students_from_ids, get_module_for_student
from django.core.management.base import BaseCommand


class Command(BaseCommand):
help = "Usage: posts problem to xqueue \n"
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't explain what the arguments are.


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

print "args = ", args
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like a debugging leave-behind.


if len(args) > 0:
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be if len(args) > 2, since you're using args[0], args[1], args[2]?

course_id = args[0]
location = args[1]
affected_students_ids = read_csv(args[2])
Copy link
Contributor

Choose a reason for hiding this comment

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

args is checked for more than none, but then we read 3 arguments for it, which won't be a good way to handle wrong input. There are a variety of ways to parse input that will be more robust.

else:
print self.help
return

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

affected_students = get_affected_students_from_ids(affected_students_ids)

for student in affected_students:
try:
module = get_module_for_student(student, course_id, location)
latest_task = module._xmodule.child_module.get_latest_task()
latest_task.send_to_grader(latest_task.latest_answer(), latest_task.system)
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?

55 changes: 55 additions & 0 deletions lms/djangoapps/instructor/management/commands/openended_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/python
#
# Admin command for open ended problems.

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 location \n"

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

print "args = ", args

if len(args) > 0:
Copy link
Contributor

Choose a reason for hiding this comment

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

same as before.

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_latest_task()
stats[latest_task.child_state] += 1

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

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 request is None:
request = DummyRequest()
request.user = student
request.session = {}
if isinstance(course, str):
course = get_course_by_id(course)
if course is None:
return None
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_affected_students_from_ids(affected_students_ids):
"""Return affected students form ids list."""

affected_students = User.objects.filter(
id__in=affected_students_ids,
courseenrollment__is_active=1
).prefetch_related("groups").order_by('username')
return affected_students


def read_csv(path_to_csv):
"""
reads a csv and returns a list
"""

affected_students_ids = []
with open(path_to_csv) as csv_file:
csv_reader = csv.reader(csv_file)
for row in csv_reader:
affected_students_ids.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 affected_students_ids


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

META = {}

def __init__(self):
return

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

def is_secure(self):
return False