=====================
Student Interventions
=====================

This package supplies the school with a number of objects for tracking
interventions with students who are having either acedemic or behavioral
problems.  Objects include messages passed between concerned faculty members
and goals that are established for the student.

Let's import some zope stuff before we use it.

    >>> from zope.component import provideHandler, provideUtility
    >>> from zope.component import provideAdapter
    >>> from zope.component import Interface
    >>> from zope.lifecycleevent.interfaces import (IObjectAddedEvent,
    ...     IObjectRemovedEvent)
    >>> from zope.interface.verify import verifyObject

Let's set up the app object.

    >>> from schooltool.app.interfaces import ISchoolToolApplication
    >>> from schooltool.testing import setup as stsetup
    >>> app = stsetup.setUpSchoolToolSite()

We'll need a schoolyear and current term.

    >>> from schooltool.schoolyear.schoolyear import getSchoolYearContainer
    >>> from schooltool.term.interfaces import ITermContainer
    >>> from schooltool.term.term import getTermContainer
    >>> from schooltool.term.term import getSchoolYearForTerm
    >>> provideAdapter(getTermContainer, [Interface], ITermContainer)
    >>> provideAdapter(getSchoolYearForTerm)
    >>> provideAdapter(getSchoolYearContainer)

    >>> from datetime import date
    >>> from schooltool.schoolyear.interfaces import ISchoolYearContainer
    >>> from schooltool.schoolyear.schoolyear import SchoolYear
    >>> schoolyear = SchoolYear("Sample", date(2004, 9, 1), date(2005, 12, 20))
    >>> ISchoolYearContainer(app)['2004-2005'] = schoolyear

    >>> from schooltool.term.term import Term
    >>> term = Term('Sample', date(2004, 9, 1), date(2004, 12, 20))
    >>> terms = ITermContainer(app)
    >>> terms['2004-fall'] = term

    >>> from schooltool.term.tests import setUpDateManagerStub
    >>> setUpDateManagerStub(date(2004, 9, 1), term)

We will need an adapter to get the schooltool application.

    >>> from zope.component import provideAdapter
    >>> provideAdapter(lambda context: app,
    ...                adapts=[None],
    ...                provides=ISchoolToolApplication)

Interventions also depend on user contact information to get the email address.

    >>> from schooltool.relationship.interfaces import IRelationshipLinks
    >>> from schooltool.relationship.annotatable import getRelationshipLinks
    >>> from zope.annotation.interfaces import IAnnotatable
    >>> provideAdapter(getRelationshipLinks, (IAnnotatable,), IRelationshipLinks)

    >>> from schooltool.basicperson.interfaces import IBasicPerson
    >>> from schooltool.contact.contact import Contactable
    >>> provideAdapter(factory=Contactable, adapts=[IBasicPerson])

    >>> from schooltool.contact.basicperson import getBoundContact
    >>> provideAdapter(factory=getBoundContact, adapts=[None])

Interventions
-------------

Now we get to the heart of the matter, the interventions themselves.  We start
with the root container of all of the interventions.  The test aparatus will
have set this up for us like the application init handler would do.

    >>> from schooltool.intervention import intervention, interfaces
    >>> interventionRoot = app['schooltool.interventions']
    >>> verifyObject(interfaces.IInterventionRoot, interventionRoot)
    True
    >>> len(interventionRoot)
    0

The intervention root container contains InterventionSchoolYear objects,
keyed by the school year's id, which in turn contain InterventionStudent
objects for each student that is under intervention during that school year.
We have a handy function that will return to us the InterventionStudent
object for a given student, school year pair.  If the InterventionSchoolYear
object is missing, it will create it.  If the InterventionStudent object is
missing, it will create that as well.

Let's create a student and not specify the school year.  We will then test this
function does what we expect.  In addition to creating the student's
intervention container, it will place the messages and goals containers in it.

    >>> from schooltool.contact.interfaces import IContact, IContactable
    >>> from schooltool.contact.contact import Contact
    >>> def addEmail(person):
    ...     IContact(person).email = '%s@example.com' % person.__name__

    >>> from schooltool.basicperson.person import BasicPerson
    >>> jdoe = BasicPerson('jdoe', 'John', 'Doe')
    >>> app['persons']['jdoe'] = jdoe
    >>> addEmail(jdoe)

    >>> parent1 = Contact()
    >>> parent1.email = 'parent1@provider.com'
    >>> IContactable(jdoe).contacts.add(parent1)

    >>> jdoeIntervention = intervention.getInterventionStudent(jdoe.__name__)
    >>> verifyObject(interfaces.IInterventionStudent, jdoeIntervention)
    True
    >>> jdoeIntervention.student is jdoe
    True
    >>> [key for key in interventionRoot]
    [u'2004-2005']
    >>> interventionSchoolYear = intervention.getInterventionSchoolYear()
    >>> verifyObject(interfaces.IInterventionSchoolYear, interventionSchoolYear)
    True
    >>> [key for key in interventionSchoolYear]
    [u'jdoe']
    >>> [key for key in jdoeIntervention]
    [u'goals', u'messages']
    >>> jdoeMessages = jdoeIntervention['messages']
    >>> verifyObject(interfaces.IInterventionMessages, jdoeMessages)
    True
    >>> jdoeMessages.student is jdoe
    True
    >>> len(jdoeMessages)
    0
    >>> jdoeGoals = jdoeIntervention['goals']
    >>> verifyObject(interfaces.IInterventionGoals, jdoeGoals)
    True
    >>> jdoeGoals.student is jdoe
    True
    >>> len(jdoeGoals)
    0

Intervention Messagess
----------------------

Now we will create some InterventionMessage objects for the student and put
them in the student's InterventionMessages container.

First, we'll need to register the object added handler that sends the message
to the recipients as an email as well as the dummy mail sender that we need
for testing.

    >>> from schooltool.email.interfaces import IEmailUtility
    >>> from schooltool.intervention import sendmail
    >>> provideHandler(sendmail.sendInterventionMessageEmail, 
    ...                adapts=(interfaces.IInterventionMessage, IObjectAddedEvent))
    >>> provideUtility(sendmail.TestMailDelivery(), provides=IEmailUtility)

    >>> manager_user = BasicPerson('manager', 'SchoolTool', 'Manager')
    >>> app['persons']['manager'] = manager_user
    >>> addEmail(manager_user)

We will create person objects for some teachers and advisors.

    >>> teacher1 = BasicPerson('teacher1', '1', 'Teacher')
    >>> app['persons']['teacher1'] = teacher1
    >>> addEmail(teacher1)
    >>> teacher2 = BasicPerson('teacher2', '2', 'Teacher')
    >>> app['persons']['teacher2'] = teacher2
    >>> addEmail(teacher2)
    >>> advisor1 = BasicPerson('advisor1', '1', 'Advisor')
    >>> app['persons']['advisor1'] = advisor1
    >>> addEmail(advisor1)
    >>> advisor2 = BasicPerson('advisor2', '2', 'Advisor')
    >>> app['persons']['advisor2'] = advisor2
    >>> addEmail(advisor2)

Now we'll create a message and add it to the container.  Upon adding the message
the dummy mail sender will print out the email that would otherwise be sent to
a real smtp server.  We'll also note that the message's student property will
be the correct student.

    >>> body = "John has been a bad student."
    >>> message1 = intervention.InterventionMessage(teacher1.username, 
    ...     [teacher2.username, advisor1.username], body)
    >>> verifyObject(interfaces.IInterventionMessage, message1)
    True
    >>> jdoeMessages['1'] = message1
    From: teacher1@example.com
    To: advisor1@example.com, teacher2@example.com
    Subject: INTERVENTION MESSAGE: John Doe
    1 Teacher writes:
    <BLANKLINE>
    John has been a bad student.
    >>> message1.student is jdoe
    True

We'll create another message with different sender and recipients and add it
to the container.  The difference will be reflected in the email messge.

    >>> body = "John still needs to learn to behave."
    >>> message2 = intervention.InterventionMessage(teacher2.username, 
    ...     [advisor1.username, advisor2.username], body)
    >>> jdoeMessages['2'] = message2
    From: teacher2@example.com
    To: advisor1@example.com, advisor2@example.com
    Subject: INTERVENTION MESSAGE: John Doe
    2 Teacher writes:
    <BLANKLINE>
    John still needs to learn to behave.

Intervention Goals
------------------

First, we'll need to register the object added handler that sends an email
to the persons responsible for a goal when the goal is added.

    >>> provideHandler(sendmail.sendInterventionGoalAddEmail, 
    ...                adapts=(interfaces.IInterventionGoal, IObjectAddedEvent))

Let's create some InterventionGoal objects for the student and put them in
the student's InterventionGoals container.  We'll note that emails will
be sent when a goal is added to the goals container.

    >>> from datetime import date
    >>> goal1 = intervention.InterventionGoal('bad behaviour', 'be nicer', 
    ...     'smart', 'nicer to clasmates', 'teach manners', date(2004, 9, 1),
    ...     [advisor1.username, advisor2.username], creator=teacher1.username)
    >>> verifyObject(interfaces.IInterventionGoal, goal1)
    True
    >>> jdoeGoals['1'] = goal1
    From: teacher1@example.com
    To: advisor1@example.com, advisor2@example.com
    Subject: INTERVENTION GOAL ADDED: John Doe
    The following goal was added for John Doe:
    <BLANKLINE>
    Presenting concerns
    -------------------
    <BLANKLINE>
    bad behaviour
    <BLANKLINE>
    Goal
    ----
    <BLANKLINE>
    be nicer
    <BLANKLINE>
    Strengths
    ---------
    <BLANKLINE>
    smart
    <BLANKLINE>
    Indicators
    ----------
    <BLANKLINE>
    nicer to clasmates
    <BLANKLINE>
    Intervention
    ------------
    <BLANKLINE>
    teach manners
    <BLANKLINE>
    Timeline
    --------
    <BLANKLINE>
    ...
    <BLANKLINE>
    Persons responsible
    -------------------
    <BLANKLINE>
    1 Advisor
    2 Advisor
    <BLANKLINE>
    Intervention Center
    -------------------
    <BLANKLINE>
    http://127.0.0.1/schooltool.interventions/2004-2005/jdoe
    <BLANKLINE>
    >>> goal1.student is jdoe
    True

As it turns out, goals have an at_one_time_responsible attribute that basically
is a union of every different value persns_responsible has had for the life
of the goal object.  Presently it's the same as persns_responsible.

    >>> goal1.at_one_time_responsible
    ['advisor1', 'advisor2']

If we change the persons_responsible to have a new user, we'll see that the
at_one_time_responsible attribute will have a record of all the hisorical
values.

    >>> goal1.persons_responsible = ['manager']
    >>> goal1.at_one_time_responsible
    ['advisor1', 'advisor2', 'manager']

We'll restore persons_responsible for later tests.

    >>> goal1.persons_responsible = ['advisor1', 'advisor2']

Let's add a second one.

    >>> goal2 = intervention.InterventionGoal('bad grades', 'passing grades', 
    ...     'friendly', 'grades are passing', 'tutor student', date.today(),
    ...     [teacher1.username, advisor2.username], creator=teacher1.username)
    >>> jdoeGoals['2'] = goal2
    From: teacher1@example.com
    To: advisor2@example.com, teacher1@example.com
    Subject: INTERVENTION GOAL ADDED: John Doe
    The following goal was added for John Doe:
    <BLANKLINE>
    Presenting concerns
    -------------------
    <BLANKLINE>
    bad grades
    <BLANKLINE>
    Goal
    ----
    <BLANKLINE>
    passing grades
    <BLANKLINE>
    Strengths
    ---------
    <BLANKLINE>
    friendly
    <BLANKLINE>
    Indicators
    ----------
    <BLANKLINE>
    grades are passing
    <BLANKLINE>
    Intervention
    ------------
    <BLANKLINE>
    tutor student
    <BLANKLINE>
    Timeline
    --------
    <BLANKLINE>
    ...
    <BLANKLINE>
    Persons responsible
    -------------------
    <BLANKLINE>
    2 Advisor
    1 Teacher
    <BLANKLINE>
    Intervention Center
    -------------------
    <BLANKLINE>
    http://127.0.0.1/schooltool.interventions/2004-2005/jdoe
    <BLANKLINE>

We chose date.today() as the timeline for our goals because we wanted to test
right away the method in our sendmail module that notifies the persons
responsible via email when the timeline has been reached for a goal.  We'll
call this method and see the email messages that get generated.   Also, we'll
need to add the schooltool manager user that's expected by the routine to be
present.

    >>> from schooltool.intervention import sendmail
    >>> notified = sendmail.sendInterventionGoalNotifyEmails()
    From: manager@example.com
    To: advisor1@example.com, advisor2@example.com
    Subject: INTERVENTION GOAL DUE: John Doe
    Please follow the link below to update the follow up notes and, if
    appropriate, the goal met status of the intervention goal for John Doe.
    <BLANKLINE>
    http://127.0.0.1/schooltool.interventions/.../jdoe/goals/1/@@editGoal.html
    <BLANKLINE>
    From: manager@example.com
    To: advisor2@example.com, teacher1@example.com
    Subject: INTERVENTION GOAL DUE: John Doe
    Please follow the link below to update the follow up notes and, if
    appropriate, the goal met status of the intervention goal for John Doe.
    <BLANKLINE>
    http://127.0.0.1/schooltool.interventions/.../jdoe/goals/2/@@editGoal.html
    <BLANKLINE>
    >>> len(notified)
    2

If we call the same routine again, we will get nothing because the notified
flags have been set on the goals.

    >>> notified = sendmail.sendInterventionGoalNotifyEmails()
    >>> len(notified)
    0


Convenience functions
---------------------

To minimuze code size and complexity, we have a number of convenince functions
that take care of converting ids to person objects, names or email addresses,
depending on the need.

    >>> intervention.convertIdToPerson('teacher1').title
    'Teacher, 1'
    >>> intervention.convertIdToPerson('advisor1').title
    'Advisor, 1'

    >>> intervention.convertIdToNameWithSort('teacher1')
    (('Teacher', '1'), '1 Teacher')
    >>> intervention.convertIdToNameWithSort('advisor1')
    (('Advisor', '1'), '1 Advisor')

    >>> intervention.convertIdToName('teacher1')
    '1 Teacher'
    >>> intervention.convertIdToName('advisor1')
    '1 Advisor'

    >>> intervention.convertIdsToNames(['teacher1', 'advisor1'])
    ['1 Advisor', '1 Teacher']

    >>> intervention.convertIdToEmail('teacher1')
    u'teacher1@example.com'
    >>> intervention.convertIdToEmail('advisor1')
    u'advisor1@example.com'

    >>> intervention.convertIdsToEmail(['teacher1', 'advisor1'])
    [u'advisor1@example.com', u'teacher1@example.com']

These convenince functions must fail safe in case user ids are orphaned when
a person record is removed.

    >>> intervention.convertIdToPerson('teacher0') is None
    True

    >>> intervention.convertIdToNameWithSort('teacher0')
    (('', ''), u'Unknown Person')

    >>> intervention.convertIdToName('teacher0')
    u'Unknown Person'

    >>> intervention.convertIdsToNames(['teacher1', 'teacher0', 'advisor1'])
    [u'Unknown Person', '1 Advisor', '1 Teacher']

    >>> intervention.convertIdToEmail('teacher0')
    u''

    >>> intervention.convertIdsToEmail(['teacher1', 'teacher0', 'advisor1'])
    [u'advisor1@example.com', u'teacher1@example.com']

The special id that consists of the student's id plus ':1' for parent one
causes the convenience functions to find the corresponding contact of the
given student and return that contact's information.

    >>> intervention.convertIdToEmail('jdoe')
    u'jdoe@example.com'

    >>> intervention.convertIdToEmail('jdoe:1')
    'parent1@provider.com'

This needs to fail safe whether the parent index is too large, the parent
has no email, or the student id itself is invalid.

    >>> intervention.convertIdToEmail('jdoe:2')
    u''

    >>> parent1.email = None
    >>> intervention.convertIdToEmail('jdoe:1')
    u''

    >>> intervention.convertIdToEmail('invalid:1')
    u''

