コード例 #1
0
 def get_page_type(self):
     if self.content_type in CourseOffering.communication_types():
         return Page.PAGE_TYPE_COMMUNICATION
     elif self.content_type in CourseOffering.assessment_types():
         return Page.PAGE_TYPE_ASSESSMENT
     else:
         return Page.PAGE_TYPE_CONTENT
コード例 #2
0
    def get(self, request, *args, **kwargs):
        # Setup list of days in course with initial values
        student_id = request.GET.get('student_id')
        resource_id = request.GET.get('resource_id')

        day_dict = {}
        our_tz = get_current_timezone()
        course_span = request.course_offering.end_date - request.course_offering.start_date
        for day_offset in range(course_span.days + 1):
            day = request.course_offering.start_date + timedelta(days=day_offset)
            day_dict[day] = {
                'day': day,
                'content_visits': 0,
                'communication_visits': 0,
                'assessment_visits': 0,
                'single_events': [],
                'submission_events': [],
            }

        # Get page visits optionally filtered by student and page
        page_visit_qs = PageVisit.objects.filter(page__course_offering=request.course_offering)
        if student_id:
            page_visit_qs = page_visit_qs.filter(lms_user_id=student_id)
        if resource_id:
            page_visit_qs = page_visit_qs.filter(page_id=resource_id)

        # Add the page visits to their corresponding entry
        for page_visit in page_visit_qs.values('visited_at', 'page__content_type'):
            visit_date = page_visit['visited_at'].astimezone(our_tz).date()
            if page_visit['page__content_type'] in CourseOffering.communication_types():
                day_dict[visit_date]['communication_visits'] += 1
            elif page_visit['page__content_type'] in CourseOffering.assessment_types():
                day_dict[visit_date]['assessment_visits'] += 1
            else:
                day_dict[visit_date]['content_visits'] += 1

        # Add the single and submission events to their corresponding entry
        for single_event in CourseSingleEvent.objects.filter(course_offering=request.course_offering):
            day_dict[single_event.event_date.date()]['single_events'].append(single_event.title)

        for submission_event in CourseSubmissionEvent.objects.filter(course_offering=request.course_offering):
            day_dict[submission_event.start_date.date()]['submission_events'].append("{} (start)".format(submission_event.title))
            day_dict[submission_event.end_date.date()]['submission_events'].append("{} (end)".format(submission_event.title))

        # Convert to array and serialize
        data = [v for v in day_dict.values()]
        data.sort(key=lambda day_data: day_data['day'])
        serializer = DailyPageVisitsSerializer(data, many=True)
        return Response(serializer.data)
コード例 #3
0
    def get(self, request, format=None):
        course_offering = self.request.course_offering
        course_start_dt = course_offering.start_datetime

        events_by_week_for_all_pages = [0] * course_offering.no_weeks
        non_content_types = CourseOffering.communication_types(
        ) + CourseOffering.assessment_types()
        page_queryset = Page.objects.filter(
            course_offering=course_offering).exclude(
                content_type__in=non_content_types).values(
                    'id', 'title', 'parent_id', 'content_type')
        total_events = 0
        for page in page_queryset:
            events_for_this_page = PageVisit.objects.filter(page_id=page['id'])
            events_by_week_for_this_page = [0] * course_offering.no_weeks
            total_events += events_for_this_page.count()
            for event in events_for_this_page:
                # Calculate the time since start of course for this event.  From this we can calculate the week.
                # (Note, it's ok for courses not to start on Monday.  In this case, the boundary of the course week won't
                # be the same as the inter-week boundary).
                td = event.visited_at - course_start_dt
                week = td.days // 7  # Integer division, the first week after the course starts is week 0.
                try:
                    # try is here to catch invalid week indexes caused by events outside of the course offering.
                    events_by_week_for_this_page[week] += 1
                    events_by_week_for_all_pages[week] += 1
                except IndexError:
                    pass
            page['weeks'] = events_by_week_for_this_page
            page['total'] = sum(
                events_by_week_for_this_page)  # Add along the row.

        # Total things for all pages and weeks.  Will appear in bottom right corner.
        events_by_week_for_all_pages.append(total_events)
        # Calculate percentages.
        for page in page_queryset:
            page['percent'] = Decimal(page['total'] * 100 /
                                      total_events) if total_events else 0

        results = {
            'page_set': list(page_queryset),
            'totals_by_week': events_by_week_for_all_pages,
        }
        serializer = CoursePagesetAndTotalsSerializer(data=results)

        return Response(serializer.initial_data)
コード例 #4
0
    def get(self, request, event_id, format=None):
        course_offering = self.request.course_offering
        repeating_event = get_object_or_404(CourseRepeatingEvent,
                                            pk=event_id,
                                            course_offering=course_offering)

        our_tz = get_current_timezone()
        course_start_dt = course_offering.start_datetime
        non_content_types = CourseOffering.communication_types(
        ) + CourseOffering.assessment_types()

        page_queryset = Page.objects.filter(
            course_offering=course_offering).exclude(
                content_type__in=non_content_types).values(
                    'id', 'title', 'parent_id', 'content_type')
        for page in page_queryset:
            page_visits_for_this_page = PageVisit.objects.filter(
                page_id=page['id'])
            visit_pairs_by_week = [[0, 0]
                                   for i in range(course_offering.no_weeks)]
            for visit in page_visits_for_this_page:
                # Calculate the time since start of course for this page visit.  From this we can calculate the week.
                # (Note, it's ok for courses not to start on Monday.  In this case, the boundary of the course week won't
                # be the same as the isoweek boundary).
                td = visit.visited_at - course_start_dt
                week = td.days // 7  # Integer division, the first week after the course starts is week 0.
                visit_date_in_local_tz = visit.visited_at.astimezone(
                    our_tz).date()
                day_of_week_of_visit = visit_date_in_local_tz.weekday()
                try:  # Is the date of the visit within the range defined by the course offering?
                    if day_of_week_of_visit < repeating_event.day_of_week:
                        visit_pairs_by_week[week][0] += 1
                    else:
                        visit_pairs_by_week[week][1] += 1
                except IndexError:
                    pass
            page['weeks'] = visit_pairs_by_week

        serializer = CourseContentPageEventSerializer(page_queryset, many=True)

        return Response(serializer.data)
コード例 #5
0
    def get(self, request, format=None):
        course_offering = self.request.course_offering

        users_set = LMSUser.objects.filter(
            course_offering=course_offering).order_by('lms_user_id')
        users_out = []
        assessments_set = Page.objects.filter(
            course_offering=course_offering,
            content_type__in=CourseOffering.assessment_types()).order_by(
                'pk').values('id', 'title')
        page_ids = tuple(a['id'] for a in assessments_set)
        for user in users_set:
            most_recent_attempts = {
            }  # Dict of attempts for this student, keyed by assessment id
            # Find all the attempts for this student
            attempts = SubmissionAttempt.objects.filter(lms_user__id=user.id,
                                                        page__in=page_ids)
            # Iterate over the attempts, recording the most recent attempt in the dict
            for attempt in attempts:
                page_id = attempt.page_id
                # If we don't have an attempt for this assessment, or this is a more recent attempt, store it.
                if page_id not in most_recent_attempts or attempt.attempted_at > most_recent_attempts[
                        page_id].attempted_at:
                    most_recent_attempts[page_id] = attempt
            # Take the dict of most recent attempts, and extract the grades.  No attempt gives a None grade.
            grades = {
                str(k): {
                    'pk': k,
                    'grade': v.grade
                }
                for k, v in most_recent_attempts.items()
            }
            users_out.append({
                'pk': user.id,
                'name': user.full_name(),
                'grades': grades
            })

        results = {
            'assessments': [{
                'pk': assessment['id'],
                'title': assessment['title']
            } for assessment in assessments_set],
            'users':
            users_out,
        }

        serializer = AssessmentUsersAndGradesSerializer(data=results)
        sd = serializer.initial_data

        return Response(sd)
コード例 #6
0
    def get_queryset(self):
        course_offering = self.request.course_offering
        # Was a week number specified? (week number is 1-based)
        week_num = self.kwargs.get('week_num')
        if week_num is not None:
            # Filter for assessment accesses within the week
            range_start = course_offering.start_datetime + datetime.timedelta(
                weeks=int(week_num) - 1)
            range_end = range_start + datetime.timedelta(weeks=1)
        else:
            # Filter for assessment accesses within the extent of the courseoffering
            range_start = course_offering.start_datetime
            range_end = range_start + datetime.timedelta(
                weeks=course_offering.no_weeks)
        dt_range = (range_start, range_end)

        # Get the page list.  If only we could do all this at the db level.
        page_qs = Page.objects.filter(
            course_offering=course_offering,
            content_type__in=CourseOffering.assessment_types()).values(
                'id', 'title', 'content_type')

        # Augment all the pages with how many submission attempts related to that page for the window of interest.
        for page in page_qs:
            page['attempts'] = SubmissionAttempt.objects.filter(
                page=page['id'], attempted_at__range=dt_range).count()

        # Sort by number of submission attempts and trim down to top 10 rows
        # WARNING: This is messing with the internals of the query set
        page_qs._result_cache = sorted(page_qs._result_cache,
                                       key=lambda p: p['attempts'],
                                       reverse=True)[0:self.max_rows_to_return]

        # Now calculate the data for the userviews and average score columns.
        # FIXME: This isn't a great way to do it.  Would be good to do it as part of the query above, in a way that didn't involve iteration or sets.
        for page in page_qs:
            # The userviews value is the number of distinct users to make a submission attempt in the time period.
            page['userviews'] = LMSUser.objects.values('id').filter(
                submissionattempt__page=page['id'],
                submissionattempt__attempted_at__range=dt_range).distinct(
                ).count()
            # The average score is the average of scores for submission attempts in the time period.
            # FIXME: Check for average-related exceptions here.
            avg = SubmissionAttempt.objects.filter(
                page=page['id'],
                attempted_at__range=dt_range).aggregate(Avg('grade'))
            page['average_score'] = avg['grade__avg']

        return page_qs
コード例 #7
0
    def get(self, request, student_id, format=None):
        communications = Page.objects.filter(
            course_offering=self.request.course_offering,
            content_type__in=CourseOffering.communication_types()).values(
                'id', 'title', 'content_type')

        for communication in communications:
            communication['user_views'] = PageVisit.objects.filter(
                lms_user_id=student_id, page_id=communication['id']).count()
            communication['posts'] = SummaryPost.objects.filter(
                lms_user_id=student_id, page_id=communication['id']).count()

        serializer = StudentCommunicationSerializer(data=communications,
                                                    many=True)
        sd = serializer.initial_data

        return Response(sd)
コード例 #8
0
    def get_queryset(self):
        course_offering = self.request.course_offering
        # Was a week number specified? (week number is 1-based)
        week_num = self.kwargs.get('week_num')
        if week_num is not None:
            # Filter for pagevisits within the week
            range_start = course_offering.start_datetime + datetime.timedelta(
                weeks=int(week_num) - 1)
            range_end = range_start + datetime.timedelta(weeks=1)
        else:
            # Filter for pagevisits within the extent of the courseoffering
            range_start = course_offering.start_datetime
            range_end = range_start + datetime.timedelta(
                weeks=course_offering.no_weeks)
        dt_range = (range_start, range_end)

        # Get the page list.  If only we could do all this at the db level.
        page_qs = Page.objects.filter(
            course_offering=course_offering,
            content_type__in=CourseOffering.communication_types()).values(
                'id', 'title', 'content_type')

        # Augment all the pages with how many page views related to that page for the window of interest.
        for page in page_qs:
            page['pageviews'] = PageVisit.objects.filter(
                page=page['id'], visited_at__range=dt_range).count()

        # Sort by number of page views and trim down to top 10 rows
        # WARNING: This is messing with the internals of the query set
        page_qs._result_cache = sorted(page_qs._result_cache,
                                       key=lambda p: p['pageviews'],
                                       reverse=True)[0:self.max_rows_to_return]

        # Now calculate the data for the userviews and posts columns.
        # FIXME: This isn't a great way to do it.  Would be good to do it as part of the query above, in a way that didn't involve iteration or sets.
        for page in page_qs:
            # The userviews value is the number of distinct users to access this page in the time period.
            page['userviews'] = LMSUser.objects.filter(
                pagevisit__page=page['id'],
                pagevisit__visited_at__range=dt_range).distinct().count()
            # The posts value is the number of posts to this page in the window of interest.
            page['posts'] = SummaryPost.objects.filter(
                page=page['id'], posted_at__range=dt_range).count()

        return page_qs
コード例 #9
0
    def _process_posts(self, posts_data):
        """
        Extracts posts from the course import files and updates/inserts into the page visits table
        """
        if set(posts_data.fieldnames) != set(self.POSTS_FIELDNAMES):
            raise LMSImportFileError(self.POSTS_FILE, 'Posts data columns do not match {}'.format(self.POSTS_FIELDNAMES))

        for row in posts_data:
            try:
                user = LMSUser.objects.get(lms_user_id=row['user_key'], course_offering=self.course_offering)
            except LMSUser.DoesNotExist:
                self._add_error('Unable to find user {} for post'.format(row['user_key']))
                continue

            values = {
                'title': row['thread'],
                'content_type': self.FORUM_CONTENT_TYPE,
                'parent_id': None,
            }
            page, _ = Page.objects.get_or_create(content_id=row['forum_key'], is_forum=True, course_offering=self.course_offering, defaults=values)

            # In case page was found already, check it's content type to ensure it is a communication type
            if page.content_type not in CourseOffering.communication_types():
                self._add_error('Resource {} for post is not a communication type'.format(page.content_id))
                continue

            try:
                posted_at = dateparse.parse_datetime(row['timestamp'])
                if posted_at is None:
                    self._add_error('Timestamp {} for post is not a valid format'.format(row['timestamp']))
                    continue
            except ValueError:
                self._add_error('Timestamp {} for post is not a valid datetime'.format(row['timestamp']))
                continue

            if posted_at < self.course_offering.start_datetime or posted_at > self.course_offering.end_datetime:
                self._add_non_critical_error('Timestamp {} for post is outside course offering start/end'.format(row['timestamp']))
                continue

            try:
                post = SummaryPost.objects.create(lms_user=user, page=page, posted_at=posted_at)
            except IntegrityError as e:
                self._add_error('Integrity Error in post insert: {}'.format(e))
コード例 #10
0
    def _process_submission_attempts(self, submissions_data):
        """
        Extracts submission attempts from the course import files and updates/inserts into the submission attempts table
        """
        if set(submissions_data.fieldnames) != set(self.SUBMISSIONS_FIELDNAMES):
            raise LMSImportFileError(self.SUBMISSIONS_FILE, 'Submissions data columns do not match {}'.format(self.SUBMISSIONS_FIELDNAMES))

        for row in submissions_data:
            try:
                user = LMSUser.objects.get(lms_user_id=row['user_key'], course_offering=self.course_offering)
            except LMSUser.DoesNotExist:
                self._add_error('Unable to find user {} for submission attempt'.format(row['user_key']))
                continue

            try:
                page = Page.objects.get(content_id=row['content_key'], is_forum=False, course_offering=self.course_offering)
                if page.content_type not in CourseOffering.assessment_types():
                    self._add_non_critical_error('Resource {} for submission attempt is not an assessment type'.format(page.content_id))
                    continue

            except Page.DoesNotExist:
                self._add_error('Unable to find page {} for submission attempt'.format(row['content_key']))
                continue

            try:
                attempted_at = dateparse.parse_datetime(row['timestamp'])
                if attempted_at is None:
                    self._add_error('Timestamp {} for submission attempt is not a valid format'.format(row['timestamp']))
                    continue
            except ValueError:
                self._add_error('Timestamp {} for submission attempt is not a valid datetime'.format(row['timestamp']))
                continue

            if attempted_at < self.course_offering.start_datetime or attempted_at > self.course_offering.end_datetime:
                self._add_non_critical_error('Timestamp {} for submission attempt is outside course offering start/end'.format(row['timestamp']))
                continue

            try:
                submission = SubmissionAttempt.objects.create(lms_user=user, page=page, attempted_at=attempted_at, grade=row['user_grade'])
            except IntegrityError as e:
                self._add_error('Integrity Error in submission attempt insert: {}'.format(e))
コード例 #11
0
    def get(self, request, student_id, format=None):
        assessments = Page.objects.filter(
            course_offering=self.request.course_offering,
            content_type__in=CourseOffering.assessment_types()).values(
                'id', 'title', 'content_type')

        for assessment in assessments:
            assessment['user_views'] = PageVisit.objects.filter(
                lms_user_id=student_id, page_id=assessment['id']).count()
            attempts = SubmissionAttempt.objects.filter(
                lms_user_id=student_id,
                page_id=assessment['id']).values_list('grade', flat=True)
            assessment['attempts'] = len(attempts)
            assessment['average_grade'] = Decimal(
                sum(attempts) /
                assessment['attempts']) if assessment['attempts'] else 0

        serializer = StudentAssessmentSerializer(data=assessments, many=True)
        sd = serializer.initial_data

        return Response(sd)
コード例 #12
0
    def get(self, request, format=None):
        course_offering = self.request.course_offering
        course_start_dt = course_offering.start_datetime

        students_by_week_for_all_pages = [
            set() for i in range(course_offering.no_weeks)
        ]
        page_queryset = Page.objects.filter(
            course_offering=course_offering,
            content_type__in=CourseOffering.communication_types()).values(
                'id', 'title', 'content_type')
        for page in page_queryset:
            page_visits_for_this_page = PageVisit.objects.filter(
                page_id=page['id'])
            students_by_week_for_this_page = [
                set() for i in range(course_offering.no_weeks)
            ]
            for visit in page_visits_for_this_page:
                # Calculate the time since start of course for this page visit.  From this we can calculate the week.
                # (Note, it's ok for courses not to start on Monday.  In this case, the boundary of the course week won't
                # be the same as the isoweek boundary).
                td = visit.visited_at - course_start_dt
                week = td.days // 7  # Integer division, the first week after the course starts is week 0.
                try:
                    # try is here to catch invalid week indexes caused by events outside of the course offering.
                    students_by_week_for_this_page[week].add(visit.lms_user.pk)
                    students_by_week_for_all_pages[week].add(visit.lms_user.pk)
                except IndexError:
                    pass
            # At this point we have a list of sets of students for the page.  Take the union of all the sets to find
            # the set of students who visited this page at any time over the duration of the course offering.
            set_of_students_for_this_page = set.union(
                *students_by_week_for_this_page)
            # For each week bin, convert the set of students for that weekinto a count.
            students_by_week_for_this_page = list(
                len(students) for students in students_by_week_for_this_page)
            page['weeks'] = students_by_week_for_this_page
            page['total'] = len(set_of_students_for_this_page)

        grand_total_uniques = len(set.union(*students_by_week_for_all_pages))
        # For each week bin, convert the set of students for that weekinto a count.
        students_by_week_for_all_pages = list(
            len(students) for students in students_by_week_for_all_pages)
        # Total page students for all pages and weeks.  Will appear in bottom right corner.
        students_by_week_for_all_pages.append(grand_total_uniques)

        # Calculate percentages.
        for page in page_queryset:
            page['percent'] = (Decimal(page['total'] * 100 /
                                       grand_total_uniques)
                               if grand_total_uniques else 0)

        results = {
            'page_set': page_queryset,
            'totals_by_week': students_by_week_for_all_pages,
        }

        serializer = CoursePagesetAndTotalsSerializer(data=results)
        sd = serializer.initial_data

        return Response(sd)
コード例 #13
0
    def get(self, request, *args, **kwargs):

        week_num = kwargs.get('week_num')
        week_start = request.course_offering.start_datetime + datetime.timedelta(
            weeks=int(week_num) - 1)
        dt_range = (week_start, week_start + datetime.timedelta(weeks=1))
        our_tz = get_current_timezone()

        day_dict = {}
        for day_offset in range(7):
            day = (week_start + datetime.timedelta(days=day_offset)).date()
            day_dict[day] = {
                'day': day,

                # Temporary values to calculate final data - will be stripped out by the serializer
                'page_list': [],

                # Final calculated values returned by the endpoint
                'unique_visits': 0,
                'content_visits': 0,
                'communication_visits': 0,
                'assessment_visits': 0,
                'repeating_events': [],
            }

        # Add the page visits to their corresponding entry
        for page_visit in PageVisit.objects.filter(
                page__course_offering=request.course_offering,
                visited_at__range=dt_range).values('page_id', 'visited_at',
                                                   'page__content_type'):
            visit_date = page_visit['visited_at'].astimezone(our_tz).date()
            day_dict[visit_date]['page_list'].append(page_visit['page_id'])
            if page_visit[
                    'page__content_type'] in CourseOffering.communication_types(
                    ):
                day_dict[visit_date]['communication_visits'] += 1
            elif page_visit[
                    'page__content_type'] in CourseOffering.assessment_types():
                day_dict[visit_date]['assessment_visits'] += 1
            else:
                day_dict[visit_date]['content_visits'] += 1

        # Add the repeating events to their corresponding entry
        for repeating_event in CourseRepeatingEvent.objects.filter(
                course_offering=request.course_offering,
                start_week__lte=week_num,
                end_week__gte=week_num):
            repeat_event_date = week_start + datetime.timedelta(
                days=repeating_event.day_of_week)
            day_dict[repeat_event_date.date()]['repeating_events'].append(
                repeating_event.title)

        # Turn the list of pages visited for each day into a count of unique visits
        for day in day_dict:
            day_dict[day]['unique_visits'] = len(
                set(day_dict[day]['page_list']))

        # Convert to array and serialize
        data = [v for v in day_dict.values()]
        data.sort(key=lambda day_data: day_data['day'])
        serializer = WeeklyPageVisitsSerializer(data, many=True)
        return Response(serializer.data)