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

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

Implement different course registration deadlines and fees (untested!)

  • Property svn:keywords set to Id
File size: 7.7 KB
Line 
1## $Id: studylevel.py 14117 2016-08-23 09:41: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(score, ca, student):
39    if None in (score, ca):
40        return (None, None)
41    total = score + ca
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        return level_gpa, credits_counted, credits_weighted
91
92    @property
93    def gpa_params_rectified(self):
94        return self.gpa_params
95
96    @property
97    def course_registration_allowed(self):
98        if self.student.is_fresh:
99            return True
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 True
118        if not deadline or deadline > datetime.now(pytz.utc):
119            return True
120        payment_made = False
121        if len(self.student['payments']):
122            for ticket in self.student['payments'].values():
123                if ticket.p_category == 'late_registration' and \
124                    ticket.p_session == self.level_session and \
125                    ticket.p_state == 'paid':
126                        payment_made = True
127        if payment_made:
128            return True
129        return False
130
131    # only AAUE
132    @property
133    def remark(self):
134        if self.cumulative_params[0] < 1.5:
135            return 'Probation'
136        if self.cumulative_params[0] < 5.1:
137            return 'Proceed'
138        return 'N/A'
139
140    # only AAUE
141    @property
142    def final_remark(self):
143        if self.cumulative_params[0] < 1.5:
144            return 'Fail'
145        if self.cumulative_params[0] < 2.4:
146            return 'Third Class (Honours)'
147        if self.cumulative_params[0] < 3.5:
148            return 'Second Class (Honours) Lower Division'
149        if self.cumulative_params[0] < 4.5:
150            return 'Second Class (Honours) Upper Division'
151        if self.cumulative_params[0] < 5.1:
152            return 'First Class Honours'
153        return 'N/A'
154
155    def addCourseTicket(self, ticket, course):
156        """Add a course ticket object.
157        """
158        if not ICourseTicket.providedBy(ticket):
159            raise TypeError(
160                'StudentStudyLeves contain only ICourseTicket instances')
161        ticket.code = course.code
162        ticket.title = course.title
163        ticket.fcode = course.__parent__.__parent__.__parent__.code
164        ticket.dcode = course.__parent__.__parent__.code
165        ticket.credits = course.credits
166        if self.student.entry_session < 2013:
167            ticket.passmark = course.passmark - 5
168        else:
169            ticket.passmark = course.passmark
170        ticket.semester = course.semester
171        self[ticket.code] = ticket
172        return
173
174CustomStudentStudyLevel = attrs_to_fields(
175    CustomStudentStudyLevel, omit=[
176    'total_credits', 'total_credits_s1', 'total_credits_s2', 'gpa'])
177
178class CustomStudentStudyLevelFactory(StudentStudyLevelFactory):
179    """A factory for student study levels.
180    """
181
182    def __call__(self, *args, **kw):
183        return CustomStudentStudyLevel()
184
185    def getInterfaces(self):
186        return implementedBy(CustomStudentStudyLevel)
187
188class CustomCourseTicket(CourseTicket):
189    """This is a course ticket which allows the
190    student to attend the course. Lecturers will enter scores and more at
191    the end of the term.
192
193    A course ticket contains a copy of the original course and
194    course referrer data. If the courses and/or their referrers are removed, the
195    corresponding tickets remain unchanged. So we do not need any event
196    triggered actions on course tickets.
197    """
198    grok.implements(ICustomCourseTicket, IStudentNavigation)
199    grok.provides(ICustomCourseTicket)
200
201    @property
202    def grade(self):
203        """Returns the grade calculated from score.
204        """
205        return getGradeWeightFromScore(self.score, self.ca, self.student)[0]
206
207    @property
208    def weight(self):
209        """Returns the weight calculated from score.
210        """
211        return getGradeWeightFromScore(self.score, self.ca, self.student)[1]
212
213CustomCourseTicket = attrs_to_fields(CustomCourseTicket)
214
215class CustomCourseTicketFactory(CourseTicketFactory):
216    """A factory for student study levels.
217    """
218
219    def __call__(self, *args, **kw):
220        return CustomCourseTicket()
221
222    def getInterfaces(self):
223        return implementedBy(CustomCourseTicket)
Note: See TracBrowser for help on using the repository browser.