Merge lp:~sinzui/launchpad/contact-new-maintainer into lp:launchpad

Proposed by Curtis Hovey on 2012-10-05
Status: Merged
Approved by: Curtis Hovey on 2012-10-05
Approved revision: no longer in the source branch.
Merged at revision: 16104
Proposed branch: lp:~sinzui/launchpad/contact-new-maintainer
Merge into: lp:launchpad
Diff against target: 274 lines (+54/-28)
2 files modified
lib/lp/registry/subscribers.py (+10/-9)
lib/lp/registry/tests/test_subscribers.py (+44/-19)
To merge this branch: bzr merge lp:~sinzui/launchpad/contact-new-maintainer
Reviewer Review Type Date Requested Status
Abel Deuring (community) code 2012-10-05 Approve on 2012-10-05
Review via email: mp+128273@code.launchpad.net

Commit Message

Send licensing email to new project maintainer.

Description of the Change

Lp is sending licensing emails to the project registrant, but the
registrant may have made someone else the maintainer. The licensing
jobs already use the maintainer, so there is a chance of confusion
when the registrant is not a member of the maintainer team.

--------------------------------------------------------------------

RULES

    Pre-implementation: abentley
    * Use the project maintainer, not the registrant when sending
      the initial email about licensing issues.
    * The emails already use neutral language to deal with user and teams.

QA

    * Clear the staging inbox so that you can find the email.
    * Visit https://qastaging.launchpad.net/projects/+new
    * Register a project with a proprietary license and set a team as the
      maintainer.
    * Check the staging inbox and verify there is a message addressed
      to the team admins.

LINT

    lib/lp/registry/subscribers.py
    lib/lp/registry/tests/test_subscribers.py

TEST

    ./bin/test -vvc lp.registry.tests.test_subscribers

IMPLEMENTATION

I first updated LicenseNotification to ignore the user and instead use
the product.owner. I then removed the user argument since it was unused.
Since the code does not adapt the event.user, tests do not need to be
logged in.
    lib/lp/registry/subscribers.py
    lib/lp/registry/tests/test_subscribers.py

To post a comment you must log in.
Abel Deuring (adeuring) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/subscribers.py'
2--- lib/lp/registry/subscribers.py 2012-06-14 05:18:22 +0000
3+++ lib/lp/registry/subscribers.py 2012-10-05 15:09:23 +0000
4@@ -14,12 +14,12 @@
5 import pytz
6 from zope.security.proxy import removeSecurityProxy
7
8-from lp.registry.interfaces.person import IPerson
9 from lp.registry.interfaces.product import License
10 from lp.services.config import config
11 from lp.services.mail.helpers import get_email_template
12 from lp.services.mail.sendmail import (
13 format_address,
14+ format_address_for_person,
15 simple_sendmail,
16 )
17 from lp.services.webapp.menu import structured
18@@ -32,8 +32,7 @@
19 def product_licenses_modified(product, event):
20 """Send a notification if licences changed and a licence is special."""
21 if LicenseNotification.needs_notification(product):
22- user = IPerson(event.user)
23- notification = LicenseNotification(product, user)
24+ notification = LicenseNotification(product)
25 notification.send()
26 notification.display()
27
28@@ -41,9 +40,8 @@
29 class LicenseNotification:
30 """Send notification about special licences to the user."""
31
32- def __init__(self, product, user):
33+ def __init__(self, product):
34 self.product = product
35- self.user = user
36
37 @staticmethod
38 def needs_notification(product):
39@@ -85,15 +83,18 @@
40 if not self.needs_notification(self.product):
41 # The project has a common licence.
42 return False
43- user_address = format_address(
44- self.user.displayname, self.user.preferredemail.email)
45+ maintainer = self.product.owner
46+ if maintainer.is_team:
47+ user_address = maintainer.getTeamAdminsEmailAddresses()
48+ else:
49+ user_address = format_address_for_person(maintainer)
50 from_address = format_address(
51 "Launchpad", config.canonical.noreply_from_address)
52 commercial_address = format_address(
53 'Commercial', 'commercial@launchpad.net')
54 substitutions = dict(
55- user_displayname=self.user.displayname,
56- user_name=self.user.name,
57+ user_displayname=maintainer.displayname,
58+ user_name=maintainer.name,
59 product_name=self.product.name,
60 product_url=canonical_url(self.product),
61 commercial_use_expiration=self.getCommercialUseMessage(),
62
63=== modified file 'lib/lp/registry/tests/test_subscribers.py'
64--- lib/lp/registry/tests/test_subscribers.py 2012-10-03 11:20:01 +0000
65+++ lib/lp/registry/tests/test_subscribers.py 2012-10-05 15:09:23 +0000
66@@ -8,18 +8,25 @@
67 from datetime import datetime
68
69 import pytz
70+from zope.component import getUtility
71 from zope.security.proxy import removeSecurityProxy
72
73+from lp.registry.interfaces.person import TeamMembershipPolicy
74 from lp.registry.interfaces.product import License
75 from lp.registry.model.product import LicensesModifiedEvent
76 from lp.registry.subscribers import (
77 LicenseNotification,
78 product_licenses_modified,
79 )
80+from lp.registry.interfaces.teammembership import (
81+ ITeamMembershipSet,
82+ TeamMembershipStatus,
83+ )
84 from lp.services.webapp.publisher import get_current_browser_request
85 from lp.testing import (
86 login_person,
87 logout,
88+ person_logged_in,
89 TestCaseWithFactory,
90 )
91 from lp.testing.layers import DatabaseFunctionalLayer
92@@ -32,16 +39,13 @@
93
94 def test_init(self):
95 product = self.factory.makeProduct()
96- login_person(product.owner)
97 event = LicensesModifiedEvent(product)
98- self.assertEqual(product.owner, event.user.person)
99 self.assertEqual(product, event.object)
100 self.assertEqual(product, event.object_before_modification)
101 self.assertEqual([], event.edited_fields)
102
103 def test_init_with_user(self):
104 product = self.factory.makeProduct()
105- login_person(product.owner)
106 event = LicensesModifiedEvent(product, user=product.owner)
107 self.assertEqual(product.owner, event.user)
108
109@@ -53,7 +57,6 @@
110 def make_product_event(self, licenses):
111 product = self.factory.makeProduct(licenses=licenses)
112 pop_notifications()
113- login_person(product.owner)
114 event = LicensesModifiedEvent(product, user=product.owner)
115 return product, event
116
117@@ -128,7 +131,7 @@
118 def test_send_known_license(self):
119 # A known licence does not generate an email.
120 product, user = self.make_product_user([License.GNU_GPL_V2])
121- notification = LicenseNotification(product, user)
122+ notification = LicenseNotification(product)
123 result = notification.send()
124 self.assertIs(False, result)
125 self.assertEqual(0, len(pop_notifications()))
126@@ -136,7 +139,7 @@
127 def test_send_other_dont_know(self):
128 # An Other/I don't know licence sends one email.
129 product, user = self.make_product_user([License.DONT_KNOW])
130- notification = LicenseNotification(product, user)
131+ notification = LicenseNotification(product)
132 result = notification.send()
133 self.assertIs(True, result)
134 self.verify_whiteboard(product)
135@@ -147,7 +150,7 @@
136 def test_send_other_open_source(self):
137 # An Other/Open Source licence sends one email.
138 product, user = self.make_product_user([License.OTHER_OPEN_SOURCE])
139- notification = LicenseNotification(product, user)
140+ notification = LicenseNotification(product)
141 result = notification.send()
142 self.assertIs(True, result)
143 self.verify_whiteboard(product)
144@@ -158,7 +161,7 @@
145 def test_send_other_proprietary(self):
146 # An Other/Proprietary licence sends one email.
147 product, user = self.make_product_user([License.OTHER_PROPRIETARY])
148- notification = LicenseNotification(product, user)
149+ notification = LicenseNotification(product)
150 result = notification.send()
151 self.assertIs(True, result)
152 self.verify_whiteboard(product)
153@@ -166,6 +169,28 @@
154 self.assertEqual(1, len(notifications))
155 self.verify_user_email(notifications.pop())
156
157+ def test_send_other_proprietary_team_admins(self):
158+ # An Other/Proprietary licence sends one email to the team admins.
159+ product, user = self.make_product_user([License.OTHER_PROPRIETARY])
160+ owner = self.factory.makePerson(email='owner@eg.dom')
161+ team = self.factory.makeTeam(
162+ owner=owner, membership_policy=TeamMembershipPolicy.RESTRICTED)
163+ admin = self.factory.makePerson(email='admin@eg.dom')
164+ with person_logged_in(owner):
165+ team.addMember(admin, owner)
166+ membership_set = getUtility(ITeamMembershipSet)
167+ tm = membership_set.getByPersonAndTeam(admin, team)
168+ tm.setStatus(TeamMembershipStatus.ADMIN, owner)
169+ with person_logged_in(product.owner):
170+ product.owner = team
171+ pop_notifications()
172+ notification = LicenseNotification(product)
173+ result = notification.send()
174+ self.assertIs(True, result)
175+ notifications = pop_notifications()
176+ self.assertEqual(1, len(notifications))
177+ self.assertEqual('admin@eg.dom,owner@eg.dom', notifications[0]['To'])
178+
179 def test_display_no_request(self):
180 # If there is no request, there is no reason to show a message in
181 # the browser.
182@@ -173,7 +198,7 @@
183 # Using the proxied product leads to an exeception when
184 # notification.display() below is called because the permission
185 # checks product require an interaction.
186- notification = LicenseNotification(removeSecurityProxy(product), user)
187+ notification = LicenseNotification(removeSecurityProxy(product))
188 logout()
189 result = notification.display()
190 self.assertIs(False, result)
191@@ -181,7 +206,7 @@
192 def test_display_no_message(self):
193 # A notification is not added if there is no message to show.
194 product, user = self.make_product_user([License.GNU_GPL_V2])
195- notification = LicenseNotification(product, user)
196+ notification = LicenseNotification(product)
197 result = notification.display()
198 self.assertEqual('', notification.getCommercialUseMessage())
199 self.assertIs(False, result)
200@@ -189,7 +214,7 @@
201 def test_display_has_message(self):
202 # A notification is added if there is a message to show.
203 product, user = self.make_product_user([License.OTHER_PROPRIETARY])
204- notification = LicenseNotification(product, user)
205+ notification = LicenseNotification(product)
206 result = notification.display()
207 message = notification.getCommercialUseMessage()
208 self.assertIs(True, result)
209@@ -204,7 +229,7 @@
210 # A notification is added if there is a message to show.
211 product, user = self.make_product_user([License.OTHER_PROPRIETARY])
212 product.displayname = '<b>Look</b>'
213- notification = LicenseNotification(product, user)
214+ notification = LicenseNotification(product)
215 result = notification.display()
216 self.assertIs(True, result)
217 request = get_current_browser_request()
218@@ -221,33 +246,33 @@
219
220 def test_getTemplateName_other_dont_know(self):
221 product, user = self.make_product_user([License.DONT_KNOW])
222- notification = LicenseNotification(product, user)
223+ notification = LicenseNotification(product)
224 self.assertEqual(
225 'product-license-dont-know.txt',
226 notification.getTemplateName())
227
228 def test_getTemplateName_propietary(self):
229 product, user = self.make_product_user([License.OTHER_PROPRIETARY])
230- notification = LicenseNotification(product, user)
231+ notification = LicenseNotification(product)
232 self.assertEqual(
233 'product-license-other-proprietary.txt',
234 notification.getTemplateName())
235
236 def test_getTemplateName_other_open_source(self):
237 product, user = self.make_product_user([License.OTHER_OPEN_SOURCE])
238- notification = LicenseNotification(product, user)
239+ notification = LicenseNotification(product)
240 self.assertEqual(
241 'product-license-other-open-source.txt',
242 notification.getTemplateName())
243
244 def test_getCommercialUseMessage_without_commercial_subscription(self):
245 product, user = self.make_product_user([License.MIT])
246- notification = LicenseNotification(product, user)
247+ notification = LicenseNotification(product)
248 self.assertEqual('', notification.getCommercialUseMessage())
249
250 def test_getCommercialUseMessage_with_complimentary_cs(self):
251 product, user = self.make_product_user([License.OTHER_PROPRIETARY])
252- notification = LicenseNotification(product, user)
253+ notification = LicenseNotification(product)
254 message = (
255 "Ball's complimentary commercial subscription expires on %s." %
256 product.commercial_subscription.date_expires.date().isoformat())
257@@ -257,7 +282,7 @@
258 product, user = self.make_product_user([License.MIT])
259 self.factory.makeCommercialSubscription(product)
260 product.licenses = [License.MIT, License.OTHER_PROPRIETARY]
261- notification = LicenseNotification(product, user)
262+ notification = LicenseNotification(product)
263 message = (
264 "Ball's commercial subscription expires on %s." %
265 product.commercial_subscription.date_expires.date().isoformat())
266@@ -267,7 +292,7 @@
267 product, user = self.make_product_user([License.MIT])
268 self.factory.makeCommercialSubscription(product, expired=True)
269 product.licenses = [License.MIT, License.OTHER_PROPRIETARY]
270- notification = LicenseNotification(product, user)
271+ notification = LicenseNotification(product)
272 message = (
273 "Ball's commercial subscription expired on %s." %
274 product.commercial_subscription.date_expires.date().isoformat())