Merge lp:~jml/launchpad/upload-permission-joy into lp:launchpad
- upload-permission-joy
- Merge into devel
Proposed by
Jonathan Lange
Status: | Merged |
---|---|
Approved by: | Jonathan Lange |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~jml/launchpad/upload-permission-joy |
Merge into: | lp:launchpad |
Diff against target: |
698 lines (+332/-81) 10 files modified
lib/canonical/launchpad/security.py (+4/-36) lib/lp/archiveuploader/nascentupload.py (+10/-5) lib/lp/archiveuploader/permission.py (+86/-1) lib/lp/archiveuploader/uploadpolicy.py (+0/-23) lib/lp/registry/interfaces/distribution.py (+10/-0) lib/lp/registry/interfaces/sourcepackage.py (+14/-0) lib/lp/registry/model/sourcepackage.py (+18/-1) lib/lp/registry/tests/test_sourcepackage.py (+57/-0) lib/lp/soyuz/interfaces/component.py (+2/-2) lib/lp/testing/factory.py (+131/-13) |
To merge this branch: | bzr merge lp:~jml/launchpad/upload-permission-joy |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Penhey (community) | Approve | ||
Abel Deuring | Pending | ||
Review via email: mp+14529@code.launchpad.net |
Commit message
Extract a function that gives a clear answer to whether you can upload to a package. Use it for branch edit checks.
Description of the change
To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote : | # |
Revision history for this message
Abel Deuring (adeuring) wrote : | # |
returning the review to the team -- got interrupted by an, let's say, serious OOPS...
Revision history for this message
Tim Penhey (thumper) wrote : | # |
As discussed on IRC, some small changes, but otherwise good.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/canonical/launchpad/security.py' |
2 | --- lib/canonical/launchpad/security.py 2009-11-17 09:51:40 +0000 |
3 | +++ lib/canonical/launchpad/security.py 2009-11-19 15:18:17 +0000 |
4 | @@ -10,8 +10,8 @@ |
5 | from zope.component import getUtility |
6 | |
7 | from canonical.launchpad.interfaces.account import IAccount |
8 | +from lp.archiveuploader.permission import can_upload_to_archive |
9 | from canonical.launchpad.interfaces.emailaddress import IEmailAddress |
10 | -from lp.archiveuploader.permission import verify_upload |
11 | from lp.registry.interfaces.announcement import IAnnouncement |
12 | from lp.soyuz.interfaces.archive import IArchive |
13 | from lp.soyuz.interfaces.archivepermission import ( |
14 | @@ -1578,17 +1578,6 @@ |
15 | |
16 | def can_upload_linked_package(person, branch): |
17 | """True if person may upload the package linked to `branch`.""" |
18 | - |
19 | - def get_current_release(ssp): |
20 | - """Get current release for the source package linked to branch. |
21 | - |
22 | - This function uses the `ISuiteSourcePackage` instance supplied. |
23 | - """ |
24 | - package = ssp.sourcepackage |
25 | - releases = ssp.distroseries.getCurrentSourceReleases( |
26 | - [package.sourcepackagename]) |
27 | - return releases.get(package, None) |
28 | - |
29 | # No associated `ISuiteSourcePackage` data -> not an official branch. |
30 | # Abort. |
31 | ssp_list = branch.associatedSuiteSourcePackages() |
32 | @@ -1601,31 +1590,10 @@ |
33 | # around this by assuming that things are fine as long as we find at least |
34 | # one combination that allows us to upload the corresponding source |
35 | # package. |
36 | - |
37 | - # Go through the associated `ISuiteSourcePackage` instances and see |
38 | - # whether we can upload to any of the distro series/pocket combinations. |
39 | - ssp = None |
40 | for ssp in ssp_list: |
41 | - # Can we upload to the respective pocket? |
42 | - if ssp.distroseries.canUploadToPocket(ssp.pocket): |
43 | - break |
44 | - else: |
45 | - # Loop terminated normally i.e. we could not upload to any of the |
46 | - # (distroseries, pocket) combinations found. |
47 | - return False |
48 | - |
49 | - archive = ssp.distroseries.distribution.main_archive |
50 | - # Find the component the linked source package was published in. |
51 | - current_release = get_current_release(ssp) |
52 | - component = getattr(current_release, 'component', None) |
53 | - |
54 | - # Is person authorised to upload the source package this branch |
55 | - # is targeting? |
56 | - result = verify_upload( |
57 | - person, ssp.sourcepackagename, archive, component, ssp.distroseries) |
58 | - # verify_upload() indicates that person *is* allowed to upload by |
59 | - # returning None. |
60 | - return result is None |
61 | + if can_upload_to_archive(person, ssp): |
62 | + return True |
63 | + return False |
64 | |
65 | |
66 | class AdminBranch(AuthorizationBase): |
67 | |
68 | === modified file 'lib/lp/archiveuploader/nascentupload.py' |
69 | --- lib/lp/archiveuploader/nascentupload.py 2009-11-09 17:54:02 +0000 |
70 | +++ lib/lp/archiveuploader/nascentupload.py 2009-11-19 15:18:17 +0000 |
71 | @@ -28,7 +28,7 @@ |
72 | from lp.archiveuploader.nascentuploadfile import ( |
73 | UploadError, UploadWarning, CustomUploadFile, SourceUploadFile, |
74 | BaseBinaryUploadFile) |
75 | -from lp.archiveuploader.permission import verify_upload |
76 | +from lp.archiveuploader.permission import check_upload_to_archive |
77 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
78 | from lp.soyuz.interfaces.archive import ArchivePurpose, MAIN_ARCHIVE_PURPOSES |
79 | from canonical.launchpad.interfaces import ( |
80 | @@ -504,9 +504,10 @@ |
81 | source_name = getUtility( |
82 | ISourcePackageNameSet).queryByName(self.changes.dsc.package) |
83 | |
84 | - rejection_reason = verify_upload( |
85 | - signer, source_name, archive, self.changes.dsc.component, |
86 | - self.policy.distroseries, not self.is_new) |
87 | + rejection_reason = check_upload_to_archive( |
88 | + signer, self.policy.distroseries, source_name, archive, |
89 | + self.changes.dsc.component, self.policy.pocket, not self.is_new) |
90 | + |
91 | if rejection_reason is not None: |
92 | self.reject(str(rejection_reason)) |
93 | |
94 | @@ -1012,6 +1013,11 @@ |
95 | if self.is_ppa: |
96 | return |
97 | |
98 | + # XXX: JonathanLange 2009-09-16: It would be nice to use |
99 | + # ISourcePackage.get_default_archive here, since it has the same |
100 | + # logic. However, I'm not sure whether we can get a SourcePackage |
101 | + # object. |
102 | + |
103 | # See if there is an archive to override with. |
104 | distribution = self.policy.distroseries.distribution |
105 | archive = distribution.getArchiveByComponent( |
106 | @@ -1027,4 +1033,3 @@ |
107 | else: |
108 | # Reset the archive in the policy to the partner archive. |
109 | self.policy.archive = archive |
110 | - |
111 | |
112 | === modified file 'lib/lp/archiveuploader/permission.py' |
113 | --- lib/lp/archiveuploader/permission.py 2009-11-09 17:50:23 +0000 |
114 | +++ lib/lp/archiveuploader/permission.py 2009-11-19 15:18:17 +0000 |
115 | @@ -7,14 +7,17 @@ |
116 | __all__ = [ |
117 | 'CannotUploadToArchive', |
118 | 'CannotUploadToPPA', |
119 | + 'can_upload_to_archive', |
120 | + 'check_upload_to_archive', |
121 | 'components_valid_for', |
122 | 'verify_upload', |
123 | ] |
124 | |
125 | from zope.component import getUtility |
126 | |
127 | -from lp.registry.interfaces.distribution import IDistributionSet |
128 | +from lp.registry.interfaces.pocket import PackagePublishingPocket |
129 | from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet |
130 | +from lp.soyuz.interfaces.archive import ArchivePurpose |
131 | |
132 | |
133 | class CannotUploadToArchive: |
134 | @@ -30,6 +33,15 @@ |
135 | return self._message |
136 | |
137 | |
138 | +class CannotUploadToPocket: |
139 | + """Returned when a pocket is closed for uploads.""" |
140 | + |
141 | + def __init__(self, distroseries, pocket): |
142 | + super(CannotUploadToPocket, self).__init__( |
143 | + "Not permitted to upload to the %s pocket in a series in the " |
144 | + "'%s' state." % (pocket.name, distroseries.status.name)) |
145 | + |
146 | + |
147 | class CannotUploadToPPA(CannotUploadToArchive): |
148 | """Raised when a person cannot upload to a PPA.""" |
149 | |
150 | @@ -63,6 +75,18 @@ |
151 | super(NoRightsForComponent, self).__init__(component=component.name) |
152 | |
153 | |
154 | +class InvalidPocketForPPA(CannotUploadToArchive): |
155 | + """PPAs only support some pockets.""" |
156 | + |
157 | + _fmt = "PPA uploads must be for the RELEASE pocket." |
158 | + |
159 | + |
160 | +class InvalidPocketForPartnerArchive(CannotUploadToArchive): |
161 | + """Partner archives only support some pockets.""" |
162 | + |
163 | + _fmt = "Partner uploads must be for the RELEASE or PROPOSED pocket." |
164 | + |
165 | + |
166 | def components_valid_for(archive, person): |
167 | """Return the components that 'person' can upload to 'archive'. |
168 | |
169 | @@ -75,6 +99,67 @@ |
170 | return set(permission.component for permission in permissions) |
171 | |
172 | |
173 | +def can_upload_to_archive(person, suitesourcepackage, archive=None): |
174 | + """Check if 'person' upload 'suitesourcepackage' to 'archive'. |
175 | + |
176 | + :param person: An `IPerson` who might be uploading. |
177 | + :param suitesourcepackage: An `ISuiteSourcePackage` to be uploaded. |
178 | + :param archive: The `IArchive` to upload to. If not provided, defaults |
179 | + to the default archive for the source package. (See |
180 | + `ISourcePackage.get_default_archive`). |
181 | + :return: True if they can, False if they cannot. |
182 | + """ |
183 | + sourcepackage = suitesourcepackage.sourcepackage |
184 | + if archive is None: |
185 | + archive = sourcepackage.get_default_archive() |
186 | + pocket = suitesourcepackage.pocket |
187 | + distroseries = sourcepackage.distroseries |
188 | + sourcepackagename = sourcepackage.sourcepackagename |
189 | + component = sourcepackage.latest_published_component |
190 | + # strict_component is True because the source package already exists |
191 | + # (otherwise we couldn't have a suitesourcepackage object) and |
192 | + # nascentupload passes True as a matter of policy when the package exists. |
193 | + reason = check_upload_to_archive( |
194 | + person, distroseries, sourcepackagename, archive, component, pocket, |
195 | + strict_component=True) |
196 | + return reason is None |
197 | + |
198 | + |
199 | +def check_upload_to_archive(person, distroseries, sourcepackagename, archive, |
200 | + component, pocket, strict_component=True): |
201 | + """Check if 'person' upload 'suitesourcepackage' to 'archive'. |
202 | + |
203 | + :param person: An `IPerson` who might be uploading. |
204 | + :param distroseries: The `IDistroSeries` being uploaded to. |
205 | + :param sourcepackagename: The `ISourcePackageName` being uploaded. |
206 | + :param archive: The `IArchive` to upload to. If not provided, defaults |
207 | + to the default archive for the source package. (See |
208 | + `ISourcePackage.get_default_archive`). |
209 | + :param component: The `Component` being uploaded to. |
210 | + :param pocket: The `PackagePublishingPocket` of 'distroseries' being |
211 | + uploaded to. |
212 | + :return: The reason for not being able to upload, None otherwise. |
213 | + """ |
214 | + if archive.purpose == ArchivePurpose.PARTNER: |
215 | + if pocket not in ( |
216 | + PackagePublishingPocket.RELEASE, |
217 | + PackagePublishingPocket.PROPOSED): |
218 | + return InvalidPocketForPartnerArchive() |
219 | + else: |
220 | + # Uploads to the partner archive are allowed in any distroseries |
221 | + # state. |
222 | + # XXX julian 2005-05-29 bug=117557: |
223 | + # This is a greasy hack until bug #117557 is fixed. |
224 | + if not distroseries.canUploadToPocket(pocket): |
225 | + return CannotUploadToPocket(distroseries, pocket) |
226 | + |
227 | + if archive.is_ppa and pocket != PackagePublishingPocket.RELEASE: |
228 | + return InvalidPocketForPPA() |
229 | + return verify_upload( |
230 | + person, sourcepackagename, archive, component, distroseries, |
231 | + strict_component) |
232 | + |
233 | + |
234 | def packagesets_valid_for(archive, person): |
235 | """Return the package sets that 'person' can upload to 'archive'. |
236 | |
237 | |
238 | === modified file 'lib/lp/archiveuploader/uploadpolicy.py' |
239 | --- lib/lp/archiveuploader/uploadpolicy.py 2009-08-28 06:39:38 +0000 |
240 | +++ lib/lp/archiveuploader/uploadpolicy.py 2009-11-19 15:18:17 +0000 |
241 | @@ -130,29 +130,6 @@ |
242 | # Buildd binary uploads (resulting from successful builds) |
243 | # to copy archives may go into *any* pocket. |
244 | return |
245 | - if upload.is_ppa: |
246 | - if self.pocket != PackagePublishingPocket.RELEASE: |
247 | - upload.reject( |
248 | - "PPA uploads must be for the RELEASE pocket.") |
249 | - elif (self.archive.purpose == ArchivePurpose.PARTNER and |
250 | - self.pocket != PackagePublishingPocket.RELEASE and |
251 | - self.pocket != PackagePublishingPocket.PROPOSED): |
252 | - # Partner uploads can only go to the release or proposed |
253 | - # pockets. |
254 | - upload.reject( |
255 | - "Partner uploads must be for the RELEASE or PROPOSED pocket.") |
256 | - else: |
257 | - # Uploads to the partner archive are allowed in any distroseries |
258 | - # state. |
259 | - # XXX julian 2005-05-29 bug=117557: |
260 | - # This is a greasy hack until bug #117557 is fixed. |
261 | - if (self.distroseries and |
262 | - self.archive.purpose != ArchivePurpose.PARTNER and |
263 | - not self.distroseries.canUploadToPocket(self.pocket)): |
264 | - upload.reject( |
265 | - "Not permitted to upload to the %s pocket in a " |
266 | - "series in the '%s' state." % ( |
267 | - self.pocket.name, self.distroseries.status.name)) |
268 | |
269 | # reject PPA uploads by default |
270 | self.rejectPPAUploads(upload) |
271 | |
272 | === modified file 'lib/lp/registry/interfaces/distribution.py' |
273 | --- lib/lp/registry/interfaces/distribution.py 2009-11-13 16:24:01 +0000 |
274 | +++ lib/lp/registry/interfaces/distribution.py 2009-11-19 15:18:17 +0000 |
275 | @@ -16,6 +16,7 @@ |
276 | 'IDistributionMirrorMenuMarker', |
277 | 'IDistributionPublic', |
278 | 'IDistributionSet', |
279 | + 'NoPartnerArchive', |
280 | 'NoSuchDistribution', |
281 | ] |
282 | |
283 | @@ -580,5 +581,14 @@ |
284 | _message_prefix = "No such distribution" |
285 | |
286 | |
287 | +class NoPartnerArchive(Exception): |
288 | + """Raised when a partner archive is needed, but none exists.""" |
289 | + |
290 | + def __init__(self, distribution): |
291 | + Exception.__init__( |
292 | + self, "Partner archive for distro '%s' not found" |
293 | + % (distribution.name,)) |
294 | + |
295 | + |
296 | # Monkey patching to fix circular imports done in |
297 | # _schema_circular_imports.py |
298 | |
299 | === modified file 'lib/lp/registry/interfaces/sourcepackage.py' |
300 | --- lib/lp/registry/interfaces/sourcepackage.py 2009-11-15 23:23:12 +0000 |
301 | +++ lib/lp/registry/interfaces/sourcepackage.py 2009-11-19 15:18:17 +0000 |
302 | @@ -224,6 +224,20 @@ |
303 | title=u'The component in which the package was last published.', |
304 | schema=IComponent, readonly=True, required=False) |
305 | |
306 | + def get_default_archive(component=None): |
307 | + """Get the default archive of this package. |
308 | + |
309 | + If 'component' is a partner component, then the default archive is the |
310 | + partner archive. Otherwise, the primary archive of the associated |
311 | + distribution. |
312 | + |
313 | + :param component: The `IComponent` to base the default archive |
314 | + decision on. If None, defaults to the last published component. |
315 | + :raise NoPartnerArchive: If returning the partner archive is |
316 | + appropriate, but no partner archive exists. |
317 | + :return: `IArchive`. |
318 | + """ |
319 | + |
320 | def getLatestTranslationsUploads(): |
321 | """Find latest Translations tarballs as produced by Soyuz. |
322 | |
323 | |
324 | === modified file 'lib/lp/registry/model/sourcepackage.py' |
325 | --- lib/lp/registry/model/sourcepackage.py 2009-11-18 11:52:25 +0000 |
326 | +++ lib/lp/registry/model/sourcepackage.py 2009-11-19 15:18:17 +0000 |
327 | @@ -23,10 +23,11 @@ |
328 | from canonical.database.sqlbase import flush_database_updates, sqlvalues |
329 | from canonical.lazr.utils import smartquote |
330 | from lp.code.model.branch import Branch |
331 | +from lp.code.model.hasbranches import HasBranchesMixin, HasMergeProposalsMixin |
332 | from lp.bugs.model.bug import get_bug_tags_open_count |
333 | from lp.bugs.model.bugtarget import BugTargetBase |
334 | from lp.bugs.model.bugtask import BugTask |
335 | -from lp.code.model.hasbranches import HasBranchesMixin, HasMergeProposalsMixin |
336 | +from lp.soyuz.interfaces.archive import IArchiveSet, ArchivePurpose |
337 | from lp.soyuz.model.build import Build, BuildSet |
338 | from lp.soyuz.model.distributionsourcepackagerelease import ( |
339 | DistributionSourcePackageRelease) |
340 | @@ -52,6 +53,7 @@ |
341 | from lp.soyuz.interfaces.buildrecords import IHasBuildRecords |
342 | from lp.registry.interfaces.packaging import PackagingType |
343 | from lp.translations.interfaces.potemplate import IHasTranslationTemplates |
344 | +from lp.registry.interfaces.distribution import NoPartnerArchive |
345 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
346 | from lp.soyuz.interfaces.publishing import PackagePublishingStatus |
347 | from lp.soyuz.interfaces.queue import PackageUploadCustomFormat |
348 | @@ -579,6 +581,21 @@ |
349 | else: |
350 | return None |
351 | |
352 | + def get_default_archive(self, component=None): |
353 | + """See `ISourcePackage`.""" |
354 | + if component is None: |
355 | + component = self.latest_published_component |
356 | + distribution = self.distribution |
357 | + if component is not None and component.name == 'partner': |
358 | + archive = getUtility(IArchiveSet).getByDistroPurpose( |
359 | + distribution, ArchivePurpose.PARTNER) |
360 | + if archive is None: |
361 | + raise NoPartnerArchive(distribution) |
362 | + else: |
363 | + return archive |
364 | + else: |
365 | + return distribution.main_archive |
366 | + |
367 | def getTranslationTemplates(self): |
368 | """See `IHasTranslationTemplates`.""" |
369 | result = POTemplate.selectBy( |
370 | |
371 | === modified file 'lib/lp/registry/tests/test_sourcepackage.py' |
372 | --- lib/lp/registry/tests/test_sourcepackage.py 2009-08-28 06:39:38 +0000 |
373 | +++ lib/lp/registry/tests/test_sourcepackage.py 2009-11-19 15:18:17 +0000 |
374 | @@ -12,9 +12,13 @@ |
375 | from zope.security.proxy import removeSecurityProxy |
376 | |
377 | from canonical.launchpad.ftests import login_person, logout |
378 | +from lp.registry.interfaces.distribution import NoPartnerArchive |
379 | from lp.registry.interfaces.distroseries import DistroSeriesStatus |
380 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
381 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
382 | +from lp.soyuz.interfaces.archive import ArchivePurpose |
383 | +from lp.soyuz.interfaces.component import IComponentSet |
384 | +from lp.soyuz.interfaces.publishing import PackagePublishingStatus |
385 | from lp.code.interfaces.seriessourcepackagebranch import ( |
386 | IMakeOfficialBranchLinks) |
387 | from lp.testing import TestCaseWithFactory |
388 | @@ -166,6 +170,59 @@ |
389 | distribution_sourcepackage, |
390 | sourcepackage.distribution_sourcepackage) |
391 | |
392 | + def test_default_archive(self): |
393 | + # The default archive of a source package is the primary archive of |
394 | + # its distribution. |
395 | + sourcepackage = self.factory.makeSourcePackage() |
396 | + distribution = sourcepackage.distribution |
397 | + self.assertEqual( |
398 | + distribution.main_archive, sourcepackage.get_default_archive()) |
399 | + |
400 | + def test_default_archive_partner(self): |
401 | + # If the source package was most recently uploaded to a partner |
402 | + # component, then its default archive is the partner archive for the |
403 | + # distribution. |
404 | + sourcepackage = self.factory.makeSourcePackage() |
405 | + partner = getUtility(IComponentSet)['partner'] |
406 | + self.factory.makeSourcePackagePublishingHistory( |
407 | + sourcepackagename=sourcepackage.sourcepackagename, |
408 | + distroseries=sourcepackage.distroseries, |
409 | + component=partner, |
410 | + status=PackagePublishingStatus.PUBLISHED) |
411 | + distribution = sourcepackage.distribution |
412 | + expected_archive = self.factory.makeArchive( |
413 | + distribution=distribution, |
414 | + purpose=ArchivePurpose.PARTNER) |
415 | + self.assertEqual( |
416 | + expected_archive, sourcepackage.get_default_archive()) |
417 | + |
418 | + def test_default_archive_specified_component(self): |
419 | + # If the component is explicitly specified as partner, then we return |
420 | + # the partner archive. |
421 | + sourcepackage = self.factory.makeSourcePackage() |
422 | + partner = getUtility(IComponentSet)['partner'] |
423 | + distribution = sourcepackage.distribution |
424 | + expected_archive = self.factory.makeArchive( |
425 | + distribution=distribution, |
426 | + purpose=ArchivePurpose.PARTNER) |
427 | + self.assertEqual( |
428 | + expected_archive, |
429 | + sourcepackage.get_default_archive(component=partner)) |
430 | + |
431 | + def test_default_archive_partner_doesnt_exist(self): |
432 | + # If the default archive ought to be the partner archive (because the |
433 | + # last published upload was to a partner component) then |
434 | + # default_archive will raise an exception. |
435 | + sourcepackage = self.factory.makeSourcePackage() |
436 | + partner = getUtility(IComponentSet)['partner'] |
437 | + self.factory.makeSourcePackagePublishingHistory( |
438 | + sourcepackagename=sourcepackage.sourcepackagename, |
439 | + distroseries=sourcepackage.distroseries, |
440 | + component=partner, |
441 | + status=PackagePublishingStatus.PUBLISHED) |
442 | + self.assertRaises( |
443 | + NoPartnerArchive, sourcepackage.get_default_archive) |
444 | + |
445 | |
446 | class TestSourcePackageSecurity(TestCaseWithFactory): |
447 | """Tests for source package branch linking security.""" |
448 | |
449 | === modified file 'lib/lp/soyuz/interfaces/component.py' |
450 | --- lib/lp/soyuz/interfaces/component.py 2009-06-25 04:06:00 +0000 |
451 | +++ lib/lp/soyuz/interfaces/component.py 2009-11-19 15:18:17 +0000 |
452 | @@ -10,7 +10,7 @@ |
453 | __all__ = [ |
454 | 'IComponent', |
455 | 'IComponentSelection', |
456 | - 'IComponentSet' |
457 | + 'IComponentSet', |
458 | ] |
459 | |
460 | from zope.interface import Interface, Attribute |
461 | @@ -18,6 +18,7 @@ |
462 | |
463 | from canonical.launchpad import _ |
464 | |
465 | + |
466 | class IComponent(Interface): |
467 | """Represents the Component table. |
468 | |
469 | @@ -54,4 +55,3 @@ |
470 | |
471 | def new(name): |
472 | """Create a new component.""" |
473 | - |
474 | |
475 | === modified file 'lib/lp/testing/factory.py' |
476 | --- lib/lp/testing/factory.py 2009-11-14 22:44:10 +0000 |
477 | +++ lib/lp/testing/factory.py 2009-11-19 15:18:17 +0000 |
478 | @@ -34,17 +34,25 @@ |
479 | |
480 | from canonical.autodecorate import AutoDecorate |
481 | from canonical.config import config |
482 | +from canonical.database.constants import UTC_NOW |
483 | from lp.codehosting.codeimport.worker import CodeImportSourceDetails |
484 | from canonical.database.sqlbase import flush_database_updates |
485 | from lp.soyuz.adapters.packagelocation import PackageLocation |
486 | +from lp.soyuz.interfaces.publishing import PackagePublishingStatus |
487 | +from lp.soyuz.interfaces.section import ISectionSet |
488 | from canonical.launchpad.database.account import Account |
489 | from canonical.launchpad.database.emailaddress import EmailAddress |
490 | from canonical.launchpad.database.message import Message, MessageChunk |
491 | from lp.soyuz.model.processor import ProcessorFamilySet |
492 | +from lp.soyuz.model.publishing import ( |
493 | + SecureSourcePackagePublishingHistory, |
494 | + SourcePackagePublishingHistory, |
495 | + ) |
496 | from canonical.launchpad.interfaces import IMasterStore |
497 | from canonical.launchpad.interfaces.account import ( |
498 | AccountCreationRationale, AccountStatus, IAccountSet) |
499 | -from lp.soyuz.interfaces.archive import IArchiveSet, ArchivePurpose |
500 | +from lp.soyuz.interfaces.archive import ( |
501 | + default_name_by_purpose, IArchiveSet, ArchivePurpose) |
502 | from lp.blueprints.interfaces.sprint import ISprintSet |
503 | from lp.bugs.interfaces.bug import CreateBugParams, IBugSet |
504 | from lp.bugs.interfaces.bugtask import BugTaskStatus |
505 | @@ -101,7 +109,8 @@ |
506 | from lp.registry.interfaces.product import IProductSet, License |
507 | from lp.registry.interfaces.productseries import IProductSeries |
508 | from lp.registry.interfaces.project import IProjectSet |
509 | -from lp.registry.interfaces.sourcepackage import ISourcePackage |
510 | +from lp.registry.interfaces.sourcepackage import ( |
511 | + ISourcePackage, SourcePackageUrgency) |
512 | from lp.registry.interfaces.sourcepackagename import ( |
513 | ISourcePackageNameSet) |
514 | from lp.registry.interfaces.ssh import ISSHKeySet, SSHKeyType |
515 | @@ -123,6 +132,7 @@ |
516 | password that needs to happen on the master store and this is forced. |
517 | However, if we then read it back the default Store has to be used. |
518 | """ |
519 | + |
520 | def with_default_master_store(*args, **kw): |
521 | try: |
522 | store_selector = getUtility(IStoreSelector) |
523 | @@ -137,9 +147,9 @@ |
524 | return mergeFunctionMetadata(func, with_default_master_store) |
525 | |
526 | |
527 | -# We use this for default paramters where None has a specific meaning. For |
528 | -# example, makeBranch(product=None) means "make a junk branch". |
529 | -# None, because None means "junk branch". |
530 | +# We use this for default parameters where None has a specific meaning. For |
531 | +# example, makeBranch(product=None) means "make a junk branch". None, because |
532 | +# None means "junk branch". |
533 | _DEFAULT = object() |
534 | |
535 | |
536 | @@ -176,10 +186,9 @@ |
537 | :return: A hexadecimal string, with 'a'-'f' in lower case. |
538 | """ |
539 | hex_number = '%x' % self.getUniqueInteger() |
540 | - if digits is None: |
541 | - return hex_number |
542 | - else: |
543 | - return hex_number.zfill(digits) |
544 | + if digits is not None: |
545 | + hex_number = hex_number.zfill(digits) |
546 | + return hex_number |
547 | |
548 | def getUniqueString(self, prefix=None): |
549 | """Return a string unique to this factory instance. |
550 | @@ -263,9 +272,10 @@ |
551 | |
552 | def makeGPGKey(self, owner): |
553 | """Give 'owner' a crappy GPG key for the purposes of testing.""" |
554 | + key_id = self.getUniqueHexString(digits=8).upper() |
555 | return getUtility(IGPGKeySet).new( |
556 | owner.id, |
557 | - keyid=self.getUniqueHexString(digits=8).upper(), |
558 | + keyid=key_id, |
559 | fingerprint='A' * 40, |
560 | keysize=self.getUniqueInteger(), |
561 | algorithm=GPGKeyAlgorithm.R, |
562 | @@ -1467,7 +1477,7 @@ |
563 | return getUtility(IComponentSet).ensure(name) |
564 | |
565 | def makeArchive(self, distribution=None, owner=None, name=None, |
566 | - purpose = None): |
567 | + purpose=None): |
568 | """Create and return a new arbitrary archive. |
569 | |
570 | :param distribution: Supply IDistribution, defaults to a new one |
571 | @@ -1481,10 +1491,13 @@ |
572 | distribution = self.makeDistribution() |
573 | if owner is None: |
574 | owner = self.makePerson() |
575 | - if name is None: |
576 | - name = self.getUniqueString() |
577 | if purpose is None: |
578 | purpose = ArchivePurpose.PPA |
579 | + if name is None: |
580 | + try: |
581 | + name = default_name_by_purpose[purpose] |
582 | + except KeyError: |
583 | + name = self.getUniqueString() |
584 | |
585 | # Making a distribution makes an archive, and there can be only one |
586 | # per distribution. |
587 | @@ -1724,6 +1737,111 @@ |
588 | distroseries = self.makeDistroRelease() |
589 | return distroseries.getSourcePackage(sourcepackagename) |
590 | |
591 | + def getAnySourcePackageUrgency(self): |
592 | + return SourcePackageUrgency.MEDIUM |
593 | + |
594 | + def makeSourcePackagePublishingHistory(self, sourcepackagename=None, |
595 | + distroseries=None, maintainer=None, |
596 | + creator=None, component=None, |
597 | + section=None, urgency=None, |
598 | + version=None, archive=None, |
599 | + builddepends=None, |
600 | + builddependsindep=None, |
601 | + build_conflicts=None, |
602 | + build_conflicts_indep=None, |
603 | + architecturehintlist='all', |
604 | + dateremoved=None, |
605 | + date_uploaded=UTC_NOW, |
606 | + pocket=None, |
607 | + status=None, |
608 | + scheduleddeletiondate=None, |
609 | + dsc_standards_version='3.6.2', |
610 | + dsc_format='1.0', |
611 | + dsc_binaries='foo-bin', |
612 | + ): |
613 | + if sourcepackagename is None: |
614 | + sourcepackagename = self.makeSourcePackageName() |
615 | + spn = sourcepackagename |
616 | + |
617 | + if distroseries is None: |
618 | + distroseries = self.makeDistroRelease() |
619 | + |
620 | + if archive is None: |
621 | + archive = self.makeArchive( |
622 | + distribution=distroseries.distribution, |
623 | + purpose=ArchivePurpose.PRIMARY) |
624 | + |
625 | + if component is None: |
626 | + component = self.makeComponent() |
627 | + |
628 | + if pocket is None: |
629 | + pocket = self.getAnyPocket() |
630 | + |
631 | + if status is None: |
632 | + status = PackagePublishingStatus.PENDING |
633 | + |
634 | + if urgency is None: |
635 | + urgency = self.getAnySourcePackageUrgency() |
636 | + |
637 | + if section is None: |
638 | + section = self.getUniqueString('section') |
639 | + section = getUtility(ISectionSet).ensure(section) |
640 | + |
641 | + if maintainer is None: |
642 | + maintainer = self.makePerson() |
643 | + |
644 | + maintainer_email = '%s <%s>' % ( |
645 | + maintainer.displayname, |
646 | + maintainer.preferredemail.email) |
647 | + |
648 | + if creator is None: |
649 | + creator = self.makePerson() |
650 | + |
651 | + if version is None: |
652 | + version = self.getUniqueString('version') |
653 | + |
654 | + gpg_key = self.makeGPGKey(creator) |
655 | + |
656 | + spr = distroseries.createUploadedSourcePackageRelease( |
657 | + sourcepackagename=spn, |
658 | + maintainer=maintainer, |
659 | + creator=creator, |
660 | + component=component, |
661 | + section=section, |
662 | + urgency=urgency, |
663 | + version=version, |
664 | + builddepends=builddepends, |
665 | + builddependsindep=builddependsindep, |
666 | + build_conflicts=build_conflicts, |
667 | + build_conflicts_indep=build_conflicts_indep, |
668 | + architecturehintlist=architecturehintlist, |
669 | + changelog_entry=None, |
670 | + dsc=None, |
671 | + copyright=self.getUniqueString(), |
672 | + dscsigningkey=gpg_key, |
673 | + dsc_maintainer_rfc822=maintainer_email, |
674 | + dsc_standards_version=dsc_standards_version, |
675 | + dsc_format=dsc_format, |
676 | + dsc_binaries=dsc_binaries, |
677 | + archive=archive, dateuploaded=date_uploaded) |
678 | + |
679 | + sspph = SecureSourcePackagePublishingHistory( |
680 | + distroseries=distroseries, |
681 | + sourcepackagerelease=spr, |
682 | + component=spr.component, |
683 | + section=spr.section, |
684 | + status=status, |
685 | + datecreated=date_uploaded, |
686 | + dateremoved=dateremoved, |
687 | + scheduleddeletiondate=scheduleddeletiondate, |
688 | + pocket=pocket, |
689 | + embargo=False, |
690 | + archive=archive) |
691 | + |
692 | + # SPPH and SSPPH IDs are the same, since they are SPPH is a SQLVIEW |
693 | + # of SSPPH and other useful attributes. |
694 | + return SourcePackagePublishingHistory.get(sspph.id) |
695 | + |
696 | def makePackageset(self, name=None, description=None, owner=None, |
697 | packages=(), distroseries=None): |
698 | """Make an `IPackageset`.""" |
This branch extracts out a function that says whether you can upload a package or not, and makes sure that this function gets used to get branch edit permissions and also for uploads, thus reducing duplication.