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

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

Implement entry_session-dependent grading system.

  • Property svn:keywords set to Id
File size: 6.2 KB
Line 
1## $Id: studylevel.py 14075 2016-08-13 03:23:04Z 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            deadline = grok.getSite()['configuration'][
102                    str(self.level_session)].coursereg_deadline
103        except (TypeError, KeyError):
104            return True
105        if not deadline or deadline > datetime.now(pytz.utc):
106            return True
107        payment_made = False
108        if len(self.student['payments']):
109            for ticket in self.student['payments'].values():
110                if ticket.p_category == 'late_registration' and \
111                    ticket.p_session == self.level_session and \
112                    ticket.p_state == 'paid':
113                        payment_made = True
114        if payment_made:
115            return True
116        return False
117
118    def addCourseTicket(self, ticket, course):
119        """Add a course ticket object.
120        """
121        if not ICourseTicket.providedBy(ticket):
122            raise TypeError(
123                'StudentStudyLeves contain only ICourseTicket instances')
124        ticket.code = course.code
125        ticket.title = course.title
126        ticket.fcode = course.__parent__.__parent__.__parent__.code
127        ticket.dcode = course.__parent__.__parent__.code
128        ticket.credits = course.credits
129        if self.student.entry_session < 2013:
130            ticket.passmark = course.passmark - 5
131        else:
132            ticket.passmark = course.passmark
133        ticket.semester = course.semester
134        self[ticket.code] = ticket
135        return
136
137CustomStudentStudyLevel = attrs_to_fields(
138    CustomStudentStudyLevel, omit=[
139    'total_credits', 'total_credits_s1', 'total_credits_s2', 'gpa'])
140
141class CustomStudentStudyLevelFactory(StudentStudyLevelFactory):
142    """A factory for student study levels.
143    """
144
145    def __call__(self, *args, **kw):
146        return CustomStudentStudyLevel()
147
148    def getInterfaces(self):
149        return implementedBy(CustomStudentStudyLevel)
150
151class CustomCourseTicket(CourseTicket):
152    """This is a course ticket which allows the
153    student to attend the course. Lecturers will enter scores and more at
154    the end of the term.
155
156    A course ticket contains a copy of the original course and
157    course referrer data. If the courses and/or their referrers are removed, the
158    corresponding tickets remain unchanged. So we do not need any event
159    triggered actions on course tickets.
160    """
161    grok.implements(ICustomCourseTicket, IStudentNavigation)
162    grok.provides(ICustomCourseTicket)
163
164    @property
165    def grade(self):
166        """Returns the grade calculated from score.
167        """
168        return getGradeWeightFromScore(self.score, self.ca, self.student)[0]
169
170    @property
171    def weight(self):
172        """Returns the weight calculated from score.
173        """
174        return getGradeWeightFromScore(self.score, self.ca, self.student)[1]
175
176CustomCourseTicket = attrs_to_fields(CustomCourseTicket)
177
178class CustomCourseTicketFactory(CourseTicketFactory):
179    """A factory for student study levels.
180    """
181
182    def __call__(self, *args, **kw):
183        return CustomCourseTicket()
184
185    def getInterfaces(self):
186        return implementedBy(CustomCourseTicket)
Note: See TracBrowser for help on using the repository browser.