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

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

ticket.weight must not be None.

  • Property svn:keywords set to Id
File size: 8.4 KB
Line 
1## $Id: studylevel.py 10313 2013-06-18 10:37:46Z 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(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 is_current_level(self):
122        try:
123            return self.__parent__.current_level == self.level
124        except AttributeError:
125            return False
126
127    def writeLogMessage(self, view, message):
128        return self.__parent__.__parent__.writeLogMessage(view, message)
129
130    @property
131    def level_title(self):
132        studylevelsource = StudyLevelSource()
133        return studylevelsource.factory.getTitle(self.__parent__, self.level)
134
135    def addCourseTicket(self, ticket, course):
136        """Add a course ticket object.
137        """
138        if not ICourseTicket.providedBy(ticket):
139            raise TypeError(
140                'StudentStudyLeves contain only ICourseTicket instances')
141        ticket.code = course.code
142        ticket.title = course.title
143        ticket.fcode = course.__parent__.__parent__.__parent__.code
144        ticket.dcode = course.__parent__.__parent__.code
145        ticket.credits = course.credits
146        ticket.passmark = course.passmark
147        ticket.semester = course.semester
148        self[ticket.code] = ticket
149        return
150
151    def addCertCourseTickets(self, cert):
152        """Collect all certificate courses and create course
153        tickets automatically.
154        """
155        if cert is not None:
156            for key, val in cert.items():
157                if val.level != self.level:
158                    continue
159                ticket = createObject(u'waeup.CourseTicket')
160                ticket.automatic = True
161                ticket.mandatory = val.mandatory
162                ticket.carry_over = False
163                self.addCourseTicket(ticket, val.course)
164        return
165
166StudentStudyLevel = attrs_to_fields(
167    StudentStudyLevel, omit=['total_credits'])
168
169class StudentStudyLevelFactory(grok.GlobalUtility):
170    """A factory for student study levels.
171    """
172    grok.implements(IFactory)
173    grok.name(u'waeup.StudentStudyLevel')
174    title = u"Create a new student study level.",
175    description = u"This factory instantiates new student study level instances."
176
177    def __call__(self, *args, **kw):
178        return StudentStudyLevel()
179
180    def getInterfaces(self):
181        return implementedBy(StudentStudyLevel)
182
183class CourseTicket(grok.Model):
184    """This is a course ticket which allows the
185    student to attend the course. Lecturers will enter scores and more at
186    the end of the term.
187
188    A course ticket contains a copy of the original course and
189    certificate course data. If the courses and/or the referrin certificate
190    courses are removed, the corresponding tickets remain unchanged.
191    So we do not need any event
192    triggered actions on course tickets.
193    """
194    grok.implements(ICourseTicket, IStudentNavigation)
195    grok.provides(ICourseTicket)
196
197    def __init__(self):
198        super(CourseTicket, self).__init__()
199        self.code = None
200        return
201
202    @property
203    def student(self):
204        """Get the associated student object.
205        """
206        try:
207            return self.__parent__.__parent__.__parent__
208        except AttributeError:
209            return None
210
211    @property
212    def certcode(self):
213        try:
214            return self.__parent__.__parent__.certificate.code
215        except AttributeError:
216            return None
217
218    @property
219    def removable_by_student(self):
220        return not self.mandatory
221
222    def writeLogMessage(self, view, message):
223        return self.__parent__.__parent__.__parent__.writeLogMessage(view, message)
224
225    @property
226    def level(self):
227        """Returns the id of the level the ticket has been added to.
228        """
229        try:
230            return self.__parent__.level
231        except AttributeError:
232            return None
233
234    @property
235    def level_session(self):
236        """Returns the session of the level the ticket has been added to.
237        """
238        try:
239            return self.__parent__.level_session
240        except AttributeError:
241            return None
242
243    @property
244    def grade(self):
245        """Returns the grade calculated from score.
246        """
247        return getGradeWeightFromScore(self.score)[0]
248
249    @property
250    def weight(self):
251        """Returns the weight calculated from score.
252        """
253        return getGradeWeightFromScore(self.score)[1]
254
255CourseTicket = attrs_to_fields(CourseTicket)
256
257class CourseTicketFactory(grok.GlobalUtility):
258    """A factory for student study levels.
259    """
260    grok.implements(IFactory)
261    grok.name(u'waeup.CourseTicket')
262    title = u"Create a new course ticket.",
263    description = u"This factory instantiates new course ticket instances."
264
265    def __call__(self, *args, **kw):
266        return CourseTicket()
267
268    def getInterfaces(self):
269        return implementedBy(CourseTicket)
Note: See TracBrowser for help on using the repository browser.