-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Changes from 6 commits
ef1f55a
b140d66
0d31772
1c13360
f47e7cc
c599dfd
9891acb
627cb9e
fce9c45
0b50613
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
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] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it ever possible that self.task_states is There was a problem hiding this comment. Choose a reason for hiding this commentThe 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=""): | ||
""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
#!/usr/bin/env python | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, my mistake. Actually, management commands shouldn't have shebang lines at all: they are not executable directly, and must be imported. |
||
# | ||
# Command to post student submissions to ORA | ||
|
||
from xmodule.open_ended_grading_classes.openendedchild import OpenEndedChild | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How would we get this list of students? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @feanil what's the best way to do that? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 "No state found." | ||
continue | ||
|
||
latest_task = module._xmodule.child_module.get_latest_task() | ||
if latest_task is None: | ||
print "State is invalid." | ||
latest_task_state = latest_task.child_state | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is |
||
|
||
if latest_task_state == OpenEndedChild.INITIAL: | ||
print "No submission." | ||
elif latest_task_state == OpenEndedChild.POST_ASSESSMENT or latest_task_state == OpenEndedChild.DONE: | ||
print "Submission already graded." | ||
elif latest_task_state == OpenEndedChild.ASSESSING: | ||
print "Sending submission to grader." | ||
latest_task.send_to_grader(latest_task.latest_answer(), latest_task.system) | ||
else: | ||
print "Invalid task_state: {0}".format(latest_task_state) | ||
except Exception as err: | ||
print err | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
else: | ||
print "Successfully sent to grader." |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
#!/usr/bin/env python | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove the shebang line. |
||
# | ||
# Command to get statistics about an openended problem. | ||
|
||
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_latest_task() | ||
stats[latest_task.child_state] += 1 | ||
|
||
print stats |
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.""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A list comprehension would be good here: |
||
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 |
There was a problem hiding this comment.
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?