Exemple #1
0
    def automark_all(self, user: User) -> int:
        """
        Fill in marking for any QuestionVersions that support it. Return number marked.
        """
        versions = QuestionVersion.objects.filter(
            question__quiz=self,
            question__status='V').select_related('question')
        activity_components = self.activitycomponents_by_question()
        member_component_results = [
        ]  # : List[Tuple[Member, ActivityComponentMark]]
        for v in versions:
            member_component_results.extend(
                v.automark_all(activity_components=activity_components))

        # Now the ugly work: combine the just-automarked components with any existing manual marking, and save...

        old_sam_lookup = {  # dict to find old StudentActivityMarks
            sam.numeric_grade.member: sam
            for sam in StudentActivityMark.objects.filter(
                activity=self.activity).order_by('created_at').select_related(
                    'numeric_grade__member').prefetch_related(
                        'activitycomponentmark_set')
        }

        # dict to find old ActivityComponentMarks
        old_acm_by_component_id = defaultdict(
            dict)  # : Dict[int, Dict[Member, ActivityComponentMark]]
        old_sam = StudentActivityMark.objects.filter(activity=self.activity).order_by('created_at') \
            .select_related('numeric_grade__member').prefetch_related('activitycomponentmark_set')
        for sam in old_sam:
            for acm in sam.activitycomponentmark_set.all():
                old_acm_by_component_id[acm.activity_component_id][
                    sam.numeric_grade.member] = acm

        numeric_grade_lookup = {  # dict to find existing NumericGrades
            ng.member: ng
            for ng in NumericGrade.objects.filter(
                activity=self.activity).select_related('member')
        }
        all_components = set(
            ActivityComponent.objects.filter(
                numeric_activity_id=self.activity_id, deleted=False))

        member_component_results.sort(
            key=lambda pair: pair[0].id)  # ... get Members grouped together
        n_marked = 0
        for member, member_acms in itertools.groupby(member_component_results,
                                                     lambda pair: pair[0]):
            # Get a NumericGrade to work with
            try:
                ngrade = numeric_grade_lookup[member]
            except KeyError:
                ngrade = NumericGrade(activity_id=self.activity_id,
                                      member=member,
                                      flag='NOGR')
                ngrade.save(newsitem=False, entered_by=None, is_temporary=True)

            # Create ActivityMark to save under
            am = StudentActivityMark(numeric_grade=ngrade,
                                     activity_id=self.activity_id,
                                     created_by=user.username)
            old_am = old_sam_lookup.get(member)
            if old_am:
                am.overall_comment = old_am.overall_comment
                am.late_penalty = old_am.late_penalty
                am.mark_adjustment = old_am.mark_adjustment
                am.mark_adjustment_reason = old_am.mark_adjustment_reason
            am.save()

            # Find/create ActivityComponentMarks for each component
            auto_acm_lookup = {
                acm.activity_component: acm
                for _, acm in member_acms
            }
            any_missing = False
            acms = []
            for c in all_components:
                # For each ActivityComponent, find one of
                # (1) just-auto-marked ActivityComponentMark,
                # (2) ActivityComponentMark from previous manual marking,
                # (3) nothing.
                if c in auto_acm_lookup:  # (1)
                    acm = auto_acm_lookup[c]
                    acm.activity_mark = am
                    n_marked += 1
                elif c.id in old_acm_by_component_id and member in old_acm_by_component_id[
                        c.id]:  # (2)
                    old_acm = old_acm_by_component_id[c.id][member]
                    acm = ActivityComponentMark(activity_mark=am,
                                                activity_component=c,
                                                value=old_acm.value,
                                                comment=old_acm.comment)
                else:  # (3)
                    acm = ActivityComponentMark(activity_mark=am,
                                                activity_component=c,
                                                value=None,
                                                comment=None)
                    any_missing = True

                acm.save()
                acms.append(acm)

            if not any_missing:
                total = am.calculated_mark(acms)
                ngrade.value = total
                ngrade.flag = 'GRAD'
                am.mark = total
            else:
                ngrade.value = 0
                ngrade.flag = 'NOGR'
                am.mark = None

            ngrade.save(newsitem=False, entered_by=user.username)
            am.save()

        return n_marked
Exemple #2
0
    def test_api_permissions(self):
        """
        Make sure the API views display activity/grade info at the right moments
        """
        client = Client()
        client.login_user("0aaa0")
        grades_url = reverse('api.OfferingGrades', kwargs={'course_slug': TEST_COURSE_SLUG})
        stats_url = reverse('api.OfferingStats', kwargs={'course_slug': TEST_COURSE_SLUG})

        o = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        na = NumericActivity.objects.get(slug='a1')
        la = LetterActivity.objects.get(slug='rep')
        instr = Member.objects.get(person__userid='ggbaker', offering=o, role='INST')
        student = Member.objects.get(person__userid='0aaa0', offering=o, role='STUD')

        # mock out the cache so we get fresh results for each request
        with self.settings(CACHES={ 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache' } }):

            # below uses assertFalse to test for None, '', [], {}, all of which are fine. Just no real grade info.

            # invisible activities shouldn't appear
            na.status = 'INVI'
            na.save(entered_by=instr.person)
            la.status = 'INVI'
            la.save(entered_by=instr.person)
            resp = client.get(grades_url)
            data = json.loads(resp.content)
            self.assertIsNone(self._get_by_slug(data, 'a1'))
            self.assertIsNone(self._get_by_slug(data, 'rep'))

            # no grades: shouldn't see
            na.status = 'URLS'
            na.save(entered_by=instr.person)
            la.status = 'URLS'
            la.save(entered_by=instr.person)
            resp = client.get(grades_url)
            data = json.loads(resp.content)
            self.assertFalse(self._get_by_slug(data, 'a1')['grade'])
            self.assertFalse(self._get_by_slug(data, 'rep')['grade'])
            self.assertFalse(self._get_by_slug(data, 'a1')['details'])
            self.assertFalse(self._get_by_slug(data, 'rep')['details'])

            resp = client.get(stats_url)
            data = json.loads(resp.content)
            self.assertIn('unreleased activities', self._get_by_slug(data, 'a1')['missing_reason'])
            self.assertIn('unreleased activities', self._get_by_slug(data, 'rep')['missing_reason'])
            self.assertIsNone(self._get_by_slug(data, 'a1')['count'])
            self.assertIsNone(self._get_by_slug(data, 'rep')['count'])

            # grades but unreleased: shouldn't see
            ng = NumericGrade(activity=na, member=student, value=1, flag='GRAD', comment='Foo')
            ng.save(entered_by=instr.person)
            from marking.models import StudentActivityMark, ActivityComponentMark, ActivityComponent
            am = StudentActivityMark(numeric_grade=ng, mark=2, created_by='ggbaker', overall_comment='thecomment')
            am.save()
            comp = ActivityComponent.objects.filter(numeric_activity=na)[0]
            cm = ActivityComponentMark(activity_mark=am, value=2, comment='foo',
                                       activity_component=comp)
            cm.save()
            am.setMark(2, entered_by=instr.person)

            lg = LetterGrade(activity=la, member=student, letter_grade='A', flag='GRAD', comment='Foo')
            lg.save(entered_by=instr.person)
            resp = client.get(grades_url)
            data = json.loads(resp.content)
            self.assertFalse(self._get_by_slug(data, 'a1')['grade'])
            self.assertFalse(self._get_by_slug(data, 'rep')['grade'])
            self.assertFalse(self._get_by_slug(data, 'a1')['details'])
            self.assertFalse(self._get_by_slug(data, 'rep')['details'])

            # release and they should appear
            na.status = 'RLS'
            na.save(entered_by=instr.person)
            la.status = 'RLS'
            la.save(entered_by=instr.person)
            resp = client.get(grades_url)
            data = json.loads(resp.content)
            self.assertEqual(self._get_by_slug(data, 'a1')['grade'], '2')
            self.assertEqual(self._get_by_slug(data, 'rep')['grade'], 'A')
            self.assertEqual(self._get_by_slug(data, 'a1')['details']['overall_comment'], 'thecomment')
            self.assertIsNone(self._get_by_slug(data, 'rep')['details']) # letter grades have no marking

            resp = client.get(stats_url)
            data = json.loads(resp.content)
            self.assertIn('small classes', self._get_by_slug(data, 'a1')['missing_reason'])
            self.assertIn('small classes', self._get_by_slug(data, 'rep')['missing_reason'])
Exemple #3
0
    def test_api_permissions(self):
        """
        Make sure the API views display activity/grade info at the right moments
        """
        client = Client()
        client.login_user("0aaa0")
        grades_url = reverse('api:OfferingGrades',
                             kwargs={'course_slug': TEST_COURSE_SLUG})
        stats_url = reverse('api:OfferingStats',
                            kwargs={'course_slug': TEST_COURSE_SLUG})

        o = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        na = NumericActivity.objects.get(slug='a1')
        la = LetterActivity.objects.get(slug='rep')
        instr = Member.objects.get(person__userid='ggbaker',
                                   offering=o,
                                   role='INST')
        student = Member.objects.get(person__userid='0aaa0',
                                     offering=o,
                                     role='STUD')

        # mock out the cache so we get fresh results for each request
        with self.settings(CACHES={
                'default': {
                    'BACKEND': 'django.core.cache.backends.dummy.DummyCache'
                }
        }):

            # below uses assertFalse to test for None, '', [], {}, all of which are fine. Just no real grade info.

            # invisible activities shouldn't appear
            na.status = 'INVI'
            na.save(entered_by=instr.person)
            la.status = 'INVI'
            la.save(entered_by=instr.person)
            resp = client.get(grades_url)
            data = json.loads(resp.content.decode('utf8'))
            self.assertIsNone(self._get_by_slug(data, 'a1'))
            self.assertIsNone(self._get_by_slug(data, 'rep'))

            # no grades: shouldn't see
            na.status = 'URLS'
            na.save(entered_by=instr.person)
            la.status = 'URLS'
            la.save(entered_by=instr.person)
            resp = client.get(grades_url)
            data = json.loads(resp.content.decode('utf8'))
            self.assertFalse(self._get_by_slug(data, 'a1')['grade'])
            self.assertFalse(self._get_by_slug(data, 'rep')['grade'])
            self.assertFalse(self._get_by_slug(data, 'a1')['details'])
            self.assertFalse(self._get_by_slug(data, 'rep')['details'])

            resp = client.get(stats_url)
            data = json.loads(resp.content.decode('utf8'))
            self.assertIn('unreleased activities',
                          self._get_by_slug(data, 'a1')['missing_reason'])
            self.assertIn('unreleased activities',
                          self._get_by_slug(data, 'rep')['missing_reason'])
            self.assertIsNone(self._get_by_slug(data, 'a1')['count'])
            self.assertIsNone(self._get_by_slug(data, 'rep')['count'])

            # grades but unreleased: shouldn't see
            ng = NumericGrade(activity=na,
                              member=student,
                              value=1,
                              flag='GRAD',
                              comment='Foo')
            ng.save(entered_by=instr.person)
            from marking.models import StudentActivityMark, ActivityComponentMark, ActivityComponent
            am = StudentActivityMark(numeric_grade=ng,
                                     mark=2,
                                     created_by='ggbaker',
                                     overall_comment='thecomment')
            am.save()
            comp = ActivityComponent.objects.filter(numeric_activity=na)[0]
            cm = ActivityComponentMark(activity_mark=am,
                                       value=2,
                                       comment='foo',
                                       activity_component=comp)
            cm.save()
            am.setMark(2, entered_by=instr.person)

            lg = LetterGrade(activity=la,
                             member=student,
                             letter_grade='A',
                             flag='GRAD',
                             comment='Foo')
            lg.save(entered_by=instr.person)
            resp = client.get(grades_url)
            data = json.loads(resp.content.decode('utf8'))
            self.assertFalse(self._get_by_slug(data, 'a1')['grade'])
            self.assertFalse(self._get_by_slug(data, 'rep')['grade'])
            self.assertFalse(self._get_by_slug(data, 'a1')['details'])
            self.assertFalse(self._get_by_slug(data, 'rep')['details'])

            # release and they should appear
            na.status = 'RLS'
            na.save(entered_by=instr.person)
            la.status = 'RLS'
            la.save(entered_by=instr.person)
            resp = client.get(grades_url)
            data = json.loads(resp.content.decode('utf8'))
            self.assertEqual(self._get_by_slug(data, 'a1')['grade'], '2.00')
            self.assertEqual(self._get_by_slug(data, 'rep')['grade'], 'A')
            self.assertEqual(
                self._get_by_slug(data, 'a1')['details']['overall_comment'],
                'thecomment')
            self.assertIsNone(self._get_by_slug(
                data, 'rep')['details'])  # letter grades have no marking

            resp = client.get(stats_url)
            data = json.loads(resp.content.decode('utf8'))
            self.assertIn('small classes',
                          self._get_by_slug(data, 'a1')['missing_reason'])
            self.assertIn('small classes',
                          self._get_by_slug(data, 'rep')['missing_reason'])