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

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

Add column.

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