2
# SchoolTool - common information systems platform for school administration
3
# Copyright (c) 2014 Shuttleworth Foundation
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
PEAS assessment initialization
22
from collections import OrderedDict
23
from decimal import Decimal
25
from persistent import Persistent
26
from zope.annotation.interfaces import IAnnotations
27
from zope.cachedescriptors.property import Lazy
28
from zope.component import adapts
29
from zope.component import adapter
30
from zope.container.contained import Contained
31
from zope.interface import Interface
32
from zope.interface import implements
33
from zope.interface import implementer
34
from zope.lifecycleevent.interfaces import IObjectAddedEvent
35
from zope.proxy import sameProxiedObjects
36
from zope.schema import Bool
37
from zope.schema import Choice
38
from zope.schema import Int
39
from zope.schema import TextLine
41
from schooltool.app.app import StartUpBase
42
from schooltool.app.interfaces import ISchoolToolApplication
43
from schooltool.gradebook.activity import Activity
44
from schooltool.gradebook.activity import Worksheet
45
from schooltool.gradebook.interfaces import IActivities
46
from schooltool.gradebook.interfaces import ICategoryContainer
47
from schooltool.requirement.interfaces import IScoreSystemContainer
48
from schooltool.requirement.scoresystem import CustomScoreSystem
49
from schooltool.requirement.scoresystem import GlobalDiscreteValuesScoreSystem
50
from schooltool.requirement.scoresystem import RangedValuesScoreSystem
51
from schooltool.schoolyear.interfaces import ISchoolYear
52
from schooltool.schoolyear.subscriber import ObjectEventAdapterSubscriber
53
from schooltool.term.interfaces import ITerm
54
from schooltool.course.interfaces import ISection
56
from schooltool.peas import PeasMessage as _
59
ASSESSMENT_PREFERENCES_KEY = 'schooltool.peas.assessment_preferences'
60
PEAS_SCORESYSTEM_KEY = 'schooltool.peas.scoresystem'
61
MOCK_SHEET_ID = u'mock'
62
MOCK_OTHER_SHEET_ID = u'mock-other'
63
TARGET_SHEET_ID = u'target'
65
ASSESSMENT_SHEETS = OrderedDict([
66
(u'bot', {'title': u'Beginning of Term', 'editable': True}),
67
(u'mot', {'title': u'Middle of Term', 'editable': True}),
68
(u'eot', {'title': u'End of Term', 'editable': True}),
69
(MOCK_SHEET_ID, {'title': u'MOCK - PEAS', 'editable': False}),
70
(MOCK_OTHER_SHEET_ID, {'title': u'MOCK - PEAS', 'editable': False}),
71
(TARGET_SHEET_ID, {'title': u'Target', 'editable': False}),
72
(UCE_SHEET_ID, {'title': u'UCE', 'editable': False}),
76
class IAssessmentPreferences(Interface):
78
bot = Bool(title=_(u'Beginning of Term'), default=False, required=False)
79
mot = Bool(title=_(u'Middle of Term'), default=False, required=False)
80
eot = Bool(title=_(u'End of Term'), default=False, required=False)
81
mock = Bool(title=_(u'MOCK - PEAS'), default=False, required=False)
83
title=_(u'Term for deploying the MOCK - PEAS sheet'),
84
source='schooltool.peas.schoolyear_terms',
86
mock_other = Bool(title=_(u'MOCK - Other'), default=False, required=False)
87
mock_other_term = Choice(
88
title=_(u'Term for deploying the MOCK - Other sheet'),
89
source='schooltool.peas.schoolyear_terms',
93
class AssessmentPreferences(Persistent, Contained):
95
implements(IAssessmentPreferences)
103
mock_other_term = None
106
@adapter(IAssessmentPreferences)
107
@implementer(ISchoolYear)
108
def getAssessmentPreferencesSchoolYear(preferences):
109
return preferences.__parent__
112
def getAssessmentPreferences(schoolyear):
113
annotations = IAnnotations(schoolyear)
114
name = 'assessment_preferences'
116
return annotations[ASSESSMENT_PREFERENCES_KEY]
118
annotations[ASSESSMENT_PREFERENCES_KEY] = AssessmentPreferences()
119
annotations[ASSESSMENT_PREFERENCES_KEY].__name__ = name
120
annotations[ASSESSMENT_PREFERENCES_KEY].__parent__ = schoolyear
121
return annotations[ASSESSMENT_PREFERENCES_KEY]
124
def default_deploy_term(schoolyear):
125
terms = sorted(schoolyear.values(), key=lambda term: term.first)
130
def get_peas_scoresystem():
131
app = ISchoolToolApplication(None)
132
scoresystems = IScoreSystemContainer(app)
133
return scoresystems[PEAS_SCORESYSTEM_KEY]
136
class AssessmentSheetDeployBase(object):
139
def default_deploy_term(self):
140
return default_deploy_term(self.schoolyear)
142
def get_mock_term(self):
143
if self.preferences.mock_term is not None:
144
return self.preferences.mock_term
145
# deploy mock sheet to second term automatically
146
return self.default_deploy_term
148
def get_mock_other_term(self):
149
if self.preferences.mock_other_term is not None:
150
return self.preferences.mock_other_term
151
# deploy mock sheet to second term automatically
152
return self.default_deploy_term
154
def get_target_term(self):
155
return self.default_deploy_term
158
def preferences(self):
159
return IAssessmentPreferences(self.schoolyear)
162
def peas_scoresystem(self):
163
return get_peas_scoresystem()
166
def ranged_scoresystem(self):
167
return RangedValuesScoreSystem(
168
u'generated', min=0, max=100)
171
def default_category(self):
172
app = ISchoolToolApplication(None)
173
return ICategoryContainer(app).default_key
175
def deploy_assessment_sheets(self, section):
176
activities = IActivities(section)
177
term = ITerm(section)
178
for sheet, sheet_data in ASSESSMENT_SHEETS.items():
179
if not sheet_data['editable']:
181
title = sheet_data['title']
182
enabled = getattr(self.preferences, sheet, False)
184
if sheet in activities:
185
if activities[sheet].hidden:
186
activities[sheet].hidden = False
187
activities[sheet].deployed = False
189
activities[sheet] = Worksheet(title)
190
elif sheet in activities:
191
ws = activities[sheet]
194
if self.preferences.mock:
195
if sameProxiedObjects(term, self.get_mock_term()):
196
if MOCK_SHEET_ID not in activities:
197
title = ASSESSMENT_SHEETS[MOCK_SHEET_ID]['title']
198
activities[MOCK_SHEET_ID] = Worksheet(title)
199
elif activities[MOCK_SHEET_ID].hidden:
200
activities[MOCK_SHEET_ID].hidden = False
201
activities[MOCK_SHEET_ID].deployed = False
202
elif MOCK_SHEET_ID in activities:
203
ws = activities[MOCK_SHEET_ID]
206
if self.preferences.mock_other:
207
if sameProxiedObjects(term, self.get_mock_other_term()):
208
if MOCK_OTHER_SHEET_ID not in activities:
209
title = ASSESSMENT_SHEETS[MOCK_OTHER_SHEET_ID]['title']
210
activities[MOCK_OTHER_SHEET_ID] = Worksheet(title)
211
elif activities[MOCK_OTHER_SHEET_ID].hidden:
212
activities[MOCK_OTHER_SHEET_ID].hidden = False
213
activities[MOCK_OTHER_SHEET_ID].deployed = False
214
elif MOCK_OTHER_SHEET_ID in activities:
215
ws = activities[MOCK_OTHER_SHEET_ID]
218
if sameProxiedObjects(term, self.get_target_term()):
219
if TARGET_SHEET_ID not in activities:
220
title = ASSESSMENT_SHEETS[TARGET_SHEET_ID]['title']
221
ws = Worksheet(title)
223
activities[TARGET_SHEET_ID] = ws
224
activity_title = title + ' (%)'
226
activity_title, self.default_category,
227
self.ranged_scoresystem, None, activity_title)
228
ws[TARGET_SHEET_ID+'-ranged'] = activity
229
activity_title = title + ' (1-9)'
231
activity_title, self.default_category,
232
self.peas_scoresystem, None, activity_title)
233
ws[TARGET_SHEET_ID] = activity
234
elif activities[TARGET_SHEET_ID].hidden:
235
activities[TARGET_SHEET_ID].hidden = False
236
if UCE_SHEET_ID not in activities:
237
title = ASSESSMENT_SHEETS[UCE_SHEET_ID]['title']
238
ws = Worksheet(title)
241
activities[UCE_SHEET_ID] = ws
243
title, self.default_category,
244
self.peas_scoresystem, None, title)
245
ws[UCE_SHEET_ID] = activity
247
if TARGET_SHEET_ID in activities:
248
ws = activities[TARGET_SHEET_ID]
253
class CreateAssessmentSheetsSubscriber(ObjectEventAdapterSubscriber,
254
AssessmentSheetDeployBase):
256
adapts(IObjectAddedEvent, ISection)
259
def schoolyear(self):
260
return ISchoolYear(self.object)
263
self.deploy_assessment_sheets(self.object)
266
PEASScoreSystem = GlobalDiscreteValuesScoreSystem(
268
_('PEAS Score System'), None,
270
('1', u'D1', Decimal(1), Decimal(80)),
271
('2', u'D2', Decimal(2), Decimal(75)),
272
('3', u'C3', Decimal(3), Decimal(70)),
273
('4', u'C4', Decimal(4), Decimal(65)),
274
('5', u'C5', Decimal(5), Decimal(60)),
275
('6', u'C6', Decimal(6), Decimal(50)),
276
('7', u'P7', Decimal(7), Decimal(45)),
277
('8', u'P8', Decimal(8), Decimal(40)),
278
('9', u'F9', Decimal(9), Decimal(0)),
283
class PeasScoreSystemAppStartup(StartUpBase):
285
after = ('schooltool.requirement.scoresystem',)
288
container = IScoreSystemContainer(self.app)
289
if PEAS_SCORESYSTEM_KEY not in container:
291
peas_scoresystem = CustomScoreSystem(ss.title, ss.description,
292
ss.scores, ss._bestScore,
294
ss._isMaxPassingScore)
295
container[PEAS_SCORESYSTEM_KEY] = peas_scoresystem
298
HUMANITIES = ['208', '223', '225', '241', '273']
299
SCIENCE = ['535', '545', '553']
301
MATHEMATICS = ['456']
302
CREDIT_LEVEL_MIN_SCORE = 6
305
def find_division(items):
306
ss = get_peas_scoresystem()
309
for code, grade in items:
310
if ss.isPassingScore(grade):
311
decimal_value = ss.scoresDict[grade]
312
passed[code] = decimal_value
313
if ss.scoresDict.get(grade) <= CREDIT_LEVEL_MIN_SCORE:
314
credit[code] = decimal_value
315
if is_division_1(passed, credit):
317
elif is_division_2(passed, credit):
319
elif is_division_3(passed, credit):
321
elif is_division_4(passed, credit):
323
elif is_division_9(passed, credit):
331
return sum(sorted(passed.values())[:8])
334
# XXX: use set intersection
335
def is_division_1(passed, credit):
337
Pass a minimum of eight subjects
338
which must include English Language (with credit),
342
At least seven of the subjects must be a credit level or better.
343
The aggregate for the best eight done subjects must not exceed 32
347
[code for code in credit.keys() if code in ENGLISH] and
348
[code for code in passed.keys() if code in HUMANITIES] and
349
[code for code in passed.keys() if code in MATHEMATICS] and
350
[code for code in passed.keys() if code in SCIENCE] and
356
def is_division_2(passed, credit):
358
Pass a minimum of eight subjects
359
including English Language.
360
Six of the subjects must be at a credit level or better.
361
The aggregate for the best eight done subjects must not exceed 45.
365
[code for code in passed.keys() if code in ENGLISH] and
371
def is_division_3(passed, credit):
374
(i) Pass a minimum of eight subjects (with at least 3 credits or better)
375
OR (i) Pass a minimum of seven subjects (with at least 4 credits or better)
376
OR (ii) Pass a minimum of five subjects with credits or better.
377
The aggregate for the best done subjects must not exceed 58.
381
(len(passed) >= 8 and len(credit) >= 3) or
382
(len(passed) >= 7 and len(credit) >= 4) or
383
(len(passed) >= 5 and credit)
389
def is_division_4(passed, credit):
392
(i) Pass at least one subject with credit or better
393
OR (ii) Pass at least two subjects with pass 7
394
OR (iii) Pass at least three subjects with pass 8 or better
396
pass7 = [v for v in passed.values() if v == 7]
397
pass8 = [v for v in passed.values() if v == 8]
408
def is_division_9(passed, credit):
409
return best8(passed) <= 72
412
class IUCEResult(Interface):
415
title=_(u'Aggregate'),
419
title=_(u'Division'),
423
class UCEResult(Persistent, Contained):
429
def getUCEResult(person):
430
annotations = IAnnotations(person)
433
return annotations[name]
435
annotations[name] = UCEResult()
436
annotations[name].__name__ = name
437
annotations[name].__parent__ = person
438
return annotations[name]