===============================
 Project Team Management  View
===============================

First we set a project up w/ a membership invitation and a membership
request so we've got some more useful data.

    >>> proj = self.portal.projects.p3 # <-- m1 is ProjectAdmin
    >>> team = proj.getTeams()[0]
    >>> self.login('m4') # <-- m4 not on team
    >>> team.join()
    True
    >>> self.login('m1')
    >>> team.manage_delObjects(ids=['m3']) # <-- remove to re-add
    >>> team.addMember('m3')
    <OpenMembership at ...m3>
    >>> view = proj.restrictedTraverse('manage-team')
    >>> view
    <Products.Five.metaclass.ManageTeamView object at ...>

The view has properties to get info for the team members and all
pending membership invitations and requests::

    >>> len(view.pending_requests)
    1
    >>> len(view.pending_invitations)
    1
    >>> len(view.active_mships)
    3
    >>> len(view.pending_mships)
    2

Now let's make sure the project admin can approve membership requests.
First we need to clear out the memoized cache:

    >>> self.clearMemoCache()
    >>> res = view.approve_requests(['m4'])
    >>> len(view.pending_requests)
    0
    >>> len(view.active_mships)
    4

Verify that the notifying email went out:

    >>> mailhost = view.get_tool('MailHost')
    >>> len(mailhost.messages)
    1

Also verify that the transient messages were saved

    >>> from opencore.nui.member.interfaces import ITransientMessage
    >>> tm = getUtility(ITransientMessage)
    >>> list(tm.get_msgs('m4', view.msg_category))
    [(0, 'You have been accepted to <a href="http://nohost/plone/projects/p3">Project Three</a>')]

Next we recreate the request and test discarding; no add'l email
should be sent:

    >>> self.clearMemoCache()
    >>> team.manage_delObjects(ids=['m4'])
    >>> self.login('m4')
    >>> team.join()
    True
    >>> self.login('m1')
    >>> res = view.discard_requests(['m4'])
    >>> len(view.pending_requests)
    0
    >>> len(view.active_mships)
    3
    >>> 'm4' in team.objectIds()
    False
    >>> len(mailhost.messages)
    1

Try again, this time reject instead of discard, should generate an
email notifier:

    >>> self.clearMemoCache()
    >>> self.login('m4')
    >>> team.join()
    True
    >>> self.login('m1')
    >>> res = view.reject_requests(['m4'])
    >>> len(view.pending_requests)
    0
    >>> len(view.active_mships)
    3
    >>> 'm4' in team.objectIds()
    True
    >>> len(mailhost.messages)
    2

And we should also have some rejection msgs for the user

    >>> msgs = list(tm.get_msgs('m4', view.msg_category))
    >>> msgs[0]
    (0, 'You have been accepted to <a href="http://nohost/plone/projects/p3">Project Three</a>')
    >>> msgs[1]
    (1, 'You have been denied membership to <a href="http://nohost/plone/projects/p3">Project Three</a>')

Verify that invitation reminders go out:

    >>> self.clearMemoCache()
    >>> res = view.remind_invitations(['m3'])
    >>> len(mailhost.messages)
    3

Make sure we can remove invitations; email notifier should be sent:

    >>> self.clearMemoCache()
    >>> res = view.remove_invitations(['m3'])
    >>> len(view.pending_invitations)
    0
    >>> len(mailhost.messages)
    4

Test membership removal.  We set the HTTP_REFERER on the request
because the octopus decorator requires (and returns) it.  Notifier
should be sent:

    >>> self.clearMemoCache()
    >>> res = view.remove_members(['m2'])
    >>> len(view.active_mships)
    2
    >>> len(mailhost.messages)
    5

Transient Message should be added as well

    >>> list(tm.get_msgs('m2', view.msg_category))
    [(0, 'You have been deactivated from <a href="http://nohost/plone/projects/p3">Project Three</a>')]

Ensure that form submissions changing the team roles for members
behave as expected.  At the beginning we re-add and approve m3 so we
can use him for testing later:

    >>> team.addMember('m3')
    <OpenMembership at ...>
    >>> res = view.approve_requests(['m3'])
    >>> team.getHighestTeamRoleForMember('m1')
    'ProjectAdmin'
    >>> team.getHighestTeamRoleForMember('portal_owner')
    'ProjectAdmin'
    >>> team.getHighestTeamRoleForMember('m3')
    'ProjectMember'
    >>> view.request.form.update(
    ...    {'task|batch_role|set-roles': 'Lalala',
    ...     'role': ['m1', 'portal_owner', 'm3'],
    ...     'm1_role': 'ProjectAdmin',
    ...     'portal_owner_roles': 'ProjectMember',
    ...     'm3_role': 'ProjectAdmin'
    ...     })
    >>> view.request.environ['HTTP_REFERER'] = 'referer'
    >>> res = view.set_roles(['m1', 'portal_owner', 'm3'],
    ...                      [{'role': 'ProjectAdmin'},
    ...                       {'role': 'ProjectMember'},
    ...                       {'role': 'ProjectAdmin'}])
    >>> team.getHighestTeamRoleForMember('m1')
    'ProjectAdmin'
    >>> team.getHighestTeamRoleForMember('portal_owner')
    'ProjectMember'
    >>> team.getHighestTeamRoleForMember('m3')
    'ProjectAdmin'
    >>> team.removeMember('m3')

Now test member search.  We should be getting results on members w/o
team memberships, and inactive team memberships, but not on those that
are active or pending approval.

For the first search, m1 is active, m2 is inactive, m3 has no
membership, and m4 is rejected_by_admin.  m1 is the only one shouldn't
be returned by the search:

    >>> view.request.form.clear()
    >>> view.request.form['search_for'] = 'mem'
    >>> view.search_members()
    >>> len(view.results)
    3
    >>> ids = [b.getId for b in view.results]
    >>> 'm1' in ids
    False

Now we add m3 as a pending member, should disappear from the results:

    >>> team.addMember('m3') # <-- now pending
    <OpenMembership at ...>
    >>> view.search_members()
    >>> len(view.results)
    2
    >>> ids = [b.getId for b in view.results]
    >>> 'm1' in ids
    False
    >>> 'm3' in ids
    False
    >>> team.removeMember('m3')

Next test the 'invite' button for member invitation.  First we try
with a member that is not on the team.  Notifier should go out:

    >>> self.clearMemoCache()
    >>> res = view.invite_member(['m3'])
    >>> len(view.pending_invitations)
    1
    >>> len(mailhost.messages)
    6

Now try to reinvite an inactive member:

    >>> self.clearMemoCache()
    >>> res = view.invite_member(['m2'])
    >>> len(view.pending_invitations)
    2
    >>> len(mailhost.messages)
    7

And last we reinvite m4, who was rejected_by_admin:

    >>> self.clearMemoCache()
    >>> res = view.invite_member(['m4'])
    >>> len(view.pending_invitations)
    3
    >>> len(mailhost.messages)
    8

Okay, now to deal with inviting people by email address.  First we try
a couple of addresses that don't belong to any site members:

    >>> self.clearMemoCache()
    >>> view.request.form.clear()
    >>> invites = 'doesnot@exist.org, isnot@there.com'
    >>> view.request.form.update({'task|email-invites': 'Send',
    ...                           'email-invites': invites})
    >>> view.add_email_invites()
    >>> len(view.pending_invitations) # <- shouldn't change
    3
    >>> len(view.pending_email_invites)
    2
    >>> len(mailhost.messages)
    10

Then we try to invite someone using an email address that is already
in use by a site member who's not a project member:

    >>> self.clearMemoCache()
    >>> team.removeMember('m3')
    >>> len(view.pending_invitations) # <- should go up by one
    2

    >>> self.clearMemoCache()
    >>> email = view.membertool.getMemberById('m3').getEmail()
    >>> view.request.form.update({'task|email-invites': 'Send',
    ...                           'email-invites': email})
    >>> view.add_email_invites()
    >>> len(view.pending_invitations) # <- should go up by one
    3
    >>> len(view.pending_email_invites) # <- doesn't change
    2
    >>> len(mailhost.messages) # <- increase by one
    11

Try the same one again, should have no result:

    >>> self.clearMemoCache()
    >>> view.add_email_invites()
    >>> len(view.pending_invitations)
    3
    >>> len(view.pending_email_invites)
    2
    >>> len(mailhost.messages)
    11

Verify we can send reminders to the email invitees:

    >>> self.clearMemoCache()
    >>> addys = [addy.strip() for addy in invites.split(',')]
    >>> view.remind_email_invites(addys)
    >>> len(mailhost.messages) # <- increase by two
    13

And that they can be removed:

    >>> self.clearMemoCache()
    >>> view.remove_email_invites(addys)
    {...doesnot@exist.org...isnot@there.com...}
    >>> len(view.pending_email_invites)
    0
    >>> len(mailhost.messages) # <- increase by two
    15
