Commit 8b03ad39 by Matt Drayer

Decoupled entrance exam scoring from milestone fulfillment

parent a9127c18
...@@ -7,6 +7,8 @@ from django.conf import settings ...@@ -7,6 +7,8 @@ from django.conf import settings
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from courseware.models import StudentModule
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from milestones.api import ( from milestones.api import (
...@@ -148,6 +150,35 @@ def fulfill_course_milestone(course_key, user): ...@@ -148,6 +150,35 @@ def fulfill_course_milestone(course_key, user):
add_user_milestone({'id': user.id}, milestone) add_user_milestone({'id': user.id}, milestone)
def calculate_entrance_exam_score(user, course_descriptor, exam_modules):
"""
Calculates the score (percent) of the entrance exam using the provided modules
"""
exam_module_ids = [exam_module.location for exam_module in exam_modules]
student_modules = StudentModule.objects.filter(
student=user,
course_id=course_descriptor.id,
module_state_key__in=exam_module_ids,
)
exam_pct = 0
if student_modules:
module_pcts = []
ignore_categories = ['course', 'chapter', 'sequential', 'vertical']
for module in exam_modules:
if module.graded and module.category not in ignore_categories:
module_pct = 0
try:
student_module = student_modules.get(module_state_key=module.location)
if student_module.max_grade:
module_pct = student_module.grade / student_module.max_grade
module_pcts.append(module_pct)
except StudentModule.DoesNotExist:
pass
if module_pcts:
exam_pct = sum(module_pcts) / float(len(module_pcts))
return exam_pct
def milestones_achieved_by_user(user, namespace): def milestones_achieved_by_user(user, namespace):
""" """
It would fetch list of milestones completed by user It would fetch list of milestones completed by user
......
...@@ -15,6 +15,7 @@ import dogstats_wrapper as dog_stats_api ...@@ -15,6 +15,7 @@ import dogstats_wrapper as dog_stats_api
from courseware import courses from courseware import courses
from courseware.model_data import FieldDataCache from courseware.model_data import FieldDataCache
from student.models import anonymous_id_for_user from student.models import anonymous_id_for_user
from util.module_utils import yield_dynamic_descriptor_descendents
from xmodule import graders from xmodule import graders
from xmodule.graders import Score from xmodule.graders import Score
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
...@@ -22,7 +23,6 @@ from xmodule.modulestore.exceptions import ItemNotFoundError ...@@ -22,7 +23,6 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.util.duedate import get_extended_due_date from xmodule.util.duedate import get_extended_due_date
from .models import StudentModule from .models import StudentModule
from .module_render import get_module_for_descriptor from .module_render import get_module_for_descriptor
from .module_utils import yield_dynamic_descriptor_descendents
from submissions import api as sub_api # installed from the edx-submissions repository from submissions import api as sub_api # installed from the edx-submissions repository
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
......
...@@ -25,7 +25,6 @@ from courseware.models import StudentModule ...@@ -25,7 +25,6 @@ from courseware.models import StudentModule
from lms.djangoapps.lms_xblock.field_data import LmsFieldData from lms.djangoapps.lms_xblock.field_data import LmsFieldData
from lms.djangoapps.lms_xblock.runtime import LmsModuleSystem, unquote_slashes, quote_slashes from lms.djangoapps.lms_xblock.runtime import LmsModuleSystem, unquote_slashes, quote_slashes
from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig
from .module_utils import yield_dynamic_descriptor_descendents
from edxmako.shortcuts import render_to_string from edxmako.shortcuts import render_to_string
from eventtracking import tracker from eventtracking import tracker
from psychometrics.psychoanalyze import make_psychometrics_data_update_handler from psychometrics.psychoanalyze import make_psychometrics_data_update_handler
...@@ -59,7 +58,8 @@ from util.sandboxing import can_execute_unsafe_code, get_python_lib_zip ...@@ -59,7 +58,8 @@ from util.sandboxing import can_execute_unsafe_code, get_python_lib_zip
if settings.FEATURES.get('MILESTONES_APP', False): if settings.FEATURES.get('MILESTONES_APP', False):
from milestones import api as milestones_api from milestones import api as milestones_api
from milestones.exceptions import InvalidMilestoneRelationshipTypeException from milestones.exceptions import InvalidMilestoneRelationshipTypeException
from util.milestones_helpers import serialize_user from util.milestones_helpers import serialize_user, calculate_entrance_exam_score
from util.module_utils import yield_dynamic_descriptor_descendents
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -382,7 +382,21 @@ def get_module_system_for_user(user, field_data_cache, ...@@ -382,7 +382,21 @@ def get_module_system_for_user(user, field_data_cache,
request_token=request_token, request_token=request_token,
) )
def _fulfill_content_milestones(course_key, content_key, user_id): # pylint: disable=unused-argument def _calculate_entrance_exam_score(user, course_descriptor):
"""
Internal helper to calculate a user's score for a course's entrance exam
"""
exam_key = UsageKey.from_string(course_descriptor.entrance_exam_id)
exam_descriptor = modulestore().get_item(exam_key)
exam_module_generators = yield_dynamic_descriptor_descendents(
exam_descriptor,
inner_get_module
)
exam_modules = [module for module in exam_module_generators]
exam_score = calculate_entrance_exam_score(user, course_descriptor, exam_modules)
return exam_score
def _fulfill_content_milestones(user, course_key, content_key):
""" """
Internal helper to handle milestone fulfillments for the specified content module Internal helper to handle milestone fulfillments for the specified content module
""" """
...@@ -395,30 +409,9 @@ def get_module_system_for_user(user, field_data_cache, ...@@ -395,30 +409,9 @@ def get_module_system_for_user(user, field_data_cache,
entrance_exam_enabled = getattr(course, 'entrance_exam_enabled', False) entrance_exam_enabled = getattr(course, 'entrance_exam_enabled', False)
in_entrance_exam = getattr(content, 'in_entrance_exam', False) in_entrance_exam = getattr(content, 'in_entrance_exam', False)
if entrance_exam_enabled and in_entrance_exam: if entrance_exam_enabled and in_entrance_exam:
exam_key = UsageKey.from_string(course.entrance_exam_id) exam_pct = _calculate_entrance_exam_score(user, course)
exam_descriptor = modulestore().get_item(exam_key)
exam_modules = yield_dynamic_descriptor_descendents(
exam_descriptor,
inner_get_module
)
ignore_categories = ['course', 'chapter', 'sequential', 'vertical']
module_pcts = []
exam_pct = 0
for module in exam_modules:
if module.graded and module.category not in ignore_categories:
module_pct = 0
try:
student_module = StudentModule.objects.get(
module_state_key=module.scope_ids.usage_id,
student_id=user_id
)
if student_module.max_grade:
module_pct = student_module.grade / student_module.max_grade
except StudentModule.DoesNotExist:
pass
module_pcts.append(module_pct)
exam_pct = sum(module_pcts) / float(len(module_pcts))
if exam_pct >= course.entrance_exam_minimum_score_pct: if exam_pct >= course.entrance_exam_minimum_score_pct:
exam_key = UsageKey.from_string(course.entrance_exam_id)
relationship_types = milestones_api.get_milestone_relationship_types() relationship_types = milestones_api.get_milestone_relationship_types()
content_milestones = milestones_api.get_course_content_milestones( content_milestones = milestones_api.get_course_content_milestones(
course_key, course_key,
...@@ -426,7 +419,7 @@ def get_module_system_for_user(user, field_data_cache, ...@@ -426,7 +419,7 @@ def get_module_system_for_user(user, field_data_cache,
relationship=relationship_types['FULFILLS'] relationship=relationship_types['FULFILLS']
) )
# Add each milestone to the user's set... # Add each milestone to the user's set...
user = {'id': user_id} user = {'id': user.id}
for milestone in content_milestones: for milestone in content_milestones:
milestones_api.add_user_milestone(user, milestone) milestones_api.add_user_milestone(user, milestone)
...@@ -470,9 +463,9 @@ def get_module_system_for_user(user, field_data_cache, ...@@ -470,9 +463,9 @@ def get_module_system_for_user(user, field_data_cache,
# thanks to the updated grading information that was just submitted # thanks to the updated grading information that was just submitted
if settings.FEATURES.get('MILESTONES_APP', False): if settings.FEATURES.get('MILESTONES_APP', False):
_fulfill_content_milestones( _fulfill_content_milestones(
user,
course_id, course_id,
descriptor.location, descriptor.location,
user_id
) )
def publish(block, event_type, event): def publish(block, event_type, event):
......
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