def add(self, persons, reason, header):
        """See `INotificationRecipientSet`."""
        from zope.security.proxy import removeSecurityProxy
        from lp.registry.model.person import get_recipients
        if IPerson.providedBy(persons):
            persons = [persons]

        for person in persons:
            assert IPerson.providedBy(person), (
                'You can only add() an IPerson: %r' % person)
            # If the person already has a rationale, keep the first one.
            if person in self._personToRationale:
                continue
            self._personToRationale[person] = reason, header
            for receiving_person in get_recipients(person):
                # Bypass zope's security because IEmailAddress.email is not
                # public.
                preferred_email = removeSecurityProxy(
                    receiving_person).preferredemail
                email = str(preferred_email.email)
                self._receiving_people.add((email, receiving_person))
                old_person = self._emailToPerson.get(email)
                # Only associate this email to the person, if there was
                # no association or if the previous one was to a team and
                # the newer one is to a person.
                if (old_person is None
                    or (old_person.is_team and not person.is_team)):
                    self._emailToPerson[email] = person
Esempio n. 2
0
    def add(self, persons, reason, header):
        """See `INotificationRecipientSet`."""
        from zope.security.proxy import removeSecurityProxy
        from lp.registry.model.person import get_recipients
        if (IPerson.providedBy(persons)
                or zope_isinstance(persons, StubPerson)):
            persons = [persons]

        for person in persons:
            assert (IPerson.providedBy(person)
                    or zope_isinstance(person, StubPerson)), (
                        'You can only add() an IPerson or a StubPerson: %r' %
                        person)
            # If the person already has a rationale, keep the first one.
            if person in self._personToRationale:
                continue
            self._personToRationale[person] = reason, header
            if IPerson.providedBy(person):
                recipients = get_recipients(person)
            else:
                recipients = [person]
            for receiving_person in recipients:
                # Bypass zope's security because IEmailAddress.email is not
                # public.
                preferred_email = removeSecurityProxy(
                    receiving_person).preferredemail
                email = str(preferred_email.email)
                self._receiving_people.add((email, receiving_person))
                old_person = self._emailToPerson.get(email)
                # Only associate this email to the person, if there was
                # no association or if the previous one was to a team and
                # the newer one is to a person.
                if (old_person is None
                        or (old_person.is_team and not person.is_team)):
                    self._emailToPerson[email] = person
Esempio n. 3
0
    def forInvitationToJoinTeam(cls, member, team):
        """Create a mailer for notifying about team joining invitations.

        XXX: Guilherme Salgado 2007-05-08:
        At some point we may want to extend this functionality to allow
        invites to be sent to users as well, but for now we only use it for
        teams.
        """
        assert member.is_team
        membership = getUtility(ITeamMembershipSet).getByPersonAndTeam(
            member, team)
        assert membership is not None
        recipients = OrderedDict()
        for admin in member.adminmembers:
            for recipient in get_recipients(admin):
                recipients[recipient] = TeamMembershipRecipientReason.forAdmin(
                    admin, member, recipient)
        from_addr = format_address(team.displayname,
                                   config.canonical.noreply_from_address)
        subject = "Invitation for %s to join" % member.name
        return cls(subject,
                   "membership-invitation.txt",
                   recipients,
                   from_addr,
                   "team-membership-invitation",
                   member,
                   team,
                   membership.proposed_by,
                   membership=membership)
Esempio n. 4
0
 def reportUserEmails(self):
     self.output.write('= PPA user emails =\n')
     people_to_email = set()
     for ppa in self.ppas:
         people_to_email.update(get_recipients(ppa.owner))
     sorted_people_to_email = sorted(people_to_email,
                                     key=operator.attrgetter('name'))
     for user in sorted_people_to_email:
         line = u"%s | %s | %s\n" % (user.name, user.displayname,
                                     user.preferredemail.email)
         self.output.write(line.encode('utf-8'))
     self.output.write('\n')
Esempio n. 5
0
 def reportUserEmails(self):
     self.output.write('= PPA user emails =\n')
     people_to_email = set()
     for ppa in self.ppas:
         people_to_email.update(get_recipients(ppa.owner))
     sorted_people_to_email = sorted(
         people_to_email, key=operator.attrgetter('name'))
     for user in sorted_people_to_email:
         line = u"%s | %s | %s\n" % (
             user.name, user.displayname, user.preferredemail.email)
         self.output.write(line.encode('utf-8'))
     self.output.write('\n')
Esempio n. 6
0
def add_recipient(recipients, person, reason_factory, logger=None):
    # Circular import.
    from lp.registry.model.person import get_recipients

    if person is None:
        return
    for recipient in get_recipients(person):
        if recipient not in recipients:
            debug(
                logger, "Adding recipient: '%s'" %
                format_address_for_person(recipient))
            reason = reason_factory(person, recipient)
            recipients[recipient] = reason
Esempio n. 7
0
def get_contact_email_addresses(person):
    """Return a set of email addresses to contact this Person.

    In general, it is better to use lp.registry.model.person.get_recipients
    instead.
    """
    # Need to remove the security proxy of the email address because the
    # logged in user may not have permission to see it.
    from zope.security.proxy import removeSecurityProxy
    # Circular imports force this import.
    from lp.registry.model.person import get_recipients
    return set(
        str(removeSecurityProxy(mail_person).preferredemail.email)
        for mail_person in get_recipients(person))
 def remove(self, persons):
     """See `INotificationRecipientSet`."""
     from zope.security.proxy import removeSecurityProxy
     from lp.registry.model.person import get_recipients
     if IPerson.providedBy(persons):
         persons = [persons]
     for person in persons:
         assert IPerson.providedBy(person), (
             'You can only remove() an IPerson: %r' % person)
         if person in self._personToRationale:
             del self._personToRationale[person]
         for removed_person in get_recipients(person):
             # Bypass zope's security because IEmailAddress.email is
             # not public.
             preferred_email = removeSecurityProxy(
                 removed_person.preferredemail)
             email = str(preferred_email.email)
             self._receiving_people.discard((email, removed_person))
             del self._emailToPerson[email]
Esempio n. 9
0
 def remove(self, persons):
     """See `INotificationRecipientSet`."""
     from zope.security.proxy import removeSecurityProxy
     from lp.registry.model.person import get_recipients
     if IPerson.providedBy(persons):
         persons = [persons]
     for person in persons:
         assert IPerson.providedBy(person), (
             'You can only remove() an IPerson: %r' % person)
         if person in self._personToRationale:
             del self._personToRationale[person]
         for removed_person in get_recipients(person):
             # Bypass zope's security because IEmailAddress.email is
             # not public.
             preferred_email = removeSecurityProxy(
                 removed_person.preferredemail)
             email = str(preferred_email.email)
             self._receiving_people.discard((email, removed_person))
             del self._emailToPerson[email]
Esempio n. 10
0
 def forSelfRenewal(cls, member, team, dateexpires):
     """Create a mailer for notifying about a self-renewal."""
     assert team.renewal_policy == TeamMembershipRenewalPolicy.ONDEMAND
     template_name = "membership-member-renewed.txt"
     subject = "%s extended their membership" % member.name
     recipients = OrderedDict()
     for admin in team.adminmembers:
         for recipient in get_recipients(admin):
             recipients[recipient] = TeamMembershipRecipientReason.forAdmin(
                 admin, team, recipient)
     extra_params = {"dateexpires": dateexpires.strftime("%Y-%m-%d")}
     from_addr = format_address(team.displayname,
                                config.canonical.noreply_from_address)
     return cls(subject,
                template_name,
                recipients,
                from_addr,
                "team-membership-renewed",
                member,
                team,
                None,
                extra_params=extra_params)
Esempio n. 11
0
    def forExpiringMembership(cls, member, team, dateexpires):
        """Create a mailer for warning about expiring membership."""
        membership = getUtility(ITeamMembershipSet).getByPersonAndTeam(
            member, team)
        assert membership is not None
        if member.is_team:
            target = member.teamowner
            template_name = "membership-expiration-warning-bulk.txt"
            subject = "%s will expire soon from %s" % (member.name, team.name)
        else:
            target = member
            template_name = "membership-expiration-warning-personal.txt"
            subject = "Your membership in %s is about to expire" % team.name

        if team.renewal_policy == TeamMembershipRenewalPolicy.ONDEMAND:
            how_to_renew = ("If you want, you can renew this membership at\n"
                            "<%s/+expiringmembership/%s>" %
                            (canonical_url(member), team.name))
        elif not membership.canChangeExpirationDate(target):
            admins_names = []
            admins = team.getDirectAdministrators()
            assert admins.count() >= 1
            if admins.count() == 1:
                admin = admins[0]
                how_to_renew = (
                    "To prevent this membership from expiring, you should "
                    "contact the\nteam's administrator, %s.\n<%s>" %
                    (admin.unique_displayname, canonical_url(admin)))
            else:
                for admin in admins:
                    admins_names.append(
                        "%s <%s>" %
                        (admin.unique_displayname, canonical_url(admin)))

                how_to_renew = (
                    "To prevent this membership from expiring, you should "
                    "get in touch\nwith one of the team's administrators:\n")
                how_to_renew += "\n".join(admins_names)
        else:
            how_to_renew = (
                "To stay a member of this team you should extend your "
                "membership at\n<%s/+member/%s>" %
                (canonical_url(team), member.name))

        recipients = OrderedDict()
        for recipient in get_recipients(target):
            recipients[recipient] = TeamMembershipRecipientReason.forMember(
                member, team, recipient)

        formatter = DurationFormatterAPI(dateexpires - datetime.now(pytz.UTC))
        extra_params = {
            "how_to_renew": how_to_renew,
            "expiration_date": dateexpires.strftime("%Y-%m-%d"),
            "approximate_duration": formatter.approximateduration(),
        }

        from_addr = format_address(team.displayname,
                                   config.canonical.noreply_from_address)
        return cls(subject,
                   template_name,
                   recipients,
                   from_addr,
                   "team-membership-expiration-warning",
                   member,
                   team,
                   membership.proposed_by,
                   membership=membership,
                   extra_params=extra_params,
                   wrap=False,
                   force_wrap=False)
Esempio n. 12
0
    def forMembershipStatusChange(cls, member, team, reviewer, old_status,
                                  new_status, last_change_comment):
        """Create a mailer for a membership status change."""
        template_name = 'membership-statuschange'
        notification_type = 'team-membership-change'
        subject = ('Membership change: %(member)s in %(team)s' % {
            'member': member.name,
            'team': team.name
        })
        if new_status == TeamMembershipStatus.EXPIRED:
            template_name = 'membership-expired'
            notification_type = 'team-membership-expired'
            subject = '%s expired from team' % member.name
        elif (new_status == TeamMembershipStatus.APPROVED
              and old_status != TeamMembershipStatus.ADMIN):
            if old_status == TeamMembershipStatus.INVITED:
                template_name = 'membership-invitation-accepted'
                notification_type = 'team-membership-invitation-accepted'
                subject = ('Invitation to %s accepted by %s' %
                           (member.name, reviewer.name))
            elif old_status == TeamMembershipStatus.PROPOSED:
                subject = '%s approved by %s' % (member.name, reviewer.name)
            else:
                subject = '%s added by %s' % (member.name, reviewer.name)
        elif new_status == TeamMembershipStatus.INVITATION_DECLINED:
            template_name = 'membership-invitation-declined'
            notification_type = 'team-membership-invitation-declined'
            subject = ('Invitation to %s declined by %s' %
                       (member.name, reviewer.name))
        elif new_status == TeamMembershipStatus.DEACTIVATED:
            subject = '%s deactivated by %s' % (member.name, reviewer.name)
        elif new_status == TeamMembershipStatus.ADMIN:
            subject = '%s made admin by %s' % (member.name, reviewer.name)
        elif new_status == TeamMembershipStatus.DECLINED:
            subject = '%s declined by %s' % (member.name, reviewer.name)
        else:
            # Use the default template and subject.
            pass
        template_name += "-%(recipient_class)s.txt"

        if last_change_comment:
            comment = "\n%s said:\n %s\n" % (reviewer.displayname,
                                             last_change_comment.strip())
        else:
            comment = ""

        recipients = OrderedDict()
        if reviewer != member:
            for recipient in get_recipients(member):
                if member.is_team:
                    recipient_class = "bulk"
                else:
                    recipient_class = "personal"
                recipients[recipient] = (
                    TeamMembershipRecipientReason.forMember(
                        member,
                        team,
                        recipient,
                        recipient_class=recipient_class))
        # Don't send admin notifications for open teams: they're
        # unrestricted, so notifications on join/leave do not help the
        # admins.
        if team.membership_policy != TeamMembershipPolicy.OPEN:
            for admin in team.adminmembers:
                for recipient in get_recipients(admin):
                    # The new member may also be a team admin; don't send
                    # two notifications in that case.
                    if recipient not in recipients:
                        recipients[recipient] = (
                            TeamMembershipRecipientReason.forAdmin(
                                admin, team, recipient,
                                recipient_class="bulk"))

        extra_params = {
            "old_status": old_status,
            "new_status": new_status,
            "comment": comment,
        }
        from_addr = format_address(team.displayname,
                                   config.canonical.noreply_from_address)
        return cls(subject,
                   template_name,
                   recipients,
                   from_addr,
                   notification_type,
                   member,
                   team,
                   reviewer,
                   extra_params=extra_params)
Esempio n. 13
0
 def forTeamJoin(cls, member, team):
     """Create a mailer for notifying about a new member joining a team."""
     membership = getUtility(ITeamMembershipSet).getByPersonAndTeam(
         member, team)
     assert membership is not None
     subject = None
     template_name = None
     notification_type = "team-membership-new"
     recipients = OrderedDict()
     reviewer = membership.proposed_by
     if reviewer != member and membership.status in [
             TeamMembershipStatus.APPROVED, TeamMembershipStatus.ADMIN
     ]:
         reviewer = membership.reviewed_by
         # Somebody added this person as a member, we better send a
         # notification to the person too.
         if member.is_team:
             template_name = "new-member-notification-for-teams.txt"
             subject = "%s joined %s" % (member.name, team.name)
         else:
             template_name = "new-member-notification.txt"
             subject = "You have been added to %s" % team.name
         for recipient in get_recipients(member):
             recipients[recipient] = (
                 TeamMembershipRecipientReason.forNewMember(
                     member,
                     team,
                     recipient,
                     subject=subject,
                     template_name=template_name))
     # Open teams do not notify admins about new members.
     if team.membership_policy != TeamMembershipPolicy.OPEN:
         reply_to = None
         if membership.status in [
                 TeamMembershipStatus.APPROVED, TeamMembershipStatus.ADMIN
         ]:
             template_name = "new-member-notification-for-admins.txt"
             subject = "%s joined %s" % (member.name, team.name)
         elif membership.status == TeamMembershipStatus.PROPOSED:
             # In the UI, a user can only propose themselves or a team
             # they admin.  Some users of the REST API have a workflow
             # where they propose users that are designated as undergoing
             # mentorship (Bug 498181).
             if reviewer != member:
                 reply_to = reviewer.preferredemail.email
                 template_name = (
                     "pending-membership-approval-for-third-party.txt")
             else:
                 reply_to = member.preferredemail.email
                 template_name = "pending-membership-approval.txt"
             notification_type = "team-membership-pending"
             subject = "%s wants to join" % member.name
         else:
             raise AssertionError("Unexpected membership status: %s" %
                                  membership.status)
         for admin in team.adminmembers:
             for recipient in get_recipients(admin):
                 # The new member may also be a team admin; don't send
                 # two notifications in that case.
                 if recipient not in recipients:
                     if recipient == team.teamowner:
                         reason_factory = (
                             TeamMembershipRecipientReason.forOwner)
                     else:
                         reason_factory = (
                             TeamMembershipRecipientReason.forAdmin)
                     recipients[recipient] = reason_factory(
                         admin,
                         team,
                         recipient,
                         subject=subject,
                         template_name=template_name,
                         reply_to=reply_to)
     from_addr = format_address(team.displayname,
                                config.canonical.noreply_from_address)
     return cls(subject,
                template_name,
                recipients,
                from_addr,
                notification_type,
                member,
                team,
                membership.proposed_by,
                membership=membership)
def construct_email_notifications(bug_notifications):
    """Construct an email from a list of related bug notifications.

    The person and bug has to be the same for all notifications, and
    there can be only one comment.
    """
    first_notification = bug_notifications[0]
    bug = first_notification.bug
    actor = first_notification.message.owner
    subject = first_notification.message.subject

    comment = None
    references = []
    text_notifications = []
    old_values = {}
    new_values = {}

    for notification in bug_notifications:
        assert notification.bug == bug, bug.id
        assert notification.message.owner == actor, actor.id
        if notification.is_comment:
            assert comment is None, (
                "Only one of the notifications is allowed to be a comment.")
            comment = notification.message
        else:
            key = get_activity_key(notification)
            if key is not None:
                if key not in old_values:
                    old_values[key] = notification.activity.oldvalue
                new_values[key] = notification.activity.newvalue

    recipients = {}
    filtered_notifications = []
    omitted_notifications = []
    for notification in bug_notifications:
        key = get_activity_key(notification)
        if (notification.is_comment or key is None
                or key == 'removed_subscriber'
                or old_values[key] != new_values[key]):
            # We will report this notification.
            filtered_notifications.append(notification)
            for subscription_source in notification.recipients:
                for recipient in get_recipients(subscription_source.person):
                    # The subscription_source.person may be a person or a
                    # team.  The get_recipients function gives us everyone
                    # who should actually get an email for that person.
                    # If subscription_source.person is a person or a team
                    # with a preferred email address, then the people to
                    # be emailed will only be subscription_source.person.
                    # However, if it is a team without a preferred email
                    # address, then this list will be the people and teams
                    # that comprise the team, transitively, stopping the walk
                    # at each person and at each team with a preferred email
                    # address.
                    sources_for_person = recipients.get(recipient)
                    if sources_for_person is None:
                        sources_for_person = []
                        recipients[recipient] = sources_for_person
                    sources_for_person.append(subscription_source)
        else:
            omitted_notifications.append(notification)

    # If the actor does not want self-generated bug notifications, remove the
    # actor now.
    if not actor.selfgenerated_bugnotifications:
        recipients.pop(actor, None)

    if bug.duplicateof is not None:
        text_notifications.append(
            '*** This bug is a duplicate of bug %d ***\n    %s' %
            (bug.duplicateof.id, canonical_url(bug.duplicateof)))

    if comment is not None:
        if comment == bug.initial_message:
            subject, text = generate_bug_add_email(bug)
        else:
            text = comment.text_contents
        text_notifications.append(text)

        msgid = comment.rfc822msgid
        email_date = comment.datecreated

        reference = comment.parent
        while reference is not None:
            references.insert(0, reference.rfc822msgid)
            reference = reference.parent
    else:
        msgid = first_notification.message.rfc822msgid
        email_date = first_notification.message.datecreated

    for notification in filtered_notifications:
        if notification.message == comment:
            # Comments were just handled in the previous if block.
            continue
        text = notification.message.text_contents.rstrip()
        text_notifications.append(text)

    if bug.initial_message.rfc822msgid not in references:
        # Ensure that references contain the initial message ID
        references.insert(0, bug.initial_message.rfc822msgid)

    # At this point we've got the data we need to construct the
    # messages. Now go ahead and actually do that.
    messages = []
    mail_wrapper = MailWrapper(width=72)
    content = '\n\n'.join(text_notifications)
    from_address = get_bugmail_from_address(actor, bug)
    bug_notification_builder = BugNotificationBuilder(bug, actor)
    recipients = getUtility(IBugNotificationSet).getRecipientFilterData(
        bug, recipients, filtered_notifications)
    sorted_recipients = sorted(recipients.items(),
                               key=lambda t: t[0].preferredemail.email)

    for email_person, data in sorted_recipients:
        address = str(email_person.preferredemail.email)
        # Choosing the first source is a bit arbitrary, but it
        # is simple for the user to understand.  We may want to reconsider
        # this in the future.
        reason = data['sources'][0].reason_body
        rationale = data['sources'][0].reason_header

        if data['filter descriptions']:
            # There are some filter descriptions as well. Add them to
            # the email body.
            filters_text = u"\nMatching subscriptions: %s" % ", ".join(
                data['filter descriptions'])
        else:
            filters_text = u""

        # In the rare case of a bug with no bugtasks, we can't generate the
        # subscription management URL so just leave off the subscription
        # management message entirely.
        if len(bug.bugtasks):
            bug_url = canonical_url(bug.bugtasks[0])
            notification_url = bug_url + '/+subscriptions'
            subscriptions_message = (
                "To manage notifications about this bug go to:\n%s" %
                notification_url)
        else:
            subscriptions_message = ''

        data_wrapper = MailWrapper(width=72, indent='  ')
        body_data = {
            'content': mail_wrapper.format(content),
            'bug_title': data_wrapper.format(bug.title),
            'bug_url': canonical_url(bug),
            'notification_rationale': mail_wrapper.format(reason),
            'subscription_filters': filters_text,
            'subscriptions_message': subscriptions_message,
        }

        # If the person we're sending to receives verbose notifications
        # we include the description and status of the bug in the email
        # footer.
        if email_person.verbose_bugnotifications:
            email_template = 'bug-notification-verbose.txt'
            body_data['bug_description'] = data_wrapper.format(bug.description)

            status_base = "Status in %s:\n  %s"
            status_strings = []
            for bug_task in bug.bugtasks:
                status_strings.append(
                    status_base %
                    (bug_task.target.title, bug_task.status.title))

            body_data['bug_statuses'] = "\n".join(status_strings)
        else:
            email_template = 'bug-notification.txt'

        body_template = get_email_template(email_template, 'bugs')
        body = (body_template % body_data).strip()
        msg = bug_notification_builder.build(
            from_address,
            address,
            body,
            subject,
            email_date,
            rationale,
            references,
            msgid,
            filters=data['filter descriptions'])
        messages.append(msg)

    return filtered_notifications, omitted_notifications, messages
Esempio n. 15
0
def construct_email_notifications(bug_notifications):
    """Construct an email from a list of related bug notifications.

    The person and bug has to be the same for all notifications, and
    there can be only one comment.
    """
    first_notification = bug_notifications[0]
    bug = first_notification.bug
    actor = first_notification.message.owner
    subject = first_notification.message.subject

    comment = None
    references = []
    text_notifications = []
    old_values = {}
    new_values = {}

    for notification in bug_notifications:
        assert notification.bug == bug, bug.id
        assert notification.message.owner == actor, actor.id
        if notification.is_comment:
            assert comment is None, "Only one of the notifications is allowed to be a comment."
            comment = notification.message
        else:
            key = get_activity_key(notification)
            if key is not None:
                if key not in old_values:
                    old_values[key] = notification.activity.oldvalue
                new_values[key] = notification.activity.newvalue

    recipients = {}
    filtered_notifications = []
    omitted_notifications = []
    for notification in bug_notifications:
        key = get_activity_key(notification)
        if notification.is_comment or key is None or key == "removed_subscriber" or old_values[key] != new_values[key]:
            # We will report this notification.
            filtered_notifications.append(notification)
            for subscription_source in notification.recipients:
                for recipient in get_recipients(subscription_source.person):
                    # The subscription_source.person may be a person or a
                    # team.  The get_recipients function gives us everyone
                    # who should actually get an email for that person.
                    # If subscription_source.person is a person or a team
                    # with a preferred email address, then the people to
                    # be emailed will only be subscription_source.person.
                    # However, if it is a team without a preferred email
                    # address, then this list will be the people and teams
                    # that comprise the team, transitively, stopping the walk
                    # at each person and at each team with a preferred email
                    # address.
                    sources_for_person = recipients.get(recipient)
                    if sources_for_person is None:
                        sources_for_person = []
                        recipients[recipient] = sources_for_person
                    sources_for_person.append(subscription_source)
        else:
            omitted_notifications.append(notification)

    # If the actor does not want self-generated bug notifications, remove the
    # actor now.
    if not actor.selfgenerated_bugnotifications:
        recipients.pop(actor, None)

    if bug.duplicateof is not None:
        text_notifications.append(
            "*** This bug is a duplicate of bug %d ***\n    %s" % (bug.duplicateof.id, canonical_url(bug.duplicateof))
        )

    if comment is not None:
        if comment == bug.initial_message:
            subject, text = generate_bug_add_email(bug)
        else:
            text = comment.text_contents
        text_notifications.append(text)

        msgid = comment.rfc822msgid
        email_date = comment.datecreated

        reference = comment.parent
        while reference is not None:
            references.insert(0, reference.rfc822msgid)
            reference = reference.parent
    else:
        msgid = first_notification.message.rfc822msgid
        email_date = first_notification.message.datecreated

    for notification in filtered_notifications:
        if notification.message == comment:
            # Comments were just handled in the previous if block.
            continue
        text = notification.message.text_contents.rstrip()
        text_notifications.append(text)

    if bug.initial_message.rfc822msgid not in references:
        # Ensure that references contain the initial message ID
        references.insert(0, bug.initial_message.rfc822msgid)

    # At this point we've got the data we need to construct the
    # messages. Now go ahead and actually do that.
    messages = []
    mail_wrapper = MailWrapper(width=72)
    content = "\n\n".join(text_notifications)
    from_address = get_bugmail_from_address(actor, bug)
    bug_notification_builder = BugNotificationBuilder(bug, actor)
    recipients = getUtility(IBugNotificationSet).getRecipientFilterData(bug, recipients, filtered_notifications)
    sorted_recipients = sorted(recipients.items(), key=lambda t: t[0].preferredemail.email)

    for email_person, data in sorted_recipients:
        address = str(email_person.preferredemail.email)
        # Choosing the first source is a bit arbitrary, but it
        # is simple for the user to understand.  We may want to reconsider
        # this in the future.
        reason = data["sources"][0].reason_body
        rationale = data["sources"][0].reason_header

        if data["filter descriptions"]:
            # There are some filter descriptions as well. Add them to
            # the email body.
            filters_text = u"\nMatching subscriptions: %s" % ", ".join(data["filter descriptions"])
        else:
            filters_text = u""

        # In the rare case of a bug with no bugtasks, we can't generate the
        # subscription management URL so just leave off the subscription
        # management message entirely.
        if len(bug.bugtasks):
            bug_url = canonical_url(bug.bugtasks[0])
            notification_url = bug_url + "/+subscriptions"
            subscriptions_message = "To manage notifications about this bug go to:\n%s" % notification_url
        else:
            subscriptions_message = ""

        data_wrapper = MailWrapper(width=72, indent="  ")
        body_data = {
            "content": mail_wrapper.format(content),
            "bug_title": data_wrapper.format(bug.title),
            "bug_url": canonical_url(bug),
            "notification_rationale": mail_wrapper.format(reason),
            "subscription_filters": filters_text,
            "subscriptions_message": subscriptions_message,
        }

        # If the person we're sending to receives verbose notifications
        # we include the description and status of the bug in the email
        # footer.
        if email_person.verbose_bugnotifications:
            email_template = "bug-notification-verbose.txt"
            body_data["bug_description"] = data_wrapper.format(bug.description)

            status_base = "Status in %s:\n  %s"
            status_strings = []
            for bug_task in bug.bugtasks:
                status_strings.append(status_base % (bug_task.target.title, bug_task.status.title))

            body_data["bug_statuses"] = "\n".join(status_strings)
        else:
            email_template = "bug-notification.txt"

        body_template = get_email_template(email_template, "bugs")
        body = (body_template % body_data).strip()
        msg = bug_notification_builder.build(
            from_address,
            address,
            body,
            subject,
            email_date,
            rationale,
            references,
            msgid,
            filters=data["filter descriptions"],
        )
        messages.append(msg)

    return filtered_notifications, omitted_notifications, messages
Esempio n. 16
0
def send_bug_details_to_new_bug_subscribers(bug,
                                            previous_subscribers,
                                            current_subscribers,
                                            subscribed_by=None,
                                            event_creator=None):
    """Send an email containing full bug details to new bug subscribers.

    This function is designed to handle situations where bugtasks get
    reassigned to new products or sourcepackages, and the new bug subscribers
    need to be notified of the bug.

    A boolean is returned indicating whether any emails were sent.
    """
    prev_subs_set = set(previous_subscribers)
    cur_subs_set = set(current_subscribers)
    new_subs = cur_subs_set.difference(prev_subs_set)

    if (event_creator is not None
            and not event_creator.selfgenerated_bugnotifications):
        new_subs.discard(event_creator)

    to_persons = set()
    for new_sub in new_subs:
        to_persons.update(get_recipients(new_sub))

    if not to_persons:
        return False

    from_addr = format_address(
        'Launchpad Bug Tracker',
        "%s@%s" % (bug.id, config.launchpad.bugs_domain))
    # Now's a good a time as any for this email; don't use the original
    # reported date for the bug as it will just confuse mailer and
    # recipient.
    email_date = datetime.datetime.now()

    # The new subscriber email is effectively the initial message regarding
    # a new bug. The bug's initial message is used in the References
    # header to establish the message's context in the email client.
    references = [bug.initial_message.rfc822msgid]
    recipients = bug.getBugNotificationRecipients()

    bug_notification_builder = BugNotificationBuilder(bug, event_creator)
    for to_person in sorted(to_persons):
        reason, rationale = recipients.getReason(
            str(removeSecurityProxy(to_person).preferredemail.email))
        subject, contents = generate_bug_add_email(bug,
                                                   new_recipients=True,
                                                   subscribed_by=subscribed_by,
                                                   reason=reason,
                                                   event_creator=event_creator)
        msg = bug_notification_builder.build(from_addr,
                                             to_person,
                                             contents,
                                             subject,
                                             email_date,
                                             rationale=rationale,
                                             references=references)
        sendmail(msg)

    return True