Merge lp:~wgrant/launchpad/bug-1098170 into lp:launchpad

Proposed by William Grant
Status: Merged
Merged at revision: 16416
Proposed branch: lp:~wgrant/launchpad/bug-1098170
Merge into: lp:launchpad
Diff against target: 108 lines (+40/-20)
2 files modified
lib/lp/registry/model/mailinglist.py (+8/-20)
lib/lp/registry/tests/test_mailinglist.py (+32/-0)
To merge this branch: bzr merge lp:~wgrant/launchpad/bug-1098170
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code Approve
Review via email: mp+142816@code.launchpad.net

Commit message

Fix MailingListSet.getSusbcribedAddresses() to not occasionally return former members when running over multiple lists.

Description of the change

This branch fixes a correctness bug in MailingListSet.getSubscribedAddresses() when called with more than one mailing list. This hadn't been seen on production before yesterday because mailman was until recently configured to only request information one list at a time.

The bug was a single missing line in the second (non-preferred address) query:

    MailingList.teamID == TeamParticipation.teamID,

Without that, a TeamParticipation for any of the teams in the batch qualifies the person as a participant in *all* of the teams in the batch, even if they're actually only a former member in some of them.

Rather than fix the duplicated query, I rather chose to merge the preferred and non-preferred address cases into a single query.

To post a comment you must log in.
Revision history for this message
Steve Kowalik (stevenk) :
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/model/mailinglist.py'
2--- lib/lp/registry/model/mailinglist.py 2013-01-07 02:40:55 +0000
3+++ lib/lp/registry/model/mailinglist.py 2013-01-11 00:08:23 +0000
4@@ -30,6 +30,7 @@
5 from storm.expr import (
6 And,
7 Join,
8+ Or,
9 )
10 from storm.info import ClassAlias
11 from storm.store import Store
12@@ -563,10 +564,6 @@
13 def getSubscribedAddresses(self, team_names):
14 """See `IMailingListSet`."""
15 store = IStore(MailingList)
16- # In order to handle the case where the preferred email address is
17- # used (i.e. where MailingListSubscription.email_address is NULL), we
18- # need to UNION, those using a specific address and those using the
19- # preferred address.
20 Team = ClassAlias(Person)
21 tables = (
22 EmailAddress,
23@@ -582,16 +579,19 @@
24 Join(Team, Team.id == MailingList.teamID),
25 )
26 team_ids, list_ids = self._getTeamIdsAndMailingListIds(team_names)
27- # Find all the people who are subscribed with their preferred address.
28 preferred = store.using(*tables).find(
29 (EmailAddress.email, Person.displayname, Team.name),
30 And(MailingListSubscription.mailing_listID.is_in(list_ids),
31 TeamParticipation.teamID.is_in(team_ids),
32 MailingList.teamID == TeamParticipation.teamID,
33 MailingList.status != MailingListStatus.INACTIVE,
34- MailingListSubscription.email_addressID == None,
35- EmailAddress.status == EmailAddressStatus.PREFERRED,
36- Account.status == AccountStatus.ACTIVE))
37+ Account.status == AccountStatus.ACTIVE,
38+ Or(
39+ And(MailingListSubscription.email_addressID == None,
40+ EmailAddress.status == EmailAddressStatus.PREFERRED),
41+ EmailAddress.id ==
42+ MailingListSubscription.email_addressID),
43+ ))
44 # Sort by team name.
45 by_team = collections.defaultdict(set)
46 for email, display_name, team_name in preferred:
47@@ -599,18 +599,6 @@
48 'Unexpected team name in results: %s' % team_name)
49 value = (display_name, email.lower())
50 by_team[team_name].add(value)
51- explicit = store.using(*tables).find(
52- (EmailAddress.email, Person.displayname, Team.name),
53- And(MailingListSubscription.mailing_listID.is_in(list_ids),
54- TeamParticipation.teamID.is_in(team_ids),
55- MailingList.status != MailingListStatus.INACTIVE,
56- EmailAddress.id == MailingListSubscription.email_addressID,
57- Account.status == AccountStatus.ACTIVE))
58- for email, display_name, team_name in explicit:
59- assert team_name in team_names, (
60- 'Unexpected team name in results: %s' % team_name)
61- value = (display_name, email.lower())
62- by_team[team_name].add(value)
63 # Turn the results into a mapping of lists.
64 results = {}
65 for team_name, address_set in by_team.items():
66
67=== modified file 'lib/lp/registry/tests/test_mailinglist.py'
68--- lib/lp/registry/tests/test_mailinglist.py 2012-12-26 01:32:19 +0000
69+++ lib/lp/registry/tests/test_mailinglist.py 2013-01-11 00:08:23 +0000
70@@ -640,6 +640,38 @@
71 list_subscribers = [(member.displayname, member.preferredemail.email)]
72 self.assertEqual(list_subscribers, result[team.name])
73
74+ def test_getSubscribedAddresses_excludes_former_participants(self):
75+ # getSubscribedAddresses() only includes present participants of
76+ # the team, even if they still participate in another team in
77+ # the batch (bug #1098170).
78+ team1, member1 = self.factory.makeTeamWithMailingListSubscribers(
79+ 'team1')
80+ team2, member2 = self.factory.makeTeamWithMailingListSubscribers(
81+ 'team2')
82+ team1.addMember(member2, reviewer=team1.teamowner)
83+ team1.mailing_list.subscribe(member2, address=member2.preferredemail)
84+
85+ result = self.mailing_list_set.getSubscribedAddresses(
86+ ['team1', 'team2'])
87+ self.assertEqual(
88+ sorted([
89+ (member1.displayname, member1.preferredemail.email),
90+ (member2.displayname, member2.preferredemail.email)]),
91+ result['team1'])
92+ self.assertEqual(
93+ [(member2.displayname, member2.preferredemail.email)],
94+ result['team2'])
95+
96+ member2.retractTeamMembership(team1, member2)
97+ result = self.mailing_list_set.getSubscribedAddresses(
98+ ['team1', 'team2'])
99+ self.assertEqual(
100+ [(member1.displayname, member1.preferredemail.email)],
101+ result['team1'])
102+ self.assertEqual(
103+ [(member2.displayname, member2.preferredemail.email)],
104+ result['team2'])
105+
106 def test_getSubscribedAddresses_preferredemail_dict_values(self):
107 # getSubscribedAddresses() dict values include users who want email to
108 # go to their preferred address.