5
This package supplies the school with a number of objects for tracking
6
interventions with students who are having either acedemic or behavioral
7
problems. Objects include messages passed between concerned faculty members
8
and goals that are established for the student.
10
Let's import some zope stuff before we use it.
12
>>> from zope.component import provideHandler, provideUtility
13
>>> from zope.component import provideAdapter
14
>>> from zope.component import Interface
15
>>> from zope.lifecycleevent.interfaces import (IObjectAddedEvent,
16
... IObjectRemovedEvent)
17
>>> from zope.interface.verify import verifyObject
19
Let's set up the app object.
21
>>> from schooltool.app.interfaces import ISchoolToolApplication
22
>>> from schooltool.testing import setup as stsetup
23
>>> app = stsetup.setUpSchoolToolSite()
25
We'll need a schoolyear and current term.
27
>>> from schooltool.schoolyear.schoolyear import getSchoolYearContainer
28
>>> from schooltool.term.interfaces import ITermContainer
29
>>> from schooltool.term.term import getTermContainer
30
>>> from schooltool.term.term import getSchoolYearForTerm
31
>>> provideAdapter(getTermContainer, [Interface], ITermContainer)
32
>>> provideAdapter(getSchoolYearForTerm)
33
>>> provideAdapter(getSchoolYearContainer)
35
>>> from datetime import date
36
>>> from schooltool.schoolyear.interfaces import ISchoolYearContainer
37
>>> from schooltool.schoolyear.schoolyear import SchoolYear
38
>>> schoolyear = SchoolYear("Sample", date(2004, 9, 1), date(2005, 12, 20))
39
>>> ISchoolYearContainer(app)['2004-2005'] = schoolyear
41
>>> from schooltool.term.term import Term
42
>>> term = Term('Sample', date(2004, 9, 1), date(2004, 12, 20))
43
>>> terms = ITermContainer(app)
44
>>> terms['2004-fall'] = term
46
>>> from schooltool.term.tests import setUpDateManagerStub
47
>>> setUpDateManagerStub(date(2004, 9, 1), term)
49
We will need an adapter to get the schooltool application.
51
>>> from zope.component import provideAdapter
52
>>> provideAdapter(lambda context: app,
54
... provides=ISchoolToolApplication)
56
Interventions also depend on user contact information to get the email address.
58
>>> from schooltool.relationship.interfaces import IRelationshipLinks
59
>>> from schooltool.relationship.annotatable import getRelationshipLinks
60
>>> from zope.annotation.interfaces import IAnnotatable
61
>>> provideAdapter(getRelationshipLinks, (IAnnotatable,), IRelationshipLinks)
63
>>> from schooltool.basicperson.interfaces import IBasicPerson
64
>>> from schooltool.contact.contact import Contactable
65
>>> provideAdapter(factory=Contactable, adapts=[IBasicPerson])
67
>>> from schooltool.contact.basicperson import getBoundContact
68
>>> provideAdapter(factory=getBoundContact, adapts=[None])
73
Now we get to the heart of the matter, the interventions themselves. We start
74
with the root container of all of the interventions. The test aparatus will
75
have set this up for us like the application init handler would do.
77
>>> from schooltool.intervention import intervention, interfaces
78
>>> interventionRoot = app['schooltool.interventions']
79
>>> verifyObject(interfaces.IInterventionRoot, interventionRoot)
81
>>> len(interventionRoot)
84
The intervention root container contains InterventionSchoolYear objects,
85
keyed by the school year's id, which in turn contain InterventionStudent
86
objects for each student that is under intervention during that school year.
87
We have a handy function that will return to us the InterventionStudent
88
object for a given student, school year pair. If the InterventionSchoolYear
89
object is missing, it will create it. If the InterventionStudent object is
90
missing, it will create that as well.
92
Let's create a student and not specify the school year. We will then test this
93
function does what we expect. In addition to creating the student's
94
intervention container, it will place the messages and goals containers in it.
96
>>> from schooltool.contact.interfaces import IContact, IContactable
97
>>> from schooltool.contact.contact import Contact
98
>>> def addEmail(person):
99
... IContact(person).email = '%s@example.com' % person.__name__
101
>>> from schooltool.basicperson.person import BasicPerson
102
>>> jdoe = BasicPerson('jdoe', 'John', 'Doe')
103
>>> app['persons']['jdoe'] = jdoe
106
>>> parent1 = Contact()
107
>>> parent1.email = 'parent1@provider.com'
108
>>> IContactable(jdoe).contacts.add(parent1)
110
>>> jdoeIntervention = intervention.getInterventionStudent(jdoe.__name__)
111
>>> verifyObject(interfaces.IInterventionStudent, jdoeIntervention)
113
>>> jdoeIntervention.student is jdoe
115
>>> [key for key in interventionRoot]
117
>>> interventionSchoolYear = intervention.getInterventionSchoolYear()
118
>>> verifyObject(interfaces.IInterventionSchoolYear, interventionSchoolYear)
120
>>> [key for key in interventionSchoolYear]
122
>>> [key for key in jdoeIntervention]
123
[u'goals', u'messages']
124
>>> jdoeMessages = jdoeIntervention['messages']
125
>>> verifyObject(interfaces.IInterventionMessages, jdoeMessages)
127
>>> jdoeMessages.student is jdoe
129
>>> len(jdoeMessages)
131
>>> jdoeGoals = jdoeIntervention['goals']
132
>>> verifyObject(interfaces.IInterventionGoals, jdoeGoals)
134
>>> jdoeGoals.student is jdoe
139
Intervention Messagess
140
----------------------
142
Now we will create some InterventionMessage objects for the student and put
143
them in the student's InterventionMessages container.
145
First, we'll need to register the object added handler that sends the message
146
to the recipients as an email as well as the dummy mail sender that we need
149
>>> from schooltool.email.interfaces import IEmailUtility
150
>>> from schooltool.intervention import sendmail
151
>>> provideHandler(sendmail.sendInterventionMessageEmail,
152
... adapts=(interfaces.IInterventionMessage, IObjectAddedEvent))
153
>>> provideUtility(sendmail.TestMailDelivery(), provides=IEmailUtility)
155
>>> manager_user = BasicPerson('manager', 'SchoolTool', 'Manager')
156
>>> app['persons']['manager'] = manager_user
157
>>> addEmail(manager_user)
159
We will create person objects for some teachers and advisors.
161
>>> teacher1 = BasicPerson('teacher1', '1', 'Teacher')
162
>>> app['persons']['teacher1'] = teacher1
163
>>> addEmail(teacher1)
164
>>> teacher2 = BasicPerson('teacher2', '2', 'Teacher')
165
>>> app['persons']['teacher2'] = teacher2
166
>>> addEmail(teacher2)
167
>>> advisor1 = BasicPerson('advisor1', '1', 'Advisor')
168
>>> app['persons']['advisor1'] = advisor1
169
>>> addEmail(advisor1)
170
>>> advisor2 = BasicPerson('advisor2', '2', 'Advisor')
171
>>> app['persons']['advisor2'] = advisor2
172
>>> addEmail(advisor2)
174
Now we'll create a message and add it to the container. Upon adding the message
175
the dummy mail sender will print out the email that would otherwise be sent to
176
a real smtp server. We'll also note that the message's student property will
177
be the correct student.
179
>>> body = "John has been a bad student."
180
>>> message1 = intervention.InterventionMessage(teacher1.username,
181
... [teacher2.username, advisor1.username], body)
182
>>> verifyObject(interfaces.IInterventionMessage, message1)
184
>>> jdoeMessages['1'] = message1
185
From: teacher1@example.com
186
To: advisor1@example.com, teacher2@example.com
187
Subject: INTERVENTION MESSAGE: John Doe
190
John has been a bad student.
191
>>> message1.student is jdoe
194
We'll create another message with different sender and recipients and add it
195
to the container. The difference will be reflected in the email messge.
197
>>> body = "John still needs to learn to behave."
198
>>> message2 = intervention.InterventionMessage(teacher2.username,
199
... [advisor1.username, advisor2.username], body)
200
>>> jdoeMessages['2'] = message2
201
From: teacher2@example.com
202
To: advisor1@example.com, advisor2@example.com
203
Subject: INTERVENTION MESSAGE: John Doe
206
John still needs to learn to behave.
211
First, we'll need to register the object added handler that sends an email
212
to the persons responsible for a goal when the goal is added.
214
>>> provideHandler(sendmail.sendInterventionGoalAddEmail,
215
... adapts=(interfaces.IInterventionGoal, IObjectAddedEvent))
217
Let's create some InterventionGoal objects for the student and put them in
218
the student's InterventionGoals container. We'll note that emails will
219
be sent when a goal is added to the goals container.
221
>>> from datetime import date
222
>>> goal1 = intervention.InterventionGoal('bad behaviour', 'be nicer',
223
... 'smart', 'nicer to clasmates', 'teach manners', date(2004, 9, 1),
224
... [advisor1.username, advisor2.username], creator=teacher1.username)
225
>>> verifyObject(interfaces.IInterventionGoal, goal1)
227
>>> jdoeGoals['1'] = goal1
228
From: teacher1@example.com
229
To: advisor1@example.com, advisor2@example.com
230
Subject: INTERVENTION GOAL ADDED: John Doe
231
The following goal was added for John Doe:
272
http://127.0.0.1/schooltool.interventions/2004-2005/jdoe
274
>>> goal1.student is jdoe
277
As it turns out, goals have an at_one_time_responsible attribute that basically
278
is a union of every different value persns_responsible has had for the life
279
of the goal object. Presently it's the same as persns_responsible.
281
>>> goal1.at_one_time_responsible
282
['advisor1', 'advisor2']
284
If we change the persons_responsible to have a new user, we'll see that the
285
at_one_time_responsible attribute will have a record of all the hisorical
288
>>> goal1.persons_responsible = ['manager']
289
>>> goal1.at_one_time_responsible
290
['advisor1', 'advisor2', 'manager']
292
We'll restore persons_responsible for later tests.
294
>>> goal1.persons_responsible = ['advisor1', 'advisor2']
296
Let's add a second one.
298
>>> goal2 = intervention.InterventionGoal('bad grades', 'passing grades',
299
... 'friendly', 'grades are passing', 'tutor student', date.today(),
300
... [teacher1.username, advisor2.username], creator=teacher1.username)
301
>>> jdoeGoals['2'] = goal2
302
From: teacher1@example.com
303
To: advisor2@example.com, teacher1@example.com
304
Subject: INTERVENTION GOAL ADDED: John Doe
305
The following goal was added for John Doe:
346
http://127.0.0.1/schooltool.interventions/2004-2005/jdoe
349
We chose date.today() as the timeline for our goals because we wanted to test
350
right away the method in our sendmail module that notifies the persons
351
responsible via email when the timeline has been reached for a goal. We'll
352
call this method and see the email messages that get generated. Also, we'll
353
need to add the schooltool manager user that's expected by the routine to be
356
>>> from schooltool.intervention import sendmail
357
>>> notified = sendmail.sendInterventionGoalNotifyEmails()
358
From: manager@example.com
359
To: advisor1@example.com, advisor2@example.com
360
Subject: INTERVENTION GOAL DUE: John Doe
361
Please follow the link below to update the follow up notes and, if
362
appropriate, the goal met status of the intervention goal for John Doe.
364
http://127.0.0.1/schooltool.interventions/.../jdoe/goals/1/@@editGoal.html
366
From: manager@example.com
367
To: advisor2@example.com, teacher1@example.com
368
Subject: INTERVENTION GOAL DUE: John Doe
369
Please follow the link below to update the follow up notes and, if
370
appropriate, the goal met status of the intervention goal for John Doe.
372
http://127.0.0.1/schooltool.interventions/.../jdoe/goals/2/@@editGoal.html
377
If we call the same routine again, we will get nothing because the notified
378
flags have been set on the goals.
380
>>> notified = sendmail.sendInterventionGoalNotifyEmails()
385
Convenience functions
386
---------------------
388
To minimuze code size and complexity, we have a number of convenince functions
389
that take care of converting ids to person objects, names or email addresses,
390
depending on the need.
392
>>> intervention.convertIdToPerson('teacher1').title
394
>>> intervention.convertIdToPerson('advisor1').title
397
>>> intervention.convertIdToNameWithSort('teacher1')
398
(('Teacher', '1'), '1 Teacher')
399
>>> intervention.convertIdToNameWithSort('advisor1')
400
(('Advisor', '1'), '1 Advisor')
402
>>> intervention.convertIdToName('teacher1')
404
>>> intervention.convertIdToName('advisor1')
407
>>> intervention.convertIdsToNames(['teacher1', 'advisor1'])
408
['1 Advisor', '1 Teacher']
410
>>> intervention.convertIdToEmail('teacher1')
411
u'teacher1@example.com'
412
>>> intervention.convertIdToEmail('advisor1')
413
u'advisor1@example.com'
415
>>> intervention.convertIdsToEmail(['teacher1', 'advisor1'])
416
[u'advisor1@example.com', u'teacher1@example.com']
418
These convenince functions must fail safe in case user ids are orphaned when
419
a person record is removed.
421
>>> intervention.convertIdToPerson('teacher0') is None
424
>>> intervention.convertIdToNameWithSort('teacher0')
425
(('', ''), u'Unknown Person')
427
>>> intervention.convertIdToName('teacher0')
430
>>> intervention.convertIdsToNames(['teacher1', 'teacher0', 'advisor1'])
431
[u'Unknown Person', '1 Advisor', '1 Teacher']
433
>>> intervention.convertIdToEmail('teacher0')
436
>>> intervention.convertIdsToEmail(['teacher1', 'teacher0', 'advisor1'])
437
[u'advisor1@example.com', u'teacher1@example.com']
439
The special id that consists of the student's id plus ':1' for parent one
440
causes the convenience functions to find the corresponding contact of the
441
given student and return that contact's information.
443
>>> intervention.convertIdToEmail('jdoe')
446
>>> intervention.convertIdToEmail('jdoe:1')
447
'parent1@provider.com'
449
This needs to fail safe whether the parent index is too large, the parent
450
has no email, or the student id itself is invalid.
452
>>> intervention.convertIdToEmail('jdoe:2')
455
>>> parent1.email = None
456
>>> intervention.convertIdToEmail('jdoe:1')
459
>>> intervention.convertIdToEmail('invalid:1')