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

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

Customize _display_gpa.

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