652592ff0c072d232bc3da5b6c1c501616fc22bd
[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, name=None, assignments=None, people=None, grades=None):
22         self.name = name
23         if assignments is None:
24             assignments = []
25         self.assignments = sorted(assignments)
26         if people is None:
27             people = []
28         self.people = sorted(people)
29         if grades is None:
30             grades = []
31         self.grades = sorted(grades)
32
33     def assignment(self, name):
34         for assignment in self.assignments:
35             if assignment.name == name:
36                 return assignment
37         raise ValueError(name)
38
39     def active_assignments(self):
40         return sorted(set(grade.assignment for grade in self.grades))
41
42     def active_groups(self):
43         groups = set()
44         for person in self.people:
45             groups.update(person.groups)
46         return sorted(groups)
47
48     def find_people(self, name=None, email=None, group=None):
49         """Yield ``Person``\s that match ``name``, ``email``, and ``group``
50
51         The value of ``None`` matches any person.
52
53         >>> from pygrader.model.person import Person
54         >>> c = Course(people=[
55         ...     Person(name='Bilbo Baggins',
56         ...            emails=['bb@shire.org', 'bb@greyhavens.net'],
57         ...            aliases=['Billy'],
58         ...            groups=['students', 'assistants']),
59         ...     Person(name='Frodo Baggins',
60         ...            emails=['fb@shire.org'],
61         ...            groups=['students']),
62         ...     ])
63         >>> for person in c.find_people(name='Bilbo Baggins'):
64         ...     print(person)
65         <Person Bilbo Baggins>
66         >>> for person in c.find_people(name='Billy'):
67         ...     print(person)
68         <Person Bilbo Baggins>
69         >>> for person in c.find_people(email='bb@greyhavens.net'):
70         ...     print(person)
71         <Person Bilbo Baggins>
72         >>> for person in c.find_people(group='assistants'):
73         ...     print(person)
74         <Person Bilbo Baggins>
75         >>> for person in c.find_people(group='students'):
76         ...     print(person)
77         <Person Bilbo Baggins>
78         <Person Frodo Baggins>
79         """
80         for person in self.people:
81             name_match = (person.name == name or
82                           (person.aliases and name in person.aliases))
83             email_match = email in person.emails
84             group_match = group in person.groups
85             matched = True
86             for (key,kmatched) in [(name, name_match),
87                                    (email, email_match),
88                                    (group, group_match),
89                                    ]:
90                 if key is not None and not kmatched:
91                     matched = False
92                     break
93             if matched:
94                 yield person
95
96     def person(self, **kwargs):
97         people = list(self.find_people(**kwargs))
98         assert len(people) == 1, '{} -> {}'.format(kwargs, people)
99         return people[0]
100
101     def grade(self, student, assignment):
102         """Return the ``Grade`` that matches ``Student`` and ``Assignment``
103
104         >>> from pygrader.model.assignment import Assignment
105         >>> from pygrader.model.grade import Grade
106         >>> from pygrader.model.person import Person
107         >>> p = Person(name='Bilbo Baggins')
108         >>> a = Assignment(name='Exam 1')
109         >>> g = Grade(student=p, assignment=a, points=10)
110         >>> c = Course(assignments=[a], people=[p], grades=[g])
111         >>> print(c.grade(student=p, assignment=a))
112         <Grade Bilbo Baggins:Exam 1>
113         """
114         for grade in self.grades:
115             if grade.student == student and grade.assignment == assignment:
116                 return grade
117         raise ValueError((student, assignment))
118
119     def total(self, student):
120         total = 0
121         for assignment in self.assignments:
122             try:
123                 grade = self.grade(student=student, assignment=assignment)
124             except ValueError:
125                 continue
126             total += float(grade.points)/assignment.points * assignment.weight
127         return total