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
=== modified file 'lib/lp/registry/model/mailinglist.py'
--- lib/lp/registry/model/mailinglist.py 2013-01-07 02:40:55 +0000
+++ lib/lp/registry/model/mailinglist.py 2013-01-11 00:08:23 +0000
@@ -30,6 +30,7 @@
30from storm.expr import (30from storm.expr import (
31 And,31 And,
32 Join,32 Join,
33 Or,
33 )34 )
34from storm.info import ClassAlias35from storm.info import ClassAlias
35from storm.store import Store36from storm.store import Store
@@ -563,10 +564,6 @@
563 def getSubscribedAddresses(self, team_names):564 def getSubscribedAddresses(self, team_names):
564 """See `IMailingListSet`."""565 """See `IMailingListSet`."""
565 store = IStore(MailingList)566 store = IStore(MailingList)
566 # In order to handle the case where the preferred email address is
567 # used (i.e. where MailingListSubscription.email_address is NULL), we
568 # need to UNION, those using a specific address and those using the
569 # preferred address.
570 Team = ClassAlias(Person)567 Team = ClassAlias(Person)
571 tables = (568 tables = (
572 EmailAddress,569 EmailAddress,
@@ -582,16 +579,19 @@
582 Join(Team, Team.id == MailingList.teamID),579 Join(Team, Team.id == MailingList.teamID),
583 )580 )
584 team_ids, list_ids = self._getTeamIdsAndMailingListIds(team_names)581 team_ids, list_ids = self._getTeamIdsAndMailingListIds(team_names)
585 # Find all the people who are subscribed with their preferred address.
586 preferred = store.using(*tables).find(582 preferred = store.using(*tables).find(
587 (EmailAddress.email, Person.displayname, Team.name),583 (EmailAddress.email, Person.displayname, Team.name),
588 And(MailingListSubscription.mailing_listID.is_in(list_ids),584 And(MailingListSubscription.mailing_listID.is_in(list_ids),
589 TeamParticipation.teamID.is_in(team_ids),585 TeamParticipation.teamID.is_in(team_ids),
590 MailingList.teamID == TeamParticipation.teamID,586 MailingList.teamID == TeamParticipation.teamID,
591 MailingList.status != MailingListStatus.INACTIVE,587 MailingList.status != MailingListStatus.INACTIVE,
592 MailingListSubscription.email_addressID == None,588 Account.status == AccountStatus.ACTIVE,
593 EmailAddress.status == EmailAddressStatus.PREFERRED,589 Or(
594 Account.status == AccountStatus.ACTIVE))590 And(MailingListSubscription.email_addressID == None,
591 EmailAddress.status == EmailAddressStatus.PREFERRED),
592 EmailAddress.id ==
593 MailingListSubscription.email_addressID),
594 ))
595 # Sort by team name.595 # Sort by team name.
596 by_team = collections.defaultdict(set)596 by_team = collections.defaultdict(set)
597 for email, display_name, team_name in preferred:597 for email, display_name, team_name in preferred:
@@ -599,18 +599,6 @@
599 'Unexpected team name in results: %s' % team_name)599 'Unexpected team name in results: %s' % team_name)
600 value = (display_name, email.lower())600 value = (display_name, email.lower())
601 by_team[team_name].add(value)601 by_team[team_name].add(value)
602 explicit = store.using(*tables).find(
603 (EmailAddress.email, Person.displayname, Team.name),
604 And(MailingListSubscription.mailing_listID.is_in(list_ids),
605 TeamParticipation.teamID.is_in(team_ids),
606 MailingList.status != MailingListStatus.INACTIVE,
607 EmailAddress.id == MailingListSubscription.email_addressID,
608 Account.status == AccountStatus.ACTIVE))
609 for email, display_name, team_name in explicit:
610 assert team_name in team_names, (
611 'Unexpected team name in results: %s' % team_name)
612 value = (display_name, email.lower())
613 by_team[team_name].add(value)
614 # Turn the results into a mapping of lists.602 # Turn the results into a mapping of lists.
615 results = {}603 results = {}
616 for team_name, address_set in by_team.items():604 for team_name, address_set in by_team.items():
617605
=== modified file 'lib/lp/registry/tests/test_mailinglist.py'
--- lib/lp/registry/tests/test_mailinglist.py 2012-12-26 01:32:19 +0000
+++ lib/lp/registry/tests/test_mailinglist.py 2013-01-11 00:08:23 +0000
@@ -640,6 +640,38 @@
640 list_subscribers = [(member.displayname, member.preferredemail.email)]640 list_subscribers = [(member.displayname, member.preferredemail.email)]
641 self.assertEqual(list_subscribers, result[team.name])641 self.assertEqual(list_subscribers, result[team.name])
642642
643 def test_getSubscribedAddresses_excludes_former_participants(self):
644 # getSubscribedAddresses() only includes present participants of
645 # the team, even if they still participate in another team in
646 # the batch (bug #1098170).
647 team1, member1 = self.factory.makeTeamWithMailingListSubscribers(
648 'team1')
649 team2, member2 = self.factory.makeTeamWithMailingListSubscribers(
650 'team2')
651 team1.addMember(member2, reviewer=team1.teamowner)
652 team1.mailing_list.subscribe(member2, address=member2.preferredemail)
653
654 result = self.mailing_list_set.getSubscribedAddresses(
655 ['team1', 'team2'])
656 self.assertEqual(
657 sorted([
658 (member1.displayname, member1.preferredemail.email),
659 (member2.displayname, member2.preferredemail.email)]),
660 result['team1'])
661 self.assertEqual(
662 [(member2.displayname, member2.preferredemail.email)],
663 result['team2'])
664
665 member2.retractTeamMembership(team1, member2)
666 result = self.mailing_list_set.getSubscribedAddresses(
667 ['team1', 'team2'])
668 self.assertEqual(
669 [(member1.displayname, member1.preferredemail.email)],
670 result['team1'])
671 self.assertEqual(
672 [(member2.displayname, member2.preferredemail.email)],
673 result['team2'])
674
643 def test_getSubscribedAddresses_preferredemail_dict_values(self):675 def test_getSubscribedAddresses_preferredemail_dict_values(self):
644 # getSubscribedAddresses() dict values include users who want email to676 # getSubscribedAddresses() dict values include users who want email to
645 # go to their preferred address.677 # go to their preferred address.