← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~sinzui/launchpad/mailing-list-subscribers-0 into lp:launchpad/devel

 

Curtis Hovey has proposed merging lp:~sinzui/launchpad/mailing-list-subscribers-0 into lp:launchpad/devel.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  #568114 User appears twice on +mailing-list-subscribers
  https://bugs.launchpad.net/bugs/568114


This is my branch to walk the list of mailing list subscribers properly.

    lp:~sinzui/launchpad/mailing-list-subscribers-0
    Diff size: 201 (lint demanded a lot of fixes)
    Launchpad bug:
          https://bugs.launchpad.net/bugs/568114
    Test command: ./bin/test -vv -t test_mailinglist
    Pre-implementation: no one
    Target release: 10.09


Walk the list of mailing list subscribers properly
-------------------------------------------------

The table layout is abusing the batch navigator, asking for items by index,
causing a query for each user using the getSubscribers() query as the base,
adding an offset and a limit. Since the getSubscribers intends to return a all
users, sorted by display, asking for OFFSET 1, LIMIT 1 and OFFSET 2, LIMIT 2
are working with a pair of results, not one, and the table builder, expecting
only one, always uses the first one!

Adding the proposed Person.id fixes the issue. Using Person.name is clearer.
But the queries on the page remain outrageous. The table could create a list
of the users in the batch before it starts getting items by index so that a
single query for the user is needed.


Rules
-----

    * Add displayname to the getSubscribers() order by clause to ensure
      only 1 person can match repeated calls to OFFSET and Limit.
    * Listify the current batch before the table starts using indexes to
      get items so that one query is called to get all the subscribers
      for the page.


QA
--

    * Visit https://launchpad.net/~kicad-developers/+mailing-list-subscribers
    * Verify that Alain Portals is listed twice and each links to a different
      user profile.
    * View the source of the page and verify less than 129 queries were
      issues (~79 queries)


Lint
----

Linting changed files:
  lib/lp/registry/browser/team.py
  lib/lp/registry/model/mailinglist.py
  lib/lp/registry/tests/test_mailinglist.py


Test
----

    * lib/lp/registry/tests/test_mailinglist.py
      * Updated an older test for getSubscribers to use current rules
        for unittests.
      * Refactored the test so that another test could share the setup.
      * Added a new test to verify that items are sorted by displayname
        and name.


Implementation
--------------

    * lib/lp/registry/browser/team.py
      * Listified the batch of persons. This is an internal optimisation
        that had not test changes.
    * lib/lp/registry/model/mailinglist.py
      * Added Person.name to the order by clause to ensure a query with
        LIMIT 1 OFFSET X will return a distinct row.
-- 
https://code.launchpad.net/~sinzui/launchpad/mailing-list-subscribers-0/+merge/33279
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~sinzui/launchpad/mailing-list-subscribers-0 into lp:launchpad/devel.
=== modified file 'lib/lp/registry/browser/team.py'
--- lib/lp/registry/browser/team.py	2010-08-12 19:55:29 +0000
+++ lib/lp/registry/browser/team.py	2010-08-21 02:05:58 +0000
@@ -125,7 +125,7 @@
         "name", "visibility", "displayname", "contactemail",
         "teamdescription", "subscriptionpolicy",
         "defaultmembershipperiod", "renewal_policy",
-        "defaultrenewalperiod",  "teamowner",
+        "defaultrenewalperiod", "teamowner",
         ]
     private_prefix = PRIVATE_TEAM_PREFIX
 
@@ -742,7 +742,7 @@
 
     def renderTable(self):
         html = ['<table style="max-width: 80em">']
-        items = self.subscribers.currentBatch()
+        items = list(self.subscribers.currentBatch())
         assert len(items) > 0, (
             "Don't call this method if there are no subscribers to show.")
         # When there are more than 10 items, we use multiple columns, but

=== modified file 'lib/lp/registry/model/mailinglist.py'
--- lib/lp/registry/model/mailinglist.py	2010-06-11 18:43:23 +0000
+++ lib/lp/registry/model/mailinglist.py	2010-08-21 02:05:58 +0000
@@ -352,7 +352,7 @@
                              TeamParticipation.team == self.team,
                              MailingListSubscription.person == Person.id,
                              MailingListSubscription.mailing_list == self)
-        return results.order_by(Person.displayname)
+        return results.order_by(Person.displayname, Person.name)
 
     def subscribe(self, person, address=None):
         """See `IMailingList`."""
@@ -414,8 +414,9 @@
                      MailingListSubscription.personID
                      == EmailAddress.personID),
             # pylint: disable-msg=C0301
-            LeftJoin(MailingList,
-                     MailingList.id == MailingListSubscription.mailing_listID),
+            LeftJoin(
+                MailingList,
+                MailingList.id == MailingListSubscription.mailing_listID),
             LeftJoin(TeamParticipation,
                      TeamParticipation.personID
                      == MailingListSubscription.personID),
@@ -435,8 +436,9 @@
                      MailingListSubscription.email_addressID
                      == EmailAddress.id),
             # pylint: disable-msg=C0301
-            LeftJoin(MailingList,
-                     MailingList.id == MailingListSubscription.mailing_listID),
+            LeftJoin(
+                MailingList,
+                MailingList.id == MailingListSubscription.mailing_listID),
             LeftJoin(TeamParticipation,
                      TeamParticipation.personID
                      == MailingListSubscription.personID),
@@ -627,8 +629,9 @@
                      MailingListSubscription.personID
                      == EmailAddress.personID),
             # pylint: disable-msg=C0301
-            LeftJoin(MailingList,
-                     MailingList.id == MailingListSubscription.mailing_listID),
+            LeftJoin(
+                MailingList,
+                MailingList.id == MailingListSubscription.mailing_listID),
             LeftJoin(TeamParticipation,
                      TeamParticipation.personID
                      == MailingListSubscription.personID),
@@ -641,8 +644,7 @@
             team.id for team in store.find(
                 Person,
                 And(Person.name.is_in(team_names),
-                    Person.teamowner != None))
-            )
+                    Person.teamowner != None)))
         list_ids = set(
             mailing_list.id for mailing_list in store.find(
                 MailingList,
@@ -672,8 +674,9 @@
                      MailingListSubscription.email_addressID
                      == EmailAddress.id),
             # pylint: disable-msg=C0301
-            LeftJoin(MailingList,
-                     MailingList.id == MailingListSubscription.mailing_listID),
+            LeftJoin(
+                MailingList,
+                MailingList.id == MailingListSubscription.mailing_listID),
             LeftJoin(TeamParticipation,
                      TeamParticipation.personID
                      == MailingListSubscription.personID),
@@ -719,8 +722,7 @@
             team.id for team in store.find(
                 Person,
                 And(Person.name.is_in(team_names),
-                    Person.teamowner != None))
-            )
+                    Person.teamowner != None)))
         team_members = store.using(*tables).find(
             (Team.name, Person.displayname, EmailAddress.email),
             And(TeamParticipation.teamID.is_in(team_ids),

=== modified file 'lib/lp/registry/tests/test_mailinglist.py'
--- lib/lp/registry/tests/test_mailinglist.py	2009-07-17 02:25:09 +0000
+++ lib/lp/registry/tests/test_mailinglist.py	2010-08-21 02:05:58 +0000
@@ -1,64 +1,64 @@
 # Copyright 2009 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
+from __future__ import with_statement
+
 __metaclass__ = type
 __all__ = []
 
-
-import unittest
-
-from canonical.launchpad.ftests import login
-from canonical.testing import LaunchpadFunctionalLayer
+from canonical.testing import DatabaseFunctionalLayer
 
 from lp.registry.interfaces.mailinglistsubscription import (
     MailingListAutoSubscribePolicy)
 from lp.registry.interfaces.person import TeamSubscriptionPolicy
-from lp.testing import TestCaseWithFactory
+from lp.testing import login_celebrity, person_logged_in, TestCaseWithFactory
 
 
 class MailingList_getSubscribers_TestCase(TestCaseWithFactory):
     """Tests for `IMailingList`.getSubscribers()."""
 
-    layer = LaunchpadFunctionalLayer
+    layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        # Create a team (tied to a mailing list) with one former member, one
-        # pending member and one active member.
         TestCaseWithFactory.setUp(self)
-        login('foo.bar@xxxxxxxxxxxxx')
+        self.team, self.mailing_list = self.factory.makeTeamAndMailingList(
+            'test-mailinglist', 'team-owner')
+
+    def test_only_active_members_can_be_subscribers(self):
         former_member = self.factory.makePerson()
         pending_member = self.factory.makePerson()
         active_member = self.active_member = self.factory.makePerson()
-        self.team, self.mailing_list = self.factory.makeTeamAndMailingList(
-            'test-mailinglist', 'team-owner')
-        self.team.subscriptionpolicy = TeamSubscriptionPolicy.MODERATED
-
         # Each of our members want to be subscribed to a team's mailing list
         # whenever they join the team.
+        login_celebrity('admin')
         former_member.mailing_list_auto_subscribe_policy = (
             MailingListAutoSubscribePolicy.ALWAYS)
         active_member.mailing_list_auto_subscribe_policy = (
             MailingListAutoSubscribePolicy.ALWAYS)
         pending_member.mailing_list_auto_subscribe_policy = (
             MailingListAutoSubscribePolicy.ALWAYS)
-
+        self.team.subscriptionpolicy = TeamSubscriptionPolicy.MODERATED
         pending_member.join(self.team)
-        self.assertEqual(False, pending_member.inTeam(self.team))
-
         self.team.addMember(former_member, reviewer=self.team.teamowner)
         former_member.leave(self.team)
-        self.assertEqual(False, former_member.inTeam(self.team))
-
         self.team.addMember(active_member, reviewer=self.team.teamowner)
-        self.assertEqual(True, active_member.inTeam(self.team))
-
-    def test_only_active_members_can_be_subscribers(self):
         # Even though our 3 members want to subscribe to the team's mailing
         # list, only the active member is considered a subscriber.
-        subscribers = [self.active_member]
-        self.assertEqual(
-            subscribers, list(self.mailing_list.getSubscribers()))
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromName(__name__)
+        self.assertEqual(
+            [active_member], list(self.mailing_list.getSubscribers()))
+
+    def test_getSubscribers_order(self):
+        person_1 = self.factory.makePerson(name="pb1", displayname="Me")
+        with person_logged_in(person_1):
+            person_1.mailing_list_auto_subscribe_policy = (
+                MailingListAutoSubscribePolicy.ALWAYS)
+            person_1.join(self.team)
+        person_2 = self.factory.makePerson(name="pa2", displayname="Me")
+        with person_logged_in(person_2):
+            person_2.mailing_list_auto_subscribe_policy = (
+                MailingListAutoSubscribePolicy.ALWAYS)
+            person_2.join(self.team)
+        subscribers = self.mailing_list.getSubscribers()
+        self.assertEqual(2, subscribers.count())
+        self.assertEqual(
+            ['pa2', 'pb1'], [person.name for person in subscribers])