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

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

Use carry over tickets in next level for GPA calculation. Ignore carry over tickets in current level.

  • Property svn:keywords set to Id
File size: 8.3 KB
RevLine 
[7191]1## $Id: studylevel.py 10277 2013-06-05 09:07:33Z 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)
110                    if co_ticket is not None:
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.