def add_course_requirements(self): """ Add requirements to course. """ requirements = ({ "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 } }, { "namespace": "new_grade", "name": "new_grade", "display_name": "new_grade", "criteria": { "min_grade": 0.8 }, }) for i, requirement in enumerate(requirements): credit_requirement, _ = CreditRequirement.add_or_update_course_requirement( self.credit_course, requirement, i) CreditRequirementStatus.add_or_update_requirement_status( self.old_username, credit_requirement, "satisfied", {"final_grade": 0.95})
def remove_credit_requirement_status(username, course_key, req_namespace, req_name): """ Remove the user's requirement status. This will remove the record from the credit requirement status table. The user will still be eligible for the credit in a course. Args: username (str): Username of the user course_key (CourseKey): Identifier for the course associated with the requirement. req_namespace (str): Namespace of the requirement (e.g. "grade" or "reverification") req_name (str): Name of the requirement (e.g. "grade" or the location of the ICRV XBlock) Example: >>> remove_credit_requirement_status( "staff", CourseKey.from_string("course-v1-edX-DemoX-1T2015"), "reverification", "i4x://edX/DemoX/edx-reverification-block/assessment_uuid". ) """ # Find the requirement we're trying to remove req_to_remove = CreditRequirement.get_course_requirements(course_key, namespace=req_namespace, name=req_name) # If we can't find the requirement, then the most likely explanation # is that there was a lag removing the credit requirements after the course # was published. We *could* attempt to remove the requirement here, # but that could cause serious performance issues if many users attempt to # lock the row at the same time. # Instead, we skip removing the requirement and log an error. if not req_to_remove: log.error( ( u'Could not remove credit requirement in course "%s" ' u'with namespace "%s" and name "%s" ' u'because the requirement does not exist. ' ), unicode(course_key), req_namespace, req_name ) return # Remove the requirement status CreditRequirementStatus.remove_requirement_status( username, req_to_remove )
def post(self, request): """ POST /api/user/v1/accounts/retire_misc/ { 'username': '******' } Retires the user with the given username in the LMS. """ username = request.data['username'] try: retirement = UserRetirementStatus.get_retirement_for_retirement_action( username) RevisionPluginRevision.retire_user(retirement.user) ArticleRevision.retire_user(retirement.user) PendingNameChange.delete_by_user_value(retirement.user, field='user') PasswordHistory.retire_user(retirement.user.id) course_enrollments = CourseEnrollment.objects.filter( user=retirement.user) ManualEnrollmentAudit.retire_manual_enrollments( course_enrollments, retirement.retired_email) CreditRequest.retire_user(retirement) ApiAccessRequest.retire_user(retirement.user) CreditRequirementStatus.retire_user(retirement) # This signal allows code in higher points of LMS to retire the user as necessary USER_RETIRE_LMS_MISC.send(sender=self.__class__, user=retirement.user) # This signal allows code in higher points of LMS to unsubscribe the user # from various types of mailings. USER_RETIRE_MAILINGS.send(sender=self.__class__, email=retirement.original_email, new_email=retirement.retired_email, user=retirement.user) except UserRetirementStatus.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except RetirementStateError as exc: return Response(text_type(exc), status=status.HTTP_400_BAD_REQUEST) except Exception as exc: # pylint: disable=broad-except return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response(status=status.HTTP_204_NO_CONTENT)
def get_credit_requirement_status(course_key, username, namespace=None, name=None): """ Retrieve the user's status for each credit requirement in the course. Args: course_key (CourseKey): The identifier for course username (str): The identifier of the user Example: >>> get_credit_requirement_status("course-v1-edX-DemoX-1T2015", "john") [ { "namespace": "reverification", "name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid", "display_name": "In Course Reverification", "criteria": {}, "reason": {}, "status": "failed", "status_date": "2015-06-26 07:49:13", }, { "namespace": "proctored_exam", "name": "i4x://edX/DemoX/proctoring-block/final_uuid", "display_name": "Proctored Mid Term Exam", "criteria": {}, "reason": {}, "status": "satisfied", "status_date": "2015-06-26 11:07:42", }, { "namespace": "grade", "name": "i4x://edX/DemoX/proctoring-block/final_uuid", "display_name": "Minimum Passing Grade", "criteria": {"min_grade": 0.8}, "reason": {"final_grade": 0.95}, "status": "satisfied", "status_date": "2015-06-26 11:07:44", }, ] Returns: list of requirement statuses """ requirements = CreditRequirement.get_course_requirements(course_key, namespace=namespace, name=name) requirement_statuses = CreditRequirementStatus.get_statuses(requirements, username) requirement_statuses = dict((o.requirement, o) for o in requirement_statuses) statuses = [] for requirement in requirements: requirement_status = requirement_statuses.get(requirement) statuses.append({ "namespace": requirement.namespace, "name": requirement.name, "display_name": requirement.display_name, "criteria": requirement.criteria, "reason": requirement_status.reason if requirement_status else None, "status": requirement_status.status if requirement_status else None, "status_date": requirement_status.modified if requirement_status else None, }) return statuses
def post(self, request): """ POST /api/user/v1/accounts/retire_misc/ { 'username': '******' } Retires the user with the given username in the LMS. """ username = request.data['username'] try: retirement = UserRetirementStatus.get_retirement_for_retirement_action(username) RevisionPluginRevision.retire_user(retirement.user) ArticleRevision.retire_user(retirement.user) PendingNameChange.delete_by_user_value(retirement.user, field='user') PasswordHistory.retire_user(retirement.user.id) course_enrollments = CourseEnrollment.objects.filter(user=retirement.user) ManualEnrollmentAudit.retire_manual_enrollments(course_enrollments, retirement.retired_email) CreditRequest.retire_user(retirement.original_username, retirement.retired_username) ApiAccessRequest.retire_user(retirement.user) CreditRequirementStatus.retire_user(retirement.user.username) # This signal allows code in higher points of LMS to retire the user as necessary USER_RETIRE_LMS_MISC.send(sender=self.__class__, user=retirement.user) # This signal allows code in higher points of LMS to unsubscribe the user # from various types of mailings. USER_RETIRE_MAILINGS.send( sender=self.__class__, email=retirement.original_email, new_email=retirement.retired_email, user=retirement.user ) except UserRetirementStatus.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except RetirementStateError as exc: return Response(text_type(exc), status=status.HTTP_400_BAD_REQUEST) except Exception as exc: # pylint: disable=broad-except return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response(status=status.HTTP_204_NO_CONTENT)
def post(self, request): """ POST /api/user/v1/accounts/retire_misc/ { 'username': '******' } Retires the user with the given username in the LMS. """ username = request.data['username'] if is_username_retired(username): return Response(status=status.HTTP_404_NOT_FOUND) try: retirement = UserRetirementStatus.get_retirement_for_retirement_action( username) RevisionPluginRevision.retire_user(retirement.user) ArticleRevision.retire_user(retirement.user) PendingNameChange.delete_by_user_value(retirement.user, field='user') PasswordHistory.retire_user(retirement.user.id) course_enrollments = CourseEnrollment.objects.filter( user=retirement.user) ManualEnrollmentAudit.retire_manual_enrollments( course_enrollments, retirement.retired_email) CreditRequest.retire_user(retirement.original_username, retirement.retired_username) ApiAccessRequest.retire_user(retirement.user) CreditRequirementStatus.retire_user(retirement.user.username) SurveyAnswer.retire_user(retirement.user.id) except UserRetirementStatus.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except RetirementStateError as exc: return Response(text_type(exc), status=status.HTTP_400_BAD_REQUEST) except Exception as exc: # pylint: disable=broad-except return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response(status=status.HTTP_204_NO_CONTENT)
def get_credit_requirement_status(course_key, username, namespace=None, name=None): """ Retrieve the user's status for each credit requirement in the course. Args: course_key (CourseKey): The identifier for course username (str): The identifier of the user Example: >>> get_credit_requirement_status("course-v1-edX-DemoX-1T2015", "john") [ { "namespace": "proctored_exam", "name": "i4x://edX/DemoX/proctoring-block/final_uuid", "display_name": "Proctored Mid Term Exam", "criteria": {}, "reason": {}, "status": "satisfied", "status_date": "2015-06-26 11:07:42", "order": 1, }, { "namespace": "grade", "name": "i4x://edX/DemoX/proctoring-block/final_uuid", "display_name": "Minimum Passing Grade", "criteria": {"min_grade": 0.8}, "reason": {"final_grade": 0.95}, "status": "satisfied", "status_date": "2015-06-26 11:07:44", "order": 2, }, ] Returns: list of requirement statuses """ requirements = CreditRequirement.get_course_requirements(course_key, namespace=namespace, name=name) requirement_statuses = CreditRequirementStatus.get_statuses(requirements, username) requirement_statuses = {o.requirement: o for o in requirement_statuses} statuses = [] for requirement in requirements: requirement_status = requirement_statuses.get(requirement) statuses.append({ "namespace": requirement.namespace, "name": requirement.name, "display_name": requirement.display_name, "criteria": requirement.criteria, "reason": requirement_status.reason if requirement_status else None, "status": requirement_status.status if requirement_status else None, "status_date": requirement_status.modified if requirement_status else None, # We retain the old name "order" in the API because changing APIs takes a lot more coordination. "order": requirement.sort_value, }) return statuses
def test_retire_user(self): self.add_course_requirements() retirement_succeeded = CreditRequirementStatus.retire_user(self.old_username) self.assertTrue(retirement_succeeded) old_username_records_exist = CreditRequirementStatus.objects.filter(username=self.old_username).exists() self.assertFalse(old_username_records_exist) new_username_records_exist = CreditRequirementStatus.objects.filter(username=self.retired_username).exists() self.assertTrue(new_username_records_exist)
def test_retire_user(self): self.add_course_requirements() retirement_succeeded = CreditRequirementStatus.retire_user(self.old_username) self.assertTrue(retirement_succeeded) old_username_records_exist = CreditRequirementStatus.objects.filter( username=self.old_username ).exists() self.assertFalse(old_username_records_exist) new_username_records_exist = CreditRequirementStatus.objects.filter(username=self.retired_username).exists() self.assertTrue(new_username_records_exist)
def test_retire_user(self): self.add_course_requirements() retirement_succeeded = CreditRequirementStatus.retire_user( self.retirement) assert retirement_succeeded old_username_records_exist = CreditRequirementStatus.objects.filter( username=self.old_username).exists() assert not old_username_records_exist new_username_records_exist = CreditRequirementStatus.objects.filter( username=self.retirement.retired_username).exists() assert new_username_records_exist
def add_course_requirements(self): """ Add requirements to course. """ requirements = ( { "namespace": "grade", "name": "grade", "display_name": "Grade", "criteria": { "min_grade": 0.8 } }, { "namespace": "new_grade", "name": "new_grade", "display_name": "new_grade", "criteria": { "min_grade": 0.8 }, } ) for i, requirement in enumerate(requirements): credit_requirement, _ = CreditRequirement.add_or_update_course_requirement( self.credit_course, requirement, i ) CreditRequirementStatus.add_or_update_requirement_status( self.old_username, credit_requirement, "satisfied", { "final_grade": 0.95 } )
def set_credit_requirement_status(user, course_key, req_namespace, req_name, status="satisfied", reason=None): """ Update the user's requirement status. This will record whether the user satisfied or failed a particular requirement in a course. If the user has satisfied all requirements, the user will be marked as eligible for credit in the course. Args: user(User): User object to set credit requirement for. course_key (CourseKey): Identifier for the course associated with the requirement. req_namespace (str): Namespace of the requirement (e.g. "grade" or "reverification") req_name (str): Name of the requirement (e.g. "grade" or the location of the ICRV XBlock) Keyword Arguments: status (str): Status of the requirement (either "satisfied" or "failed") reason (dict): Reason of the status """ # Check whether user has credit eligible enrollment. enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(user, course_key) has_credit_eligible_enrollment = (CourseMode.is_credit_eligible_slug(enrollment_mode) and is_active) # Refuse to set status of requirement if the user enrollment is not credit eligible. if not has_credit_eligible_enrollment: return # Do not allow students who have requested credit to change their eligibility if CreditRequest.get_user_request_status(user.username, course_key): log.info( u'Refusing to set status of requirement with namespace "%s" and name "%s" because the ' u'user "%s" has already requested credit for the course "%s".', req_namespace, req_name, user.username, course_key ) return # Do not allow a student who has earned eligibility to un-earn eligibility eligible_before_update = CreditEligibility.is_user_eligible_for_credit(course_key, user.username) if eligible_before_update and status == 'failed': log.info( u'Refusing to set status of requirement with namespace "%s" and name "%s" to "failed" because the ' u'user "%s" is already eligible for credit in the course "%s".', req_namespace, req_name, user.username, course_key ) return # Retrieve all credit requirements for the course # We retrieve all of them to avoid making a second query later when # we need to check whether all requirements have been satisfied. reqs = CreditRequirement.get_course_requirements(course_key) # Find the requirement we're trying to set req_to_update = next(( req for req in reqs if req.namespace == req_namespace and req.name == req_name ), None) # If we can't find the requirement, then the most likely explanation # is that there was a lag updating the credit requirements after the course # was published. We *could* attempt to create the requirement here, # but that could cause serious performance issues if many users attempt to # lock the row at the same time. # Instead, we skip updating the requirement and log an error. if req_to_update is None: log.error( ( u'Could not update credit requirement in course "%s" ' u'with namespace "%s" and name "%s" ' u'because the requirement does not exist. ' u'The user "%s" should have had his/her status updated to "%s".' ), unicode(course_key), req_namespace, req_name, user.username, status ) return # Update the requirement status CreditRequirementStatus.add_or_update_requirement_status( user.username, req_to_update, status=status, reason=reason ) # If we're marking this requirement as "satisfied", there's a chance that the user has met all eligibility # requirements, and should be notified. However, if the user was already eligible, do not send another notification. if status == "satisfied" and not eligible_before_update: is_eligible, eligibility_record_created = CreditEligibility.update_eligibility(reqs, user.username, course_key) if eligibility_record_created and is_eligible: try: send_credit_notifications(user.username, course_key) except Exception: # pylint: disable=broad-except log.exception("Error sending email")
def set_credit_requirement_status(user, course_key, req_namespace, req_name, status="satisfied", reason=None): """ Update the user's requirement status. This will record whether the user satisfied or failed a particular requirement in a course. If the user has satisfied all requirements, the user will be marked as eligible for credit in the course. Args: user(User): User object to set credit requirement for. course_key (CourseKey): Identifier for the course associated with the requirement. req_namespace (str): Namespace of the requirement (e.g. "grade" or "reverification") req_name (str): Name of the requirement (e.g. "grade" or the location of the ICRV XBlock) Keyword Arguments: status (str): Status of the requirement (either "satisfied" or "failed") reason (dict): Reason of the status """ # Check whether user has credit eligible enrollment. enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user( user, course_key) has_credit_eligible_enrollment = ( CourseMode.is_credit_eligible_slug(enrollment_mode) and is_active) # Refuse to set status of requirement if the user enrollment is not credit eligible. if not has_credit_eligible_enrollment: return # Do not allow students who have requested credit to change their eligibility if CreditRequest.get_user_request_status(user.username, course_key): log.info( u'Refusing to set status of requirement with namespace "%s" and name "%s" because the ' u'user "%s" has already requested credit for the course "%s".', req_namespace, req_name, user.username, course_key) return # Do not allow a student who has earned eligibility to un-earn eligibility eligible_before_update = CreditEligibility.is_user_eligible_for_credit( course_key, user.username) if eligible_before_update and status == 'failed': log.info( u'Refusing to set status of requirement with namespace "%s" and name "%s" to "failed" because the ' u'user "%s" is already eligible for credit in the course "%s".', req_namespace, req_name, user.username, course_key) return # Retrieve all credit requirements for the course # We retrieve all of them to avoid making a second query later when # we need to check whether all requirements have been satisfied. reqs = CreditRequirement.get_course_requirements(course_key) # Find the requirement we're trying to set req_to_update = next( (req for req in reqs if req.namespace == req_namespace and req.name == req_name), None) # If we can't find the requirement, then the most likely explanation # is that there was a lag updating the credit requirements after the course # was published. We *could* attempt to create the requirement here, # but that could cause serious performance issues if many users attempt to # lock the row at the same time. # Instead, we skip updating the requirement and log an error. if req_to_update is None: log.error( (u'Could not update credit requirement in course "%s" ' u'with namespace "%s" and name "%s" ' u'because the requirement does not exist. ' u'The user "%s" should have had his/her status updated to "%s".'), unicode(course_key), req_namespace, req_name, user.username, status) return # Update the requirement status CreditRequirementStatus.add_or_update_requirement_status(user.username, req_to_update, status=status, reason=reason) # If we're marking this requirement as "satisfied", there's a chance that the user has met all eligibility # requirements, and should be notified. However, if the user was already eligible, do not send another notification. if status == "satisfied" and not eligible_before_update: is_eligible, eligibility_record_created = CreditEligibility.update_eligibility( reqs, user.username, course_key) if eligibility_record_created and is_eligible: try: send_credit_notifications(user.username, course_key) except Exception: # pylint: disable=broad-except log.exception("Error sending email")
def get_credit_requirement_status(course_key, username, namespace=None, name=None): """ Retrieve the user's status for each credit requirement in the course. Args: course_key (CourseKey): The identifier for course username (str): The identifier of the user Example: >>> get_credit_requirement_status("course-v1-edX-DemoX-1T2015", "john") [ { "namespace": "reverification", "name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid", "display_name": "In Course Reverification", "criteria": {}, "reason": {}, "status": "failed", "status_date": "2015-06-26 07:49:13", "order": 0, }, { "namespace": "proctored_exam", "name": "i4x://edX/DemoX/proctoring-block/final_uuid", "display_name": "Proctored Mid Term Exam", "criteria": {}, "reason": {}, "status": "satisfied", "status_date": "2015-06-26 11:07:42", "order": 1, }, { "namespace": "grade", "name": "i4x://edX/DemoX/proctoring-block/final_uuid", "display_name": "Minimum Passing Grade", "criteria": {"min_grade": 0.8}, "reason": {"final_grade": 0.95}, "status": "satisfied", "status_date": "2015-06-26 11:07:44", "order": 2, }, ] Returns: list of requirement statuses """ requirements = CreditRequirement.get_course_requirements(course_key, namespace=namespace, name=name) requirement_statuses = CreditRequirementStatus.get_statuses(requirements, username) requirement_statuses = dict((o.requirement, o) for o in requirement_statuses) statuses = [] for requirement in requirements: requirement_status = requirement_statuses.get(requirement) statuses.append({ "namespace": requirement.namespace, "name": requirement.name, "display_name": requirement.display_name, "criteria": requirement.criteria, "reason": requirement_status.reason if requirement_status else None, "status": requirement_status.status if requirement_status else None, "status_date": requirement_status.modified if requirement_status else None, "order": requirement.order, }) return statuses
def test_retire_user_with_data(self): retirement_succeeded = CreditRequirementStatus.retire_user( self.retired_username) self.assertFalse(retirement_succeeded)
def set_credit_requirement_status(username, course_key, req_namespace, req_name, status="satisfied", reason=None): """ Update the user's requirement status. This will record whether the user satisfied or failed a particular requirement in a course. If the user has satisfied all requirements, the user will be marked as eligible for credit in the course. Args: username (str): Username of the user course_key (CourseKey): Identifier for the course associated with the requirement. req_namespace (str): Namespace of the requirement (e.g. "grade" or "reverification") req_name (str): Name of the requirement (e.g. "grade" or the location of the ICRV XBlock) Keyword Arguments: status (str): Status of the requirement (either "satisfied" or "failed") reason (dict): Reason of the status Example: >>> set_credit_requirement_status( "staff", CourseKey.from_string("course-v1-edX-DemoX-1T2015"), "reverification", "i4x://edX/DemoX/edx-reverification-block/assessment_uuid", status="satisfied", reason={} ) """ # Check if we're already eligible for credit. # If so, short-circuit this process. if CreditEligibility.is_user_eligible_for_credit(course_key, username): log.info( u'Skipping update of credit requirement with namespace "%s" ' u'and name "%s" because the user "%s" is already eligible for credit ' u'in the course "%s".', req_namespace, req_name, username, course_key ) return # Retrieve all credit requirements for the course # We retrieve all of them to avoid making a second query later when # we need to check whether all requirements have been satisfied. reqs = CreditRequirement.get_course_requirements(course_key) # Find the requirement we're trying to set req_to_update = next(( req for req in reqs if req.namespace == req_namespace and req.name == req_name ), None) # If we can't find the requirement, then the most likely explanation # is that there was a lag updating the credit requirements after the course # was published. We *could* attempt to create the requirement here, # but that could cause serious performance issues if many users attempt to # lock the row at the same time. # Instead, we skip updating the requirement and log an error. if req_to_update is None: log.error( ( u'Could not update credit requirement in course "%s" ' u'with namespace "%s" and name "%s" ' u'because the requirement does not exist. ' u'The user "%s" should have had his/her status updated to "%s".' ), unicode(course_key), req_namespace, req_name, username, status ) return # Update the requirement status CreditRequirementStatus.add_or_update_requirement_status( username, req_to_update, status=status, reason=reason ) # If we're marking this requirement as "satisfied", there's a chance # that the user has met all eligibility requirements. if status == "satisfied": is_eligible, eligibility_record_created = CreditEligibility.update_eligibility(reqs, username, course_key) if eligibility_record_created and is_eligible: try: send_credit_notifications(username, course_key) except Exception: # pylint: disable=broad-except log.error("Error sending email")
def test_retire_user_without_data(self): retirement_succeeded = CreditRequirementStatus.retire_user(self.retirement) self.assertFalse(retirement_succeeded)
def test_retire_user_without_data(self): retirement_succeeded = CreditRequirementStatus.retire_user( self.retirement) assert not retirement_succeeded