Ran update-copyright.py.
[pygrader.git] / pygrader / model / course.py
1 # Copyright (C) 2012 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of pygrader.
4 #
5 # pygrader is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 3 of the License, or (at your option) any later
8 # version.
9 #
10 # pygrader is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along with
15 # pygrader.  If not, see <http://www.gnu.org/licenses/>.
16
17 from .. import LOG as _LOG
18
19
20 class Course (object):
21     def __init__(self, assignments=None, people=None, grades=None):
22         if assignments is None:
23             assignments = []
24         self.assignments = sorted(assignments)
25         if people is None:
26             people = []
27         self.people = sorted(people)
28         if grades is None:
29             grades = []
30         self.grades = sorted(grades)
31
32     def assignment(self, name):
33         for assignment in self.assignments:
34             if assignment.name == name:
35                 return assignment
36         raise ValueError(name)
37
38     def active_assignments(self):
39         return sorted(set(grade.assignment for grade in self.grades))
40
41     def active_groups(self):
42         groups = set()
43         for person in self.people:
44             groups.update(person.groups)
45         return sorted(groups)
46
47     def find_people(self, name=None, email=None, group=None):
48         """Yield ``Person``\s that match ``name``, ``email``, and ``group``
49
50         The value of ``None`` matches any person.
51
52         >>> from pygrade.model.person import Person
53         >>> c = Course(people=[
54         ...     Person(name='Bilbo Baggins',
55         ...            emails=['bb@shire.org', 'bb@greyhavens.net'],
56         ...            aliases=['Billy'],
57         ...            groups=['students', 'assistants']),
58         ...     Person(name='Frodo Baggins',
59         ...            emails=['fb@shire.org'],
60         ...            groups=['students']),
61         ...     ])
62         >>> for person in c.find_people(name='Bilbo Baggins'):
63         ...     print(person)
64         <Person Bilbo Baggins>
65         >>> for person in c.find_people(name='Billy'):
66         ...     print(person)
67         <Person Bilbo Baggins>
68         >>> for person in c.find_people(email='bb@greyhavens.net'):
69         ...     print(person)
70         <Person Bilbo Baggins>
71         >>> for person in c.find_people(group='assistants'):
72         ...     print(person)
73         <Person Bilbo Baggins>
74         >>> for person in c.find_people(group='students'):
75         ...     print(person)
76         <Person Bilbo Baggins>
77         <Person Frodo Baggins>
78         """
79         for person in self.people:
80             name_match = (person.name == name or
81                           (person.aliases and name in person.aliases))
82             email_match = email in person.emails
83             group_match = group in person.groups
84             matched = True
85             for (key,kmatched) in [(name, name_match),
86                                    (email, email_match),
87                                    (group, group_match),
88                                    ]:
89                 if key is not None and not kmatched:
90                     matched = False
91                     break
92             if matched:
93                 yield person
94
95     def person(self, **kwargs):
96         people = list(self.find_people(**kwargs))
97         assert len(people) == 1, '{} -> {}'.format(kwargs, people)
98         return people[0]
99
100     def grade(self, student, assignment):
101         """Return the ``Grade`` that matches ``Student`` and ``Assignment``
102
103         >>> from pygrade.model.assignment import Assignment
104         >>> from pygrade.model.grade import Grade
105         >>> from pygrade.model.person import Person
106         >>> p = Person(name='Bilbo Baggins')
107         >>> a = Assignment(name='Exam 1')
108         >>> g = Grade(student=p, assignment=a, points=10)
109         >>> c = Course(assignments=[a], people=[p], grades=[g])
110         >>> print(c.grade(student=p, assignment=a))
111         <Grade Bilbo Baggins:Exam 1>
112         """
113         for grade in self.grades:
114             if grade.student == student and grade.assignment == assignment:
115                 return grade
116         raise ValueError((student, assignment))
117
118     def total(self, student):
119         total = 0
120         for assignment in self.assignments:
121             try:
122                 grade = self.grade(student=student, assignment=assignment)
123             except ValueError:
124                 continue
125             total += float(grade.points)/assignment.points * assignment.weight
126         return total