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

Last change on this file since 10508 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
RevLine 
[7191]1## $Id: studylevel.py 10479 2013-08-12 08:57:26Z henrik $
2##
[6775]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
[7536]23from zope.component.interfaces import IFactory
[9501]24from zope.component import createObject
[8330]25from zope.interface import implementedBy
[9912]26from waeup.kofa.interfaces import academic_sessions_vocab
[7811]27from waeup.kofa.students.interfaces import (
[6781]28    IStudentStudyLevel, IStudentNavigation, ICourseTicket)
[7811]29from waeup.kofa.utils.helpers import attrs_to_fields
30from waeup.kofa.students.vocabularies import StudyLevelSource
[6775]31
[10276]32def find_carry_over(ticket):
[10277]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
[10276]44
[9684]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
[6775]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
[8736]71    @property
72    def student(self):
73        try:
74            return self.__parent__.__parent__
75        except AttributeError:
76            return None
[6775]77
[9235]78    @property
[9253]79    def certcode(self):
80        try:
81            return self.__parent__.certificate.code
82        except AttributeError:
83            return None
84
85    @property
[9235]86    def number_of_tickets(self):
87        return len(self)
88
[9257]89    @property
[9532]90    def total_credits(self):
91        total = 0
92        for ticket in self.values():
93            total += ticket.credits
94        return total
95
96    @property
[9912]97    def getSessionString(self):
98        return academic_sessions_vocab.getTerm(
99            self.level_session).title
100
101    @property
[10479]102    def gpa_params_rectified(self):
[10276]103        weighted_credits = 0.0
[9687]104        credits_counted = 0
[10276]105        level_gpa = 0.0
[9687]106        for ticket in self.values():
[10276]107            if ticket.carry_over is False and ticket.score:
108                if ticket.score < ticket.passmark:
109                    co_ticket = find_carry_over(ticket)
[10313]110                    if co_ticket is not None and co_ticket.weight is not None:
[10276]111                        credits_counted += co_ticket.credits
112                        weighted_credits += co_ticket.credits * co_ticket.weight
113                    continue
[9687]114                credits_counted += ticket.credits
[10276]115                weighted_credits += ticket.credits * ticket.weight
[9687]116        if credits_counted:
[10276]117            level_gpa = round(weighted_credits/credits_counted, 2)
118        return level_gpa, credits_counted, weighted_credits
[9687]119
120    @property
[10479]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
[9257]138    def is_current_level(self):
139        try:
140            return self.__parent__.current_level == self.level
141        except AttributeError:
142            return False
143
[8735]144    def writeLogMessage(self, view, message):
145        return self.__parent__.__parent__.writeLogMessage(view, message)
146
[6775]147    @property
148    def level_title(self):
149        studylevelsource = StudyLevelSource()
150        return studylevelsource.factory.getTitle(self.__parent__, self.level)
151
[8920]152    def addCourseTicket(self, ticket, course):
[6781]153        """Add a course ticket object.
154        """
[8920]155        if not ICourseTicket.providedBy(ticket):
[6781]156            raise TypeError(
157                'StudentStudyLeves contain only ICourseTicket instances')
[8920]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
[6781]166        return
167
[9501]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
[9690]183StudentStudyLevel = attrs_to_fields(
[10479]184    StudentStudyLevel, omit=['total_credits', 'gpa'])
[6781]185
[7536]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
[6781]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.
[6783]204
205    A course ticket contains a copy of the original course and
[8920]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
[6783]209    triggered actions on course tickets.
[6781]210    """
211    grok.implements(ICourseTicket, IStudentNavigation)
212    grok.provides(ICourseTicket)
213
[6795]214    def __init__(self):
[6781]215        super(CourseTicket, self).__init__()
[6795]216        self.code = None
[6781]217        return
218
[8736]219    @property
220    def student(self):
[8338]221        """Get the associated student object.
222        """
223        try:
224            return self.__parent__.__parent__.__parent__
225        except AttributeError:
226            return None
[6781]227
[9253]228    @property
229    def certcode(self):
230        try:
231            return self.__parent__.__parent__.certificate.code
232        except AttributeError:
233            return None
234
[9698]235    @property
236    def removable_by_student(self):
237        return not self.mandatory
238
[8735]239    def writeLogMessage(self, view, message):
240        return self.__parent__.__parent__.__parent__.writeLogMessage(view, message)
241
[9925]242    @property
243    def level(self):
[7633]244        """Returns the id of the level the ticket has been added to.
245        """
[8338]246        try:
247            return self.__parent__.level
248        except AttributeError:
249            return None
[7633]250
[9925]251    @property
252    def level_session(self):
[7633]253        """Returns the session of the level the ticket has been added to.
254        """
[8338]255        try:
256            return self.__parent__.level_session
257        except AttributeError:
258            return None
[7633]259
[9684]260    @property
261    def grade(self):
262        """Returns the grade calculated from score.
263        """
264        return getGradeWeightFromScore(self.score)[0]
[7633]265
[9684]266    @property
267    def weight(self):
268        """Returns the weight calculated from score.
269        """
270        return getGradeWeightFromScore(self.score)[1]
271
[6782]272CourseTicket = attrs_to_fields(CourseTicket)
[7548]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.