source: main/waeup.kofa/trunk/src/waeup/kofa/students/studylevel.py @ 10546

Last change on this file since 10546 was 10539, checked in by Henrik Bettermann, 11 years ago

After some experiments with dynamical roles which allow lecturers to edit the score attribute of course tickets through the UI, I decided to discard the idea completely. It's not feasible and very dangerous to enable lecturers to edit course tickets directly: There are too many course tickets, too many lecturers and to many restrictions. The solution will be that lecturers export the course tickets of their courses, fill the score column and re-upload the csv file. This is still not easy to implement because we can't allow lecturers to access the data center directly. Further discussions are necessary ...

  • Property svn:keywords set to Id
File size: 9.4 KB
Line 
1## $Id: studylevel.py 10539 2013-08-27 19:13:51Z henrik $
2##
3## Copyright (C) 2011 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
23from zope.component.interfaces import IFactory
24from zope.catalog.interfaces import ICatalog
25from zope.component import createObject, queryUtility
26from zope.interface import implementedBy
27from waeup.kofa.interfaces import academic_sessions_vocab
28from waeup.kofa.students.interfaces import (
29    IStudentStudyLevel, IStudentNavigation, ICourseTicket)
30from waeup.kofa.utils.helpers import attrs_to_fields
31from waeup.kofa.students.vocabularies import StudyLevelSource
32
33def find_carry_over(ticket):
34    studylevel = ticket.__parent__
35    studycourse = ticket.__parent__.__parent__
36    levels = sorted(studycourse.keys())
37    index = levels.index(str(studylevel.level))
38    try:
39        next_level = levels[index+1]
40    except IndexError:
41        return None
42    next_studylevel = studycourse[next_level]
43    co_ticket = next_studylevel.get(ticket.code, None)
44    return co_ticket
45
46def getGradeWeightFromScore(score):
47    if score is None:
48        return (None, None)
49    if score >= 70:
50        return ('A',5)
51    if score >= 60:
52        return ('B',4)
53    if score >= 50:
54        return ('C',3)
55    if score >= 45:
56        return ('D',2)
57    if score >= 40:
58        return ('E',1)
59    return ('F',0)
60
61class StudentStudyLevel(grok.Container):
62    """This is a container for course tickets.
63    """
64    grok.implements(IStudentStudyLevel, IStudentNavigation)
65    grok.provides(IStudentStudyLevel)
66
67    def __init__(self):
68        super(StudentStudyLevel, self).__init__()
69        self.level = None
70        return
71
72    @property
73    def student(self):
74        try:
75            return self.__parent__.__parent__
76        except AttributeError:
77            return None
78
79    @property
80    def certcode(self):
81        try:
82            return self.__parent__.certificate.code
83        except AttributeError:
84            return None
85
86    @property
87    def number_of_tickets(self):
88        return len(self)
89
90    @property
91    def total_credits(self):
92        total = 0
93        for ticket in self.values():
94            total += ticket.credits
95        return total
96
97    @property
98    def getSessionString(self):
99        return academic_sessions_vocab.getTerm(
100            self.level_session).title
101
102    @property
103    def gpa_params_rectified(self):
104        weighted_credits = 0.0
105        credits_counted = 0
106        level_gpa = 0.0
107        for ticket in self.values():
108            if ticket.carry_over is False and ticket.score:
109                if ticket.score < ticket.passmark:
110                    co_ticket = find_carry_over(ticket)
111                    if co_ticket is not None and co_ticket.weight is not None:
112                        credits_counted += co_ticket.credits
113                        weighted_credits += co_ticket.credits * co_ticket.weight
114                    continue
115                credits_counted += ticket.credits
116                weighted_credits += ticket.credits * ticket.weight
117        if credits_counted:
118            level_gpa = round(weighted_credits/credits_counted, 2)
119        return level_gpa, credits_counted, weighted_credits
120
121    @property
122    def gpa_params(self):
123        weighted_credits = 0.0
124        credits_counted = 0
125        level_gpa = 0.0
126        for ticket in self.values():
127            if ticket.score:
128                credits_counted += ticket.credits
129                weighted_credits += ticket.credits * ticket.weight
130        if credits_counted:
131            level_gpa = round(weighted_credits/credits_counted, 2)
132        return level_gpa, credits_counted, weighted_credits
133
134    @property
135    def gpa(self):
136        return self.gpa_params[0]
137
138    @property
139    def is_current_level(self):
140        try:
141            return self.__parent__.current_level == self.level
142        except AttributeError:
143            return False
144
145    def writeLogMessage(self, view, message):
146        return self.__parent__.__parent__.writeLogMessage(view, message)
147
148    @property
149    def level_title(self):
150        studylevelsource = StudyLevelSource()
151        return studylevelsource.factory.getTitle(self.__parent__, self.level)
152
153    def addCourseTicket(self, ticket, course):
154        """Add a course ticket object.
155        """
156        if not ICourseTicket.providedBy(ticket):
157            raise TypeError(
158                'StudentStudyLeves contain only ICourseTicket instances')
159        ticket.code = course.code
160        ticket.title = course.title
161        ticket.fcode = course.__parent__.__parent__.__parent__.code
162        ticket.dcode = course.__parent__.__parent__.code
163        ticket.credits = course.credits
164        ticket.passmark = course.passmark
165        ticket.semester = course.semester
166        self[ticket.code] = ticket
167        return
168
169    def addCertCourseTickets(self, cert):
170        """Collect all certificate courses and create course
171        tickets automatically.
172        """
173        if cert is not None:
174            for key, val in cert.items():
175                if val.level != self.level:
176                    continue
177                ticket = createObject(u'waeup.CourseTicket')
178                ticket.automatic = True
179                ticket.mandatory = val.mandatory
180                ticket.carry_over = False
181                self.addCourseTicket(ticket, val.course)
182        return
183
184StudentStudyLevel = attrs_to_fields(
185    StudentStudyLevel, omit=['total_credits', 'gpa'])
186
187class StudentStudyLevelFactory(grok.GlobalUtility):
188    """A factory for student study levels.
189    """
190    grok.implements(IFactory)
191    grok.name(u'waeup.StudentStudyLevel')
192    title = u"Create a new student study level.",
193    description = u"This factory instantiates new student study level instances."
194
195    def __call__(self, *args, **kw):
196        return StudentStudyLevel()
197
198    def getInterfaces(self):
199        return implementedBy(StudentStudyLevel)
200
201class CourseTicket(grok.Model):
202    """This is a course ticket which allows the
203    student to attend the course. Lecturers will enter scores and more at
204    the end of the term.
205
206    A course ticket contains a copy of the original course and
207    certificate course data. If the courses and/or the referrin certificate
208    courses are removed, the corresponding tickets remain unchanged.
209    So we do not need any event
210    triggered actions on course tickets.
211    """
212    grok.implements(ICourseTicket, IStudentNavigation)
213    grok.provides(ICourseTicket)
214
215    def __init__(self):
216        super(CourseTicket, self).__init__()
217        self.code = None
218        return
219
220    @property
221    def student(self):
222        """Get the associated student object.
223        """
224        try:
225            return self.__parent__.__parent__.__parent__
226        except AttributeError:
227            return None
228
229    @property
230    def certcode(self):
231        try:
232            return self.__parent__.__parent__.certificate.code
233        except AttributeError:
234            return None
235
236    @property
237    def removable_by_student(self):
238        return not self.mandatory
239
240    def writeLogMessage(self, view, message):
241        return self.__parent__.__parent__.__parent__.writeLogMessage(view, message)
242
243    @property
244    def level(self):
245        """Returns the id of the level the ticket has been added to.
246        """
247        try:
248            return self.__parent__.level
249        except AttributeError:
250            return None
251
252    @property
253    def level_session(self):
254        """Returns the session of the level the ticket has been added to.
255        """
256        try:
257            return self.__parent__.level_session
258        except AttributeError:
259            return None
260
261    @property
262    def grade(self):
263        """Returns the grade calculated from score.
264        """
265        return getGradeWeightFromScore(self.score)[0]
266
267    @property
268    def weight(self):
269        """Returns the weight calculated from score.
270        """
271        return getGradeWeightFromScore(self.score)[1]
272
273    @property
274    def course(self):
275        """Returns the course the ticket is referring to. Returns
276        None if the course has been removed.
277
278        This method is not used in Kofa anymore.
279        """
280        cat = queryUtility(ICatalog, name='courses_catalog')
281        result = cat.searchResults(code=(self.code, self.code))
282        if len(result) != 1:
283            return None
284        return list(result)[0]
285
286CourseTicket = attrs_to_fields(CourseTicket)
287
288class CourseTicketFactory(grok.GlobalUtility):
289    """A factory for student study levels.
290    """
291    grok.implements(IFactory)
292    grok.name(u'waeup.CourseTicket')
293    title = u"Create a new course ticket.",
294    description = u"This factory instantiates new course ticket instances."
295
296    def __call__(self, *args, **kw):
297        return CourseTicket()
298
299    def getInterfaces(self):
300        return implementedBy(CourseTicket)
Note: See TracBrowser for help on using the repository browser.