def _sendFailureNotification(self, notify_owner, log):
        """Send a failure notification to the distribution's mirror admins and
        to the mirror owner, in case notify_owner is True.
        """
        template = get_email_template(
            'notify-mirror-owner.txt', app='registry')
        fromaddress = format_address(
            "Launchpad Mirror Prober", config.canonical.noreply_from_address)

        replacements = {
            'distro': self.distribution.title,
            'mirror_name': self.name,
            'mirror_url': canonical_url(self),
            'log_snippet': "\n".join(log.split('\n')[:20]),
            'logfile_url': self.last_probe_record.log_file.http_url}
        message = template % replacements
        subject = "Launchpad: Verification of %s failed" % self.name

        mirror_admin_address = get_contact_email_addresses(
            self.distribution.mirror_admin)
        simple_sendmail(fromaddress, mirror_admin_address, subject, message)

        if notify_owner:
            owner_address = get_contact_email_addresses(self.owner)
            if len(owner_address) > 0:
                simple_sendmail(fromaddress, owner_address, subject, message)
Exemplo n.º 2
0
 def send(self):
     """Send a message to the user about the product's licence."""
     if not self.needs_notification(self.product):
         # The project has a common licence.
         return False
     maintainer = self.product.owner
     if maintainer.is_team:
         user_address = maintainer.getTeamAdminsEmailAddresses()
     else:
         user_address = format_address_for_person(maintainer)
     from_address = format_address("Launchpad",
                                   config.canonical.noreply_from_address)
     commercial_address = format_address('Commercial',
                                         '*****@*****.**')
     substitutions = dict(
         user_displayname=maintainer.displayname,
         user_name=maintainer.name,
         product_name=self.product.name,
         product_url=canonical_url(self.product),
         commercial_use_expiration=self.getCommercialUseMessage(),
     )
     # Email the user about licence policy.
     subject = ("Licence information for %(product_name)s "
                "in Launchpad" % substitutions)
     template = get_email_template(self.getTemplateName(), app='registry')
     message = template % substitutions
     simple_sendmail(from_address,
                     user_address,
                     subject,
                     message,
                     headers={'Reply-To': commercial_address})
     # Inform that Launchpad recognized the licence change.
     self._addLicenseChangeToReviewWhiteboard()
     return True
Exemplo n.º 3
0
 def send(self):
     """Send a message to the user about the product's licence."""
     if not self.needs_notification(self.product):
         # The project has a common licence.
         return False
     maintainer = self.product.owner
     if maintainer.is_team:
         user_address = maintainer.getTeamAdminsEmailAddresses()
     else:
         user_address = format_address_for_person(maintainer)
     from_address = format_address(
         "Launchpad", config.canonical.noreply_from_address)
     commercial_address = format_address(
         'Commercial', '*****@*****.**')
     substitutions = dict(
         user_displayname=maintainer.displayname,
         user_name=maintainer.name,
         product_name=self.product.name,
         product_url=canonical_url(self.product),
         commercial_use_expiration=self.getCommercialUseMessage(),
         )
     # Email the user about licence policy.
     subject = (
         "Licence information for %(product_name)s "
         "in Launchpad" % substitutions)
     template = get_email_template(
         self.getTemplateName(), app='registry')
     message = template % substitutions
     simple_sendmail(
         from_address, user_address,
         subject, message, headers={'Reply-To': commercial_address})
     # Inform that Launchpad recognized the licence change.
     self._addLicenseChangeToReviewWhiteboard()
     return True
Exemplo n.º 4
0
def notify_message_held(message_approval, event):
    """Send a notification of a message hold to all team administrators."""
    message_details = getAdapter(message_approval, IHeldMessageDetails)
    team = message_approval.mailing_list.team
    from_address = format_address(team.displayname,
                                  config.canonical.noreply_from_address)
    subject = ('New mailing list message requiring approval for %s' %
               team.displayname)
    template = get_email_template('new-held-message.txt', app='registry')

    # Most of the replacements are the same for everyone.
    replacements = {
        'subject': message_details.subject,
        'author_name': message_details.author.displayname,
        'author_url': canonical_url(message_details.author),
        'date': message_details.date,
        'message_id': message_details.message_id,
        'review_url': '%s/+mailinglist-moderate' % canonical_url(team),
        'team': team.displayname,
    }

    # Don't wrap the paragraph with the url.
    def wrap_function(paragraph):
        return (paragraph.startswith('http:')
                or paragraph.startswith('https:'))

    # Send one message to every team administrator.
    person_set = getUtility(IPersonSet)
    for address in team.getTeamAdminsEmailAddresses():
        user = person_set.getByEmail(address)
        replacements['user'] = user.displayname
        body = MailWrapper(72).format(template % replacements,
                                      force_wrap=True,
                                      wrap_func=wrap_function)
        simple_sendmail(from_address, address, subject, body)
Exemplo n.º 5
0
    def _sendFailureNotification(self, notify_owner, log):
        """Send a failure notification to the distribution's mirror admins and
        to the mirror owner, in case notify_owner is True.
        """
        template = get_email_template('notify-mirror-owner.txt',
                                      app='registry')
        fromaddress = format_address("Launchpad Mirror Prober",
                                     config.canonical.noreply_from_address)

        replacements = {
            'distro': self.distribution.title,
            'mirror_name': self.name,
            'mirror_url': canonical_url(self),
            'log_snippet': "\n".join(log.split('\n')[:20]),
            'logfile_url': self.last_probe_record.log_file.http_url
        }
        message = template % replacements
        subject = "Launchpad: Verification of %s failed" % self.name

        mirror_admin_address = get_contact_email_addresses(
            self.distribution.mirror_admin)
        simple_sendmail(fromaddress, mirror_admin_address, subject, message)

        if notify_owner:
            owner_address = get_contact_email_addresses(self.owner)
            if len(owner_address) > 0:
                simple_sendmail(fromaddress, owner_address, subject, message)
Exemplo n.º 6
0
def notify_message_held(message_approval, event):
    """Send a notification of a message hold to all team administrators."""
    message_details = getAdapter(message_approval, IHeldMessageDetails)
    team = message_approval.mailing_list.team
    from_address = format_address(
        team.displayname, config.canonical.noreply_from_address)
    subject = (
        'New mailing list message requiring approval for %s'
        % team.displayname)
    template = get_email_template('new-held-message.txt', app='registry')

    # Most of the replacements are the same for everyone.
    replacements = {
        'subject': message_details.subject,
        'author_name': message_details.author.displayname,
        'author_url': canonical_url(message_details.author),
        'date': message_details.date,
        'message_id': message_details.message_id,
        'review_url': '%s/+mailinglist-moderate' % canonical_url(team),
        'team': team.displayname,
        }

    # Don't wrap the paragraph with the url.
    def wrap_function(paragraph):
        return (paragraph.startswith('http:') or
                paragraph.startswith('https:'))

    # Send one message to every team administrator.
    person_set = getUtility(IPersonSet)
    for address in team.getTeamAdminsEmailAddresses():
        user = person_set.getByEmail(address)
        replacements['user'] = user.displayname
        body = MailWrapper(72).format(
            template % replacements, force_wrap=True, wrap_func=wrap_function)
        simple_sendmail(from_address, address, subject, body)
Exemplo n.º 7
0
 def _send_email(self, from_name, subject, message, headers=None):
     """Send an email to this token's email address."""
     from_address = format_address(
         from_name, config.canonical.noreply_from_address)
     to_address = str(self.email)
     simple_sendmail(
         from_address, to_address, subject, message,
         headers=headers, bulk=False)
Exemplo n.º 8
0
def send_team_email(from_addr, address, subject, template, replacements,
                    rationale, headers=None):
    """Send a team message with a rationale."""
    if headers is None:
        headers = {}
    body = MailWrapper().format(template % replacements, force_wrap=True)
    footer = "-- \n%s" % rationale
    message = '%s\n\n%s' % (body, footer)
    simple_sendmail(from_addr, address, subject, message, headers)
Exemplo n.º 9
0
def notify_new_ppa_subscription(subscription, event):
    """Notification that a new PPA subscription can be activated."""
    non_active_subscribers = subscription.getNonActiveSubscribers()

    archive = subscription.archive

    # We don't send notification emails for some PPAs, particularly those that
    # are purchased via the Software Centre, so that its users do not have to
    # learn about Launchpad.
    if archive.suppress_subscription_notifications:
        return

    registrant_name = subscription.registrant.displayname
    ppa_displayname = archive.displayname
    ppa_reference = "ppa:%s/%s" % (
        archive.owner.name, archive.name)
    ppa_description = archive.description
    subject = 'PPA access granted for ' + ppa_displayname

    template = get_email_template('ppa-subscription-new.txt', app='soyuz')

    for person, preferred_email in non_active_subscribers:
        to_address = [preferred_email.email]
        root = getUtility(ILaunchpadRoot)
        recipient_subscriptions_url = "%s~/+archivesubscriptions" % (
            canonical_url(root))
        description_blurb = '.'
        if ppa_description is not None and ppa_description != '':
            description_blurb = (
                ' and has the following description:\n\n%s' % ppa_description)
        replacements = {
            'recipient_name': person.displayname,
            'registrant_name': registrant_name,
            'registrant_profile_url': canonical_url(subscription.registrant),
            'ppa_displayname': ppa_displayname,
            'ppa_reference': ppa_reference,
            'ppa_description_blurb': description_blurb,
            'recipient_subscriptions_url': recipient_subscriptions_url,
            }
        body = MailWrapper(72).format(template % replacements,
                                      force_wrap=True)

        from_address = format_address(
            registrant_name, config.canonical.noreply_from_address)

        headers = {
            'Sender': config.canonical.bounce_address,
            }

        # If the registrant has a preferred email, then use it for the
        # Reply-To.
        if subscription.registrant.preferredemail:
            headers['Reply-To'] = format_address(
                registrant_name,
                subscription.registrant.preferredemail.email)

        simple_sendmail(from_address, to_address, subject, body, headers)
Exemplo n.º 10
0
 def sendEmailToMaintainer(self, template_name, subject, from_address):
     """See `IProductNotificationJob`."""
     email_template = get_email_template(
         "%s.txt" % template_name, app='registry')
     for address in self.recipients.getEmails():
         body, headers = self.getBodyAndHeaders(
             email_template, address, self.reply_to)
         simple_sendmail(from_address, address, subject, body, headers)
     log.debug("%s has sent email to the maintainer of %s.",
         self.log_name, self.product.name)
Exemplo n.º 11
0
 def send(self, logger=None):
     """See `IPersonNotification`."""
     if not self.can_send:
         raise AssertionError("Can't send a notification to a person without an email.")
     to_addresses = self.to_addresses
     if logger:
         logger.info("Sending notification to %r." % to_addresses)
     from_addr = config.canonical.bounce_address
     simple_sendmail(from_addr, to_addresses, self.subject, self.body)
     self.date_emailed = datetime.now(pytz.timezone("UTC"))
Exemplo n.º 12
0
 def sendEmailToMaintainer(self, template_name, subject, from_address):
     """See `IProductNotificationJob`."""
     email_template = get_email_template(
         "%s.txt" % template_name, app='registry')
     for address in self.recipients.getEmails():
         body, headers = self.getBodyAndHeaders(
             email_template, address, self.reply_to)
         simple_sendmail(from_address, address, subject, body, headers)
     log.debug("%s has sent email to the maintainer of %s.",
         self.log_name, self.product.name)
Exemplo n.º 13
0
 def sendHelpEmail(self, to_address):
     """Send usage help to `to_address`."""
     # Get the help text (formatted as MoinMoin markup)
     help_text = get_email_template('help.txt', app='bugs')
     help_text = reformat_wiki_text(help_text)
     # Wrap text
     mailwrapper = MailWrapper(width=72)
     help_text = mailwrapper.format(help_text)
     simple_sendmail('*****@*****.**', to_address,
                     'Launchpad Bug Tracker Email Interface Help',
                     help_text)
Exemplo n.º 14
0
 def send(self, logger=None):
     """See `IPersonNotification`."""
     if not self.can_send:
         raise AssertionError(
             "Can't send a notification to a person without an email.")
     to_addresses = self.to_addresses
     if logger:
         logger.info("Sending notification to %r." % to_addresses)
     from_addr = config.canonical.bounce_address
     simple_sendmail(from_addr, to_addresses, self.subject, self.body)
     self.date_emailed = datetime.now(pytz.timezone('UTC'))
Exemplo n.º 15
0
 def sendAdvertisementEmail(self, subject, content):
     """See ISignedCodeOfConduct."""
     assert self.owner.preferredemail
     template = open('lib/lp/registry/emailtemplates/'
                     'signedcoc-acknowledge.txt').read()
     fromaddress = format_address("Launchpad Code Of Conduct System",
                                  config.canonical.noreply_from_address)
     replacements = {'user': self.owner.displayname, 'content': content}
     message = template % replacements
     simple_sendmail(fromaddress, str(self.owner.preferredemail.email),
                     subject, message)
Exemplo n.º 16
0
 def sendHelpEmail(self, to_address):
     """Send usage help to `to_address`."""
     # Get the help text (formatted as MoinMoin markup)
     help_text = get_email_template('help.txt', app='bugs')
     help_text = reformat_wiki_text(help_text)
     # Wrap text
     mailwrapper = MailWrapper(width=72)
     help_text = mailwrapper.format(help_text)
     simple_sendmail(
         '*****@*****.**', to_address,
         'Launchpad Bug Tracker Email Interface Help',
         help_text)
Exemplo n.º 17
0
def send_team_email(from_addr,
                    address,
                    subject,
                    template,
                    replacements,
                    rationale,
                    headers=None):
    """Send a team message with a rationale."""
    if headers is None:
        headers = {}
    body = MailWrapper().format(template % replacements, force_wrap=True)
    footer = "-- \n%s" % rationale
    message = '%s\n\n%s' % (body, footer)
    simple_sendmail(from_addr, address, subject, message, headers)
Exemplo n.º 18
0
 def sendAdvertisementEmail(self, subject, content):
     """See ISignedCodeOfConduct."""
     assert self.owner.preferredemail
     template = open('lib/lp/registry/emailtemplates/'
                     'signedcoc-acknowledge.txt').read()
     fromaddress = format_address(
         "Launchpad Code Of Conduct System",
         config.canonical.noreply_from_address)
     replacements = {'user': self.owner.displayname,
                     'content': content}
     message = template % replacements
     simple_sendmail(
         fromaddress, str(self.owner.preferredemail.email),
         subject, message)
Exemplo n.º 19
0
    def addRemoteComment(self, remote_bug, comment_body, rfc822msgid):
        """Push a comment to the remote DebBugs instance.

        See `ISupportsCommentPushing`.
        """
        debian_bug = self._findBug(remote_bug)

        # We set the subject to "Re: <bug subject>" in the same way that
        # a mail client would.
        subject = "Re: %s" % debian_bug.subject
        host_name = urlsplit(self.baseurl)[1]
        to_addr = "%s@%s" % (remote_bug, host_name)

        headers = {'Message-Id': rfc822msgid}

        # We str()ify to_addr since simple_sendmail expects ASCII
        # strings and gets awfully upset when it gets a unicode one.
        sent_msg_id = simple_sendmail('*****@*****.**',
                                      [str(to_addr)],
                                      subject,
                                      comment_body,
                                      headers=headers)

        # We add angle-brackets to the sent_msg_id because
        # simple_sendmail strips them out. We want to remain consistent
        # with debbugs, which uses angle-brackets in its message IDS (as
        # does Launchpad).
        return "<%s>" % sent_msg_id
Exemplo n.º 20
0
    def addRemoteComment(self, remote_bug, comment_body, rfc822msgid):
        """Push a comment to the remote DebBugs instance.

        See `ISupportsCommentPushing`.
        """
        debian_bug = self._findBug(remote_bug)

        # We set the subject to "Re: <bug subject>" in the same way that
        # a mail client would.
        subject = "Re: %s" % debian_bug.subject
        host_name = urlsplit(self.baseurl)[1]
        to_addr = "%s@%s" % (remote_bug, host_name)

        headers = {'Message-Id': rfc822msgid}

        # We str()ify to_addr since simple_sendmail expects ASCII
        # strings and gets awfully upset when it gets a unicode one.
        sent_msg_id = simple_sendmail(
            '*****@*****.**', [str(to_addr)], subject,
            comment_body, headers=headers)

        # We add angle-brackets to the sent_msg_id because
        # simple_sendmail strips them out. We want to remain consistent
        # with debbugs, which uses angle-brackets in its message IDS (as
        # does Launchpad).
        return "<%s>" % sent_msg_id
Exemplo n.º 21
0
    def notify(self):
        """Send a notification email to the given person about the export.

        If there is a failure, a copy of the email is also sent to the
        Launchpad error mailing list for debugging purposes.
        """
        if self.failure is None and self.url is not None:
            # There is no failure, so we have a full export without
            # problems.
            body = self._getSuccessEmailBody()
        elif self.failure is not None and self.url is None:
            body = self._getFailureEmailBody()
        elif self.failure is not None and self.url is not None:
            raise AssertionError(
                'We cannot have a URL for the export and a failure.')
        else:
            raise AssertionError('On success, an exported URL is expected.')

        recipients = list(get_contact_email_addresses(self.person))

        for recipient in [str(recipient) for recipient in recipients]:
            simple_sendmail(
                from_addr=config.rosetta.notification_address,
                to_addrs=[recipient],
                subject='Launchpad translation download: %s' % self.name,
                body=body)

        if self.failure is None:
            # There are no errors, so nothing else to do here.
            return

        # The export process had errors that we should notify admins about.
        try:
            admins_email_body = self._getAdminFailureNotificationEmailBody()
        except UnicodeDecodeError:
            # Unfortunately this happens sometimes: invalidly-encoded data
            # makes it into the exception description, possibly from error
            # messages printed by msgfmt.  Before we can fix that, we need to
            # know what exports suffer from this problem.
            admins_email_body = self._getUnicodeDecodeErrorEmailBody()

        simple_sendmail(
            from_addr=config.rosetta.notification_address,
            to_addrs=[config.launchpad.errors_address],
            subject=(
                'Launchpad translation download errors: %s' % self.name),
            body=admins_email_body)
Exemplo n.º 22
0
    def notify(self):
        """Send a notification email to the given person about the export.

        If there is a failure, a copy of the email is also sent to the
        Launchpad error mailing list for debugging purposes.
        """
        if self.failure is None and self.url is not None:
            # There is no failure, so we have a full export without
            # problems.
            body = self._getSuccessEmailBody()
        elif self.failure is not None and self.url is None:
            body = self._getFailureEmailBody()
        elif self.failure is not None and self.url is not None:
            raise AssertionError(
                'We cannot have a URL for the export and a failure.')
        else:
            raise AssertionError('On success, an exported URL is expected.')

        recipients = list(get_contact_email_addresses(self.person))

        for recipient in [str(recipient) for recipient in recipients]:
            simple_sendmail(from_addr=config.rosetta.notification_address,
                            to_addrs=[recipient],
                            subject='Launchpad translation download: %s' %
                            self.name,
                            body=body)

        if self.failure is None:
            # There are no errors, so nothing else to do here.
            return

        # The export process had errors that we should notify admins about.
        try:
            admins_email_body = self._getAdminFailureNotificationEmailBody()
        except UnicodeDecodeError:
            # Unfortunately this happens sometimes: invalidly-encoded data
            # makes it into the exception description, possibly from error
            # messages printed by msgfmt.  Before we can fix that, we need to
            # know what exports suffer from this problem.
            admins_email_body = self._getUnicodeDecodeErrorEmailBody()

        simple_sendmail(from_addr=config.rosetta.notification_address,
                        to_addrs=[config.launchpad.errors_address],
                        subject=('Launchpad translation download errors: %s' %
                                 self.name),
                        body=admins_email_body)
Exemplo n.º 23
0
    def run(self):
        """See `IRunnableJob`.

        Send emails to all the question recipients.
        """
        log.debug("%s will send email for question %s.", self.log_name,
                  self.question.id)
        headers = self.headers
        recipients = self.recipients
        for email in recipients.getEmails():
            reason, header = recipients.getReason(email)
            headers['X-Launchpad-Message-Rationale'] = header
            headers['X-Launchpad-Message-For'] = reason.subscriber.name
            formatted_body = self.buildBody(reason.getReason())
            simple_sendmail(self.from_address, email, self.subject,
                            formatted_body, headers)
        log.debug("%s has sent email for question %s.", self.log_name,
                  self.question.id)
Exemplo n.º 24
0
    def process(self, mail, email_addr, file_alias):
        """Process an email for the code domain.

        Emails may be converted to CodeReviewComments, and / or
        deferred to jobs to create BranchMergeProposals.
        """
        if email_addr.startswith('merge@'):
            body = get_error_message('mergedirectivenotsupported.txt')
            simple_sendmail(config.canonical.noreply_from_address,
                            [mail.get('from')],
                            'Merge directive not supported.', body)
        else:
            try:
                return self.processComment(mail, email_addr, file_alias)
            except AssertionError:
                body = get_error_message('messagemissingsubject.txt')
                simple_sendmail('*****@*****.**', [mail.get('from')],
                                'Error Creating Merge Proposal', body)
                return True
Exemplo n.º 25
0
    def process(self, mail, email_addr, file_alias):
        """Process an email for the code domain.

        Emails may be converted to CodeReviewComments, and / or
        deferred to jobs to create BranchMergeProposals.
        """
        if email_addr.startswith('merge@'):
            body = get_error_message('mergedirectivenotsupported.txt')
            simple_sendmail(
                config.canonical.noreply_from_address, [mail.get('from')],
                'Merge directive not supported.', body)
        else:
            try:
                return self.processComment(mail, email_addr, file_alias)
            except AssertionError:
                body = get_error_message('messagemissingsubject.txt')
                simple_sendmail('*****@*****.**',
                    [mail.get('from')],
                    'Error Creating Merge Proposal', body)
                return True
Exemplo n.º 26
0
    def _importEntry(self, entry):
        """Perform the import of one entry, and notify the uploader."""
        target = entry.import_into
        self.logger.info('Importing: %s' % target.title)
        (mail_subject, mail_body) = target.importFromQueue(entry, self.logger)

        if mail_subject is not None and self._shouldNotify(entry.importer):
            # A `mail_subject` of None indicates that there
            # is no notification worth sending out.
            from_email = config.rosetta.notification_address
            katie = getUtility(ILaunchpadCelebrities).katie
            if entry.importer == katie:
                # Email import state to Debian imports email.
                to_email = None
            else:
                to_email = get_contact_email_addresses(entry.importer)

            if to_email:
                text = MailWrapper().format(mail_body)
                simple_sendmail(from_email, to_email, mail_subject, text)
Exemplo n.º 27
0
    def run(self):
        """See `IRunnableJob`.

        Send emails to all the question recipients.
        """
        log.debug(
            "%s will send email for question %s.",
            self.log_name, self.question.id)
        headers = self.headers
        recipients = self.recipients
        for email in recipients.getEmails():
            rationale, header = recipients.getReason(email)
            headers['X-Launchpad-Message-Rationale'] = header
            formatted_body = self.buildBody(rationale)
            simple_sendmail(
                self.from_address, email, self.subject, formatted_body,
                headers)
        log.debug(
            "%s has sent email for question %s.",
            self.log_name, self.question.id)
Exemplo n.º 28
0
def notify_invitation_to_join_team(event):
    """Notify team admins that the team has been invited to join another team.

    The notification will include a link to a page in which any team admin can
    accept the invitation.

    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.
    """
    member = event.member
    assert member.is_team
    team = event.team
    membership = getUtility(ITeamMembershipSet).getByPersonAndTeam(
        member, team)
    assert membership is not None

    reviewer = membership.proposed_by
    admin_addrs = member.getTeamAdminsEmailAddresses()
    from_addr = format_address(team.displayname,
                               config.canonical.noreply_from_address)
    subject = 'Invitation for %s to join' % member.name
    templatename = 'membership-invitation.txt'
    template = get_email_template(templatename, app='registry')
    replacements = {
        'reviewer':
        '%s (%s)' % (reviewer.displayname, reviewer.name),
        'member':
        '%s (%s)' % (member.displayname, member.name),
        'team':
        '%s (%s)' % (team.displayname, team.name),
        'team_url':
        canonical_url(team),
        'membership_invitations_url':
        "%s/+invitation/%s" % (canonical_url(member), team.name)
    }
    for address in admin_addrs:
        recipient = getUtility(IPersonSet).getByEmail(address)
        replacements['recipient_name'] = recipient.displayname
        msg = MailWrapper().format(template % replacements, force_wrap=True)
        simple_sendmail(from_addr, address, subject, msg)
Exemplo n.º 29
0
def notify_mailinglist_activated(mailinglist, event):
    """Notification that a mailing list is available.

    All active members of a team and its subteams receive notification when
    the team's mailing list is available.
    """
    # We will use the setting of the date_activated field as a hint
    # that this list is new, and that noboby has subscribed yet.  See
    # `MailingList.transitionToStatus()` for the details.
    old_date = event.object_before_modification.date_activated
    new_date = event.object.date_activated
    list_looks_new = old_date is None and new_date is not None

    if not (list_looks_new and mailinglist.is_usable):
        return

    team = mailinglist.team
    from_address = format_address(
        team.displayname, config.canonical.noreply_from_address)
    headers = {}
    subject = "New Mailing List for %s" % team.displayname
    template = get_email_template('new-mailing-list.txt', app='registry')
    editemails_url = '%s/+editemails'

    for person in team.allmembers:
        if person.is_team or person.preferredemail is None:
            # This is either a team or a person without a preferred email, so
            # don't send a notification.
            continue
        to_address = [str(person.preferredemail.email)]
        replacements = {
            'user': person.displayname,
            'team_displayname': team.displayname,
            'team_name': team.name,
            'team_url': canonical_url(team),
            'subscribe_url': editemails_url % canonical_url(person),
            }
        body = MailWrapper(72).format(template % replacements,
                                      force_wrap=True)
        simple_sendmail(from_address, to_address, subject, body, headers)
Exemplo n.º 30
0
def notify_mailinglist_activated(mailinglist, event):
    """Notification that a mailing list is available.

    All active members of a team and its subteams receive notification when
    the team's mailing list is available.
    """
    # We will use the setting of the date_activated field as a hint
    # that this list is new, and that noboby has subscribed yet.  See
    # `MailingList.transitionToStatus()` for the details.
    old_date = event.object_before_modification.date_activated
    new_date = event.object.date_activated
    list_looks_new = old_date is None and new_date is not None

    if not (list_looks_new and mailinglist.is_usable):
        return

    team = mailinglist.team
    from_address = format_address(team.displayname,
                                  config.canonical.noreply_from_address)
    headers = {}
    subject = "New Mailing List for %s" % team.displayname
    template = get_email_template('new-mailing-list.txt', app='registry')
    editemails_url = '%s/+editmailinglists'

    for person in team.allmembers:
        if person.is_team or person.preferredemail is None:
            # This is either a team or a person without a preferred email, so
            # don't send a notification.
            continue
        to_address = [str(person.preferredemail.email)]
        replacements = {
            'user': person.displayname,
            'team_displayname': team.displayname,
            'team_name': team.name,
            'team_url': canonical_url(team),
            'subscribe_url': editemails_url % canonical_url(person),
        }
        body = MailWrapper(72).format(template % replacements, force_wrap=True)
        simple_sendmail(from_address, to_address, subject, body, headers)
Exemplo n.º 31
0
    def sendCancellationEmail(self, token):
        """Send an email to the person whose subscription was cancelled."""
        if token.archive.suppress_subscription_notifications:
            # Don't send an email if they should be suppresed for the
            # archive
            return
        send_to_person = token.person
        ppa_name = token.archive.displayname
        ppa_owner_url = canonical_url(token.archive.owner)
        subject = "PPA access cancelled for %s" % ppa_name
        template = get_email_template(
            "ppa-subscription-cancelled.txt", app='soyuz')

        assert not send_to_person.is_team, (
            "Token.person is a team, it should always be individuals.")

        if send_to_person.preferredemail is None:
            # The person has no preferred email set, so we don't
            # email them.
            return

        to_address = [send_to_person.preferredemail.email]
        replacements = {
            'recipient_name': send_to_person.displayname,
            'ppa_name': ppa_name,
            'ppa_owner_url': ppa_owner_url,
            }
        body = MailWrapper(72).format(
            template % replacements, force_wrap=True)

        from_address = format_address(
            ppa_name,
            config.canonical.noreply_from_address)

        headers = {
            'Sender': config.canonical.bounce_address,
            }

        simple_sendmail(from_address, to_address, subject, body, headers)
Exemplo n.º 32
0
def new_import(code_import, event):
    """Email the vcs-imports team about a new code import."""
    if (event.user is None
        or IUnauthenticatedPrincipal.providedBy(event.user)):
        # If there is no logged in user, then we are most likely in a
        # test.
        return
    user = IPerson(event.user)
    subject = 'New code import: %s/%s' % (
        code_import.branch.target.name, code_import.branch.name)
    if code_import.rcs_type == RevisionControlSystems.CVS:
        location = '%s, %s' % (code_import.cvs_root, code_import.cvs_module)
    else:
        location = code_import.url
    rcs_type_map = {
        RevisionControlSystems.CVS: 'CVS',
        RevisionControlSystems.SVN: 'subversion',
        RevisionControlSystems.BZR_SVN: 'subversion',
        RevisionControlSystems.GIT: 'git',
        RevisionControlSystems.BZR: 'bazaar',
        }
    body = get_email_template('new-code-import.txt', app='code') % {
        'person': code_import.registrant.displayname,
        'branch': canonical_url(code_import.branch),
        'rcs_type': rcs_type_map[code_import.rcs_type],
        'location': location,
        }

    from_address = format_address(
        user.displayname, user.preferredemail.email)

    vcs_imports = getUtility(ILaunchpadCelebrities).vcs_imports
    headers = {'X-Launchpad-Branch': code_import.branch.unique_name,
               'X-Launchpad-Message-Rationale':
                   'Operator @%s' % vcs_imports.name,
               'X-Launchpad-Notification-Type': 'code-import',
               }
    for address in get_contact_email_addresses(vcs_imports):
        simple_sendmail(from_address, address, subject, body, headers)
Exemplo n.º 33
0
    def sendSelfRenewalNotification(self):
        """See `ITeamMembership`."""
        team = self.team
        member = self.person
        assert team.renewal_policy == TeamMembershipRenewalPolicy.ONDEMAND

        from_addr = format_address(
            team.displayname, config.canonical.noreply_from_address)
        replacements = {'member_name': member.unique_displayname,
                        'team_name': team.unique_displayname,
                        'team_url': canonical_url(team),
                        'dateexpires': self.dateexpires.strftime('%Y-%m-%d')}
        subject = '%s extended their membership' % member.name
        template = get_email_template(
            'membership-member-renewed.txt', app='registry')
        admins_addrs = self.team.getTeamAdminsEmailAddresses()
        for address in admins_addrs:
            recipient = getUtility(IPersonSet).getByEmail(address)
            replacements['recipient_name'] = recipient.displayname
            msg = MailWrapper().format(
                template % replacements, force_wrap=True)
            simple_sendmail(from_addr, address, subject, msg)
Exemplo n.º 34
0
def new_import(code_import, event):
    """Email the vcs-imports team about a new code import."""
    if (event.user is None
            or IUnauthenticatedPrincipal.providedBy(event.user)):
        # If there is no logged in user, then we are most likely in a
        # test.
        return
    user = IPerson(event.user)
    subject = 'New code import: %s/%s' % (code_import.branch.target.name,
                                          code_import.branch.name)
    if code_import.rcs_type == RevisionControlSystems.CVS:
        location = '%s, %s' % (code_import.cvs_root, code_import.cvs_module)
    else:
        location = code_import.url
    rcs_type_map = {
        RevisionControlSystems.CVS: 'CVS',
        RevisionControlSystems.SVN: 'subversion',
        RevisionControlSystems.BZR_SVN: 'subversion',
        RevisionControlSystems.GIT: 'git',
        RevisionControlSystems.BZR: 'bazaar',
    }
    body = get_email_template('new-code-import.txt', app='code') % {
        'person': code_import.registrant.displayname,
        'branch': canonical_url(code_import.branch),
        'rcs_type': rcs_type_map[code_import.rcs_type],
        'location': location,
    }

    from_address = format_address(user.displayname, user.preferredemail.email)

    vcs_imports = getUtility(ILaunchpadCelebrities).vcs_imports
    headers = {
        'X-Launchpad-Branch': code_import.branch.unique_name,
        'X-Launchpad-Message-Rationale': 'Operator @%s' % vcs_imports.name,
        'X-Launchpad-Notification-Type': 'code-import',
    }
    for address in get_contact_email_addresses(vcs_imports):
        simple_sendmail(from_address, address, subject, body, headers)
Exemplo n.º 35
0
    def run(self):
        self.logger.info("Starting verification of POFile stats at id %d"
            % self.start_at_id)
        loop = Verifier(self.transaction, self.logger, self.start_at_id)

        # Since the script can run for a long time, our deployment
        # process might remove the Launchpad tree the script was run
        # from, thus the script failing to find the email template
        # if it was attempted after DBLoopTuner run is completed.
        # See bug #811447 for OOPS we used to get then.
        template = get_email_template(
            'pofile-stats.txt', 'translations')

        # Each iteration of our loop collects all statistics first, before
        # modifying any rows in the database.  With any locks on the database
        # acquired only at the very end of the iteration, we can afford to
        # make relatively long, low-overhead iterations without disrupting
        # application response times.
        iteration_duration = (
            config.rosetta_pofile_stats.looptuner_iteration_duration)
        DBLoopTuner(loop, iteration_duration).run()

        if loop.total_incorrect > 0 or loop.total_exceptions > 0:
            # Not all statistics were correct, or there were failures while
            # checking them.  Email the admins.
            message = template % {
                'exceptions': loop.total_exceptions,
                'errors': loop.total_incorrect,
                'total': loop.total_checked}
            simple_sendmail(
                from_addr=config.canonical.noreply_from_address,
                to_addrs=[config.launchpad.errors_address],
                subject="POFile statistics errors",
                body=MailWrapper().format(message))
            self.transaction.commit()

        self.logger.info("Done.")
Exemplo n.º 36
0
def notify_invitation_to_join_team(event):
    """Notify team admins that the team has been invited to join another team.

    The notification will include a link to a page in which any team admin can
    accept the invitation.

    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.
    """
    member = event.member
    assert member.is_team
    team = event.team
    membership = getUtility(ITeamMembershipSet).getByPersonAndTeam(
        member, team)
    assert membership is not None

    reviewer = membership.proposed_by
    admin_addrs = member.getTeamAdminsEmailAddresses()
    from_addr = format_address(
        team.displayname, config.canonical.noreply_from_address)
    subject = 'Invitation for %s to join' % member.name
    templatename = 'membership-invitation.txt'
    template = get_email_template(templatename, app='registry')
    replacements = {
        'reviewer': '%s (%s)' % (reviewer.displayname, reviewer.name),
        'member': '%s (%s)' % (member.displayname, member.name),
        'team': '%s (%s)' % (team.displayname, team.name),
        'team_url': canonical_url(team),
        'membership_invitations_url':
            "%s/+invitation/%s" % (canonical_url(member), team.name)}
    for address in admin_addrs:
        recipient = getUtility(IPersonSet).getByEmail(address)
        replacements['recipient_name'] = recipient.displayname
        msg = MailWrapper().format(template % replacements, force_wrap=True)
        simple_sendmail(from_addr, address, subject, msg)
Exemplo n.º 37
0
    def run(self):
        self.logger.info("Starting verification of POFile stats at id %d" %
                         self.start_at_id)
        loop = Verifier(self.transaction, self.logger, self.start_at_id)

        # Since the script can run for a long time, our deployment
        # process might remove the Launchpad tree the script was run
        # from, thus the script failing to find the email template
        # if it was attempted after DBLoopTuner run is completed.
        # See bug #811447 for OOPS we used to get then.
        template = get_email_template('pofile-stats.txt', 'translations')

        # Each iteration of our loop collects all statistics first, before
        # modifying any rows in the database.  With any locks on the database
        # acquired only at the very end of the iteration, we can afford to
        # make relatively long, low-overhead iterations without disrupting
        # application response times.
        iteration_duration = (
            config.rosetta_pofile_stats.looptuner_iteration_duration)
        DBLoopTuner(loop, iteration_duration).run()

        if loop.total_incorrect > 0 or loop.total_exceptions > 0:
            # Not all statistics were correct, or there were failures while
            # checking them.  Email the admins.
            message = template % {
                'exceptions': loop.total_exceptions,
                'errors': loop.total_incorrect,
                'total': loop.total_checked
            }
            simple_sendmail(from_addr=config.canonical.noreply_from_address,
                            to_addrs=[config.launchpad.errors_address],
                            subject="POFile statistics errors",
                            body=MailWrapper().format(message))
            self.transaction.commit()

        self.logger.info("Done.")
Exemplo n.º 38
0
def code_import_updated(code_import, event, new_whiteboard, person):
    """Email the branch subscribers, and the vcs-imports team with new status.
    """
    branch = code_import.branch
    recipients = branch.getNotificationRecipients()
    # Add in the vcs-imports user.
    vcs_imports = getUtility(ILaunchpadCelebrities).vcs_imports
    herder_rationale = 'Operator @%s' % vcs_imports.name
    recipients.add(vcs_imports, None, herder_rationale)

    headers = {'X-Launchpad-Branch': branch.unique_name}

    subject = 'Code import %s/%s status: %s' % (
        code_import.branch.target.name, branch.name,
        code_import.review_status.title)

    email_template = get_email_template(
        'code-import-status-updated.txt', app='code')
    template_params = {
        'body': make_email_body_for_code_import_update(
            code_import, event, new_whiteboard),
        'branch': canonical_url(code_import.branch)}

    if person:
        from_address = format_address(
            person.displayname, person.preferredemail.email)
    else:
        from_address = config.canonical.noreply_from_address

    interested_levels = (
        BranchSubscriptionNotificationLevel.ATTRIBUTEONLY,
        BranchSubscriptionNotificationLevel.FULL)

    for email_address in recipients.getEmails():
        subscription, rationale = recipients.getReason(email_address)

        if subscription is None:
            if rationale == herder_rationale:
                template_params['rationale'] = (
                    'You are getting this email because you are a member of'
                    ' the vcs-imports team.')
            else:
                template_params['rationale'] = rationale
            template_params['unsubscribe'] = ''
        else:
            if subscription.notification_level in interested_levels:
                template_params['rationale'] = (
                    'You are receiving this email as you are subscribed '
                    'to the branch.')
                if not subscription.person.is_team:
                    # Give the users a link to unsubscribe.
                    template_params['unsubscribe'] = (
                        "\nTo unsubscribe from this branch go to "
                        "%s/+edit-subscription." % canonical_url(branch))
                else:
                    template_params['unsubscribe'] = ''
            else:
                # Don't send email to this subscriber.
                continue

        headers['X-Launchpad-Message-Rationale'] = rationale
        body = email_template % template_params
        simple_sendmail(from_address, email_address, subject, body, headers)
 def _sendMail(self, sender, recipients, subject, text):
     """Wrapper for `simple_sendmail`.  Fakeable for easy testing."""
     simple_sendmail(sender, recipients, subject, text)
Exemplo n.º 40
0
    def run(self):
        """See `IMembershipNotificationJob`."""
        from lp.services.scripts import log
        from_addr = format_address(
            self.team.displayname, config.canonical.noreply_from_address)
        admin_emails = self.team.getTeamAdminsEmailAddresses()
        # person might be a self.team, so we can't rely on its preferredemail.
        self.member_email = get_contact_email_addresses(self.member)
        # Make sure we don't send the same notification twice to anybody.
        for email in self.member_email:
            if email in admin_emails:
                admin_emails.remove(email)

        if self.reviewer != self.member:
            self.reviewer_name = self.reviewer.unique_displayname
        else:
            self.reviewer_name = 'the user'

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

        replacements = {
            'member_name': self.member.unique_displayname,
            'recipient_name': self.member.displayname,
            'team_name': self.team.unique_displayname,
            'team_url': canonical_url(self.team),
            'old_status': self.old_status.title,
            'new_status': self.new_status.title,
            'reviewer_name': self.reviewer_name,
            'comment': comment}

        template_name = 'membership-statuschange'
        subject = (
            'Membership change: %(member)s in %(team)s'
            % {
                'member': self.member.name,
                'team': self.team.name,
              })
        if self.new_status == TeamMembershipStatus.EXPIRED:
            template_name = 'membership-expired'
            subject = '%s expired from team' % self.member.name
        elif (self.new_status == TeamMembershipStatus.APPROVED and
            self.old_status != TeamMembershipStatus.ADMIN):
            if self.old_status == TeamMembershipStatus.INVITED:
                subject = ('Invitation to %s accepted by %s'
                        % (self.member.name, self.reviewer.name))
                template_name = 'membership-invitation-accepted'
            elif self.old_status == TeamMembershipStatus.PROPOSED:
                subject = '%s approved by %s' % (
                    self.member.name, self.reviewer.name)
            else:
                subject = '%s added by %s' % (
                    self.member.name, self.reviewer.name)
        elif self.new_status == TeamMembershipStatus.INVITATION_DECLINED:
            subject = ('Invitation to %s declined by %s'
                    % (self.member.name, self.reviewer.name))
            template_name = 'membership-invitation-declined'
        elif self.new_status == TeamMembershipStatus.DEACTIVATED:
            subject = '%s deactivated by %s' % (
                self.member.name, self.reviewer.name)
        elif self.new_status == TeamMembershipStatus.ADMIN:
            subject = '%s made admin by %s' % (
                self.member.name, self.reviewer.name)
        elif self.new_status == TeamMembershipStatus.DECLINED:
            subject = '%s declined by %s' % (
                self.member.name, self.reviewer.name)
        else:
            # Use the default template and subject.
            pass

        # Must have someone to mail, and be a non-open team (because open
        # teams are unrestricted, notifications on join/ leave do not help the
        # admins.
        if (len(admin_emails) != 0 and
            self.team.membership_policy != TeamMembershipPolicy.OPEN):
            admin_template = get_email_template(
                "%s-bulk.txt" % template_name, app='registry')
            for address in admin_emails:
                recipient = getUtility(IPersonSet).getByEmail(address)
                replacements['recipient_name'] = recipient.displayname
                msg = MailWrapper().format(
                    admin_template % replacements, force_wrap=True)
                simple_sendmail(from_addr, address, subject, msg)

        # The self.member can be a self.self.team without any
        # self.members, and in this case we won't have a single email
        # address to send this notification to.
        if self.member_email and self.reviewer != self.member:
            if self.member.is_team:
                template = '%s-bulk.txt' % template_name
            else:
                template = '%s-personal.txt' % template_name
            self.member_template = get_email_template(
                template, app='registry')
            for address in self.member_email:
                recipient = getUtility(IPersonSet).getByEmail(address)
                replacements['recipient_name'] = recipient.displayname
                msg = MailWrapper().format(
                    self.member_template % replacements, force_wrap=True)
                simple_sendmail(from_addr, address, subject, msg)
        log.debug('MembershipNotificationJob sent email')
Exemplo n.º 41
0
def code_import_updated(code_import, event, new_whiteboard, person):
    """Email the branch subscribers, and the vcs-imports team with new status.
    """
    branch = code_import.branch
    recipients = branch.getNotificationRecipients()
    # Add in the vcs-imports user.
    vcs_imports = getUtility(ILaunchpadCelebrities).vcs_imports
    herder_rationale = 'Operator @%s' % vcs_imports.name
    recipients.add(vcs_imports, None, herder_rationale)

    headers = {'X-Launchpad-Branch': branch.unique_name}

    subject = 'Code import %s/%s status: %s' % (
        code_import.branch.target.name, branch.name,
        code_import.review_status.title)

    email_template = get_email_template('code-import-status-updated.txt',
                                        app='code')
    template_params = {
        'body':
        make_email_body_for_code_import_update(code_import, event,
                                               new_whiteboard),
        'branch':
        canonical_url(code_import.branch)
    }

    if person:
        from_address = format_address(person.displayname,
                                      person.preferredemail.email)
    else:
        from_address = config.canonical.noreply_from_address

    interested_levels = (BranchSubscriptionNotificationLevel.ATTRIBUTEONLY,
                         BranchSubscriptionNotificationLevel.FULL)

    for email_address in recipients.getEmails():
        subscription, rationale = recipients.getReason(email_address)

        if subscription is None:
            if rationale == herder_rationale:
                template_params['rationale'] = (
                    'You are getting this email because you are a member of'
                    ' the vcs-imports team.')
            else:
                template_params['rationale'] = rationale
            template_params['unsubscribe'] = ''
        else:
            if subscription.notification_level in interested_levels:
                template_params['rationale'] = (
                    'You are receiving this email as you are subscribed '
                    'to the branch.')
                if not subscription.person.is_team:
                    # Give the users a link to unsubscribe.
                    template_params['unsubscribe'] = (
                        "\nTo unsubscribe from this branch go to "
                        "%s/+edit-subscription." % canonical_url(branch))
                else:
                    template_params['unsubscribe'] = ''
            else:
                # Don't send email to this subscriber.
                continue

        headers['X-Launchpad-Message-Rationale'] = rationale
        body = email_template % template_params
        simple_sendmail(from_address, email_address, subject, body, headers)
Exemplo n.º 42
0
    def sendExpirationWarningEmail(self):
        """See `ITeamMembership`."""
        if self.dateexpires is None:
            raise AssertionError(
                '%s in team %s has no membership expiration date.' %
                (self.person.name, self.team.name))
        if self.dateexpires < datetime.now(pytz.timezone('UTC')):
            # The membership has reached expiration. Silently return because
            # there is nothing to do. The member will have received emails
            # from previous calls by flag-expired-memberships.py
            return
        member = self.person
        team = self.team
        if member.is_team:
            recipient = member.teamowner
            templatename = 'membership-expiration-warning-bulk.txt'
            subject = '%s will expire soon from %s' % (member.name, team.name)
        else:
            recipient = member
            templatename = '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 self.canChangeExpirationDate(recipient):
            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))

        to_addrs = get_contact_email_addresses(recipient)
        if len(to_addrs) == 0:
            # The user does not have a preferred email address, he was
            # probably suspended.
            return
        formatter = DurationFormatterAPI(
            self.dateexpires - datetime.now(pytz.timezone('UTC')))
        replacements = {
            'recipient_name': recipient.displayname,
            'member_name': member.unique_displayname,
            'team_url': canonical_url(team),
            'how_to_renew': how_to_renew,
            'team_name': team.unique_displayname,
            'expiration_date': self.dateexpires.strftime('%Y-%m-%d'),
            'approximate_duration': formatter.approximateduration()}

        msg = get_email_template(templatename, app='registry') % replacements
        from_addr = format_address(
            team.displayname, config.canonical.noreply_from_address)
        simple_sendmail(from_addr, to_addrs, subject, msg)
Exemplo n.º 43
0
    def notify(self, extra_info=None):
        """See `IPackageBuild`.

        If config.buildmaster.build_notification is disable, simply
        return.

        If config.builddmaster.notify_owner is enabled and SPR.creator
        has preferredemail it will send an email to the creator, Bcc:
        to the config.builddmaster.default_recipient. If one of the
        conditions was not satisfied, no preferredemail found (autosync
        or untouched packages from debian) or config options disabled,
        it will only send email to the specified default recipient.

        This notification will contain useful information about
        the record in question (all states are supported), see
        doc/build-notification.txt for further information.
        """

        if not config.builddmaster.send_build_notification:
            return
        if self.status == BuildStatus.FULLYBUILT:
            return

        recipients = set()

        fromaddress = format_address(
            config.builddmaster.default_sender_name,
            config.builddmaster.default_sender_address)

        extra_headers = {
            'X-Launchpad-Build-State': self.status.name,
            'X-Launchpad-Build-Component': self.current_component.name,
            'X-Launchpad-Build-Arch':
                self.distro_arch_series.architecturetag,
            }

        # XXX cprov 2006-10-27: Temporary extra debug info about the
        # SPR.creator in context, to be used during the service quarantine,
        # notify_owner will be disabled to avoid *spamming* Debian people.
        creator = self.source_package_release.creator
        extra_headers['X-Creator-Recipient'] = ",".join(
            get_contact_email_addresses(creator))

        # Currently there are 7038 SPR published in edgy which the creators
        # have no preferredemail. They are the autosync ones (creator = katie,
        # 3583 packages) and the untouched sources since we have migrated from
        # DAK (the rest). We should not spam Debian maintainers.

        # Please note that both the package creator and the package uploader
        # will be notified of failures if:
        #     * the 'notify_owner' flag is set
        #     * the package build (failure) occurred in the original
        #       archive.
        package_was_not_copied = (
            self.archive == self.source_package_release.upload_archive)

        if package_was_not_copied and config.builddmaster.notify_owner:
            if (self.archive.is_ppa and creator.inTeam(self.archive.owner)
                or
                not self.archive.is_ppa):
                # If this is a PPA, the package creator should only be
                # notified if they are the PPA owner or in the PPA team.
                # (see bug 375757)
                # Non-PPA notifications inform the creator regardless.
                recipients = recipients.union(
                    get_contact_email_addresses(creator))
            dsc_key = self.source_package_release.dscsigningkey
            if dsc_key:
                recipients = recipients.union(
                    get_contact_email_addresses(dsc_key.owner))

        # Modify notification contents according to the targeted archive.
        # 'Archive Tag', 'Subject' and 'Source URL' are customized for PPA.
        # We only send build-notifications to 'buildd-admin' celebrity for
        # main archive candidates.
        # For PPA build notifications we include the archive.owner
        # contact_address.
        if not self.archive.is_ppa:
            buildd_admins = getUtility(ILaunchpadCelebrities).buildd_admin
            recipients = recipients.union(
                get_contact_email_addresses(buildd_admins))
            archive_tag = '%s primary archive' % self.distribution.name
            subject = "[Build #%d] %s" % (self.id, self.title)
            source_url = canonical_url(self.distributionsourcepackagerelease)
        else:
            recipients = recipients.union(
                get_contact_email_addresses(self.archive.owner))
            # For PPAs we run the risk of having no available contact_address,
            # for instance, when both, SPR.creator and Archive.owner have
            # not enabled it.
            if len(recipients) == 0:
                return
            archive_tag = '%s PPA' % get_ppa_reference(self.archive)
            subject = "[Build #%d] %s (%s)" % (
                self.id, self.title, archive_tag)
            source_url = 'not available'
            extra_headers['X-Launchpad-PPA'] = get_ppa_reference(self.archive)

        # XXX cprov 2006-08-02: pending security recipients for SECURITY
        # pocket build. We don't build SECURITY yet :(

        # XXX cprov 2006-08-02: find out a way to glue parameters reported
        # with the state in the build worflow, maybe by having an
        # IBuild.statusReport property, which could also be used in the
        # respective page template.
        if self.status in [
            BuildStatus.NEEDSBUILD, BuildStatus.SUPERSEDED]:
            # untouched builds
            buildduration = 'not available'
            buildlog_url = 'not available'
            builder_url = 'not available'
        elif self.status == BuildStatus.UPLOADING:
            buildduration = 'uploading'
            buildlog_url = 'see builder page'
            builder_url = 'not available'
        elif self.status == BuildStatus.BUILDING:
            # build in process
            buildduration = 'not finished'
            buildlog_url = 'see builder page'
            builder_url = canonical_url(self.buildqueue_record.builder)
        else:
            # completed states (success and failure)
            buildduration = DurationFormatterAPI(
                self.duration).approximateduration()
            buildlog_url = self.log_url
            builder_url = canonical_url(self.builder)

        if self.status == BuildStatus.FAILEDTOUPLOAD:
            assert extra_info is not None, (
                'Extra information is required for FAILEDTOUPLOAD '
                'notifications.')
            extra_info = 'Upload log:\n%s' % extra_info
        else:
            extra_info = ''

        template = get_email_template('build-notification.txt', app='soyuz')
        replacements = {
            'source_name': self.source_package_release.name,
            'source_version': self.source_package_release.version,
            'architecturetag': self.distro_arch_series.architecturetag,
            'build_state': self.status.title,
            'build_duration': buildduration,
            'buildlog_url': buildlog_url,
            'builder_url': builder_url,
            'build_title': self.title,
            'build_url': canonical_url(self),
            'source_url': source_url,
            'extra_info': extra_info,
            'archive_tag': archive_tag,
            'component_tag': self.current_component.name,
            }
        message = template % replacements

        for toaddress in recipients:
            simple_sendmail(
                fromaddress, toaddress, subject, message,
                headers=extra_headers)