Esempio n. 1
0
def launch_lti(request):
    """
    Gets a request from an LTI consumer.
    Passes along information to render a welcome screen to the user.

    Assumptions: LTI launch request has already been validated by middleware
    """

    # collect anonymous_id and consumer key in order to fetch LTIProfile
    # if it exists, we initialize the tool otherwise, we create a new user
    user_id = request.LTI['launch_params']['user_id']
    logger.debug('DEBUG - Found anonymous ID in request: %s' % user_id)

    course = request.LTI['launch_params'][settings.LTI_COURSE_ID]
    logger.debug('DEBUG - Found course being accessed: %s' % course)

    resource_link_id = request.LTI['launch_params']['resource_link_id']

    # This is where we identify the "scope" of the LTI user_id (anon_id), meaning
    # the scope in which the identifier is unique. In canvas this is the domain instance,
    # where as in edX this is the course instance.
    #
    # The idea is that the combination of the LTI user_id (anon_id) and scope should be
    # globally unique.
    user_scope = None
    if settings.ORGANIZATION == "HARVARDX":
        user_scope = "course:%s" % course
    else:
        tool_consumer_instance_guid = request.LTI['launch_params'][
            'tool_consumer_instance_guid']
        if tool_consumer_instance_guid:
            user_scope = "consumer:%s" % tool_consumer_instance_guid
    logger.debug("DEBUG - user scope is: %s" % user_scope)

    # default to student
    save_session(request, is_staff=False)

    # this is where canvas will tell us what level individual is coming into
    # the tool the 'roles' field usually consists of just 'Instructor'
    # or 'Learner'
    roles = request.LTI['launch_params'][settings.LTI_ROLES]
    logger.debug("DEBUG - user logging in with roles: " + str(roles))

    # This is the name that we will show on the UI if provided...
    # EDX-NOTE: edx does not return the person's name!
    display_name = request.LTI['launch_params'].get('lis_person_name_full',
                                                    None)
    if not display_name:
        display_name = request.LTI['launch_params'].get(
            'lis_person_sourcedid', '')

    # This is the unique identifier for the person in the source system
    # In canvas this would be the SIS user id, in edX the registered username
    external_user_id = request.LTI['launch_params'].get(
        'lis_person_sourcedid', '')

    # This handles the rare case in which we have neither display name nor external user id
    if not (display_name or external_user_id):
        try:
            lti_profile = LTIProfile.objects.get(anon_id=str(course))
            roles = ['student']
            display_name = lti_profile.user.username
            messages.warning(
                request,
                "edX still has not fixed issue with no user_id in studio.")
            messages.error(
                request,
                "Warning: you are logged in as a Preview user. Please view this in live to access admin hub."
            )
        except:
            logger.debug('DEBUG - username not found in post.')
            raise PermissionDenied('username not found in LTI launch')
    logger.debug("DEBUG - user name: " + display_name)

    # Check whether user is a admin, instructor or teaching assistant
    if set(roles) & set(settings.ADMIN_ROLES):
        try:
            # See if the user already has a profile, and use it if so.
            lti_profile = LTIProfile.objects.get(anon_id=user_id)
            logger.debug('DEBUG - LTI Profile was found via anonymous id.')
        except LTIProfile.DoesNotExist:
            # if it's a new user (profile doesn't exist), set up and save a new LTI Profile
            logger.debug(
                'DEBUG - LTI Profile NOT found. New User to be created.')
            user, lti_profile = create_new_user(anon_id=user_id,
                                                username=external_user_id,
                                                display_name=display_name,
                                                roles=roles,
                                                scope=user_scope)
            # log the user into the Django backend
        lti_profile.user.backend = 'django.contrib.auth.backends.ModelBackend'
        login(request, lti_profile.user)
        save_session(request,
                     user_id=user_id,
                     user_name=display_name,
                     user_scope=user_scope,
                     context_id=course,
                     roles=roles,
                     is_staff=True,
                     resource_link_id=resource_link_id)
    else:
        # For HX, students only access one object or collection, and don't
        # have an index
        # For ATG, students have the index  to choose where to go, so
        # collection_id and object_id are probably blank for their session
        # right now.
        collection_id = request.LTI['launch_params'].get(
            settings.LTI_COLLECTION_ID)
        object_id = request.LTI['launch_params'].get(settings.LTI_OBJECT_ID)
        save_session(request,
                     user_id=user_id,
                     user_name=display_name,
                     user_scope=user_scope,
                     context_id=course,
                     roles=roles,
                     is_staff=False,
                     resource_link_id=resource_link_id)

    # now it's time to deal with the course_id it does not associate
    # with users as they can flow in and out in a MOOC
    try:
        course_object = LTICourse.get_course_by_id(course)
        logger.debug('DEBUG - Course was found %s' % course)

        # save the course name to the session so it auto-populate later.
        save_session(
            request,
            course_name=course_object.course_name,
            course_id=course_object.id,
        )

    except LTICourse.DoesNotExist:
        logger.debug('DEBUG - Course %s was NOT found. Will be created.' %
                     course)

        # Put a message on the screen to indicate to the user that the course doesn't exist
        message_error = "Sorry, the course you are trying to reach does not exist."
        messages.error(request, message_error)

        if set(roles) & set(settings.ADMIN_ROLES):
            # This must be the instructor's first time accessing the annotation tool
            # Make him/her a new course within the tool

            message_error = "Because you are an instructor, a course has been created for you, please refresh the page to begin editing your course."
            messages.warning(request, message_error)

            # create and save a new course for the instructor, with a default name of their canvas course's name
            context_title = None
            if request.LTI['launch_params']['context_title'] is not None:
                context_title = request.LTI['launch_params']['context_title']
            course_object = LTICourse.create_course(course,
                                                    lti_profile,
                                                    name=context_title)
            create_new_user(anon_id=str(course),
                            username='******' % course_object.id,
                            display_name="Preview %s" % str(course_object),
                            roles=['student'],
                            scope=user_scope)

            # save the course name to the session so it auto-populate later.
            save_session(
                request,
                course_name=course_object.course_name,
                course_id=course_object.id,
            )
        else:
            logger.info(
                'Course not created because user does not have an admin role')
    try:
        config = LTIResourceLinkConfig.objects.get(
            resource_link_id=resource_link_id)
        assignment_id = config.collection_id
        object_id = config.object_id
        logger.debug(
            "DEBUG - LTIResourceLinkConfig: resource_link_id=%s collection_id=%s object_id=%s"
            % (resource_link_id, config.collection_id, config.object_id))
        course_id = str(course)
        if set(roles) & set(settings.ADMIN_ROLES):
            try:
                userfound = LTICourseAdmin.objects.get(
                    admin_unique_identifier=lti_profile.user.username,
                    new_admin_course_id=course)
                course_object.add_admin(lti_profile)
                logger.info("CourseAdmin Pending found: %s" % userfound)
                userfound.delete()
            except:
                logger.info("Not waiting to be added as admin")
        logger.debug(
            "DEBUG - User wants to go directly to annotations for a specific target object using UI"
        )
        return access_annotation_target(request, course_id, assignment_id,
                                        object_id)
    except AnnotationTargetDoesNotExist as e:
        logger.warning(
            'Could not access annotation target using resource config.')
        logger.info('Deleting resource config because it is invalid.')
        LTIResourceLinkConfig.objects.filter(
            resource_link_id=resource_link_id).delete()
        logger.info('Proceed to the admin hub.')
    except PermissionDenied as e:
        raise e  # make sure to re-raise this exception since we shouldn't proceed
    except:
        # For the use case where the course head wants to display an assignment object instead
        # of the admin_hub upon launch (i.e. for embedded use), this allows the user
        # to be routed directly to an assignment given the correct POST parameters,
        # as by Luis' original method of putting collection_id and object_id in the
        # LTI tool launch params.
        try:
            # Keeping the HX functionality whereby students are routed to specific assignment objects
            # This is motivated by the Poetry in America Course

            # If there are variables passed into the launch indicating a desired target object, render that object
            assignment_id = request.LTI['launch_params'][
                settings.LTI_COLLECTION_ID]
            object_id = request.LTI['launch_params'][settings.LTI_OBJECT_ID]
            course_id = str(course)
            if set(roles) & set(settings.ADMIN_ROLES):
                try:
                    userfound = LTICourseAdmin.objects.get(
                        admin_unique_identifier=lti_profile.user.username,
                        new_admin_course_id=course)
                    course_object.add_admin(lti_profile)
                    logger.info("CourseAdmin Pending found: %s" % userfound)
                    userfound.delete()
                except:
                    logger.info("Not waiting to be added as admin")
                return course_admin_hub(request)
            else:
                logger.debug(
                    "DEBUG - User wants to go directly to annotations for a specific target object"
                )
                return access_annotation_target(request, course_id,
                                                assignment_id, object_id)
        except:
            logger.debug("DEBUG - User wants the index")

    try:
        userfound = LTICourseAdmin.objects.get(
            admin_unique_identifier=lti_profile.user.username,
            new_admin_course_id=course)
        course_object.add_admin(lti_profile)
        logger.info("CourseAdmin Pending found: %s" % userfound)
        userfound.delete()
    except:
        logger.debug("DEBUG - Not waiting to be added as admin")

    return course_admin_hub(request)
Esempio n. 2
0
def access_annotation_target(request,
                             course_id,
                             assignment_id,
                             object_id,
                             user_id=None,
                             user_name=None,
                             roles=None):
    """
    Renders an assignment page
    """
    if user_id is None:
        user_name = request.LTI['hx_user_name']
        user_id = request.LTI['hx_user_id']
        roles = request.LTI['hx_roles']
    try:
        assignment = Assignment.objects.get(assignment_id=assignment_id)
        targ_obj = TargetObject.objects.get(pk=object_id)
        assignment_target = AssignmentTargets.objects.get(
            assignment=assignment, target_object=targ_obj)
        object_uri = targ_obj.get_target_content_uri()
        course_obj = LTICourse.objects.get(course_id=course_id)
    except Assignment.DoesNotExist or TargetObject.DoesNotExist:
        logger.error(
            "User attempted to access an Assignment or Target Object that does not exist: assignment_id={assignment_id} object_id={object_id}"
            .format(assignment_id=assignment_id, object_id=object_id))
        raise AnnotationTargetDoesNotExist(
            'Assignment or target object does not exist')
    try:
        is_instructor = request.LTI['is_staff']
    except:
        is_instructor = False

    if not is_instructor and not assignment.is_published:
        raise PermissionDenied(
            'Permission to access unpublished assignment by a non-instructor is denied'
        )

    save_session(request,
                 collection_id=assignment_id,
                 object_id=object_id,
                 object_uri=object_uri,
                 context_id=course_id)

    # Dynamically pass in the address that the detail view will use to fetch annotations.
    # there's definitely a more elegant way (or a library function) to do this.
    # also, we may want to consider denying if theres no ssl
    protocol = 'https://' if request.is_secure() else 'http://'
    abstract_db_url = protocol + get_current_site(request).domain + reverse(
        'annotation_store:api_root')
    logger.debug("DEBUG - Abstract Database URL: " + abstract_db_url)
    original = {
        'user_id':
        user_id,
        'username':
        user_name,
        'is_instructor':
        request.LTI['is_staff'],
        'collection':
        assignment_id,
        'course':
        course_id,
        'object':
        object_id,
        'target_object':
        targ_obj,
        'token':
        retrieve_token(user_id, assignment.annotation_database_apikey,
                       assignment.annotation_database_secret_token),
        'assignment':
        assignment,
        'roles': [str(role) for role in roles],
        'instructions':
        assignment_target.target_instructions,
        'abstract_db_url':
        abstract_db_url,
        'session':
        request.session.session_key,
        'org':
        settings.ORGANIZATION,
        'logger_url':
        settings.ANNOTATION_LOGGER_URL,
    }
    if not assignment.object_before(object_id) is None:
        original['prev_object'] = assignment.object_before(object_id)
        original['assignment_target'] = assignment_target

    if not assignment.object_after(object_id) is None:
        original['next_object'] = assignment.object_after(object_id)
        original['assignment_target'] = assignment_target

    if targ_obj.target_type == 'vd':
        srcurl = targ_obj.target_content
        if 'youtu' in srcurl:
            typeSource = 'video/youtube'
        else:
            disassembled = urlparse(srcurl)
            file_ext = os.path.splitext(os.path.basename(disassembled.path))[1]
            typeSource = 'video/' + file_ext.replace('.', '')
        original.update({'typeSource': typeSource})
    elif targ_obj.target_type == 'ig':
        original.update({'osd_json': targ_obj.target_content})
        viewtype = assignment_target.get_view_type_for_mirador()
        canvas_id = assignment_target.get_canvas_id_for_mirador()

        if viewtype is not None:
            original.update({'viewType': viewtype})
        if canvas_id is not None:
            original.update({'canvas_id': canvas_id})

    if assignment_target.target_external_css:
        original.update({'custom_css': assignment_target.target_external_css})
    elif course_obj.course_external_css_default:
        original.update({'custom_css': course_obj.course_external_css_default})

    original.update(
        {'dashboard_hidden': assignment_target.get_dashboard_hidden()})

    original.update(
        {'transcript_hidden': assignment_target.get_transcript_hidden()})

    original.update(
        {'transcript_download': assignment_target.get_transcript_download()})

    original.update({'video_download': assignment_target.get_video_download()})

    get_paras = {}
    for k in request.GET.keys():
        get_paras[k] = request.GET[k]

    original.update(get_paras)
    return render(request, '%s/detail.html' % targ_obj.target_type, original)
Esempio n. 3
0
def access_annotation_target(
    request,
    course_id,
    assignment_id,
    object_id,
    user_id=None,
    user_name=None,
    roles=None,
):
    """
    Renders an assignment page
    """
    if user_id is None:
        user_name = request.LTI["hx_user_name"]
        user_id = request.LTI["hx_user_id"]
        roles = request.LTI["hx_roles"]
    try:
        assignment = Assignment.objects.get(assignment_id=assignment_id)
        targ_obj = TargetObject.objects.get(pk=object_id)
        assignment_target = AssignmentTargets.objects.get(
            assignment=assignment, target_object=targ_obj)
        object_uri = targ_obj.get_target_content_uri()
        course_obj = LTICourse.objects.get(course_id=course_id)
    except Assignment.DoesNotExist or TargetObject.DoesNotExist:
        logger.error(
            "User attempted to access an Assignment or Target Object that does not exist: assignment_id={assignment_id} object_id={object_id}"
            .format(assignment_id=assignment_id, object_id=object_id))
        raise AnnotationTargetDoesNotExist(
            "Assignment or target object does not exist")

    is_instructor = request.LTI.get("is_staff", False)
    if not is_instructor and not assignment.is_published:
        raise PermissionDenied(
            "Permission to access unpublished assignment by a non-instructor is denied"
        )

    try:
        hide_sidebar = request.LTI["launch_params"][
            "custom_hide_sidebar_instance"].split(",")
    except:
        hide_sidebar = []

    save_session(
        request,
        collection_id=assignment_id,
        object_id=object_id,
        object_uri=object_uri,
        context_id=course_id,
    )

    # Dynamically pass in the address that the detail view will use to fetch annotations.
    # there's definitely a more elegant way (or a library function) to do this.
    # also, we may want to consider denying if theres no ssl
    protocol = "https://" if request.is_secure() else "http://"
    abstract_db_url = (protocol + get_current_site(request).domain +
                       reverse("annotation_store:api_root"))
    logger.debug("DEBUG - Abstract Database URL: " + abstract_db_url)
    original = {
        "user_id":
        user_id,
        "username":
        user_name,
        "is_instructor":
        request.LTI["is_staff"],
        "collection":
        assignment_id,
        "course":
        course_id,
        "object":
        object_id,
        "target_object":
        targ_obj,
        "token":
        retrieve_token(
            user_id,
            assignment.annotation_database_apikey,
            assignment.annotation_database_secret_token,
        ),
        "assignment":
        assignment,
        "roles": [str(role) for role in roles],
        "instructions":
        assignment_target.target_instructions,
        "abstract_db_url":
        abstract_db_url,
        "session":
        request.session.session_key,
        "org":
        settings.ORGANIZATION,
        "logger_url":
        settings.ANNOTATION_LOGGER_URL,
        "accessibility":
        settings.ACCESSIBILITY,
        "hide_sidebar_instance":
        hide_sidebar,
        "is_graded":
        request.LTI["launch_params"].get("lis_outcome_service_url", None)
        is not None,
    }
    if not assignment.object_before(object_id) is None:
        original["prev_object"] = assignment.object_before(object_id)
        original["assignment_target"] = assignment_target

    if not assignment.object_after(object_id) is None:
        original["next_object"] = assignment.object_after(object_id)
        original["assignment_target"] = assignment_target

    if targ_obj.target_type == "vd":
        srcurl = targ_obj.target_content
        if "youtu" in srcurl:
            typeSource = "video/youtube"
        else:
            disassembled = urlparse(srcurl)
            file_ext = os.path.splitext(os.path.basename(disassembled.path))[1]
            typeSource = "video/" + file_ext.replace(".", "")
        original.update({"typeSource": typeSource})
    elif targ_obj.target_type == "ig":
        original.update({"osd_json": targ_obj.target_content})
        viewtype = assignment_target.get_view_type_for_mirador()
        canvas_id = assignment_target.get_canvas_id_for_mirador()
        logger.debug("CANVAS: %s" % canvas_id)

        if viewtype is not None:
            original.update({"viewType": viewtype})
        if canvas_id is not None:
            original.update({"canvas_id": canvas_id})

    if assignment_target.target_external_css:
        original.update({"custom_css": assignment_target.target_external_css})
    elif course_obj.course_external_css_default:
        original.update({"custom_css": course_obj.course_external_css_default})

    original.update(
        {"dashboard_hidden": assignment_target.get_dashboard_hidden()})

    original.update(
        {"transcript_hidden": assignment_target.get_transcript_hidden()})

    original.update(
        {"transcript_download": assignment_target.get_transcript_download()})

    original.update({"video_download": assignment_target.get_video_download()})

    get_paras = {}
    for k in request.GET.keys():
        get_paras[k] = request.GET[k]

    original.update(get_paras)
    if (targ_obj.target_type == "tx"
            or targ_obj.target_type == "ig") and assignment.use_hxighlighter:
        return render(request,
                      "%s/detail_hxighlighter.html" % targ_obj.target_type,
                      original)
    else:
        return render(request, "%s/detail.html" % targ_obj.target_type,
                      original)
Esempio n. 4
0
def edit_course(request, id):
    course = get_object_or_404(LTICourse, pk=id)
    user_scope = request.LTI.get('hx_user_scope', None)
    if request.method == "POST":
        form = CourseForm(request.POST, instance=course)
        if form.is_valid():
            course = form.save()
            course.save()
            # this removes an administrator if they were checked off the list
            admins = course.course_admins.all()
            selected_list = request.POST.getlist(
                'select_existing_user') + request.POST['new_admin_list'].split(
                    ',')
            for admin in admins:
                if admin.user.username not in selected_list:
                    course.course_admins.remove(admin)
                else:
                    selected_list.remove(admin.user.username)
            course.save()

            # this will create an item in the database so when the user
            # that was just added as an admin logs in, they get added
            # to the list of admins in the course.
            if len(selected_list) > 0:
                for name in selected_list:
                    if str(name.strip()) != "":
                        try:
                            new_course_admin = LTICourseAdmin(
                                admin_unique_identifier=name,
                                new_admin_course_id=course.course_id)
                            new_course_admin.save()
                        except:
                            # admin already pending
                            logger.info("Admin already pending.")

            # save the course name to the session so it auto-populate later.
            save_session(request, course_name=course.course_name)

            messages.success(request, 'Course was successfully edited!')
            url = reverse(
                'hx_lti_initializer:course_admin_hub'
            ) + '?resource_link_id=%s' % request.LTI['resource_link_id']
            return redirect(url)
        else:
            raise PermissionDenied('Invalid course form submitted')
    else:
        form = CourseForm(instance=course, user_scope=user_scope)

    try:
        pending_admins = LTICourseAdmin.objects.filter(
            new_admin_course_id=course.course_id)
    except:
        pending_admins = None

    return render(
        request, 'hx_lti_initializer/edit_course.html', {
            'form': form,
            'user': request.user,
            'pending': pending_admins,
            'org': settings.ORGANIZATION,
            'is_instructor': request.LTI['is_staff'],
        })
Esempio n. 5
0
def edit_course(request, id):
    course = get_object_or_404(LTICourse, pk=id)
    user_scope = request.LTI.get("hx_user_scope", None)
    if request.method == "POST":
        form = CourseForm(request.POST, instance=course)
        if form.is_valid():
            course = form.save()
            course.save()
            # this removes an administrator if they were checked off the list
            admins = course.course_admins.all()
            selected_list = request.POST.getlist(
                "select_existing_user") + request.POST["new_admin_list"].split(
                    ",")
            for admin in admins:
                if admin.user.username not in selected_list:
                    course.course_admins.remove(admin)
                else:
                    selected_list.remove(admin.user.username)
            course.save()

            # this will create an item in the database so when the user
            # that was just added as an admin logs in, they get added
            # to the list of admins in the course.
            if len(selected_list) > 0:
                for name in selected_list:
                    if str(name.strip()) != "":
                        try:
                            new_course_admin = LTICourseAdmin(
                                admin_unique_identifier=name,
                                new_admin_course_id=course.course_id,
                            )
                            new_course_admin.save()
                        except:
                            # admin already pending
                            logger.info("Admin already pending.")

            # save the course name to the session so it auto-populate later.
            save_session(request, course_name=course.course_name)

            messages.success(request, "Course was successfully edited!")
            url = reverse("hx_lti_initializer:course_admin_hub"
                          ) + "?resource_link_id={}&utm_source={}".format(
                              request.LTI["resource_link_id"],
                              request.session.session_key)
            return redirect(url)
    else:
        form = CourseForm(instance=course, user_scope=user_scope)

    try:
        pending_admins = LTICourseAdmin.objects.filter(
            new_admin_course_id=course.course_id)
    except:
        pending_admins = None

    return render(
        request,
        "hx_lti_initializer/edit_course.html",
        {
            "form": form,
            "user": request.user,
            "pending": pending_admins,
            "org": settings.ORGANIZATION,
            "is_instructor": request.LTI["is_staff"],
        },
    )