Merge ~pappacena/launchpad:ociproject-project-pillar into launchpad:master

Proposed by Thiago F. Pappacena
Status: Merged
Approved by: Thiago F. Pappacena
Approved revision: 930d51228dae7001d9c74d0c84ba9f990d80ac0f
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~pappacena/launchpad:ociproject-project-pillar
Merge into: launchpad:master
Diff against target: 236 lines (+85/-24)
5 files modified
lib/lp/registry/browser/ociproject.py (+2/-2)
lib/lp/registry/interfaces/ociproject.py (+17/-6)
lib/lp/registry/model/distribution.py (+1/-1)
lib/lp/registry/model/ociproject.py (+40/-13)
lib/lp/registry/tests/test_ociproject.py (+25/-2)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Tom Wardill (community) Approve
Review via email: mp+383817@code.launchpad.net

Commit message

Model changes to allow OCIProject pillar to be either a Distribution or a Product.

To post a comment you must log in.
Revision history for this message
Tom Wardill (twom) wrote :

To me, the approach looks correct, but I've spotted a couple of things to make it more robust to accidental problems.

review: Needs Fixing
Revision history for this message
Tom Wardill (twom) :
7b77fbb... by Thiago F. Pappacena

Renaming OCIProject.product and adding checks when setting pillar

Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

Thanks for the review, twom! I'm pushing the requested changes, but I could use some clarification on one of the comments bellow.

Revision history for this message
Tom Wardill (twom) :
Revision history for this message
Tom Wardill (twom) wrote :

LGTM!

review: Approve
8442d00... by Thiago F. Pappacena

Merge branch 'master' into ociproject-project-pillar

bc86804... by Thiago F. Pappacena

Merge branch 'master' into ociproject-project-pillar

2d5c6b3... by Thiago F. Pappacena

Merge branch 'master' into ociproject-project-pillar

Revision history for this message
Colin Watson (cjwatson) :
review: Approve
1ca81b6... by Thiago F. Pappacena

Merge branch 'master' into ociproject-project-pillar

930d512... by Thiago F. Pappacena

Allowing pillar change for OCIProject

Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

Pushed the requested change. I'll land this once the corresponding db changes are applied in production.

- https://code.launchpad.net/~pappacena/launchpad/+git/launchpad/+merge/383819
- https://code.launchpad.net/~pappacena/launchpad/+git/launchpad/+merge/384670

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/registry/browser/ociproject.py b/lib/lp/registry/browser/ociproject.py
2index eedec0c..df7ac85 100644
3--- a/lib/lp/registry/browser/ociproject.py
4+++ b/lib/lp/registry/browser/ociproject.py
5@@ -82,7 +82,7 @@ class OCIProjectAddView(LaunchpadFormView):
6 oci_project_name = getUtility(
7 IOCIProjectNameSet).getOrCreateByName(name)
8
9- oci_project = getUtility(IOCIProjectSet).getByDistributionAndName(
10+ oci_project = getUtility(IOCIProjectSet).getByPillarAndName(
11 self.context, oci_project_name.name)
12 if oci_project:
13 self.setFieldError(
14@@ -194,7 +194,7 @@ class OCIProjectEditView(LaunchpadEditFormView):
15 distribution = data.get('distribution')
16 name = data.get('name')
17 if distribution and name:
18- oci_project = getUtility(IOCIProjectSet).getByDistributionAndName(
19+ oci_project = getUtility(IOCIProjectSet).getByPillarAndName(
20 distribution, name)
21 if oci_project is not None and oci_project != self.context:
22 self.setFieldError(
23diff --git a/lib/lp/registry/interfaces/ociproject.py b/lib/lp/registry/interfaces/ociproject.py
24index 8e66f5d..d6f8468 100644
25--- a/lib/lp/registry/interfaces/ociproject.py
26+++ b/lib/lp/registry/interfaces/ociproject.py
27@@ -47,6 +47,7 @@ from lp.code.interfaces.gitref import IGitRef
28 from lp.code.interfaces.hasgitrepositories import IHasGitRepositories
29 from lp.registry.interfaces.distribution import IDistribution
30 from lp.registry.interfaces.ociprojectname import IOCIProjectName
31+from lp.registry.interfaces.product import IProduct
32 from lp.registry.interfaces.series import SeriesStatus
33 from lp.services.database.constants import DEFAULT
34 from lp.services.fields import (
35@@ -107,7 +108,11 @@ class IOCIProjectEditableAttributes(IBugTarget):
36 distribution = exported(ReferenceChoice(
37 title=_("The distribution that this OCI project is associated with."),
38 schema=IDistribution, vocabulary="Distribution",
39- required=True, readonly=False))
40+ required=False, readonly=False))
41+ project = exported(ReferenceChoice(
42+ title=_('The project that this OCI project is associated with.'),
43+ schema=IProduct, vocabulary='Product',
44+ required=False, readonly=False))
45 name = exported(TextLine(
46 title=_("Name"), required=True, readonly=False,
47 constraint=name_validator,
48@@ -120,9 +125,10 @@ class IOCIProjectEditableAttributes(IBugTarget):
49 description = exported(Text(
50 title=_("The description for this OCI project."),
51 required=True, readonly=False))
52- pillar = exported(Reference(
53- IDistribution,
54- title=_("The pillar containing this target."), readonly=True))
55+ pillar = Reference(
56+ Interface,
57+ title=_("The pillar containing this target."),
58+ required=True, readonly=False)
59
60
61 class IOCIProjectEdit(Interface):
62@@ -189,8 +195,13 @@ class IOCIProjectSet(Interface):
63 bugfiling_duplicate_search=False):
64 """Create an `IOCIProject`."""
65
66- def getByDistributionAndName(distribution, name):
67- """Get the OCIProjects for a given distribution."""
68+ def getByPillarAndName(pillar, name):
69+ """Get the OCIProjects for a given distribution or project.
70+
71+ :param pillar: An instance of Distribution or Product.
72+ :param name: The OCIProject name to find.
73+ :return: The OCIProject found.
74+ """
75
76 def findByDistributionAndName(distribution, name_substring):
77 """Find OCIProjects for a given distribution that contains the
78diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
79index 9c4c96e..50efe68 100644
80--- a/lib/lp/registry/model/distribution.py
81+++ b/lib/lp/registry/model/distribution.py
82@@ -841,7 +841,7 @@ class Distribution(SQLBase, BugTargetBase, MakesAnnouncements,
83 """ % sqlvalues(self.id, name))
84
85 def getOCIProject(self, name):
86- oci_project = getUtility(IOCIProjectSet).getByDistributionAndName(
87+ oci_project = getUtility(IOCIProjectSet).getByPillarAndName(
88 self, name)
89 return oci_project
90
91diff --git a/lib/lp/registry/model/ociproject.py b/lib/lp/registry/model/ociproject.py
92index 0d94e38..b991c19 100644
93--- a/lib/lp/registry/model/ociproject.py
94+++ b/lib/lp/registry/model/ociproject.py
95@@ -33,6 +33,7 @@ from lp.registry.interfaces.ociproject import (
96 )
97 from lp.registry.interfaces.ociprojectname import IOCIProjectNameSet
98 from lp.registry.interfaces.person import IPersonSet
99+from lp.registry.interfaces.product import IProduct
100 from lp.registry.interfaces.series import SeriesStatus
101 from lp.registry.model.ociprojectname import OCIProjectName
102 from lp.registry.model.ociprojectseries import OCIProjectSeries
103@@ -78,6 +79,9 @@ class OCIProject(BugTargetBase, StormBase):
104 distribution_id = Int(name="distribution", allow_none=True)
105 distribution = Reference(distribution_id, "Distribution.id")
106
107+ project_id = Int(name='project', allow_none=True)
108+ project = Reference(project_id, 'Product.id')
109+
110 ociprojectname_id = Int(name="ociprojectname", allow_none=False)
111 ociprojectname = Reference(ociprojectname_id, "OCIProjectName.id")
112
113@@ -100,7 +104,20 @@ class OCIProject(BugTargetBase, StormBase):
114 @property
115 def pillar(self):
116 """See `IBugTarget`."""
117- return self.distribution
118+ return self.project if self.project_id else self.distribution
119+
120+ @pillar.setter
121+ def pillar(self, pillar):
122+ if IDistribution.providedBy(pillar):
123+ self.distribution = pillar
124+ self.project = None
125+ elif IProduct.providedBy(pillar):
126+ self.project = pillar
127+ self.distribution = None
128+ else:
129+ raise ValueError(
130+ 'The target of an OCIProject must be either an IDistribution '
131+ 'or IProduct instance.')
132
133 @property
134 def display_name(self):
135@@ -214,16 +231,7 @@ class OCIProjectSet:
136 target = OCIProject()
137 target.date_created = date_created
138 target.date_last_modified = date_created
139-
140- # XXX twom 2019-10-10 This needs to have IProduct support
141- # when the model supports it
142- if IDistribution.providedBy(pillar):
143- target.distribution = pillar
144- else:
145- raise ValueError(
146- 'The target of an OCIProject must be an '
147- 'IDistribution instance.')
148-
149+ target.pillar = pillar
150 target.registrant = registrant
151 target.ociprojectname = name
152 target.description = description
153@@ -233,11 +241,30 @@ class OCIProjectSet:
154 store.add(target)
155 return target
156
157- def getByDistributionAndName(self, distribution, name):
158+ def _get_pillar_attribute(self, pillar):
159+ """Checks if the provided pillar is a valid one for OCIProject,
160+ returning the model attribute where this pillar would be stored.
161+
162+ If pillar is not valid, raises ValueError.
163+
164+ :param pillar: A Distribution or Product.
165+ :return: Storm attribute where the pillar would be stored.
166+ If pillar is not valid, raises ValueError.
167+ """
168+ if IDistribution.providedBy(pillar):
169+ return OCIProject.distribution
170+ elif IProduct.providedBy(pillar):
171+ return OCIProject.project
172+ else:
173+ raise ValueError(
174+ 'The target of an OCIProject must be either an '
175+ 'IDistribution or an IProduct instance.')
176+
177+ def getByPillarAndName(self, pillar, name):
178 """See `IOCIProjectSet`."""
179 target = IStore(OCIProject).find(
180 OCIProject,
181- OCIProject.distribution == distribution,
182+ self._get_pillar_attribute(pillar) == pillar,
183 OCIProject.ociprojectname == OCIProjectName.id,
184 OCIProjectName.name == name).one()
185 return target
186diff --git a/lib/lp/registry/tests/test_ociproject.py b/lib/lp/registry/tests/test_ociproject.py
187index 6ac98bb..c01f939 100644
188--- a/lib/lp/registry/tests/test_ociproject.py
189+++ b/lib/lp/registry/tests/test_ociproject.py
190@@ -46,6 +46,30 @@ class TestOCIProject(TestCaseWithFactory):
191 with admin_logged_in():
192 self.assertProvides(oci_project, IOCIProject)
193
194+ def test_product_pillar(self):
195+ product = self.factory.makeProduct(name="some-project")
196+ oci_project = self.factory.makeOCIProject(pillar=product)
197+ self.assertEqual(product, oci_project.pillar)
198+
199+ def test_prevents_moving_pillar_to_invalid_type(self):
200+ project = self.factory.makeProduct()
201+ distro = self.factory.makeDistribution()
202+
203+ project_oci_project = self.factory.makeOCIProject(pillar=project)
204+ distro_oci_project = self.factory.makeOCIProject(pillar=distro)
205+
206+ with admin_logged_in():
207+ project_oci_project.pillar = distro
208+ self.assertEqual(project_oci_project.distribution, distro)
209+ self.assertIsNone(project_oci_project.project)
210+
211+ distro_oci_project.pillar = project
212+ self.assertIsNone(distro_oci_project.distribution)
213+ self.assertEqual(distro_oci_project.project, project)
214+
215+ self.assertRaises(
216+ ValueError, setattr, distro_oci_project, 'pillar', 'Invalid')
217+
218 def test_newSeries(self):
219 driver = self.factory.makePerson()
220 distribution = self.factory.makeDistribution(driver=driver)
221@@ -144,7 +168,7 @@ class TestOCIProjectSet(TestCaseWithFactory):
222
223 with person_logged_in(registrant):
224 fetched_result = getUtility(
225- IOCIProjectSet).getByDistributionAndName(
226+ IOCIProjectSet).getByPillarAndName(
227 distribution, oci_project.ociprojectname.name)
228 self.assertEqual(oci_project, fetched_result)
229
230@@ -287,6 +311,5 @@ class TestOCIProjectWebservice(TestCaseWithFactory):
231 other_user = self.factory.makePerson()
232 distro = removeSecurityProxy(self.factory.makeDistribution(
233 owner=other_user))
234- url = api_url(distro)
235
236 self.assertCanCreateOCIProject(distro, self.person)