def calculate_letter_grade(course, activity): """ Calculate all the student's grade in the course's CalletterActivity. If student param is specified, this student's grade is calculated instead of the whole class, please also make sure this student is in the course before passing the student param. """ if not isinstance(course, CourseOffering): raise TypeError('CourseOffering type is required') if not isinstance(activity, CalLetterActivity): raise TypeError('CalLetterActivity type is required') # calculate for all student student_list = Member.objects.filter(offering=course, role='STUD') letter_grade_list = LetterGrade.objects.filter( activity=activity).select_related('member') ignored = 0 for s in student_list: # calculate grade result = generate_lettergrades(s, activity) # save grade member_found = False for letter_grade in letter_grade_list: if letter_grade.member == s: member_found = True if letter_grade.flag != "CALC": ignored += 1 elif result != letter_grade.letter_grade or letter_grade.flag != "CALC": # ignore manually-set grades; only save when the value changes letter_grade.letter_grade = result letter_grade.flag = "CALC" letter_grade.save(newsitem=False, entered_by=None) break if not member_found: letter_grade = LetterGrade(activity=activity, member=s, letter_grade=result, flag='CALC') letter_grade.save(newsitem=False, entered_by=None) return ignored
def calculate_letter_grade(course, activity): """ Calculate all the student's grade in the course's CalletterActivity. If student param is specified, this student's grade is calculated instead of the whole class, please also make sure this student is in the course before passing the student param. """ if not isinstance(course, CourseOffering): raise TypeError('CourseOffering type is required') if not isinstance(activity, CalLetterActivity): raise TypeError('CalLetterActivity type is required') # calculate for all student student_list = Member.objects.filter(offering=course, role='STUD') letter_grade_list = LetterGrade.objects.filter(activity = activity).select_related('member') ignored = 0 for s in student_list: # calculate grade result = generate_lettergrades(s,activity) # save grade member_found = False for letter_grade in letter_grade_list: if letter_grade.member == s: member_found = True if letter_grade.flag != "CALC": ignored += 1 elif result != letter_grade.letter_grade or letter_grade.flag != "CALC": # ignore manually-set grades; only save when the value changes letter_grade.letter_grade = result letter_grade.flag = "CALC" letter_grade.save(newsitem=False, entered_by=None) break if not member_found: letter_grade = LetterGrade(activity=activity, member=s, letter_grade=result, flag='CALC') letter_grade.save(newsitem=False, entered_by=None) return ignored
def setMark(self, grade, entered_by): """ Set the mark of the group members """ super(GroupActivityMark_LetterGrade, self).setMark(grade) #assign mark for each member in the group group_members = GroupMember.objects.filter(group=self.group, activity=self.letter_activity, confirmed=True) for g_member in group_members: try: lgrade = LetterGrade.objects.get(activity=self.letter_activity, member=g_member.student) except LetterGrade.DoesNotExist: lgrade = LetterGrade(activity=self.letter_activity, member=g_member.student) lgrade.letter_grade = grade lgrade.flag = 'GRAD' lgrade.save(entered_by=entered_by, group=self.group)
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'])
def test_get_grade(self): """ Make sure activity.get_grade() keeps the right things hidden """ o = CourseOffering.objects.get(slug=self.course_slug) na = NumericActivity.objects.get(slug='a1') la = LetterActivity.objects.get(slug='rep') instr = Person.objects.get(userid='ggbaker') student = Member.objects.get(person__userid='0aaa0', offering=o) na.status = 'RLS' na.save(entered_by=instr) la.status = 'RLS' la.save(entered_by=instr) # no grades yet self.assertEqual(na.get_grade(student.person, 'STUD'), None) self.assertEqual(la.get_grade(student.person, 'STUD'), None) self.assertEqual(na.get_grade(student.person, 'INST'), None) self.assertEqual(la.get_grade(student.person, 'INST'), None) # grades should be visible ng = NumericGrade(activity=na, member=student, value=1, flag='GRAD', comment='Foo') ng.save(entered_by=instr) lg = LetterGrade(activity=la, member=student, letter_grade='A', flag='GRAD', comment='Foo') lg.save(entered_by=instr) self.assertEqual(na.get_grade(student.person, 'STUD').grade, 1) self.assertEqual(la.get_grade(student.person, 'STUD').grade, 'A') self.assertEqual(na.get_grade(student.person, 'INST').grade, 1) self.assertEqual(la.get_grade(student.person, 'INST').grade, 'A') # unreleased: grades visible only to staff na.status = 'URLS' na.save(entered_by=instr) la.status = 'URLS' la.save(entered_by=instr) self.assertEqual(na.get_grade(student.person, 'STUD'), None) self.assertEqual(la.get_grade(student.person, 'STUD'), None) self.assertEqual(na.get_grade(student.person, 'INST').grade, 1) self.assertEqual(la.get_grade(student.person, 'INST').grade, 'A') # student shouldn't ever see invisible grade na.status = 'INVI' na.save(entered_by=instr) la.status = 'INVI' la.save(entered_by=instr) with self.assertRaises(RuntimeError): na.get_grade(student.person, 'STUD') with self.assertRaises(RuntimeError): la.get_grade(student.person, 'STUD') self.assertEqual(na.get_grade(student.person, 'INST').grade, 1) self.assertEqual(la.get_grade(student.person, 'INST').grade, 'A') # flag==NOGR handling ng.flag = 'NOGR' ng.save(entered_by=instr) lg.flag = 'NOGR' lg.save(entered_by=instr) na.status = 'RLS' na.save(entered_by=instr) la.status = 'RLS' la.save(entered_by=instr) self.assertEqual(na.get_grade(student.person, 'STUD'), None) self.assertEqual(la.get_grade(student.person, 'STUD'), None) self.assertEqual(na.get_grade(student.person, 'INST'), None) self.assertEqual(la.get_grade(student.person, 'INST'), None)
def test_sort_letter(self): """ Test sorting letter grades """ s, c = create_offering() a = LetterActivity(name="Assignment 1", short_name="A1", status="RLS", offering=c, position=2, due_date=None, group=False) a.save() ms = [] for i in range(10): p = Person.objects.get(userid="0aaa%i"%i) m = Member(person=p, offering=c, role="STUD", added_reason="UNK") m.save() ms.append(m) g = LetterGrade(activity=a, member=ms[0], letter_grade="B+", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[1], letter_grade="A", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[2], letter_grade="D", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[3], letter_grade="B-", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[4], letter_grade="P", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[5], letter_grade="GN", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[6], letter_grade="F", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[7], letter_grade="DE", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[8], letter_grade="C-", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[9], letter_grade="N", flag="GRAD") g.save(entered_by='ggbaker') g_objs = LetterGrade.objects.filter(activity=a) gs = [g.letter_grade for g in g_objs] gs_sort = sorted_letters(gs) self.assertEquals(gs_sort, ['A', 'B+', 'B-', 'C-', 'D', 'P', 'F', 'DE', 'N', 'GN']) # pre-sort by userid for median testing (so we know which subsets we're grabbing) gs = [(int(g.member.person.userid[4:]), g.letter_grade) for g in g_objs] gs.sort() gs = [g for u,g in gs] # odd-length case self.assertEquals(median_letters(sorted_letters(gs[0:5])), "B-") # even length with median at boundary self.assertEquals(median_letters(sorted_letters(gs[0:6])), "B-/D") # empty list self.assertEquals(median_letters([]), u"\u2014")
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'])
def test_get_grade(self): """ Make sure activity.get_grade() keeps the right things hidden """ o = CourseOffering.objects.get(slug=self.course_slug) na = NumericActivity.objects.get(slug='a1') la = LetterActivity.objects.get(slug='rep') instr = Person.objects.get(userid='ggbaker') student = Member.objects.get(person__userid='0aaa0', offering=o) na.status = 'RLS' na.save(entered_by=instr) la.status = 'RLS' la.save(entered_by=instr) # no grades yet self.assertEqual(na.get_grade(student.person, 'STUD'), None) self.assertEqual(la.get_grade(student.person, 'STUD'), None) self.assertEqual(na.get_grade(student.person, 'INST'), None) self.assertEqual(la.get_grade(student.person, 'INST'), None) # grades should be visible ng = NumericGrade(activity=na, member=student, value=1, flag='GRAD', comment='Foo') ng.save(entered_by=instr) lg = LetterGrade(activity=la, member=student, letter_grade='A', flag='GRAD', comment='Foo') lg.save(entered_by=instr) self.assertEqual(na.get_grade(student.person, 'STUD').grade, 1) self.assertEqual(la.get_grade(student.person, 'STUD').grade, 'A') self.assertEqual(na.get_grade(student.person, 'INST').grade, 1) self.assertEqual(la.get_grade(student.person, 'INST').grade, 'A') # unreleased: grades visible only to staff na.status = 'URLS' na.save(entered_by=instr) la.status = 'URLS' la.save(entered_by=instr) self.assertEqual(na.get_grade(student.person, 'STUD'), None) self.assertEqual(la.get_grade(student.person, 'STUD'), None) self.assertEqual(na.get_grade(student.person, 'INST').grade, 1) self.assertEqual(la.get_grade(student.person, 'INST').grade, 'A') # student shouldn't ever see invisible grade na.status = 'INVI' na.save(entered_by=instr) la.status = 'INVI' la.save(entered_by=instr) with self.assertRaises(RuntimeError): na.get_grade(student.person, 'STUD') with self.assertRaises(RuntimeError): la.get_grade(student.person, 'STUD') self.assertEqual(na.get_grade(student.person, 'INST').grade, 1) self.assertEqual(la.get_grade(student.person, 'INST').grade, 'A') # flag==NOGR handling ng.flag = 'NOGR' ng.save(entered_by=instr) lg.flag = 'NOGR' lg.save(entered_by=instr) na.status = 'RLS' na.save(entered_by=instr) la.status = 'RLS' la.save(entered_by=instr) self.assertEqual(na.get_grade(student.person, 'STUD'), None) self.assertEqual(la.get_grade(student.person, 'STUD'), None) self.assertEqual(na.get_grade(student.person, 'INST'), None) self.assertEqual(la.get_grade(student.person, 'INST'), None)
def test_sort_letter(self): """ Test sorting letter grades """ s, c = create_offering() a = LetterActivity(name="Assignment 1", short_name="A1", status="RLS", offering=c, position=2, due_date=None, group=False) a.save() ms = [] for i in range(10): p = Person.objects.get(userid="0aaa%i" % i) m = Member(person=p, offering=c, role="STUD", added_reason="UNK") m.save() ms.append(m) g = LetterGrade(activity=a, member=ms[0], letter_grade="B+", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[1], letter_grade="A", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[2], letter_grade="D", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[3], letter_grade="B-", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[4], letter_grade="P", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[5], letter_grade="GN", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[6], letter_grade="F", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[7], letter_grade="DE", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[8], letter_grade="C-", flag="GRAD") g.save(entered_by='ggbaker') g = LetterGrade(activity=a, member=ms[9], letter_grade="N", flag="GRAD") g.save(entered_by='ggbaker') g_objs = LetterGrade.objects.filter(activity=a) gs = [g.letter_grade for g in g_objs] gs_sort = sorted_letters(gs) self.assertEqual( gs_sort, ['A', 'B+', 'B-', 'C-', 'D', 'P', 'F', 'DE', 'N', 'GN']) # pre-sort by userid for median testing (so we know which subsets we're grabbing) gs = [(int(g.member.person.userid[4:]), g.letter_grade) for g in g_objs] gs.sort() gs = [g for u, g in gs] # odd-length case self.assertEqual(median_letters(sorted_letters(gs[0:5])), "B-") # even length with median at boundary self.assertEqual(median_letters(sorted_letters(gs[0:6])), "B-/D") # empty list self.assertEqual(median_letters([]), "\u2014")