## $Id: studylevel.py 14082 2016-08-16 11:14:45Z henrik $ ## ## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## """ Container which holds the data of a student study level and contains the course tickets. """ import grok import pytz from datetime import datetime from zope.component.interfaces import IFactory from zope.component import createObject from zope.interface import implementedBy from waeup.kofa.utils.helpers import attrs_to_fields from waeup.kofa.interfaces import CREATED from waeup.kofa.students.studylevel import ( StudentStudyLevel, CourseTicket, CourseTicketFactory, StudentStudyLevelFactory) from waeup.kofa.students.interfaces import IStudentNavigation, ICourseTicket from waeup.aaue.students.interfaces import ( ICustomStudentStudyLevel, ICustomCourseTicket) def getGradeWeightFromScore(score, ca, student): if None in (score, ca): return (None, None) total = score + ca if total >= 70: return ('A',5) if total >= 60: return ('B',4) if total >= 50: return ('C',3) if total >= 45: return ('D',2) if total >= 40 and student.entry_session < 2013: return ('E',1) return ('F',0) class CustomStudentStudyLevel(StudentStudyLevel): """This is a container for course tickets. """ grok.implements(ICustomStudentStudyLevel, IStudentNavigation) grok.provides(ICustomStudentStudyLevel) @property def total_credits_s1(self): total = 0 for ticket in self.values(): if ticket.semester == 1: total += ticket.credits return total @property def total_credits_s2(self): total = 0 for ticket in self.values(): if ticket.semester == 2: total += ticket.credits return total @property def gpa_params(self): """Calculate gpa parameters for this level. """ credits_weighted = 0.0 credits_counted = 0 level_gpa = 0.0 for ticket in self.values(): if None not in (ticket.score, ticket.ca): credits_counted += ticket.credits credits_weighted += ticket.credits * ticket.weight if credits_counted: level_gpa = round(credits_weighted/credits_counted, 3) return level_gpa, credits_counted, credits_weighted @property def gpa_params_rectified(self): return self.gpa_params @property def course_registration_allowed(self): if self.student.is_fresh: return True try: deadline = grok.getSite()['configuration'][ str(self.level_session)].coursereg_deadline except (TypeError, KeyError): return True if not deadline or deadline > datetime.now(pytz.utc): return True payment_made = False if len(self.student['payments']): for ticket in self.student['payments'].values(): if ticket.p_category == 'late_registration' and \ ticket.p_session == self.level_session and \ ticket.p_state == 'paid': payment_made = True if payment_made: return True return False # only AAUE @property def remark(self): if self.cumulative_params[0] < 1.5: return 'Probation' if self.cumulative_params[0] < 5.1: return 'Proceed' return 'N/A' # only AAUE @property def final_remark(self): if self.cumulative_params[0] < 1.5: return 'Fail' if self.cumulative_params[0] < 2.4: return 'Third Class (Honours)' if self.cumulative_params[0] < 3.5: return 'Second Class (Honours) Lower Division' if self.cumulative_params[0] < 4.5: return 'Second Class (Honours) Upper Division' if self.cumulative_params[0] < 5.1: return 'First Class Honours' return 'N/A' def addCourseTicket(self, ticket, course): """Add a course ticket object. """ if not ICourseTicket.providedBy(ticket): raise TypeError( 'StudentStudyLeves contain only ICourseTicket instances') ticket.code = course.code ticket.title = course.title ticket.fcode = course.__parent__.__parent__.__parent__.code ticket.dcode = course.__parent__.__parent__.code ticket.credits = course.credits if self.student.entry_session < 2013: ticket.passmark = course.passmark - 5 else: ticket.passmark = course.passmark ticket.semester = course.semester self[ticket.code] = ticket return CustomStudentStudyLevel = attrs_to_fields( CustomStudentStudyLevel, omit=[ 'total_credits', 'total_credits_s1', 'total_credits_s2', 'gpa']) class CustomStudentStudyLevelFactory(StudentStudyLevelFactory): """A factory for student study levels. """ def __call__(self, *args, **kw): return CustomStudentStudyLevel() def getInterfaces(self): return implementedBy(CustomStudentStudyLevel) class CustomCourseTicket(CourseTicket): """This is a course ticket which allows the student to attend the course. Lecturers will enter scores and more at the end of the term. A course ticket contains a copy of the original course and course referrer data. If the courses and/or their referrers are removed, the corresponding tickets remain unchanged. So we do not need any event triggered actions on course tickets. """ grok.implements(ICustomCourseTicket, IStudentNavigation) grok.provides(ICustomCourseTicket) @property def grade(self): """Returns the grade calculated from score. """ return getGradeWeightFromScore(self.score, self.ca, self.student)[0] @property def weight(self): """Returns the weight calculated from score. """ return getGradeWeightFromScore(self.score, self.ca, self.student)[1] CustomCourseTicket = attrs_to_fields(CustomCourseTicket) class CustomCourseTicketFactory(CourseTicketFactory): """A factory for student study levels. """ def __call__(self, *args, **kw): return CustomCourseTicket() def getInterfaces(self): return implementedBy(CustomCourseTicket)