Merge lp:~james-w/launchpad/archive-collection into lp:launchpad
- archive-collection
- Merge into devel
Status: | Work in progress |
---|---|
Proposed branch: | lp:~james-w/launchpad/archive-collection |
Merge into: | lp:launchpad |
Diff against target: |
611 lines (+421/-70) 7 files modified
lib/lp/services/database/collection.py (+3/-3) lib/lp/soyuz/configure.zcml (+13/-0) lib/lp/soyuz/interfaces/archivecollection.py (+70/-0) lib/lp/soyuz/model/archive.py (+17/-64) lib/lp/soyuz/model/archivecollection.py (+86/-0) lib/lp/soyuz/tests/test_archivecollection.py (+220/-0) lib/lp/testing/factory.py (+12/-3) |
To merge this branch: | bzr merge lp:~james-w/launchpad/archive-collection |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Launchpad code reviewers | Pending | ||
Review via email: mp+31499@code.launchpad.net |
Commit message
Description of the change
Hi,
Here's a branch to add an ArchiveCollection based
on the new generic Collection.
It's fairly standalone right now, though I ported a
few easy queries in ArchiveSet methods over to it.
Hopefully once it is available it will see more use.
Thanks,
James
Jonathan Lange (jml) wrote : | # |
James Westby (james-w) wrote : | # |
On Mon, 02 Aug 2010 15:58:40 -0000, Jonathan Lange <email address hidden> wrote:
> What's the reason for this change? Where are the corresponding tests?
Sorry, I forgot to mention it in the cover letter.
When you register a Collection as a utility it is instantiated by the
zcml.
However, as the store is queried in the constructor this can fail if
there is not a utility registered for the store yet.
How this manifested was that as soon as I added the utility I could no
longer run any tests as there was an exception during zcml processing.
I'm not sure how I would test this directly? A test that created a
Collection and tested that .store was None?
It does make me realise that I should probably add a test that the
utility returns something sensible?
> In IBranchCollection, we have a *args method for something similar. Do
> you think that would be nicer here?
The implementation is *purposes, so the interface needs correcting.
> Maybe it's my crappy email client, but the indentation on this looks messed up.
It's non-standard as it was copy-paste code, I will fix.
> > + # FIXME: Include private PPA's if user is an uploader
> > + return self.refine(
> > + Or(public_archive, Archive.
> >
>
> Are you going to fix this in this branch? Won't this be buggy if
> someone uses it?
This is copy-paste code, so we apparently have survived this far without
needing it.
I can look at fixing it though.
> These tests are good, thanks.
>
> One thing I've done with similar tests is have a setUp() that removes
> all of the data in question (e.g. removing all branches). That way you
> can do actual equality testing rather than testing to if if objects
> are members in tests.
>
> You don't have to though.
That's a good idea, I'll look for your code to learn from it.
Thanks,
James
Unmerged revisions
- 11270. By James Westby
-
Port a few more easy queries to ArchiveCollection.
- 11269. By James Westby
-
Move the first method over to the new code.
- 11268. By James Westby
-
Add the Interfaces and register a utility for all archives.
Registering a utility means that the zcml processor tries to instantiate
our class, which fails given that the IStoreSelector utility isn't
registered yet. Therefore we lazily get the store in the base Collection
if it isn't in the kwargs in the constructor. - 11267. By James Westby
-
Implement visibleByUser.
- 11266. By James Westby
-
Basic ArchiveCollection implementation.
Preview Diff
1 | === modified file 'lib/lp/services/database/collection.py' |
2 | --- lib/lp/services/database/collection.py 2010-07-16 14:10:18 +0000 |
3 | +++ lib/lp/services/database/collection.py 2010-08-02 00:41:06 +0000 |
4 | @@ -68,9 +68,6 @@ |
5 | base_tables = list(base.tables) |
6 | |
7 | self.store = kwargs.get('store') |
8 | - if self.store is None: |
9 | - self.store = getUtility(IStoreSelector).get( |
10 | - MAIN_STORE, DEFAULT_FLAVOR) |
11 | |
12 | self.tables = ( |
13 | starting_tables + base_tables + |
14 | @@ -118,6 +115,9 @@ |
15 | If no values are requested, this selects the type of object that |
16 | the Collection is a collection of. |
17 | """ |
18 | + if self.store is None: |
19 | + self.store = getUtility(IStoreSelector).get( |
20 | + MAIN_STORE, DEFAULT_FLAVOR) |
21 | if len(self.tables) == 0: |
22 | source = self.store |
23 | else: |
24 | |
25 | === modified file 'lib/lp/soyuz/configure.zcml' |
26 | --- lib/lp/soyuz/configure.zcml 2010-06-30 17:35:36 +0000 |
27 | +++ lib/lp/soyuz/configure.zcml 2010-08-02 00:41:06 +0000 |
28 | @@ -67,6 +67,19 @@ |
29 | interface="lp.soyuz.interfaces.archivearch.IArchiveArchSet"/> |
30 | </securedutility> |
31 | |
32 | + <!-- ArchiveCollection --> |
33 | + <securedutility |
34 | + class="lp.soyuz.model.archivecollection.ArchiveCollection" |
35 | + provides="lp.soyuz.interfaces.archivecollection.IAllArchives"> |
36 | + <allow |
37 | + interface="lp.soyuz.interfaces.archivecollection.IAllArchives" /> |
38 | + </securedutility> |
39 | + |
40 | + <class class="lp.soyuz.model.archivecollection.ArchiveCollection"> |
41 | + <allow |
42 | + interface="lp.soyuz.interfaces.archivecollection.IAllArchives" /> |
43 | + </class> |
44 | + |
45 | <!-- DistroSeriesPackageCache --> |
46 | <class |
47 | class="canonical.launchpad.database.DistroSeriesPackageCache"> |
48 | |
49 | === added file 'lib/lp/soyuz/interfaces/archivecollection.py' |
50 | --- lib/lp/soyuz/interfaces/archivecollection.py 1970-01-01 00:00:00 +0000 |
51 | +++ lib/lp/soyuz/interfaces/archivecollection.py 2010-08-02 00:41:06 +0000 |
52 | @@ -0,0 +1,70 @@ |
53 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
54 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
55 | + |
56 | +"""A collection of archives. |
57 | + |
58 | +See `IArchiveCollection` for more details. |
59 | +""" |
60 | + |
61 | +__metaclass__ = type |
62 | +__all__ = [ |
63 | + 'IAllArchives', |
64 | + 'IArchiveCollection', |
65 | + ] |
66 | + |
67 | +from zope.interface import Interface |
68 | + |
69 | + |
70 | +class IArchiveCollection(Interface): |
71 | + """A collection of archives. |
72 | + |
73 | + An `IArchiveCollection` is an immutable collection of archives. |
74 | + |
75 | + You can use the methods it provides to get a new collection with |
76 | + a filtered set of archives. |
77 | + |
78 | + Finally you can call `select` to get a ResultSet from the collection. |
79 | + """ |
80 | + |
81 | + def select(*args): |
82 | + """Return a ResultSet for this collection, with values set to args.""" |
83 | + |
84 | + def withPurpose(purpose): |
85 | + """Restrict the archives to only the given purpose.""" |
86 | + |
87 | + def withPurposes(purposes): |
88 | + """Restrict the archives to one of the given purposes.""" |
89 | + |
90 | + def withStatus(status): |
91 | + """Restrict the archives to only the given status.""" |
92 | + |
93 | + def withDistribution(distribution): |
94 | + """Restrict the archives to only the given distribution.""" |
95 | + |
96 | + def ownedBy(owner): |
97 | + """Restrict the archives to only those owned by the given person.""" |
98 | + |
99 | + def withName(name): |
100 | + """Restrict the archives to only those with the given name.""" |
101 | + |
102 | + def isEnabled(enabled=True): |
103 | + """Restrict the archives to only those enabled as passed.""" |
104 | + |
105 | + def isPrivate(private=True): |
106 | + """Restrict the archives to only those with the given privacy.""" |
107 | + |
108 | + def isRequireVirtualized(require_virtualized=True): |
109 | + """Restrict the archives to those requiring virtualization.""" |
110 | + |
111 | + def isCommercial(commercial=True): |
112 | + """Restrict the archives to commercial as given.""" |
113 | + |
114 | + def visibleByUser(user): |
115 | + """Restrict the archives to those visible by the given user. |
116 | + |
117 | + :param user: a Person, or None for public archives. |
118 | + """ |
119 | + |
120 | + |
121 | +class IAllArchives(IArchiveCollection): |
122 | + """An `IArchiveCollection` representing all archives in Launchpad.""" |
123 | |
124 | === modified file 'lib/lp/soyuz/model/archive.py' |
125 | --- lib/lp/soyuz/model/archive.py 2010-07-27 12:53:24 +0000 |
126 | +++ lib/lp/soyuz/model/archive.py 2010-08-02 00:41:06 +0000 |
127 | @@ -40,6 +40,7 @@ |
128 | from lp.soyuz.adapters.packagelocation import PackageLocation |
129 | from canonical.launchpad.components.tokens import ( |
130 | create_unique_token_for_table) |
131 | +from lp.soyuz.interfaces.archivecollection import IAllArchives |
132 | from lp.soyuz.model.archivedependency import ArchiveDependency |
133 | from lp.soyuz.model.archiveauthtoken import ArchiveAuthToken |
134 | from lp.soyuz.model.archivesubscriber import ArchiveSubscriber |
135 | @@ -1810,13 +1811,10 @@ |
136 | |
137 | def getPPAOwnedByPerson(self, person, name=None): |
138 | """See `IArchiveSet`.""" |
139 | - store = Store.of(person) |
140 | - clause = [ |
141 | - Archive.purpose == ArchivePurpose.PPA, |
142 | - Archive.owner == person] |
143 | + ppas = getUtility(IAllArchives).withPurpose(ArchivePurpose.PPA) |
144 | if name is not None: |
145 | - clause.append(Archive.name == name) |
146 | - result = store.find(Archive, *clause).order_by(Archive.id).first() |
147 | + ppas = ppas.withName(name) |
148 | + result = ppas.ownedBy(person).select().order_by(Archive.id).first() |
149 | if name is not None and result is None: |
150 | raise NoSuchPPA(name) |
151 | return result |
152 | @@ -1954,79 +1952,34 @@ |
153 | |
154 | def getPrivatePPAs(self): |
155 | """See `IArchiveSet`.""" |
156 | - store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) |
157 | - return store.find( |
158 | - Archive, |
159 | - Archive.private == True, |
160 | - Archive.purpose == ArchivePurpose.PPA) |
161 | + ppas = getUtility(IAllArchives).withPurpose(ArchivePurpose.PPA) |
162 | + return ppas.isPrivate().select() |
163 | |
164 | def getCommercialPPAs(self): |
165 | """See `IArchiveSet`.""" |
166 | - store = IStore(Archive) |
167 | - return store.find( |
168 | - Archive, |
169 | - Archive.commercial == True, |
170 | - Archive.purpose == ArchivePurpose.PPA) |
171 | + ppas = getUtility(IAllArchives).withPurpose(ArchivePurpose.PPA) |
172 | + return ppas.isCommercial().select() |
173 | |
174 | def getArchivesForDistribution(self, distribution, name=None, |
175 | purposes=None, user=None, |
176 | exclude_disabled=True): |
177 | """See `IArchiveSet`.""" |
178 | - extra_exprs = [] |
179 | + archives = getUtility(IAllArchives).withDistribution(distribution) |
180 | |
181 | - # If a single purpose is passed in, convert it into a tuple, |
182 | - # otherwise assume a list was passed in. |
183 | if purposes in ArchivePurpose: |
184 | - purposes = (purposes,) |
185 | - |
186 | - if purposes: |
187 | - extra_exprs.append(Archive.purpose.is_in(purposes)) |
188 | + archives = archives.withPurpose(purposes) |
189 | + elif purposes: |
190 | + archives = archives.withPurposes(*purposes) |
191 | |
192 | if name is not None: |
193 | - extra_exprs.append(Archive.name == name) |
194 | - |
195 | - public_archive = And(Archive.private == False, |
196 | - Archive._enabled == True) |
197 | - |
198 | - if user is not None: |
199 | - admins = getUtility(ILaunchpadCelebrities).admin |
200 | - if not user.inTeam(admins): |
201 | - # Enforce privacy-awareness for logged-in, non-admin users, |
202 | - # so that they can only see the private archives that they're |
203 | - # allowed to see. |
204 | - |
205 | - # Create a subselect to capture all the teams that are |
206 | - # owners of archives AND the user is a member of: |
207 | - user_teams_subselect = Select( |
208 | - TeamParticipation.teamID, |
209 | - where=And( |
210 | - TeamParticipation.personID == user.id, |
211 | - TeamParticipation.teamID == Archive.ownerID)) |
212 | - |
213 | - # Append the extra expression to capture either public |
214 | - # archives, or archives owned by the user, or archives |
215 | - # owned by a team of which the user is a member: |
216 | - # Note: 'Archive.ownerID == user.id' |
217 | - # is unnecessary below because there is a TeamParticipation |
218 | - # entry showing that each person is a member of the "team" |
219 | - # that consists of themselves. |
220 | - |
221 | - # FIXME: Include private PPA's if user is an uploader |
222 | - extra_exprs.append( |
223 | - Or(public_archive, |
224 | - Archive.ownerID.is_in(user_teams_subselect))) |
225 | - else: |
226 | - # Anonymous user; filter to include only public archives in |
227 | - # the results. |
228 | - extra_exprs.append(public_archive) |
229 | + archives = archives.withName(name) |
230 | + |
231 | + archives = archives.visibleByUser(user) |
232 | |
233 | if exclude_disabled: |
234 | - extra_exprs.append(Archive._enabled == True) |
235 | + archives = archives.isEnabled() |
236 | |
237 | - query = Store.of(distribution).find( |
238 | - Archive, |
239 | - Archive.distribution == distribution, |
240 | - *extra_exprs) |
241 | + query = archives.select() |
242 | |
243 | return query.order_by(Archive.name) |
244 | |
245 | |
246 | === added file 'lib/lp/soyuz/model/archivecollection.py' |
247 | --- lib/lp/soyuz/model/archivecollection.py 1970-01-01 00:00:00 +0000 |
248 | +++ lib/lp/soyuz/model/archivecollection.py 2010-08-02 00:41:06 +0000 |
249 | @@ -0,0 +1,86 @@ |
250 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
251 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
252 | + |
253 | +__metaclass__ = type |
254 | + |
255 | +from storm.expr import And, Or, Select |
256 | +from zope.component import getUtility |
257 | +from zope.interface import implements |
258 | + |
259 | +from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
260 | +from lp.registry.model.teammembership import TeamParticipation |
261 | +from lp.services.database.collection import Collection |
262 | +from lp.soyuz.interfaces.archivecollection import IArchiveCollection |
263 | +from lp.soyuz.model.archive import Archive |
264 | + |
265 | + |
266 | +class ArchiveCollection(Collection): |
267 | + |
268 | + implements(IArchiveCollection) |
269 | + |
270 | + starting_table = Archive |
271 | + |
272 | + def withPurpose(self, purpose): |
273 | + return self.refine(Archive.purpose == purpose) |
274 | + |
275 | + def withPurposes(self, *purposes): |
276 | + return self.refine(Archive.purpose.is_in(purposes)) |
277 | + |
278 | + def withStatus(self, status): |
279 | + return self.refine(Archive.status == status) |
280 | + |
281 | + def withDistribution(self, distribution): |
282 | + return self.refine(Archive.distribution == distribution) |
283 | + |
284 | + def ownedBy(self, owner): |
285 | + return self.refine(Archive.owner == owner) |
286 | + |
287 | + def withName(self, name): |
288 | + return self.refine(Archive.name == name) |
289 | + |
290 | + def isEnabled(self, enabled=True): |
291 | + return self.refine(Archive._enabled == enabled) |
292 | + |
293 | + def isPrivate(self, private=True): |
294 | + return self.refine(Archive.private == private) |
295 | + |
296 | + def isRequireVirtualized(self, require_virtualized=True): |
297 | + return self.refine(Archive.require_virtualized == require_virtualized) |
298 | + |
299 | + def isCommercial(self, commercial=True): |
300 | + return self.refine(Archive.commercial == commercial) |
301 | + |
302 | + def visibleByUser(self, user): |
303 | + public_archive = And(Archive.private == False, |
304 | + Archive._enabled == True) |
305 | + if user is None: |
306 | + # Anonymous user; filter to include only public archives in |
307 | + # the results. |
308 | + return self.refine(public_archive) |
309 | + celebs = getUtility(ILaunchpadCelebrities) |
310 | + if user.inTeam(celebs.admin): |
311 | + # Admins can see everything |
312 | + return self |
313 | + # Enforce privacy-awareness for non-admin users, |
314 | + # so that they can only see the private archives that they're |
315 | + # allowed to see. |
316 | + |
317 | + # Create a subselect to capture all the teams that are |
318 | + # owners of archives AND the user is a member of: |
319 | + user_teams_subselect = Select( |
320 | + TeamParticipation.teamID, |
321 | + where=And( |
322 | + TeamParticipation.personID == user.id, |
323 | + TeamParticipation.teamID == Archive.ownerID)) |
324 | + |
325 | + # Append the extra expression to capture either public |
326 | + # archives, or archives owned by the user, or archives |
327 | + # owned by a team of which the user is a member: |
328 | + # Note: 'Archive.ownerID == user.id' |
329 | + # is unnecessary below because there is a TeamParticipation |
330 | + # entry showing that each person is a member of the "team" |
331 | + # that consists of themselves. |
332 | + |
333 | + # FIXME: Include private PPA's if user is an uploader |
334 | + return self.refine( |
335 | + Or(public_archive, Archive.ownerID.is_in(user_teams_subselect))) |
336 | |
337 | === added file 'lib/lp/soyuz/tests/test_archivecollection.py' |
338 | --- lib/lp/soyuz/tests/test_archivecollection.py 1970-01-01 00:00:00 +0000 |
339 | +++ lib/lp/soyuz/tests/test_archivecollection.py 2010-08-02 00:41:06 +0000 |
340 | @@ -0,0 +1,220 @@ |
341 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
342 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
343 | + |
344 | +"""Tests for `ArchiveCollection`.""" |
345 | + |
346 | +__metaclass__ = type |
347 | + |
348 | +from zope.component import getUtility |
349 | +from zope.security.proxy import removeSecurityProxy |
350 | + |
351 | +from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
352 | +from canonical.testing import DatabaseFunctionalLayer |
353 | +from lp.soyuz.interfaces.archive import ArchivePurpose, ArchiveStatus |
354 | +from lp.soyuz.model.archivecollection import ArchiveCollection |
355 | +from lp.testing import TestCaseWithFactory |
356 | + |
357 | + |
358 | +class TestGetPublicationsInArchive(TestCaseWithFactory): |
359 | + |
360 | + layer = DatabaseFunctionalLayer |
361 | + |
362 | + def test_baseline(self): |
363 | + archive = self.factory.makeArchive() |
364 | + collection = ArchiveCollection() |
365 | + self.assertIn(archive, collection.select()) |
366 | + |
367 | + def test_purpose_found(self): |
368 | + archive = self.factory.makeArchive(purpose=ArchivePurpose.PPA) |
369 | + collection = ArchiveCollection() |
370 | + ppas = collection.withPurpose(ArchivePurpose.PPA) |
371 | + self.assertIn(archive, ppas.select()) |
372 | + |
373 | + def test_purpose_not_found(self): |
374 | + archive = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY) |
375 | + collection = ArchiveCollection() |
376 | + ppas = collection.withPurpose(ArchivePurpose.PPA) |
377 | + self.assertNotIn(archive, ppas.select()) |
378 | + |
379 | + def test_purposes(self): |
380 | + archive = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY) |
381 | + ppa = self.factory.makeArchive(purpose=ArchivePurpose.PPA) |
382 | + copy_archive = self.factory.makeArchive(purpose=ArchivePurpose.COPY) |
383 | + archives = ArchiveCollection().withPurposes( |
384 | + ArchivePurpose.PPA, ArchivePurpose.COPY) |
385 | + result = archives.select() |
386 | + self.assertIn(ppa, result) |
387 | + self.assertIn(copy_archive, result) |
388 | + self.assertNotIn(archive, result) |
389 | + |
390 | + def test_distribution_found(self): |
391 | + distribution = self.factory.makeDistribution() |
392 | + archive = self.factory.makeArchive(distribution=distribution) |
393 | + archives = ArchiveCollection().withDistribution(distribution) |
394 | + self.assertIn(archive, archives.select()) |
395 | + |
396 | + def test_distribution_notfound(self): |
397 | + distribution = self.factory.makeDistribution() |
398 | + archive = self.factory.makeArchive(distribution=distribution) |
399 | + other_distribution = self.factory.makeDistribution() |
400 | + archives = ArchiveCollection().withDistribution(other_distribution) |
401 | + self.assertNotIn(archive, archives.select()) |
402 | + |
403 | + def test_owner_found(self): |
404 | + owner = self.factory.makePerson() |
405 | + archive = self.factory.makeArchive(owner=owner) |
406 | + archives = ArchiveCollection().ownedBy(owner) |
407 | + self.assertIn(archive, archives.select()) |
408 | + |
409 | + def test_owner_not_found(self): |
410 | + owner = self.factory.makePerson() |
411 | + archive = self.factory.makeArchive(owner=owner) |
412 | + other_person = self.factory.makePerson() |
413 | + archives = ArchiveCollection().ownedBy(other_person) |
414 | + self.assertNotIn(archive, archives.select()) |
415 | + |
416 | + def test_name_found(self): |
417 | + name = "foo" |
418 | + archive = self.factory.makeArchive(name=name) |
419 | + archives = ArchiveCollection().withName(name) |
420 | + self.assertIn(archive, archives.select()) |
421 | + |
422 | + def test_name_not_found(self): |
423 | + name = "foo" |
424 | + archive = self.factory.makeArchive(name=name) |
425 | + archives = ArchiveCollection().withName("bar") |
426 | + self.assertNotIn(archive, archives.select()) |
427 | + |
428 | + def test_enabled_found(self): |
429 | + archive = self.factory.makeArchive(enabled=True) |
430 | + archives = ArchiveCollection().isEnabled() |
431 | + self.assertIn(archive, archives.select()) |
432 | + |
433 | + def test_enabled_not_found(self): |
434 | + archive = self.factory.makeArchive(enabled=True) |
435 | + archives = ArchiveCollection().isEnabled(False) |
436 | + self.assertNotIn(archive, archives.select()) |
437 | + |
438 | + def test_disabled_found(self): |
439 | + archive = self.factory.makeArchive(enabled=False) |
440 | + archives = ArchiveCollection().isEnabled(False) |
441 | + self.assertIn(archive, archives.select()) |
442 | + |
443 | + def test_disabled_not_found(self): |
444 | + archive = self.factory.makeArchive(enabled=False) |
445 | + archives = ArchiveCollection().isEnabled() |
446 | + self.assertNotIn(archive, archives.select()) |
447 | + |
448 | + def test_private_found(self): |
449 | + archive = self.factory.makeArchive(private=True) |
450 | + archives = ArchiveCollection().isPrivate() |
451 | + self.assertIn(archive, archives.select()) |
452 | + |
453 | + def test_private_not_found(self): |
454 | + archive = self.factory.makeArchive(private=True) |
455 | + archives = ArchiveCollection().isPrivate(False) |
456 | + self.assertNotIn(archive, archives.select()) |
457 | + |
458 | + def test_non_private_found(self): |
459 | + archive = self.factory.makeArchive(private=False) |
460 | + archives = ArchiveCollection().isPrivate(False) |
461 | + self.assertIn(archive, archives.select()) |
462 | + |
463 | + def test_non_private_not_found(self): |
464 | + archive = self.factory.makeArchive(private=False) |
465 | + archives = ArchiveCollection().isPrivate() |
466 | + self.assertNotIn(archive, archives.select()) |
467 | + |
468 | + def test_require_virtualized_found(self): |
469 | + archive = self.factory.makeArchive(virtualized=True) |
470 | + archives = ArchiveCollection().isRequireVirtualized() |
471 | + self.assertIn(archive, archives.select()) |
472 | + |
473 | + def test_require_virtualized_not_found(self): |
474 | + archive = self.factory.makeArchive(virtualized=True) |
475 | + archives = ArchiveCollection().isRequireVirtualized(False) |
476 | + self.assertNotIn(archive, archives.select()) |
477 | + |
478 | + def test_not_require_virtualized_found(self): |
479 | + archive = self.factory.makeArchive(virtualized=False) |
480 | + archives = ArchiveCollection().isRequireVirtualized(False) |
481 | + self.assertIn(archive, archives.select()) |
482 | + |
483 | + def test_not_require_virtualized_not_found(self): |
484 | + archive = self.factory.makeArchive(virtualized=False) |
485 | + archives = ArchiveCollection().isRequireVirtualized() |
486 | + self.assertNotIn(archive, archives.select()) |
487 | + |
488 | + def test_status_found(self): |
489 | + archive = self.factory.makeArchive(status=ArchiveStatus.ACTIVE) |
490 | + archives = ArchiveCollection().withStatus(ArchiveStatus.ACTIVE) |
491 | + self.assertIn(archive, archives.select()) |
492 | + |
493 | + def test_status_not_found(self): |
494 | + archive = self.factory.makeArchive(status=ArchiveStatus.ACTIVE) |
495 | + archives = ArchiveCollection().withStatus(ArchiveStatus.DELETING) |
496 | + self.assertNotIn(archive, archives.select()) |
497 | + |
498 | + def test_commercial_found(self): |
499 | + archive = self.factory.makeArchive(commercial=True) |
500 | + archives = ArchiveCollection().isCommercial() |
501 | + self.assertIn(archive, archives.select()) |
502 | + |
503 | + def test_commercial_not_found(self): |
504 | + archive = self.factory.makeArchive(commercial=True) |
505 | + archives = ArchiveCollection().isCommercial(False) |
506 | + self.assertNotIn(archive, archives.select()) |
507 | + |
508 | + def test_not_commercial_not_found(self): |
509 | + archive = self.factory.makeArchive(commercial=False) |
510 | + archives = ArchiveCollection().isCommercial() |
511 | + self.assertNotIn(archive, archives.select()) |
512 | + |
513 | + def test_not_commercial_found(self): |
514 | + archive = self.factory.makeArchive(commercial=False) |
515 | + archives = ArchiveCollection().isCommercial(False) |
516 | + self.assertIn(archive, archives.select()) |
517 | + |
518 | + def test_admins_can_see_all(self): |
519 | + archive = self.factory.makeArchive(private=True) |
520 | + all_archives = ArchiveCollection() |
521 | + admin = self.factory.makePerson() |
522 | + admin_team = removeSecurityProxy( |
523 | + getUtility(ILaunchpadCelebrities).admin) |
524 | + admin_team.addMember(admin, admin_team.teamowner) |
525 | + archives = ArchiveCollection().visibleByUser(admin) |
526 | + self.assertEqual( |
527 | + sorted(all_archives.select()), sorted(archives.select())) |
528 | + |
529 | + def test_plain_user_cant_see_private(self): |
530 | + archive = self.factory.makeArchive(private=True) |
531 | + person = self.factory.makePerson() |
532 | + archives = ArchiveCollection().visibleByUser(person) |
533 | + self.assertNotIn(archive, archives.select()) |
534 | + |
535 | + def test_plain_user_cant_see_disabled(self): |
536 | + archive = self.factory.makeArchive(enabled=False, private=False) |
537 | + person = self.factory.makePerson() |
538 | + archives = ArchiveCollection().visibleByUser(person) |
539 | + self.assertNotIn(archive, archives.select()) |
540 | + |
541 | + def test_plain_user_can_see_public_enabled(self): |
542 | + archive = self.factory.makeArchive(enabled=True, private=False) |
543 | + person = self.factory.makePerson() |
544 | + archives = ArchiveCollection().visibleByUser(person) |
545 | + self.assertIn(archive, archives.select()) |
546 | + |
547 | + def test_user_can_see_private_disabled_archives_they_own(self): |
548 | + person = self.factory.makePerson() |
549 | + archive = self.factory.makeArchive( |
550 | + enabled=False, private=True, owner=person) |
551 | + archives = ArchiveCollection().visibleByUser(person) |
552 | + self.assertIn(archive, archives.select()) |
553 | + |
554 | + def test_user_can_see_private_disabled_archives_for_their_teams(self): |
555 | + person = self.factory.makePerson() |
556 | + team = self.factory.makeTeam(members=[person]) |
557 | + archive = self.factory.makeArchive( |
558 | + enabled=False, private=True, owner=team) |
559 | + archives = ArchiveCollection().visibleByUser(person) |
560 | + self.assertIn(archive, archives.select()) |
561 | |
562 | === modified file 'lib/lp/testing/factory.py' |
563 | --- lib/lp/testing/factory.py 2010-08-01 04:34:26 +0000 |
564 | +++ lib/lp/testing/factory.py 2010-08-02 00:41:06 +0000 |
565 | @@ -144,7 +144,7 @@ |
566 | |
567 | from lp.soyuz.adapters.packagelocation import PackageLocation |
568 | from lp.soyuz.interfaces.archive import ( |
569 | - default_name_by_purpose, IArchiveSet, ArchivePurpose) |
570 | + default_name_by_purpose, IArchiveSet, ArchivePurpose, ArchiveStatus) |
571 | from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet |
572 | from lp.soyuz.interfaces.binarypackagerelease import BinaryPackageFormat |
573 | from lp.soyuz.interfaces.component import IComponentSet |
574 | @@ -1731,7 +1731,8 @@ |
575 | |
576 | def makeArchive(self, distribution=None, owner=None, name=None, |
577 | purpose=None, enabled=True, private=False, |
578 | - virtualized=True, description=None, displayname=None): |
579 | + virtualized=True, description=None, displayname=None, |
580 | + commercial=False, status=None): |
581 | """Create and return a new arbitrary archive. |
582 | |
583 | :param distribution: Supply IDistribution, defaults to a new one |
584 | @@ -1744,6 +1745,8 @@ |
585 | :param private: Whether the archive is created private. |
586 | :param virtualized: Whether the archive is virtualized. |
587 | :param description: A description of the archive. |
588 | + :param commercial: Whether the archive is commercial. |
589 | + :param status: The ArchiveStatus of the archive. |
590 | """ |
591 | if purpose is None: |
592 | purpose = ArchivePurpose.PPA |
593 | @@ -1772,11 +1775,17 @@ |
594 | enabled=enabled, require_virtualized=virtualized, |
595 | description=description) |
596 | |
597 | - if private: |
598 | + if private or commercial: |
599 | naked_archive = removeSecurityProxy(archive) |
600 | naked_archive.private = True |
601 | naked_archive.buildd_secret = "sekrit" |
602 | |
603 | + if commercial: |
604 | + removeSecurityProxy(archive).commercial = True |
605 | + |
606 | + if status is not None: |
607 | + removeSecurityProxy(archive).status = status |
608 | + |
609 | return archive |
610 | |
611 | def makeBuilder(self, processor=None, url=None, name=None, title=None, |
On Mon, Aug 2, 2010 at 1:41 AM, James Westby <email address hidden> wrote: reviewers)
> James Westby has proposed merging lp:~james-w/launchpad/archive-collection into lp:launchpad/devel.
>
> Requested reviews:
> Launchpad code reviewers (launchpad-
>
>
> Hi,
>
> Here's a branch to add an ArchiveCollection based
> on the new generic Collection.
>
> It's fairly standalone right now, though I ported a
> few easy queries in ArchiveSet methods over to it.
> Hopefully once it is available it will see more use.
>
>
Thanks for doing this. It's important to have new code actually be used.
> === modified file 'lib/lp/ services/ database/ collection. py' services/ database/ collection. py 2010-07-16 14:10:18 +0000 services/ database/ collection. py 2010-08-02 00:41:06 +0000 IStoreSelector) .get( IStoreSelector) .get(
> --- lib/lp/
> +++ lib/lp/
> @@ -68,9 +68,6 @@
> base_tables = list(base.tables)
>
> self.store = kwargs.get('store')
> - if self.store is None:
> - self.store = getUtility(
> - MAIN_STORE, DEFAULT_FLAVOR)
>
> self.tables = (
> starting_tables + base_tables +
> @@ -118,6 +115,9 @@
> If no values are requested, this selects the type of object that
> the Collection is a collection of.
> """
> + if self.store is None:
> + self.store = getUtility(
> + MAIN_STORE, DEFAULT_FLAVOR)
> if len(self.tables) == 0:
> source = self.store
> else:
>
What's the reason for this change? Where are the corresponding tests?
> === added file 'lib/lp/ soyuz/interface s/archivecollec tion.py' soyuz/interface s/archivecollec tion.py 1970-01-01 00:00:00 +0000 soyuz/interface s/archivecollec tion.py 2010-08-02 00:41:06 +0000 purposes) :
> --- lib/lp/
> +++ lib/lp/
> @@ -0,0 +1,70 @@
> +# Copyright 2010 Canonical Ltd. This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).
> +
> +"""A collection of archives.
> +
...
> +
> + def withPurposes(
> + """Restrict the archives to one of the given purposes."""
In IBranchCollection, we have a *args method for something similar. Do
you think that would be nicer here?
> === added file 'lib/lp/ soyuz/model/ archivecollecti on.py' soyuz/model/ archivecollecti on.py 1970-01-01 00:00:00 +0000 soyuz/model/ archivecollecti on.py 2010-08-02 00:41:06 +0000
> --- lib/lp/
> +++ lib/lp/
> @@ -0,0 +1,86 @@
> +# Copyright 2010 Canonical Ltd. This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).
> +
> +__metaclass__ = type
> +
...
> +
> + def visibleByUser(self, user):
> + public_archive = And(Archive.private == False,
> + Archive._enabled == True)
Maybe it's my crappy email client, but the indentation on this looks messed up.
> + if user is None: public_ archive) ILaunchpadCeleb rities) celebs. admin):
> + # Anonymous user; filter to include only public archives in
> + # the results.
> + return self.refine(
> + celebs = getUtility(
> + if user.inTeam(
> + # Admi...