2
# SchoolTool - common information systems platform for school administration
3
# Copyright (c) 2005 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, write to the Free Software
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
from persistent import Persistent
22
from datetime import datetime
25
from zope.annotation.interfaces import IAttributeAnnotatable
26
from zope.authentication.interfaces import IUnauthenticatedPrincipal
27
from zope.container.contained import Contained, NameChooser
28
from zope.container.interfaces import INameChooser
29
from zope.container.btree import BTreeContainer
30
from zope.component import queryUtility
31
from zope.component import adapter, adapts
32
from zope.interface import implementer
33
from zope.interface import implements
34
from zope.lifecycleevent.interfaces import IObjectRemovedEvent
35
from zope.publisher.interfaces import IRequest
36
from zope.publisher.browser import TestRequest
37
from zope.security import management
38
from zope.security.proxy import removeSecurityProxy
39
from zope.traversing.api import getParent
41
from schooltool.app.app import InitBase, StartUpBase
42
from schooltool.app.interfaces import ISchoolToolApplication
43
from schooltool.app.membership import URIGroup
44
from schooltool.basicperson.interfaces import IBasicPerson
45
from schooltool.contact.interfaces import IContactable, IContact
46
from schooltool.course.interfaces import ISectionContainer, ISection
47
from schooltool.group.interfaces import IGroupContainer
48
from schooltool.intervention import InterventionGettext as _
49
from schooltool.person.interfaces import IPerson
50
from schooltool.relationship.relationship import getRelatedObjects
51
from schooltool.schoolyear.interfaces import ISchoolYear
52
from schooltool.schoolyear.interfaces import ISchoolYearContainer
53
from schooltool.schoolyear.subscriber import ObjectEventAdapterSubscriber
54
from schooltool.securitypolicy import crowds
55
from schooltool.term.interfaces import IDateManager
61
"""Return the request object for the current request.
62
In the case of unit testing, return a TestRequest instance."""
64
i = management.getInteraction()
65
for p in i.participations:
66
if IRequest.providedBy(p):
71
def convertIdToPerson(id):
72
return ISchoolToolApplication(None)['persons'].get(id)
75
def convertIdToNameWithSort(id):
77
student = convertIdToPerson(id[:-2])
79
('', ''), _('Unknown Contact')
80
for index, contact in enumerate(IContactable(student).contacts):
81
if id[-1] == str(index + 1):
82
first_name = contact.first_name
83
last_name = contact.last_name
86
return ('', ''), _('Unknown Contact')
88
person = convertIdToPerson(id)
90
return ('', ''), _('Unknown Person')
91
first_name = person.first_name
92
last_name = person.last_name
93
name = '%s %s' % (first_name, last_name)
94
sort = (last_name, first_name)
98
def convertIdToName(id):
99
sort, name = convertIdToNameWithSort(id)
103
def convertIdsToNames(ids):
104
return [name for sort, name in
105
sorted([convertIdToNameWithSort(id) for id in ids])]
108
def convertIdToEmail(id):
110
student = convertIdToPerson(id[:-2])
113
for index, contact in enumerate(IContactable(student).contacts):
114
if id[-1] == str(index + 1):
115
return contact.email or u''
118
person = convertIdToPerson(id)
121
return IContact(person).email or u''
124
def convertIdsToEmail(ids):
125
emails = [convertIdToEmail(id) for id in ids]
126
return sorted([email for email in emails if email])
129
def getPersonsResponsible(student):
130
"""Return the list of persons responsible for the student.
131
This includes all teachers that teach the student, the student's
132
advisors, and all members of the school administation.
133
Additionally, we will include the student and the student's
134
parents (designated by student id + :1 or :2) in this list
135
for the purpose of sending emails, but they will be filtered
136
out of any application elements that are for staff only."""
138
responsible = [removeSecurityProxy(advisor).username
139
for advisor in student.advisors]
141
term = queryUtility(IDateManager).current_term
142
groups = IGroupContainer(ISchoolYear(term))
143
sections = ISectionContainer(term)
145
for member in groups['administrators'].members:
146
if member.username not in responsible:
147
responsible.append(member.username)
149
for section in sections.values():
150
if student in section.members:
151
for teacher in section.instructors:
152
if teacher.username not in responsible:
153
responsible.append(teacher.username)
155
responsible.append(student.username)
156
for index, contact in enumerate(IContactable(student).contacts):
157
responsible.append(student.username + ':%d' % (index + 1))
162
class InterventionInstructorsCrowd(crowds.Crowd):
163
"""Crowd of instructors of the student indicated by the given
164
intervention object."""
166
title = _(u'Instructors')
167
description = _(u'Instructors of a student in any of his sections.')
169
def _getSections(self, ob):
170
return [section for section in getRelatedObjects(ob, URIGroup)
171
if ISection.providedBy(section)]
173
def contains(self, principal):
174
if IUnauthenticatedPrincipal.providedBy(principal):
176
teacher = IPerson(principal)
177
student = removeSecurityProxy(self.context.student)
178
for section in self._getSections(student):
179
if teacher in section.instructors:
184
class InterventionAdvisorsCrowd(crowds.Crowd):
185
"""Crowd of advisors of the student indicated by the given
186
intervention object."""
188
title = _(u'Advisors')
189
description = _(u'Advisors of a student.')
191
def contains(self, principal):
192
if IUnauthenticatedPrincipal.providedBy(principal):
194
teacher = IPerson(principal)
195
student = removeSecurityProxy(self.context.student)
196
return teacher in student.advisors
199
class BaseResponsibleCrowd(crowds.Crowd):
200
"""Crowd of any user who is on the list of persons responsible for the
203
def contains(self, principal):
204
if IUnauthenticatedPrincipal.providedBy(principal):
206
if not interfaces.IInterventionMarker.providedBy(self.context):
208
responsible = interfaces.IInterventionPersonsResponsible(self.context)
209
return IPerson(principal).username in responsible
212
class StaffResponsibleCrowd(BaseResponsibleCrowd):
213
"""Crowd of school staff who are on the list of persons responsible for the
216
title = _(u'Staff responsible')
217
description = _(u'Staff members responsible for the message or goal.')
219
def contains(self, principal):
220
if not super(StaffResponsibleCrowd, self).contains(principal):
222
student = removeSecurityProxy(self.context.student)
223
return IPerson(principal).username != student.username
226
class StudentResponsibleCrowd(BaseResponsibleCrowd):
227
"""Crowd containing the student who is in the list of persons responsible
228
for the message or goal."""
230
title = _(u'Students responsible')
231
description = _(u'Students responsible for the message or goal.')
233
def contains(self, principal):
234
if not super(StudentResponsibleCrowd, self).contains(principal):
236
student = removeSecurityProxy(self.context.student)
237
return IPerson(principal).username == student.username
240
class InterventionRoot(BTreeContainer):
241
"""Container of InterventionSchoolYear objects."""
243
implements(interfaces.IInterventionRoot)
246
class InterventionSchoolYear(BTreeContainer):
247
"""Container of InteventionStudent objects."""
249
implements(interfaces.IInterventionSchoolYear)
252
class InterventionStudent(BTreeContainer):
253
"""Container of the student's intervention containers."""
255
implements(interfaces.IInterventionStudent)
258
super(InterventionStudent, self).__init__()
262
app = ISchoolToolApplication(None)
263
if self.__name__ in app['persons']:
264
return removeSecurityProxy(app['persons'][self.__name__])
269
class InterventionMessages(BTreeContainer):
270
"""Container of Tier1 InteventionMessage objects."""
272
implements(interfaces.IInterventionMessages)
277
return removeSecurityProxy(self.__parent__).student
282
class InterventionMessage(Persistent, Contained):
283
"""Intervention message about a given student."""
285
implements(interfaces.IInterventionMessage, IAttributeAnnotatable)
289
def __init__(self, sender, recipients, body, status_change=False):
291
self.recipients = recipients
293
self.status_change = status_change
294
self.created = pytz.UTC.localize(datetime.utcnow())
299
student = removeSecurityProxy(self.__parent__).student
300
return removeSecurityProxy(student)
305
class InterventionGoals(BTreeContainer):
306
"""Container of InterventionGoal objects."""
308
implements(interfaces.IInterventionGoals)
313
return removeSecurityProxy(self.__parent__).student
318
class InterventionGoal(Persistent, Contained):
319
"""Intervention goal for a given student."""
320
implements(interfaces.IInterventionGoal, IAttributeAnnotatable)
324
at_one_time_responsible = []
325
_persons_responsible = []
327
def __init__(self, presenting_concerns, goal, strengths, indicators,
328
intervention, timeline, persons_responsible, goal_met=False,
329
follow_up_notes=u'', creator=None):
330
self.presenting_concerns = presenting_concerns
332
self.strengths = strengths
333
self.indicators = indicators
334
self.intervention = intervention
335
self.timeline = timeline
336
self.persons_responsible = persons_responsible
337
self.goal_met = goal_met
338
self.follow_up_notes = follow_up_notes
339
self.notified = False
340
self.creator = creator
341
self.created = pytz.UTC.localize(datetime.utcnow())
346
student = removeSecurityProxy(self.__parent__).student
347
return removeSecurityProxy(student)
351
@rwproperty.getproperty
352
def persons_responsible(self):
353
return self._persons_responsible
355
@rwproperty.setproperty
356
def persons_responsible(self, value):
357
self._persons_responsible = value
358
new = list(set(self.at_one_time_responsible).union(value))
359
self.at_one_time_responsible = new
362
def setUpInterventions(app):
363
app[u'schooltool.interventions'] = InterventionRoot()
366
class InterventionStartup(StartUpBase):
368
if u'schooltool.interventions' not in self.app:
369
setUpInterventions(self.app)
372
class InterventionInit(InitBase):
373
"""Create the InterventionRoot object."""
376
setUpInterventions(self.app)
379
@adapter(ISchoolToolApplication)
380
@implementer(interfaces.IInterventionRoot)
381
def getInterventionRoot(app):
382
return app[u'schooltool.interventions']
385
@adapter(ISchoolYear)
386
@implementer(interfaces.IInterventionSchoolYear)
387
def getSchoolYearInterventionSchoolYear(schoolyear):
388
return getInterventionSchoolYear(schoolyear.__name__)
391
@adapter(ISchoolToolApplication)
392
@implementer(interfaces.IInterventionSchoolYear)
393
def getSchoolToolApplicationInterventionSchoolYear(app):
394
return getInterventionSchoolYear()
397
@adapter(interfaces.IInterventionSchoolYear)
398
@implementer(ISchoolYear)
399
def getInterventionSchoolYearSchoolYear(schoolyear):
400
app = ISchoolToolApplication(None)
401
return ISchoolYearContainer(app)[schoolyear.__name__]
404
@adapter(interfaces.IInterventionMarker)
405
@implementer(interfaces.IInterventionStudent)
406
def getMarkerInterventionStudent(marker):
407
obj = marker.__parent__.__parent__
408
if interfaces.IInterventionStudent.providedBy(obj):
410
student = obj.__parent__.student
411
interventionStudent = removeSecurityProxy(obj.year[student.username])
412
return interventionStudent
415
def getInterventionSchoolYearFromObj(obj):
416
while not interfaces.IInterventionSchoolYear.providedBy(obj):
417
if interfaces.IStudentSchoolYearProxy.providedBy(obj):
423
def getInterventionSchoolYear(schoolYearId=None):
424
"""Get InterventionSchoolYear object for the given school year.
425
If school year not specified, use current school year."""
427
app = ISchoolToolApplication(None)
428
interventionRoot = app[u'schooltool.interventions']
430
term = queryUtility(IDateManager).current_term
431
schoolyear = ISchoolYear(term)
432
schoolYearId = schoolyear.__name__
434
interventionSchoolYear = interventionRoot[schoolYearId]
436
interventionSchoolYear = InterventionSchoolYear()
437
interventionRoot[schoolYearId] = interventionSchoolYear
438
return interventionSchoolYear
441
def getInterventionStudent(studentId, schoolYearId=None):
442
"""Get InterventionStudent object for the given student, school year pair.
443
If school year not specified, use current school year.
444
Create InterventionSchoolYear and InterventionStudent objects if
445
not already present."""
447
interventionSchoolYear = getInterventionSchoolYear(schoolYearId)
449
interventionStudent = interventionSchoolYear[studentId]
451
interventionStudent = InterventionStudent()
452
interventionSchoolYear[studentId] = interventionStudent
453
interventionStudent[u'messages'] = InterventionMessages()
454
interventionStudent[u'goals'] = InterventionGoals()
455
return interventionStudent
458
class SequenceNumberNameChooser(NameChooser):
459
"""A name chooser that returns a sequence number."""
461
implements(INameChooser)
463
def chooseName(self, name, obj):
464
"""See INameChooser."""
465
numbers = [int(v.__name__) for v in self.context.values()
466
if v.__name__.isdigit()]
468
n = str(max(numbers) + 1)
471
self.checkName(n, obj)
475
class PersonRemovedSubsciber(ObjectEventAdapterSubscriber):
476
adapts(IObjectRemovedEvent, IBasicPerson)
479
app = ISchoolToolApplication(None)
480
interventionRoot = app[u'schooltool.interventions']
481
for schoolYear in interventionRoot.values():
482
if self.object.username in schoolYear:
483
del schoolYear[self.object.username]
486
class SchoolYearRemovedSubsciber(ObjectEventAdapterSubscriber):
487
adapts(IObjectRemovedEvent, ISchoolYear)
490
app = ISchoolToolApplication(None)
491
interventionRoot = app[u'schooltool.interventions']
492
if self.object.__name__ in interventionRoot:
493
del interventionRoot[self.object.__name__]