diff --git a/common/djangoapps/util/milestones_helpers.py b/common/djangoapps/util/milestones_helpers.py index 16094bfa39..ef5f635783 100644 --- a/common/djangoapps/util/milestones_helpers.py +++ b/common/djangoapps/util/milestones_helpers.py @@ -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 diff --git a/lms/djangoapps/courseware/module_utils.py b/common/djangoapps/util/module_utils.py similarity index 100% rename from lms/djangoapps/courseware/module_utils.py rename to common/djangoapps/util/module_utils.py diff --git a/lms/djangoapps/courseware/grades.py b/lms/djangoapps/courseware/grades.py index e100916fbd..bd8cb577f2 100644 --- a/lms/djangoapps/courseware/grades.py +++ b/lms/djangoapps/courseware/grades.py @@ -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 diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 4a0ae1e8a4..9ecf00a9ad 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -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):