def create_canvas_course(sis_course_id, sis_user_id, bulk_job=None):
    """
    This method creates a canvas course for the sis_course_id provided, initiated by the sis_user_id. The bulk_job_id
    would be passed in if it's invoked from a bulk feed process.
    """

    # instantiate any variables required for method return or logger calls
    new_course = None
    section = None
    course_job_id = None

    # 1. Insert a CanvasCourseGenerationJob record on initiation with STATUS_SETUP status. This would  help in
    # keeping track of the status of the various courses in the bulk job context as well as general reporting

    # if there's no bulk id, we need to create the CanvasCourseGenerationJob
    bulk_job_id = None
    template_id = None
    if bulk_job:
        bulk_job_id = bulk_job.id
        template_id = bulk_job.template_canvas_course_id
        try:
            course_generation_job = CanvasCourseGenerationJob.objects.filter(
                                        sis_course_id=sis_course_id,
                                        bulk_job_id=bulk_job_id).get()
        except Exception as e:
            ex = CourseGenerationJobNotFoundError(msg_details=(bulk_job_id,
                                                               sis_course_id))
            logger.exception(ex.display_text)
            raise ex
    else:
        try:
            logger.debug('Create content migration job tracking row...')
            course_generation_job = CanvasCourseGenerationJob.objects.create(
                sis_course_id=sis_course_id,
                created_by_user_id=sis_user_id,
                workflow_state=CanvasCourseGenerationJob.STATUS_SETUP,
            )
            course_job_id = course_generation_job.pk
            logger.debug('Job row created: %s' % course_generation_job)
        except Exception as e:
            logger.exception('Error  in inserting CanvasCourseGenerationJob record for '
                             'with sis_course_id=%s: exception=%s' % (sis_course_id, e))

            # send email in addition to showing error page to user
            ex = CourseGenerationJobCreationError(msg_details=sis_course_id)
            send_failure_msg_to_support(sis_course_id, sis_user_id, ex.display_text)
            raise ex

    try:
        # 2. fetch the course instance info
        course_data = get_course_data(sis_course_id)
        logger.info("\n obtained course info for ci=%s, acct_id=%s, course_name=%s, code=%s, term=%s, section_name=%s\n"
                    % (course_data, course_data.sis_account_id, course_data.course_name, course_data.course_code,
                       course_data.sis_term_id, course_data.primary_section_name()))
    except ObjectDoesNotExist as e:
        logger.error('ObjectDoesNotExist exception when fetching SIS data for course '
                     'with sis_course_id=%s: exception=%s' % (sis_course_id, e))
        # Update the status to STATUS_SETUP_FAILED on any failures
        update_course_generation_workflow_state(
            sis_course_id, CanvasCourseGenerationJob.STATUS_SETUP_FAILED,
            course_job_id=course_job_id, bulk_job_id=bulk_job_id)

        ex = SISCourseDoesNotExistError(sis_course_id)
        # If the course is part of bulk job, do not send individual email. .
        if not bulk_job_id:
            msg = ex.display_text
            # TLT-393: send an email to support group, in addition to showing error page to user
            send_failure_msg_to_support(sis_course_id, sis_user_id, msg)
        raise ex

    # If the account ID begins with dept: then check if department already exists in Canvas
    if course_data.sis_account_id.startswith('dept:'):
        # TLT-3689 Check to see if the department exists in Canvas
        # If it does not, then create it
        try:
            get_single_account(request_ctx=SDK_CONTEXT,
                               id='sis_account_id:%s' % course_data.sis_account_id)
        except CanvasAPIError:
            department_id = course_data.sis_account_id.replace('dept:', '')
            department = Department.objects.get(department_id=department_id)
            logger.info("Department does not exist for {}, creating one now".format(course_data.sis_account_id))
            # It seems that using the sis_account_id:xxx in create_new_sub_account
            # returns a 404 below and requires the Canvas numeric ID
            parent_account_id = get_single_account(SDK_CONTEXT,
                                                   id='sis_account_id:school:'+department.school_id).json()['id']
            create_new_sub_account(request_ctx=SDK_CONTEXT,
                                   account_id=parent_account_id,
                                   account_name=department.name,
                                   sis_account_id=course_data.sis_account_id)

    if course_data.sis_account_id.startswith('coursegroup:'):
        # TLT-3878 Check to see if the course group exists in Canvas
        # If it does not, then create it
        try:
            get_single_account(request_ctx=SDK_CONTEXT,
                               id='sis_account_id:%s' % course_data.sis_account_id)
        except CanvasAPIError:
            course_group_id = course_data.sis_account_id.replace('coursegroup:', '')
            course_group = CourseGroup.objects.get(course_group_id=course_group_id)
            logger.info("Course group does not exist for {}, creating one now".format(course_data.sis_account_id))
            # It seems that using the sis_account_id:xxx in create_new_sub_account
            # returns a 404 below and requires the Canvas numeric ID
            parent_account_id = get_single_account(SDK_CONTEXT,
                                                   id='sis_account_id:school:' + course_group.school_id).json()['id']
            create_new_sub_account(request_ctx=SDK_CONTEXT,
                                   account_id=parent_account_id,
                                   account_name=course_group.name,
                                   sis_account_id=course_data.sis_account_id)

    # 3. Attempt to create a canvas course
    request_parameters = dict(
        request_ctx=SDK_CONTEXT,
        account_id='sis_account_id:%s' % course_data.sis_account_id,
        course_name=course_data.course_name,
        course_course_code=course_data.course_code,
        course_term_id='sis_term_id:%s' % course_data.sis_term_id,
        course_sis_course_id=sis_course_id,
    )

    # If this was not part of a bulk job, attempt to get the default template for the given school
    if not bulk_job_id:
        try:
            template_id = get_default_template_for_school(course_data.school_code).template_id
        except NoTemplateExistsForSchool:
            # No template exists for the school, so no need to copy visibility settings
            pass

    # If creating from a template, get template course, so visibility settings
    # can be copied over to the new course
    if template_id:
        try:
            template_course = get_single_course_courses(SDK_CONTEXT, template_id, 'all_courses').json()
            # Update create course request parameters
            request_parameters.update({
                'course_is_public': template_course['is_public'],
                'course_public_syllabus': template_course['public_syllabus'],
            })
        except CanvasAPIError:
            logger.exception(
                'Failed to retrieve template course %d for creation of site for course instance %s in account %s',
                template_id,
                sis_course_id,
                course_data.sis_account_id
            )
            # Update the status to STATUS_SETUP_FAILED on failure to retrieve template course
            update_course_generation_workflow_state(
                sis_course_id,
                CanvasCourseGenerationJob.STATUS_SETUP_FAILED,
                course_job_id=course_generation_job.id,
                bulk_job_id=bulk_job_id
            )
            ex = CanvasCourseCreateError(msg_details=sis_course_id)
            if not bulk_job_id:
                send_failure_msg_to_support(sis_course_id, sis_user_id, ex.display_text)
            raise ex

    try:
        new_course = create_new_course(**request_parameters).json()
    except CanvasAPIError as api_error:
        logger.exception(
            'Error building request_parameters or executing create_new_course() '
            'SDK call for new Canvas course with request=%s:',
            request_parameters)
        # Update the status to STATUS_SETUP_FAILED on any failures
        update_course_generation_workflow_state(sis_course_id,
            CanvasCourseGenerationJob.STATUS_SETUP_FAILED,
            course_job_id=course_job_id, bulk_job_id=bulk_job_id)

        # a 400 errors here means that the SIS id already exists in Canvas
        if api_error.status_code == 400:
            raise CanvasCourseAlreadyExistsError(msg_details=sis_course_id)

        ex = CanvasCourseCreateError(msg_details=sis_course_id)
        if not bulk_job_id:
            send_failure_msg_to_support(sis_course_id, sis_user_id, ex.display_text)
        raise ex

    logger.info("created course object, ret=%s" % new_course)

    # 4. Save the canvas course id to the generation job
    course_generation_job.canvas_course_id = new_course['id']
    try:
        course_generation_job.save(update_fields=['canvas_course_id'])
    except Exception as e:
        # Update the status to STATUS_SETUP_FAILED on any failures
        update_course_generation_workflow_state(sis_course_id,
            CanvasCourseGenerationJob.STATUS_SETUP_FAILED,
            course_job_id=course_job_id, bulk_job_id=bulk_job_id)
        ex = SaveCanvasCourseIdToCourseGenerationJobError(
                msg_details=(new_course['id'], course_generation_job.pk))
        logging.exception(ex.display_text)
        if not bulk_job_id:
            send_failure_msg_to_support(sis_course_id, sis_user_id,
                                        ex.display_text)
        raise ex

    # 5. Save the canvas course id to the course instance
    course_data.canvas_course_id = new_course['id']
    try:
        course_data.save(update_fields=['canvas_course_id'])
    except Exception as e:
        # Update the status to STATUS_SETUP_FAILED on any failures
        update_course_generation_workflow_state(sis_course_id,
            CanvasCourseGenerationJob.STATUS_SETUP_FAILED,
            course_job_id=course_job_id, bulk_job_id=bulk_job_id)
        ex = SaveCanvasCourseIdToCourseInstanceError(
                msg_details=(new_course['id'], course_data.pk))
        logging.exception(ex.display_text)
        if not bulk_job_id:
            send_failure_msg_to_support(sis_course_id, sis_user_id,
                                        ex.display_text)
        raise ex

    # 6. Create course section after course creation
    try:
        request_parameters = dict(request_ctx=SDK_CONTEXT,
                                  course_id=new_course['id'],
                                  course_section_name=course_data.primary_section_name(),
                                  course_section_sis_section_id=sis_course_id)
        section = create_course_section(**request_parameters).json()
        logger.info("created section= %s" % section)
    except CanvasAPIError as e:
        logger.exception(
            'Error building request_parameters or executing '
            'create_course_section() SDK call for new Canvas course id=%s with '
            'request=%s' % (new_course.get('id', '<no ID>'),
                            request_parameters))

        # Update the status to STATUS_SETUP_FAILED on any failures
        update_course_generation_workflow_state(sis_course_id,
            CanvasCourseGenerationJob.STATUS_SETUP_FAILED,
            course_job_id=course_job_id, bulk_job_id=bulk_job_id)

        # send email in addition to showing error page to user
        ex = CanvasSectionCreateError(msg_details=sis_course_id)
        if not bulk_job_id:
            send_failure_msg_to_support(sis_course_id, sis_user_id, ex.display_text)
        raise ex

    # if this creation is part of a single course creation process return
    # the course along with the new job_id. The start_course_template_copy method will updated
    # the worng record if job id is no supplied.
    if course_job_id:
        return new_course, course_job_id

    return new_course
def create_canvas_course(sis_course_id, sis_user_id, bulk_job=None):
    """
    This method creates a canvas course for the sis_course_id provided, initiated by the sis_user_id. The bulk_job_id
    would be passed in if it's invoked from a bulk feed process.
    """

    # instantiate any variables required for method return or logger calls
    new_course = None
    section = None
    course_job_id = None

    # 1. Insert a CanvasCourseGenerationJob record on initiation with STATUS_SETUP status. This would  help in
    # keeping track of the status of the various courses in the bulk job context as well as general reporting

    # if there's no bulk id, we need to create the CanvasCourseGenerationJob
    bulk_job_id = None
    template_id = None
    if bulk_job:
        bulk_job_id = bulk_job.id
        template_id = bulk_job.template_canvas_course_id
        try:
            course_generation_job = CanvasCourseGenerationJob.objects.filter(
                                        sis_course_id=sis_course_id,
                                        bulk_job_id=bulk_job_id).get()
        except Exception as e:
            ex = CourseGenerationJobNotFoundError(msg_details=(bulk_job_id,
                                                               sis_course_id))
            logger.exception(ex.display_text)
            raise ex
    else:
        try:
            logger.debug('Create content migration job tracking row...')
            course_generation_job = CanvasCourseGenerationJob.objects.create(
                sis_course_id=sis_course_id,
                created_by_user_id=sis_user_id,
                workflow_state=CanvasCourseGenerationJob.STATUS_SETUP,
            )
            course_job_id = course_generation_job.pk
            logger.debug('Job row created: %s' % course_generation_job)
        except Exception as e:
            logger.exception('Error  in inserting CanvasCourseGenerationJob record for '
                             'with sis_course_id=%s: exception=%s' % (sis_course_id, e))

            # send email in addition to showing error page to user
            ex = CourseGenerationJobCreationError(msg_details=sis_course_id)
            send_failure_msg_to_support(sis_course_id, sis_user_id, ex.display_text)
            raise ex

    try:
        # 2. fetch the course instance info
        course_data = get_course_data(sis_course_id)
        logger.info("\n obtained course info for ci=%s, acct_id=%s, course_name=%s, code=%s, term=%s, section_name=%s\n"
                    % (course_data, course_data.sis_account_id, course_data.course_name, course_data.course_code,
                       course_data.sis_term_id, course_data.primary_section_name()))
    except ObjectDoesNotExist as e:
        logger.error('ObjectDoesNotExist exception when fetching SIS data for course '
                     'with sis_course_id=%s: exception=%s' % (sis_course_id, e))
        # Update the status to STATUS_SETUP_FAILED on any failures
        update_course_generation_workflow_state(
            sis_course_id, CanvasCourseGenerationJob.STATUS_SETUP_FAILED,
            course_job_id=course_job_id, bulk_job_id=bulk_job_id)

        ex = SISCourseDoesNotExistError(sis_course_id)
        # If the course is part of bulk job, do not send individual email. .
        if not bulk_job_id:
            msg = ex.display_text
            # TLT-393: send an email to support group, in addition to showing error page to user
            send_failure_msg_to_support(sis_course_id, sis_user_id, msg)
        raise ex

    # 3. Attempt to create a canvas course
    request_parameters = dict(
        request_ctx=SDK_CONTEXT,
        account_id='sis_account_id:%s' % course_data.sis_account_id,
        course_name=course_data.course_name,
        course_course_code=course_data.course_code,
        course_term_id='sis_term_id:%s' % course_data.sis_term_id,
        course_sis_course_id=sis_course_id,
        course_is_public_to_auth_users=course_data.shopping_active
    )

    # If this was not part of a bulk job, attempt to get the default template for the given school
    if not bulk_job_id:
        try:
            template_id = get_default_template_for_school(course_data.school_code).template_id
        except NoTemplateExistsForSchool:
            # No template exists for the school, so no need to copy visibility settings
            pass

    # If creating from a template, get template course, so visibility settings
    # can be copied over to the new course
    if template_id:
        try:
            template_course = get_single_course_courses(SDK_CONTEXT, template_id, 'all_courses').json()
            is_public_to_auth_users = course_data.shopping_active or template_course['is_public_to_auth_users']
            # Update create course request parameters
            request_parameters.update({
                'course_is_public': template_course['is_public'],
                'course_public_syllabus': template_course['public_syllabus'],
                'course_is_public_to_auth_users': is_public_to_auth_users
            })
        except CanvasAPIError:
            logger.exception(
                'Failed to retrieve template course %d for creation of site for course instance %s in account %s',
                template_id,
                sis_course_id,
                course_data.sis_account_id
            )
            # Update the status to STATUS_SETUP_FAILED on failure to retrieve template course
            update_course_generation_workflow_state(
                sis_course_id,
                CanvasCourseGenerationJob.STATUS_SETUP_FAILED,
                course_job_id=course_generation_job.id,
                bulk_job_id=bulk_job_id
            )
            ex = CanvasCourseCreateError(msg_details=sis_course_id)
            if not bulk_job_id:
                send_failure_msg_to_support(sis_course_id, sis_user_id, ex.display_text)
            raise ex

    try:
        new_course = create_new_course(**request_parameters).json()
    except CanvasAPIError as api_error:
        logger.exception(
            'Error building request_parameters or executing create_new_course() '
            'SDK call for new Canvas course with request=%s:',
            request_parameters)
        # Update the status to STATUS_SETUP_FAILED on any failures
        update_course_generation_workflow_state(sis_course_id,
            CanvasCourseGenerationJob.STATUS_SETUP_FAILED,
            course_job_id=course_job_id, bulk_job_id=bulk_job_id)

        # a 400 errors here means that the SIS id already exists in Canvas
        if api_error.status_code == 400:
            raise CanvasCourseAlreadyExistsError(msg_details=sis_course_id)

        ex = CanvasCourseCreateError(msg_details=sis_course_id)
        if not bulk_job_id:
            send_failure_msg_to_support(sis_course_id, sis_user_id, ex.display_text)
        raise ex

    logger.info("created course object, ret=%s" % new_course)

    # 4. Save the canvas course id to the generation job
    course_generation_job.canvas_course_id = new_course['id']
    try:
        course_generation_job.save(update_fields=['canvas_course_id'])
    except Exception as e:
        # Update the status to STATUS_SETUP_FAILED on any failures
        update_course_generation_workflow_state(sis_course_id,
            CanvasCourseGenerationJob.STATUS_SETUP_FAILED,
            course_job_id=course_job_id, bulk_job_id=bulk_job_id)
        ex = SaveCanvasCourseIdToCourseGenerationJobError(
                msg_details=(new_course['id'], course_generation_job.pk))
        logging.exception(ex.display_text)
        if not bulk_job_id:
            send_failure_msg_to_support(sis_course_id, sis_user_id,
                                        ex.display_text)
        raise ex

    # 5. Save the canvas course id to the course instance
    course_data.canvas_course_id = new_course['id']
    try:
        course_data.save(update_fields=['canvas_course_id'])
    except Exception as e:
        # Update the status to STATUS_SETUP_FAILED on any failures
        update_course_generation_workflow_state(sis_course_id,
            CanvasCourseGenerationJob.STATUS_SETUP_FAILED,
            course_job_id=course_job_id, bulk_job_id=bulk_job_id)
        ex = SaveCanvasCourseIdToCourseInstanceError(
                msg_details=(new_course['id'], course_data.pk))
        logging.exception(ex.display_text)
        if not bulk_job_id:
            send_failure_msg_to_support(sis_course_id, sis_user_id,
                                        ex.display_text)
        raise ex

    # 6. Create course section after course creation
    try:
        request_parameters = dict(request_ctx=SDK_CONTEXT,
                                  course_id=new_course['id'],
                                  course_section_name=course_data.primary_section_name(),
                                  course_section_sis_section_id=sis_course_id)
        section = create_course_section(**request_parameters).json()
        logger.info("created section= %s" % section)
    except CanvasAPIError as e:
        logger.exception(
            'Error building request_parameters or executing '
            'create_course_section() SDK call for new Canvas course id=%s with '
            'request=%s' % (new_course.get('id', '<no ID>'),
                            request_parameters))

        # Update the status to STATUS_SETUP_FAILED on any failures
        update_course_generation_workflow_state(sis_course_id,
            CanvasCourseGenerationJob.STATUS_SETUP_FAILED,
            course_job_id=course_job_id, bulk_job_id=bulk_job_id)

        # send email in addition to showing error page to user
        ex = CanvasSectionCreateError(msg_details=sis_course_id)
        if not bulk_job_id:
            send_failure_msg_to_support(sis_course_id, sis_user_id, ex.display_text)
        raise ex

    # if this creation is part of a single course creation process return
    # the course along with the new job_id. The start_course_template_copy method will updated
    # the worng record if job id is no supplied.
    if course_job_id:
        return new_course, course_job_id

    return new_course
def create_canvas_course_and_section(request):

    try:
        data = json.loads(request.body)
        account_id = 'sis_account_id:%s' % data['dept_id']
        # not using .get() default because we want to fall back on course_code
        # if short_title is an empty string
        course_code = data.get('short_title', '').strip() or data['course_code']
        course_instance_id = data['course_instance_id']
        school = data['school']
        section_id = data['section_id']
        term_id = 'sis_term_id:%s' % data['term_id']
        title = data['title']
    except Exception:
        message = (u'Failed to extract canvas parameters from posted data; '
                   u'request body={}'.format(request.body))
        logger.exception(message)
        return JsonResponse({'error': message}, status=400)

    request_parameters = dict(
        request_ctx=SDK_CONTEXT,
        account_id=account_id,
        course_course_code=course_code,
        course_name=title,
        course_sis_course_id=course_instance_id,
        course_term_id=term_id
    )
    try:
        course_result = create_new_course(**request_parameters).json()
    except Exception as e:
        message = u'Error creating new course via SDK with request={}'.format(
            request_parameters)
        if isinstance(e, CanvasAPIError):
            message += u', SDK error details={}'.format(e)
        logger.exception(message)
        return JsonResponse({}, status=500)

    # create the canvas section
    section_result = {}
    request_parameters = {}
    try:
        # format section name similar to how it is handled in the bulk feed :
        #  school + short title/course_code  + section id

        section_name = u'{} {} {}'.format(school.upper(), course_code,
                                          section_id)
        request_parameters = dict(
            request_ctx=SDK_CONTEXT,
            course_id=course_result['id'],
            course_section_name=section_name,
            course_section_sis_section_id=course_instance_id)
        section_result = create_course_section(**request_parameters).json()
    except Exception as e:
        message = (u'Error creating section for new course via SDK with '
                   u'request={}'.format(request_parameters))
        if isinstance(e, CanvasAPIError):
            message += u', SDK error details={}'.format(e)
        logger.exception(message)
        return JsonResponse(section_result, status=500)

    return JsonResponse(course_result, status=200)
Exemple #4
0
def create_canvas_course_and_section(request):

    try:
        data = json.loads(request.body)
        is_blueprint = data['is_blueprint']
        # If this is a blueprint course, create course at school level not in the ILE sub account
        account_id = 'sis_account_id:%s' % (data['school_id'] if is_blueprint
                                            else data['dept_id'])
        # not using .get() default because we want to fall back on course_code
        # if short_title is an empty string
        course_code = data.get('short_title',
                               '').strip() or data['course_code']
        course_instance_id = data['course_instance_id']
        school = data['school']
        section_id = data['section_id']
        term_id = 'sis_term_id:%s' % data['term_id']
        title = data['title']
    except Exception:
        message = (u'Failed to extract canvas parameters from posted data; '
                   u'request body={}'.format(request.body))
        logger.exception(message)
        return JsonResponse({'error': message}, status=400)

    request_parameters = dict(request_ctx=SDK_CONTEXT,
                              account_id=account_id,
                              course_course_code=course_code,
                              course_name=title,
                              course_sis_course_id=course_instance_id,
                              course_term_id=term_id)

    try:
        course_result = create_new_course(**request_parameters).json()

        # If this course is meant to be a blueprint course,
        # the newly created course needs to have its blueprint field set to True
        if is_blueprint:
            update_parameters = dict(request_ctx=SDK_CONTEXT,
                                     id=course_result['id'],
                                     course_blueprint=True)
            try:
                update_course(**update_parameters).json()
            except:
                logger.exception(
                    "Error creating blueprint course via update with request {}"
                    .format(update_parameters))
                return JsonResponse({}, status=500)
    except Exception as e:
        message = u'Error creating new course via SDK with request={}'.format(
            request_parameters)
        if isinstance(e, CanvasAPIError):
            message += u', SDK error details={}'.format(e)
        logger.exception(message)
        return JsonResponse({}, status=500)

    # create the canvas section
    section_result = {}
    request_parameters = {}
    try:
        # format section name similar to how it is handled in the bulk feed :
        #  school + short title/course_code  + section id

        section_name = u'{} {} {}'.format(school.upper(), course_code,
                                          section_id)
        request_parameters = dict(
            request_ctx=SDK_CONTEXT,
            course_id=course_result['id'],
            course_section_name=section_name,
            course_section_sis_section_id=course_instance_id)
        section_result = create_course_section(**request_parameters).json()
    except Exception as e:
        message = (u'Error creating section for new course via SDK with '
                   u'request={}'.format(request_parameters))
        if isinstance(e, CanvasAPIError):
            message += u', SDK error details={}'.format(e)
        logger.exception(message)
        return JsonResponse(section_result, status=500)

    return JsonResponse(course_result, status=200)