source: main/waeup.aaue/trunk/src/waeup/aaue/students/studylevel.py @ 14212

Last change on this file since 14212 was 14206, checked in by Henrik Bettermann, 8 years ago

Add imported_gpa and imported_cgpa fields and adjust exporters and batch processors.

  • Property svn:keywords set to Id
File size: 8.2 KB
Line 
1## $Id: studylevel.py 14206 2016-09-29 08:54:32Z henrik $
2##
3## Copyright (C) 2012 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
23import pytz
24from datetime import datetime
25from zope.component.interfaces import IFactory
26from zope.component import createObject
27from zope.interface import implementedBy
28from waeup.kofa.utils.helpers import attrs_to_fields
29from waeup.kofa.interfaces import CREATED
30from waeup.kofa.students.studylevel import (
31    StudentStudyLevel, CourseTicket,
32    CourseTicketFactory, StudentStudyLevelFactory)
33from waeup.kofa.students.interfaces import IStudentNavigation, ICourseTicket
34from waeup.aaue.students.interfaces import (
35    ICustomStudentStudyLevel, ICustomCourseTicket)
36
37
38def getGradeWeightFromScore(total, student):
39    if total is None:
40        return (None, None)
41    if total >= 70:
42        return ('A',5)
43    if total >= 60:
44        return ('B',4)
45    if total >= 50:
46        return ('C',3)
47    if total >= 45:
48        return ('D',2)
49    if total >= 40 and student.entry_session < 2013:
50        return ('E',1)
51    return ('F',0)
52
53
54class CustomStudentStudyLevel(StudentStudyLevel):
55    """This is a container for course tickets.
56    """
57    grok.implements(ICustomStudentStudyLevel, IStudentNavigation)
58    grok.provides(ICustomStudentStudyLevel)
59
60    @property
61    def total_credits_s1(self):
62        total = 0
63        for ticket in self.values():
64            if ticket.semester == 1:
65                total += ticket.credits
66        return total
67
68    @property
69    def total_credits_s2(self):
70        total = 0
71        for ticket in self.values():
72            if ticket.semester == 2:
73                total += ticket.credits
74        return total
75
76    @property
77    def gpa_params(self):
78        """Calculate gpa parameters for this level.
79        """
80        credits_weighted = 0.0
81        credits_counted = 0
82        level_gpa = 0.0
83        for ticket in self.values():
84            if None not in (ticket.score, ticket.ca):
85                credits_counted += ticket.credits
86                credits_weighted += ticket.credits * ticket.weight
87        if credits_counted:
88            level_gpa = round(credits_weighted/credits_counted, 3)
89        # Override level_gpa if value has been imported
90        imported_gpa = getattr(self, 'imported_gpa', None)
91        if imported_gpa:
92            level_gpa = imported_gpa
93        return level_gpa, credits_counted, credits_weighted
94
95    @property
96    def gpa_params_rectified(self):
97        return self.gpa_params
98
99    @property
100    def course_registration_allowed(self):
101        if self.student.is_fresh:
102            return True
103        try:
104            if self.student.is_postgrad:
105                deadline = grok.getSite()['configuration'][
106                        str(self.level_session)].coursereg_deadline_pg
107            elif self.student.current_mode.startswith('dp'):
108                deadline = grok.getSite()['configuration'][
109                        str(self.level_session)].coursereg_deadline_dp
110            elif self.student.current_mode.endswith('_pt'):
111                deadline = grok.getSite()['configuration'][
112                        str(self.level_session)].coursereg_deadline_pt
113            elif self.student.current_mode == 'found':
114                deadline = grok.getSite()['configuration'][
115                        str(self.level_session)].coursereg_deadline_found
116            else:
117                deadline = grok.getSite()['configuration'][
118                        str(self.level_session)].coursereg_deadline
119        except (TypeError, KeyError):
120            return True
121        if not deadline or deadline > datetime.now(pytz.utc):
122            return True
123        payment_made = False
124        if len(self.student['payments']):
125            for ticket in self.student['payments'].values():
126                if ticket.p_category == 'late_registration' and \
127                    ticket.p_session == self.level_session and \
128                    ticket.p_state == 'paid':
129                        payment_made = True
130        if payment_made:
131            return True
132        return False
133
134    # only AAUE
135    @property
136    def remark(self):
137        certificate = getattr(self.__parent__,'certificate',None)
138        end_level = getattr(certificate, 'end_level', None)
139        # final student remark
140        if end_level and self.student.current_level >= end_level:
141            failed_courses = self.passed_params[4]
142            if '_m' in failed_courses:
143                return 'FRNS'
144            if self.cumulative_params[0] < 1.5:
145                return 'Fail'
146            if self.cumulative_params[0] < 2.4:
147                return '3rd'
148            if self.cumulative_params[0] < 3.5:
149                return '2nd Lower'
150            if self.cumulative_params[0] < 4.5:
151                return '2nd Upper'
152            if self.cumulative_params[0] < 5.1:
153                return '1st'
154            return 'N/A'
155        # returning student remark
156        if self.cumulative_params[0] < 1.5:
157            return 'Probation'
158        if self.cumulative_params[0] < 5.1:
159            return 'Proceed'
160        return 'N/A'
161
162    def addCourseTicket(self, ticket, course):
163        """Add a course ticket object.
164        """
165        if not ICourseTicket.providedBy(ticket):
166            raise TypeError(
167                'StudentStudyLeves contain only ICourseTicket instances')
168        ticket.code = course.code
169        ticket.title = course.title
170        ticket.fcode = course.__parent__.__parent__.__parent__.code
171        ticket.dcode = course.__parent__.__parent__.code
172        ticket.credits = course.credits
173        if self.student.entry_session < 2013:
174            ticket.passmark = course.passmark - 5
175        else:
176            ticket.passmark = course.passmark
177        ticket.semester = course.semester
178        self[ticket.code] = ticket
179        return
180
181CustomStudentStudyLevel = attrs_to_fields(
182    CustomStudentStudyLevel, omit=[
183    'total_credits', 'total_credits_s1', 'total_credits_s2', 'gpa'])
184
185class CustomStudentStudyLevelFactory(StudentStudyLevelFactory):
186    """A factory for student study levels.
187    """
188
189    def __call__(self, *args, **kw):
190        return CustomStudentStudyLevel()
191
192    def getInterfaces(self):
193        return implementedBy(CustomStudentStudyLevel)
194
195class CustomCourseTicket(CourseTicket):
196    """This is a course ticket which allows the
197    student to attend the course. Lecturers will enter scores and more at
198    the end of the term.
199
200    A course ticket contains a copy of the original course and
201    course referrer data. If the courses and/or their referrers are removed, the
202    corresponding tickets remain unchanged. So we do not need any event
203    triggered actions on course tickets.
204    """
205    grok.implements(ICustomCourseTicket, IStudentNavigation)
206    grok.provides(ICustomCourseTicket)
207
208    @property
209    def grade(self):
210        """Returns the grade calculated from score.
211        """
212        return getGradeWeightFromScore(self.total_score, self.student)[0]
213
214    @property
215    def weight(self):
216        """Returns the weight calculated from score.
217        """
218        return getGradeWeightFromScore(self.total_score, self.student)[1]
219
220    @property
221    def total_score(self):
222        """Returns ca + score.
223        """
224        if not None in (self.score, self.ca):
225            return self.score + self.ca
226
227CustomCourseTicket = attrs_to_fields(CustomCourseTicket)
228
229class CustomCourseTicketFactory(CourseTicketFactory):
230    """A factory for student study levels.
231    """
232
233    def __call__(self, *args, **kw):
234        return CustomCourseTicket()
235
236    def getInterfaces(self):
237        return implementedBy(CustomCourseTicket)
Note: See TracBrowser for help on using the repository browser.