Merge lp:~bac/launchpad/bug-422128 into lp:launchpad
- bug-422128
- Merge into devel
Proposed by
Brad Crittenden
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~bac/launchpad/bug-422128 |
Merge into: | lp:launchpad |
Diff against target: |
689 lines 14 files modified
lib/lp/registry/browser/product.py (+0/-24) lib/lp/registry/doc/private-team-roles.txt (+53/-5) lib/lp/registry/doc/product.txt (+69/-1) lib/lp/registry/interfaces/productrelease.py (+8/-4) lib/lp/registry/model/milestone.py (+1/-2) lib/lp/registry/model/product.py (+32/-5) lib/lp/registry/model/productrelease.py (+6/-3) lib/lp/registry/stories/webservice/xx-project-registry.txt (+74/-17) lib/lp/registry/tests/test_doc.py (+12/-0) lib/lp/services/database/precache.py (+1/-1) lib/lp/services/database/tests/test_precache.py (+2/-2) lib/lp/testing/factory.py (+2/-3) lib/lp/translations/interfaces/translationimportqueue.py (+3/-4) lib/lp/translations/model/translationimportqueue.py (+4/-3) |
To merge this branch: | bzr merge lp:~bac/launchpad/bug-422128 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Paul Hummer (community) | code | Approve | |
Canonical Launchpad Engineering | Pending | ||
Review via email: mp+12735@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote : | # |
Revision history for this message
Paul Hummer (rockstar) : | # |
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/registry/browser/product.py' |
2 | --- lib/lp/registry/browser/product.py 2009-09-23 14:58:12 +0000 |
3 | +++ lib/lp/registry/browser/product.py 2009-10-05 11:36:16 +0000 |
4 | @@ -47,7 +47,6 @@ |
5 | from zope.lifecycleevent import ObjectCreatedEvent |
6 | from zope.interface import implements, Interface |
7 | from zope.formlib import form |
8 | -from zope.security.proxy import removeSecurityProxy |
9 | |
10 | from z3c.ptcompat import ViewPageTemplateFile |
11 | |
12 | @@ -65,8 +64,6 @@ |
13 | from lp.services.worlddata.interfaces.country import ICountry |
14 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
15 | from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet |
16 | -from lp.translations.interfaces.translationimportqueue import ( |
17 | - ITranslationImportQueue) |
18 | from canonical.launchpad.webapp.interfaces import ( |
19 | ILaunchBag, NotFoundError, UnsafeFormGetSubmissionError) |
20 | from lp.registry.interfaces.pillar import IPillarNameSet |
21 | @@ -1709,8 +1706,6 @@ |
22 | old_owner = self.context.owner |
23 | old_driver = self.context.driver |
24 | self.updateContextFromData(data) |
25 | - self._reassignProductDependencies( |
26 | - self.context, old_owner, self.context.owner) |
27 | if self.context.owner != old_owner: |
28 | self.request.response.addNotification( |
29 | "Successfully changed the maintainer to %s" |
30 | @@ -1733,22 +1728,3 @@ |
31 | def cancel_url(self): |
32 | """See `LaunchpadFormView`.""" |
33 | return canonical_url(self.context) |
34 | - |
35 | - def _reassignProductDependencies(self, product, oldOwner, newOwner): |
36 | - """Reassign ownership of objects related to this product. |
37 | - |
38 | - Objects related to this product includes: ProductSeries, |
39 | - ProductReleases and TranslationImportQueueEntries that are owned |
40 | - by oldOwner of the product. |
41 | - |
42 | - """ |
43 | - import_queue = getUtility(ITranslationImportQueue) |
44 | - for entry in import_queue.getAllEntries(target=product): |
45 | - if entry.importer == oldOwner: |
46 | - removeSecurityProxy(entry).importer = newOwner |
47 | - for series in product.serieses: |
48 | - if series.owner == oldOwner: |
49 | - series.owner = newOwner |
50 | - for release in product.releases: |
51 | - if release.owner == oldOwner: |
52 | - release.owner = newOwner |
53 | |
54 | === modified file 'lib/lp/registry/doc/private-team-roles.txt' |
55 | --- lib/lp/registry/doc/private-team-roles.txt 2009-08-11 12:53:54 +0000 |
56 | +++ lib/lp/registry/doc/private-team-roles.txt 2009-10-05 11:36:16 +0000 |
57 | @@ -332,17 +332,16 @@ |
58 | membership team cannot. |
59 | |
60 | >>> # The registrant must be specified or it will default to the owner. |
61 | - >>> product = factory.makeProduct(registrant=admin_user, owner=public_team) |
62 | - >>> product = factory.makeProduct(registrant=admin_user, owner=priv_team) |
63 | - |
64 | - >>> product = factory.makeProduct(registrant=admin_user, owner=pm_team) |
65 | + >>> product = factory.makeProduct(registrant=admin_user) |
66 | + >>> product.owner = public_team |
67 | + >>> product.owner = priv_team |
68 | + >>> product.owner = pm_team |
69 | Traceback (most recent call last): |
70 | ... |
71 | PrivatePersonLinkageError: Cannot link person |
72 | (name=private-membership-team, visibility=PRIVATE_MEMBERSHIP) to |
73 | <Product at... |
74 | |
75 | - |
76 | Driver |
77 | ------ |
78 | |
79 | @@ -419,6 +418,55 @@ |
80 | <ProductSeries at... |
81 | |
82 | |
83 | +Product Release Roles |
84 | +===================== |
85 | + |
86 | +Owner |
87 | +----- |
88 | + |
89 | +A public team and a private team can be a product series owner but a |
90 | +private membership team cannot. |
91 | + |
92 | + >>> product = factory.makeProduct(registrant=admin_user, |
93 | + ... owner=public_team) |
94 | + >>> product_series = factory.makeProductSeries(product, owner=public_team) |
95 | + >>> product_milestone = factory.makeMilestone( |
96 | + ... product=product, productseries=product_series) |
97 | + >>> product_release = factory.makeProductRelease( |
98 | + ... product=product, milestone=product_milestone) |
99 | + >>> product_release.owner = public_team |
100 | + >>> product_release.owner = priv_team |
101 | + >>> product_release.owner = pm_team |
102 | + Traceback (most recent call last): |
103 | + ... |
104 | + PrivatePersonLinkageError: Cannot link person |
105 | + (name=private-membership-team, visibility=PRIVATE_MEMBERSHIP) to |
106 | + <ProductRelease at... |
107 | + |
108 | +Some artifacts of a product change ownership when the product owner |
109 | +changes. The artifacts are product series, product release, and |
110 | +translation import queue entries. |
111 | + |
112 | + >>> product = factory.makeProduct(registrant=admin_user) |
113 | + >>> product_series = factory.makeProductSeries( |
114 | + ... product=product, owner=public_team) |
115 | + >>> product_release = factory.makeProductRelease(product=product) |
116 | + >>> from lp.translations.interfaces.translationimportqueue import ( |
117 | + ... ITranslationImportQueue) |
118 | + >>> import_queue = getUtility(ITranslationImportQueue) |
119 | + >>> entry = import_queue.addOrUpdateEntry( |
120 | + ... u'po/sr.po', 'foo', True, public_team, |
121 | + ... productseries=product_series) |
122 | + >>> product.owner = public_team |
123 | + >>> product.owner = priv_team |
124 | + >>> product.owner = pm_team |
125 | + Traceback (most recent call last): |
126 | + ... |
127 | + PrivatePersonLinkageError: Cannot link person |
128 | + (name=private-membership-team, visibility=PRIVATE_MEMBERSHIP) to |
129 | + <Product at... |
130 | + |
131 | + |
132 | Team Membership |
133 | =============== |
134 | |
135 | |
136 | === modified file 'lib/lp/registry/doc/product.txt' |
137 | --- lib/lp/registry/doc/product.txt 2009-07-23 13:44:13 +0000 |
138 | +++ lib/lp/registry/doc/product.txt 2009-10-05 11:36:16 +0000 |
139 | @@ -63,7 +63,7 @@ |
140 | u'a52dec' |
141 | >>> productset['a52dec'].name |
142 | u'a52dec' |
143 | - |
144 | + |
145 | >>> a52dec.setAliases(['a51dec']) |
146 | >>> a52dec.aliases |
147 | [u'a51dec'] |
148 | @@ -654,3 +654,71 @@ |
149 | >>> for series in firefox_view.sorted_active_series_list: |
150 | ... print series.name |
151 | trunk |
152 | + |
153 | += Changing ownership = |
154 | + |
155 | +If the owner of a project changes, all series and productreleases |
156 | +owned by the old owner are transfered to the new owner. |
157 | + |
158 | + >>> print firefox.owner.name |
159 | + name12 |
160 | + |
161 | + >>> for series in firefox.serieses: |
162 | + ... print series.owner.name, series.name |
163 | + name12 1.0 |
164 | + name12 trunk |
165 | + |
166 | + >>> for release in firefox.releases: |
167 | + ... print release.owner.name, release.title |
168 | + name16 Mozilla Firefox 0.9 "One Tree Hill" |
169 | + name16 Mozilla Firefox 0.9.1 "One Tree Hill (v2)" |
170 | + name16 Mozilla Firefox 0.9.2 "One (secure) Tree Hill" |
171 | + name12 Mozilla Firefox 1.0.0 "First Stable Release" |
172 | + |
173 | + >>> from lp.translations.interfaces.translationimportqueue import ( |
174 | + ... ITranslationImportQueue) |
175 | + >>> import_queue = getUtility(ITranslationImportQueue) |
176 | + >>> entry = import_queue.addOrUpdateEntry( |
177 | + ... u'po/sr.po', 'foo', True, firefox.owner, |
178 | + ... productseries=firefox.serieses[0]) |
179 | + >>> foobar = getUtility(IPersonSet).getByEmail("foo.bar@canonical.com") |
180 | + >>> entry = import_queue.addOrUpdateEntry( |
181 | + ... u'po/sr.po', 'foo', True, foobar, |
182 | + ... productseries=firefox.serieses[1]) |
183 | + >>> for entry in import_queue.getAllEntries(target=firefox): |
184 | + ... print entry.importer.name |
185 | + name12 |
186 | + name16 |
187 | + |
188 | +The owner of firefox can be changed. |
189 | + |
190 | + >>> login("foo.bar@canonical.com") |
191 | + >>> mark = getUtility(IPersonSet).getByEmail('mark@example.com') |
192 | + >>> print mark.name |
193 | + mark |
194 | + |
195 | + >>> firefox.owner = mark |
196 | + |
197 | +Now that the owner for firefox has changed the series and product |
198 | +releases previously owned by name12 are now owned by mark. Those not |
199 | +owned by name12 are unchanged. |
200 | + |
201 | + >>> print firefox.owner.name |
202 | + mark |
203 | + |
204 | + >>> for series in firefox.serieses: |
205 | + ... print series.owner.name, series.name |
206 | + mark 1.0 |
207 | + mark trunk |
208 | + |
209 | + >>> for release in firefox.releases: |
210 | + ... print release.owner.name, release.title |
211 | + name16 Mozilla Firefox 0.9 "One Tree Hill" |
212 | + name16 Mozilla Firefox 0.9.1 "One Tree Hill (v2)" |
213 | + name16 Mozilla Firefox 0.9.2 "One (secure) Tree Hill" |
214 | + mark Mozilla Firefox 1.0.0 "First Stable Release" |
215 | + |
216 | + >>> for entry in import_queue.getAllEntries(target=firefox): |
217 | + ... print entry.importer.name |
218 | + mark |
219 | + name16 |
220 | |
221 | === modified file 'lib/lp/registry/interfaces/productrelease.py' |
222 | --- lib/lp/registry/interfaces/productrelease.py 2009-06-25 04:06:00 +0000 |
223 | +++ lib/lp/registry/interfaces/productrelease.py 2009-10-05 11:36:16 +0000 |
224 | @@ -27,8 +27,8 @@ |
225 | from canonical.config import config |
226 | from canonical.launchpad import _ |
227 | from canonical.launchpad.validators.version import sane_version |
228 | -from canonical.launchpad.fields import ContentNameField |
229 | -from lp.registry.interfaces.person import IPerson |
230 | +from canonical.launchpad.fields import ( |
231 | + ContentNameField, ParticipatingPersonChoice) |
232 | from canonical.launchpad.validators import LaunchpadValidationError |
233 | |
234 | from lazr.restful.fields import CollectionField, Reference, ReferenceChoice |
235 | @@ -270,9 +270,13 @@ |
236 | ) |
237 | |
238 | owner = exported( |
239 | - Reference(title=u"The owner of this release.", |
240 | - schema=IPerson, required=True) |
241 | + ParticipatingPersonChoice( |
242 | + title=u"The owner of this release.", |
243 | + required=True, |
244 | + vocabulary='ValidOwner', |
245 | + description=_("The person or team who owns his product release.") |
246 | ) |
247 | + ) |
248 | |
249 | productseries = Choice( |
250 | title=_('Release series'), readonly=True, |
251 | |
252 | === modified file 'lib/lp/registry/model/milestone.py' |
253 | --- lib/lp/registry/model/milestone.py 2009-08-24 04:03:27 +0000 |
254 | +++ lib/lp/registry/model/milestone.py 2009-10-05 11:36:16 +0000 |
255 | @@ -176,7 +176,7 @@ |
256 | """See `IMilestone`.""" |
257 | if self.product_release is not None: |
258 | raise AssertionError( |
259 | - 'A milestone can only have one Productrelease.') |
260 | + 'A milestone can only have one ProductRelease.') |
261 | return ProductRelease( |
262 | owner=owner, |
263 | changelog=changelog, |
264 | @@ -301,4 +301,3 @@ |
265 | def official_bug_tags(self): |
266 | """See `IHasBugs`.""" |
267 | return self.target.official_bug_tags |
268 | - |
269 | |
270 | === modified file 'lib/lp/registry/model/product.py' |
271 | --- lib/lp/registry/model/product.py 2009-09-16 20:08:36 +0000 |
272 | +++ lib/lp/registry/model/product.py 2009-10-05 11:36:16 +0000 |
273 | @@ -23,6 +23,7 @@ |
274 | from storm.locals import And, Desc, Join, SQL, Store, Unicode |
275 | from zope.interface import implements |
276 | from zope.component import getUtility |
277 | +from zope.security.proxy import removeSecurityProxy |
278 | |
279 | from canonical.cachedproperty import cachedproperty |
280 | from lazr.delegates import delegates |
281 | @@ -69,7 +70,7 @@ |
282 | HasSpecificationsMixin, Specification) |
283 | from lp.blueprints.model.sprint import HasSprintsMixin |
284 | from lp.translations.model.translationimportqueue import ( |
285 | - HasTranslationImportsMixin) |
286 | + HasTranslationImportsMixin, ITranslationImportQueue) |
287 | from canonical.launchpad.database.structuralsubscription import ( |
288 | StructuralSubscriptionTargetMixin) |
289 | from canonical.launchpad.helpers import shortlist |
290 | @@ -177,7 +178,7 @@ |
291 | |
292 | project = ForeignKey( |
293 | foreignKey="Project", dbName="project", notNull=False, default=None) |
294 | - owner = ForeignKey( |
295 | + _owner = ForeignKey( |
296 | dbName="owner", foreignKey="Person", |
297 | storm_validator=validate_person_not_private_membership, |
298 | notNull=True) |
299 | @@ -490,6 +491,32 @@ |
300 | |
301 | licenses = property(_getLicenses, _setLicenses) |
302 | |
303 | + def _getOwner(self): |
304 | + """Get the owner.""" |
305 | + return self._owner |
306 | + |
307 | + def _setOwner(self, new_owner): |
308 | + """Set the owner. |
309 | + |
310 | + Change the owner and change the ownership of related artifacts. |
311 | + """ |
312 | + old_owner = self._owner |
313 | + self._owner = new_owner |
314 | + if old_owner is not None: |
315 | + import_queue = getUtility(ITranslationImportQueue) |
316 | + for entry in import_queue.getAllEntries(target=self): |
317 | + if entry.importer == old_owner: |
318 | + removeSecurityProxy(entry).importer = new_owner |
319 | + for series in self.serieses: |
320 | + if series.owner == old_owner: |
321 | + series.owner = new_owner |
322 | + for release in self.releases: |
323 | + if release.owner == old_owner: |
324 | + release.owner = new_owner |
325 | + Store.of(self).flush() |
326 | + |
327 | + owner = property(_getOwner, _setOwner) |
328 | + |
329 | def _getBugTaskContextWhereClause(self): |
330 | """See BugTargetBase.""" |
331 | return "BugTask.product = %d" % self.id |
332 | @@ -1006,7 +1033,7 @@ |
333 | results = Product.selectBy( |
334 | active=True, orderBy="-Product.datecreated") |
335 | # The main product listings include owner, so we prejoin it. |
336 | - return results.prejoin(["owner"]) |
337 | + return results.prejoin(["_owner"]) |
338 | |
339 | def get(self, productid): |
340 | """See `IProductSet`.""" |
341 | @@ -1247,7 +1274,7 @@ |
342 | queries.append('Product.active IS TRUE') |
343 | query = " AND ".join(queries) |
344 | return Product.select(query, distinct=True, |
345 | - prejoins=["owner"], |
346 | + prejoins=["_owner"], |
347 | clauseTables=clauseTables) |
348 | |
349 | def getTranslatables(self): |
350 | @@ -1258,7 +1285,7 @@ |
351 | Product.id == ProductSeries.productID, |
352 | POTemplate.productseriesID == ProductSeries.id, |
353 | Product.official_rosetta == True, |
354 | - Person.id == Product.ownerID |
355 | + Person.id == Product._ownerID |
356 | ).config(distinct=True).order_by(Product.title) |
357 | |
358 | # We only want Product - the other tables are just to populate |
359 | |
360 | === modified file 'lib/lp/registry/model/productrelease.py' |
361 | --- lib/lp/registry/model/productrelease.py 2009-07-17 00:26:05 +0000 |
362 | +++ lib/lp/registry/model/productrelease.py 2009-10-05 11:36:16 +0000 |
363 | @@ -22,9 +22,11 @@ |
364 | |
365 | from canonical.launchpad.webapp.interfaces import NotFoundError |
366 | from lp.registry.interfaces.productrelease import ( |
367 | - IProductRelease, IProductReleaseFile, IProductReleaseSet, UpstreamFileType) |
368 | + IProductRelease, IProductReleaseFile, IProductReleaseSet, |
369 | + UpstreamFileType) |
370 | from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet |
371 | -from lp.registry.interfaces.person import validate_public_person |
372 | +from lp.registry.interfaces.person import ( |
373 | + validate_person_not_private_membership, validate_public_person) |
374 | from canonical.launchpad.webapp.interfaces import ( |
375 | DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE) |
376 | |
377 | @@ -45,7 +47,8 @@ |
378 | dbName='datecreated', notNull=True, default=UTC_NOW) |
379 | owner = ForeignKey( |
380 | dbName="owner", foreignKey="Person", |
381 | - storm_validator=validate_public_person, notNull=True) |
382 | + storm_validator=validate_person_not_private_membership, |
383 | + notNull=True) |
384 | milestone = ForeignKey(dbName='milestone', foreignKey='Milestone') |
385 | |
386 | files = SQLMultipleJoin( |
387 | |
388 | === renamed file 'lib/canonical/launchpad/pagetests/webservice/xx-people.txt' => 'lib/lp/registry/stories/webservice/xx-people.txt' |
389 | === renamed file 'lib/canonical/launchpad/pagetests/webservice/xx-personlocation.txt' => 'lib/lp/registry/stories/webservice/xx-personlocation.txt' |
390 | === renamed file 'lib/canonical/launchpad/pagetests/webservice/xx-private-membership.txt' => 'lib/lp/registry/stories/webservice/xx-private-membership.txt' |
391 | === renamed file 'lib/canonical/launchpad/pagetests/webservice/xx-project-registry.txt' => 'lib/lp/registry/stories/webservice/xx-project-registry.txt' |
392 | --- lib/canonical/launchpad/pagetests/webservice/xx-project-registry.txt 2009-08-21 19:49:18 +0000 |
393 | +++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2009-10-05 11:36:16 +0000 |
394 | @@ -338,6 +338,47 @@ |
395 | >>> webservice.get(firefox['bug_tracker_link']).jsonBody()['self_link'] |
396 | u'http://.../bugs/bugtrackers/mozilla.org' |
397 | |
398 | +When the owner_link is changed the ownership of some attributes is |
399 | +changed as well. |
400 | + |
401 | + >>> # Create a product with a series and release. |
402 | + >>> login('test@canonical.com') |
403 | + >>> test_project_owner = factory.makePerson(name='test-project-owner') |
404 | + >>> test_project = factory.makeProduct(name='test-project', owner=test_project_owner) |
405 | + >>> test_series = factory.makeProductSeries( |
406 | + ... product=test_project, name='test-series', owner=test_project_owner) |
407 | + >>> test_milestone = factory.makeMilestone( |
408 | + ... product=test_project, name='test-milestone', productseries=test_series) |
409 | + >>> test_project_release = factory.makeProductRelease( |
410 | + ... product=test_project, milestone=test_milestone) |
411 | + >>> logout() |
412 | + |
413 | + >>> test_project = webservice.get('/test-project').jsonBody() |
414 | + >>> test_project['owner_link'] |
415 | + u'http://.../~test-project-owner' |
416 | + |
417 | + >>> patch = { |
418 | + ... u'owner_link': webservice.getAbsoluteUrl('/~mark'), |
419 | + ... } |
420 | + >>> print webservice.patch( |
421 | + ... '/test-project', 'application/json', dumps(patch)) |
422 | + HTTP/1.1 209 Content Returned |
423 | + ... |
424 | + |
425 | + >>> test_project = webservice.get('/test-project').jsonBody() |
426 | + >>> test_project['owner_link'] |
427 | + u'http://.../~mark' |
428 | + |
429 | + >>> test_series = webservice.get('/test-project/test-series').jsonBody() |
430 | + >>> test_series['owner_link'] |
431 | + u'http://.../~mark' |
432 | + |
433 | + >>> release_path = '/test-project/test-series/test-milestone' |
434 | + >>> test_project_release = webservice.get(release_path).jsonBody() |
435 | + >>> test_project_release['owner_link'] |
436 | + u'http://.../~mark' |
437 | + |
438 | + |
439 | Read-only attributes, like registrant, cannot be modified via the |
440 | webservice.patch() method. |
441 | |
442 | @@ -424,24 +465,37 @@ |
443 | >>> project_collection['resource_type_link'] |
444 | u'http://.../#projects' |
445 | |
446 | +The entire collection has 25 entries. |
447 | + |
448 | >>> project_collection['total_size'] |
449 | - 24 |
450 | + 25 |
451 | + |
452 | +But the batch has only 5. (The batch size is 5 for testing but larger |
453 | +in production.) |
454 | + |
455 | + >>> project_entries = project_collection['entries'] |
456 | + >>> len(project_entries) |
457 | + 5 |
458 | + |
459 | +The batch size can be changed through the ws.size argument. |
460 | + |
461 | + >>> project_collection = webservice.get("/projects?ws.size=75").jsonBody() |
462 | |
463 | >>> project_entries = sorted( |
464 | ... project_collection['entries'], key=itemgetter('display_name')) |
465 | >>> len(project_entries) |
466 | - 5 |
467 | + 25 |
468 | |
469 | >>> project_entries[0]['self_link'] |
470 | - u'http://.../gnome-terminal' |
471 | + u'http://.../aptoncd' |
472 | |
473 | - >>> for project in project_entries: |
474 | - ... print project['display_name'] |
475 | - GNOME Terminal |
476 | - Mozilla Firefox |
477 | - Redfish |
478 | - The Landscape Project |
479 | - Tomcat |
480 | + >>> for project in project_entries[:5]: |
481 | + ... print "%s (%s)" % (project['display_name'], project['name']) |
482 | + APTonCD (aptoncd) |
483 | + Arch mirrors (arch-mirrors) |
484 | + Bazaar (bazaar) |
485 | + Bazaar (bzr) |
486 | + Derby (derby) |
487 | |
488 | It's possible to search the list and get a subset of the project groups. |
489 | |
490 | @@ -480,7 +534,7 @@ |
491 | Launchpad Translations |
492 | Mega Money Maker |
493 | Obsolete Junk |
494 | - Redfish |
495 | + Test-project |
496 | |
497 | There is a method for doing a query about attributes related to project |
498 | licensing. We can find all projects with unreviewed licenses. |
499 | @@ -726,10 +780,13 @@ |
500 | virtual host. |
501 | |
502 | >>> login('test@canonical.com') |
503 | - >>> babadoo = factory.makeProduct(name='babadoo') |
504 | - >>> foobadoo = factory.makeProductSeries(product=babadoo, name='foobadoo') |
505 | + >>> babadoo_owner = factory.makePerson(name='babadoo-owner') |
506 | + >>> babadoo = factory.makeProduct(name='babadoo', owner=babadoo_owner) |
507 | + >>> foobadoo = factory.makeProductSeries( |
508 | + ... product=babadoo, name='foobadoo', owner=babadoo_owner) |
509 | >>> foobadoo.summary = u'Foobadoo support for Babadoo' |
510 | - >>> fooey = factory.makeAnyBranch(product=babadoo, name='fooey') |
511 | + >>> fooey = factory.makeAnyBranch( |
512 | + ... product=babadoo, name='fooey', owner=babadoo_owner) |
513 | >>> foobadoo.branch = fooey |
514 | >>> logout() |
515 | |
516 | @@ -737,7 +794,7 @@ |
517 | >>> pprint_entry(babadoo_foobadoo) |
518 | active_milestones_collection_link: u'http://.../babadoo/foobadoo/active_milestones' |
519 | all_milestones_collection_link: u'http://.../babadoo/foobadoo/all_milestones' |
520 | - branch_link: u'http://api.launchpad.dev/beta/~person-name12/babadoo/fooey' |
521 | + branch_link: u'http://.../~babadoo-owner/babadoo/fooey' |
522 | bug_reporting_guidelines: None |
523 | date_created: u'...' |
524 | display_name: u'foobadoo' |
525 | @@ -745,7 +802,7 @@ |
526 | drivers_collection_link: u'http://.../babadoo/foobadoo/drivers' |
527 | name: u'foobadoo' |
528 | official_bug_tags: [] |
529 | - owner_link: u'http://.../~person-name8' |
530 | + owner_link: u'http://.../~babadoo-owner' |
531 | project_link: u'http://.../babadoo' |
532 | releases_collection_link: u'http://.../babadoo/foobadoo/releases' |
533 | resource_type_link: u'...' |
534 | @@ -885,7 +942,7 @@ |
535 | >>> print response |
536 | HTTP/1.1 500 Internal Server Error |
537 | ... |
538 | - AssertionError: A milestone can only have one Productrelease. |
539 | + AssertionError: A milestone can only have one ProductRelease. |
540 | |
541 | |
542 | Project release entries |
543 | |
544 | === modified file 'lib/lp/registry/tests/test_doc.py' |
545 | --- lib/lp/registry/tests/test_doc.py 2009-07-17 00:26:05 +0000 |
546 | +++ lib/lp/registry/tests/test_doc.py 2009-10-05 11:36:16 +0000 |
547 | @@ -133,6 +133,18 @@ |
548 | tearDown=tearDown, |
549 | layer=LaunchpadFunctionalLayer, |
550 | ), |
551 | + 'product.txt': LayeredDocFileSuite( |
552 | + '../doc/product.txt', |
553 | + setUp=setUp, |
554 | + tearDown=tearDown, |
555 | + layer=LaunchpadFunctionalLayer, |
556 | + ), |
557 | + 'private-team-roles.txt': LayeredDocFileSuite( |
558 | + '../doc/private-team-roles.txt', |
559 | + setUp=setUp, |
560 | + tearDown=tearDown, |
561 | + layer=LaunchpadFunctionalLayer, |
562 | + ), |
563 | 'productrelease.txt': LayeredDocFileSuite( |
564 | '../doc/productrelease.txt', |
565 | setUp=setUp, |
566 | |
567 | === modified file 'lib/lp/services/database/precache.py' |
568 | --- lib/lp/services/database/precache.py 2009-08-05 18:52:52 +0000 |
569 | +++ lib/lp/services/database/precache.py 2009-10-05 11:36:16 +0000 |
570 | @@ -27,7 +27,7 @@ |
571 | |
572 | >>> results = store.find(Product).precache( |
573 | ... (Person, EmailAddress), |
574 | - ... Product.ownerID == Person.id, |
575 | + ... Product._ownerID == Person.id, |
576 | ... EmailAddress.personID == Person.id) |
577 | """ |
578 | delegates(IResultSet, context='result_set') |
579 | |
580 | === modified file 'lib/lp/services/database/tests/test_precache.py' |
581 | --- lib/lp/services/database/tests/test_precache.py 2009-07-17 02:25:09 +0000 |
582 | +++ lib/lp/services/database/tests/test_precache.py 2009-10-05 11:36:16 +0000 |
583 | @@ -33,7 +33,7 @@ |
584 | # to hide this from callsites. |
585 | self.unwrapped_result = self.store.find( |
586 | (Product, Person), |
587 | - Product.ownerID == Person.id).order_by(Product.name) |
588 | + Product._ownerID == Person.id).order_by(Product.name) |
589 | self.precache_result = precache(self.unwrapped_result) |
590 | |
591 | def verify(self, precached, normal): |
592 | @@ -87,7 +87,7 @@ |
593 | standard_result = self.store.find(Product, Product.name == 'firefox') |
594 | precache_result = precache(self.store.find( |
595 | (Product, Person), |
596 | - Person.id == Product.ownerID, |
597 | + Person.id == Product._ownerID, |
598 | Product.name == 'firefox')) |
599 | self.assertEqual(standard_result.one(), precache_result.one()) |
600 | |
601 | |
602 | === modified file 'lib/lp/testing/factory.py' |
603 | --- lib/lp/testing/factory.py 2009-09-19 04:06:14 +0000 |
604 | +++ lib/lp/testing/factory.py 2009-10-05 11:36:16 +0000 |
605 | @@ -76,7 +76,6 @@ |
606 | CodeImportResultStatus, CodeReviewNotificationLevel, |
607 | RevisionControlSystems) |
608 | from lp.code.interfaces.branch import UnknownBranchTypeError |
609 | -from lp.code.interfaces.branchtarget import IBranchTarget |
610 | from lp.code.interfaces.branchmergequeue import IBranchMergeQueueSet |
611 | from lp.code.interfaces.branchnamespace import get_branch_namespace |
612 | from lp.code.interfaces.codeimport import ICodeImportSet |
613 | @@ -509,9 +508,9 @@ |
614 | productseries=productseries, |
615 | name=name) |
616 | |
617 | - def makeProductRelease(self, milestone=None): |
618 | + def makeProductRelease(self, milestone=None, product=None): |
619 | if milestone is None: |
620 | - milestone = self.makeMilestone() |
621 | + milestone = self.makeMilestone(product=product) |
622 | return milestone.createProductRelease( |
623 | milestone.product.owner, datetime.now(pytz.UTC)) |
624 | |
625 | |
626 | === modified file 'lib/lp/translations/interfaces/translationimportqueue.py' |
627 | --- lib/lp/translations/interfaces/translationimportqueue.py 2009-09-18 07:39:51 +0000 |
628 | +++ lib/lp/translations/interfaces/translationimportqueue.py 2009-10-05 11:36:16 +0000 |
629 | @@ -9,15 +9,15 @@ |
630 | from lazr.enum import DBEnumeratedType, DBItem, EnumeratedType, Item |
631 | |
632 | from canonical.launchpad import _ |
633 | +from canonical.launchpad.fields import ParticipatingPersonChoice |
634 | from lp.registry.interfaces.sourcepackage import ISourcePackage |
635 | from lp.translations.interfaces.translationfileformat import ( |
636 | TranslationFileFormat) |
637 | from lp.registry.interfaces.distroseries import IDistroSeries |
638 | -from lp.registry.interfaces.person import IPerson |
639 | from lp.registry.interfaces.productseries import IProductSeries |
640 | |
641 | from lazr.restful.interface import copy_field |
642 | -from lazr.restful.fields import Reference, ReferenceChoice |
643 | +from lazr.restful.fields import Reference |
644 | from lazr.restful.declarations import ( |
645 | collection_default_content, exported, export_as_webservice_collection, |
646 | export_as_webservice_entry, export_read_operation, operation_parameters, |
647 | @@ -152,9 +152,8 @@ |
648 | required=True)) |
649 | |
650 | importer = exported( |
651 | - ReferenceChoice( |
652 | + ParticipatingPersonChoice( |
653 | title=_("Uploader"), |
654 | - schema=IPerson, |
655 | required=True, |
656 | readonly=True, |
657 | description=_( |
658 | |
659 | === modified file 'lib/lp/translations/model/translationimportqueue.py' |
660 | --- lib/lp/translations/model/translationimportqueue.py 2009-09-28 09:46:27 +0000 |
661 | +++ lib/lp/translations/model/translationimportqueue.py 2009-10-05 11:36:16 +0000 |
662 | @@ -38,7 +38,8 @@ |
663 | from lp.registry.interfaces.distribution import IDistribution |
664 | from lp.registry.interfaces.distroseries import ( |
665 | IDistroSeries, DistroSeriesStatus) |
666 | -from lp.registry.interfaces.person import IPerson |
667 | +from lp.registry.interfaces.person import ( |
668 | + IPerson, validate_person_not_private_membership) |
669 | from lp.registry.interfaces.product import IProduct |
670 | from lp.registry.interfaces.productseries import IProductSeries |
671 | from lp.registry.interfaces.sourcepackage import ISourcePackage |
672 | @@ -61,7 +62,6 @@ |
673 | from lp.translations.utilities.gettext_po_importer import ( |
674 | GettextPOImporter) |
675 | from canonical.librarian.interfaces import ILibrarianClient |
676 | -from lp.registry.interfaces.person import validate_public_person |
677 | |
678 | |
679 | # Period to wait before entries with terminal statuses are removed from |
680 | @@ -120,7 +120,8 @@ |
681 | notNull=False) |
682 | importer = ForeignKey( |
683 | dbName='importer', foreignKey='Person', |
684 | - storm_validator=validate_public_person, notNull=True) |
685 | + storm_validator=validate_person_not_private_membership, |
686 | + notNull=True) |
687 | dateimported = UtcDateTimeCol(dbName='dateimported', notNull=True, |
688 | default=DEFAULT) |
689 | sourcepackagename_id = Int(name='sourcepackagename', allow_none=True) |
= Summary =
Originally it was noted in bug 422128 that changing the ownership of a product to a
private team failed if that product had any product releases owned by the original
owner of the product. That failure was because a ProductRelease could not be owned
by a private team and the view code was changing the ownership of the ProductRelease too.
Further investigation showed that the view code was changing product releases,
product series, and translation import queue entries that were owned by the old
product owner.
== Proposed fix ==
The first fix was to get the ownership reassignment out of the view and into the
model where it belongs. Being in the view meant that changing a product's owner via
the API wouldn't do the same artifact ownership reassignment.
Once that was done, changing ProductRelease and TranslationImpo rtQueueEntry to allow
the owner and importer, respectively, to be a private team was straightforward.
The webservice tests for registry items was moved to the proper place under lp/registry.
== Pre-implementation notes ==
None.
== Implementation details ==
As above.
== Tests ==
bin/test -t private- team-roles. txt -t xx-project- registry. txt -t doc/product.txt
== Demo and Q/A ==
On launchpad.dev change the owner of firefox and ensure it works.
= Launchpad lint =
Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.
Linting changed files: registry/ interfaces/ productrelease. py registry/ model/productre lease.py registry/ doc/private- team-roles. txt registry/ doc/product. txt registry/ browser/ product. py translations/ model/translati onimportqueue. py testing/ factory. py registry/ tests/test_ doc.py registry/ stories/ webservice/ xx-project- registry. txt registry/ model/product. py registry/ model/milestone .py translations/ interfaces/ translationimpo rtqueue. py
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
== Pylint notices ==
lib/lp/ registry/ interfaces/ productrelease. py fields' (No module named restful) interface' (No module named restful) declarations' (No module named restful)
25: [F0401] Unable to import 'lazr.enum' (No module named enum)
34: [F0401] Unable to import 'lazr.restful.
35: [F0401] Unable to import 'lazr.restful.
36: [F0401] Unable to import 'lazr.restful.
lib/lp/ registry/ browser/ product. py
56: [F0401] Unable to import 'lazr.delegates' (No module named delegates)
lib/lp/ registry/ model/product. py
29: [F0401] Unable to import 'lazr.delegates' (No module named delegates)
lib/lp/ translations/ interfaces/ translationimpo rtqueue. py interface' (No module named restful) fields' (No module named restful) declarations' (No module named restful)
9: [F0401] Unable to import 'lazr.enum' (No module named enum)
19: [F0401] Unable to import 'lazr.restful.
20: [F0401] Unable to import 'lazr.restful.
21: [F0401] Unable to import 'lazr.restful.