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()
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
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()
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')
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')
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}
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'))
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))
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 )
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()
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')
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'))
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')
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)
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')
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)
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)
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))