launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #26351
[Merge] ~pappacena/launchpad:snap-pillar-subscribe-ui into launchpad:master
Thiago F. Pappacena has proposed merging ~pappacena/launchpad:snap-pillar-subscribe-ui into launchpad:master with ~pappacena/launchpad:snap-pillar-subscribe-removal-job as a prerequisite.
Commit message:
UI workflow for users to subscribe to snaps
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~pappacena/launchpad/+git/launchpad/+merge/398319
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~pappacena/launchpad:snap-pillar-subscribe-ui into launchpad:master.
diff --git a/database/schema/security.cfg b/database/schema/security.cfg
index c20e7fd..e343a5f 100644
--- a/database/schema/security.cfg
+++ b/database/schema/security.cfg
@@ -2106,8 +2106,6 @@ public.job = SELECT, INSERT, UPDATE
public.person = SELECT
public.product = SELECT
public.sharingjob = SELECT, INSERT, UPDATE
-public.snap = SELECT
-public.snapsubscription = SELECT, INSERT, UPDATE, DELETE
public.specification = SELECT
public.specificationsubscription = SELECT, DELETE
public.teamparticipation = SELECT
diff --git a/lib/lp/blueprints/model/specification.py b/lib/lp/blueprints/model/specification.py
index 55ce9d5..e0d4001 100644
--- a/lib/lp/blueprints/model/specification.py
+++ b/lib/lp/blueprints/model/specification.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -755,7 +755,7 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
# Grant the subscriber access if they can't see the
# specification.
service = getUtility(IService, 'sharing')
- _, _, _, _, shared_specs = service.getVisibleArtifacts(
+ _, _, _, shared_specs = service.getVisibleArtifacts(
person, specifications=[self], ignore_permissions=True)
if not shared_specs:
service.ensureAccessGrants(
diff --git a/lib/lp/blueprints/tests/test_specification.py b/lib/lp/blueprints/tests/test_specification.py
index 86c4e4a..8de08c9 100644
--- a/lib/lp/blueprints/tests/test_specification.py
+++ b/lib/lp/blueprints/tests/test_specification.py
@@ -1,4 +1,4 @@
-# Copyright 2010-2021 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Unit tests for Specification."""
@@ -492,7 +492,7 @@ class SpecificationTests(TestCaseWithFactory):
product=product, information_type=InformationType.PROPRIETARY)
spec.subscribe(user, subscribed_by=owner)
service = getUtility(IService, 'sharing')
- _, _, _, _, shared_specs = service.getVisibleArtifacts(
+ _, _, _, shared_specs = service.getVisibleArtifacts(
user, specifications=[spec])
self.assertEqual([spec], shared_specs)
# The spec is also returned by getSharedSpecifications(),
@@ -509,7 +509,7 @@ class SpecificationTests(TestCaseWithFactory):
service.sharePillarInformation(
product, user_2, owner, permissions)
spec.subscribe(user_2, subscribed_by=owner)
- _, _, _, _, shared_specs = service.getVisibleArtifacts(
+ _, _, _, shared_specs = service.getVisibleArtifacts(
user_2, specifications=[spec])
self.assertEqual([spec], shared_specs)
self.assertEqual(
@@ -529,7 +529,7 @@ class SpecificationTests(TestCaseWithFactory):
spec.subscribe(user, subscribed_by=owner)
spec.unsubscribe(user, unsubscribed_by=owner)
service = getUtility(IService, 'sharing')
- _, _, _, _, shared_specs = service.getVisibleArtifacts(
+ _, _, _, shared_specs = service.getVisibleArtifacts(
user, specifications=[spec])
self.assertEqual([], shared_specs)
diff --git a/lib/lp/bugs/model/bug.py b/lib/lp/bugs/model/bug.py
index f7ebc86..a9b177c 100644
--- a/lib/lp/bugs/model/bug.py
+++ b/lib/lp/bugs/model/bug.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Launchpad bug-related database table classes."""
@@ -875,7 +875,7 @@ class Bug(SQLBase, InformationTypeMixin):
# there is at least one bugtask for which access can be checked.
if self.default_bugtask:
service = getUtility(IService, 'sharing')
- bugs, _, _, _, _ = service.getVisibleArtifacts(
+ bugs, _, _, _ = service.getVisibleArtifacts(
person, bugs=[self], ignore_permissions=True)
if not bugs:
service.ensureAccessGrants(
@@ -1819,7 +1819,7 @@ class Bug(SQLBase, InformationTypeMixin):
if information_type in PRIVATE_INFORMATION_TYPES:
service = getUtility(IService, 'sharing')
for person in (who, self.owner):
- bugs, _, _, _, _ = service.getVisibleArtifacts(
+ bugs, _, _, _ = service.getVisibleArtifacts(
person, bugs=[self], ignore_permissions=True)
if not bugs:
# subscribe() isn't sufficient if a subscription
diff --git a/lib/lp/code/browser/branchsubscription.py b/lib/lp/code/browser/branchsubscription.py
index cee4504..fbd98bc 100644
--- a/lib/lp/code/browser/branchsubscription.py
+++ b/lib/lp/code/browser/branchsubscription.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -276,7 +276,7 @@ class BranchSubscriptionEditView(LaunchpadEditFormView):
url = canonical_url(self.branch)
# If the subscriber can no longer see the branch, redirect them away.
service = getUtility(IService, 'sharing')
- _, branches, _, _, _ = service.getVisibleArtifacts(
+ _, branches, _, _ = service.getVisibleArtifacts(
self.person, branches=[self.branch], ignore_permissions=True)
if not branches:
url = canonical_url(self.branch.target)
diff --git a/lib/lp/code/browser/gitsubscription.py b/lib/lp/code/browser/gitsubscription.py
index a18d593..3eda78c 100644
--- a/lib/lp/code/browser/gitsubscription.py
+++ b/lib/lp/code/browser/gitsubscription.py
@@ -1,4 +1,4 @@
-# Copyright 2015-2021 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -280,7 +280,7 @@ class GitSubscriptionEditView(LaunchpadEditFormView):
# If the subscriber can no longer see the repository, redirect them
# away.
service = getUtility(IService, "sharing")
- _, _, repositories, _, _ = service.getVisibleArtifacts(
+ _, _, repositories, _ = service.getVisibleArtifacts(
self.person, gitrepositories=[self.repository],
ignore_permissions=True)
if not repositories:
diff --git a/lib/lp/code/model/branch.py b/lib/lp/code/model/branch.py
index 278db40..947aaf1 100644
--- a/lib/lp/code/model/branch.py
+++ b/lib/lp/code/model/branch.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -1041,7 +1041,7 @@ class Branch(SQLBase, WebhookTargetMixin, BzrIdentityMixin):
subscription.review_level = code_review_level
# Grant the subscriber access if they can't see the branch.
service = getUtility(IService, 'sharing')
- _, branches, _, _, _ = service.getVisibleArtifacts(
+ _, branches, _, _ = service.getVisibleArtifacts(
person, branches=[self], ignore_permissions=True)
if not branches:
service.ensureAccessGrants(
diff --git a/lib/lp/code/model/gitrepository.py b/lib/lp/code/model/gitrepository.py
index 589dcbf..74d94ec 100644
--- a/lib/lp/code/model/gitrepository.py
+++ b/lib/lp/code/model/gitrepository.py
@@ -1002,7 +1002,7 @@ class GitRepository(StormBase, WebhookTargetMixin, GitIdentityMixin):
subscription.review_level = code_review_level
# Grant the subscriber access if they can't see the repository.
service = getUtility(IService, "sharing")
- _, _, repositories, _, _ = service.getVisibleArtifacts(
+ _, _, repositories, _ = service.getVisibleArtifacts(
person, gitrepositories=[self], ignore_permissions=True)
if not repositories:
service.ensureAccessGrants(
diff --git a/lib/lp/code/model/tests/test_branchsubscription.py b/lib/lp/code/model/tests/test_branchsubscription.py
index b5b234e..6a15cd0 100644
--- a/lib/lp/code/model/tests/test_branchsubscription.py
+++ b/lib/lp/code/model/tests/test_branchsubscription.py
@@ -1,4 +1,4 @@
-# Copyright 2010-2021 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for the BranchSubscription model object."""
@@ -134,7 +134,7 @@ class TestBranchSubscriptions(TestCaseWithFactory):
None, CodeReviewNotificationLevel.NOEMAIL, owner)
# The stacked on branch should be visible.
service = getUtility(IService, 'sharing')
- _, visible_branches, _, _, _ = service.getVisibleArtifacts(
+ _, visible_branches, _, _ = service.getVisibleArtifacts(
grantee, branches=[private_stacked_on_branch])
self.assertContentEqual(
[private_stacked_on_branch], visible_branches)
@@ -162,7 +162,7 @@ class TestBranchSubscriptions(TestCaseWithFactory):
grantee, BranchSubscriptionNotificationLevel.NOEMAIL,
None, CodeReviewNotificationLevel.NOEMAIL, owner)
# The stacked on branch should not be visible.
- _, visible_branches, _, _, _ = service.getVisibleArtifacts(
+ _, visible_branches, _, _ = service.getVisibleArtifacts(
grantee, branches=[private_stacked_on_branch])
self.assertContentEqual([], visible_branches)
self.assertIn(
diff --git a/lib/lp/registry/model/sharingjob.py b/lib/lp/registry/model/sharingjob.py
index fe3838e..7d10fa9 100644
--- a/lib/lp/registry/model/sharingjob.py
+++ b/lib/lp/registry/model/sharingjob.py
@@ -1,4 +1,4 @@
-# Copyright 2012-2021 Canonical Ltd. This software is licensed under the
+# Copyright 2012-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Job classes related to the sharing feature are in here."""
@@ -91,12 +91,6 @@ from lp.services.job.model.job import (
)
from lp.services.job.runner import BaseRunnableJob
from lp.services.mail.sendmail import format_address_for_person
-from lp.snappy.interfaces.snap import ISnap
-from lp.snappy.model.snap import (
- get_private_snap_subscriber_filter,
- Snap,
- )
-from lp.snappy.model.snapsubscription import SnapSubscription
class SharingJobType(DBEnumeratedType):
@@ -269,7 +263,6 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
bug_ids = []
branch_ids = []
gitrepository_ids = []
- snap_ids = []
specification_ids = []
if artifacts:
for artifact in artifacts:
@@ -279,8 +272,6 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
branch_ids.append(artifact.id)
elif IGitRepository.providedBy(artifact):
gitrepository_ids.append(artifact.id)
- elif ISnap.providedBy(artifact):
- snap_ids.append(artifact.id)
elif ISpecification.providedBy(artifact):
specification_ids.append(artifact.id)
else:
@@ -293,7 +284,6 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
'bug_ids': bug_ids,
'branch_ids': branch_ids,
'gitrepository_ids': gitrepository_ids,
- 'snap_ids': snap_ids,
'specification_ids': specification_ids,
'information_types': information_types,
'requestor.id': requestor.id
@@ -330,10 +320,6 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
return self.metadata.get('gitrepository_ids', [])
@property
- def snap_ids(self):
- return self.metadata.get('snap_ids', [])
-
- @property
def specification_ids(self):
return self.metadata.get('specification_ids', [])
@@ -363,7 +349,6 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
'bug_ids': self.bug_ids,
'branch_ids': self.branch_ids,
'gitrepository_ids': self.gitrepository_ids,
- 'snap_ids': self.snap_ids,
'specification_ids': self.specification_ids,
'pillar': getattr(self.pillar, 'name', None),
'grantee': getattr(self.grantee, 'name', None)
@@ -380,7 +365,6 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
bug_filters = []
branch_filters = []
gitrepository_filters = []
- snap_filters = []
specification_filters = []
if self.branch_ids:
@@ -388,8 +372,6 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
if self.gitrepository_ids:
gitrepository_filters.append(GitRepository.id.is_in(
self.gitrepository_ids))
- if self.snap_ids:
- snap_filters.append(Snap.id.is_in(self.snap_ids))
if self.specification_ids:
specification_filters.append(Specification.id.is_in(
self.specification_ids))
@@ -405,8 +387,6 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
gitrepository_filters.append(
GitRepository.information_type.is_in(
self.information_types))
- snap_filters.append(Snap._information_type.is_in(
- self.information_types))
specification_filters.append(
Specification.information_type.is_in(
self.information_types))
@@ -443,11 +423,6 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
Select(
TeamParticipation.personID,
where=TeamParticipation.team == self.grantee)))
- snap_filters.append(
- In(SnapSubscription.person_id,
- Select(
- TeamParticipation.personID,
- where=TeamParticipation.team == self.grantee)))
specification_filters.append(
In(SpecificationSubscription.person_id,
Select(
@@ -491,17 +466,6 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
for sub in gitrepository_subscriptions:
sub.repository.unsubscribe(
sub.person, self.requestor, ignore_permissions=True)
- if snap_filters:
- snap_filters.append(Not(
- Or(*get_private_snap_subscriber_filter(
- SnapSubscription.person_id))))
- snap_subscriptions = IStore(SnapSubscription).using(
- SnapSubscription,
- Join(Snap, Snap.id == SnapSubscription.snap_id)
- ).find(SnapSubscription, *snap_filters).config(distinct=True)
- for sub in snap_subscriptions:
- sub.snap.unsubscribe(
- sub.person, self.requestor, ignore_permissions=True)
if specification_filters:
specification_filters.append(Not(*get_specification_privacy_filter(
SpecificationSubscription.person_id)))
diff --git a/lib/lp/registry/services/sharingservice.py b/lib/lp/registry/services/sharingservice.py
index 4909604..01e90da 100644
--- a/lib/lp/registry/services/sharingservice.py
+++ b/lib/lp/registry/services/sharingservice.py
@@ -81,7 +81,10 @@ from lp.services.webapp.authorization import (
available_with_permission,
check_permission,
)
-from lp.snappy.interfaces.snap import ISnapSet
+from lp.snappy.interfaces.snap import (
+ ISnap,
+ ISnapSet,
+ )
@implementer(ISharingService)
@@ -329,7 +332,6 @@ class SharingService:
bug_ids = []
branch_ids = []
gitrepository_ids = []
- snap_ids = []
for bug in bugs or []:
if (not ignore_permissions
and not check_permission('launchpad.View', bug)):
@@ -342,14 +344,9 @@ class SharingService:
branch_ids.append(branch.id)
for gitrepository in gitrepositories or []:
if (not ignore_permissions
- and not check_permission('launchpad.View', gitrepository)):
+ and not check_permission('launchpad.View', gitrepository)):
raise Unauthorized
gitrepository_ids.append(gitrepository.id)
- for snap in snaps or []:
- if (not ignore_permissions
- and not check_permission('launchpad.View', snap)):
- raise Unauthorized
- snap_ids.append(snap.id)
for spec in specifications or []:
if (not ignore_permissions
and not check_permission('launchpad.View', spec)):
@@ -379,12 +376,6 @@ class SharingService:
visible_gitrepositories = list(
wanted_gitrepositories.getRepositories())
- # Load the Snaps.
- visible_snaps = []
- if snap_ids:
- visible_snaps = list(getUtility(ISnapSet).findByIds(
- snap_ids, visible_by_user=person))
-
# Load the specifications.
visible_specs = []
if specifications:
@@ -396,7 +387,7 @@ class SharingService:
return (
visible_bugs, visible_branches, visible_gitrepositories,
- visible_snaps, visible_specs)
+ visible_specs)
def getInvisibleArtifacts(self, person, bugs=None, branches=None,
gitrepositories=None):
@@ -809,6 +800,12 @@ class SharingService:
getUtility(IAccessArtifactGrantSource).revokeByArtifact(
artifacts_to_delete, [grantee])
+ # XXX: Pappacena 2021-02-05: snaps should not trigger this job,
+ # since we do not have a "SnapSubscription" yet.
+ artifacts = [i for i in artifacts if not ISnap.providedBy(i)]
+ if not artifacts:
+ return
+
# Create a job to remove subscriptions for artifacts the grantee can no
# longer see.
return getUtility(IRemoveArtifactSubscriptionsJobSource).create(
diff --git a/lib/lp/registry/services/tests/test_sharingservice.py b/lib/lp/registry/services/tests/test_sharingservice.py
index 8d94723..9d0e07c 100644
--- a/lib/lp/registry/services/tests/test_sharingservice.py
+++ b/lib/lp/registry/services/tests/test_sharingservice.py
@@ -1,4 +1,4 @@
-# Copyright 2012-2021 Canonical Ltd. This software is licensed under the
+# Copyright 2012-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -1101,7 +1101,7 @@ class TestSharingService(TestCaseWithFactory):
# Check that grantees have expected access grants and subscriptions.
for person in [team_grantee, person_grantee]:
(visible_bugs, visible_branches, visible_gitrepositories,
- visible_snaps, visible_specs) = (
+ visible_specs) = (
self.service.getVisibleArtifacts(
person, bugs=bugs, branches=branches,
gitrepositories=gitrepositories,
@@ -1133,7 +1133,7 @@ class TestSharingService(TestCaseWithFactory):
for bug in bugs or []:
self.assertNotIn(person, bug.getDirectSubscribers())
(visible_bugs, visible_branches, visible_gitrepositories,
- visible_snaps, visible_specs) = (
+ visible_specs) = (
self.service.getVisibleArtifacts(
person, bugs=bugs, branches=branches,
gitrepositories=gitrepositories))
@@ -1783,8 +1783,7 @@ class TestSharingService(TestCaseWithFactory):
grantee, ignore, bugs, branches, gitrepositories, specs = (
self._make_Artifacts())
# Check the results.
- (shared_bugs, shared_branches, shared_gitrepositories,
- shared_snaps, shared_specs) = (
+ shared_bugs, shared_branches, shared_gitrepositories, shared_specs = (
self.service.getVisibleArtifacts(
grantee, bugs=bugs, branches=branches,
gitrepositories=gitrepositories, specifications=specs))
@@ -1798,8 +1797,7 @@ class TestSharingService(TestCaseWithFactory):
# user has a policy grant for the pillar of the specification.
_, owner, bugs, branches, gitrepositories, specs = (
self._make_Artifacts())
- (shared_bugs, shared_branches, shared_gitrepositories,
- shared_snaps, shared_specs) = (
+ shared_bugs, shared_branches, shared_gitrepositories, shared_specs = (
self.service.getVisibleArtifacts(
owner, bugs=bugs, branches=branches,
gitrepositories=gitrepositories, specifications=specs))
@@ -1842,8 +1840,7 @@ class TestSharingService(TestCaseWithFactory):
information_type=InformationType.USERDATA)
bugs.append(bug)
- (shared_bugs, shared_branches, shared_gitrepositories,
- visible_snaps, shared_specs) = (
+ shared_bugs, shared_branches, shared_gitrepositories, shared_specs = (
self.service.getVisibleArtifacts(grantee, bugs=bugs))
self.assertContentEqual(bugs, shared_bugs)
@@ -1851,8 +1848,7 @@ class TestSharingService(TestCaseWithFactory):
for x in range(0, 5):
change_callback(bugs[x], owner)
# Check the results.
- (shared_bugs, shared_branches, shared_gitrepositories,
- visible_snaps, shared_specs) = (
+ shared_bugs, shared_branches, shared_gitrepositories, shared_specs = (
self.service.getVisibleArtifacts(grantee, bugs=bugs))
self.assertContentEqual(bugs[5:], shared_bugs)
diff --git a/lib/lp/registry/tests/test_personmerge.py b/lib/lp/registry/tests/test_personmerge.py
index d080746..f1a86b6 100644
--- a/lib/lp/registry/tests/test_personmerge.py
+++ b/lib/lp/registry/tests/test_personmerge.py
@@ -680,22 +680,22 @@ class TestMergePeople(TestCaseWithFactory, KarmaTestMixin):
login_admin()
snap.subscribe(duplicate, snap.owner)
self.assertTrue(snap.visibleByUser(duplicate))
- self.assertThat(snap._getSubscription(duplicate), MatchesStructure(
+ self.assertThat(snap.getSubscription(duplicate), MatchesStructure(
snap=Equals(snap),
person=Equals(duplicate)
))
self.assertFalse(snap.visibleByUser(mergee))
- self.assertIsNone(snap._getSubscription(mergee))
+ self.assertIsNone(snap.getSubscription(mergee))
duplicate, mergee = self._do_merge(duplicate, mergee)
self.assertTrue(snap.visibleByUser(mergee))
- self.assertThat(snap._getSubscription(mergee), MatchesStructure(
+ self.assertThat(snap.getSubscription(mergee), MatchesStructure(
snap=Equals(snap),
person=Equals(mergee)
))
self.assertFalse(snap.visibleByUser(duplicate))
- self.assertIsNone(snap._getSubscription(duplicate))
+ self.assertIsNone(snap.getSubscription(duplicate))
def test_merge_moves_oci_recipes(self):
# When person/teams are merged, oci recipes owned by the from
diff --git a/lib/lp/registry/tests/test_sharingjob.py b/lib/lp/registry/tests/test_sharingjob.py
index 32aec0f..58fa5c1 100644
--- a/lib/lp/registry/tests/test_sharingjob.py
+++ b/lib/lp/registry/tests/test_sharingjob.py
@@ -1,4 +1,4 @@
-# Copyright 2012-2021 Canonical Ltd. This software is licensed under the
+# Copyright 2012-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for SharingJobs."""
@@ -39,7 +39,6 @@ from lp.services.features.testing import FeatureFixture
from lp.services.job.interfaces.job import JobStatus
from lp.services.job.tests import block_on_job
from lp.services.mail.sendmail import format_address_for_person
-from lp.snappy.interfaces.snap import SNAP_TESTING_FLAGS
from lp.testing import (
login_person,
person_logged_in,
@@ -128,16 +127,6 @@ class SharingJobDerivedTestCase(TestCaseWithFactory):
'for gitrepository_ids=[%d], requestor=%s>'
% (gitrepository.id, requestor.name), repr(job))
- def test_repr_snaps(self):
- requestor = self.factory.makePerson()
- snap = self.factory.makeSnap()
- job = getUtility(IRemoveArtifactSubscriptionsJobSource).create(
- requestor, artifacts=[snap])
- self.assertEqual(
- '<REMOVE_ARTIFACT_SUBSCRIPTIONS job reconciling subscriptions '
- 'for requestor=%s, snap_ids=[%d]>'
- % (requestor.name, snap.id), repr(job))
-
def test_repr_specifications(self):
requestor = self.factory.makePerson()
specification = self.factory.makeSpecification()
@@ -252,11 +241,9 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
layer = CeleryJobLayer
def setUp(self):
- features = {
+ self.useFixture(FeatureFixture({
'jobs.celery.enabled_classes': 'RemoveArtifactSubscriptionsJob',
- }
- features.update(SNAP_TESTING_FLAGS)
- self.useFixture(FeatureFixture(features))
+ }))
super(RemoveArtifactSubscriptionsJobTestCase, self).setUp()
def test_create(self):
@@ -328,9 +315,6 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
gitrepository = self.factory.makeGitRepository(
owner=owner, target=product,
information_type=InformationType.USERDATA)
- snap = self.factory.makeSnap(
- owner=owner, registrant=owner, project=product,
- information_type=InformationType.USERDATA)
specification = self.factory.makeSpecification(
owner=owner, product=product,
information_type=InformationType.PROPRIETARY)
@@ -348,7 +332,6 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
gitrepository.subscribe(artifact_indirect_grantee,
BranchSubscriptionNotificationLevel.NOEMAIL, None,
CodeReviewNotificationLevel.NOEMAIL, owner)
- snap.subscribe(artifact_indirect_grantee, owner)
# Subscribing somebody to a specification does not automatically
# create an artifact grant.
spec_artifact = self.factory.makeAccessArtifact(specification)
@@ -358,11 +341,10 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
specification.subscribe(artifact_indirect_grantee, owner)
# pick one of the concrete artifacts (bug, branch, Git repository,
- # snap, or spec) and subscribe the teams and persons.
+ # or spec) and subscribe the teams and persons.
concrete_artifact, get_pillars, get_subscribers = configure_test(
- bug, branch, gitrepository, snap, specification,
- policy_team_grantee, policy_indirect_grantee,
- artifact_team_grantee, owner)
+ bug, branch, gitrepository, specification, policy_team_grantee,
+ policy_indirect_grantee, artifact_team_grantee, owner)
# Subscribing policy_team_grantee has created an artifact grant so we
# need to revoke that to test the job.
@@ -395,7 +377,6 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
self.assertIn(artifact_indirect_grantee, bug.getDirectSubscribers())
self.assertIn(artifact_indirect_grantee, branch.subscribers)
self.assertIn(artifact_indirect_grantee, gitrepository.subscribers)
- self.assertIn(artifact_indirect_grantee, snap.subscribers)
self.assertIn(artifact_indirect_grantee,
removeSecurityProxy(specification).subscribers)
@@ -408,7 +389,7 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
return removeSecurityProxy(
concrete_artifact).getDirectSubscribers()
- def configure_test(bug, branch, gitrepository, snap, specification,
+ def configure_test(bug, branch, gitrepository, specification,
policy_team_grantee, policy_indirect_grantee,
artifact_team_grantee, owner):
concrete_artifact = bug
@@ -428,7 +409,7 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
def get_subscribers(concrete_artifact):
return concrete_artifact.subscribers
- def configure_test(bug, branch, gitrepository, snap, specification,
+ def configure_test(bug, branch, gitrepository, specification,
policy_team_grantee, policy_indirect_grantee,
artifact_team_grantee, owner):
concrete_artifact = branch
@@ -457,7 +438,7 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
def get_subscribers(concrete_artifact):
return concrete_artifact.subscribers
- def configure_test(bug, branch, gitrepository, snap, specification,
+ def configure_test(bug, branch, gitrepository, specification,
policy_team_grantee, policy_indirect_grantee,
artifact_team_grantee, owner):
concrete_artifact = gitrepository
@@ -478,26 +459,6 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
self._assert_artifact_change_unsubscribes(
change_callback, configure_test)
- def _assert_snap_change_unsubscribes(self, change_callback):
-
- def get_pillars(concrete_artifact):
- return [concrete_artifact.project]
-
- def get_subscribers(concrete_artifact):
- return concrete_artifact.subscribers
-
- def configure_test(bug, branch, gitrepository, snap, specification,
- policy_team_grantee, policy_indirect_grantee,
- artifact_team_grantee, owner):
- concrete_artifact = snap
- snap.subscribe(policy_team_grantee, owner)
- snap.subscribe(policy_indirect_grantee, owner)
- snap.subscribe(artifact_team_grantee, owner)
- return concrete_artifact, get_pillars, get_subscribers
-
- self._assert_artifact_change_unsubscribes(
- change_callback, configure_test)
-
def _assert_specification_change_unsubscribes(self, change_callback):
def get_pillars(concrete_artifact):
@@ -506,7 +467,7 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
def get_subscribers(concrete_artifact):
return concrete_artifact.subscribers
- def configure_test(bug, branch, gitrepository, snap, specification,
+ def configure_test(bug, branch, gitrepository, specification,
policy_team_grantee, policy_indirect_grantee,
artifact_team_grantee, owner):
naked_spec = removeSecurityProxy(specification)
@@ -535,13 +496,6 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
self._assert_gitrepository_change_unsubscribes(change_information_type)
- def test_change_information_type_snap(self):
- def change_information_type(snap):
- removeSecurityProxy(snap).information_type = (
- InformationType.PRIVATESECURITY)
-
- self._assert_snap_change_unsubscribes(change_information_type)
-
def test_change_information_type_specification(self):
def change_information_type(specification):
removeSecurityProxy(specification).information_type = (
diff --git a/lib/lp/security.py b/lib/lp/security.py
index 35c8b8c..2e4739f 100644
--- a/lib/lp/security.py
+++ b/lib/lp/security.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Security policies for using content objects."""
@@ -214,6 +214,7 @@ from lp.snappy.interfaces.snappyseries import (
ISnappySeries,
ISnappySeriesSet,
)
+from lp.snappy.interfaces.snapsubscription import ISnapSubscription
from lp.soyuz.interfaces.archive import IArchive
from lp.soyuz.interfaces.archiveauthtoken import IArchiveAuthToken
from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet
@@ -3298,17 +3299,13 @@ class ViewSnap(AuthorizationBase):
permission = 'launchpad.View'
usedfor = ISnap
- def checkUnauthenticated(self):
- return not self.obj.private
-
def checkAuthenticated(self, user):
- if not self.obj.private:
+ if user.isOwner(self.obj) or user.in_commercial_admin or user.in_admin:
return True
+ return self.obj.visibleByUser(user.person)
- return (
- user.isOwner(self.obj) or
- user.in_commercial_admin or
- user.in_admin)
+ def checkUnauthenticated(self):
+ return self.obj.visibleByUser(None)
class EditSnap(AuthorizationBase):
@@ -3339,6 +3336,30 @@ class AdminSnap(AuthorizationBase):
and EditSnap(self.obj).checkAuthenticated(user))
+class SnapSubscriptionEdit(AuthorizationBase):
+ permission = 'launchpad.Edit'
+ usedfor = ISnapSubscription
+
+ def checkAuthenticated(self, user):
+ """Is the user able to edit a Snap recipe subscription?
+
+ Any team member can edit a Snap recipe subscription for their
+ team.
+ Launchpad Admins can also edit any Snap recipe subscription.
+ The owner of the subscribed Snap can edit the subscription. If
+ the Snap owner is a team, then members of the team can edit
+ the subscription.
+ """
+ return (user.inTeam(self.obj.snap.owner) or
+ user.inTeam(self.obj.person) or
+ user.inTeam(self.obj.subscribed_by) or
+ user.in_admin)
+
+
+class SnapSubscriptionView(SnapSubscriptionEdit):
+ permission = 'launchpad.View'
+
+
class ViewSnapBuildRequest(DelegatedAuthorization):
permission = 'launchpad.View'
usedfor = ISnapBuildRequest
diff --git a/lib/lp/snappy/browser/configure.zcml b/lib/lp/snappy/browser/configure.zcml
index 9da248a..b11f797 100644
--- a/lib/lp/snappy/browser/configure.zcml
+++ b/lib/lp/snappy/browser/configure.zcml
@@ -1,4 +1,4 @@
-<!-- Copyright 2015-2020 Canonical Ltd. This software is licensed under the
+<!-- Copyright 2015-2021 Canonical Ltd. This software is licensed under the
GNU Affero General Public License version 3 (see the file LICENSE).
-->
@@ -37,6 +37,45 @@
name="+portlet-privacy"
template="../templates/snap-portlet-privacy.pt"/>
<browser:page
+ for="lp.snappy.interfaces.snap.ISnap"
+ permission="launchpad.View"
+ name="+portlet-subscribers"
+ template="../templates/snap-portlet-subscribers.pt"/>
+ <browser:page
+ for="lp.snappy.interfaces.snap.ISnap"
+ class="lp.snappy.browser.snapsubscription.SnapPortletSubscribersContent"
+ permission="launchpad.View"
+ name="+snap-portlet-subscriber-content"
+ template="../templates/snap-portlet-subscribers-content.pt"/>
+
+ <browser:defaultView
+ for="lp.snappy.interfaces.snapsubscription.ISnapSubscription"
+ name="+index"/>
+ <browser:page
+ for="lp.snappy.interfaces.snapsubscription.ISnapSubscription"
+ class="lp.snappy.browser.snapsubscription.SnapSubscriptionEditView"
+ permission="launchpad.Edit"
+ name="+index"
+ template="../templates/snapsubscription-edit.pt"/>
+ <browser:page
+ for="lp.snappy.interfaces.snap.ISnap"
+ class="lp.snappy.browser.snapsubscription.SnapSubscriptionAddView"
+ permission="launchpad.AnyPerson"
+ name="+subscribe"
+ template="../../app/templates/generic-edit.pt"/>
+ <browser:page
+ for="lp.snappy.interfaces.snap.ISnap"
+ class="lp.snappy.browser.snapsubscription.SnapSubscriptionAddOtherView"
+ permission="launchpad.AnyPerson"
+ name="+addsubscriber"
+ template="../../app/templates/generic-edit.pt"/>
+ <browser:url
+ for="lp.snappy.interfaces.snapsubscription.ISnapSubscription"
+ path_expression="string:+subscription/${person/name}"
+ attribute_to_parent="snap"
+ rootsite="code"/>
+
+ <browser:page
for="lp.code.interfaces.branch.IBranch"
class="lp.snappy.browser.snap.SnapAddView"
permission="launchpad.AnyPerson"
diff --git a/lib/lp/snappy/browser/snap.py b/lib/lp/snappy/browser/snap.py
index 7056519..d8bc3d4 100644
--- a/lib/lp/snappy/browser/snap.py
+++ b/lib/lp/snappy/browser/snap.py
@@ -45,25 +45,19 @@ from lp.app.browser.launchpadform import (
)
from lp.app.browser.lazrjs import InlinePersonEditPickerWidget
from lp.app.browser.tales import format_link
-from lp.app.enums import (
- FREE_INFORMATION_TYPES,
- InformationType,
- PRIVATE_INFORMATION_TYPES,
- PROPRIETARY_INFORMATION_TYPES,
- )
+from lp.app.enums import PRIVATE_INFORMATION_TYPES
from lp.app.interfaces.informationtype import IInformationType
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
-from lp.app.vocabularies import InformationTypeVocabulary
from lp.app.widgets.itemswidgets import (
LabeledMultiCheckBoxWidget,
LaunchpadDropdownWidget,
LaunchpadRadioWidget,
- LaunchpadRadioWidgetWithDescription,
)
from lp.buildmaster.interfaces.processor import IProcessorSet
from lp.code.browser.widgets.gitref import GitRefWidget
from lp.code.interfaces.gitref import IGitRef
from lp.registry.enums import VCSType
+from lp.registry.interfaces.person import IPersonSet
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.services.features import getFeatureFlag
from lp.services.propertycache import cachedproperty
@@ -133,6 +127,13 @@ class SnapNavigation(WebhookTargetNavigationMixin, Navigation):
return None
return build
+ @stepthrough("+subscription")
+ def traverse_subscription(self, name):
+ """Traverses to an `ISnapSubscription`."""
+ person = getUtility(IPersonSet).getByName(name)
+ if person is not None:
+ return self.context.getSubscription(person)
+
class SnapBreadcrumb(NameBreadcrumb):
@@ -187,12 +188,31 @@ class SnapContextMenu(ContextMenu):
facet = 'overview'
- links = ('request_builds',)
+ links = ('request_builds', 'add_subscriber', 'subscription')
@enabled_with_permission('launchpad.Edit')
def request_builds(self):
return Link('+request-builds', 'Request builds', icon='add')
+ @enabled_with_permission("launchpad.AnyPerson")
+ def subscription(self):
+ if self.context.hasSubscription(self.user):
+ url = "+subscription/%s" % self.user.name
+ text = "Edit your subscription"
+ icon = "edit"
+ elif self.context.userCanBeSubscribed(self.user):
+ url = "+subscribe"
+ text = "Subscribe yourself"
+ icon = "add"
+ else:
+ return None
+ return Link(url, text, icon=icon)
+
+ @enabled_with_permission("launchpad.Edit")
+ def add_subscriber(self):
+ text = "Subscribe someone else"
+ return Link("+addsubscriber", text, icon="add")
+
class SnapView(LaunchpadView):
"""Default view of a Snap."""
@@ -350,7 +370,7 @@ class ISnapEditSchema(Interface):
use_template(ISnap, include=[
'owner',
'name',
- 'information_type',
+ 'private',
'project',
'require_virtualized',
'allow_internet',
@@ -359,7 +379,6 @@ class ISnapEditSchema(Interface):
'auto_build_channels',
'store_upload',
])
-
store_distro_series = Choice(
vocabulary='SnappyDistroSeries', required=True,
title='Series')
@@ -529,10 +548,8 @@ class SnapAddView(
kwargs = {'git_ref': self.context}
else:
kwargs = {'branch': self.context}
- private = not getUtility(ISnapSet).isValidPrivacy(
- False, data['owner'], **kwargs)
- information_type = (InformationType.PROPRIETARY if private else
- InformationType.PUBLIC)
+ private = not getUtility(
+ ISnapSet).isValidPrivacy(False, data['owner'], **kwargs)
if not data.get('auto_build', False):
data['auto_build_archive'] = None
data['auto_build_pocket'] = None
@@ -543,8 +560,7 @@ class SnapAddView(
auto_build_archive=data['auto_build_archive'],
auto_build_pocket=data['auto_build_pocket'],
auto_build_channels=data['auto_build_channels'],
- information_type=information_type,
- processors=data['processors'],
+ processors=data['processors'], private=private,
build_source_tarball=data['build_source_tarball'],
store_upload=data['store_upload'],
store_series=data['store_distro_series'].snappy_series,
@@ -624,33 +640,31 @@ class BaseSnapEditView(LaunchpadEditFormView, SnapAuthorizeMixin):
def validate(self, data):
super(BaseSnapEditView, self).validate(data)
- info_type = data.get('information_type', self.context.information_type)
- editing_info_type = 'information_type' in data
- private = info_type in PRIVATE_INFORMATION_TYPES
- if private is False:
+ if data.get('private', self.context.private) is False:
# These are the requirements for public snaps.
- if 'information_type' in data or 'owner' in data:
+ if 'private' in data or 'owner' in data:
owner = data.get('owner', self.context.owner)
if owner is not None and owner.private:
self.setFieldError(
- 'information_type' if editing_info_type else 'owner',
+ 'private' if 'private' in data else 'owner',
'A public snap cannot have a private owner.')
- if 'information_type' in data or 'branch' in data:
+ if 'private' in data or 'branch' in data:
branch = data.get('branch', self.context.branch)
if branch is not None and branch.private:
self.setFieldError(
- 'information_type' if editing_info_type else 'branch',
+ 'private' if 'private' in data else 'branch',
'A public snap cannot have a private branch.')
- if 'information_type' in data or 'git_ref' in data:
+ if 'private' in data or 'git_ref' in data:
ref = data.get('git_ref', self.context.git_ref)
if ref is not None and ref.private:
self.setFieldError(
- 'information_type' if editing_info_type else 'git_ref',
+ 'private' if 'private' in data else 'git_ref',
'A public snap cannot have a private repository.')
else:
- # Requirements for private snaps.
+ # These are the requirements for private snaps.
project = data.get('project', self.context.project)
- if project is None:
+ private = data.get('private', self.context.private)
+ if private and project is None:
msg = ('Private Snap recipes should be associated '
'with a project.')
self.setFieldError('project', msg)
@@ -720,36 +734,25 @@ class SnapAdminView(BaseSnapEditView):
page_title = 'Administer'
field_names = [
- 'project', 'information_type', 'require_virtualized', 'allow_internet']
-
- custom_widget_information_type = CustomWidgetFactory(
- LaunchpadRadioWidgetWithDescription,
- vocabulary=InformationTypeVocabulary(
- types=FREE_INFORMATION_TYPES + PROPRIETARY_INFORMATION_TYPES))
-
- @property
- def initial_values(self):
- """Set initial values for the form."""
- # XXX pappacena 2021-02-12: Until we back fill information_type
- # database column, it will be NULL, but snap.information_type
- # property has a fallback to check "private" property. This should
- # be removed once we back fill snap.information_type.
- return {'information_type': self.context.information_type}
+ 'project', 'private', 'require_virtualized', 'allow_internet']
def validate(self, data):
super(SnapAdminView, self).validate(data)
# BaseSnapEditView.validate checks the rules for 'private' in
# combination with other attributes.
- if data.get('information_type', None) in PRIVATE_INFORMATION_TYPES:
+ if data.get('private', None) is True:
if not getFeatureFlag(SNAP_PRIVATE_FEATURE_FLAG):
self.setFieldError(
- 'information_type',
+ 'private',
'You do not have permission to create private snaps.')
def updateContextFromData(self, data, context=None, notify_modified=True):
if 'project' in data:
project = data.pop('project')
self.context.setProject(project)
+ if 'private' in data:
+ private = data.pop('private')
+ self.context.setPrivate(private)
super(SnapAdminView, self).updateContextFromData(
data, context, notify_modified)
diff --git a/lib/lp/snappy/browser/snapsubscription.py b/lib/lp/snappy/browser/snapsubscription.py
new file mode 100644
index 0000000..84c1597
--- /dev/null
+++ b/lib/lp/snappy/browser/snapsubscription.py
@@ -0,0 +1,173 @@
+# Copyright 2020-2021 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Snap subscription views."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'SnapPortletSubscribersContent'
+]
+
+from zope.component._api import getUtility
+from zope.formlib.form import action
+
+from lp.app.browser.launchpadform import (
+ LaunchpadEditFormView,
+ LaunchpadFormView,
+ )
+from lp.registry.interfaces.person import IPersonSet
+from lp.services.webapp import (
+ canonical_url,
+ LaunchpadView,
+ )
+from lp.services.webapp.authorization import (
+ check_permission,
+ precache_permission_for_objects,
+ )
+from lp.snappy.interfaces.snapsubscription import ISnapSubscription
+
+
+class SnapPortletSubscribersContent(LaunchpadView):
+ """View for the contents for the subscribers portlet."""
+
+ def subscriptions(self):
+ """Return a decorated list of Snap recipe subscriptions."""
+
+ # Cache permissions so private subscribers can be rendered.
+ # The security adaptor will do the job also but we don't want or
+ # need the expense of running several complex SQL queries.
+ subscriptions = list(self.context.subscriptions)
+ person_ids = [sub.person.id for sub in subscriptions]
+ list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+ person_ids, need_validity=True))
+ if self.user is not None:
+ subscribers = [
+ subscription.person for subscription in subscriptions]
+ precache_permission_for_objects(
+ self.request, "launchpad.LimitedView", subscribers)
+
+ visible_subscriptions = [
+ subscription for subscription in subscriptions
+ if check_permission("launchpad.LimitedView", subscription.person)]
+ return sorted(
+ visible_subscriptions,
+ key=lambda subscription: subscription.person.displayname)
+
+
+class RedirectToSnapMixin:
+ @property
+ def next_url(self):
+ url = canonical_url(self.snap)
+ # If the subscriber can no longer see the Snap recipe, redirect them
+ # away.
+ if not self.snap.visibleByUser(self.user):
+ url = canonical_url(self.snap.project)
+ return url
+
+ cancel_url = next_url
+
+
+class SnapSubscriptionEditView(RedirectToSnapMixin, LaunchpadEditFormView):
+ """The view for editing Snap recipe subscriptions."""
+ schema = ISnapSubscription
+ field_names = []
+
+ @property
+ def page_title(self):
+ return (
+ "Edit subscription to Snap recipe %s" %
+ self.snap.displayname)
+
+ @property
+ def label(self):
+ return (
+ "Edit subscription to Snap recipe for %s" %
+ self.person.displayname)
+
+ def initialize(self):
+ self.snap = self.context.snap
+ self.person = self.context.person
+ super(SnapSubscriptionEditView, self).initialize()
+
+ @action("Unsubscribe", name="unsubscribe")
+ def unsubscribe_action(self, action, data):
+ """Unsubscribe the team from the Snap recipe."""
+ self.snap.unsubscribe(self.person, self.user)
+ self.request.response.addNotification(
+ "%s has been unsubscribed from this Snap recipe."
+ % self.person.displayname)
+
+
+class _SnapSubscriptionCreationView(RedirectToSnapMixin, LaunchpadFormView):
+ """Contains the common functionality of the Add and Edit views."""
+
+ schema = ISnapSubscription
+ field_names = []
+
+ def initialize(self):
+ self.snap = self.context
+ super(_SnapSubscriptionCreationView, self).initialize()
+
+
+class SnapSubscriptionAddView(_SnapSubscriptionCreationView):
+
+ page_title = label = "Subscribe to Snap recipe"
+
+ @action("Subscribe")
+ def subscribe(self, action, data):
+ # To catch the stale post problem, check that the user is not
+ # subscribed before continuing.
+ if self.context.hasSubscription(self.user):
+ self.request.response.addNotification(
+ "You are already subscribed to this Snap recipe.")
+ else:
+ self.context.subscribe(self.user, self.user)
+
+ self.request.response.addNotification(
+ "You have subscribed to this Snap recipe.")
+
+
+class SnapSubscriptionAddOtherView(_SnapSubscriptionCreationView):
+ """View used to subscribe someone other than the current user."""
+
+ field_names = ["person"]
+ for_input = True
+
+ # Since we are subscribing other people, the current user
+ # is never considered subscribed.
+ user_is_subscribed = False
+
+ page_title = label = "Subscribe to Snap recipe"
+
+ def validate(self, data):
+ if "person" in data:
+ person = data["person"]
+ subscription = self.context.getSubscription(person)
+ if (subscription is None
+ and not self.context.userCanBeSubscribed(person)):
+ self.setFieldError(
+ "person",
+ "Open and delegated teams cannot be subscribed to "
+ "private Snap recipes.")
+
+ @action("Subscribe", name="subscribe_action")
+ def subscribe_action(self, action, data):
+ """Subscribe the specified user to the Snap recipe.
+
+ The user must be a member of a team in order to subscribe that team
+ to the Snap recipe. Launchpad Admins are special and they can
+ subscribe any team.
+ """
+ person = data["person"]
+ subscription = self.context.getSubscription(person)
+ if subscription is None:
+ self.context.subscribe(person, self.user)
+ self.request.response.addNotification(
+ "%s has been subscribed to this Snap recipe." %
+ person.displayname)
+ else:
+ self.request.response.addNotification(
+ "%s was already subscribed to this Snap recipe with." %
+ person.displayname)
diff --git a/lib/lp/snappy/browser/tests/test_snap.py b/lib/lp/snappy/browser/tests/test_snap.py
index 77e7f6a..d048778 100644
--- a/lib/lp/snappy/browser/tests/test_snap.py
+++ b/lib/lp/snappy/browser/tests/test_snap.py
@@ -2,7 +2,7 @@
# NOTE: The first line above must stay first; do not move the copyright
# notice to the top. See http://www.python.org/dev/peps/pep-0263/.
#
-# Copyright 2015-2021 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Test snap package views."""
@@ -659,21 +659,18 @@ class TestSnapAdminView(BaseTestSnapView):
project.information_type = InformationType.PROPRIETARY
snap = self.factory.makeSnap(registrant=self.person)
self.assertTrue(snap.require_virtualized)
- self.assertIsNone(snap.project)
self.assertFalse(snap.private)
self.assertTrue(snap.allow_internet)
- private = InformationType.PROPRIETARY.name
browser = self.getViewBrowser(snap, user=commercial_admin)
browser.getLink("Administer snap package").click()
browser.getControl(name='field.project').value = "my-project"
browser.getControl("Require virtualized builders").selected = False
- browser.getControl(name="field.information_type").value = private
+ browser.getControl("Private").selected = True
browser.getControl("Allow external network access").selected = False
browser.getControl("Update snap package").click()
login_person(self.person)
- self.assertEqual(project, snap.project)
self.assertFalse(snap.require_virtualized)
self.assertTrue(snap.private)
self.assertFalse(snap.allow_internet)
@@ -684,11 +681,10 @@ class TestSnapAdminView(BaseTestSnapView):
snap = self.factory.makeSnap(registrant=self.person)
commercial_admin = self.factory.makePerson(
member_of=[getUtility(ILaunchpadCelebrities).commercial_admin])
- private = InformationType.PROPRIETARY.name
browser = self.getViewBrowser(snap, user=commercial_admin)
browser.getLink("Administer snap package").click()
browser.getControl(name='field.project').value = ''
- browser.getControl(name="field.information_type").value = private
+ browser.getControl("Private").selected = True
browser.getControl("Update snap package").click()
self.assertEqual(
'Private Snap recipes should be associated with a project.',
@@ -705,10 +701,9 @@ class TestSnapAdminView(BaseTestSnapView):
# can reach this snap because it's owned by a private team.
commercial_admin = self.factory.makePerson(
member_of=[getUtility(ILaunchpadCelebrities).commercial_admin])
- public = InformationType.PUBLIC.name
browser = self.getViewBrowser(snap, user=commercial_admin)
browser.getLink("Administer snap package").click()
- browser.getControl(name="field.information_type").value = public
+ browser.getControl("Private").selected = False
browser.getControl("Update snap package").click()
self.assertEqual(
'A public snap cannot have a private owner.',
diff --git a/lib/lp/snappy/configure.zcml b/lib/lp/snappy/configure.zcml
index a16c664..05529f8 100644
--- a/lib/lp/snappy/configure.zcml
+++ b/lib/lp/snappy/configure.zcml
@@ -1,4 +1,4 @@
-<!-- Copyright 2015-2019 Canonical Ltd. This software is licensed under the
+<!-- Copyright 2015-2021 Canonical Ltd. This software is licensed under the
GNU Affero General Public License version 3 (see the file LICENSE).
-->
@@ -42,6 +42,15 @@
<allow interface="lp.snappy.interfaces.snap.ISnapSet" />
</securedutility>
+ <!-- SnapSubscription -->
+
+ <class class="lp.snappy.model.snapsubscription.SnapSubscription">
+ <allow interface="lp.snappy.interfaces.snapsubscription.ISnapSubscription"/>
+ <require
+ permission="zope.Public"
+ set_schema="lp.snappy.interfaces.snapsubscription.ISnapSubscription"/>
+ </class>
+
<!-- SnapBuildRequest -->
<class class="lp.snappy.model.snap.SnapBuildRequest">
<require
diff --git a/lib/lp/snappy/interfaces/snap.py b/lib/lp/snappy/interfaces/snap.py
index 687a605..9d96db1 100644
--- a/lib/lp/snappy/interfaces/snap.py
+++ b/lib/lp/snappy/interfaces/snap.py
@@ -89,9 +89,7 @@ from zope.security.interfaces import (
)
from lp import _
-from lp.app.enums import InformationType
from lp.app.errors import NameLookupFailed
-from lp.app.interfaces.informationtype import IInformationType
from lp.app.interfaces.launchpad import IPrivacy
from lp.app.validators.name import name_validator
from lp.buildmaster.interfaces.processor import IProcessor
@@ -570,10 +568,25 @@ class ISnapView(Interface):
# Really ISnapBuild, patched in lp.snappy.interfaces.webservice.
value_type=Reference(schema=Interface), readonly=True)))
+ subscriptions = CollectionField(
+ title=_("SnapSubscriptions associated with this repository."),
+ readonly=True,
+ # Really IGitSubscription, patched in _schema_circular_imports.py.
+ value_type=Reference(Interface))
+
subscribers = CollectionField(
- title=_("Persons subscribed to this repository."),
+ title=_("Persons subscribed to this snap recipe."),
readonly=True, value_type=Reference(IPerson))
+ def getSubscription(person):
+ """Returns the person's snap subscription for this snap recipe."""
+
+ def hasSubscription(person):
+ """Is this person subscribed to the snap recipe?"""
+
+ def userCanBeSubscribed(person):
+ """Checks if the given person can be subscribed to this snap recipe."""
+
def visibleByUser(user):
"""Can the specified user see this snap recipe?"""
@@ -853,12 +866,6 @@ class ISnapAdminAttributes(Interface):
title=_("Private"), required=False, readonly=False,
description=_("Whether or not this snap is private.")))
- information_type = exported(Choice(
- title=_("Information type"), vocabulary=InformationType,
- required=True, readonly=False, default=InformationType.PUBLIC,
- description=_(
- "The type of information contained in this Snap recipe.")))
-
require_virtualized = exported(Bool(
title=_("Require virtualized builders"), required=True, readonly=False,
description=_("Only build this snap package on virtual builders.")))
@@ -883,6 +890,9 @@ class ISnapAdminAttributes(Interface):
def unsubscribe(person, unsubscribed_by):
"""Unsubscribe a person to this snap recipe."""
+ def setPrivate(private):
+ """Set the current snap recipe as public or private."""
+
# XXX cjwatson 2015-07-17 bug=760849: "beta" is a lie to get WADL
# generation working. Individual attributes must set their version to
@@ -890,7 +900,7 @@ class ISnapAdminAttributes(Interface):
@exported_as_webservice_entry(as_of="beta")
class ISnap(
ISnapView, ISnapEdit, ISnapEditableAttributes, ISnapAdminAttributes,
- IPrivacy, IInformationType):
+ IPrivacy):
"""A buildable snap package."""
@@ -900,13 +910,6 @@ class ISnapSet(Interface):
@call_with(registrant=REQUEST_USER)
@operation_parameters(
- # Redefining information_type param to make it optional on the API
- # (although it is mandatory on the UI).
- information_type=Choice(
- title=_("Information type"), vocabulary=InformationType,
- required=False, default=InformationType.PUBLIC,
- description=_(
- "The type of information contained in this Snap recipe.")),
processors=List(
value_type=Reference(schema=IProcessor), required=False))
@export_factory_operation(
@@ -914,16 +917,15 @@ class ISnapSet(Interface):
"owner", "distro_series", "name", "description", "branch",
"git_repository", "git_repository_url", "git_path", "git_ref",
"auto_build", "auto_build_archive", "auto_build_pocket",
- "store_upload", "store_series", "store_name", "store_channels",
- "project"])
+ "private", "store_upload", "store_series", "store_name",
+ "store_channels", "project"])
@operation_for_version("devel")
def new(registrant, owner, distro_series, name, description=None,
branch=None, git_repository=None, git_repository_url=None,
git_path=None, git_ref=None, auto_build=False,
auto_build_archive=None, auto_build_pocket=None,
require_virtualized=True, processors=None, date_created=None,
- information_type=InformationType.PUBLIC, store_upload=False,
- store_series=None,
+ private=False, store_upload=False, store_series=None,
store_name=None, store_secrets=None, store_channels=None,
project=None):
"""Create an `ISnap`."""
@@ -937,10 +939,6 @@ class ISnapSet(Interface):
def findByIds(snap_ids):
"""Return all snap packages with the given ids."""
- def isValidInformationType(
- information_type, owner, branch=None, git_ref=None):
- """Whether or not the information type context is valid."""
-
@operation_parameters(
owner=Reference(IPerson, title=_("Owner"), required=True),
name=TextLine(title=_("Snap name"), required=True))
diff --git a/lib/lp/snappy/model/snap.py b/lib/lp/snappy/model/snap.py
index a1000ea..886b752 100644
--- a/lib/lp/snappy/model/snap.py
+++ b/lib/lp/snappy/model/snap.py
@@ -5,7 +5,6 @@ from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
- 'get_private_snap_subscriber_filter',
'Snap',
]
@@ -61,14 +60,10 @@ from lp.app.browser.tales import (
ArchiveFormatterAPI,
DateTimeFormatterAPI,
)
-from lp.app.enums import (
- InformationType,
- PUBLIC_INFORMATION_TYPES,
- )
+from lp.app.enums import InformationType
from lp.app.errors import (
IncompatibleArguments,
SubscriptionPrivacyViolation,
- UserCannotUnsubscribePerson,
)
from lp.app.interfaces.security import IAuthorization
from lp.app.interfaces.services import IService
@@ -369,18 +364,13 @@ class Snap(Storm, WebhookTargetMixin):
require_virtualized = Bool(name='require_virtualized')
- _private = Bool(name='private')
-
- def _valid_information_type(self, attr, value):
- if not getUtility(ISnapSet).isValidInformationType(
+ def _validate_private(self, attr, value):
+ if not getUtility(ISnapSet).isValidPrivacy(
value, self.owner, self.branch, self.git_ref):
raise SnapPrivacyMismatch
return value
- _information_type = DBEnum(
- enum=InformationType, default=InformationType.PUBLIC,
- name="information_type",
- validator=_valid_information_type)
+ private = Bool(name='private', validator=_validate_private)
allow_internet = Bool(name='allow_internet', allow_none=False)
@@ -401,21 +391,18 @@ class Snap(Storm, WebhookTargetMixin):
description=None, branch=None, git_ref=None, auto_build=False,
auto_build_archive=None, auto_build_pocket=None,
auto_build_channels=None, require_virtualized=True,
- date_created=DEFAULT, information_type=InformationType.PUBLIC,
- allow_internet=True, build_source_tarball=False,
- store_upload=False, store_series=None, store_name=None,
- store_secrets=None, store_channels=None, project=None):
+ date_created=DEFAULT, private=False, allow_internet=True,
+ build_source_tarball=False, store_upload=False,
+ store_series=None, store_name=None, store_secrets=None,
+ store_channels=None, project=None):
"""Construct a `Snap`."""
super(Snap, self).__init__()
- # Set the information type first so that other validators can perform
+ # Set the private flag first so that other validators can perform
# suitable privacy checks, but pillar should also be set, since it's
# mandatory for private snaps.
- # Note that we set self._information_type (not self.information_type)
- # to avoid the call to self._reconcileAccess() while building the
- # Snap instance.
self.project = project
- self._information_type = information_type
+ self.private = private
self.registrant = registrant
self.owner = owner
@@ -443,22 +430,6 @@ class Snap(Storm, WebhookTargetMixin):
return "<Snap ~%s/+snap/%s>" % (self.owner.name, self.name)
@property
- def information_type(self):
- if self._information_type is None:
- return (InformationType.PROPRIETARY if self._private
- else InformationType.PUBLIC)
- return self._information_type
-
- @information_type.setter
- def information_type(self, information_type):
- self._information_type = information_type
- self._reconcileAccess()
-
- @property
- def private(self):
- return self.information_type not in PUBLIC_INFORMATION_TYPES
-
- @property
def valid_webhook_event_types(self):
return ["snap:build:0.1"]
@@ -1098,6 +1069,18 @@ class Snap(Storm, WebhookTargetMixin):
order_by = Desc(SnapBuild.id)
return self._getBuilds(filter_term, order_by)
+ @property
+ def subscriptions(self):
+ return Store.of(self).find(
+ SnapSubscription, SnapSubscription.snap == self)
+
+ @property
+ def subscribers(self):
+ return Store.of(self).find(
+ Person,
+ SnapSubscription.person_id == Person.id,
+ SnapSubscription.snap == self)
+
def visibleByUser(self, user):
"""See `IGitRepository`."""
if not self.private:
@@ -1115,7 +1098,11 @@ class Snap(Storm, WebhookTargetMixin):
Snap.id == self.id,
visibility_clause).is_empty()
- def _getSubscription(self, person):
+ def hasSubscription(self, person):
+ """See `ISnap`."""
+ return self.getSubscription(person) is not None
+
+ def getSubscription(self, person):
"""Returns person's subscription to this snap recipe, or None if no
subscription is available.
"""
@@ -1126,49 +1113,33 @@ class Snap(Storm, WebhookTargetMixin):
SnapSubscription.person == person,
SnapSubscription.snap == self).one()
- def _userCanBeSubscribed(self, person):
+ def userCanBeSubscribed(self, person):
"""Checks if the given person can subscribe to this snap recipe."""
return not (
self.private and
person.is_team and
person.anyone_can_join())
- @property
- def subscribers(self):
- return Store.of(self).find(
- Person,
- SnapSubscription.person_id == Person.id,
- SnapSubscription.snap == self)
-
def subscribe(self, person, subscribed_by):
"""See `ISnap`."""
- if not self._userCanBeSubscribed(person):
+ if not self.userCanBeSubscribed(person):
raise SubscriptionPrivacyViolation(
"Open and delegated teams cannot be subscribed to private "
"snap recipes.")
- subscription = self._getSubscription(person)
+ subscription = self.getSubscription(person)
if subscription is None:
subscription = SnapSubscription(
person=person, snap=self, subscribed_by=subscribed_by)
Store.of(subscription).flush()
service = getUtility(IService, "sharing")
- _, _, _, snaps, _ = service.getVisibleArtifacts(
- person, snaps=[self], ignore_permissions=True)
- if not snaps:
- service.ensureAccessGrants([person], subscribed_by, snaps=[self])
+ service.ensureAccessGrants([person], subscribed_by, snaps=[self])
- def unsubscribe(self, person, unsubscribed_by, ignore_permissions=False):
+ def unsubscribe(self, person, unsubscribed_by):
"""See `ISnap`."""
service = getUtility(IService, "sharing")
service.revokeAccessGrants(
self.pillar, person, unsubscribed_by, snaps=[self])
- subscription = self._getSubscription(person)
- if (not ignore_permissions
- and not subscription.canBeUnsubscribedByUser(unsubscribed_by)):
- raise UserCannotUnsubscribePerson(
- '%s does not have permission to unsubscribe %s.' % (
- unsubscribed_by.displayname,
- person.displayname))
+ subscription = self.getSubscription(person)
# It should never be None, since we always create a SnapSubscription
# on Snap.subscribe. But just in case...
if subscription is not None:
@@ -1184,8 +1155,14 @@ class Snap(Storm, WebhookTargetMixin):
"""
if self.project is None:
return
+ info_type = (InformationType.PUBLIC if not self.private
+ else InformationType.PROPRIETARY)
pillars = [self.project]
- reconcile_access_for_artifact(self, self.information_type, pillars)
+ reconcile_access_for_artifact(self, info_type, pillars)
+
+ def setPrivate(self, private):
+ self.private = private
+ self._reconcileAccess()
def setProject(self, project):
self.project = project
@@ -1254,11 +1231,10 @@ class SnapSet:
git_path=None, git_ref=None, auto_build=False,
auto_build_archive=None, auto_build_pocket=None,
auto_build_channels=None, require_virtualized=True,
- processors=None, date_created=DEFAULT,
- information_type=InformationType.PUBLIC, allow_internet=True,
- build_source_tarball=False, store_upload=False,
- store_series=None, store_name=None, store_secrets=None,
- store_channels=None, project=None):
+ processors=None, date_created=DEFAULT, private=False,
+ allow_internet=True, build_source_tarball=False,
+ store_upload=False, store_series=None, store_name=None,
+ store_secrets=None, store_channels=None, project=None):
"""See `ISnapSet`."""
if not registrant.inTeam(owner):
if owner.is_team:
@@ -1295,8 +1271,7 @@ class SnapSet:
# IntegrityError due to exceptions being raised during object
# creation and to ensure that everything relevant is in the Storm
# cache.
- if not self.isValidInformationType(
- information_type, owner, branch, git_ref):
+ if not self.isValidPrivacy(private, owner, branch, git_ref):
raise SnapPrivacyMismatch
store = IMasterStore(Snap)
@@ -1307,7 +1282,7 @@ class SnapSet:
auto_build_pocket=auto_build_pocket,
auto_build_channels=auto_build_channels,
require_virtualized=require_virtualized, date_created=date_created,
- information_type=information_type, allow_internet=allow_internet,
+ private=private, allow_internet=allow_internet,
build_source_tarball=build_source_tarball,
store_upload=store_upload, store_series=store_series,
store_name=store_name, store_secrets=store_secrets,
@@ -1341,11 +1316,6 @@ class SnapSet:
return True
- def isValidInformationType(self, information_type, owner, branch=None,
- git_ref=None):
- private = information_type not in PUBLIC_INFORMATION_TYPES
- return self.isValidPrivacy(private, owner, branch, git_ref)
-
def _getByName(self, owner, name):
return IStore(Snap).find(
Snap, Snap.owner == owner, Snap.name == name).one()
@@ -1373,12 +1343,9 @@ class SnapSet:
expressions.append(Snap.owner == owner)
return IStore(Snap).find(Snap, *expressions)
- def findByIds(self, snap_ids, visible_by_user=None):
+ def findByIds(self, snap_ids):
"""See `ISnapSet`."""
- clauses = [Snap.id.is_in(snap_ids)]
- if visible_by_user is not None:
- clauses.append(self._findSnapVisibilityClause(visible_by_user))
- return IStore(Snap).find(Snap, *clauses)
+ return IStore(Snap).find(Snap, Snap.id.is_in(snap_ids))
def findByOwner(self, owner):
"""See `ISnapSet`."""
@@ -1453,22 +1420,15 @@ class SnapSet:
# XXX cjwatson 2016-11-25: This is in principle a poor query, but we
# don't yet have the access grant infrastructure to do better, and
# in any case the numbers involved should be very small.
- # XXX pappacena 2021-02-12: Once we do the migration to back fill
- # information_type, we should be able to change this.
- private_snap = SQL(
- "CASE information_type"
- " WHEN NULL THEN private"
- " ELSE information_type NOT IN ?"
- "END", params=[tuple(i.value for i in PUBLIC_INFORMATION_TYPES)])
if visible_by_user is None:
- return private_snap == False
+ return Snap.private == False
else:
roles = IPersonRoles(visible_by_user)
if roles.in_admin or roles.in_commercial_admin:
return True
else:
return Or(
- private_snap == False,
+ Snap.private == False,
Snap.owner_id.is_in(Select(
TeamParticipation.teamID,
TeamParticipation.person == visible_by_user)),
diff --git a/lib/lp/snappy/templates/snap-index.pt b/lib/lp/snappy/templates/snap-index.pt
index 7d3083e..09eea5c 100644
--- a/lib/lp/snappy/templates/snap-index.pt
+++ b/lib/lp/snappy/templates/snap-index.pt
@@ -32,6 +32,7 @@
<metal:side fill-slot="side">
<div tal:replace="structure context/@@+portlet-privacy" />
<div tal:replace="structure context/@@+global-actions"/>
+ <tal:subscribers replace="structure context/@@+portlet-subscribers" />
</metal:side>
<metal:heading fill-slot="heading">
diff --git a/lib/lp/snappy/templates/snap-portlet-subscribers-content.pt b/lib/lp/snappy/templates/snap-portlet-subscribers-content.pt
new file mode 100644
index 0000000..05495ff
--- /dev/null
+++ b/lib/lp/snappy/templates/snap-portlet-subscribers-content.pt
@@ -0,0 +1,31 @@
+<div
+ tal:omit-tag=""
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+ <div class="section snap-subscribers">
+ <div
+ tal:condition="view/subscriptions"
+ tal:repeat="subscription view/subscriptions"
+ tal:attributes="id string:subscriber-${subscription/person/name}">
+ <a tal:condition="subscription/person/name|nothing"
+ tal:attributes="href subscription/person/fmt:url">
+
+ <tal:block replace="structure subscription/person/fmt:icon" />
+ <tal:block replace="subscription/person/fmt:displayname/fmt:shorten/20" />
+ </a>
+
+ <a tal:condition="subscription/required:launchpad.Edit"
+ tal:attributes="
+ href subscription/fmt:url;
+ title string:Edit subscription ${subscription/person/fmt:displayname};
+ id string:editsubscription-${subscription/person/name}">
+ <img class="editsub-icon" src="/@@/edit"
+ tal:attributes="id string:editsubscription-icon-${subscription/person/name}" />
+ </a>
+ </div>
+ <div id="none-subscribers" tal:condition="not:view/subscriptions">
+ No subscribers.
+ </div>
+ </div>
+</div>
diff --git a/lib/lp/snappy/templates/snap-portlet-subscribers.pt b/lib/lp/snappy/templates/snap-portlet-subscribers.pt
new file mode 100644
index 0000000..5f0dd60
--- /dev/null
+++ b/lib/lp/snappy/templates/snap-portlet-subscribers.pt
@@ -0,0 +1,29 @@
+<div
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ class="portlet" id="portlet-subscribers">
+ <div tal:define="context_menu view/context/menu:context">
+ <div>
+ <div class="section">
+ <div
+ tal:define="link context_menu/subscription"
+ tal:condition="link/enabled"
+ id="selfsubscriptioncontainer">
+ <a class="sprite add subscribe-self"
+ tal:attributes="href link/url"
+ tal:content="link/text" />
+ </div>
+ <div
+ tal:define="link context_menu/add_subscriber"
+ tal:condition="link/enabled"
+ tal:content="structure link/render" />
+ </div>
+ </div>
+
+ <h2>Subscribers</h2>
+ <div id="snap-subscribers-outer">
+ <div tal:replace="structure context/@@+snap-portlet-subscriber-content" />
+ </div>
+ </div>
+</div>
diff --git a/lib/lp/snappy/templates/snapsubscription-edit.pt b/lib/lp/snappy/templates/snapsubscription-edit.pt
new file mode 100644
index 0000000..f2d9d8c
--- /dev/null
+++ b/lib/lp/snappy/templates/snapsubscription-edit.pt
@@ -0,0 +1,25 @@
+<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="main">
+
+ <div metal:use-macro="context/@@launchpad_form/form" >
+ <metal:extra fill-slot="extra_info">
+ <p class="documentDescription">
+ If you unsubscribe from a snap recipe it will no longer show up on
+ your personal pages.
+ </p>
+ </metal:extra>
+ </div>
+
+</div>
+
+</body>
+</html>
diff --git a/lib/lp/snappy/tests/test_snap.py b/lib/lp/snappy/tests/test_snap.py
index d089a46..052e196 100644
--- a/lib/lp/snappy/tests/test_snap.py
+++ b/lib/lp/snappy/tests/test_snap.py
@@ -138,7 +138,6 @@ from lp.testing import (
ANONYMOUS,
api_url,
login,
- login_admin,
logout,
person_logged_in,
record_two_runs,
@@ -173,8 +172,7 @@ class TestSnapFeatureFlag(TestCaseWithFactory):
self.assertRaises(
SnapPrivateFeatureDisabled, getUtility(ISnapSet).new,
person, person, None, None,
- branch=self.factory.makeAnyBranch(),
- information_type=InformationType.PROPRIETARY)
+ branch=self.factory.makeAnyBranch(), private=True)
class TestSnap(TestCaseWithFactory):
@@ -1350,9 +1348,6 @@ class TestSnapVisibility(TestCaseWithFactory):
AccessArtifactGrant.abstract_artifact_id == AccessArtifact.id,
*conditions)
- def getSnapSubscription(self, snap, person):
- return removeSecurityProxy(snap)._getSubscription(person)
-
def test_only_owner_can_grant_access(self):
owner = self.factory.makePerson()
pillar = self.factory.makeProduct(owner=owner)
@@ -1389,20 +1384,18 @@ class TestSnapVisibility(TestCaseWithFactory):
with person_logged_in(owner):
self.assertFalse(snap.visibleByUser(person))
snap.subscribe(person, snap.owner)
- self.assertThat(
- self.getSnapSubscription(snap, person),
- MatchesStructure(
- person=Equals(person),
- snap=Equals(snap),
- subscribed_by=Equals(snap.owner),
- date_created=IsInstance(datetime)))
+ self.assertThat(snap.getSubscription(person), MatchesStructure(
+ person=Equals(person),
+ snap=Equals(snap),
+ subscribed_by=Equals(snap.owner),
+ date_created=IsInstance(datetime)))
# Calling again should be a no-op.
snap.subscribe(person, snap.owner)
self.assertTrue(snap.visibleByUser(person))
snap.unsubscribe(person, snap.owner)
self.assertFalse(snap.visibleByUser(person))
- self.assertIsNone(self.getSnapSubscription(snap, person))
+ self.assertIsNone(snap.getSubscription(person))
def test_reconcile_set_public(self):
owner = self.factory.makePerson()
@@ -1413,17 +1406,17 @@ class TestSnapVisibility(TestCaseWithFactory):
snap.subscribe(another_user, snap.owner)
self.assertEqual(1, self.getSnapGrants(snap, another_user).count())
self.assertThat(
- self.getSnapSubscription(snap, another_user),
+ snap.getSubscription(another_user),
MatchesStructure(
person=Equals(another_user),
snap=Equals(snap),
subscribed_by=Equals(snap.owner),
date_created=IsInstance(datetime)))
- snap.information_type = InformationType.PUBLIC
+ snap.setPrivate(False)
self.assertEqual(0, self.getSnapGrants(snap, another_user).count())
self.assertThat(
- self.getSnapSubscription(snap, another_user),
+ snap.getSubscription(another_user),
MatchesStructure(
person=Equals(another_user),
snap=Equals(snap),
@@ -1448,7 +1441,7 @@ class TestSnapVisibility(TestCaseWithFactory):
self.assertTrue(snap.visibleByUser(another_person))
self.assertEqual(1, self.getSnapGrants(snap).count())
self.assertThat(
- self.getSnapSubscription(snap, another_person),
+ snap.getSubscription(another_person),
MatchesStructure(
person=Equals(another_person),
snap=Equals(snap),
@@ -1459,7 +1452,7 @@ class TestSnapVisibility(TestCaseWithFactory):
self.assertTrue(snap.visibleByUser(another_person))
self.assertEqual(1, self.getSnapGrants(snap).count())
self.assertThat(
- self.getSnapSubscription(snap, another_person),
+ snap.getSubscription(another_person),
MatchesStructure(
person=Equals(another_person),
snap=Equals(snap),
@@ -1559,25 +1552,11 @@ class TestSnapSet(TestCaseWithFactory):
self.assertEqual(ref.path, snap.git_path)
self.assertEqual(ref, snap.git_ref)
- def test_private_snap_information_type_compatibility(self):
- login_admin()
- private = InformationType.PROPRIETARY
- public = InformationType.PUBLIC
- private_snap = getUtility(ISnapSet).new(
- information_type=private, **self.makeSnapComponents())
- self.assertEqual(
- InformationType.PROPRIETARY, private_snap.information_type)
-
- public_snap = getUtility(ISnapSet).new(
- information_type=public, **self.makeSnapComponents())
- self.assertEqual(
- InformationType.PUBLIC, public_snap.information_type)
-
def test_private_snap_for_public_sources(self):
# Creating private snaps for public sources is allowed.
[ref] = self.factory.makeGitRefs()
components = self.makeSnapComponents(git_ref=ref)
- components['information_type'] = InformationType.PROPRIETARY
+ components['private'] = True
components['project'] = self.factory.makeProduct()
snap = getUtility(ISnapSet).new(**components)
with person_logged_in(components['owner']):
@@ -2690,11 +2669,9 @@ class TestSnapWebservice(TestCaseWithFactory):
if auto_build_pocket is not None:
kwargs["auto_build_pocket"] = auto_build_pocket.title
logout()
- information_type = (InformationType.PROPRIETARY if private else
- InformationType.PUBLIC)
response = webservice.named_post(
"/+snaps", "new", owner=owner_url, distro_series=distroseries_url,
- name="mir", information_type=information_type.title, **kwargs)
+ name="mir", private=private, **kwargs)
self.assertEqual(201, response.status)
return webservice.get(response.getHeader("Location")).jsonBody()
@@ -2839,8 +2816,7 @@ class TestSnapWebservice(TestCaseWithFactory):
admin, permission=OAuthPermission.WRITE_PRIVATE)
admin_webservice.default_api_version = "devel"
response = admin_webservice.patch(
- snap_url, "application/json",
- json.dumps({"information_type": 'Public'}))
+ snap_url, "application/json", json.dumps({"private": False}))
self.assertEqual(400, response.status)
self.assertEqual(
b"Snap recipe contains private information and cannot be public.",
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index 580a5f3..950323e 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -4748,10 +4748,10 @@ class BareLaunchpadObjectFactory(ObjectFactory):
auto_build_archive=None, auto_build_pocket=None,
auto_build_channels=None, is_stale=None,
require_virtualized=True, processors=None,
- date_created=DEFAULT, private=None, information_type=None,
- allow_internet=True, build_source_tarball=False,
- store_upload=False, store_series=None, store_name=None,
- store_secrets=None, store_channels=None, project=_DEFAULT):
+ date_created=DEFAULT, private=False, allow_internet=True,
+ build_source_tarball=False, store_upload=False,
+ store_series=None, store_name=None, store_secrets=None,
+ store_channels=None, project=_DEFAULT):
"""Make a new Snap."""
if registrant is None:
registrant = self.makePerson()
@@ -4775,18 +4775,13 @@ class BareLaunchpadObjectFactory(ObjectFactory):
project = self.makeProduct()
if project is _DEFAULT:
project = None
- assert information_type is None or private is None
- if information_type is None:
- information_type = (InformationType.PUBLIC if not private
- else InformationType.PROPRIETARY)
snap = getUtility(ISnapSet).new(
registrant, owner, distroseries, name,
require_virtualized=require_virtualized, processors=processors,
date_created=date_created, branch=branch, git_ref=git_ref,
auto_build=auto_build, auto_build_archive=auto_build_archive,
auto_build_pocket=auto_build_pocket,
- auto_build_channels=auto_build_channels,
- information_type=information_type,
+ auto_build_channels=auto_build_channels, private=private,
allow_internet=allow_internet,
build_source_tarball=build_source_tarball,
store_upload=store_upload, store_series=store_series,
References