launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #05052
[Merge] lp:~rvb/launchpad/sync-bug-827608-add-synchronized-packages into lp:launchpad
Raphaël Victor Badin has proposed merging lp:~rvb/launchpad/sync-bug-827608-add-synchronized-packages into lp:launchpad with lp:~rvb/launchpad/sync-bug-827608-populate-ancestor as a prerequisite.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #827608 in Launchpad itself: "Sync requester isn't credited with upload"
https://bugs.launchpad.net/launchpad/+bug/827608
For more details, see:
https://code.launchpad.net/~rvb/launchpad/sync-bug-827608-add-synchronized-packages/+merge/76569
This branch adds a new page (+synchronised-packages) and a new slot on +related-packages to show Synchronised packages for a person.
= Tests =
./bin/test -vvc test_person_view test_latest_synchronised_publishings_with_stats
./bin/test -vvc test_person_view test_view_helper_attributes
./bin/test -vvc test_person_view test_verify_bugs_and_answers_links
./bin/test -vvc test_person_view test_related_software_no_link_synchronised_packages
./bin/test -vvc test_person_view test_related_software_link_synchronised_packages
./bin/test -vvc test_person_view test_related_software_displays_synchronised_packages
./bin/test -vvc test_person test_getLatestSynchronisedPublishings_most_recent_first
./bin/test -vvc test_person test_getLatestSynchronisedPublishings_other_creator
./bin/test -vvc test_person test_getLatestSynchronisedPublishings_latest
./bin/test -vvc test_person test_getLatestSynchronisedPublishings_cross_archive_copies
./bin/test -vvc test_person test_getLatestSynchronisedPublishings_main_archive
= QA =
Sync a source (cross distro, destination archive should be PRIMARY) and make sure the synced source is displayed on https://launchpad.net/~syncer/+related-software and on https://launchpad.net/~synced/+synchronised-packages.
--
https://code.launchpad.net/~rvb/launchpad/sync-bug-827608-add-synchronized-packages/+merge/76569
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/launchpad/sync-bug-827608-add-synchronized-packages into lp:launchpad.
=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py 2011-09-21 01:41:08 +0000
+++ lib/lp/registry/browser/person.py 2011-09-22 12:54:34 +0000
@@ -326,6 +326,7 @@
from lp.soyuz.interfaces.archive import IArchiveSet
from lp.soyuz.interfaces.archivesubscriber import IArchiveSubscriberSet
from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet
+from lp.soyuz.interfaces.publishing import ISourcePackagePublishingHistory
from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
@@ -990,6 +991,13 @@
enabled = bool(self.person.getLatestUploadedPPAPackages())
return Link(target, text, enabled=enabled, icon='info')
+ def synchronised(self):
+ target = '+synchronised-packages'
+ text = 'Synchronised packages'
+ enabled = bool(
+ self.person.getLatestSynchronisedPublishings())
+ return Link(target, text, enabled=enabled, icon='info')
+
def projects(self):
target = '+related-projects'
text = 'Related projects'
@@ -1058,6 +1066,7 @@
'projects',
'activate_ppa',
'maintained',
+ 'synchronised',
'view_ppa_subscriptions',
'ppa',
'oauth_tokens',
@@ -1193,7 +1202,7 @@
usedfor = IPersonRelatedSoftwareMenu
facet = 'overview'
links = ('related_software_summary', 'maintained', 'uploaded', 'ppa',
- 'projects')
+ 'synchronised', 'projects')
@property
def person(self):
@@ -5160,23 +5169,38 @@
return Link('+subscribedquestions', text, summary, icon='question')
-class SourcePackageReleaseWithStats:
- """An ISourcePackageRelease, with extra stats added."""
-
- implements(ISourcePackageRelease)
- delegates(ISourcePackageRelease)
+class BaseWithStats:
+ """An ISourcePackageRelease or a ISourcePackagePublishingHistory,
+ with extra stats added.
+
+ """
+
failed_builds = None
needs_building = None
- def __init__(self, sourcepackage_release, open_bugs, open_questions,
+ def __init__(self, object, open_bugs, open_questions,
failed_builds, needs_building):
- self.context = sourcepackage_release
+ self.context = object
self.open_bugs = open_bugs
self.open_questions = open_questions
self.failed_builds = failed_builds
self.needs_building = needs_building
+class SourcePackageReleaseWithStats(BaseWithStats):
+ """An ISourcePackageRelease, with extra stats added."""
+
+ implements(ISourcePackageRelease)
+ delegates(ISourcePackageRelease)
+
+
+class SourcePackagePublishingHistoryWithStats(BaseWithStats):
+ """An ISourcePackagePublishingHistory, with extra stats added."""
+
+ implements(ISourcePackagePublishingHistory)
+ delegates(ISourcePackagePublishingHistory)
+
+
class PersonRelatedSoftwareView(LaunchpadView):
"""View for +related-software."""
implements(IPersonRelatedSoftwareMenu)
@@ -5302,6 +5326,23 @@
header_message = self._tableHeaderMessage(packages.count())
return results, header_message
+ def _getDecoratedPublishingsSummary(self, publishings):
+ """Helper returning decorated publishings for the summary page.
+
+ :param publishings: A SelectResults that contains the query
+ :return: A tuple of (publishings, header_message).
+
+ The publishings returned are limited to self.max_results_to_display
+ and decorated with the stats required in the page template.
+ The header_message is the text to be displayed at the top of the
+ results table in the template.
+ """
+ # This code causes two SQL queries to be generated.
+ results = self._addStatsToPublishings(
+ publishings[:self.max_results_to_display])
+ header_message = self._tableHeaderMessage(publishings.count())
+ return results, header_message
+
@property
def latest_uploaded_ppa_packages_with_stats(self):
"""Return the sourcepackagereleases uploaded to PPAs by this person.
@@ -5333,6 +5374,17 @@
self.uploaded_packages_header_message = header_message
return results
+ @property
+ def latest_synchronised_publishings_with_stats(self):
+ """Return the latest synchronised publishings, including stats.
+
+ """
+ publishings = self.context.getLatestSynchronisedPublishings()
+ results, header_message = self._getDecoratedPublishingsSummary(
+ publishings)
+ self.synchronised_packages_header_message = header_message
+ return results
+
def _calculateBuildStats(self, package_releases):
"""Calculate failed builds and needs_build state.
@@ -5394,6 +5446,38 @@
needs_build_by_package[package])
for package in package_releases]
+ def _addStatsToPublishings(self, publishings):
+ """Add stats to the given publishings, and return them."""
+ filtered_spphs = [
+ spph for spph in publishings if
+ check_permission('launchpad.View', spph)]
+ distro_packages = [
+ spph.meta_sourcepackage.distribution_sourcepackage
+ for spph in filtered_spphs]
+ package_bug_counts = getUtility(IBugTaskSet).getBugCountsForPackages(
+ self.user, distro_packages)
+ open_bugs = {}
+ for bug_count in package_bug_counts:
+ distro_package = bug_count['package']
+ open_bugs[distro_package] = bug_count['open']
+
+ question_set = getUtility(IQuestionSet)
+ package_question_counts = question_set.getOpenQuestionCountByPackages(
+ distro_packages)
+
+ builds_by_package, needs_build_by_package = self._calculateBuildStats(
+ [spph.sourcepackagerelease for spph in filtered_spphs])
+
+ return [
+ SourcePackagePublishingHistoryWithStats(
+ spph,
+ open_bugs[spph.meta_sourcepackage.distribution_sourcepackage],
+ package_question_counts[
+ spph.meta_sourcepackage.distribution_sourcepackage],
+ builds_by_package[spph.sourcepackagerelease],
+ needs_build_by_package[spph.sourcepackagerelease])
+ for spph in filtered_spphs]
+
def setUpBatch(self, packages):
"""Set up the batch navigation for the page being viewed.
@@ -5454,6 +5538,30 @@
return "PPA packages"
+class PersonSynchronisedPackagesView(PersonRelatedSoftwareView):
+ """View for +synchronised-packages."""
+ _max_results_key = 'default_batch_size'
+
+ def initialize(self):
+ """Set up the batch navigation."""
+ publishings = self.context.getLatestSynchronisedPublishings()
+ self.setUpBatch(publishings)
+
+ def setUpBatch(self, publishings):
+ """Set up the batch navigation for the page being viewed.
+
+ This method creates the BatchNavigator and converts its
+ results batch into a list of decorated sourcepackagepublishinghistory.
+ """
+ self.batchnav = BatchNavigator(publishings, self.request)
+ publishings_batch = list(self.batchnav.currentBatch())
+ self.batch = self._addStatsToPublishings(publishings_batch)
+
+ @property
+ def page_title(self):
+ return "Synchronised packages"
+
+
class PersonRelatedProjectsView(PersonRelatedSoftwareView):
"""View for +related-projects."""
_max_results_key = 'default_batch_size'
=== modified file 'lib/lp/registry/browser/tests/test_person_view.py'
--- lib/lp/registry/browser/tests/test_person_view.py 2011-09-21 01:41:08 +0000
+++ lib/lp/registry/browser/tests/test_person_view.py 2011-09-22 12:54:34 +0000
@@ -1,15 +1,17 @@
-# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
import doctest
+import soupmatchers
from storm.expr import LeftJoin
from storm.store import Store
from testtools.matchers import (
DocTestMatches,
LessThan,
+ Not,
)
import transaction
from zope.component import getUtility
@@ -23,6 +25,7 @@
from canonical.launchpad.interfaces.authtoken import LoginTokenType
from canonical.launchpad.interfaces.logintoken import ILoginTokenSet
from canonical.launchpad.testing.pages import extract_text
+from canonical.launchpad.webapp import canonical_url
from canonical.launchpad.webapp.interfaces import ILaunchBag
from canonical.launchpad.webapp.servers import LaunchpadTestRequest
from canonical.testing.layers import (
@@ -45,6 +48,7 @@
PersonVisibility,
)
from lp.registry.interfaces.persontransferjob import IPersonMergeJobSource
+from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.teammembership import (
ITeamMembershipSet,
TeamMembershipStatus,
@@ -528,20 +532,31 @@
self.warty = self.ubuntu.getSeries('warty')
self.view = create_initialized_view(self.user, '+related-software')
- def publishSource(self, archive, maintainer):
+ def publishSources(self, archive, maintainer):
publisher = SoyuzTestPublisher()
publisher.person = self.user
login('foo.bar@xxxxxxxxxxxxx')
+ spphs = []
for count in range(0, self.view.max_results_to_display + 3):
source_name = "foo" + str(count)
- publisher.getPubSource(
+ spph = publisher.getPubSource(
sourcename=source_name,
status=PackagePublishingStatus.PUBLISHED,
archive=archive,
maintainer=maintainer,
creator=self.user,
distroseries=self.warty)
+ spphs.append(spph)
login(ANONYMOUS)
+ return spphs
+
+ def copySources(self, spphs, copier, dest_distroseries):
+ self.copier = self.factory.makePerson()
+ for spph in spphs:
+ spph.copyTo(
+ dest_distroseries, creator=copier,
+ pocket=PackagePublishingPocket.UPDATES,
+ archive=dest_distroseries.main_archive)
def test_view_helper_attributes(self):
# Verify view helper attributes.
@@ -563,24 +578,34 @@
def test_latest_uploaded_ppa_packages_with_stats(self):
# Verify number of PPA packages to display.
ppa = self.factory.makeArchive(owner=self.user)
- self.publishSource(ppa, self.user)
+ self.publishSources(ppa, self.user)
count = len(self.view.latest_uploaded_ppa_packages_with_stats)
self.assertEqual(self.view.max_results_to_display, count)
def test_latest_maintained_packages_with_stats(self):
# Verify number of maintained packages to display.
- self.publishSource(self.warty.main_archive, self.user)
+ self.publishSources(self.warty.main_archive, self.user)
count = len(self.view.latest_maintained_packages_with_stats)
self.assertEqual(self.view.max_results_to_display, count)
def test_latest_uploaded_nonmaintained_packages_with_stats(self):
# Verify number of non maintained packages to display.
maintainer = self.factory.makePerson()
- self.publishSource(self.warty.main_archive, maintainer)
+ self.publishSources(self.warty.main_archive, maintainer)
count = len(
self.view.latest_uploaded_but_not_maintained_packages_with_stats)
self.assertEqual(self.view.max_results_to_display, count)
+ def test_latest_synchronised_publishings_with_stats(self):
+ # Verify number of non synchronised publishings to display.
+ creator = self.factory.makePerson()
+ spphs = self.publishSources(self.warty.main_archive, creator)
+ dest_distroseries = self.factory.makeDistroSeries()
+ self.copySources(spphs, self.user, dest_distroseries)
+ count = len(
+ self.view.latest_synchronised_publishings_with_stats)
+ self.assertEqual(self.view.max_results_to_display, count)
+
class TestPersonMaintainedPackagesView(TestCaseWithFactory):
"""Test the maintained packages view."""
@@ -654,6 +679,55 @@
self.view.max_results_to_display)
+class TestPersonSynchronisedPackagesView(TestCaseWithFactory):
+ """Test the synchronised packages view."""
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(TestPersonSynchronisedPackagesView, self).setUp()
+ user = self.factory.makePerson()
+ archive = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)
+ spr = self.factory.makeSourcePackageRelease(
+ creator=user, archive=archive)
+ spph = self.factory.makeSourcePackagePublishingHistory(
+ sourcepackagerelease=spr, archive=archive)
+ self.copier = self.factory.makePerson()
+ dest_distroseries = self.factory.makeDistroSeries()
+ self.copied_spph = spph.copyTo(
+ dest_distroseries, creator=self.copier,
+ pocket=PackagePublishingPocket.UPDATES,
+ archive=dest_distroseries.main_archive)
+ self.view = create_initialized_view(
+ self.copier, '+synchronised-packages')
+
+ def test_view_helper_attributes(self):
+ # Verify view helper attributes.
+ self.assertEqual('Synchronised packages', self.view.page_title)
+ self.assertEqual('default_batch_size', self.view._max_results_key)
+ self.assertEqual(
+ config.launchpad.default_batch_size,
+ self.view.max_results_to_display)
+
+ def test_verify_bugs_and_answers_links(self):
+ # Verify the links for bugs and answers point to locations that
+ # exist.
+ html = self.view()
+ expected_base = '/%s/+source/%s' % (
+ self.copied_spph.distroseries.distribution.name,
+ self.copied_spph.source_package_name)
+ bug_matcher = soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ 'Bugs link', 'a',
+ attrs={'href': expected_base + '/+bugs'}))
+ question_matcher = soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ 'Questions link', 'a',
+ attrs={'href': expected_base + '/+questions'}))
+ self.assertThat(html, bug_matcher)
+ self.assertThat(html, question_matcher)
+
+
class TestPersonRelatedProjectsView(TestCaseWithFactory):
"""Test the maintained packages view."""
@@ -718,6 +792,75 @@
self.build.id) in html)
+class TestPersonRelatedSoftwareSynchronisedPackages(TestCaseWithFactory):
+ """The related software views display links to synchronised packages."""
+
+ layer = LaunchpadFunctionalLayer
+
+ def setUp(self):
+ super(TestPersonRelatedSoftwareSynchronisedPackages, self).setUp()
+ self.user = self.factory.makePerson()
+ self.spph = self.factory.makeSourcePackagePublishingHistory()
+
+ def createCopiedSource(self, copier, spph):
+ self.copier = self.factory.makePerson()
+ dest_distroseries = self.factory.makeDistroSeries()
+ return spph.copyTo(
+ dest_distroseries, creator=copier,
+ pocket=PackagePublishingPocket.UPDATES,
+ archive=dest_distroseries.main_archive)
+
+ def getLinkToSynchronisedMatcher(self):
+ person_url = canonical_url(self.user)
+ return soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ 'Synchronised packages link', 'a',
+ attrs={'href': person_url + '/+synchronised-packages'},
+ text='Synchronised packages'))
+
+ def test_related_software_no_link_synchronised_packages(self):
+ # No link to the synchronised packages page if no synchronised
+ # packages.
+ view = create_view(self.user, name='+related-software')
+ synced_package_link_matcher = self.getLinkToSynchronisedMatcher()
+ self.assertThat(view(), Not(synced_package_link_matcher))
+
+ def test_related_software_link_synchronised_packages(self):
+ # If this person has synced packages, the link to the synchronised
+ # packages page is present.
+ self.createCopiedSource(self.user, self.spph)
+ view = create_view(self.user, name='+related-software')
+ synced_package_link_matcher = self.getLinkToSynchronisedMatcher()
+ self.assertThat(view(), synced_package_link_matcher)
+
+ def test_related_software_displays_synchronised_packages(self):
+ copied_spph = self.createCopiedSource(self.user, self.spph)
+ view = create_view(self.user, name='+related-software')
+ synced_packages_title = soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ 'Synchronised packages title', 'h2',
+ text='Synchronised packages'))
+ expected_base = '/%s/+source/%s' % (
+ copied_spph.distroseries.distribution.name,
+ copied_spph.source_package_name)
+ source_link = soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ 'Source package link', 'a',
+ text=copied_spph.sourcepackagerelease.name,
+ attrs={'href': expected_base}))
+ version_url = (expected_base + '/%s' %
+ copied_spph.sourcepackagerelease.version)
+ version_link = soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ 'Source package version link', 'a',
+ text=copied_spph.sourcepackagerelease.version,
+ attrs={'href': version_url}))
+
+ self.assertThat(view(), synced_packages_title)
+ self.assertThat(view(), source_link)
+ self.assertThat(view(), version_link)
+
+
class TestPersonDeactivateAccountView(TestCaseWithFactory):
"""Tests for the PersonDeactivateAccountView."""
=== modified file 'lib/lp/registry/interfaces/person.py'
--- lib/lp/registry/interfaces/person.py 2011-09-21 01:41:08 +0000
+++ lib/lp/registry/interfaces/person.py 2011-09-22 12:54:34 +0000
@@ -1240,6 +1240,14 @@
for each source package name, distribution series combination.
"""
+ def getLatestSynchronisedPublishings(self):
+ """Return `SourcePackagePublishingHistory`s synchronised by this
+ person.
+
+ This method will only include the latest publishings for each source
+ package name, distribution series combination.
+ """
+
def getLatestUploadedButNotMaintainedPackages():
"""Return `SourcePackageRelease`s created by this person but
not maintained by him.
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2011-09-21 01:41:08 +0000
+++ lib/lp/registry/model/person.py 2011-09-22 12:54:34 +0000
@@ -297,6 +297,7 @@
from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet
from lp.soyuz.interfaces.archivesubscriber import IArchiveSubscriberSet
from lp.soyuz.model.archive import Archive
+from lp.soyuz.model.publishing import SourcePackagePublishingHistory
from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
from lp.translations.model.hastranslationimports import (
HasTranslationImportsMixin,
@@ -2586,6 +2587,40 @@
"""See `IPerson`."""
return self._latestSeriesQuery()
+ def getLatestSynchronisedPublishings(self):
+ """See `IPerson`."""
+ query = """
+ SourcePackagePublishingHistory.id IN (
+ SELECT DISTINCT ON (spph.distroseries,
+ spr.sourcepackagename)
+ spph.id
+ FROM
+ SourcePackagePublishingHistory as spph, archive,
+ SourcePackagePublishingHistory as ancestor_spph,
+ SourcePackageRelease as spr
+ WHERE
+ spph.sourcepackagerelease = spr.id AND
+ spph.creator = %(creator)s AND
+ spph.ancestor = ancestor_spph.id AND
+ spph.archive = archive.id AND
+ ancestor_spph.archive != spph.archive AND
+ archive.purpose = %(archive_purpose)s
+ ORDER BY spph.distroseries,
+ spr.sourcepackagename,
+ spph.datecreated DESC,
+ spph.id DESC
+ )
+ """ % dict(
+ creator=quote(self.id),
+ archive_purpose=quote(ArchivePurpose.PRIMARY),
+ )
+
+ return SourcePackagePublishingHistory.select(
+ query,
+ orderBy=['-SourcePackagePublishingHistory.datecreated',
+ '-SourcePackagePublishingHistory.id'],
+ prejoins=['sourcepackagerelease', 'archive'])
+
def getLatestUploadedButNotMaintainedPackages(self):
"""See `IPerson`."""
return self._latestSeriesQuery(uploader_only=True)
=== modified file 'lib/lp/registry/templates/person-macros.pt'
--- lib/lp/registry/templates/person-macros.pt 2011-09-21 01:41:08 +0000
+++ lib/lp/registry/templates/person-macros.pt 2011-09-22 12:54:34 +0000
@@ -181,6 +181,68 @@
</tr>
</metal:macro>
+<metal:macro define-macro="spphs-rows">
+
+ <tal:comment replace="nothing">
+ This macro expects the following variables defined:
+ :spphs: A list of SourcePackagePublishingHistory objects
+ </tal:comment>
+
+ <tr tal:repeat="spph spphs">
+ <tal:define define="spr spph/sourcepackagerelease;
+ distroseries spph/distroseries">
+ <td>
+ <a tal:attributes="href string:${distroseries/distribution/fmt:url}/+source/${spr/name}"
+ class="distrosrcpackage"
+ tal:content="spr/sourcepackagename/name">
+ </a>
+ </td>
+ <td>
+ <a tal:attributes="href string:${distroseries/fmt:url}/+source/${spr/name}"
+ class="distroseriessrcpackage"
+ tal:content="distroseries/fullseriesname">
+ </a>
+ </td>
+ <td>
+ <a tal:attributes="href string:${distroseries/distribution/fmt:url}/+source/${spr/name}/${spr/version}"
+ class="distrosrcpackagerelease"
+ tal:content="spr/version">
+ </a>
+ </td>
+ <td
+ tal:attributes="title spph/datecreated/fmt:datetime"
+ tal:content="spph/datecreated/fmt:approximatedate">
+ 2005-10-24
+ </td>
+ <td>
+ <tal:needs_building condition="spph/needs_building">
+ Not yet built
+ </tal:needs_building>
+ <tal:built condition="not: spph/needs_building">
+ <tal:failed repeat="build spph/failed_builds">
+ <a tal:attributes="href build/fmt:url"
+ tal:content="build/distro_arch_series/architecturetag" />
+ </tal:failed>
+ <tal:not_failed condition="not: spph/failed_builds">
+ None
+ </tal:not_failed>
+ </tal:built>
+ </td>
+ <td style="text-align: right">
+ <a tal:attributes="href string:${spph/meta_sourcepackage/distribution_sourcepackage/fmt:url}/+bugs"
+ tal:content="spph/open_bugs">
+ </a>
+ </td>
+ <td style="text-align: right">
+ <a tal:attributes="href string:${spph/meta_sourcepackage/distribution_sourcepackage/fmt:url}/+questions"
+ tal:content="spph/open_questions">
+ </a>
+ </td>
+ </tal:define>
+ </tr>
+</metal:macro>
+
+
<metal:macro define-macro="private-team-js">
<tal:comment replace="nothing">
This macro inserts the javascript necessary to automatically insert the
=== modified file 'lib/lp/registry/templates/person-related-software-navlinks.pt'
--- lib/lp/registry/templates/person-related-software-navlinks.pt 2009-10-16 00:47:43 +0000
+++ lib/lp/registry/templates/person-related-software-navlinks.pt 2011-09-22 12:54:34 +0000
@@ -22,6 +22,10 @@
tal:condition="link/enabled"
tal:content="structure link/fmt:link" />
<li
+ tal:define="link view/menu:navigation/synchronised"
+ tal:condition="link/enabled"
+ tal:content="structure link/fmt:link" />
+ <li
tal:define="link view/menu:navigation/projects"
tal:condition="link/enabled"
tal:content="structure link/fmt:link" />
=== modified file 'lib/lp/registry/templates/person-related-software.pt'
--- lib/lp/registry/templates/person-related-software.pt 2011-09-21 01:41:08 +0000
+++ lib/lp/registry/templates/person-related-software.pt 2011-09-22 12:54:34 +0000
@@ -99,6 +99,32 @@
</div>
</tal:ppa-packages>
+ <tal:synchronised-packages
+ define="spphs view/latest_synchronised_publishings_with_stats"
+ condition="spphs">
+
+ <div class="top-portlet">
+ <h2>Synchronised packages</h2>
+
+ <tal:message replace="view/synchronised_packages_header_message"/>
+ <table class="listing">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Uploaded to</th>
+ <th>Version</th>
+ <th>When</th>
+ <th>Failures</th>
+ <th>Bugs</th>
+ <th>Questions</th>
+ </tr>
+ </thead>
+
+ <div metal:use-macro="context/@@+person-macros/spphs-rows" />
+ </table>
+ </div>
+ </tal:synchronised-packages>
+
</div><!--id packages-->
<div id="projects" class="top-portlet">
=== modified file 'lib/lp/registry/tests/test_person.py'
--- lib/lp/registry/tests/test_person.py 2011-09-21 01:41:08 +0000
+++ lib/lp/registry/tests/test_person.py 2011-09-22 12:54:34 +0000
@@ -61,6 +61,7 @@
PersonVisibility,
)
from lp.registry.interfaces.personnotification import IPersonNotificationSet
+from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.product import IProductSet
from lp.registry.model.karma import (
KarmaCategory,
@@ -437,6 +438,88 @@
list(user.getBugSubscriberPackages())
self.assertThat(recorder, HasQueryCount(Equals(1)))
+ def createCopiedPackage(self, spph, copier, dest_distroseries=None,
+ dest_archive=None):
+ if dest_distroseries is None:
+ dest_distroseries = self.factory.makeDistroSeries()
+ if dest_archive is None:
+ dest_archive = dest_distroseries.main_archive
+ return spph.copyTo(
+ dest_distroseries, creator=copier,
+ pocket=PackagePublishingPocket.UPDATES,
+ archive=dest_archive)
+
+ def test_getLatestSynchronisedPublishings_most_recent_first(self):
+ # getLatestSynchronisedPublishings returns the latest copies sorted
+ # by most recent first.
+ spph = self.factory.makeSourcePackagePublishingHistory()
+ copier = self.factory.makePerson()
+ copied_spph1 = self.createCopiedPackage(spph, copier)
+ copied_spph2 = self.createCopiedPackage(spph, copier)
+ synchronised_spphs = copier.getLatestSynchronisedPublishings()
+
+ self.assertContentEqual(
+ [copied_spph2, copied_spph1],
+ synchronised_spphs)
+
+ def test_getLatestSynchronisedPublishings_other_creator(self):
+ spph = self.factory.makeSourcePackagePublishingHistory()
+ copier = self.factory.makePerson()
+ self.createCopiedPackage(spph, copier)
+ someone_else = self.factory.makePerson()
+ synchronised_spphs = someone_else.getLatestSynchronisedPublishings()
+
+ self.assertEqual(
+ 0,
+ synchronised_spphs.count())
+
+ def test_getLatestSynchronisedPublishings_latest(self):
+ # getLatestSynchronisedPublishings returns only the latest copy of
+ # a package in a distroseries
+ spph = self.factory.makeSourcePackagePublishingHistory()
+ copier = self.factory.makePerson()
+ dest_distroseries = self.factory.makeDistroSeries()
+ self.createCopiedPackage(
+ spph, copier, dest_distroseries)
+ copied_spph2 = self.createCopiedPackage(
+ spph, copier, dest_distroseries)
+ synchronised_spphs = copier.getLatestSynchronisedPublishings()
+
+ self.assertContentEqual(
+ [copied_spph2],
+ synchronised_spphs)
+
+ def test_getLatestSynchronisedPublishings_cross_archive_copies(self):
+ # getLatestSynchronisedPublishings returns only the copies copied
+ # cross archive.
+ spph = self.factory.makeSourcePackagePublishingHistory()
+ copier = self.factory.makePerson()
+ dest_distroseries2 = self.factory.makeDistroSeries(
+ distribution=spph.distroseries.distribution)
+ self.createCopiedPackage(
+ spph, copier, dest_distroseries2)
+ synchronised_spphs = copier.getLatestSynchronisedPublishings()
+
+ self.assertEqual(
+ 0,
+ synchronised_spphs.count())
+
+ def test_getLatestSynchronisedPublishings_main_archive(self):
+ # getLatestSynchronisedPublishings returns only the copies copied in
+ # a primary archive (as opposed to a ppa).
+ spph = self.factory.makeSourcePackagePublishingHistory()
+ copier = self.factory.makePerson()
+ dest_distroseries = self.factory.makeDistroSeries()
+ ppa = self.factory.makeArchive(
+ distribution=dest_distroseries.distribution)
+ self.createCopiedPackage(
+ spph, copier, dest_distroseries, ppa)
+ synchronised_spphs = copier.getLatestSynchronisedPublishings()
+
+ self.assertEqual(
+ 0,
+ synchronised_spphs.count())
+
class TestPersonStates(TestCaseWithFactory):
=== modified file 'lib/lp/soyuz/browser/configure.zcml'
--- lib/lp/soyuz/browser/configure.zcml 2011-09-19 14:29:47 +0000
+++ lib/lp/soyuz/browser/configure.zcml 2011-09-22 12:54:34 +0000
@@ -686,6 +686,12 @@
name="+ppa-packages"
template="../templates/person-ppa-packages.pt"/>
<browser:page
+ for="lp.registry.interfaces.person.IPerson"
+ permission="zope.Public"
+ class="lp.registry.browser.person.PersonSynchronisedPackagesView"
+ name="+synchronised-packages"
+ template="../templates/person-synchronised-packages.pt"/>
+ <browser:page
name="+archivesubscriptions"
for="lp.registry.interfaces.person.IPerson"
class="lp.soyuz.browser.archivesubscription.PersonArchiveSubscriptionsView"
=== added file 'lib/lp/soyuz/templates/person-synchronised-packages.pt'
--- lib/lp/soyuz/templates/person-synchronised-packages.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/templates/person-synchronised-packages.pt 2011-09-22 12:54:34 +0000
@@ -0,0 +1,59 @@
+
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ metal:use-macro="view/macro:page/main_only"
+ i18n:domain="launchpad"
+>
+
+<body>
+
+<div metal:fill-slot="heading">
+ <h1 tal:content="view/page_title"/>
+</div>
+
+<div metal:fill-slot="main">
+ <div class="top-portlet">
+ <tal:navlinks replace="structure context/@@+related-software-navlinks"/>
+ </div>
+
+ <div id="packages" class="top-portlet">
+
+ <tal:navigation_top
+ replace="structure view/batchnav/@@+navigation-links-upper" />
+
+ <tal:synchronised-packages
+ define="spphs view/batch">
+
+ <table class="listing" tal:condition="spphs">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Uploaded to</th>
+ <th>Version</th>
+ <th>When</th>
+ <th>Failures</th>
+ <th>Bugs</th>
+ <th>Questions</th>
+ </tr>
+ </thead>
+ <tbody>
+ <div metal:use-macro="context/@@+person-macros/spphs-rows" />
+ </tbody>
+ </table>
+
+ <tal:navigation_bottom
+ replace="structure view/batchnav/@@+navigation-links-lower" />
+
+ <tal:no_packages condition="not: spphs">
+ <tal:name replace="context/fmt:displayname"/> has not synchronised any packages.
+ </tal:no_packages>
+
+ </tal:synchronised-packages>
+ </div>
+</div>
+
+</body>
+</html>