def create(self, subsection, read_only=False): """ Returns the SubsectionGrade object for the student and subsection. If read_only is True, doesn't save any updates to the grades. """ self._log_event( log.debug, u"create, read_only: {0}, subsection: {1}".format( read_only, subsection.location), subsection, ) subsection_grade = self._get_bulk_cached_grade(subsection) if not subsection_grade: if assume_zero_if_absent(self.course_data.course_key): subsection_grade = ZeroSubsectionGrade(subsection, self.course_data) else: subsection_grade = CreateSubsectionGrade( subsection, self.course_data.structure, self._submissions_scores, self._csm_scores, ) if should_persist_grades(self.course_data.course_key): if read_only: self._unsaved_subsection_grades[ subsection_grade.location] = subsection_grade else: grade_model = subsection_grade.update_or_create_model( self.student) self._update_saved_subsection_grade( subsection.location, grade_model) return subsection_grade
def create(self, subsection, read_only=False, force_calculate=False): """ Returns the SubsectionGrade object for the student and subsection. If read_only is True, doesn't save any updates to the grades. force_calculate - If true, will cause this function to return a `CreateSubsectionGrade` object if no cached grade currently exists, even if the assume_zero_if_absent flag is enabled for the course. """ self._log_event( log.debug, u"create, read_only: {0}, subsection: {1}".format(read_only, subsection.location), subsection, ) subsection_grade = self._get_bulk_cached_grade(subsection) if not subsection_grade: if assume_zero_if_absent(self.course_data.course_key) and not force_calculate: subsection_grade = ZeroSubsectionGrade(subsection, self.course_data) else: subsection_grade = CreateSubsectionGrade( subsection, self.course_data.structure, self._submissions_scores, self._csm_scores, ) if should_persist_grades(self.course_data.course_key): if read_only: self._unsaved_subsection_grades[subsection_grade.location] = subsection_grade else: grade_model = subsection_grade.update_or_create_model(self.student) self._update_saved_subsection_grade(subsection.location, grade_model) return subsection_grade
def update(self, subsection, only_if_higher=None, score_deleted=False, force_update_subsections=False, persist_grade=True): """ Updates the SubsectionGrade object for the student and subsection. """ self._log_event(log.debug, u"update, subsection: {}".format(subsection.location), subsection) calculated_grade = CreateSubsectionGrade( subsection, self.course_data.structure, self._submissions_scores, self._csm_scores, ) if persist_grade and should_persist_grades(self.course_data.course_key): if only_if_higher: try: grade_model = PersistentSubsectionGrade.read_grade(self.student.id, subsection.location) except PersistentSubsectionGrade.DoesNotExist: pass else: orig_subsection_grade = ReadSubsectionGrade(subsection, grade_model, self) if not is_score_higher_or_equal( orig_subsection_grade.graded_total.earned, orig_subsection_grade.graded_total.possible, calculated_grade.graded_total.earned, calculated_grade.graded_total.possible, treat_undefined_as_zero=True, ): return orig_subsection_grade grade_model = calculated_grade.update_or_create_model( self.student, score_deleted, force_update_subsections ) self._update_saved_subsection_grade(subsection.location, grade_model) return calculated_grade
def create(self, subsection, read_only=False): """ Returns the SubsectionGrade object for the student and subsection. If read_only is True, doesn't save any updates to the grades. """ self._log_event( log.debug, u"create, read_only: {0}, subsection: {1}".format(read_only, subsection.location), subsection, ) subsection_grade = self._get_bulk_cached_grade(subsection) if not subsection_grade: if assume_zero_if_absent(self.course_data.course_key): subsection_grade = ZeroSubsectionGrade(subsection, self.course_data) else: subsection_grade = SubsectionGrade(subsection).init_from_structure( self.student, self.course_data.structure, self._submissions_scores, self._csm_scores, ) if should_persist_grades(self.course_data.course_key): if read_only: self._unsaved_subsection_grades[subsection_grade.location] = subsection_grade else: grade_model = subsection_grade.create_model(self.student) self._update_saved_subsection_grade(subsection.location, grade_model) return subsection_grade
def update(self, subsection, only_if_higher=None): """ Updates the SubsectionGrade object for the student and subsection. """ # Save ourselves the extra queries if the course does not persist # subsection grades. self._log_event(log.warning, u"update, subsection: {}".format(subsection.location), subsection) calculated_grade = SubsectionGrade(subsection).init_from_structure( self.student, self.course_data.structure, self._submissions_scores, self._csm_scores, ) if should_persist_grades(self.course_data.course_key): if only_if_higher: try: grade_model = PersistentSubsectionGrade.read_grade(self.student.id, subsection.location) except PersistentSubsectionGrade.DoesNotExist: pass else: orig_subsection_grade = SubsectionGrade(subsection).init_from_model( self.student, grade_model, self.course_data.structure, self._submissions_scores, self._csm_scores, ) if not is_score_higher_or_equal( orig_subsection_grade.graded_total.earned, orig_subsection_grade.graded_total.possible, calculated_grade.graded_total.earned, calculated_grade.graded_total.possible, ): return orig_subsection_grade grade_model = calculated_grade.update_or_create_model(self.student) self._update_saved_subsection_grade(subsection.location, grade_model) return calculated_grade
def update(self, subsection, only_if_higher=None, score_deleted=False): """ Updates the SubsectionGrade object for the student and subsection. """ self._log_event(log.debug, u"update, subsection: {}".format(subsection.location), subsection) calculated_grade = CreateSubsectionGrade( subsection, self.course_data.structure, self._submissions_scores, self._csm_scores, ) if should_persist_grades(self.course_data.course_key): if only_if_higher: try: grade_model = PersistentSubsectionGrade.read_grade(self.student.id, subsection.location) except PersistentSubsectionGrade.DoesNotExist: pass else: orig_subsection_grade = ReadSubsectionGrade(subsection, grade_model, self) if not is_score_higher_or_equal( orig_subsection_grade.graded_total.earned, orig_subsection_grade.graded_total.possible, calculated_grade.graded_total.earned, calculated_grade.graded_total.possible, ): return orig_subsection_grade grade_model = calculated_grade.update_or_create_model(self.student, score_deleted) self._update_saved_subsection_grade(subsection.location, grade_model) return calculated_grade
def _get_bulk_cached_grade(self, subsection): """ Returns the student's SubsectionGrade for the subsection, while caching the results of a bulk retrieval for the course, for future access of other subsections. Returns None if not found. """ if should_persist_grades(self.course_data.course_key): saved_subsection_grades = self._get_bulk_cached_subsection_grades() grade = saved_subsection_grades.get(subsection.location) if grade: return ReadSubsectionGrade(subsection, grade, self)
def _get_bulk_cached_grade(self, subsection): """ Returns the student's SubsectionGrade for the subsection, while caching the results of a bulk retrieval for the course, for future access of other subsections. Returns None if not found. """ if should_persist_grades(self.course_data.course_key): saved_subsection_grades = self._get_bulk_cached_subsection_grades() grade = saved_subsection_grades.get(subsection.location) if grade: return ReadSubsectionGrade(subsection, grade, self)
def update(self, subsection, only_if_higher=None, score_deleted=False, force_update_subsections=False, persist_grade=True): # lint-amnesty, pylint: disable=line-too-long """ Updates the SubsectionGrade object for the student and subsection. """ self._log_event(log.debug, u"update, subsection: {}".format(subsection.location), subsection) calculated_grade = CreateSubsectionGrade( subsection, self.course_data.structure, self._submissions_scores, self._csm_scores, ) if persist_grade and should_persist_grades( self.course_data.course_key): if only_if_higher: try: grade_model = PersistentSubsectionGrade.read_grade( self.student.id, subsection.location) except PersistentSubsectionGrade.DoesNotExist: pass else: orig_subsection_grade = ReadSubsectionGrade( subsection, grade_model, self) if not is_score_higher_or_equal( orig_subsection_grade.graded_total.earned, orig_subsection_grade.graded_total.possible, calculated_grade.graded_total.earned, calculated_grade.graded_total.possible, treat_undefined_as_zero=True, ): return orig_subsection_grade grade_model = calculated_grade.update_or_create_model( self.student, score_deleted, force_update_subsections) self._update_saved_subsection_grade(subsection.location, grade_model) if settings.FEATURES.get( 'ENABLE_COURSE_ASSESSMENT_GRADE_CHANGE_SIGNAL'): COURSE_ASSESSMENT_GRADE_CHANGED.send( sender=self, course_id=self.course_data.course_key, user=self.student, subsection_id=calculated_grade.location, subsection_grade=calculated_grade.graded_total.earned) return calculated_grade
def _get_bulk_cached_grade(self, subsection): """ Returns the student's SubsectionGrade for the subsection, while caching the results of a bulk retrieval for the course, for future access of other subsections. Returns None if not found. """ if should_persist_grades(self.course_data.course_key): saved_subsection_grades = self._get_bulk_cached_subsection_grades() subsection_grade = saved_subsection_grades.get(subsection.location) if subsection_grade: return SubsectionGrade(subsection).init_from_model( self.student, subsection_grade, self.course_data.structure, self._submissions_scores, self._csm_scores, )
def update(self, subsection, only_if_higher=None, score_deleted=False): """ Updates the SubsectionGrade object for the student and subsection. """ # Save ourselves the extra queries if the course does not persist # subsection grades. self._log_event(log.warning, u"update, subsection: {}".format(subsection.location), subsection) calculated_grade = SubsectionGrade(subsection).init_from_structure( self.student, self.course_data.structure, self._submissions_scores, self._csm_scores, ) if should_persist_grades(self.course_data.course_key): if only_if_higher: try: grade_model = PersistentSubsectionGrade.read_grade( self.student.id, subsection.location) except PersistentSubsectionGrade.DoesNotExist: pass else: orig_subsection_grade = SubsectionGrade( subsection).init_from_model( self.student, grade_model, self.course_data.structure, self._submissions_scores, self._csm_scores, ) if not is_score_higher_or_equal( orig_subsection_grade.graded_total.earned, orig_subsection_grade.graded_total.possible, calculated_grade.graded_total.earned, calculated_grade.graded_total.possible, ): return orig_subsection_grade grade_model = calculated_grade.update_or_create_model( self.student, score_deleted) self._update_saved_subsection_grade(subsection.location, grade_model) return calculated_grade
def _get_bulk_cached_grade(self, subsection): """ Returns the student's SubsectionGrade for the subsection, while caching the results of a bulk retrieval for the course, for future access of other subsections. Returns None if not found. """ if should_persist_grades(self.course_data.course_key): saved_subsection_grades = self._get_bulk_cached_subsection_grades() subsection_grade = saved_subsection_grades.get(subsection.location) if subsection_grade: return SubsectionGrade(subsection).init_from_model( self.student, subsection_grade, self.course_data.structure, self._submissions_scores, self._csm_scores, )
def handle(self, *args, **options): # build a list of CourseKeys from any course IDs given course_key_list = [] for course_id in options["course_id"]: try: course_key_list.append(CourseKey.from_string(course_id)) except (InvalidKeyError, ValueError): log.error("Invalid course ID: %s", course_id) sys.exit(1) # get the Learndot:edX course mappings course_mappings = CourseMapping.objects.all() if course_key_list: course_mappings = course_mappings.filter( edx_course_key__in=course_key_list) if not course_mappings.exists(): if options["course_id"]: log.error( "No course mappings were found for your specified course IDs." ) else: log.error("No course mappings were found.") sys.exit(1) learndot_client = LearndotAPIClient() # for each mapped course, go through its enrollments, get the # course grade for each enrolled user, and if the user has passed, # update the Learndot enrolment date_settings = { 'TIMEZONE': settings.TIME_ZONE, 'RETURN_AS_TIMEZONE_AWARE': True } end_enrollments_date = dateparser.parse(options["end"], settings=date_settings) start_enrolments_date = dateparser.parse(options["start"], settings=date_settings) for cm in course_mappings: try: course = get_course(cm.edx_course_key) except (InvalidKeyError, ValueError): log.error("Invalid edX course found in map: %s", cm.edx_course_key) continue log.info("Processing enrollments in course %s", cm.edx_course_key) enrollments = CourseEnrollment.objects.filter( course_id=cm.edx_course_key, created__range=[start_enrolments_date, end_enrollments_date], ) if options["users"]: enrollments = enrollments.filter( user__username__in=options["users"]) for enrollment in enrollments: contact_id = learndot_client.get_contact_id(enrollment.user) if not contact_id: log.info( "Not setting enrolment status for user %s in course %s, because contact_id is None .", enrollment.user, cm.edx_course_key) continue # # Disturbingly enough, if persistent grades are not # enabled, it just takes looking up the grade to get # the Learndot enrolment updated, because when # CourseGradeFactory constructs the CourseGrade in its # read() method, it will actually call its _update() # method, which sends the COURSE_GRADE_NOW_PASSED # signal, which of course fires # edxlearndot.signals.listen_for_passing_grade. # # However, if the edX instance has persistent course # grades enabled, the CourseGrade doesn't have to be # constructed, so the signal isn't fired, and we have # to explicitly update Learndot. # course_grade = CourseGradeFactory().read( enrollment.user, course) if not course_grade: log.info( "Not setting enrolment status for user %s in course %s, because no grade is available.", enrollment.user, cm.edx_course_key) elif course_grade.passed and should_persist_grades( cm.edx_course_key): log.info( "Grades are persistent; explicitly updating Learndot enrolment." ) learndot_client.check_if_enrolment_and_set_status_to_passed( contact_id, cm.learndot_component_id, unconditional=options["unconditional"])