Example #1
0
    def handle(self, *args, **options):
        course_id = options.get('course_id')
        term_id = options.get('term_id')
        name = options.get('name')

        prefixed_course_id = canvas_id_to_incremented_id(course_id)
        prefixed_term_id = canvas_id_to_incremented_id(term_id)

        try:
            term_obj = AcademicTerms.objects.get(id=prefixed_term_id)
        except AcademicTerms.DoesNotExist:
            self.stdout.write(f"Error: Term {term_id} does not exists.")
            return

        is_new = False
        try:
            course_obj = Course.objects.get(id=prefixed_course_id)
            self.stdout.write(f"Updating course {course_id}")
        except Course.DoesNotExist:
            course_obj = Course(id=prefixed_course_id)
            self.stdout.write(f"Creating course {course_id}")
            is_new = True

        course_obj.canvas_id = course_id
        course_obj.name = name
        course_obj.term_id = term_obj
        course_obj.save()

        if is_new:
            self.stdout.write("Creating course view options")
            course_view_obj = CourseViewOption(course_id=prefixed_course_id,
                                               show_resources_accessed=True,
                                               show_assignment_planning=True,
                                               show_grade_distribution=True)
            course_view_obj.save()
Example #2
0
def extract_launch_variables_for_tool_use(request, message_launch):
    launch_data = message_launch.get_launch_data()
    logger.debug(f'lti launch data {launch_data}')
    custom_params = launch_data['https://purl.imsglobal.org/spec/lti/claim/custom']
    logger.debug(f'lti_custom_param {custom_params}')
    if not custom_params:
        raise Exception(
            f'You need to have custom parameters configured on your LTI Launch. Please see the LTI installation guide on the Github Wiki for more information.'
        )
    course_name = launch_data['https://purl.imsglobal.org/spec/lti/claim/context']['title']
    roles = launch_data['https://purl.imsglobal.org/spec/lti/claim/roles']
    username = custom_params['user_username']
    course_id = custom_params['canvas_course_id']
    canvas_course_long_id = canvas_id_to_incremented_id(course_id)
    canvas_user_id = custom_params['canvas_user_id']
    canvas_user_long_id = canvas_id_to_incremented_id(canvas_user_id)
    if 'email' not in launch_data.keys():
        logger.info('Possibility that LTI launch by Instructor/admin becoming Canvas Test Student')
        error_message = 'Student view is not available for My Learning Analytics.'
        raise Exception(error_message)

    email = launch_data['email']
    first_name = launch_data['given_name']
    last_name = launch_data['family_name']
    full_name = launch_data['name']
    user_sis_id = launch_data['https://purl.imsglobal.org/spec/lti/claim/lis']['person_sourcedid']

    # Add user to DB if not there; avoids Django redirection to login page
    try:
        user_obj = User.objects.get(username=username)
        # update
        user_obj.first_name = first_name
        user_obj.last_name = last_name
        user_obj.email = email
        user_obj.save()
    except User.DoesNotExist:
        password = ''.join(random.sample(string.ascii_letters, settings.RANDOM_PASSWORD_DEFAULT_LENGTH))
        user_obj = User.objects.create_user(username=username, email=email, password=password, first_name=first_name,
                                            last_name=last_name)
    user_obj.backend = 'django.contrib.auth.backends.ModelBackend'
    django.contrib.auth.login(request, user_obj)
    user_roles = course_user_roles(roles, username)
    is_instructor = check_if_instructor(user_roles, username, course_id)

    try:
        Course.objects.get(canvas_id=course_id)
    except ObjectDoesNotExist:
        if is_instructor or user_obj.is_staff:
            Course.objects.create(id=canvas_course_long_id, canvas_id=course_id, name=course_name)
            CourseViewOption.objects.create(course_id=canvas_course_long_id)
        if is_instructor:
            MylaUser.objects.create(name=full_name, sis_name=username,
                                    course_id=canvas_course_long_id,
                                    user_id=canvas_user_long_id, sis_id=user_sis_id,
                                    enrollment_type=MylaUser.EnrollmentType.TEACHER)
    return course_id
Example #3
0
    def handle(self, *args, **options):
        course_id = options.get('course_id')
        term_id = options.get('term_id')
        name = options.get('name')
        date_start = options.get('date_start')
        if date_start is not None:
            date_start = datetime.strptime(
                date_start, '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.UTC)
        date_end = options.get('date_end')
        if date_end is not None:
            date_end = datetime.strptime(
                date_end, '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.UTC)

        prefixed_course_id = canvas_id_to_incremented_id(course_id)
        if term_id is not None:
            prefixed_term_id = canvas_id_to_incremented_id(term_id)
            try:
                term_obj = AcademicTerms.objects.get(id=prefixed_term_id)
            except AcademicTerms.DoesNotExist:
                self.stdout.write(f"Error: Term {term_id} does not exists.")
                return
        else:
            term_obj = None

        is_new = False
        try:
            course_obj = Course.objects.get(id=prefixed_course_id)
            self.stdout.write(f"Updating course {course_id}")
        except Course.DoesNotExist:
            course_obj = Course(id=prefixed_course_id)
            self.stdout.write(f"Creating course {course_id}")
            is_new = True

        course_obj.canvas_id = course_id
        course_obj.term = term_obj
        course_obj.name = name
        course_obj.date_start = date_start
        course_obj.date_end = date_end
        course_obj.save()

        if is_new:
            self.stdout.write("Creating course view options")
            course_view_obj = CourseViewOption(
                course_id=prefixed_course_id,
                show_resources_accessed=True,
                show_assignment_planning_v1=True,
                show_assignment_planning=True,
                show_grade_distribution=True)
            course_view_obj.save()
Example #4
0
def update_user_default_selection_for_views(request, course_id=0):
    logger.info(update_user_default_selection_for_views.__name__)
    course_id = canvas_id_to_incremented_id(course_id)
    current_user = request.user.get_username()
    default_selection = json.loads(request.body.decode("utf-8"))
    logger.info(default_selection)
    default_type = list(default_selection.keys())[0]
    default_type_value = default_selection.get(default_type)
    logger.info(
        f"request to set default for type: {default_type} and default_type value: {default_type_value}"
    )
    # json for eventlog
    data = {
        "course_id": course_id,
        "default_type": default_type,
        "default_value": default_type_value
    }
    eventlog(request.user,
             EventLogTypes.EVENT_VIEW_SET_DEFAULT.value,
             extra=data)
    key = 'default'
    try:
        obj, create_or_update_bool = UserDefaultSelection.objects.set_user_defaults(
            int(course_id), current_user, default_type, default_type_value)
        logger.info(
            f"""setting default returns with success with response {obj.__dict__} and entry created or Updated: {create_or_update_bool}
                        for user {current_user} in course {course_id} """)
        value = 'success'
    except (ObjectDoesNotExist, Exception) as e:
        logger.info(
            f"updating default failed due to {e} for user {current_user} in course: {course_id} "
        )
        value = 'fail'
    return HttpResponse(json.dumps({key: value}),
                        content_type='application/json')
Example #5
0
def get_user_default_selection(request, course_id=0):
    logger.info(get_user_default_selection.__name__)
    course_id = canvas_id_to_incremented_id(course_id)
    user_sis_name = request.user.get_username()
    default_view_type = request.GET.get('default_type')
    key = 'default'
    no_user_default_response = json.dumps({key: ''})
    logger.info(
        f"the default option request from user {user_sis_name} in course {course_id} of type: {default_view_type}"
    )
    default_value = UserDefaultSelection.objects.get_user_defaults(
        int(course_id), user_sis_name, default_view_type)
    logger.info(
        f"""default option check returned from DB for user: {user_sis_name} course {course_id} and type:
                    {default_view_type} is {default_value}""")
    if not default_value:
        logger.info(
            f"user {user_sis_name} in course {course_id} don't have any defaults values set type {default_view_type}"
        )
        return HttpResponse(no_user_default_response,
                            content_type='application/json')
    result = json.dumps({key: default_value})
    logger.info(
        f"user {user_sis_name} in course {course_id} for type {default_view_type} defaults: {result}"
    )
    return HttpResponse(result, content_type='application/json')
Example #6
0
def current_user_incremented_course_id(request):
    course_id = str(request.resolver_match.kwargs.get('course_id'))
    if not course_id:
        logger.info(f"Course ID could not be determined from request, attempting to look up for user {request.user.username}")
        course_id = db_util.get_default_user_course_id(request.user.username)
    incremented_course_id = db_util.canvas_id_to_incremented_id(course_id)
    return {'current_user_incremented_course_id': incremented_course_id}
Example #7
0
def grade_distribution(request: HttpRequest, course_id: 0) -> HttpResponse:
    logger.info(grade_distribution.__name__)

    course_id = canvas_id_to_incremented_id(course_id)

    current_user = request.user.get_username()

    grade_score_sql = f"""select current_grade,
       (select show_grade_counts From course where id=%(course_id)s) as show_number_on_bars,
    (select current_grade from user where sis_name=%(current_user)s and course_id=%(course_id)s) as current_user_grade
        from user where course_id=%(course_id)s and enrollment_type='StudentEnrollment';
                    """
    df = pd.read_sql(grade_score_sql,
                     conn,
                     params={
                         "current_user": current_user,
                         'course_id': course_id
                     })
    if df.empty or df['current_grade'].isnull().all():
        return HttpResponse(json.dumps({}), content_type='application/json')

    df['tot_students'] = df.shape[0]
    df = df[df['current_grade'].notnull()]
    df['current_grade'] = df['current_grade'].astype(float)
    df['grade_avg'] = df['current_grade'].mean().round(2)
    df['median_grade'] = df['current_grade'].median().round(2)
    df['show_number_on_bars'] = df['show_number_on_bars'].apply(
        lambda x: True if x == 1 else False)

    df.sort_values(by=['current_grade'], inplace=True)
    df.reset_index(drop=True, inplace=True)
    grades = df['current_grade'].values.tolist()
    logger.debug(f"Grades distribution: {grades}")
    BinningGrade = find_binning_grade_value(grades)
    if BinningGrade is not None and not BinningGrade.binning_all:
        df['current_grade'] = df['current_grade'].replace(
            df['current_grade'].head(BinningGrade.index), BinningGrade.value)
    df['show_dash_line'] = show_dashed_line(df['current_grade'].iloc[0],
                                            BinningGrade)

    if df[df['current_grade'] > 100.0].shape[0] > 0:
        df['graph_upper_limit'] = int(
            (5 * round(float(df['current_grade'].max()) / 5) + 5))
    else:
        df['current_grade'] = df['current_grade'].apply(lambda x: 99.99
                                                        if x == 100.00 else x)
        df['graph_upper_limit'] = 100

    # json for eventlog
    data = {
        "course_id": course_id,
        "show_number_on_bars": df['show_number_on_bars'].values[0]
    }
    eventlog(request.user,
             EventLogTypes.EVENT_VIEW_GRADE_DISTRIBUTION.value,
             extra=data)

    return HttpResponse(df.to_json(orient='records'))
Example #8
0
def get_course_info(request, course_id=0):
    """Returns JSON data about a course

    :param request: HTTP Request
    :type request: Request
    :param course_id: Unizin Course ID, defaults to 0
    :param course_id: int, optional
    :return: JSON to be used
    :rtype: str
    """
    course_id = canvas_id_to_incremented_id(course_id)
    today = timezone.now()

    try:
        course = Course.objects.get(id=course_id)
    except ObjectDoesNotExist:
        return HttpResponse("{}")

    course_resource_list = []
    try:
        resource_list = Resource.objects.get_course_resource_type(course_id)
        if resource_list is not None:
            logger.info(f"Course {course_id} resources data type are: {resource_list}")
            resource_defaults = settings.RESOURCE_VALUES
            for item in resource_list:
                result = utils.look_up_key_for_value(resource_defaults, item)
                if result is not None:
                    course_resource_list.append(result.capitalize())
            logger.info(f"Mapped generic resource types in a course {course_id}: {course_resource_list}")
    except(ObjectDoesNotExist,Exception) as e:
        logger.info(f"getting the course {course_id} resources types has errors due to:{e}")

    course_resource_list.sort()

    resp = model_to_dict(course)

    course_start, course_end = course.get_course_date_range()

    current_week_number = math.ceil((today - course_start).days/7)
    total_weeks = math.ceil((course_end - course_start).days/7)

    if course.term is not None:
        resp['term'] = model_to_dict(course.term)
    else:
        resp['term'] = None

    # Have a fixed maximum number of weeks
    if total_weeks > settings.MAX_DEFAULT_WEEKS:
        logger.debug(f'{total_weeks} is greater than {settings.MAX_DEFAULT_WEEKS} setting total weeks to default.')
        total_weeks = settings.MAX_DEFAULT_WEEKS

    resp['current_week_number'] = current_week_number
    resp['total_weeks'] = total_weeks
    resp['course_view_options'] = CourseViewOption.objects.get(course=course).json(include_id=False)
    resp['resource_types'] = course_resource_list

    return HttpResponse(json.dumps(resp, default=str))
Example #9
0
    def execute_graphql_request(self, request, data, query, variables, operation_name, show_graphiql=False):
        if operation_name == 'Assignment':
            event_data = {
                "course_id": canvas_id_to_incremented_id(variables['courseId']),
            }
            eventlog(request.user, EventLogTypes.EVENT_VIEW_ASSIGNMENT_PLANNING_WITH_GOAL_SETTING.value, extra=event_data)


        return super(DashboardGraphQLView, self).execute_graphql_request(
            request, data, query, variables, operation_name, show_graphiql
        )
Example #10
0
    def handle(self, *args, **options):
        term_id = options.get('term_id')
        name = options.get('name')
        date_start = datetime.strptime(options.get('date_start'), '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.UTC)
        date_end = datetime.strptime(options.get('date_end'), '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.UTC)

        prefixed_term_id = canvas_id_to_incremented_id(term_id)

        try:
            term_obj = AcademicTerms.objects.get(id=prefixed_term_id)
            self.stdout.write (f"Updating term {term_id}")
        except AcademicTerms.DoesNotExist:
            term_obj = AcademicTerms(id=prefixed_term_id)
            self.stdout.write (f"Creating term {term_id}")

        term_obj.canvas_id = term_id
        term_obj.name = name
        term_obj.date_start = date_start
        term_obj.date_end = date_end
        term_obj.save()
Example #11
0
def update_user_default_selection_for_views(request, course_id=0):
    """

    :param request: HTTP `PUT` req.; body should contain a single JSON pair, `{"key": value}`
    :param course_id: Integer Canvas course ID number, typically six digits or less.
    :return: HttpResponse containing `{"default": "success"}` or `{"default": "fail"}`
    """
    logger.info(update_user_default_selection_for_views.__name__)
    course_id = canvas_id_to_incremented_id(course_id)
    current_user = request.user.get_username()
    default_selection = json.loads(request.body.decode("utf-8"))
    logger.info(default_selection)
    default_type = list(default_selection.keys())[0]
    default_type_value = default_selection.get(default_type)
    logger.info(
        f"request to set default for type: {default_type} and default_type value: {default_type_value}"
    )
    # json for eventlog
    data = {
        "course_id": course_id,
        "default_type": default_type,
        "default_value": default_type_value
    }
    eventlog(request.user,
             EventLogTypes.EVENT_VIEW_SET_DEFAULT.value,
             extra=data)
    key = 'default'
    try:
        obj, create_or_update_bool = UserDefaultSelection.objects. \
            set_user_defaults(int(course_id), current_user, default_type, default_type_value)
        logger.info(
            f"""setting default returns with success with response {obj.__dict__} and entry created or Updated: {create_or_update_bool}
                        for user {current_user} in course {course_id} """)
        value = 'success'
    except (ObjectDoesNotExist, Exception) as e:
        logger.info(
            f"updating default failed due to {e} for user {current_user} in course: {course_id} "
        )
        value = 'fail'
    return HttpResponse(json.dumps({key: value}),
                        content_type='application/json')
Example #12
0
def grade_distribution(request, course_id=0):
    logger.info(grade_distribution.__name__)

    course_id = canvas_id_to_incremented_id(course_id)

    current_user = request.user.get_username()
    grade_score_sql = "select current_grade,(select current_grade from user where sis_name=" \
                      "%(current_user)s and course_id=%(course_id)s) as current_user_grade " \
                      "from user where course_id=%(course_id)s and enrollment_type='StudentEnrollment';"
    df = pd.read_sql(grade_score_sql,
                     conn,
                     params={
                         "current_user": current_user,
                         'course_id': course_id
                     })
    if df.empty or df['current_grade'].isnull().all():
        return HttpResponse(json.dumps({}), content_type='application/json')
    number_of_students = df.shape[0]
    df = df[df['current_grade'].notnull()]
    df['current_grade'] = df['current_grade'].astype(float)
    if df[df['current_grade'] > 100.0].shape[0] > 0:
        df['graph_upper_limit'] = int(
            (5 * round(float(df['current_grade'].max()) / 5) + 5))
    else:
        df['current_grade'] = df['current_grade'].apply(lambda x: 99.99
                                                        if x == 100.00 else x)
        df['graph_upper_limit'] = 100
    average_grade = df['current_grade'].mean().round(2)
    df['tot_students'] = number_of_students
    df['grade_avg'] = average_grade

    # json for eventlog
    data = {"course_id": course_id}
    eventlog(request.user,
             EventLogTypes.EVENT_VIEW_GRADE_DISTRIBUTION.value,
             extra=data)

    return HttpResponse(df.to_json(orient='records'))
Example #13
0
def assignments(request, course_id=0):

    logger.info(assignments.__name__)

    course_id = canvas_id_to_incremented_id(course_id)

    current_user = request.user.get_username()
    df_default_display_settings()

    percent_selection = float(request.GET.get('percent', '0.0'))

    # json for eventlog
    data = {"course_id": course_id, "percent_selection": percent_selection}
    eventlog(request.user,
             EventLogTypes.EVENT_VIEW_ASSIGNMENT_PLANNING.value,
             extra=data)

    logger.info(
        'selection from assignment Planning {}'.format(percent_selection))

    assignments_in_course = get_course_assignments(course_id)

    if assignments_in_course.empty:
        return HttpResponse(json.dumps([]), content_type='application/json')

    assignment_submissions = get_user_assignment_submission(
        current_user, assignments_in_course, course_id)

    df = pd.merge(assignments_in_course,
                  assignment_submissions,
                  on='assignment_id',
                  how='left')
    if df.empty:
        logger.info(
            'There are no assignment data in the course %s for user %s ' %
            (course_id, current_user))
        return HttpResponse(json.dumps([]), content_type='application/json')

    df.sort_values(by='due_date', inplace=True)
    df.drop(columns=['assignment_id', 'due_date', 'grp_id'], inplace=True)
    df.drop_duplicates(keep='first', inplace=True)

    # instructor might not ever see the avg score as he don't have grade in assignment. we don't have role described in the flow to open the gates for him
    if not request.user.is_superuser:
        df['avg_score'] = df.apply(no_show_avg_score_for_ungraded_assignments,
                                   axis=1)
    df['avg_score'] = df['avg_score'].fillna('Not available')

    # operate on dataframe copy to prevent Pandas "SettingWithCopyWarning" warning
    df_progressbar = df.loc[df['towards_final_grade'] > 0.0].copy()
    df_progressbar[['score']] = df_progressbar[['score']].astype(float)
    df_progressbar['graded'] = df_progressbar['graded'].fillna(False)
    df_progressbar['submitted'] = df_progressbar['submitted'].fillna(False)
    df_progressbar[['score']] = df_progressbar[['score']].astype(float)
    df_progressbar['percent_gotten'] = df_progressbar.apply(
        lambda x: user_percent(x), axis=1)
    df_progressbar.sort_values(by=['graded', 'due_date_mod'],
                               ascending=[False, True],
                               inplace=True)
    df_progressbar.reset_index(inplace=True)
    df_progressbar.drop(columns=['index'], inplace=True)

    assignment_data = {}
    assignment_data['progress'] = json.loads(
        df_progressbar.to_json(orient='records'))

    # Group the data according the assignment prep view
    df_plan = df.loc[df['towards_final_grade'] >= percent_selection].copy()
    df_plan.reset_index(inplace=True)
    df_plan.drop(columns=['index'], inplace=True)
    logger.debug('The Dataframe for the assignment planning %s ' % df_plan)
    grouped = df_plan.groupby(['week', 'due_dates'])

    assignment_list = []
    for name, group in grouped:
        # name is a tuple of (week,due_date) => (1,'06/23/2018')
        # group is a dataframe based on grouping by week,due_date
        dic = {}
        group.drop(['week', 'due_dates'], axis=1, inplace=True)
        dic['week'] = name[0]
        dic['due_date'] = name[1]
        dic['assign'] = json.loads(group.to_json(orient='records'))
        assignment_list.append(dic)
    week_list = set()
    for item in assignment_list:
        week_list.add(item['week'])
    weeks = sorted(week_list)
    full = []
    for i, week in enumerate(weeks):
        data = {}
        data["week"] = np.uint64(week).item()
        data["id"] = i + 1
        dd_items = data["due_date_items"] = []
        for item in assignment_list:
            assignment_due_date_grp = {}
            if item['week'] == week:
                assignment_due_date_grp['due_date'] = item['due_date']
                assignment_due_date_grp['assignment_items'] = item['assign']
                dd_items.append(assignment_due_date_grp)
        full.append(data)
    assignment_data['plan'] = json.loads(json.dumps(full))
    return HttpResponse(json.dumps(assignment_data),
                        content_type='application/json')
Example #14
0
 def save_model(self, request: HttpRequest, obj: Course, form: ModelForm,
                change: AlwaysChangedModelForm) -> super:
     obj.id = canvas_id_to_incremented_id(obj.canvas_id)
     return super(CourseAdmin, self).save_model(request, obj, form, change)
Example #15
0
def resource_access_within_week(request, course_id=0):

    course_id = canvas_id_to_incremented_id(course_id)

    current_user = request.user.get_username()

    logger.debug("current_user="******",")

    filter_list = []
    for filter_value in filter_values:
        if filter_value != '':
            filter_list.extend(RESOURCE_VALUES[filter_value.lower()]['types'])

    # json for eventlog
    data = {
        "week_num_start": week_num_start,
        "week_num_end": week_num_end,
        "grade": grade,
        "course_id": course_id,
        "resource_type": filter_values
    }
    eventlog(request.user,
             EventLogTypes.EVENT_VIEW_RESOURCE_ACCESS.value,
             extra=data)

    # get total number of student within the course_id
    total_number_student_sql = "select count(*) from user where course_id = %(course_id)s and enrollment_type='StudentEnrollment'"
    if (grade == GRADE_A):
        total_number_student_sql += " and current_grade >= 90"
    elif (grade == GRADE_B):
        total_number_student_sql += " and current_grade >= 80 and current_grade < 90"
    elif (grade == GRADE_C):
        total_number_student_sql += " and current_grade >= 70 and current_grade < 80"

    total_number_student_df = pd.read_sql(total_number_student_sql,
                                          conn,
                                          params={"course_id": course_id})
    total_number_student = total_number_student_df.iloc[0, 0]
    logger.info(f"course_id {course_id} total student={total_number_student}")
    if total_number_student == 0:
        logger.info(
            f"There are no students in the percent grade range {grade} for course {course_id}"
        )
        return HttpResponse("{}")

    course_date_start = get_course_date_start(course_id)

    start = course_date_start + timedelta(days=(week_num_start * 7))
    end = course_date_start + timedelta(days=(week_num_end * 7))
    logger.debug("course_start=" + str(course_date_start) + " start=" +
                 str(start) + " end=" + str(end))

    # get time range based on week number passed in via request

    sqlString = f"""SELECT a.resource_id as resource_id, r.resource_type as resource_type, r.name as resource_name, u.current_grade as current_grade, a.user_id as user_id
                    FROM resource r, resource_access a, user u, course c, academic_terms t
                    WHERE a.resource_id = r.resource_id and a.user_id = u.user_id
                    and a.course_id = c.id and c.term_id = t.id
                    and a.access_time > %(start_time)s
                    and a.access_time < %(end_time)s
                    and a.course_id = %(course_id)s
                    and u.course_id = %(course_id)s
                    and u.enrollment_type = 'StudentEnrollment' """

    startTimeString = start.strftime('%Y%m%d') + "000000"
    endTimeString = end.strftime('%Y%m%d') + "000000"
    logger.debug(sqlString)
    logger.debug("start time=" + startTimeString + " end_time=" +
                 endTimeString)
    df = pd.read_sql(sqlString,
                     conn,
                     params={
                         "start_time": startTimeString,
                         "end_time": endTimeString,
                         "course_id": course_id
                     })
    logger.debug(df)

    # return if there is no data during this interval
    if (df.empty):
        return HttpResponse("{}")

    # group by resource_id, and resource_name
    # reformat for output
    df['resource_id_name'] = df['resource_id'].astype(str).str.cat(
        df['resource_name'], sep=';')

    df = df.drop(['resource_id', 'resource_name'], axis=1)
    df.set_index(['resource_id_name'])
    # drop resource records when the resource has been accessed multiple times by one user
    df.drop_duplicates(inplace=True)

    # map point grade to letter grade
    df['grade'] = df['current_grade'].map(gpa_map)

    # calculate the percentage
    df['percent'] = df.groupby([
        'resource_id_name', 'grade'
    ])['resource_id_name'].transform('count') / total_number_student

    df = df.drop(['current_grade', 'user_id'], axis=1)
    # now only keep the resource access stats by grade level
    df.drop_duplicates(inplace=True)

    resource_id_name = df["resource_id_name"].unique()

    #df.reset_index(inplace=True)

    # zero filled dataframe with resource name as row name, and grade as column name
    output_df = pd.DataFrame(0.0,
                             index=resource_id_name,
                             columns=[
                                 GRADE_A, GRADE_B, GRADE_C, GRADE_LOW,
                                 NO_GRADE_STRING, RESOURCE_TYPE_STRING
                             ])
    output_df = output_df.rename_axis('resource_id_name')
    output_df = output_df.astype({RESOURCE_TYPE_STRING: str})

    for index, row in df.iterrows():
        # set value
        output_df.at[row['resource_id_name'], row['grade']] = row['percent']
        output_df.at[row['resource_id_name'],
                     RESOURCE_TYPE_STRING] = row[RESOURCE_TYPE_STRING]
    output_df.reset_index(inplace=True)

    # now insert person's own viewing records: what resources the user has viewed, and the last access timestamp
    selfSqlString = f"""select CONCAT(r.resource_id, ';', r.name) as resource_id_name, count(*) as self_access_count, max(a.access_time) as self_access_last_time 
                    from resource_access a, user u, resource r 
                    where a.user_id = u.user_id 
                    and a.resource_id = r.resource_id 
                    and u.sis_name=%(current_user)s 
                    and a.course_id = %(course_id)s
                    group by CONCAT(r.resource_id, ';', r.name)"""
    logger.debug(selfSqlString)
    logger.debug("current_user="******"current_user": current_user,
                             "course_id": course_id
                         })

    output_df = output_df.join(selfDf.set_index('resource_id_name'),
                               on='resource_id_name',
                               how='left')
    output_df["total_percent"] = output_df.apply(lambda row: row[
        GRADE_A] + row[GRADE_B] + row[GRADE_C] + row[GRADE_LOW] + row.NO_GRADE,
                                                 axis=1)

    if (grade != "all"):
        # drop all other grades
        grades = [GRADE_A, GRADE_B, GRADE_C, GRADE_LOW, NO_GRADE_STRING]
        for i_grade in grades:
            if (i_grade == grade):
                output_df["total_percent"] = output_df[i_grade]
            else:
                output_df = output_df.drop([i_grade], axis=1)

    output_df = output_df[output_df.resource_type.isin(filter_list)]

    # if no checkboxes are checked send nothing
    if (output_df.empty):
        return HttpResponse("{}")

    # only keep rows where total_percent > 0
    output_df = output_df[output_df.total_percent > 0]

    # time 100 to show the percentage
    output_df["total_percent"] *= 100
    # round all numbers to whole numbers
    output_df = output_df.round(0)

    output_df.fillna(0, inplace=True)  #replace null value with 0

    output_df[['resource_id_part', 'resource_name_part'
               ]] = output_df['resource_id_name'].str.split(';', expand=True)

    output_df['resource_name'] = output_df.apply(lambda row: (
        RESOURCE_ACCESS_CONFIG.get(row.resource_type).get("urls").get("prefix")
        + row.resource_id_part + RESOURCE_ACCESS_CONFIG.get(row.resource_type).
        get("urls").get("postfix") + CANVAS_FILE_ID_NAME_SEPARATOR + row.
        resource_name_part + CANVAS_FILE_ID_NAME_SEPARATOR + RESOURCE_VALUES.
        get(RESOURCE_VALUES_MAP.get(row.resource_type)).get('icon')),
                                                 axis=1)
    # RESOURCE_VALUES_MAP {'canvas': 'files', 'leccap': 'videos', 'mivideo': 'videos'}
    output_df['resource_type'] = output_df['resource_type'].replace(
        RESOURCE_VALUES_MAP)
    output_df.drop(
        columns=['resource_id_part', 'resource_name_part', 'resource_id_name'],
        inplace=True)

    logger.debug(output_df.to_json(orient='records'))

    return HttpResponse(output_df.to_json(orient='records'),
                        content_type='application/json')
Example #16
0
def update_course_info(request, course_id=0):
    """

    :param request: HTTP `PUT` req.; body should contain the JSON body…
    :param course_id: Integer Canvas course ID number, typically six digits or less.
    :return: JsonResponse containing `{"default": "success"}` or `{"default": "fail"}`
    """
    logger.info(update_course_info.__name__)

    if (request.method != 'PUT'):
        return JsonResponse({'error': 'Invalid request method.'}, status=400)

    course_id = canvas_id_to_incremented_id(course_id)
    current_user = request.user.get_username()

    bad_json_response = JsonResponse({'error': 'Request JSON malformed.'},
                                     status=400)

    try:
        request_data: dict = json.loads(request.body.decode('utf-8'))
    except JSONDecodeError:
        return bad_json_response

    schema = {
        '$schema': 'http://json-schema.org/draft-07/schema',
        'type': 'object',
        'additionalProperties': False,
        'properties': {
            'ap': {
                'type': 'object',
                'required': ['enabled'],
                'additionalProperties': False,
                'properties': {
                    'enabled': {
                        'type': 'boolean'
                    }
                }
            },
            'apv1': {
                'type': 'object',
                'required': ['enabled'],
                'additionalProperties': False,
                'properties': {
                    'enabled': {
                        'type': 'boolean'
                    }
                }
            },
            'gd': {
                'type': 'object',
                'required': ['enabled'],
                'additionalProperties': False,
                'properties': {
                    'enabled': {
                        'type': 'boolean'
                    },
                    'show_grade_counts': {
                        'type': 'boolean'
                    }
                }
            },
            'ra': {
                'type': 'object',
                'required': ['enabled'],
                'additionalProperties': False,
                'properties': {
                    'enabled': {
                        'type': 'boolean'
                    }
                }
            }
        },
        'minProperties': 1
    }

    try:
        jsonschema.validate(request_data, schema)
    except jsonschema.ValidationError:
        return bad_json_response

    # to translate short names returned by model back to original column names
    view_column_names: dict = view_names_mapping()

    view_settings: dict
    view_data: dict = {}
    success: bool = True  # always look on the bright side of life

    try:
        for (view_key, view_settings) in request_data.items():
            view_data[view_column_names[view_key]] = view_settings['enabled']
            if (view_key == 'gd'
                    and 'show_grade_counts' in view_settings.keys()):
                Course.objects.filter(pk=course_id).update(
                    show_grade_counts=view_settings['show_grade_counts'])

        CourseViewOption.objects.filter(pk=course_id).update(**view_data)
    except (ObjectDoesNotExist, Exception) as e:
        logger.info(
            f'updating course visualization options failed due to {e} for user {current_user} in course {course_id}'
        )
        success = False

    return JsonResponse({'default': 'success' if success else 'fail'},
                        status=200 if success else 500)
Example #17
0
 def save_model(self, request, obj, form, change):
     obj.id = canvas_id_to_incremented_id(obj.canvas_id)
     return super(CourseAdmin, self).save_model(request, obj, form, change)
Example #18
0
def grade_distribution(request, course_id=0):
    logger.info(grade_distribution.__name__)

    course_id = canvas_id_to_incremented_id(course_id)

    current_user = request.user.get_username()

    grade_score_sql = f"""select current_grade,
       (select show_grade_counts From course where id=%(course_id)s) as show_number_on_bars,
       (select current_grade from user where sis_name=%(current_user)s and course_id=%(course_id)s) as current_user_grade
       from user where course_id=%(course_id)s and enrollment_type=%(enrollment_type)s
       """
    df = pd.read_sql(grade_score_sql,
                     conn,
                     params={
                         'current_user': current_user,
                         'course_id': course_id,
                         'enrollment_type': 'StudentEnrollment'
                     })
    if df.empty or df.count().current_grade < 6:
        logger.info(
            f"Not enough students grades (only {df.count().current_grade}) in a course {course_id} to show the view"
        )
        return HttpResponse(json.dumps({}), content_type='application/json')

    grade_view_data = dict()
    summary = dict()
    summary['current_user_grade'] = df['current_user_grade'].values[0]
    summary['tot_students'] = df.shape[0]
    df = df[df['current_grade'].notnull()]
    df['current_grade'] = df['current_grade'].astype(float)
    summary['grade_avg'] = df['current_grade'].mean().round(2)
    summary['median_grade'] = df['current_grade'].median().round(2)
    summary['show_number_on_bars'] = False
    if df['show_number_on_bars'].values[0] == 1:
        summary['show_number_on_bars'] = True

    df.sort_values(by=['current_grade'], inplace=True)
    df.reset_index(drop=True, inplace=True)
    grades = df['current_grade'].values.tolist()
    logger.debug(f"Grades distribution: {grades}")
    BinningGrade = find_binning_grade_value(grades)
    if BinningGrade is not None and not BinningGrade.binning_all:
        scores_to_replace = df['current_grade'].head(
            BinningGrade.index).to_list()
        df['current_grade'] = df['current_grade'].replace(
            scores_to_replace, BinningGrade.value)
    summary['show_dash_line'] = show_dashed_line(df['current_grade'].iloc[0],
                                                 BinningGrade)

    if df[df['current_grade'] > 100.0].shape[0] > 0:
        summary['graph_upper_limit'] = int(
            (5 * round(float(df['current_grade'].max()) / 5) + 5))
    else:
        df['current_grade'] = df['current_grade'].apply(lambda x: 99.99
                                                        if x == 100.00 else x)
        summary['graph_upper_limit'] = 100

    grade_view_data['summary'] = summary
    grade_view_data['grades'] = df['current_grade'].values.tolist()

    # json for eventlog
    data = {
        "course_id": course_id,
        "show_number_on_bars": int(df['show_number_on_bars'].values[0])
    }
    eventlog(request.user,
             EventLogTypes.EVENT_VIEW_GRADE_DISTRIBUTION.value,
             extra=data)

    return HttpResponse(json.dumps(grade_view_data))