source: main/waeup.kofa/trunk/src/waeup/kofa/students/studylevel.py @ 10558

Last change on this file since 10558 was 10553, checked in by Henrik Bettermann, 11 years ago

Derive information about passed and failed course.

  • Property svn:keywords set to Id
File size: 10.0 KB
Line 
1## $Id: studylevel.py 10553 2013-08-29 05:24:10Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""
19Container which holds the data of a student study level
20and contains the course tickets.
21"""
22import grok
23from zope.component.interfaces import IFactory
24from zope.catalog.interfaces import ICatalog
25from zope.component import createObject, queryUtility
26from zope.interface import implementedBy
27from waeup.kofa.interfaces import academic_sessions_vocab
28from waeup.kofa.students.interfaces import (
29    IStudentStudyLevel, IStudentNavigation, ICourseTicket)
30from waeup.kofa.utils.helpers import attrs_to_fields
31from waeup.kofa.students.vocabularies import StudyLevelSource
32
33def find_carry_over(ticket):
34    studylevel = ticket.__parent__
35    studycourse = ticket.__parent__.__parent__
36    levels = sorted(studycourse.keys())
37    index = levels.index(str(studylevel.level))
38    try:
39        next_level = levels[index+1]
40    except IndexError:
41        return None
42    next_studylevel = studycourse[next_level]
43    co_ticket = next_studylevel.get(ticket.code, None)
44    return co_ticket
45
46def getGradeWeightFromScore(score):
47    if score is None:
48        return (None, None)
49    if score >= 70:
50        return ('A',5)
51    if score >= 60:
52        return ('B',4)
53    if score >= 50:
54        return ('C',3)
55    if score >= 45:
56        return ('D',2)
57    if score >= 40:
58        return ('E',1)
59    return ('F',0)
60
61class StudentStudyLevel(grok.Container):
62    """This is a container for course tickets.
63    """
64    grok.implements(IStudentStudyLevel, IStudentNavigation)
65    grok.provides(IStudentStudyLevel)
66
67    def __init__(self):
68        super(StudentStudyLevel, self).__init__()
69        self.level = None
70        return
71
72    @property
73    def student(self):
74        try:
75            return self.__parent__.__parent__
76        except AttributeError:
77            return None
78
79    @property
80    def certcode(self):
81        try:
82            return self.__parent__.certificate.code
83        except AttributeError:
84            return None
85
86    @property
87    def number_of_tickets(self):
88        return len(self)
89
90    @property
91    def total_credits(self):
92        total = 0
93        for ticket in self.values():
94            total += ticket.credits
95        return total
96
97    @property
98    def getSessionString(self):
99        return academic_sessions_vocab.getTerm(
100            self.level_session).title
101
102    @property
103    def gpa_params_rectified(self):
104        weighted_credits = 0.0
105        credits_counted = 0
106        level_gpa = 0.0
107        for ticket in self.values():
108            if ticket.carry_over is False and ticket.score:
109                if ticket.score < ticket.passmark:
110                    co_ticket = find_carry_over(ticket)
111                    if co_ticket is not None and co_ticket.weight is not None:
112                        credits_counted += co_ticket.credits
113                        weighted_credits += co_ticket.credits * co_ticket.weight
114                    continue
115                credits_counted += ticket.credits
116                weighted_credits += ticket.credits * ticket.weight
117        if credits_counted:
118            level_gpa = round(weighted_credits/credits_counted, 2)
119        return level_gpa, credits_counted, weighted_credits
120
121    @property
122    def gpa_params(self):
123        weighted_credits = 0.0
124        credits_counted = 0
125        level_gpa = 0.0
126        for ticket in self.values():
127            if ticket.score:
128                credits_counted += ticket.credits
129                weighted_credits += ticket.credits * ticket.weight
130        if credits_counted:
131            level_gpa = round(weighted_credits/credits_counted, 2)
132        return level_gpa, credits_counted, weighted_credits
133
134    @property
135    def gpa(self):
136        return self.gpa_params[0]
137
138    @property
139    def passed_params(self):
140        passed = failed = 0
141        failed_courses = []
142        failed_credits = 0
143        passed_credits = 0
144        for ticket in self.values():
145            if ticket.score is not None:
146                if ticket.score < ticket.passmark:
147                    failed += 1
148                    failed_credits += ticket.credits
149                    failed_courses.append(ticket.code)
150                else:
151                    passed += 1
152                    passed_credits += ticket.credits
153        return passed, failed, passed_credits, failed_credits, failed_courses
154
155    @property
156    def is_current_level(self):
157        try:
158            return self.__parent__.current_level == self.level
159        except AttributeError:
160            return False
161
162    def writeLogMessage(self, view, message):
163        return self.__parent__.__parent__.writeLogMessage(view, message)
164
165    @property
166    def level_title(self):
167        studylevelsource = StudyLevelSource()
168        return studylevelsource.factory.getTitle(self.__parent__, self.level)
169
170    def addCourseTicket(self, ticket, course):
171        """Add a course ticket object.
172        """
173        if not ICourseTicket.providedBy(ticket):
174            raise TypeError(
175                'StudentStudyLeves contain only ICourseTicket instances')
176        ticket.code = course.code
177        ticket.title = course.title
178        ticket.fcode = course.__parent__.__parent__.__parent__.code
179        ticket.dcode = course.__parent__.__parent__.code
180        ticket.credits = course.credits
181        ticket.passmark = course.passmark
182        ticket.semester = course.semester
183        self[ticket.code] = ticket
184        return
185
186    def addCertCourseTickets(self, cert):
187        """Collect all certificate courses and create course
188        tickets automatically.
189        """
190        if cert is not None:
191            for key, val in cert.items():
192                if val.level != self.level:
193                    continue
194                ticket = createObject(u'waeup.CourseTicket')
195                ticket.automatic = True
196                ticket.mandatory = val.mandatory
197                ticket.carry_over = False
198                self.addCourseTicket(ticket, val.course)
199        return
200
201StudentStudyLevel = attrs_to_fields(
202    StudentStudyLevel, omit=['total_credits', 'gpa'])
203
204class StudentStudyLevelFactory(grok.GlobalUtility):
205    """A factory for student study levels.
206    """
207    grok.implements(IFactory)
208    grok.name(u'waeup.StudentStudyLevel')
209    title = u"Create a new student study level.",
210    description = u"This factory instantiates new student study level instances."
211
212    def __call__(self, *args, **kw):
213        return StudentStudyLevel()
214
215    def getInterfaces(self):
216        return implementedBy(StudentStudyLevel)
217
218class CourseTicket(grok.Model):
219    """This is a course ticket which allows the
220    student to attend the course. Lecturers will enter scores and more at
221    the end of the term.
222
223    A course ticket contains a copy of the original course and
224    certificate course data. If the courses and/or the referrin certificate
225    courses are removed, the corresponding tickets remain unchanged.
226    So we do not need any event
227    triggered actions on course tickets.
228    """
229    grok.implements(ICourseTicket, IStudentNavigation)
230    grok.provides(ICourseTicket)
231
232    def __init__(self):
233        super(CourseTicket, self).__init__()
234        self.code = None
235        return
236
237    @property
238    def student(self):
239        """Get the associated student object.
240        """
241        try:
242            return self.__parent__.__parent__.__parent__
243        except AttributeError:
244            return None
245
246    @property
247    def certcode(self):
248        try:
249            return self.__parent__.__parent__.certificate.code
250        except AttributeError:
251            return None
252
253    @property
254    def removable_by_student(self):
255        return not self.mandatory
256
257    def writeLogMessage(self, view, message):
258        return self.__parent__.__parent__.__parent__.writeLogMessage(view, message)
259
260    @property
261    def level(self):
262        """Returns the id of the level the ticket has been added to.
263        """
264        try:
265            return self.__parent__.level
266        except AttributeError:
267            return None
268
269    @property
270    def level_session(self):
271        """Returns the session of the level the ticket has been added to.
272        """
273        try:
274            return self.__parent__.level_session
275        except AttributeError:
276            return None
277
278    @property
279    def grade(self):
280        """Returns the grade calculated from score.
281        """
282        return getGradeWeightFromScore(self.score)[0]
283
284    @property
285    def weight(self):
286        """Returns the weight calculated from score.
287        """
288        return getGradeWeightFromScore(self.score)[1]
289
290    @property
291    def course(self):
292        """Returns the course the ticket is referring to. Returns
293        None if the course has been removed.
294
295        This method is not used in Kofa anymore.
296        """
297        cat = queryUtility(ICatalog, name='courses_catalog')
298        result = cat.searchResults(code=(self.code, self.code))
299        if len(result) != 1:
300            return None
301        return list(result)[0]
302
303CourseTicket = attrs_to_fields(CourseTicket)
304
305class CourseTicketFactory(grok.GlobalUtility):
306    """A factory for student study levels.
307    """
308    grok.implements(IFactory)
309    grok.name(u'waeup.CourseTicket')
310    title = u"Create a new course ticket.",
311    description = u"This factory instantiates new course ticket instances."
312
313    def __call__(self, *args, **kw):
314        return CourseTicket()
315
316    def getInterfaces(self):
317        return implementedBy(CourseTicket)
Note: See TracBrowser for help on using the repository browser.