~ubuntu-branches/ubuntu/raring/schooltool.intervention/raring

« back to all changes in this revision

Viewing changes to src/schooltool/intervention/intervention.py

  • Committer: Bazaar Package Importer
  • Author(s): Gediminas Paulauskas
  • Date: 2011-02-24 17:10:33 UTC
  • Revision ID: james.westby@ubuntu.com-20110224171033-8wflfqxxe3zld6bf
Tags: upstream-0.4.2
ImportĀ upstreamĀ versionĀ 0.4.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# SchoolTool - common information systems platform for school administration
 
3
# Copyright (c) 2005 Shuttleworth Foundation
 
4
#
 
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.
 
9
#
 
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.
 
14
#
 
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
 
18
#
 
19
 
 
20
import pytz
 
21
from persistent import Persistent
 
22
from datetime import datetime
 
23
import rwproperty
 
24
 
 
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
 
40
 
 
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
 
56
 
 
57
import interfaces
 
58
 
 
59
 
 
60
def getRequest():
 
61
    """Return the request object for the current request.
 
62
       In the case of unit testing, return a TestRequest instance."""
 
63
 
 
64
    i = management.getInteraction()
 
65
    for p in i.participations:
 
66
        if IRequest.providedBy(p):
 
67
            return p
 
68
    return TestRequest()
 
69
 
 
70
 
 
71
def convertIdToPerson(id):
 
72
    return ISchoolToolApplication(None)['persons'].get(id)
 
73
 
 
74
 
 
75
def convertIdToNameWithSort(id):
 
76
    if id[-2] == ':':
 
77
        student = convertIdToPerson(id[:-2])
 
78
        if student is None:
 
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
 
84
                break
 
85
        else:
 
86
            return ('', ''), _('Unknown Contact')
 
87
    else:
 
88
        person = convertIdToPerson(id)
 
89
        if person is None:
 
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)
 
95
    return sort, name
 
96
 
 
97
 
 
98
def convertIdToName(id):
 
99
    sort, name = convertIdToNameWithSort(id)
 
100
    return name
 
101
 
 
102
 
 
103
def convertIdsToNames(ids):
 
104
    return [name for sort, name in
 
105
        sorted([convertIdToNameWithSort(id) for id in ids])]
 
106
 
 
107
 
 
108
def convertIdToEmail(id):
 
109
    if id[-2] == ':':
 
110
        student = convertIdToPerson(id[:-2])
 
111
        if not student:
 
112
            return u''
 
113
        for index, contact in enumerate(IContactable(student).contacts):
 
114
            if id[-1] == str(index + 1):
 
115
                return contact.email or u''
 
116
        else:
 
117
            return u''
 
118
    person = convertIdToPerson(id)
 
119
    if person is None:
 
120
        return u''
 
121
    return IContact(person).email or u''
 
122
 
 
123
 
 
124
def convertIdsToEmail(ids):
 
125
    emails = [convertIdToEmail(id) for id in ids]
 
126
    return sorted([email for email in emails if email])
 
127
 
 
128
 
 
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."""
 
137
 
 
138
    responsible = [removeSecurityProxy(advisor).username
 
139
                       for advisor in student.advisors]
 
140
 
 
141
    term = queryUtility(IDateManager).current_term
 
142
    groups = IGroupContainer(ISchoolYear(term))
 
143
    sections = ISectionContainer(term)
 
144
 
 
145
    for member in groups['administrators'].members:
 
146
        if member.username not in responsible:
 
147
            responsible.append(member.username)
 
148
 
 
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)
 
154
 
 
155
    responsible.append(student.username)
 
156
    for index, contact in enumerate(IContactable(student).contacts):
 
157
        responsible.append(student.username + ':%d' % (index + 1))
 
158
 
 
159
    return responsible
 
160
 
 
161
 
 
162
class InterventionInstructorsCrowd(crowds.Crowd):
 
163
    """Crowd of instructors of the student indicated by the given 
 
164
       intervention object."""
 
165
 
 
166
    title = _(u'Instructors')
 
167
    description = _(u'Instructors of a student in any of his sections.')
 
168
 
 
169
    def _getSections(self, ob):
 
170
        return [section for section in getRelatedObjects(ob, URIGroup)
 
171
                if ISection.providedBy(section)]
 
172
 
 
173
    def contains(self, principal):
 
174
        if IUnauthenticatedPrincipal.providedBy(principal):
 
175
            return False
 
176
        teacher = IPerson(principal)
 
177
        student = removeSecurityProxy(self.context.student)
 
178
        for section in self._getSections(student):
 
179
            if teacher in section.instructors:
 
180
                return True
 
181
        return False
 
182
 
 
183
 
 
184
class InterventionAdvisorsCrowd(crowds.Crowd):
 
185
    """Crowd of advisors of the student indicated by the given 
 
186
       intervention object."""
 
187
 
 
188
    title = _(u'Advisors')
 
189
    description = _(u'Advisors of a student.')
 
190
 
 
191
    def contains(self, principal):
 
192
        if IUnauthenticatedPrincipal.providedBy(principal):
 
193
            return False
 
194
        teacher = IPerson(principal)
 
195
        student = removeSecurityProxy(self.context.student)
 
196
        return teacher in student.advisors
 
197
 
 
198
 
 
199
class BaseResponsibleCrowd(crowds.Crowd):
 
200
    """Crowd of any user who is on the list of persons responsible for the
 
201
       message or goal."""
 
202
 
 
203
    def contains(self, principal):
 
204
        if IUnauthenticatedPrincipal.providedBy(principal):
 
205
            return False
 
206
        if not interfaces.IInterventionMarker.providedBy(self.context):
 
207
            return False
 
208
        responsible = interfaces.IInterventionPersonsResponsible(self.context)
 
209
        return IPerson(principal).username in responsible
 
210
 
 
211
 
 
212
class StaffResponsibleCrowd(BaseResponsibleCrowd):
 
213
    """Crowd of school staff who are on the list of persons responsible for the
 
214
       message or goal."""
 
215
 
 
216
    title = _(u'Staff responsible')
 
217
    description = _(u'Staff members responsible for the message or goal.')
 
218
 
 
219
    def contains(self, principal):
 
220
        if not super(StaffResponsibleCrowd, self).contains(principal):
 
221
            return False
 
222
        student = removeSecurityProxy(self.context.student)
 
223
        return IPerson(principal).username != student.username
 
224
 
 
225
 
 
226
class StudentResponsibleCrowd(BaseResponsibleCrowd):
 
227
    """Crowd containing the student who is in the list of persons responsible
 
228
       for the message or goal."""
 
229
 
 
230
    title = _(u'Students responsible')
 
231
    description = _(u'Students responsible for the message or goal.')
 
232
 
 
233
    def contains(self, principal):
 
234
        if not super(StudentResponsibleCrowd, self).contains(principal):
 
235
            return False
 
236
        student = removeSecurityProxy(self.context.student)
 
237
        return IPerson(principal).username == student.username
 
238
 
 
239
 
 
240
class InterventionRoot(BTreeContainer):
 
241
    """Container of InterventionSchoolYear objects."""
 
242
 
 
243
    implements(interfaces.IInterventionRoot)
 
244
 
 
245
 
 
246
class InterventionSchoolYear(BTreeContainer):
 
247
    """Container of InteventionStudent objects."""
 
248
 
 
249
    implements(interfaces.IInterventionSchoolYear)
 
250
 
 
251
 
 
252
class InterventionStudent(BTreeContainer):
 
253
    """Container of the student's intervention containers."""
 
254
 
 
255
    implements(interfaces.IInterventionStudent)
 
256
 
 
257
    def __init__(self):
 
258
        super(InterventionStudent, self).__init__()
 
259
 
 
260
    @property
 
261
    def student(self):
 
262
        app = ISchoolToolApplication(None)
 
263
        if self.__name__ in app['persons']:
 
264
            return removeSecurityProxy(app['persons'][self.__name__])
 
265
        else:
 
266
            return None
 
267
 
 
268
 
 
269
class InterventionMessages(BTreeContainer):
 
270
    """Container of Tier1 InteventionMessage objects."""
 
271
 
 
272
    implements(interfaces.IInterventionMessages)
 
273
 
 
274
    @property
 
275
    def student(self):
 
276
        try:
 
277
            return removeSecurityProxy(self.__parent__).student
 
278
        except:
 
279
            return None
 
280
 
 
281
 
 
282
class InterventionMessage(Persistent, Contained):
 
283
    """Intervention message about a given student."""
 
284
 
 
285
    implements(interfaces.IInterventionMessage, IAttributeAnnotatable)
 
286
 
 
287
    created = None
 
288
 
 
289
    def __init__(self, sender, recipients, body, status_change=False):
 
290
        self.sender = sender
 
291
        self.recipients = recipients
 
292
        self.body = body
 
293
        self.status_change = status_change
 
294
        self.created = pytz.UTC.localize(datetime.utcnow())
 
295
 
 
296
    @property
 
297
    def student(self):
 
298
        try:
 
299
            student = removeSecurityProxy(self.__parent__).student
 
300
            return removeSecurityProxy(student)
 
301
        except:
 
302
            return None
 
303
 
 
304
 
 
305
class InterventionGoals(BTreeContainer):
 
306
    """Container of InterventionGoal objects."""
 
307
 
 
308
    implements(interfaces.IInterventionGoals)
 
309
 
 
310
    @property
 
311
    def student(self):
 
312
        try:
 
313
            return removeSecurityProxy(self.__parent__).student
 
314
        except:
 
315
            return None
 
316
 
 
317
 
 
318
class InterventionGoal(Persistent, Contained):
 
319
    """Intervention goal for a given student."""
 
320
    implements(interfaces.IInterventionGoal, IAttributeAnnotatable)
 
321
 
 
322
    creator = None
 
323
    created = None
 
324
    at_one_time_responsible = []
 
325
    _persons_responsible = []
 
326
 
 
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
 
331
        self.goal = goal
 
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())
 
342
 
 
343
    @property
 
344
    def student(self):
 
345
        try:
 
346
            student = removeSecurityProxy(self.__parent__).student
 
347
            return removeSecurityProxy(student)
 
348
        except:
 
349
            return None
 
350
 
 
351
    @rwproperty.getproperty
 
352
    def persons_responsible(self):
 
353
        return self._persons_responsible
 
354
 
 
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
 
360
 
 
361
 
 
362
def setUpInterventions(app):
 
363
    app[u'schooltool.interventions'] = InterventionRoot()
 
364
 
 
365
 
 
366
class InterventionStartup(StartUpBase):
 
367
    def __call__(self):
 
368
        if u'schooltool.interventions' not in self.app:
 
369
            setUpInterventions(self.app)
 
370
 
 
371
 
 
372
class InterventionInit(InitBase):
 
373
    """Create the InterventionRoot object."""
 
374
 
 
375
    def __call__(self):
 
376
        setUpInterventions(self.app)
 
377
 
 
378
 
 
379
@adapter(ISchoolToolApplication)
 
380
@implementer(interfaces.IInterventionRoot)
 
381
def getInterventionRoot(app):
 
382
    return app[u'schooltool.interventions']
 
383
 
 
384
 
 
385
@adapter(ISchoolYear)
 
386
@implementer(interfaces.IInterventionSchoolYear)
 
387
def getSchoolYearInterventionSchoolYear(schoolyear):
 
388
    return getInterventionSchoolYear(schoolyear.__name__)
 
389
 
 
390
 
 
391
@adapter(ISchoolToolApplication)
 
392
@implementer(interfaces.IInterventionSchoolYear)
 
393
def getSchoolToolApplicationInterventionSchoolYear(app):
 
394
    return getInterventionSchoolYear()
 
395
 
 
396
 
 
397
@adapter(interfaces.IInterventionSchoolYear)
 
398
@implementer(ISchoolYear)
 
399
def getInterventionSchoolYearSchoolYear(schoolyear):
 
400
    app = ISchoolToolApplication(None)
 
401
    return ISchoolYearContainer(app)[schoolyear.__name__]
 
402
 
 
403
 
 
404
@adapter(interfaces.IInterventionMarker)
 
405
@implementer(interfaces.IInterventionStudent)
 
406
def getMarkerInterventionStudent(marker):
 
407
    obj = marker.__parent__.__parent__
 
408
    if interfaces.IInterventionStudent.providedBy(obj):
 
409
        return obj
 
410
    student = obj.__parent__.student
 
411
    interventionStudent = removeSecurityProxy(obj.year[student.username])
 
412
    return interventionStudent
 
413
 
 
414
 
 
415
def getInterventionSchoolYearFromObj(obj):
 
416
    while not interfaces.IInterventionSchoolYear.providedBy(obj):
 
417
        if interfaces.IStudentSchoolYearProxy.providedBy(obj):
 
418
            return obj.year
 
419
        obj = getParent(obj)
 
420
    return obj
 
421
 
 
422
 
 
423
def getInterventionSchoolYear(schoolYearId=None):
 
424
    """Get InterventionSchoolYear object for the given school year.
 
425
       If school year not specified, use current school year."""
 
426
 
 
427
    app = ISchoolToolApplication(None)
 
428
    interventionRoot = app[u'schooltool.interventions']
 
429
    if not schoolYearId:
 
430
        term = queryUtility(IDateManager).current_term
 
431
        schoolyear = ISchoolYear(term)
 
432
        schoolYearId = schoolyear.__name__
 
433
    try:
 
434
        interventionSchoolYear = interventionRoot[schoolYearId]
 
435
    except KeyError:
 
436
        interventionSchoolYear = InterventionSchoolYear()
 
437
        interventionRoot[schoolYearId] = interventionSchoolYear
 
438
    return interventionSchoolYear
 
439
 
 
440
 
 
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."""
 
446
 
 
447
    interventionSchoolYear = getInterventionSchoolYear(schoolYearId)
 
448
    try:
 
449
        interventionStudent = interventionSchoolYear[studentId]
 
450
    except KeyError:
 
451
        interventionStudent = InterventionStudent()
 
452
        interventionSchoolYear[studentId] = interventionStudent
 
453
        interventionStudent[u'messages'] = InterventionMessages()
 
454
        interventionStudent[u'goals'] = InterventionGoals()
 
455
    return interventionStudent
 
456
 
 
457
 
 
458
class SequenceNumberNameChooser(NameChooser):
 
459
    """A name chooser that returns a sequence number."""
 
460
 
 
461
    implements(INameChooser)
 
462
 
 
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()]
 
467
        if numbers:
 
468
            n = str(max(numbers) + 1)
 
469
        else:
 
470
            n = '1'
 
471
        self.checkName(n, obj)
 
472
        return n
 
473
 
 
474
 
 
475
class PersonRemovedSubsciber(ObjectEventAdapterSubscriber):
 
476
    adapts(IObjectRemovedEvent, IBasicPerson)
 
477
 
 
478
    def __call__(self):
 
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]
 
484
 
 
485
 
 
486
class SchoolYearRemovedSubsciber(ObjectEventAdapterSubscriber):
 
487
    adapts(IObjectRemovedEvent, ISchoolYear)
 
488
 
 
489
    def __call__(self):
 
490
        app = ISchoolToolApplication(None)
 
491
        interventionRoot = app[u'schooltool.interventions']
 
492
        if self.object.__name__ in interventionRoot:
 
493
            del interventionRoot[self.object.__name__]
 
494