launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #00368
[Merge] lp:~james-w/launchpad/archive-collection into lp:launchpad/devel
James Westby has proposed merging lp:~james-w/launchpad/archive-collection into lp:launchpad/devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Hi,
Here's a branch to add an ArchiveCollection based
on the new generic Collection.
It's fairly standalone right now, though I ported a
few easy queries in ArchiveSet methods over to it.
Hopefully once it is available it will see more use.
Thanks,
James
--
https://code.launchpad.net/~james-w/launchpad/archive-collection/+merge/31499
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~james-w/launchpad/archive-collection into lp:launchpad/devel.
=== modified file 'lib/lp/services/database/collection.py'
--- lib/lp/services/database/collection.py 2010-07-16 14:10:18 +0000
+++ lib/lp/services/database/collection.py 2010-08-02 00:41:06 +0000
@@ -68,9 +68,6 @@
base_tables = list(base.tables)
self.store = kwargs.get('store')
- if self.store is None:
- self.store = getUtility(IStoreSelector).get(
- MAIN_STORE, DEFAULT_FLAVOR)
self.tables = (
starting_tables + base_tables +
@@ -118,6 +115,9 @@
If no values are requested, this selects the type of object that
the Collection is a collection of.
"""
+ if self.store is None:
+ self.store = getUtility(IStoreSelector).get(
+ MAIN_STORE, DEFAULT_FLAVOR)
if len(self.tables) == 0:
source = self.store
else:
=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml 2010-06-30 17:35:36 +0000
+++ lib/lp/soyuz/configure.zcml 2010-08-02 00:41:06 +0000
@@ -67,6 +67,19 @@
interface="lp.soyuz.interfaces.archivearch.IArchiveArchSet"/>
</securedutility>
+ <!-- ArchiveCollection -->
+ <securedutility
+ class="lp.soyuz.model.archivecollection.ArchiveCollection"
+ provides="lp.soyuz.interfaces.archivecollection.IAllArchives">
+ <allow
+ interface="lp.soyuz.interfaces.archivecollection.IAllArchives" />
+ </securedutility>
+
+ <class class="lp.soyuz.model.archivecollection.ArchiveCollection">
+ <allow
+ interface="lp.soyuz.interfaces.archivecollection.IAllArchives" />
+ </class>
+
<!-- DistroSeriesPackageCache -->
<class
class="canonical.launchpad.database.DistroSeriesPackageCache">
=== added file 'lib/lp/soyuz/interfaces/archivecollection.py'
--- lib/lp/soyuz/interfaces/archivecollection.py 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/interfaces/archivecollection.py 2010-08-02 00:41:06 +0000
@@ -0,0 +1,70 @@
+# Copyright 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""A collection of archives.
+
+See `IArchiveCollection` for more details.
+"""
+
+__metaclass__ = type
+__all__ = [
+ 'IAllArchives',
+ 'IArchiveCollection',
+ ]
+
+from zope.interface import Interface
+
+
+class IArchiveCollection(Interface):
+ """A collection of archives.
+
+ An `IArchiveCollection` is an immutable collection of archives.
+
+ You can use the methods it provides to get a new collection with
+ a filtered set of archives.
+
+ Finally you can call `select` to get a ResultSet from the collection.
+ """
+
+ def select(*args):
+ """Return a ResultSet for this collection, with values set to args."""
+
+ def withPurpose(purpose):
+ """Restrict the archives to only the given purpose."""
+
+ def withPurposes(purposes):
+ """Restrict the archives to one of the given purposes."""
+
+ def withStatus(status):
+ """Restrict the archives to only the given status."""
+
+ def withDistribution(distribution):
+ """Restrict the archives to only the given distribution."""
+
+ def ownedBy(owner):
+ """Restrict the archives to only those owned by the given person."""
+
+ def withName(name):
+ """Restrict the archives to only those with the given name."""
+
+ def isEnabled(enabled=True):
+ """Restrict the archives to only those enabled as passed."""
+
+ def isPrivate(private=True):
+ """Restrict the archives to only those with the given privacy."""
+
+ def isRequireVirtualized(require_virtualized=True):
+ """Restrict the archives to those requiring virtualization."""
+
+ def isCommercial(commercial=True):
+ """Restrict the archives to commercial as given."""
+
+ def visibleByUser(user):
+ """Restrict the archives to those visible by the given user.
+
+ :param user: a Person, or None for public archives.
+ """
+
+
+class IAllArchives(IArchiveCollection):
+ """An `IArchiveCollection` representing all archives in Launchpad."""
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2010-07-27 12:53:24 +0000
+++ lib/lp/soyuz/model/archive.py 2010-08-02 00:41:06 +0000
@@ -40,6 +40,7 @@
from lp.soyuz.adapters.packagelocation import PackageLocation
from canonical.launchpad.components.tokens import (
create_unique_token_for_table)
+from lp.soyuz.interfaces.archivecollection import IAllArchives
from lp.soyuz.model.archivedependency import ArchiveDependency
from lp.soyuz.model.archiveauthtoken import ArchiveAuthToken
from lp.soyuz.model.archivesubscriber import ArchiveSubscriber
@@ -1810,13 +1811,10 @@
def getPPAOwnedByPerson(self, person, name=None):
"""See `IArchiveSet`."""
- store = Store.of(person)
- clause = [
- Archive.purpose == ArchivePurpose.PPA,
- Archive.owner == person]
+ ppas = getUtility(IAllArchives).withPurpose(ArchivePurpose.PPA)
if name is not None:
- clause.append(Archive.name == name)
- result = store.find(Archive, *clause).order_by(Archive.id).first()
+ ppas = ppas.withName(name)
+ result = ppas.ownedBy(person).select().order_by(Archive.id).first()
if name is not None and result is None:
raise NoSuchPPA(name)
return result
@@ -1954,79 +1952,34 @@
def getPrivatePPAs(self):
"""See `IArchiveSet`."""
- store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
- return store.find(
- Archive,
- Archive.private == True,
- Archive.purpose == ArchivePurpose.PPA)
+ ppas = getUtility(IAllArchives).withPurpose(ArchivePurpose.PPA)
+ return ppas.isPrivate().select()
def getCommercialPPAs(self):
"""See `IArchiveSet`."""
- store = IStore(Archive)
- return store.find(
- Archive,
- Archive.commercial == True,
- Archive.purpose == ArchivePurpose.PPA)
+ ppas = getUtility(IAllArchives).withPurpose(ArchivePurpose.PPA)
+ return ppas.isCommercial().select()
def getArchivesForDistribution(self, distribution, name=None,
purposes=None, user=None,
exclude_disabled=True):
"""See `IArchiveSet`."""
- extra_exprs = []
+ archives = getUtility(IAllArchives).withDistribution(distribution)
- # If a single purpose is passed in, convert it into a tuple,
- # otherwise assume a list was passed in.
if purposes in ArchivePurpose:
- purposes = (purposes,)
-
- if purposes:
- extra_exprs.append(Archive.purpose.is_in(purposes))
+ archives = archives.withPurpose(purposes)
+ elif purposes:
+ archives = archives.withPurposes(*purposes)
if name is not None:
- extra_exprs.append(Archive.name == name)
-
- public_archive = And(Archive.private == False,
- Archive._enabled == True)
-
- if user is not None:
- admins = getUtility(ILaunchpadCelebrities).admin
- if not user.inTeam(admins):
- # Enforce privacy-awareness for logged-in, non-admin users,
- # so that they can only see the private archives that they're
- # allowed to see.
-
- # Create a subselect to capture all the teams that are
- # owners of archives AND the user is a member of:
- user_teams_subselect = Select(
- TeamParticipation.teamID,
- where=And(
- TeamParticipation.personID == user.id,
- TeamParticipation.teamID == Archive.ownerID))
-
- # Append the extra expression to capture either public
- # archives, or archives owned by the user, or archives
- # owned by a team of which the user is a member:
- # Note: 'Archive.ownerID == user.id'
- # is unnecessary below because there is a TeamParticipation
- # entry showing that each person is a member of the "team"
- # that consists of themselves.
-
- # FIXME: Include private PPA's if user is an uploader
- extra_exprs.append(
- Or(public_archive,
- Archive.ownerID.is_in(user_teams_subselect)))
- else:
- # Anonymous user; filter to include only public archives in
- # the results.
- extra_exprs.append(public_archive)
+ archives = archives.withName(name)
+
+ archives = archives.visibleByUser(user)
if exclude_disabled:
- extra_exprs.append(Archive._enabled == True)
+ archives = archives.isEnabled()
- query = Store.of(distribution).find(
- Archive,
- Archive.distribution == distribution,
- *extra_exprs)
+ query = archives.select()
return query.order_by(Archive.name)
=== added file 'lib/lp/soyuz/model/archivecollection.py'
--- lib/lp/soyuz/model/archivecollection.py 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/model/archivecollection.py 2010-08-02 00:41:06 +0000
@@ -0,0 +1,86 @@
+# Copyright 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+from storm.expr import And, Or, Select
+from zope.component import getUtility
+from zope.interface import implements
+
+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
+from lp.registry.model.teammembership import TeamParticipation
+from lp.services.database.collection import Collection
+from lp.soyuz.interfaces.archivecollection import IArchiveCollection
+from lp.soyuz.model.archive import Archive
+
+
+class ArchiveCollection(Collection):
+
+ implements(IArchiveCollection)
+
+ starting_table = Archive
+
+ def withPurpose(self, purpose):
+ return self.refine(Archive.purpose == purpose)
+
+ def withPurposes(self, *purposes):
+ return self.refine(Archive.purpose.is_in(purposes))
+
+ def withStatus(self, status):
+ return self.refine(Archive.status == status)
+
+ def withDistribution(self, distribution):
+ return self.refine(Archive.distribution == distribution)
+
+ def ownedBy(self, owner):
+ return self.refine(Archive.owner == owner)
+
+ def withName(self, name):
+ return self.refine(Archive.name == name)
+
+ def isEnabled(self, enabled=True):
+ return self.refine(Archive._enabled == enabled)
+
+ def isPrivate(self, private=True):
+ return self.refine(Archive.private == private)
+
+ def isRequireVirtualized(self, require_virtualized=True):
+ return self.refine(Archive.require_virtualized == require_virtualized)
+
+ def isCommercial(self, commercial=True):
+ return self.refine(Archive.commercial == commercial)
+
+ def visibleByUser(self, user):
+ public_archive = And(Archive.private == False,
+ Archive._enabled == True)
+ if user is None:
+ # Anonymous user; filter to include only public archives in
+ # the results.
+ return self.refine(public_archive)
+ celebs = getUtility(ILaunchpadCelebrities)
+ if user.inTeam(celebs.admin):
+ # Admins can see everything
+ return self
+ # Enforce privacy-awareness for non-admin users,
+ # so that they can only see the private archives that they're
+ # allowed to see.
+
+ # Create a subselect to capture all the teams that are
+ # owners of archives AND the user is a member of:
+ user_teams_subselect = Select(
+ TeamParticipation.teamID,
+ where=And(
+ TeamParticipation.personID == user.id,
+ TeamParticipation.teamID == Archive.ownerID))
+
+ # Append the extra expression to capture either public
+ # archives, or archives owned by the user, or archives
+ # owned by a team of which the user is a member:
+ # Note: 'Archive.ownerID == user.id'
+ # is unnecessary below because there is a TeamParticipation
+ # entry showing that each person is a member of the "team"
+ # that consists of themselves.
+
+ # FIXME: Include private PPA's if user is an uploader
+ return self.refine(
+ Or(public_archive, Archive.ownerID.is_in(user_teams_subselect)))
=== added file 'lib/lp/soyuz/tests/test_archivecollection.py'
--- lib/lp/soyuz/tests/test_archivecollection.py 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/tests/test_archivecollection.py 2010-08-02 00:41:06 +0000
@@ -0,0 +1,220 @@
+# Copyright 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for `ArchiveCollection`."""
+
+__metaclass__ = type
+
+from zope.component import getUtility
+from zope.security.proxy import removeSecurityProxy
+
+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
+from canonical.testing import DatabaseFunctionalLayer
+from lp.soyuz.interfaces.archive import ArchivePurpose, ArchiveStatus
+from lp.soyuz.model.archivecollection import ArchiveCollection
+from lp.testing import TestCaseWithFactory
+
+
+class TestGetPublicationsInArchive(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_baseline(self):
+ archive = self.factory.makeArchive()
+ collection = ArchiveCollection()
+ self.assertIn(archive, collection.select())
+
+ def test_purpose_found(self):
+ archive = self.factory.makeArchive(purpose=ArchivePurpose.PPA)
+ collection = ArchiveCollection()
+ ppas = collection.withPurpose(ArchivePurpose.PPA)
+ self.assertIn(archive, ppas.select())
+
+ def test_purpose_not_found(self):
+ archive = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)
+ collection = ArchiveCollection()
+ ppas = collection.withPurpose(ArchivePurpose.PPA)
+ self.assertNotIn(archive, ppas.select())
+
+ def test_purposes(self):
+ archive = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)
+ ppa = self.factory.makeArchive(purpose=ArchivePurpose.PPA)
+ copy_archive = self.factory.makeArchive(purpose=ArchivePurpose.COPY)
+ archives = ArchiveCollection().withPurposes(
+ ArchivePurpose.PPA, ArchivePurpose.COPY)
+ result = archives.select()
+ self.assertIn(ppa, result)
+ self.assertIn(copy_archive, result)
+ self.assertNotIn(archive, result)
+
+ def test_distribution_found(self):
+ distribution = self.factory.makeDistribution()
+ archive = self.factory.makeArchive(distribution=distribution)
+ archives = ArchiveCollection().withDistribution(distribution)
+ self.assertIn(archive, archives.select())
+
+ def test_distribution_notfound(self):
+ distribution = self.factory.makeDistribution()
+ archive = self.factory.makeArchive(distribution=distribution)
+ other_distribution = self.factory.makeDistribution()
+ archives = ArchiveCollection().withDistribution(other_distribution)
+ self.assertNotIn(archive, archives.select())
+
+ def test_owner_found(self):
+ owner = self.factory.makePerson()
+ archive = self.factory.makeArchive(owner=owner)
+ archives = ArchiveCollection().ownedBy(owner)
+ self.assertIn(archive, archives.select())
+
+ def test_owner_not_found(self):
+ owner = self.factory.makePerson()
+ archive = self.factory.makeArchive(owner=owner)
+ other_person = self.factory.makePerson()
+ archives = ArchiveCollection().ownedBy(other_person)
+ self.assertNotIn(archive, archives.select())
+
+ def test_name_found(self):
+ name = "foo"
+ archive = self.factory.makeArchive(name=name)
+ archives = ArchiveCollection().withName(name)
+ self.assertIn(archive, archives.select())
+
+ def test_name_not_found(self):
+ name = "foo"
+ archive = self.factory.makeArchive(name=name)
+ archives = ArchiveCollection().withName("bar")
+ self.assertNotIn(archive, archives.select())
+
+ def test_enabled_found(self):
+ archive = self.factory.makeArchive(enabled=True)
+ archives = ArchiveCollection().isEnabled()
+ self.assertIn(archive, archives.select())
+
+ def test_enabled_not_found(self):
+ archive = self.factory.makeArchive(enabled=True)
+ archives = ArchiveCollection().isEnabled(False)
+ self.assertNotIn(archive, archives.select())
+
+ def test_disabled_found(self):
+ archive = self.factory.makeArchive(enabled=False)
+ archives = ArchiveCollection().isEnabled(False)
+ self.assertIn(archive, archives.select())
+
+ def test_disabled_not_found(self):
+ archive = self.factory.makeArchive(enabled=False)
+ archives = ArchiveCollection().isEnabled()
+ self.assertNotIn(archive, archives.select())
+
+ def test_private_found(self):
+ archive = self.factory.makeArchive(private=True)
+ archives = ArchiveCollection().isPrivate()
+ self.assertIn(archive, archives.select())
+
+ def test_private_not_found(self):
+ archive = self.factory.makeArchive(private=True)
+ archives = ArchiveCollection().isPrivate(False)
+ self.assertNotIn(archive, archives.select())
+
+ def test_non_private_found(self):
+ archive = self.factory.makeArchive(private=False)
+ archives = ArchiveCollection().isPrivate(False)
+ self.assertIn(archive, archives.select())
+
+ def test_non_private_not_found(self):
+ archive = self.factory.makeArchive(private=False)
+ archives = ArchiveCollection().isPrivate()
+ self.assertNotIn(archive, archives.select())
+
+ def test_require_virtualized_found(self):
+ archive = self.factory.makeArchive(virtualized=True)
+ archives = ArchiveCollection().isRequireVirtualized()
+ self.assertIn(archive, archives.select())
+
+ def test_require_virtualized_not_found(self):
+ archive = self.factory.makeArchive(virtualized=True)
+ archives = ArchiveCollection().isRequireVirtualized(False)
+ self.assertNotIn(archive, archives.select())
+
+ def test_not_require_virtualized_found(self):
+ archive = self.factory.makeArchive(virtualized=False)
+ archives = ArchiveCollection().isRequireVirtualized(False)
+ self.assertIn(archive, archives.select())
+
+ def test_not_require_virtualized_not_found(self):
+ archive = self.factory.makeArchive(virtualized=False)
+ archives = ArchiveCollection().isRequireVirtualized()
+ self.assertNotIn(archive, archives.select())
+
+ def test_status_found(self):
+ archive = self.factory.makeArchive(status=ArchiveStatus.ACTIVE)
+ archives = ArchiveCollection().withStatus(ArchiveStatus.ACTIVE)
+ self.assertIn(archive, archives.select())
+
+ def test_status_not_found(self):
+ archive = self.factory.makeArchive(status=ArchiveStatus.ACTIVE)
+ archives = ArchiveCollection().withStatus(ArchiveStatus.DELETING)
+ self.assertNotIn(archive, archives.select())
+
+ def test_commercial_found(self):
+ archive = self.factory.makeArchive(commercial=True)
+ archives = ArchiveCollection().isCommercial()
+ self.assertIn(archive, archives.select())
+
+ def test_commercial_not_found(self):
+ archive = self.factory.makeArchive(commercial=True)
+ archives = ArchiveCollection().isCommercial(False)
+ self.assertNotIn(archive, archives.select())
+
+ def test_not_commercial_not_found(self):
+ archive = self.factory.makeArchive(commercial=False)
+ archives = ArchiveCollection().isCommercial()
+ self.assertNotIn(archive, archives.select())
+
+ def test_not_commercial_found(self):
+ archive = self.factory.makeArchive(commercial=False)
+ archives = ArchiveCollection().isCommercial(False)
+ self.assertIn(archive, archives.select())
+
+ def test_admins_can_see_all(self):
+ archive = self.factory.makeArchive(private=True)
+ all_archives = ArchiveCollection()
+ admin = self.factory.makePerson()
+ admin_team = removeSecurityProxy(
+ getUtility(ILaunchpadCelebrities).admin)
+ admin_team.addMember(admin, admin_team.teamowner)
+ archives = ArchiveCollection().visibleByUser(admin)
+ self.assertEqual(
+ sorted(all_archives.select()), sorted(archives.select()))
+
+ def test_plain_user_cant_see_private(self):
+ archive = self.factory.makeArchive(private=True)
+ person = self.factory.makePerson()
+ archives = ArchiveCollection().visibleByUser(person)
+ self.assertNotIn(archive, archives.select())
+
+ def test_plain_user_cant_see_disabled(self):
+ archive = self.factory.makeArchive(enabled=False, private=False)
+ person = self.factory.makePerson()
+ archives = ArchiveCollection().visibleByUser(person)
+ self.assertNotIn(archive, archives.select())
+
+ def test_plain_user_can_see_public_enabled(self):
+ archive = self.factory.makeArchive(enabled=True, private=False)
+ person = self.factory.makePerson()
+ archives = ArchiveCollection().visibleByUser(person)
+ self.assertIn(archive, archives.select())
+
+ def test_user_can_see_private_disabled_archives_they_own(self):
+ person = self.factory.makePerson()
+ archive = self.factory.makeArchive(
+ enabled=False, private=True, owner=person)
+ archives = ArchiveCollection().visibleByUser(person)
+ self.assertIn(archive, archives.select())
+
+ def test_user_can_see_private_disabled_archives_for_their_teams(self):
+ person = self.factory.makePerson()
+ team = self.factory.makeTeam(members=[person])
+ archive = self.factory.makeArchive(
+ enabled=False, private=True, owner=team)
+ archives = ArchiveCollection().visibleByUser(person)
+ self.assertIn(archive, archives.select())
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-08-01 04:34:26 +0000
+++ lib/lp/testing/factory.py 2010-08-02 00:41:06 +0000
@@ -144,7 +144,7 @@
from lp.soyuz.adapters.packagelocation import PackageLocation
from lp.soyuz.interfaces.archive import (
- default_name_by_purpose, IArchiveSet, ArchivePurpose)
+ default_name_by_purpose, IArchiveSet, ArchivePurpose, ArchiveStatus)
from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet
from lp.soyuz.interfaces.binarypackagerelease import BinaryPackageFormat
from lp.soyuz.interfaces.component import IComponentSet
@@ -1731,7 +1731,8 @@
def makeArchive(self, distribution=None, owner=None, name=None,
purpose=None, enabled=True, private=False,
- virtualized=True, description=None, displayname=None):
+ virtualized=True, description=None, displayname=None,
+ commercial=False, status=None):
"""Create and return a new arbitrary archive.
:param distribution: Supply IDistribution, defaults to a new one
@@ -1744,6 +1745,8 @@
:param private: Whether the archive is created private.
:param virtualized: Whether the archive is virtualized.
:param description: A description of the archive.
+ :param commercial: Whether the archive is commercial.
+ :param status: The ArchiveStatus of the archive.
"""
if purpose is None:
purpose = ArchivePurpose.PPA
@@ -1772,11 +1775,17 @@
enabled=enabled, require_virtualized=virtualized,
description=description)
- if private:
+ if private or commercial:
naked_archive = removeSecurityProxy(archive)
naked_archive.private = True
naked_archive.buildd_secret = "sekrit"
+ if commercial:
+ removeSecurityProxy(archive).commercial = True
+
+ if status is not None:
+ removeSecurityProxy(archive).status = status
+
return archive
def makeBuilder(self, processor=None, url=None, name=None, title=None,
Follow ups