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

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

Reinvent temporary/unrectified/momentary/sessional gpa as a property attribute of study levels. The sessional gpa shown on transcript slips is now called 'rectified sessional gpa'.

  • Property svn:keywords set to Id
File size: 8.9 KB
Line 
1## $Id: studylevel.py 10479 2013-08-12 08:57:26Z 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.component import createObject
25from zope.interface import implementedBy
26from waeup.kofa.interfaces import academic_sessions_vocab
27from waeup.kofa.students.interfaces import (
28    IStudentStudyLevel, IStudentNavigation, ICourseTicket)
29from waeup.kofa.utils.helpers import attrs_to_fields
30from waeup.kofa.students.vocabularies import StudyLevelSource
31
32def find_carry_over(ticket):
33    studylevel = ticket.__parent__
34    studycourse = ticket.__parent__.__parent__
35    levels = sorted(studycourse.keys())
36    index = levels.index(str(studylevel.level))
37    try:
38        next_level = levels[index+1]
39    except IndexError:
40        return None
41    next_studylevel = studycourse[next_level]
42    co_ticket = next_studylevel.get(ticket.code, None)
43    return co_ticket
44
45def getGradeWeightFromScore(score):
46    if score is None:
47        return (None, None)
48    if score >= 70:
49        return ('A',5)
50    if score >= 60:
51        return ('B',4)
52    if score >= 50:
53        return ('C',3)
54    if score >= 45:
55        return ('D',2)
56    if score >= 40:
57        return ('E',1)
58    return ('F',0)
59
60class StudentStudyLevel(grok.Container):
61    """This is a container for course tickets.
62    """
63    grok.implements(IStudentStudyLevel, IStudentNavigation)
64    grok.provides(IStudentStudyLevel)
65
66    def __init__(self):
67        super(StudentStudyLevel, self).__init__()
68        self.level = None
69        return
70
71    @property
72    def student(self):
73        try:
74            return self.__parent__.__parent__
75        except AttributeError:
76            return None
77
78    @property
79    def certcode(self):
80        try:
81            return self.__parent__.certificate.code
82        except AttributeError:
83            return None
84
85    @property
86    def number_of_tickets(self):
87        return len(self)
88
89    @property
90    def total_credits(self):
91        total = 0
92        for ticket in self.values():
93            total += ticket.credits
94        return total
95
96    @property
97    def getSessionString(self):
98        return academic_sessions_vocab.getTerm(
99            self.level_session).title
100
101    @property
102    def gpa_params_rectified(self):
103        weighted_credits = 0.0
104        credits_counted = 0
105        level_gpa = 0.0
106        for ticket in self.values():
107            if ticket.carry_over is False and ticket.score:
108                if ticket.score < ticket.passmark:
109                    co_ticket = find_carry_over(ticket)
110                    if co_ticket is not None and co_ticket.weight is not None:
111                        credits_counted += co_ticket.credits
112                        weighted_credits += co_ticket.credits * co_ticket.weight
113                    continue
114                credits_counted += ticket.credits
115                weighted_credits += ticket.credits * ticket.weight
116        if credits_counted:
117            level_gpa = round(weighted_credits/credits_counted, 2)
118        return level_gpa, credits_counted, weighted_credits
119
120    @property
121    def gpa_params(self):
122        weighted_credits = 0.0
123        credits_counted = 0
124        level_gpa = 0.0
125        for ticket in self.values():
126            if ticket.score:
127                credits_counted += ticket.credits
128                weighted_credits += ticket.credits * ticket.weight
129        if credits_counted:
130            level_gpa = round(weighted_credits/credits_counted, 2)
131        return level_gpa, credits_counted, weighted_credits
132
133    @property
134    def gpa(self):
135        return self.gpa_params[0]
136
137    @property
138    def is_current_level(self):
139        try:
140            return self.__parent__.current_level == self.level
141        except AttributeError:
142            return False
143
144    def writeLogMessage(self, view, message):
145        return self.__parent__.__parent__.writeLogMessage(view, message)
146
147    @property
148    def level_title(self):
149        studylevelsource = StudyLevelSource()
150        return studylevelsource.factory.getTitle(self.__parent__, self.level)
151
152    def addCourseTicket(self, ticket, course):
153        """Add a course ticket object.
154        """
155        if not ICourseTicket.providedBy(ticket):
156            raise TypeError(
157                'StudentStudyLeves contain only ICourseTicket instances')
158        ticket.code = course.code
159        ticket.title = course.title
160        ticket.fcode = course.__parent__.__parent__.__parent__.code
161        ticket.dcode = course.__parent__.__parent__.code
162        ticket.credits = course.credits
163        ticket.passmark = course.passmark
164        ticket.semester = course.semester
165        self[ticket.code] = ticket
166        return
167
168    def addCertCourseTickets(self, cert):
169        """Collect all certificate courses and create course
170        tickets automatically.
171        """
172        if cert is not None:
173            for key, val in cert.items():
174                if val.level != self.level:
175                    continue
176                ticket = createObject(u'waeup.CourseTicket')
177                ticket.automatic = True
178                ticket.mandatory = val.mandatory
179                ticket.carry_over = False
180                self.addCourseTicket(ticket, val.course)
181        return
182
183StudentStudyLevel = attrs_to_fields(
184    StudentStudyLevel, omit=['total_credits', 'gpa'])
185
186class StudentStudyLevelFactory(grok.GlobalUtility):
187    """A factory for student study levels.
188    """
189    grok.implements(IFactory)
190    grok.name(u'waeup.StudentStudyLevel')
191    title = u"Create a new student study level.",
192    description = u"This factory instantiates new student study level instances."
193
194    def __call__(self, *args, **kw):
195        return StudentStudyLevel()
196
197    def getInterfaces(self):
198        return implementedBy(StudentStudyLevel)
199
200class CourseTicket(grok.Model):
201    """This is a course ticket which allows the
202    student to attend the course. Lecturers will enter scores and more at
203    the end of the term.
204
205    A course ticket contains a copy of the original course and
206    certificate course data. If the courses and/or the referrin certificate
207    courses are removed, the corresponding tickets remain unchanged.
208    So we do not need any event
209    triggered actions on course tickets.
210    """
211    grok.implements(ICourseTicket, IStudentNavigation)
212    grok.provides(ICourseTicket)
213
214    def __init__(self):
215        super(CourseTicket, self).__init__()
216        self.code = None
217        return
218
219    @property
220    def student(self):
221        """Get the associated student object.
222        """
223        try:
224            return self.__parent__.__parent__.__parent__
225        except AttributeError:
226            return None
227
228    @property
229    def certcode(self):
230        try:
231            return self.__parent__.__parent__.certificate.code
232        except AttributeError:
233            return None
234
235    @property
236    def removable_by_student(self):
237        return not self.mandatory
238
239    def writeLogMessage(self, view, message):
240        return self.__parent__.__parent__.__parent__.writeLogMessage(view, message)
241
242    @property
243    def level(self):
244        """Returns the id of the level the ticket has been added to.
245        """
246        try:
247            return self.__parent__.level
248        except AttributeError:
249            return None
250
251    @property
252    def level_session(self):
253        """Returns the session of the level the ticket has been added to.
254        """
255        try:
256            return self.__parent__.level_session
257        except AttributeError:
258            return None
259
260    @property
261    def grade(self):
262        """Returns the grade calculated from score.
263        """
264        return getGradeWeightFromScore(self.score)[0]
265
266    @property
267    def weight(self):
268        """Returns the weight calculated from score.
269        """
270        return getGradeWeightFromScore(self.score)[1]
271
272CourseTicket = attrs_to_fields(CourseTicket)
273
274class CourseTicketFactory(grok.GlobalUtility):
275    """A factory for student study levels.
276    """
277    grok.implements(IFactory)
278    grok.name(u'waeup.CourseTicket')
279    title = u"Create a new course ticket.",
280    description = u"This factory instantiates new course ticket instances."
281
282    def __call__(self, *args, **kw):
283        return CourseTicket()
284
285    def getInterfaces(self):
286        return implementedBy(CourseTicket)
Note: See TracBrowser for help on using the repository browser.