launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #18073
Re: [Merge] lp:~cjwatson/launchpad/participation-query-count into lp:launchpad
Review: Approve code
Diff comments:
> === modified file 'lib/lp/registry/browser/person.py'
> --- lib/lp/registry/browser/person.py 2015-03-05 11:39:06 +0000
> +++ lib/lp/registry/browser/person.py 2015-03-09 11:42:57 +0000
> @@ -2028,7 +2028,8 @@
> def label(self):
> return 'Team participation for ' + self.context.displayname
>
> - def _asParticipation(self, membership=None, team=None, via=None):
> + def _asParticipation(self, membership=None, team=None, via=None,
> + mailing_list=None, subscription=None):
> """Return a dict of participation information for the membership.
>
> Method requires membership or team, not both.
> @@ -2057,15 +2058,14 @@
> # The member is a direct member; use the membership data.
> team = membership.team
> datejoined = membership.datejoined
> - if membership.person == team.teamowner:
> + if membership.personID == team.teamownerID:
> role = 'Owner'
> elif membership.status == TeamMembershipStatus.ADMIN:
> role = 'Admin'
> else:
> role = 'Member'
>
> - if team.mailing_list is not None and team.mailing_list.is_usable:
> - subscription = team.mailing_list.getSubscription(self.context)
> + if mailing_list is not None:
> if subscription is None:
> subscribed = 'Not subscribed'
> else:
> @@ -2081,28 +2081,37 @@
> def active_participations(self):
> """Return the participation information for active memberships."""
> paths, memberships = self.context.getPathsToTeams()
> + memberships = [
> + membership for membership in memberships
> + if check_permission('launchpad.View', membership.team)]
> direct_teams = [membership.team for membership in memberships]
> indirect_teams = [
> team for team in paths.keys()
> - if team not in direct_teams]
> + if team not in direct_teams and
> + check_permission('launchpad.View', team)]
> participations = []
>
> + # Bulk-load mailing list subscriptions.
> + subscriptions = getUtility(IMailingListSet).getSubscriptionsForTeams(
> + self.context, direct_teams + indirect_teams)
> +
> # First, create a participation for all direct memberships.
> for membership in memberships:
> - # Add a participation record for the membership if allowed.
> - if check_permission('launchpad.View', membership.team):
> - participations.append(
> - self._asParticipation(membership=membership))
> + mailing_list, subscription = subscriptions.get(
> + membership.team.id, (None, None))
> + participations.append(self._asParticipation(
> + membership=membership, mailing_list=mailing_list,
> + subscription=subscription))
>
> # Second, create a participation for all indirect memberships,
> # using the remaining paths.
> for indirect_team in indirect_teams:
> - if not check_permission('launchpad.View', indirect_team):
> - continue
> + mailing_list, subscription = subscriptions.get(
> + indirect_team.id, (None, None))
> participations.append(
> self._asParticipation(
> - via=paths[indirect_team],
> - team=indirect_team))
> + via=paths[indirect_team], team=indirect_team,
> + mailing_list=mailing_list, subscription=subscription))
> return sorted(participations, key=itemgetter('displayname'))
I wonder if you can refactor this to create a single list of teams, some of which have a membership and some of which have a via? Would remove the duplicated check_permissions etc.
>
> @cachedproperty
>
> === modified file 'lib/lp/registry/browser/tests/test_person.py'
> --- lib/lp/registry/browser/tests/test_person.py 2015-03-05 15:28:11 +0000
> +++ lib/lp/registry/browser/tests/test_person.py 2015-03-09 11:42:57 +0000
> @@ -47,6 +47,7 @@
> from lp.services.identity.interfaces.emailaddress import IEmailAddressSet
> from lp.services.log.logger import FakeLogger
> from lp.services.mail import stub
> +from lp.services.propertycache import clear_property_cache
> from lp.services.verification.interfaces.authtoken import LoginTokenType
> from lp.services.verification.interfaces.logintoken import ILoginTokenSet
> from lp.services.verification.tests.logintoken import get_token_url_from_email
> @@ -68,6 +69,7 @@
> login_person,
> monkey_patch,
> person_logged_in,
> + record_two_runs,
> StormStatementRecorder,
> TestCaseWithFactory,
> )
> @@ -878,6 +880,26 @@
> self.assertEqual(1, len(participations))
> self.assertEqual(True, self.view.has_participations)
>
> + def test_mailing_list_subscriptions_query_count(self):
> + # Additional mailing list subscriptions do not add additional queries.
> + def create_subscriptions():
> + direct_team = self.factory.makeTeam(members=[self.user])
> + direct_list = self.factory.makeMailingList(
> + direct_team, direct_team.teamowner)
> + direct_list.subscribe(self.user)
> + indirect_team = self.factory.makeTeam(members=[direct_team])
> + indirect_list = self.factory.makeMailingList(
> + indirect_team, indirect_team.teamowner)
> + indirect_list.subscribe(self.user)
> +
> + def get_participations():
> + clear_property_cache(self.view)
> + return list(self.view.active_participations)
> +
> + recorder1, recorder2 = record_two_runs(
> + get_participations, create_subscriptions, 5)
> + self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count)))
> +
>
> class TestPersonRelatedPackagesView(TestCaseWithFactory):
> """Test the related software view."""
>
> === modified file 'lib/lp/registry/configure.zcml'
> --- lib/lp/registry/configure.zcml 2015-02-26 11:34:47 +0000
> +++ lib/lp/registry/configure.zcml 2015-03-09 11:42:57 +0000
> @@ -37,6 +37,7 @@
> id
> team
> person
> + personID
> status
> proposed_by
> acknowledged_by
>
> === modified file 'lib/lp/registry/interfaces/mailinglist.py'
> --- lib/lp/registry/interfaces/mailinglist.py 2013-01-07 02:40:55 +0000
> +++ lib/lp/registry/interfaces/mailinglist.py 2015-03-09 11:42:57 +0000
> @@ -1,4 +1,4 @@
> -# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
> +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
> # GNU Affero General Public License version 3 (see the file LICENSE).
>
> """Mailing list interfaces."""
> @@ -464,6 +464,17 @@
> :raises AssertionError: When `team_name` is not a string.
> """
>
> + def getSubscriptionsForTeams(person, teams):
> + """Return a person's subscriptions to usable lists for a set of teams.
> +
> + :param person: An `IPerson`.
> + :param teams: A list of `ITeam`s.
> + :return: A dictionary mapping team IDs to tuples of `IMailingList`
> + IDs and `IMailingListSubscription` IDs; the second element will
> + be None if a team has a list to which the person is not
> + subscribed.
> + """
> +
> def getSubscribedAddresses(team_names):
> """Return the set of subscribed email addresses for members.
>
>
> === modified file 'lib/lp/registry/interfaces/teammembership.py'
> --- lib/lp/registry/interfaces/teammembership.py 2013-01-07 02:40:55 +0000
> +++ lib/lp/registry/interfaces/teammembership.py 2015-03-09 11:42:57 +0000
> @@ -1,4 +1,4 @@
> -# Copyright 2009 Canonical Ltd. This software is licensed under the
> +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
> # GNU Affero General Public License version 3 (see the file LICENSE).
>
> """Team membership interfaces."""
> @@ -131,6 +131,7 @@
> Reference(title=_("Member"), required=True, readonly=True,
> schema=Interface), # Specified in interfaces/person.py.
> exported_as='member')
> + personID = Int(title=_("Person ID"), required=True, readonly=True)
> proposed_by = Attribute(_('Proponent'))
> reviewed_by = Attribute(
> _("The team admin who approved/rejected the member."))
>
> === modified file 'lib/lp/registry/model/mailinglist.py'
> --- lib/lp/registry/model/mailinglist.py 2013-06-20 05:50:00 +0000
> +++ lib/lp/registry/model/mailinglist.py 2015-03-09 11:42:57 +0000
> @@ -1,4 +1,4 @@
> -# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
> +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
> # GNU Affero General Public License version 3 (see the file LICENSE).
>
> __metaclass__ = type
> @@ -100,6 +100,13 @@
> PostedMessageStatus.APPROVAL_PENDING)
>
>
> +USABLE_STATUSES = (
> + MailingListStatus.ACTIVE,
> + MailingListStatus.MODIFIED,
> + MailingListStatus.UPDATING,
> + MailingListStatus.MOD_FAILED)
> +
> +
> class MessageApproval(SQLBase):
> """A held message."""
>
> @@ -344,10 +351,7 @@
> @property
> def is_usable(self):
> """See `IMailingList`."""
> - return self.status in [MailingListStatus.ACTIVE,
> - MailingListStatus.MODIFIED,
> - MailingListStatus.UPDATING,
> - MailingListStatus.MOD_FAILED]
> + return self.status in USABLE_STATUSES
>
> def _get_welcome_message(self):
> return self.welcome_message_
> @@ -547,6 +551,24 @@
> """ % sqlvalues(team_name),
> clauseTables=['Person'])
>
> + def getSubscriptionsForTeams(self, person, teams):
> + """See `IMailingListSet`."""
> + store = IStore(MailingList)
> + team_ids = set(map(operator.attrgetter("id"), teams))
> + lists = dict(store.find(
> + (MailingList.teamID, MailingList.id),
> + MailingList.teamID.is_in(team_ids),
> + MailingList.status.is_in(USABLE_STATUSES)))
> + subscriptions = dict(store.find(
> + (MailingListSubscription.mailing_listID,
> + MailingListSubscription.id),
> + MailingListSubscription.person == person,
> + MailingListSubscription.mailing_listID.is_in(lists.values())))
> + by_team = {}
> + for team, mailing_list in lists.items():
> + by_team[team] = (mailing_list, subscriptions.get(mailing_list))
> + return by_team
Is there a particular reason for this to return two IDs, rather than just the MailingListSubscription object?
> +
> def _getTeamIdsAndMailingListIds(self, team_names):
> """Return a tuple of team and mailing list Ids for the team names."""
> store = IStore(MailingList)
>
--
https://code.launchpad.net/~cjwatson/launchpad/participation-query-count/+merge/252267
Your team Launchpad code reviewers is subscribed to branch lp:launchpad.
References