~ubuntu-branches/ubuntu/quantal/schooltool.intervention/quantal-201209201712

« back to all changes in this revision

Viewing changes to src/schooltool/intervention/README.txt

  • 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
Student Interventions
 
3
=====================
 
4
 
 
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.
 
9
 
 
10
Let's import some zope stuff before we use it.
 
11
 
 
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
 
18
 
 
19
Let's set up the app object.
 
20
 
 
21
    >>> from schooltool.app.interfaces import ISchoolToolApplication
 
22
    >>> from schooltool.testing import setup as stsetup
 
23
    >>> app = stsetup.setUpSchoolToolSite()
 
24
 
 
25
We'll need a schoolyear and current term.
 
26
 
 
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)
 
34
 
 
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
 
40
 
 
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
 
45
 
 
46
    >>> from schooltool.term.tests import setUpDateManagerStub
 
47
    >>> setUpDateManagerStub(date(2004, 9, 1), term)
 
48
 
 
49
We will need an adapter to get the schooltool application.
 
50
 
 
51
    >>> from zope.component import provideAdapter
 
52
    >>> provideAdapter(lambda context: app,
 
53
    ...                adapts=[None],
 
54
    ...                provides=ISchoolToolApplication)
 
55
 
 
56
Interventions also depend on user contact information to get the email address.
 
57
 
 
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)
 
62
 
 
63
    >>> from schooltool.basicperson.interfaces import IBasicPerson
 
64
    >>> from schooltool.contact.contact import Contactable
 
65
    >>> provideAdapter(factory=Contactable, adapts=[IBasicPerson])
 
66
 
 
67
    >>> from schooltool.contact.basicperson import getBoundContact
 
68
    >>> provideAdapter(factory=getBoundContact, adapts=[None])
 
69
 
 
70
Interventions
 
71
-------------
 
72
 
 
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.
 
76
 
 
77
    >>> from schooltool.intervention import intervention, interfaces
 
78
    >>> interventionRoot = app['schooltool.interventions']
 
79
    >>> verifyObject(interfaces.IInterventionRoot, interventionRoot)
 
80
    True
 
81
    >>> len(interventionRoot)
 
82
    0
 
83
 
 
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.
 
91
 
 
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.
 
95
 
 
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__
 
100
 
 
101
    >>> from schooltool.basicperson.person import BasicPerson
 
102
    >>> jdoe = BasicPerson('jdoe', 'John', 'Doe')
 
103
    >>> app['persons']['jdoe'] = jdoe
 
104
    >>> addEmail(jdoe)
 
105
 
 
106
    >>> parent1 = Contact()
 
107
    >>> parent1.email = 'parent1@provider.com'
 
108
    >>> IContactable(jdoe).contacts.add(parent1)
 
109
 
 
110
    >>> jdoeIntervention = intervention.getInterventionStudent(jdoe.__name__)
 
111
    >>> verifyObject(interfaces.IInterventionStudent, jdoeIntervention)
 
112
    True
 
113
    >>> jdoeIntervention.student is jdoe
 
114
    True
 
115
    >>> [key for key in interventionRoot]
 
116
    [u'2004-2005']
 
117
    >>> interventionSchoolYear = intervention.getInterventionSchoolYear()
 
118
    >>> verifyObject(interfaces.IInterventionSchoolYear, interventionSchoolYear)
 
119
    True
 
120
    >>> [key for key in interventionSchoolYear]
 
121
    [u'jdoe']
 
122
    >>> [key for key in jdoeIntervention]
 
123
    [u'goals', u'messages']
 
124
    >>> jdoeMessages = jdoeIntervention['messages']
 
125
    >>> verifyObject(interfaces.IInterventionMessages, jdoeMessages)
 
126
    True
 
127
    >>> jdoeMessages.student is jdoe
 
128
    True
 
129
    >>> len(jdoeMessages)
 
130
    0
 
131
    >>> jdoeGoals = jdoeIntervention['goals']
 
132
    >>> verifyObject(interfaces.IInterventionGoals, jdoeGoals)
 
133
    True
 
134
    >>> jdoeGoals.student is jdoe
 
135
    True
 
136
    >>> len(jdoeGoals)
 
137
    0
 
138
 
 
139
Intervention Messagess
 
140
----------------------
 
141
 
 
142
Now we will create some InterventionMessage objects for the student and put
 
143
them in the student's InterventionMessages container.
 
144
 
 
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
 
147
for testing.
 
148
 
 
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)
 
154
 
 
155
    >>> manager_user = BasicPerson('manager', 'SchoolTool', 'Manager')
 
156
    >>> app['persons']['manager'] = manager_user
 
157
    >>> addEmail(manager_user)
 
158
 
 
159
We will create person objects for some teachers and advisors.
 
160
 
 
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)
 
173
 
 
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.
 
178
 
 
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)
 
183
    True
 
184
    >>> jdoeMessages['1'] = message1
 
185
    From: teacher1@example.com
 
186
    To: advisor1@example.com, teacher2@example.com
 
187
    Subject: INTERVENTION MESSAGE: John Doe
 
188
    1 Teacher writes:
 
189
    <BLANKLINE>
 
190
    John has been a bad student.
 
191
    >>> message1.student is jdoe
 
192
    True
 
193
 
 
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.
 
196
 
 
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
 
204
    2 Teacher writes:
 
205
    <BLANKLINE>
 
206
    John still needs to learn to behave.
 
207
 
 
208
Intervention Goals
 
209
------------------
 
210
 
 
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.
 
213
 
 
214
    >>> provideHandler(sendmail.sendInterventionGoalAddEmail, 
 
215
    ...                adapts=(interfaces.IInterventionGoal, IObjectAddedEvent))
 
216
 
 
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.
 
220
 
 
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)
 
226
    True
 
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:
 
232
    <BLANKLINE>
 
233
    Presenting concerns
 
234
    -------------------
 
235
    <BLANKLINE>
 
236
    bad behaviour
 
237
    <BLANKLINE>
 
238
    Goal
 
239
    ----
 
240
    <BLANKLINE>
 
241
    be nicer
 
242
    <BLANKLINE>
 
243
    Strengths
 
244
    ---------
 
245
    <BLANKLINE>
 
246
    smart
 
247
    <BLANKLINE>
 
248
    Indicators
 
249
    ----------
 
250
    <BLANKLINE>
 
251
    nicer to clasmates
 
252
    <BLANKLINE>
 
253
    Intervention
 
254
    ------------
 
255
    <BLANKLINE>
 
256
    teach manners
 
257
    <BLANKLINE>
 
258
    Timeline
 
259
    --------
 
260
    <BLANKLINE>
 
261
    ...
 
262
    <BLANKLINE>
 
263
    Persons responsible
 
264
    -------------------
 
265
    <BLANKLINE>
 
266
    1 Advisor
 
267
    2 Advisor
 
268
    <BLANKLINE>
 
269
    Intervention Center
 
270
    -------------------
 
271
    <BLANKLINE>
 
272
    http://127.0.0.1/schooltool.interventions/2004-2005/jdoe
 
273
    <BLANKLINE>
 
274
    >>> goal1.student is jdoe
 
275
    True
 
276
 
 
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.
 
280
 
 
281
    >>> goal1.at_one_time_responsible
 
282
    ['advisor1', 'advisor2']
 
283
 
 
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
 
286
values.
 
287
 
 
288
    >>> goal1.persons_responsible = ['manager']
 
289
    >>> goal1.at_one_time_responsible
 
290
    ['advisor1', 'advisor2', 'manager']
 
291
 
 
292
We'll restore persons_responsible for later tests.
 
293
 
 
294
    >>> goal1.persons_responsible = ['advisor1', 'advisor2']
 
295
 
 
296
Let's add a second one.
 
297
 
 
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:
 
306
    <BLANKLINE>
 
307
    Presenting concerns
 
308
    -------------------
 
309
    <BLANKLINE>
 
310
    bad grades
 
311
    <BLANKLINE>
 
312
    Goal
 
313
    ----
 
314
    <BLANKLINE>
 
315
    passing grades
 
316
    <BLANKLINE>
 
317
    Strengths
 
318
    ---------
 
319
    <BLANKLINE>
 
320
    friendly
 
321
    <BLANKLINE>
 
322
    Indicators
 
323
    ----------
 
324
    <BLANKLINE>
 
325
    grades are passing
 
326
    <BLANKLINE>
 
327
    Intervention
 
328
    ------------
 
329
    <BLANKLINE>
 
330
    tutor student
 
331
    <BLANKLINE>
 
332
    Timeline
 
333
    --------
 
334
    <BLANKLINE>
 
335
    ...
 
336
    <BLANKLINE>
 
337
    Persons responsible
 
338
    -------------------
 
339
    <BLANKLINE>
 
340
    2 Advisor
 
341
    1 Teacher
 
342
    <BLANKLINE>
 
343
    Intervention Center
 
344
    -------------------
 
345
    <BLANKLINE>
 
346
    http://127.0.0.1/schooltool.interventions/2004-2005/jdoe
 
347
    <BLANKLINE>
 
348
 
 
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
 
354
present.
 
355
 
 
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.
 
363
    <BLANKLINE>
 
364
    http://127.0.0.1/schooltool.interventions/.../jdoe/goals/1/@@editGoal.html
 
365
    <BLANKLINE>
 
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.
 
371
    <BLANKLINE>
 
372
    http://127.0.0.1/schooltool.interventions/.../jdoe/goals/2/@@editGoal.html
 
373
    <BLANKLINE>
 
374
    >>> len(notified)
 
375
    2
 
376
 
 
377
If we call the same routine again, we will get nothing because the notified
 
378
flags have been set on the goals.
 
379
 
 
380
    >>> notified = sendmail.sendInterventionGoalNotifyEmails()
 
381
    >>> len(notified)
 
382
    0
 
383
 
 
384
 
 
385
Convenience functions
 
386
---------------------
 
387
 
 
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.
 
391
 
 
392
    >>> intervention.convertIdToPerson('teacher1').title
 
393
    'Teacher, 1'
 
394
    >>> intervention.convertIdToPerson('advisor1').title
 
395
    'Advisor, 1'
 
396
 
 
397
    >>> intervention.convertIdToNameWithSort('teacher1')
 
398
    (('Teacher', '1'), '1 Teacher')
 
399
    >>> intervention.convertIdToNameWithSort('advisor1')
 
400
    (('Advisor', '1'), '1 Advisor')
 
401
 
 
402
    >>> intervention.convertIdToName('teacher1')
 
403
    '1 Teacher'
 
404
    >>> intervention.convertIdToName('advisor1')
 
405
    '1 Advisor'
 
406
 
 
407
    >>> intervention.convertIdsToNames(['teacher1', 'advisor1'])
 
408
    ['1 Advisor', '1 Teacher']
 
409
 
 
410
    >>> intervention.convertIdToEmail('teacher1')
 
411
    u'teacher1@example.com'
 
412
    >>> intervention.convertIdToEmail('advisor1')
 
413
    u'advisor1@example.com'
 
414
 
 
415
    >>> intervention.convertIdsToEmail(['teacher1', 'advisor1'])
 
416
    [u'advisor1@example.com', u'teacher1@example.com']
 
417
 
 
418
These convenince functions must fail safe in case user ids are orphaned when
 
419
a person record is removed.
 
420
 
 
421
    >>> intervention.convertIdToPerson('teacher0') is None
 
422
    True
 
423
 
 
424
    >>> intervention.convertIdToNameWithSort('teacher0')
 
425
    (('', ''), u'Unknown Person')
 
426
 
 
427
    >>> intervention.convertIdToName('teacher0')
 
428
    u'Unknown Person'
 
429
 
 
430
    >>> intervention.convertIdsToNames(['teacher1', 'teacher0', 'advisor1'])
 
431
    [u'Unknown Person', '1 Advisor', '1 Teacher']
 
432
 
 
433
    >>> intervention.convertIdToEmail('teacher0')
 
434
    u''
 
435
 
 
436
    >>> intervention.convertIdsToEmail(['teacher1', 'teacher0', 'advisor1'])
 
437
    [u'advisor1@example.com', u'teacher1@example.com']
 
438
 
 
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.
 
442
 
 
443
    >>> intervention.convertIdToEmail('jdoe')
 
444
    u'jdoe@example.com'
 
445
 
 
446
    >>> intervention.convertIdToEmail('jdoe:1')
 
447
    'parent1@provider.com'
 
448
 
 
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.
 
451
 
 
452
    >>> intervention.convertIdToEmail('jdoe:2')
 
453
    u''
 
454
 
 
455
    >>> parent1.email = None
 
456
    >>> intervention.convertIdToEmail('jdoe:1')
 
457
    u''
 
458
 
 
459
    >>> intervention.convertIdToEmail('invalid:1')
 
460
    u''
 
461