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

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

getGradeWeightFromScore is now the same in the base package.

  • Property svn:keywords set to Id
File size: 11.8 KB
Line 
1## $Id: studylevel.py 14354 2016-12-16 17:36:07Z 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)
37from waeup.aaue.interfaces import MessageFactory as _
38
39
40class CustomStudentStudyLevel(StudentStudyLevel):
41    """This is a container for course tickets.
42    """
43    grok.implements(ICustomStudentStudyLevel, IStudentNavigation)
44    grok.provides(ICustomStudentStudyLevel)
45
46    @property
47    def total_credits_s1(self):
48        total = 0
49        for ticket in self.values():
50            if ticket.semester == 1:
51                total += ticket.credits
52        return total
53
54    @property
55    def total_credits_s2(self):
56        total = 0
57        for ticket in self.values():
58            if ticket.semester == 2:
59                total += ticket.credits
60        return total
61
62    @property
63    def gpa_params(self):
64        """Calculate gpa parameters for this level.
65        """
66        credits_weighted = 0.0
67        credits_counted = 0
68        level_gpa = 0.0
69        for ticket in self.values():
70            if None not in (ticket.score, ticket.ca):
71                credits_counted += ticket.credits
72                credits_weighted += ticket.credits * ticket.weight
73        if credits_counted:
74            level_gpa = round(credits_weighted/credits_counted, 3)
75        # Override level_gpa if value has been imported
76        imported_gpa = getattr(self, 'imported_gpa', None)
77        if imported_gpa:
78            level_gpa = imported_gpa
79        return level_gpa, credits_counted, credits_weighted
80
81    @property
82    def gpa_params_rectified(self):
83        return self.gpa_params
84
85    @property
86    def course_registration_forbidden(self):
87        fac_dep_paid = True
88        if self.student.entry_session >= 2016:
89            fac_dep_paid = False
90            for ticket in self.student['payments'].values():
91                if ticket.p_category == 'fac_dep' and \
92                    ticket.p_session == self.level_session and \
93                    ticket.p_state == 'paid':
94                        fac_dep_paid = True
95                        continue
96        if not fac_dep_paid:
97            return _("Please pay faculty and departmental dues first.")
98        if self.student.is_fresh:
99            return
100        try:
101            if self.student.is_postgrad:
102                deadline = grok.getSite()['configuration'][
103                        str(self.level_session)].coursereg_deadline_pg
104            elif self.student.current_mode.startswith('dp'):
105                deadline = grok.getSite()['configuration'][
106                        str(self.level_session)].coursereg_deadline_dp
107            elif self.student.current_mode.endswith('_pt'):
108                deadline = grok.getSite()['configuration'][
109                        str(self.level_session)].coursereg_deadline_pt
110            elif self.student.current_mode == 'found':
111                deadline = grok.getSite()['configuration'][
112                        str(self.level_session)].coursereg_deadline_found
113            else:
114                deadline = grok.getSite()['configuration'][
115                        str(self.level_session)].coursereg_deadline
116        except (TypeError, KeyError):
117            return
118        if not deadline or deadline > datetime.now(pytz.utc):
119            return
120        if len(self.student['payments']):
121            for ticket in self.student['payments'].values():
122                if ticket.p_category == 'late_registration' and \
123                    ticket.p_session == self.level_session and \
124                    ticket.p_state == 'paid':
125                        return
126        return _("Course registration has ended. "
127                 "Please pay the late registration fee.")
128
129    # only AAUE
130    @property
131    def remark(self):
132        certificate = getattr(self.__parent__,'certificate',None)
133        end_level = getattr(certificate, 'end_level', None)
134        # final student remark
135        if end_level and self.student.current_level >= end_level:
136            failed_courses = self.passed_params[4]
137            if '_m' in failed_courses:
138                return 'FRNS'
139            if self.cumulative_params[0] < 1.5:
140                return 'Fail'
141            if self.cumulative_params[0] < 2.4:
142                return '3s_rd_s'
143            if self.cumulative_params[0] < 3.5:
144                return '2s_2_s'
145            if self.cumulative_params[0] < 4.5:
146                return '2s_1_s'
147            if self.cumulative_params[0] < 5.1:
148                return '1s_st_s'
149            return 'N/A'
150        # returning student remark
151        if self.cumulative_params[0] < 1.5:
152            return 'Probation'
153        if self.cumulative_params[0] < 5.1:
154            return 'Proceed'
155        return 'N/A'
156
157    def _schoolfeePaymentMade(self):
158        if len(self.student['payments']):
159            for ticket in self.student['payments'].values():
160                if ticket.p_state == 'paid' and \
161                    ticket.p_category in (
162                        'schoolfee', 'schoolfee_incl', 'schoolfee_2',)  and \
163                    ticket.p_session == self.student[
164                        'studycourse'].current_session:
165                    return True
166        return False
167
168    def _coursePaymentsMade(self, course):
169        if self.level_session < 2016:
170            return True
171        if not course.code[:3] in ('GST', 'ENT'):
172            return True
173        if len(self.student['payments']):
174            paid_cats = list()
175            for pticket in self.student['payments'].values():
176                if pticket.p_state == 'paid':
177                    paid_cats.append(pticket.p_category)
178            if course.code in ('GST101', 'GST102', 'GST111', 'GST112') and \
179                not 'gst_registration_1' in paid_cats:
180                return False
181            if course.code in ('GST222',) and \
182                not 'gst_registration_2' in paid_cats:
183                return False
184            if course.code in ('ENT201',) and \
185                not 'gst_registration_3' in paid_cats:
186                return False
187            if course.code == 'GST101'and \
188                not 'gst_text_book_1' in paid_cats and \
189                not 'gst_text_book_5' in paid_cats:
190                return False
191            if course.code == 'GST102'and \
192                not 'gst_text_book_1' in paid_cats and \
193                not 'gst_text_book_6' in paid_cats:
194                return False
195            if course.code == 'GST111'and \
196                not 'gst_text_book_2' in paid_cats and \
197                not 'gst_text_book_7' in paid_cats:
198                return False
199            if course.code == 'GST112'and \
200                not 'gst_text_book_2' in paid_cats and \
201                not 'gst_text_book_8' in paid_cats:
202                return False
203            if course.code in ('GST222',) and \
204                not 'gst_text_book_3' in paid_cats:
205                return False
206            if course.code in ('ENT201',) and \
207                not 'gst_text_book_4' in paid_cats:
208                return False
209            return True
210        return False
211
212    def addCourseTicket(self, ticket, course):
213        """Add a course ticket object.
214        """
215        if not ICourseTicket.providedBy(ticket):
216            raise TypeError(
217                'StudentStudyLeves contain only ICourseTicket instances')
218        # Raise TicketError if course is in 2nd semester but
219        # schoolfee has not yet been fully paid.
220        if course.semester == 2 and not self._schoolfeePaymentMade():
221            raise TicketError(
222                _('%s is a 2nd semester course which can only be added '
223                  'if school fees have been fully paid.' % course.code))
224        # Raise TicketError if registration fee or text
225        # book fee haven't been paid.
226        if not self._coursePaymentsMade(course):
227            raise TicketError(
228                _('%s can only be added if both registration fee and text '
229                  'book fee have been paid.'
230                  % course.code))
231        ticket.code = course.code
232        ticket.title = course.title
233        ticket.fcode = course.__parent__.__parent__.__parent__.code
234        ticket.dcode = course.__parent__.__parent__.code
235        ticket.credits = course.credits
236        if self.student.entry_session < 2013:
237            ticket.passmark = course.passmark - 5
238        else:
239            ticket.passmark = course.passmark
240        ticket.semester = course.semester
241        self[ticket.code] = ticket
242        return
243
244    def addCertCourseTickets(self, cert):
245        """Collect all certificate courses and create course
246        tickets automatically.
247        """
248        if cert is not None:
249            for key, val in cert.items():
250                if val.level != self.level:
251                    continue
252                ticket = createObject(u'waeup.CourseTicket')
253                ticket.automatic = True
254                ticket.mandatory = val.mandatory
255                ticket.carry_over = False
256                try:
257                    self.addCourseTicket(ticket, val.course)
258                except TicketError:
259                    pass
260        return
261
262CustomStudentStudyLevel = attrs_to_fields(
263    CustomStudentStudyLevel, omit=[
264    'total_credits', 'total_credits_s1', 'total_credits_s2', 'gpa'])
265
266class CustomStudentStudyLevelFactory(StudentStudyLevelFactory):
267    """A factory for student study levels.
268    """
269
270    def __call__(self, *args, **kw):
271        return CustomStudentStudyLevel()
272
273    def getInterfaces(self):
274        return implementedBy(CustomStudentStudyLevel)
275
276class CustomCourseTicket(CourseTicket):
277    """This is a course ticket which allows the
278    student to attend the course. Lecturers will enter scores and more at
279    the end of the term.
280
281    A course ticket contains a copy of the original course and
282    course referrer data. If the courses and/or their referrers are removed, the
283    corresponding tickets remain unchanged. So we do not need any event
284    triggered actions on course tickets.
285    """
286    grok.implements(ICustomCourseTicket, IStudentNavigation)
287    grok.provides(ICustomCourseTicket)
288
289    @property
290    def total_score(self):
291        """Returns ca + score.
292        """
293        if not None in (self.score, self.ca):
294            return self.score + self.ca
295
296    @property
297    def editable_by_lecturer(self):
298        """True if lecturer is allowed to edit the ticket.
299        """
300        return True
301
302CustomCourseTicket = attrs_to_fields(CustomCourseTicket)
303
304class CustomCourseTicketFactory(CourseTicketFactory):
305    """A factory for student study levels.
306    """
307
308    def __call__(self, *args, **kw):
309        return CustomCourseTicket()
310
311    def getInterfaces(self):
312        return implementedBy(CustomCourseTicket)
Note: See TracBrowser for help on using the repository browser.