def ensure_active_user(user): """ Activates the user (if required) and generates Open edX credentials and corresponding user if necessary Args: user (users.models.User): The user to activate/verify as functional """ from courseware.api import repair_faulty_edx_user # circular import issues if not user.is_active: user.is_active = True user.save() log.info("User %s activated", user.email) # Check if the user is properly onboard with edX (has proper auth credentials) # and try to repair if necessary if User.faulty_courseware_users.filter(pk=user.id).exists(): try: created_user, created_auth_token = repair_faulty_edx_user(user) if created_user: log.info("Created edX user for %s", user.email) if created_auth_token: log.info("Created edX auth token for %s", user.email) except HTTPError as exc: log.error( "%s (%s): Failed to repair (%s)", user.username, user.email, get_error_response_summary(exc.response), ) except Exception: # pylint: disable=broad-except log.exception("%s (%s): Failed to repair", user.username, user.email)
def repair_faulty_courseware_users(): """ Loops through all Users that are incorrectly configured with the courseware and attempts to get them in the correct state. Returns: list of User: Users that were successfully repaired """ now = now_in_utc() repaired_users = [] for user in User.faulty_courseware_users.filter( created_on__lt=now - timedelta(minutes=COURSEWARE_REPAIR_GRACE_PERIOD_MINS)): try: # edX is our only courseware for the time being. If a different courseware is added, this # function will need to be updated. created_user, created_auth_token = repair_faulty_edx_user(user) except HTTPError as exc: log.exception( "Failed to repair faulty user %s (%s). %s", user.username, user.email, get_error_response_summary(exc.response), ) except Exception: # pylint: disable=broad-except log.exception("Failed to repair faulty user %s (%s)", user.username, user.email) else: if created_user or created_auth_token: repaired_users.append(user) return repaired_users
def enroll_in_edx_course_runs(user, course_runs): """ Enrolls a user in edx course runs Args: user (users.models.User): The user to enroll course_runs (iterable of CourseRun): The course runs to enroll in Returns: list of edx_api.enrollments.models.Enrollment: The results of enrollments via the edx API client Raises: EdxApiEnrollErrorException: Raised if the underlying edX API HTTP request fails UnknownEdxApiEnrollException: Raised if an unknown error was encountered during the edX API request """ edx_client = get_edx_api_client(user) results = [] for course_run in course_runs: try: result = edx_client.enrollments.create_student_enrollment( course_run.courseware_id, mode=EDX_ENROLLMENT_PRO_MODE) results.append(result) except HTTPError as exc: # If there is an error message and it indicates that the preferred enrollment mode was the cause of the # error, log an error and try to enroll the user in 'audit' mode as a failover. if not is_json_response(exc.response): raise EdxApiEnrollErrorException(user, course_run, exc) from exc error_msg = exc.response.json().get("message", "") is_enroll_mode_error = any([ error_text in error_msg for error_text in PRO_ENROLL_MODE_ERROR_TEXTS ]) if not is_enroll_mode_error: raise EdxApiEnrollErrorException(user, course_run, exc) from exc log.error( "Failed to enroll user in %s with '%s' mode. Attempting to enroll with '%s' mode instead. " "(%s)", course_run.courseware_id, EDX_ENROLLMENT_PRO_MODE, EDX_ENROLLMENT_AUDIT_MODE, get_error_response_summary(exc.response), ) try: result = edx_client.enrollments.create_student_enrollment( course_run.courseware_id, mode=EDX_ENROLLMENT_AUDIT_MODE) except HTTPError as inner_exc: raise EdxApiEnrollErrorException(user, course_run, inner_exc) from inner_exc except Exception as inner_exc: # pylint: disable=broad-except raise UnknownEdxApiEnrollException(user, course_run, inner_exc) from inner_exc results.append(result) except Exception as exc: # pylint: disable=broad-except raise UnknownEdxApiEnrollException(user, course_run, exc) from exc return results
def test_get_error_response_summary(content, content_type, exp_summary_content, exp_url_in_summary): """ get_error_response_summary should provide a summary of an error HTTP response object with the correct bits of information depending on the type of content. """ status_code = 400 url = "http://example.com" mock_response = MockResponse(status_code=status_code, content=content, content_type=content_type, url=url) summary = get_error_response_summary(mock_response) assert f"Response - code: {status_code}" in summary assert f"content: {exp_summary_content}" in summary assert (f"url: {url}" in summary) is exp_url_in_summary
def __init__(self, user, course_run, http_error, msg=None): """ Sets exception properties and adds a default message Args: user (users.models.User): The user for which the enrollment failed course_run (courses.models.CourseRun): The course run for which the enrollment failed http_error (requests.exceptions.HTTPError): The exception from the API call which contains request and response data. """ self.user = user self.course_run = course_run self.http_error = http_error if msg is None: # Set some default useful error message msg = "EdX API error enrolling user {} ({}) in course run '{}'.\n{}".format( self.user.username, self.user.email, self.course_run.courseware_id, get_error_response_summary(self.http_error.response), ) super().__init__(msg)