コード例 #1
0
ファイル: utils.py プロジェクト: xhacker/coursys
def parse_and_validate_formula(formula, course, activity, numeric_activities):
    """
    Handy function to parse the formula and validate if the activity references
    in the formula are in the numeric_activities list
    Return the parsed formula if no exception raised
    
    May raise exception: ParseException, ValidateError
    """
    for a in numeric_activities:
        if not isinstance(a, NumericActivity):
            raise TypeError(u'NumericActivity list is required')
    try:
        parsed_expr = parse(formula, course, activity)
        activities_dict = activities_dictionary(numeric_activities)
        cols = set([])
        cols = cols_used(parsed_expr)
        for col in cols:
            if not col in activities_dict:
                raise ValidationError(u'Invalid activity reference')
    except ParseException:
        raise ValidationError(u'Incorrect formula syntax')
    return parsed_expr
コード例 #2
0
ファイル: utils.py プロジェクト: avacariu/coursys
def parse_and_validate_formula(formula, course, activity, numeric_activities):
    """
    Handy function to parse the formula and validate if the activity references
    in the formula are in the numeric_activities list
    Return the parsed formula if no exception raised
    
    May raise exception: ParseException, ValidateError
    """
    for a in numeric_activities:
        if not isinstance(a, NumericActivity):
            raise TypeError(u'NumericActivity list is required')
    try:
        parsed_expr = parse(formula, course, activity)
        activities_dict = activities_dictionary(numeric_activities)
        cols = set([])
        cols = cols_used(parsed_expr)
        for col in cols:
            if not col in activities_dict:
                raise ValidationError(u'Invalid activity reference')
    except ParseException:
        raise ValidationError(u'Incorrect formula syntax')
    return parsed_expr
コード例 #3
0
ファイル: tests.py プロジェクト: tedkirkpatrick/coursys
    def test_formulas(self):
        """
        Test the formula parsing & evaluation.
        """
        # set up course and related data
        s, c = create_offering()
        p = Person.objects.get(userid="0aaa0")
        m = Member(person=p, offering=c, role="STUD", credits=3, added_reason="UNK")
        m.save()
       
        a = NumericActivity(name="Paragraph", short_name=u"\u00b6", status="RLS", offering=c, position=3, max_grade=40, percent=5)
        a.save()
        g = NumericGrade(activity=a, member=m, value="4.5", flag="CALC")
        g.save(entered_by='ggbaker')
        a1 = NumericActivity(name="Assignment #1", short_name="A1", status="RLS", offering=c, position=1, max_grade=15, percent=10)
        a1.save()
        g = NumericGrade(activity=a1, member=m, value=10, flag="GRAD")
        g.save(entered_by='ggbaker')
        a2 = NumericActivity(name="Assignment #2", short_name="A2", status="URLS", offering=c, position=2, max_grade=40, percent=20)
        a2.save(entered_by='ggbaker')
        g = NumericGrade(activity=a2, member=m, value=30, flag="GRAD")
        g.save(entered_by='ggbaker')
        
        ca = CalNumericActivity(name="Final Grade", short_name=u"FG", status="RLS", offering=c, position=4, max_grade=1)
        ca.save()
        
        activities = NumericActivity.objects.filter(offering=c)
        act_dict = activities_dictionary(activities)
        
        # make sure a formula can be pickled and unpickled safely (i.e. can be cached)
        tree = parse("sum([Assignment #1], [A1], [A2])/20*-3", c, ca)
        p = pickle.dumps(tree)
        tree2 = pickle.loads(p)
        self.assertEqual(tree, tree2)
        # check that it found the right list of columns used
        self.assertEqual(cols_used(tree), set(['A1', 'A2', 'Assignment #1']))
        
        # test parsing and evaluation to make sure we get the right values out
        for expr, correct in test_formulas:
            tree = parse(expr, c, ca)
            res = eval_parse(tree, ca, act_dict, m, False)
            self.assertAlmostEqual(correct, res, msg=u"Incorrect result for %s"%(expr,))

        # test some badly-formed stuff for appropriate exceptions
        tree = parse("1 + BEST(3, [A1], [A2])", c, ca)
        self.assertRaises(EvalException, eval_parse, tree, ca, act_dict, m, True)
        tree = parse("1 + BEST(0, [A1], [A2])", c, ca)
        self.assertRaises(EvalException, eval_parse, tree, ca, act_dict, m, True)
        tree = parse("[Foo] /2", c, ca)
        self.assertRaises(KeyError, eval_parse, tree, ca, act_dict, m, True)
        tree = parse("[a1] /2", c, ca)
        self.assertRaises(KeyError, eval_parse, tree, ca, act_dict, m, True)
        
        self.assertRaises(ParseException, parse, "AVG()", c, ca)
        self.assertRaises(ParseException, parse, "(2+3*84", c, ca)
        self.assertRaises(ParseException, parse, "2+3**84", c, ca)
        self.assertRaises(ParseException, parse, "AVG(2,3,4", c, ca)
        self.assertRaises(ParseException, parse, "{something}", c, ca)
        
        # test visible/invisible switching
        tree = parse("[Assignment #2]", c, ca)
        res = eval_parse(tree, ca, act_dict, m, True)
        self.assertAlmostEqual(res, 0.0)
        res = eval_parse(tree, ca, act_dict, m, False)
        self.assertAlmostEqual(res, 30.0)

        # test unreleased/missing grade conditions
        expr = "[Assignment #2]"
        tree = parse(expr, c, ca)
        
        # unreleased assignment (with grade)
        a2.status='URLS'
        a2.save()
        activities = NumericActivity.objects.filter(offering=c)
        act_dict = activities_dictionary(activities)
        res = eval_parse(tree, ca, act_dict, m, True)
        self.assertAlmostEqual(res, 0.0)
        
        # explicit no grade (released assignment)
        g.flag="NOGR"
        g.save(entered_by='ggbaker')
        a2.status='RLS'
        a2.save(entered_by='ggbaker')
        activities = NumericActivity.objects.filter(offering=c)
        act_dict = activities_dictionary(activities)
        res = eval_parse(tree, ca, act_dict, m, True)
        self.assertAlmostEqual(res, 0.0)

        # no grade in database (released assignment)
        g.delete()
        activities = NumericActivity.objects.filter(offering=c)
        act_dict = activities_dictionary(activities)
        res = eval_parse(tree, ca, act_dict, m, True)
        self.assertAlmostEqual(res, 0.0)
        
        # test [[activitytotal]]
        expr = "[[activitytotal]]"
        tree = parse(expr, c, ca)
        res = eval_parse(tree, ca, act_dict, m, True)
        self.assertAlmostEqual(res, 7.229166666)
コード例 #4
0
ファイル: utils.py プロジェクト: xhacker/coursys
def calculate_numeric_grade(course, activity, student=None):
    """
    Calculate all the student's grade in the course's CalNumericActivity.
    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, CalNumericActivity):
        raise TypeError('CalNumericActivity type is required')

    numeric_activities = NumericActivity.objects.filter(offering=course,
                                                        deleted=False)
    act_dict = activities_dictionary(numeric_activities)
    try:
        parsed_expr = parse_and_validate_formula(activity.formula,
                                                 activity.offering, activity,
                                                 numeric_activities)
    except ValidationError as e:
        raise ValidationError('Formula Error: ' + e.args[0])

    if student != None:  # calculate for one student
        if not isinstance(student, Member):
            raise TypeError('Member type is required')
        student_list = [student]
        numeric_grade_list = NumericGrade.objects.filter(activity=activity,
                                                         member=student)
    else:  # calculate for all student
        student_list = Member.objects.filter(offering=course, role='STUD')
        numeric_grade_list = NumericGrade.objects.filter(
            activity=activity).select_related('member')

    ignored = 0
    visible = activity.status == "RLS"
    for s in student_list:
        # calculate grade
        try:
            result = eval_parse(parsed_expr, activity, act_dict, s, visible)
            result = decimal.Decimal(str(result))  # convert to decimal
        except EvalException:
            raise EvalException(
                "Formula Error: Can not evaluate formula for student: '%s'" %
                s.person.name())

        # save grade
        member_found = False
        for numeric_grade in numeric_grade_list:
            if numeric_grade.member == s:
                member_found = True
                if numeric_grade.flag != "CALC":
                    # ignore manually-set grades
                    ignored += 1
                elif result != numeric_grade.value:
                    # only save when the value changes
                    numeric_grade.value = result
                    numeric_grade.flag = "CALC"
                    numeric_grade.save(newsitem=False, entered_by=None)
                break
        if not member_found:
            numeric_grade = NumericGrade(activity=activity,
                                         member=s,
                                         value=str(result),
                                         flag='CALC')
            numeric_grade.save(newsitem=False, entered_by=None)

    uses_unreleased = True in (act_dict[c].status != 'RLS'
                               for c in cols_used(parsed_expr))
    hiding_info = visible and uses_unreleased

    if student != None:
        return StudentActivityInfo(student, activity, FLAGS['CALC'],
                                   numeric_grade.value,
                                   None).display_grade_staff(), hiding_info
    else:
        return ignored, hiding_info
コード例 #5
0
ファイル: utils.py プロジェクト: avacariu/coursys
def calculate_numeric_grade(course, activity, student=None):
    """
    Calculate all the student's grade in the course's CalNumericActivity.
    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, CalNumericActivity):
        raise TypeError('CalNumericActivity type is required')

    numeric_activities = NumericActivity.objects.filter(offering=course, deleted=False)
    act_dict = activities_dictionary(numeric_activities)
    try:
        parsed_expr = parse_and_validate_formula(activity.formula, activity.offering, activity, numeric_activities)
    except ValidationError as e:
        raise ValidationError('Formula Error: ' + e.args[0])
        
    if student != None: # calculate for one student
        if not isinstance(student, Member):
            raise TypeError('Member type is required')
        student_list = [student]
        numeric_grade_list = NumericGrade.objects.filter(activity=activity, member=student)
    else: # calculate for all student
        student_list = Member.objects.filter(offering=course, role='STUD')
        numeric_grade_list = NumericGrade.objects.filter(activity = activity).select_related('member')
    
    ignored = 0
    visible = activity.status=="RLS"
    for s in student_list:
        # calculate grade
        try:
            result = eval_parse(parsed_expr, activity, act_dict, s, visible)
            result = decimal.Decimal(str(result)) # convert to decimal
        except EvalException:
            raise EvalException("Formula Error: Can not evaluate formula for student: '%s'" % s.person.name())
        
        # save grade
        member_found = False
        for numeric_grade in numeric_grade_list:
            if numeric_grade.member == s:
                member_found = True     
                if numeric_grade.flag != "CALC":
                    # ignore manually-set grades
                    ignored += 1
                elif result != numeric_grade.value:
                    # only save when the value changes
                    numeric_grade.value = result
                    numeric_grade.flag = "CALC"
                    numeric_grade.save(newsitem=False, entered_by=None)
                break
        if not member_found:
            numeric_grade = NumericGrade(activity=activity, member=s,
                                         value=str(result), flag='CALC')
            numeric_grade.save(newsitem=False, entered_by=None)

    uses_unreleased = True in (act_dict[c].status != 'RLS' for c in cols_used(parsed_expr))
    hiding_info = visible and uses_unreleased

    if student != None:
        return StudentActivityInfo(student, activity, FLAGS['CALC'], numeric_grade.value, None).display_grade_staff(), hiding_info
    else:
        return ignored, hiding_info
コード例 #6
0
    def test_formulas(self):
        """
        Test the formula parsing & evaluation.
        """
        # set up course and related data
        s, c = create_offering()
        p = Person.objects.get(userid="0aaa0")
        m = Member(person=p,
                   offering=c,
                   role="STUD",
                   credits=3,
                   added_reason="UNK")
        m.save()

        a = NumericActivity(name="Paragraph",
                            short_name="\u00b6",
                            status="RLS",
                            offering=c,
                            position=3,
                            max_grade=40,
                            percent=5)
        a.save()
        g = NumericGrade(activity=a, member=m, value="4.5", flag="CALC")
        g.save(entered_by='ggbaker')
        a1 = NumericActivity(name="Assignment #1",
                             short_name="A1",
                             status="RLS",
                             offering=c,
                             position=1,
                             max_grade=15,
                             percent=10)
        a1.save()
        g = NumericGrade(activity=a1, member=m, value=10, flag="GRAD")
        g.save(entered_by='ggbaker')
        a2 = NumericActivity(name="Assignment #2",
                             short_name="A2",
                             status="URLS",
                             offering=c,
                             position=2,
                             max_grade=40,
                             percent=20)
        a2.save(entered_by='ggbaker')
        g = NumericGrade(activity=a2, member=m, value=30, flag="GRAD")
        g.save(entered_by='ggbaker')

        ca = CalNumericActivity(name="Final Grade",
                                short_name="FG",
                                status="RLS",
                                offering=c,
                                position=4,
                                max_grade=1)
        ca.save()

        activities = NumericActivity.objects.filter(offering=c)
        act_dict = activities_dictionary(activities)

        # make sure a formula can be pickled and unpickled safely (i.e. can be cached)
        tree = parse("sum([Assignment #1], [A1], [A2])/20*-3", c, ca)
        p = pickle.dumps(tree)
        tree2 = pickle.loads(p)
        self.assertEqual(tree, tree2)
        # check that it found the right list of columns used
        self.assertEqual(cols_used(tree), set(['A1', 'A2', 'Assignment #1']))

        # test parsing and evaluation to make sure we get the right values out
        for expr, correct in test_formulas:
            tree = parse(expr, c, ca)
            res = eval_parse(tree, ca, act_dict, m, False)
            self.assertAlmostEqual(correct,
                                   res,
                                   msg="Incorrect result for %s" % (expr, ))

        # test some badly-formed stuff for appropriate exceptions
        tree = parse("1 + BEST(3, [A1], [A2])", c, ca)
        self.assertRaises(EvalException, eval_parse, tree, ca, act_dict, m,
                          True)
        tree = parse("1 + BEST(0, [A1], [A2])", c, ca)
        self.assertRaises(EvalException, eval_parse, tree, ca, act_dict, m,
                          True)
        tree = parse("[Foo] /2", c, ca)
        self.assertRaises(KeyError, eval_parse, tree, ca, act_dict, m, True)
        tree = parse("[a1] /2", c, ca)
        self.assertRaises(KeyError, eval_parse, tree, ca, act_dict, m, True)

        self.assertRaises(ParseException, parse, "AVG()", c, ca)
        self.assertRaises(ParseException, parse, "(2+3*84", c, ca)
        self.assertRaises(ParseException, parse, "2+3**84", c, ca)
        self.assertRaises(ParseException, parse, "AVG(2,3,4", c, ca)
        self.assertRaises(ParseException, parse, "{something}", c, ca)

        # test visible/invisible switching
        tree = parse("[Assignment #2]", c, ca)
        res = eval_parse(tree, ca, act_dict, m, True)
        self.assertAlmostEqual(res, 0.0)
        res = eval_parse(tree, ca, act_dict, m, False)
        self.assertAlmostEqual(res, 30.0)

        # test unreleased/missing grade conditions
        expr = "[Assignment #2]"
        tree = parse(expr, c, ca)

        # unreleased assignment (with grade) should not be included in the calculation
        a2.status = 'URLS'
        a2.save()
        activities = NumericActivity.objects.filter(offering=c)
        act_dict = activities_dictionary(activities)
        res = eval_parse(tree, ca, act_dict, m, True)
        self.assertAlmostEqual(res, 0.0)
        # ... unless the instructor said to do so.
        ca.set_calculation_leak(True)
        res = eval_parse(tree, ca, act_dict, m, True)
        self.assertAlmostEqual(res, 30.0)

        # explicit no grade (released assignment)
        g.flag = "NOGR"
        g.save(entered_by='ggbaker')
        a2.status = 'RLS'
        a2.save(entered_by='ggbaker')
        activities = NumericActivity.objects.filter(offering=c)
        act_dict = activities_dictionary(activities)
        res = eval_parse(tree, ca, act_dict, m, True)
        self.assertAlmostEqual(res, 0.0)

        # no grade in database (released assignment)
        g.delete()
        activities = NumericActivity.objects.filter(offering=c)
        act_dict = activities_dictionary(activities)
        res = eval_parse(tree, ca, act_dict, m, True)
        self.assertAlmostEqual(res, 0.0)

        # test [[activitytotal]]
        expr = "[[activitytotal]]"
        tree = parse(expr, c, ca)
        res = eval_parse(tree, ca, act_dict, m, True)
        self.assertAlmostEqual(res, 7.229166666)