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

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

Raise TicketError? if course is in 2nd semester but
schoolfee has not yet been fully paid.

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