Merge pull request #6782 from edx/mattdrayer/generalize-entrance-exam-scoring
Decoupled entrance exam scoring from milestone fulfillment
This commit is contained in:
@@ -7,6 +7,8 @@ from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from courseware.models import StudentModule
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from milestones.api import (
|
||||
@@ -148,6 +150,35 @@ def fulfill_course_milestone(course_key, user):
|
||||
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):
|
||||
"""
|
||||
It would fetch list of milestones completed by user
|
||||
|
||||
@@ -15,6 +15,7 @@ import dogstats_wrapper as dog_stats_api
|
||||
from courseware import courses
|
||||
from courseware.model_data import FieldDataCache
|
||||
from student.models import anonymous_id_for_user
|
||||
from util.module_utils import yield_dynamic_descriptor_descendents
|
||||
from xmodule import graders
|
||||
from xmodule.graders import Score
|
||||
from xmodule.modulestore.django import modulestore
|
||||
@@ -22,7 +23,6 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from xmodule.util.duedate import get_extended_due_date
|
||||
from .models import StudentModule
|
||||
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 opaque_keys import InvalidKeyError
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ from courseware.models import StudentModule
|
||||
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.models import XBlockAsidesConfig
|
||||
from .module_utils import yield_dynamic_descriptor_descendents
|
||||
from edxmako.shortcuts import render_to_string
|
||||
from eventtracking import tracker
|
||||
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
|
||||
if settings.FEATURES.get('MILESTONES_APP', False):
|
||||
from milestones import api as milestones_api
|
||||
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__)
|
||||
|
||||
@@ -382,7 +382,21 @@ def get_module_system_for_user(user, field_data_cache,
|
||||
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
|
||||
"""
|
||||
@@ -395,30 +409,9 @@ def get_module_system_for_user(user, field_data_cache,
|
||||
entrance_exam_enabled = getattr(course, 'entrance_exam_enabled', False)
|
||||
in_entrance_exam = getattr(content, 'in_entrance_exam', False)
|
||||
if entrance_exam_enabled and in_entrance_exam:
|
||||
exam_key = UsageKey.from_string(course.entrance_exam_id)
|
||||
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))
|
||||
exam_pct = _calculate_entrance_exam_score(user, course)
|
||||
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()
|
||||
content_milestones = milestones_api.get_course_content_milestones(
|
||||
course_key,
|
||||
@@ -426,7 +419,7 @@ def get_module_system_for_user(user, field_data_cache,
|
||||
relationship=relationship_types['FULFILLS']
|
||||
)
|
||||
# Add each milestone to the user's set...
|
||||
user = {'id': user_id}
|
||||
user = {'id': user.id}
|
||||
for milestone in content_milestones:
|
||||
milestones_api.add_user_milestone(user, milestone)
|
||||
|
||||
@@ -470,9 +463,9 @@ def get_module_system_for_user(user, field_data_cache,
|
||||
# thanks to the updated grading information that was just submitted
|
||||
if settings.FEATURES.get('MILESTONES_APP', False):
|
||||
_fulfill_content_milestones(
|
||||
user,
|
||||
course_id,
|
||||
descriptor.location,
|
||||
user_id
|
||||
)
|
||||
|
||||
def publish(block, event_type, event):
|
||||
|
||||
Reference in New Issue
Block a user