~launchpad-pqm/launchpad/devel

13668.1.21 by Curtis Hovey
Updated copyrights.
1
# Copyright 2010-2011 Canonical Ltd.  This software is licensed under the
11015.2.4 by Graham Binns
Moved a bunch of code into lp.bugs.mail.bugnotificationbuilder.py, where it broke.
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
4
"""Bug notification building code."""
5
6
__metaclass__ = type
7
__all__ = [
8
    'BugNotificationBuilder',
9
    'format_rfc2822_date',
11015.2.6 by Graham Binns
Fixed lint.
10
    'get_bugmail_error_address',
11015.2.5 by Graham Binns
Fixed failing tests.
11
    'get_bugmail_from_address',
11015.2.4 by Graham Binns
Moved a bunch of code into lp.bugs.mail.bugnotificationbuilder.py, where it broke.
12
    ]
13
14
from email.MIMEText import MIMEText
15
from email.Utils import formatdate
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
16
import rfc822
11015.2.4 by Graham Binns
Moved a bunch of code into lp.bugs.mail.bugnotificationbuilder.py, where it broke.
17
11015.2.6 by Graham Binns
Fixed lint.
18
from zope.component import getUtility
11015.2.4 by Graham Binns
Moved a bunch of code into lp.bugs.mail.bugnotificationbuilder.py, where it broke.
19
14605.1.1 by Curtis Hovey
Moved canonical.config to lp.services.
20
from lp.services.config import config
14593.2.15 by Curtis Hovey
Moved helpers to lp.services.
21
from lp.services.helpers import shortlist
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
22
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
14538.1.2 by Curtis Hovey
Moved account and email address to lp.services.identity.
23
from lp.services.identity.interfaces.emailaddress import IEmailAddressSet
13668.1.2 by Curtis Hovey
import from lp.services.mail.sendmail
24
from lp.services.mail.sendmail import format_address
11015.2.4 by Graham Binns
Moved a bunch of code into lp.bugs.mail.bugnotificationbuilder.py, where it broke.
25
26
27
def format_rfc2822_date(date):
28
    """Formats a date according to RFC2822's desires."""
29
    return formatdate(rfc822.mktime_tz(date.utctimetuple() + (0, )))
30
31
32
def get_bugmail_from_address(person, bug):
33
    """Returns the right From: address to use for a bug notification."""
34
    if person == getUtility(ILaunchpadCelebrities).janitor:
35
        return format_address(
36
            'Launchpad Bug Tracker',
37
            "%s@%s" % (bug.id, config.launchpad.bugs_domain))
38
39
    if person.hide_email_addresses:
40
        return format_address(
41
            person.displayname,
42
            "%s@%s" % (bug.id, config.launchpad.bugs_domain))
43
44
    if person.preferredemail is not None:
45
        return format_address(person.displayname, person.preferredemail.email)
46
47
    # XXX: Bjorn Tillenius 2006-04-05:
48
    # The person doesn't have a preferred email set, but he
49
    # added a comment (either via the email UI, or because he was
50
    # imported as a deaf reporter). It shouldn't be possible to use the
51
    # email UI if you don't have a preferred email set, but work around
52
    # it for now by trying hard to find the right email address to use.
53
    email_addresses = shortlist(
54
        getUtility(IEmailAddressSet).getByPerson(person))
55
    if not email_addresses:
56
        # XXX: Bjorn Tillenius 2006-05-21 bug=33427:
57
        # A user should always have at least one email address,
58
        # but due to bug #33427, this isn't always the case.
59
        return format_address(person.displayname,
60
            "%s@%s" % (bug.id, config.launchpad.bugs_domain))
61
62
    # At this point we have no validated emails to use: if any of the
63
    # person's emails had been validated the preferredemail would be
64
    # set. Since we have no idea of which email address is best to use,
65
    # we choose the first one.
66
    return format_address(person.displayname, email_addresses[0].email)
67
68
69
def get_bugmail_replyto_address(bug):
70
    """Return an appropriate bugmail Reply-To address.
71
72
    :bug: the IBug.
73
74
    :user: an IPerson whose name will appear in the From address, e.g.:
75
76
        From: Foo Bar via Malone <123@bugs...>
77
    """
78
    return u"Bug %d <%s@%s>" % (bug.id, bug.id, config.launchpad.bugs_domain)
79
80
81
def get_bugmail_error_address():
82
    """Return a suitable From address for a bug transaction error email."""
83
    return config.malone.bugmail_error_from_address
84
85
86
class BugNotificationBuilder:
87
    """Constructs a MIMEText message for a bug notification.
88
89
    Takes a bug and a set of headers and returns a new MIMEText
90
    object. Common and expensive to calculate headers are cached
91
    up-front.
92
    """
93
11126.4.1 by Brian Murray
add in a X-Launchpad-Bug-Modifier email header
94
    def __init__(self, bug, event_creator=None):
11015.2.4 by Graham Binns
Moved a bunch of code into lp.bugs.mail.bugnotificationbuilder.py, where it broke.
95
        self.bug = bug
96
97
        # Pre-calculate common headers.
98
        self.common_headers = [
99
            ('Reply-To', get_bugmail_replyto_address(bug)),
100
            ('Sender', config.canonical.bounce_address),
101
            ]
102
103
        # X-Launchpad-Bug
104
        self.common_headers.extend(
105
            ('X-Launchpad-Bug', bugtask.asEmailHeaderValue())
106
            for bugtask in bug.bugtasks)
107
108
        # X-Launchpad-Bug-Tags
109
        if len(bug.tags) > 0:
110
            self.common_headers.append(
111
                ('X-Launchpad-Bug-Tags', ' '.join(bug.tags)))
112
113
        # Add the X-Launchpad-Bug-Private header. This is a simple
114
        # yes/no value denoting privacy for the bug.
115
        if bug.private:
116
            self.common_headers.append(
117
                ('X-Launchpad-Bug-Private', 'yes'))
118
        else:
119
            self.common_headers.append(
120
                ('X-Launchpad-Bug-Private', 'no'))
121
122
        # Add the X-Launchpad-Bug-Security-Vulnerability header to
123
        # denote security for this bug. This follows the same form as
124
        # the -Bug-Private header.
125
        if bug.security_related:
126
            self.common_headers.append(
127
                ('X-Launchpad-Bug-Security-Vulnerability', 'yes'))
128
        else:
129
            self.common_headers.append(
130
                ('X-Launchpad-Bug-Security-Vulnerability', 'no'))
131
132
        # Add the -Bug-Commenters header, a space-separated list of
133
        # distinct IDs of people who have commented on the bug. The
134
        # list is sorted to aid testing.
135
        commenters = set(message.owner.name for message in bug.messages)
136
        self.common_headers.append(
137
            ('X-Launchpad-Bug-Commenters', ' '.join(sorted(commenters))))
138
11015.2.9 by Graham Binns
Merged devel, fixed conflicts.
139
        # Add the -Bug-Reporter header to identify the owner of the bug
11126.4.2 by Brian Murray
punctuate up comments and remove event_creator attribute
140
        # and the original bug task for filtering.
11015.2.9 by Graham Binns
Merged devel, fixed conflicts.
141
        self.common_headers.append(
142
            ('X-Launchpad-Bug-Reporter',
11126.4.1 by Brian Murray
add in a X-Launchpad-Bug-Modifier email header
143
             '%s (%s)' % (bug.owner.displayname, bug.owner.name)))
144
145
        # Add the -Bug-Modifier header to identify the person who
11126.4.2 by Brian Murray
punctuate up comments and remove event_creator attribute
146
        # modified the bug report.
11126.4.1 by Brian Murray
add in a X-Launchpad-Bug-Modifier email header
147
        if event_creator:
148
            self.common_headers.append(
149
                ('X-Launchpad-Bug-Modifier',
150
                    '%s (%s)' % (event_creator.displayname,
151
                        event_creator.name)))
11015.2.9 by Graham Binns
Merged devel, fixed conflicts.
152
11015.2.4 by Graham Binns
Moved a bunch of code into lp.bugs.mail.bugnotificationbuilder.py, where it broke.
153
    def build(self, from_address, to_address, body, subject, email_date,
7675.1059.18 by Danilo Segan
Add BugNotificationBuilder filters parameter to build() method.
154
              rationale=None, references=None, message_id=None, filters=None):
11015.2.4 by Graham Binns
Moved a bunch of code into lp.bugs.mail.bugnotificationbuilder.py, where it broke.
155
        """Construct the notification.
156
157
        :param from_address: The From address of the notification.
158
        :param to_address: The To address for the notification.
159
        :param body: The body text of the notification.
160
        :type body: unicode
161
        :param subject: The Subject of the notification.
162
        :param email_date: The Date for the notification.
163
        :param rationale: The rationale for why the recipient is
164
            receiving this notification.
165
        :param references: A value for the References header.
166
        :param message_id: A value for the Message-ID header.
167
168
        :return: An `email.MIMEText.MIMEText` object.
169
        """
170
        message = MIMEText(body.encode('utf8'), 'plain', 'utf8')
171
        message['Date'] = format_rfc2822_date(email_date)
172
        message['From'] = from_address
173
        message['To'] = to_address
174
175
        # Add the common headers.
176
        for header in self.common_headers:
177
            message.add_header(*header)
178
179
        if references is not None:
180
            message['References'] = ' '.join(references)
181
        if message_id is not None:
182
            message['Message-Id'] = message_id
183
184
        subject_prefix = "[Bug %d]" % self.bug.id
185
        if subject is None:
186
            message['Subject'] = subject_prefix
187
        elif subject_prefix in subject:
188
            message['Subject'] = subject
189
        else:
190
            message['Subject'] = "%s %s" % (subject_prefix, subject)
191
192
        if rationale is not None:
193
            message.add_header('X-Launchpad-Message-Rationale', rationale)
194
7675.1059.18 by Danilo Segan
Add BugNotificationBuilder filters parameter to build() method.
195
        if filters is not None:
196
            for filter in filters:
197
                message.add_header(
12631.1.1 by Danilo Segan
Make no mention of subscription filters when subscription is sufficient.
198
                    'X-Launchpad-Subscription', filter)
7675.1059.18 by Danilo Segan
Add BugNotificationBuilder filters parameter to build() method.
199
11015.2.4 by Graham Binns
Moved a bunch of code into lp.bugs.mail.bugnotificationbuilder.py, where it broke.
200
        return message