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

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

ticket.weight must not be None.

  • Property svn:keywords set to Id
File size: 8.4 KB
RevLine 
[7191]1## $Id: studylevel.py 10313 2013-06-18 10:37:46Z 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
[10276]102    def gpa_params(self):
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
[9257]121    def is_current_level(self):
122        try:
123            return self.__parent__.current_level == self.level
124        except AttributeError:
125            return False
126
[8735]127    def writeLogMessage(self, view, message):
128        return self.__parent__.__parent__.writeLogMessage(view, message)
129
[6775]130    @property
131    def level_title(self):
132        studylevelsource = StudyLevelSource()
133        return studylevelsource.factory.getTitle(self.__parent__, self.level)
134
[8920]135    def addCourseTicket(self, ticket, course):
[6781]136        """Add a course ticket object.
137        """
[8920]138        if not ICourseTicket.providedBy(ticket):
[6781]139            raise TypeError(
140                'StudentStudyLeves contain only ICourseTicket instances')
[8920]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
[6781]149        return
150
[9501]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
[9690]166StudentStudyLevel = attrs_to_fields(
[10276]167    StudentStudyLevel, omit=['total_credits'])
[6781]168
[7536]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
[6781]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.
[6783]187
188    A course ticket contains a copy of the original course and
[8920]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
[6783]192    triggered actions on course tickets.
[6781]193    """
194    grok.implements(ICourseTicket, IStudentNavigation)
195    grok.provides(ICourseTicket)
196
[6795]197    def __init__(self):
[6781]198        super(CourseTicket, self).__init__()
[6795]199        self.code = None
[6781]200        return
201
[8736]202    @property
203    def student(self):
[8338]204        """Get the associated student object.
205        """
206        try:
207            return self.__parent__.__parent__.__parent__
208        except AttributeError:
209            return None
[6781]210
[9253]211    @property
212    def certcode(self):
213        try:
214            return self.__parent__.__parent__.certificate.code
215        except AttributeError:
216            return None
217
[9698]218    @property
219    def removable_by_student(self):
220        return not self.mandatory
221
[8735]222    def writeLogMessage(self, view, message):
223        return self.__parent__.__parent__.__parent__.writeLogMessage(view, message)
224
[9925]225    @property
226    def level(self):
[7633]227        """Returns the id of the level the ticket has been added to.
228        """
[8338]229        try:
230            return self.__parent__.level
231        except AttributeError:
232            return None
[7633]233
[9925]234    @property
235    def level_session(self):
[7633]236        """Returns the session of the level the ticket has been added to.
237        """
[8338]238        try:
239            return self.__parent__.level_session
240        except AttributeError:
241            return None
[7633]242
[9684]243    @property
244    def grade(self):
245        """Returns the grade calculated from score.
246        """
247        return getGradeWeightFromScore(self.score)[0]
[7633]248
[9684]249    @property
250    def weight(self):
251        """Returns the weight calculated from score.
252        """
253        return getGradeWeightFromScore(self.score)[1]
254
[6782]255CourseTicket = attrs_to_fields(CourseTicket)
[7548]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.