Merge lp:~cjwatson/launchpad/das-filter-model into lp:launchpad
- das-filter-model
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | 19058 |
Proposed branch: | lp:~cjwatson/launchpad/das-filter-model |
Merge into: | lp:launchpad |
Prerequisite: | lp:~cjwatson/launchpad/packageset-is-source-included |
Diff against target: |
789 lines (+588/-2) 13 files modified
database/schema/security.cfg (+3/-0) lib/lp/_schema_circular_imports.py (+3/-1) lib/lp/registry/scripts/closeaccount.py (+1/-0) lib/lp/security.py (+19/-0) lib/lp/soyuz/configure.zcml (+20/-0) lib/lp/soyuz/doc/distroarchseries.txt (+34/-0) lib/lp/soyuz/enums.py (+17/-1) lib/lp/soyuz/interfaces/distroarchseries.py (+59/-0) lib/lp/soyuz/interfaces/distroarchseriesfilter.py (+127/-0) lib/lp/soyuz/model/distroarchseries.py (+30/-0) lib/lp/soyuz/model/distroarchseriesfilter.py (+124/-0) lib/lp/soyuz/tests/test_distroarchseriesfilter.py (+128/-0) lib/lp/testing/factory.py (+23/-0) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/das-filter-model |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+372261@code.launchpad.net |
Commit message
Add basic model for per-DAS filtering of build creation.
Description of the change
This isn't yet used or exposed on the webservice; that will come in future MPs.
The corresponding DB patch is in https:/
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
review:
Approve
(code)
Revision history for this message
Colin Watson (cjwatson) : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'database/schema/security.cfg' | |||
2 | --- database/schema/security.cfg 2019-08-09 12:04:04 +0000 | |||
3 | +++ database/schema/security.cfg 2019-09-10 11:16:47 +0000 | |||
4 | @@ -176,6 +176,7 @@ | |||
5 | 176 | public.distributionmirror = SELECT, INSERT, UPDATE, DELETE | 176 | public.distributionmirror = SELECT, INSERT, UPDATE, DELETE |
6 | 177 | public.distributionsourcepackage = SELECT, INSERT, UPDATE, DELETE | 177 | public.distributionsourcepackage = SELECT, INSERT, UPDATE, DELETE |
7 | 178 | public.distributionsourcepackagecache = SELECT, INSERT | 178 | public.distributionsourcepackagecache = SELECT, INSERT |
8 | 179 | public.distroarchseriesfilter = SELECT, INSERT, UPDATE, DELETE | ||
9 | 179 | public.distroseriesdifference = SELECT, INSERT, UPDATE | 180 | public.distroseriesdifference = SELECT, INSERT, UPDATE |
10 | 180 | public.distroseriesdifferencemessage = SELECT, INSERT, UPDATE | 181 | public.distroseriesdifferencemessage = SELECT, INSERT, UPDATE |
11 | 181 | public.distroserieslanguage = SELECT, INSERT, UPDATE | 182 | public.distroserieslanguage = SELECT, INSERT, UPDATE |
12 | @@ -2221,6 +2222,7 @@ | |||
13 | 2221 | public.distributionsourcepackage = SELECT, INSERT, UPDATE, DELETE | 2222 | public.distributionsourcepackage = SELECT, INSERT, UPDATE, DELETE |
14 | 2222 | public.distributionmirror = SELECT, UPDATE | 2223 | public.distributionmirror = SELECT, UPDATE |
15 | 2223 | public.distroarchseries = SELECT, UPDATE | 2224 | public.distroarchseries = SELECT, UPDATE |
16 | 2225 | public.distroarchseriesfilter = SELECT, UPDATE | ||
17 | 2224 | public.distroseries = SELECT, UPDATE | 2226 | public.distroseries = SELECT, UPDATE |
18 | 2225 | public.emailaddress = SELECT, UPDATE, DELETE | 2227 | public.emailaddress = SELECT, UPDATE, DELETE |
19 | 2226 | public.faq = SELECT, UPDATE | 2228 | public.faq = SELECT, UPDATE |
20 | @@ -2356,6 +2358,7 @@ | |||
21 | 2356 | public.commercialsubscription = SELECT, UPDATE | 2358 | public.commercialsubscription = SELECT, UPDATE |
22 | 2357 | public.diff = SELECT, DELETE | 2359 | public.diff = SELECT, DELETE |
23 | 2358 | public.distributionsourcepackagecache = SELECT, INSERT | 2360 | public.distributionsourcepackagecache = SELECT, INSERT |
24 | 2361 | public.distroarchseriesfilter = SELECT | ||
25 | 2359 | public.distroseries = SELECT, UPDATE | 2362 | public.distroseries = SELECT, UPDATE |
26 | 2360 | public.emailaddress = SELECT, UPDATE, DELETE | 2363 | public.emailaddress = SELECT, UPDATE, DELETE |
27 | 2361 | public.garbojobstate = SELECT, INSERT, UPDATE, DELETE | 2364 | public.garbojobstate = SELECT, INSERT, UPDATE, DELETE |
28 | 2362 | 2365 | ||
29 | === modified file 'lib/lp/_schema_circular_imports.py' | |||
30 | --- lib/lp/_schema_circular_imports.py 2018-09-25 16:41:21 +0000 | |||
31 | +++ lib/lp/_schema_circular_imports.py 2019-09-10 11:16:47 +0000 | |||
32 | @@ -1,4 +1,4 @@ | |||
34 | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2019 Canonical Ltd. This software is licensed under the |
35 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
36 | 3 | 3 | ||
37 | 4 | """Update the interface schema values due to circular imports. | 4 | """Update the interface schema values due to circular imports. |
38 | @@ -496,6 +496,8 @@ | |||
39 | 496 | patch_reference_property(IDistroArchSeries, 'main_archive', IArchive) | 496 | patch_reference_property(IDistroArchSeries, 'main_archive', IArchive) |
40 | 497 | patch_plain_parameter_type( | 497 | patch_plain_parameter_type( |
41 | 498 | IDistroArchSeries, 'setChrootFromBuild', 'livefsbuild', ILiveFSBuild) | 498 | IDistroArchSeries, 'setChrootFromBuild', 'livefsbuild', ILiveFSBuild) |
42 | 499 | patch_plain_parameter_type( | ||
43 | 500 | IDistroArchSeries, 'setSourceFilter', 'packageset', IPackageset) | ||
44 | 499 | 501 | ||
45 | 500 | # IGitRef | 502 | # IGitRef |
46 | 501 | patch_reference_property(IGitRef, 'repository', IGitRepository) | 503 | patch_reference_property(IGitRef, 'repository', IGitRepository) |
47 | 502 | 504 | ||
48 | === modified file 'lib/lp/registry/scripts/closeaccount.py' | |||
49 | --- lib/lp/registry/scripts/closeaccount.py 2019-08-09 12:04:04 +0000 | |||
50 | +++ lib/lp/registry/scripts/closeaccount.py 2019-09-10 11:16:47 +0000 | |||
51 | @@ -114,6 +114,7 @@ | |||
52 | 114 | ('codeimport', 'owner'), | 114 | ('codeimport', 'owner'), |
53 | 115 | ('codeimport', 'registrant'), | 115 | ('codeimport', 'registrant'), |
54 | 116 | ('codeimportevent', 'person'), | 116 | ('codeimportevent', 'person'), |
55 | 117 | ('distroarchseriesfilter', 'creator'), | ||
56 | 117 | ('faq', 'last_updated_by'), | 118 | ('faq', 'last_updated_by'), |
57 | 118 | ('featureflagchangelogentry', 'person'), | 119 | ('featureflagchangelogentry', 'person'), |
58 | 119 | ('gitactivity', 'changee'), | 120 | ('gitactivity', 'changee'), |
59 | 120 | 121 | ||
60 | === modified file 'lib/lp/security.py' | |||
61 | --- lib/lp/security.py 2019-07-01 12:48:37 +0000 | |||
62 | +++ lib/lp/security.py 2019-09-10 11:16:47 +0000 | |||
63 | @@ -229,6 +229,7 @@ | |||
64 | 229 | IBinaryPackageReleaseDownloadCount, | 229 | IBinaryPackageReleaseDownloadCount, |
65 | 230 | ) | 230 | ) |
66 | 231 | from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries | 231 | from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries |
67 | 232 | from lp.soyuz.interfaces.distroarchseriesfilter import IDistroArchSeriesFilter | ||
68 | 232 | from lp.soyuz.interfaces.livefs import ILiveFS | 233 | from lp.soyuz.interfaces.livefs import ILiveFS |
69 | 233 | from lp.soyuz.interfaces.livefsbuild import ILiveFSBuild | 234 | from lp.soyuz.interfaces.livefsbuild import ILiveFSBuild |
70 | 234 | from lp.soyuz.interfaces.packagecopyjob import IPlainPackageCopyJob | 235 | from lp.soyuz.interfaces.packagecopyjob import IPlainPackageCopyJob |
71 | @@ -1468,6 +1469,24 @@ | |||
72 | 1468 | or user.in_admin) | 1469 | or user.in_admin) |
73 | 1469 | 1470 | ||
74 | 1470 | 1471 | ||
75 | 1472 | class ViewDistroArchSeriesFilter(DelegatedAuthorization): | ||
76 | 1473 | permission = 'launchpad.View' | ||
77 | 1474 | usedfor = IDistroArchSeriesFilter | ||
78 | 1475 | |||
79 | 1476 | def __init__(self, obj): | ||
80 | 1477 | super(ViewDistroArchSeriesFilter, self).__init__( | ||
81 | 1478 | obj, obj.distroarchseries, 'launchpad.View') | ||
82 | 1479 | |||
83 | 1480 | |||
84 | 1481 | class EditDistroArchSeriesFilter(DelegatedAuthorization): | ||
85 | 1482 | permission = 'launchpad.Edit' | ||
86 | 1483 | usedfor = IDistroArchSeriesFilter | ||
87 | 1484 | |||
88 | 1485 | def __init__(self, obj): | ||
89 | 1486 | super(EditDistroArchSeriesFilter, self).__init__( | ||
90 | 1487 | obj, obj.distroarchseries, 'launchpad.Moderate') | ||
91 | 1488 | |||
92 | 1489 | |||
93 | 1471 | class ViewAnnouncement(AuthorizationBase): | 1490 | class ViewAnnouncement(AuthorizationBase): |
94 | 1472 | permission = 'launchpad.View' | 1491 | permission = 'launchpad.View' |
95 | 1473 | usedfor = IAnnouncement | 1492 | usedfor = IAnnouncement |
96 | 1474 | 1493 | ||
97 | === modified file 'lib/lp/soyuz/configure.zcml' | |||
98 | --- lib/lp/soyuz/configure.zcml 2019-03-12 19:12:29 +0000 | |||
99 | +++ lib/lp/soyuz/configure.zcml 2019-09-10 11:16:47 +0000 | |||
100 | @@ -616,6 +616,26 @@ | |||
101 | 616 | set_schema="lp.soyuz.interfaces.distroarchseries.IPocketChroot"/> | 616 | set_schema="lp.soyuz.interfaces.distroarchseries.IPocketChroot"/> |
102 | 617 | </class> | 617 | </class> |
103 | 618 | 618 | ||
104 | 619 | <!-- DistroArchSeriesFilter --> | ||
105 | 620 | <class | ||
106 | 621 | class="lp.soyuz.model.distroarchseriesfilter.DistroArchSeriesFilter"> | ||
107 | 622 | <allow | ||
108 | 623 | interface="lp.soyuz.interfaces.distroarchseriesfilter.IDistroArchSeriesFilterView" /> | ||
109 | 624 | <require | ||
110 | 625 | permission="launchpad.Edit" | ||
111 | 626 | interface="lp.soyuz.interfaces.distroarchseriesfilter.IDistroArchSeriesFilterEdit" /> | ||
112 | 627 | </class> | ||
113 | 628 | <securedutility | ||
114 | 629 | class="lp.soyuz.model.distroarchseriesfilter.DistroArchSeriesFilterSet" | ||
115 | 630 | provides="lp.soyuz.interfaces.distroarchseriesfilter.IDistroArchSeriesFilterSet"> | ||
116 | 631 | <allow | ||
117 | 632 | interface="lp.soyuz.interfaces.distroarchseriesfilter.IDistroArchSeriesFilterSet" /> | ||
118 | 633 | </securedutility> | ||
119 | 634 | <subscriber | ||
120 | 635 | for="lp.soyuz.interfaces.distroarchseriesfilter.IDistroArchSeriesFilter | ||
121 | 636 | lazr.lifecycle.interfaces.IObjectModifiedEvent" | ||
122 | 637 | handler="lp.soyuz.model.distroarchseriesfilter.distro_arch_series_filter_modified" /> | ||
123 | 638 | |||
124 | 619 | <!-- Component --> | 639 | <!-- Component --> |
125 | 620 | 640 | ||
126 | 621 | <class | 641 | <class |
127 | 622 | 642 | ||
128 | === modified file 'lib/lp/soyuz/doc/distroarchseries.txt' | |||
129 | --- lib/lp/soyuz/doc/distroarchseries.txt 2019-02-13 14:39:18 +0000 | |||
130 | +++ lib/lp/soyuz/doc/distroarchseries.txt 2019-09-10 11:16:47 +0000 | |||
131 | @@ -255,3 +255,37 @@ | |||
132 | 255 | >>> print_architectures(hoary.buildable_architectures) | 255 | >>> print_architectures(hoary.buildable_architectures) |
133 | 256 | The Hoary Hedgehog Release for hppa (hppa) (ppa) | 256 | The Hoary Hedgehog Release for hppa (hppa) (ppa) |
134 | 257 | The Hoary Hedgehog Release for i386 (386) (official, ppa) | 257 | The Hoary Hedgehog Release for i386 (386) (official, ppa) |
135 | 258 | |||
136 | 259 | An architecture can have an associated filter that controls which packages | ||
137 | 260 | are included in it. It has an `isSourceIncluded` method that allows | ||
138 | 261 | querying inclusion by `SourcePackageName`. | ||
139 | 262 | |||
140 | 263 | >>> from lp.soyuz.enums import DistroArchSeriesFilterSense | ||
141 | 264 | |||
142 | 265 | >>> spns = [factory.makeSourcePackageName() for _ in range(3)] | ||
143 | 266 | >>> hoary.getDistroArchSeries('i386').isSourceIncluded(spns[0]) | ||
144 | 267 | True | ||
145 | 268 | |||
146 | 269 | >>> packageset_include = factory.makePackageset(distroseries=hoary) | ||
147 | 270 | >>> packageset_include.add(spns[:2]) | ||
148 | 271 | >>> hoary.getDistroArchSeries('i386').setSourceFilter( | ||
149 | 272 | ... packageset_include, DistroArchSeriesFilterSense.INCLUDE, | ||
150 | 273 | ... factory.makePerson()) | ||
151 | 274 | >>> packageset_exclude = factory.makePackageset(distroseries=hoary) | ||
152 | 275 | >>> packageset_exclude.add(spns[1:]) | ||
153 | 276 | >>> hoary.getDistroArchSeries('hppa').setSourceFilter( | ||
154 | 277 | ... packageset_exclude, DistroArchSeriesFilterSense.EXCLUDE, | ||
155 | 278 | ... factory.makePerson()) | ||
156 | 279 | |||
157 | 280 | >>> hoary.getDistroArchSeries('i386').isSourceIncluded(spns[0]) | ||
158 | 281 | True | ||
159 | 282 | >>> hoary.getDistroArchSeries('i386').isSourceIncluded(spns[1]) | ||
160 | 283 | True | ||
161 | 284 | >>> hoary.getDistroArchSeries('i386').isSourceIncluded(spns[2]) | ||
162 | 285 | False | ||
163 | 286 | >>> hoary.getDistroArchSeries('hppa').isSourceIncluded(spns[0]) | ||
164 | 287 | True | ||
165 | 288 | >>> hoary.getDistroArchSeries('hppa').isSourceIncluded(spns[1]) | ||
166 | 289 | False | ||
167 | 290 | >>> hoary.getDistroArchSeries('hppa').isSourceIncluded(spns[2]) | ||
168 | 291 | False | ||
169 | 258 | 292 | ||
170 | === modified file 'lib/lp/soyuz/enums.py' | |||
171 | --- lib/lp/soyuz/enums.py 2017-03-29 09:28:09 +0000 | |||
172 | +++ lib/lp/soyuz/enums.py 2019-09-10 11:16:47 +0000 | |||
173 | @@ -1,4 +1,4 @@ | |||
175 | 1 | # Copyright 2010-2017 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2010-2019 Canonical Ltd. This software is licensed under the |
176 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
177 | 3 | 3 | ||
178 | 4 | """Enumerations used in the lp/soyuz modules.""" | 4 | """Enumerations used in the lp/soyuz modules.""" |
179 | @@ -13,6 +13,7 @@ | |||
180 | 13 | 'archive_suffixes', | 13 | 'archive_suffixes', |
181 | 14 | 'BinaryPackageFileType', | 14 | 'BinaryPackageFileType', |
182 | 15 | 'BinaryPackageFormat', | 15 | 'BinaryPackageFormat', |
183 | 16 | 'DistroArchSeriesFilterSense', | ||
184 | 16 | 'IndexCompressionType', | 17 | 'IndexCompressionType', |
185 | 17 | 'PackageCopyPolicy', | 18 | 'PackageCopyPolicy', |
186 | 18 | 'PackageCopyStatus', | 19 | 'PackageCopyStatus', |
187 | @@ -597,3 +598,18 @@ | |||
188 | 597 | GZIP = DBItem(1, "gzip") | 598 | GZIP = DBItem(1, "gzip") |
189 | 598 | BZIP2 = DBItem(2, "bzip2") | 599 | BZIP2 = DBItem(2, "bzip2") |
190 | 599 | XZ = DBItem(3, "xz") | 600 | XZ = DBItem(3, "xz") |
191 | 601 | |||
192 | 602 | |||
193 | 603 | class DistroArchSeriesFilterSense(DBEnumeratedType): | ||
194 | 604 | |||
195 | 605 | INCLUDE = DBItem(1, """ | ||
196 | 606 | Include | ||
197 | 607 | |||
198 | 608 | Packages in this package set are included in the distro arch series. | ||
199 | 609 | """) | ||
200 | 610 | |||
201 | 611 | EXCLUDE = DBItem(2, """ | ||
202 | 612 | Exclude | ||
203 | 613 | |||
204 | 614 | Packages in this package set are excluded from the distro arch series. | ||
205 | 615 | """) | ||
206 | 600 | 616 | ||
207 | === modified file 'lib/lp/soyuz/interfaces/distroarchseries.py' | |||
208 | --- lib/lp/soyuz/interfaces/distroarchseries.py 2019-07-30 11:38:18 +0000 | |||
209 | +++ lib/lp/soyuz/interfaces/distroarchseries.py 2019-09-10 11:16:47 +0000 | |||
210 | @@ -7,6 +7,7 @@ | |||
211 | 7 | 7 | ||
212 | 8 | __all__ = [ | 8 | __all__ = [ |
213 | 9 | 'ChrootNotPublic', | 9 | 'ChrootNotPublic', |
214 | 10 | 'FilterSeriesMismatch', | ||
215 | 10 | 'IDistroArchSeries', | 11 | 'IDistroArchSeries', |
216 | 11 | 'InvalidChrootUploaded', | 12 | 'InvalidChrootUploaded', |
217 | 12 | 'IPocketChroot', | 13 | 'IPocketChroot', |
218 | @@ -65,6 +66,19 @@ | |||
219 | 65 | "Cannot set chroot from a private build.") | 66 | "Cannot set chroot from a private build.") |
220 | 66 | 67 | ||
221 | 67 | 68 | ||
222 | 69 | @error_status(httplib.BAD_REQUEST) | ||
223 | 70 | class FilterSeriesMismatch(Exception): | ||
224 | 71 | """DAS and packageset distroseries do not match when setting a filter.""" | ||
225 | 72 | |||
226 | 73 | def __init__(self, distroarchseries, packageset): | ||
227 | 74 | super(Exception, self).__init__( | ||
228 | 75 | "The requested package set is for %s and cannot be set as a " | ||
229 | 76 | "filter for %s %s." % ( | ||
230 | 77 | packageset.distroseries.fullseriesname, | ||
231 | 78 | distroarchseries.distroseries.fullseriesname, | ||
232 | 79 | distroarchseries.architecturetag)) | ||
233 | 80 | |||
234 | 81 | |||
235 | 68 | class IDistroArchSeriesPublic(IHasBuildRecords, IHasOwner): | 82 | class IDistroArchSeriesPublic(IHasBuildRecords, IHasOwner): |
236 | 69 | """Public attributes for a DistroArchSeries.""" | 83 | """Public attributes for a DistroArchSeries.""" |
237 | 70 | 84 | ||
238 | @@ -216,6 +230,21 @@ | |||
239 | 216 | this distro arch series. | 230 | this distro arch series. |
240 | 217 | """ | 231 | """ |
241 | 218 | 232 | ||
242 | 233 | def getSourceFilter(): | ||
243 | 234 | """Get the filter for packages to build for this architecture, if any. | ||
244 | 235 | |||
245 | 236 | Packages are normally built for all available architectures, subject | ||
246 | 237 | to any constraints in their `Architecture` field. If a filter is | ||
247 | 238 | set, then it applies the additional constraint that packages not | ||
248 | 239 | included by the filter will not be built for this architecture. | ||
249 | 240 | """ | ||
250 | 241 | |||
251 | 242 | def isSourceIncluded(sourcepackagename): | ||
252 | 243 | """Is this source package included in this distro arch series? | ||
253 | 244 | |||
254 | 245 | :param sourcepackagename: An `ISourcePackageName` to check. | ||
255 | 246 | """ | ||
256 | 247 | |||
257 | 219 | 248 | ||
258 | 220 | class IDistroArchSeriesModerate(Interface): | 249 | class IDistroArchSeriesModerate(Interface): |
259 | 221 | 250 | ||
260 | @@ -263,6 +292,36 @@ | |||
261 | 263 | tarball". | 292 | tarball". |
262 | 264 | """ | 293 | """ |
263 | 265 | 294 | ||
264 | 295 | def setSourceFilter(packageset, sense, creator): | ||
265 | 296 | """Set a filter for packages to build for this architecture. | ||
266 | 297 | |||
267 | 298 | Packages are normally built for all available architectures, subject | ||
268 | 299 | to any constraints in their `Architecture` field. If a filter is | ||
269 | 300 | set, then it applies the additional constraint that packages not | ||
270 | 301 | included by the filter will not be built for this architecture. | ||
271 | 302 | |||
272 | 303 | If the sense of the filter is "Include", then the filter only | ||
273 | 304 | includes packages in the given package set. If the sense of the | ||
274 | 305 | filter is "Exclude", then the filter only includes packages not in | ||
275 | 306 | the given package set. | ||
276 | 307 | |||
277 | 308 | Later changes to the given package set will also affect any filters | ||
278 | 309 | using it. | ||
279 | 310 | |||
280 | 311 | :param packageset: An `IPackageset` to use as a filter. | ||
281 | 312 | :param sense: A `DistroArchSeriesFilterSense` item indicating | ||
282 | 313 | whether the filter includes or excludes packages. | ||
283 | 314 | :param creator: The `IPerson` who is creating this filter. | ||
284 | 315 | """ | ||
285 | 316 | |||
286 | 317 | def removeSourceFilter(): | ||
287 | 318 | """Remove any filter for packages to build for this architecture. | ||
288 | 319 | |||
289 | 320 | This causes packages to be built for this architecture when they | ||
290 | 321 | might previously have been filtered, subject to any constraints in | ||
291 | 322 | their `Architecture` field. | ||
292 | 323 | """ | ||
293 | 324 | |||
294 | 266 | 325 | ||
295 | 267 | class IDistroArchSeries(IDistroArchSeriesPublic, IDistroArchSeriesModerate): | 326 | class IDistroArchSeries(IDistroArchSeriesPublic, IDistroArchSeriesModerate): |
296 | 268 | """An architecture for a distroseries.""" | 327 | """An architecture for a distroseries.""" |
297 | 269 | 328 | ||
298 | === added file 'lib/lp/soyuz/interfaces/distroarchseriesfilter.py' | |||
299 | --- lib/lp/soyuz/interfaces/distroarchseriesfilter.py 1970-01-01 00:00:00 +0000 | |||
300 | +++ lib/lp/soyuz/interfaces/distroarchseriesfilter.py 2019-09-10 11:16:47 +0000 | |||
301 | @@ -0,0 +1,127 @@ | |||
302 | 1 | # Copyright 2019 Canonical Ltd. This software is licensed under the | ||
303 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
304 | 3 | |||
305 | 4 | """Distro arch series filter interfaces.""" | ||
306 | 5 | |||
307 | 6 | from __future__ import absolute_import, print_function, unicode_literals | ||
308 | 7 | |||
309 | 8 | __metaclass__ = type | ||
310 | 9 | __all__ = [ | ||
311 | 10 | 'IDistroArchSeriesFilter', | ||
312 | 11 | 'IDistroArchSeriesFilterSet', | ||
313 | 12 | 'NoSuchDistroArchSeriesFilter', | ||
314 | 13 | ] | ||
315 | 14 | |||
316 | 15 | from lazr.restful.fields import Reference | ||
317 | 16 | from zope.interface import Interface | ||
318 | 17 | from zope.schema import ( | ||
319 | 18 | Choice, | ||
320 | 19 | Datetime, | ||
321 | 20 | Int, | ||
322 | 21 | ) | ||
323 | 22 | |||
324 | 23 | from lp import _ | ||
325 | 24 | from lp.app.errors import NameLookupFailed | ||
326 | 25 | from lp.services.fields import PublicPersonChoice | ||
327 | 26 | from lp.soyuz.enums import DistroArchSeriesFilterSense | ||
328 | 27 | from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries | ||
329 | 28 | from lp.soyuz.interfaces.packageset import IPackageset | ||
330 | 29 | |||
331 | 30 | |||
332 | 31 | class NoSuchDistroArchSeriesFilter(NameLookupFailed): | ||
333 | 32 | """Raised when we try to look up a nonexistent DistroArchSeriesFilter.""" | ||
334 | 33 | _message_prefix = ( | ||
335 | 34 | "The given distro arch series has no DistroArchSeriesFilter") | ||
336 | 35 | |||
337 | 36 | |||
338 | 37 | class IDistroArchSeriesFilterView(Interface): | ||
339 | 38 | """`IDistroArchSeriesFilter` attributes that require launchpad.View.""" | ||
340 | 39 | |||
341 | 40 | id = Int(title=_("ID"), readonly=True, required=True) | ||
342 | 41 | |||
343 | 42 | distroarchseries = Reference( | ||
344 | 43 | title=_("Distro arch series"), required=True, readonly=True, | ||
345 | 44 | schema=IDistroArchSeries, | ||
346 | 45 | description=_("The distro arch series that this filter is for.")) | ||
347 | 46 | |||
348 | 47 | packageset = Reference( | ||
349 | 48 | title=_("Package set"), required=True, readonly=True, | ||
350 | 49 | schema=IPackageset, | ||
351 | 50 | description=_( | ||
352 | 51 | "The package set to be included in or excluded from this distro " | ||
353 | 52 | "arch series.")) | ||
354 | 53 | |||
355 | 54 | sense = Choice( | ||
356 | 55 | title=_("Sense"), | ||
357 | 56 | vocabulary=DistroArchSeriesFilterSense, required=True, readonly=True, | ||
358 | 57 | description=_( | ||
359 | 58 | "Whether the filter represents packages to include or exclude " | ||
360 | 59 | "from the distro arch series.")) | ||
361 | 60 | |||
362 | 61 | creator = PublicPersonChoice( | ||
363 | 62 | title=_("Creator"), required=True, readonly=True, | ||
364 | 63 | vocabulary="ValidPerson", | ||
365 | 64 | description=_("The user who created this filter.")) | ||
366 | 65 | |||
367 | 66 | date_created = Datetime( | ||
368 | 67 | title=_("Date created"), required=True, readonly=True, | ||
369 | 68 | description=_("The time when this filter was created.")) | ||
370 | 69 | |||
371 | 70 | date_last_modified = Datetime( | ||
372 | 71 | title=_("Date last modified"), required=True, readonly=True, | ||
373 | 72 | description=_("The time when this filter was last modified.")) | ||
374 | 73 | |||
375 | 74 | def isSourceIncluded(sourcepackagename): | ||
376 | 75 | """Is this source package name included by this filter? | ||
377 | 76 | |||
378 | 77 | If the sense of the filter is INCLUDE, then this returns True iff | ||
379 | 78 | the source package name is included in the related package set; | ||
380 | 79 | otherwise, it returns True iff the source package name is not | ||
381 | 80 | included in the related package set. | ||
382 | 81 | |||
383 | 82 | :param sourcepackagename: an `ISourcePackageName`. | ||
384 | 83 | :return: True if the source is included by this filter, otherwise | ||
385 | 84 | False. | ||
386 | 85 | """ | ||
387 | 86 | |||
388 | 87 | |||
389 | 88 | class IDistroArchSeriesFilterEdit(Interface): | ||
390 | 89 | """`IDistroArchSeriesFilter` attributes that require launchpad.Edit.""" | ||
391 | 90 | |||
392 | 91 | def destroySelf(): | ||
393 | 92 | """Delete this filter.""" | ||
394 | 93 | |||
395 | 94 | |||
396 | 95 | class IDistroArchSeriesFilter( | ||
397 | 96 | IDistroArchSeriesFilterView, IDistroArchSeriesFilterEdit): | ||
398 | 97 | """A filter for packages to be included in or excluded from a DAS. | ||
399 | 98 | |||
400 | 99 | Since package sets can include other package sets, a single package set | ||
401 | 100 | is flexible enough for this. However, one might reasonably want to | ||
402 | 101 | either include some packages ("this architecture is obsolescent or | ||
403 | 102 | experimental and we only want to build a few packages for it") or | ||
404 | 103 | exclude some packages ("this architecture can't handle some packages so | ||
405 | 104 | we want to make them go away centrally"). | ||
406 | 105 | """ | ||
407 | 106 | |||
408 | 107 | |||
409 | 108 | class IDistroArchSeriesFilterSet(Interface): | ||
410 | 109 | """An interface for multiple distro arch series filters.""" | ||
411 | 110 | |||
412 | 111 | def new(distroarchseries, packageset, sense, creator, date_created=None): | ||
413 | 112 | """Create an `IDistroArchSeriesFilter`.""" | ||
414 | 113 | |||
415 | 114 | def getByDistroArchSeries(distroarchseries): | ||
416 | 115 | """Return the filter for this distro arch series, if any. | ||
417 | 116 | |||
418 | 117 | :param distroarchseries: The `IDistroArchSeries` to search for. | ||
419 | 118 | :return: An `IDistroArchSeriesFilter` instance. | ||
420 | 119 | :raises NoSuchDistroArchSeriesFilter: if no filter is found. | ||
421 | 120 | """ | ||
422 | 121 | |||
423 | 122 | def findByPackageset(packageset): | ||
424 | 123 | """Return any filters using this package set. | ||
425 | 124 | |||
426 | 125 | :param packageset: The `IPackageset` to search for. | ||
427 | 126 | :return: A `ResultSet` of `IDistroArchSeriesFilter` instances. | ||
428 | 127 | """ | ||
429 | 0 | 128 | ||
430 | === modified file 'lib/lp/soyuz/model/distroarchseries.py' | |||
431 | --- lib/lp/soyuz/model/distroarchseries.py 2019-07-30 11:38:18 +0000 | |||
432 | +++ lib/lp/soyuz/model/distroarchseries.py 2019-09-10 11:16:47 +0000 | |||
433 | @@ -55,10 +55,14 @@ | |||
434 | 55 | from lp.soyuz.interfaces.buildrecords import IHasBuildRecords | 55 | from lp.soyuz.interfaces.buildrecords import IHasBuildRecords |
435 | 56 | from lp.soyuz.interfaces.distroarchseries import ( | 56 | from lp.soyuz.interfaces.distroarchseries import ( |
436 | 57 | ChrootNotPublic, | 57 | ChrootNotPublic, |
437 | 58 | FilterSeriesMismatch, | ||
438 | 58 | IDistroArchSeries, | 59 | IDistroArchSeries, |
439 | 59 | InvalidChrootUploaded, | 60 | InvalidChrootUploaded, |
440 | 60 | IPocketChroot, | 61 | IPocketChroot, |
441 | 61 | ) | 62 | ) |
442 | 63 | from lp.soyuz.interfaces.distroarchseriesfilter import ( | ||
443 | 64 | IDistroArchSeriesFilterSet, | ||
444 | 65 | ) | ||
445 | 62 | from lp.soyuz.interfaces.publishing import active_publishing_status | 66 | from lp.soyuz.interfaces.publishing import active_publishing_status |
446 | 63 | from lp.soyuz.model.binarypackagename import BinaryPackageName | 67 | from lp.soyuz.model.binarypackagename import BinaryPackageName |
447 | 64 | from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease | 68 | from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease |
448 | @@ -341,6 +345,32 @@ | |||
449 | 341 | def main_archive(self): | 345 | def main_archive(self): |
450 | 342 | return self.distroseries.distribution.main_archive | 346 | return self.distroseries.distribution.main_archive |
451 | 343 | 347 | ||
452 | 348 | def getSourceFilter(self): | ||
453 | 349 | """See `IDistroArchSeries`.""" | ||
454 | 350 | return getUtility(IDistroArchSeriesFilterSet).getByDistroArchSeries( | ||
455 | 351 | self) | ||
456 | 352 | |||
457 | 353 | def setSourceFilter(self, packageset, sense, creator): | ||
458 | 354 | """See `IDistroArchSeries`.""" | ||
459 | 355 | if self.distroseries != packageset.distroseries: | ||
460 | 356 | raise FilterSeriesMismatch(self, packageset) | ||
461 | 357 | self.removeSourceFilter() | ||
462 | 358 | getUtility(IDistroArchSeriesFilterSet).new( | ||
463 | 359 | self, packageset, sense, creator) | ||
464 | 360 | |||
465 | 361 | def removeSourceFilter(self): | ||
466 | 362 | """See `IDistroArchSeries`.""" | ||
467 | 363 | dasf = self.getSourceFilter() | ||
468 | 364 | if dasf is not None: | ||
469 | 365 | dasf.destroySelf() | ||
470 | 366 | |||
471 | 367 | def isSourceIncluded(self, sourcepackagename): | ||
472 | 368 | """See `IDistroArchSeries`.""" | ||
473 | 369 | dasf = self.getSourceFilter() | ||
474 | 370 | if dasf is None: | ||
475 | 371 | return True | ||
476 | 372 | return dasf.isSourceIncluded(sourcepackagename) | ||
477 | 373 | |||
478 | 344 | 374 | ||
479 | 345 | @implementer(IPocketChroot) | 375 | @implementer(IPocketChroot) |
480 | 346 | class PocketChroot(SQLBase): | 376 | class PocketChroot(SQLBase): |
481 | 347 | 377 | ||
482 | === added file 'lib/lp/soyuz/model/distroarchseriesfilter.py' | |||
483 | --- lib/lp/soyuz/model/distroarchseriesfilter.py 1970-01-01 00:00:00 +0000 | |||
484 | +++ lib/lp/soyuz/model/distroarchseriesfilter.py 2019-09-10 11:16:47 +0000 | |||
485 | @@ -0,0 +1,124 @@ | |||
486 | 1 | # Copyright 2019 Canonical Ltd. This software is licensed under the | ||
487 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
488 | 3 | |||
489 | 4 | """Distro arch series filters.""" | ||
490 | 5 | |||
491 | 6 | from __future__ import absolute_import, print_function, unicode_literals | ||
492 | 7 | |||
493 | 8 | __metaclass__ = type | ||
494 | 9 | __all__ = [ | ||
495 | 10 | 'DistroArchSeriesFilter', | ||
496 | 11 | ] | ||
497 | 12 | |||
498 | 13 | import pytz | ||
499 | 14 | from storm.locals import ( | ||
500 | 15 | DateTime, | ||
501 | 16 | Int, | ||
502 | 17 | Reference, | ||
503 | 18 | Storm, | ||
504 | 19 | ) | ||
505 | 20 | from zope.interface import implementer | ||
506 | 21 | from zope.security.proxy import removeSecurityProxy | ||
507 | 22 | |||
508 | 23 | from lp.services.database.constants import ( | ||
509 | 24 | DEFAULT, | ||
510 | 25 | UTC_NOW, | ||
511 | 26 | ) | ||
512 | 27 | from lp.services.database.enumcol import DBEnum | ||
513 | 28 | from lp.services.database.interfaces import ( | ||
514 | 29 | IMasterStore, | ||
515 | 30 | IStore, | ||
516 | 31 | ) | ||
517 | 32 | from lp.soyuz.enums import DistroArchSeriesFilterSense | ||
518 | 33 | from lp.soyuz.interfaces.distroarchseriesfilter import ( | ||
519 | 34 | IDistroArchSeriesFilter, | ||
520 | 35 | IDistroArchSeriesFilterSet, | ||
521 | 36 | ) | ||
522 | 37 | |||
523 | 38 | |||
524 | 39 | def distro_arch_series_filter_modified(pss, event): | ||
525 | 40 | """Update date_last_modified when a `DistroArchSeriesFilter` is modified. | ||
526 | 41 | |||
527 | 42 | This method is registered as a subscriber to `IObjectModifiedEvent` | ||
528 | 43 | events on `DistroArchSeriesFilter`s. | ||
529 | 44 | """ | ||
530 | 45 | removeSecurityProxy(pss).date_last_modified = UTC_NOW | ||
531 | 46 | |||
532 | 47 | |||
533 | 48 | @implementer(IDistroArchSeriesFilter) | ||
534 | 49 | class DistroArchSeriesFilter(Storm): | ||
535 | 50 | """See `IDistroArchSeriesFilter`.""" | ||
536 | 51 | |||
537 | 52 | __storm_table__ = "DistroArchSeriesFilter" | ||
538 | 53 | |||
539 | 54 | id = Int(primary=True) | ||
540 | 55 | |||
541 | 56 | distroarchseries_id = Int(name="distroarchseries", allow_none=False) | ||
542 | 57 | distroarchseries = Reference(distroarchseries_id, "DistroArchSeries.id") | ||
543 | 58 | |||
544 | 59 | packageset_id = Int(name="packageset", allow_none=False) | ||
545 | 60 | packageset = Reference(packageset_id, "Packageset.id") | ||
546 | 61 | |||
547 | 62 | sense = DBEnum(enum=DistroArchSeriesFilterSense, allow_none=False) | ||
548 | 63 | |||
549 | 64 | creator_id = Int(name="creator", allow_none=False) | ||
550 | 65 | creator = Reference(creator_id, "Person.id") | ||
551 | 66 | |||
552 | 67 | date_created = DateTime( | ||
553 | 68 | name="date_created", tzinfo=pytz.UTC, allow_none=False) | ||
554 | 69 | date_last_modified = DateTime( | ||
555 | 70 | name="date_last_modified", tzinfo=pytz.UTC, allow_none=False) | ||
556 | 71 | |||
557 | 72 | def __init__(self, distroarchseries, packageset, sense, creator, | ||
558 | 73 | date_created=DEFAULT): | ||
559 | 74 | """Construct a `DistroArchSeriesFilter`.""" | ||
560 | 75 | super(DistroArchSeriesFilter, self).__init__() | ||
561 | 76 | self.distroarchseries = distroarchseries | ||
562 | 77 | self.packageset = packageset | ||
563 | 78 | self.sense = sense | ||
564 | 79 | self.creator = creator | ||
565 | 80 | self.date_created = date_created | ||
566 | 81 | self.date_last_modified = date_created | ||
567 | 82 | |||
568 | 83 | def __repr__(self): | ||
569 | 84 | return "<DistroArchSeriesFilter for %s>" % self.distroarchseries.title | ||
570 | 85 | |||
571 | 86 | def isSourceIncluded(self, sourcepackagename): | ||
572 | 87 | """See `IDistroArchSeriesFilter`.""" | ||
573 | 88 | return ( | ||
574 | 89 | (self.sense == DistroArchSeriesFilterSense.INCLUDE) == | ||
575 | 90 | self.packageset.isSourceIncluded(sourcepackagename)) | ||
576 | 91 | |||
577 | 92 | def destroySelf(self): | ||
578 | 93 | """See `IDistroArchSeriesFilter`.""" | ||
579 | 94 | IStore(DistroArchSeriesFilter).remove(self) | ||
580 | 95 | |||
581 | 96 | |||
582 | 97 | @implementer(IDistroArchSeriesFilterSet) | ||
583 | 98 | class DistroArchSeriesFilterSet: | ||
584 | 99 | """See `IDistroArchSeriesFilterSet`.""" | ||
585 | 100 | |||
586 | 101 | def new(self, distroarchseries, packageset, sense, creator, | ||
587 | 102 | date_created=DEFAULT): | ||
588 | 103 | """See `IDistroArchSeriesFilterSet`. | ||
589 | 104 | |||
590 | 105 | The caller must check that the creator has suitable permissions on | ||
591 | 106 | `distroarchseries`. | ||
592 | 107 | """ | ||
593 | 108 | store = IMasterStore(DistroArchSeriesFilter) | ||
594 | 109 | dasf = DistroArchSeriesFilter( | ||
595 | 110 | distroarchseries, packageset, sense, creator, | ||
596 | 111 | date_created=date_created) | ||
597 | 112 | store.add(dasf) | ||
598 | 113 | return dasf | ||
599 | 114 | |||
600 | 115 | def getByDistroArchSeries(self, distroarchseries): | ||
601 | 116 | """See `IDistroArchSeriesFilterSet`.""" | ||
602 | 117 | return IStore(DistroArchSeriesFilter).find( | ||
603 | 118 | DistroArchSeriesFilter, | ||
604 | 119 | DistroArchSeriesFilter.distroarchseries == distroarchseries).one() | ||
605 | 120 | |||
606 | 121 | def findByPackageset(self, packageset): | ||
607 | 122 | return IStore(DistroArchSeriesFilter).find( | ||
608 | 123 | DistroArchSeriesFilter, | ||
609 | 124 | DistroArchSeriesFilter.packageset == packageset) | ||
610 | 0 | 125 | ||
611 | === added file 'lib/lp/soyuz/tests/test_distroarchseriesfilter.py' | |||
612 | --- lib/lp/soyuz/tests/test_distroarchseriesfilter.py 1970-01-01 00:00:00 +0000 | |||
613 | +++ lib/lp/soyuz/tests/test_distroarchseriesfilter.py 2019-09-10 11:16:47 +0000 | |||
614 | @@ -0,0 +1,128 @@ | |||
615 | 1 | # Copyright 2019 Canonical Ltd. This software is licensed under the | ||
616 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
617 | 3 | |||
618 | 4 | """Test distro arch series filters.""" | ||
619 | 5 | |||
620 | 6 | from __future__ import absolute_import, print_function, unicode_literals | ||
621 | 7 | |||
622 | 8 | __metaclass__ = type | ||
623 | 9 | |||
624 | 10 | from testtools.matchers import MatchesStructure | ||
625 | 11 | from zope.component import getUtility | ||
626 | 12 | from zope.security.interfaces import Unauthorized | ||
627 | 13 | |||
628 | 14 | from lp.services.database.interfaces import IStore | ||
629 | 15 | from lp.services.database.sqlbase import get_transaction_timestamp | ||
630 | 16 | from lp.soyuz.enums import DistroArchSeriesFilterSense | ||
631 | 17 | from lp.soyuz.interfaces.distroarchseriesfilter import ( | ||
632 | 18 | IDistroArchSeriesFilter, | ||
633 | 19 | IDistroArchSeriesFilterSet, | ||
634 | 20 | ) | ||
635 | 21 | from lp.testing import ( | ||
636 | 22 | person_logged_in, | ||
637 | 23 | TestCaseWithFactory, | ||
638 | 24 | ) | ||
639 | 25 | from lp.testing.layers import ( | ||
640 | 26 | DatabaseFunctionalLayer, | ||
641 | 27 | ZopelessDatabaseLayer, | ||
642 | 28 | ) | ||
643 | 29 | |||
644 | 30 | |||
645 | 31 | class TestDistroArchSeriesFilter(TestCaseWithFactory): | ||
646 | 32 | |||
647 | 33 | layer = DatabaseFunctionalLayer | ||
648 | 34 | |||
649 | 35 | def test_implements_interfaces(self): | ||
650 | 36 | # DistroArchSeriesFilter implements IDistroArchSeriesFilter. | ||
651 | 37 | dasf = self.factory.makeDistroArchSeriesFilter() | ||
652 | 38 | self.assertProvides(dasf, IDistroArchSeriesFilter) | ||
653 | 39 | |||
654 | 40 | def test___repr__(self): | ||
655 | 41 | # `DistroArchSeriesFilter` objects have an informative __repr__. | ||
656 | 42 | das = self.factory.makeDistroArchSeries() | ||
657 | 43 | dasf = self.factory.makeDistroArchSeriesFilter(distroarchseries=das) | ||
658 | 44 | self.assertEqual( | ||
659 | 45 | "<DistroArchSeriesFilter for %s>" % das.title, repr(dasf)) | ||
660 | 46 | |||
661 | 47 | def test_isSourceIncluded_include(self): | ||
662 | 48 | # INCLUDE filters report that a source is included if it is in the | ||
663 | 49 | # packageset. | ||
664 | 50 | spns = [self.factory.makeSourcePackageName() for _ in range(3)] | ||
665 | 51 | dasf = self.factory.makeDistroArchSeriesFilter( | ||
666 | 52 | sense=DistroArchSeriesFilterSense.INCLUDE) | ||
667 | 53 | dasf.packageset.add(spns[:2]) | ||
668 | 54 | self.assertTrue(dasf.isSourceIncluded(spns[0])) | ||
669 | 55 | self.assertTrue(dasf.isSourceIncluded(spns[1])) | ||
670 | 56 | self.assertFalse(dasf.isSourceIncluded(spns[2])) | ||
671 | 57 | |||
672 | 58 | def test_isSourceIncluded_exclude(self): | ||
673 | 59 | # EXCLUDE filters report that a source is included if it is not in | ||
674 | 60 | # the packageset. | ||
675 | 61 | spns = [self.factory.makeSourcePackageName() for _ in range(3)] | ||
676 | 62 | dasf = self.factory.makeDistroArchSeriesFilter( | ||
677 | 63 | sense=DistroArchSeriesFilterSense.EXCLUDE) | ||
678 | 64 | dasf.packageset.add(spns[:2]) | ||
679 | 65 | self.assertFalse(dasf.isSourceIncluded(spns[0])) | ||
680 | 66 | self.assertFalse(dasf.isSourceIncluded(spns[1])) | ||
681 | 67 | self.assertTrue(dasf.isSourceIncluded(spns[2])) | ||
682 | 68 | |||
683 | 69 | def test_destroySelf_unauthorized(self): | ||
684 | 70 | # Ordinary users cannot delete a filter. | ||
685 | 71 | das = self.factory.makeDistroArchSeries() | ||
686 | 72 | self.factory.makeDistroArchSeriesFilter(distroarchseries=das) | ||
687 | 73 | dasf = das.getSourceFilter() | ||
688 | 74 | with person_logged_in(self.factory.makePerson()): | ||
689 | 75 | self.assertRaises(Unauthorized, getattr, dasf, "destroySelf") | ||
690 | 76 | |||
691 | 77 | def test_destroySelf(self): | ||
692 | 78 | # Owners of the DAS's archive can delete a filter. | ||
693 | 79 | das = self.factory.makeDistroArchSeries() | ||
694 | 80 | self.factory.makeDistroArchSeriesFilter(distroarchseries=das) | ||
695 | 81 | dasf = das.getSourceFilter() | ||
696 | 82 | with person_logged_in(das.main_archive.owner): | ||
697 | 83 | dasf.destroySelf() | ||
698 | 84 | self.assertIsNone(das.getSourceFilter()) | ||
699 | 85 | |||
700 | 86 | |||
701 | 87 | class TestDistroArchSeriesFilterSet(TestCaseWithFactory): | ||
702 | 88 | |||
703 | 89 | layer = ZopelessDatabaseLayer | ||
704 | 90 | |||
705 | 91 | def test_class_implements_interface(self): | ||
706 | 92 | # The DistroArchSeriesFilterSet class implements | ||
707 | 93 | # IDistroArchSeriesFilterSet. | ||
708 | 94 | self.assertProvides( | ||
709 | 95 | getUtility(IDistroArchSeriesFilterSet), IDistroArchSeriesFilterSet) | ||
710 | 96 | |||
711 | 97 | def test_new(self): | ||
712 | 98 | # The arguments passed when creating a filter are present on the new | ||
713 | 99 | # object. | ||
714 | 100 | das = self.factory.makeDistroArchSeries() | ||
715 | 101 | packageset = self.factory.makePackageset(distroseries=das.distroseries) | ||
716 | 102 | sense = DistroArchSeriesFilterSense.EXCLUDE | ||
717 | 103 | creator = self.factory.makePerson() | ||
718 | 104 | dasf = getUtility(IDistroArchSeriesFilterSet).new( | ||
719 | 105 | distroarchseries=das, packageset=packageset, sense=sense, | ||
720 | 106 | creator=creator) | ||
721 | 107 | now = get_transaction_timestamp(IStore(dasf)) | ||
722 | 108 | self.assertThat(dasf, MatchesStructure.byEquality( | ||
723 | 109 | distroarchseries=das, packageset=packageset, sense=sense, | ||
724 | 110 | creator=creator, date_created=now, date_last_modified=now)) | ||
725 | 111 | |||
726 | 112 | def test_getByDistroArchSeries(self): | ||
727 | 113 | # getByDistroArchSeries returns the filter for a DAS, if any. | ||
728 | 114 | das = self.factory.makeDistroArchSeries() | ||
729 | 115 | dasf_set = getUtility(IDistroArchSeriesFilterSet) | ||
730 | 116 | self.assertIsNone(dasf_set.getByDistroArchSeries(das)) | ||
731 | 117 | dasf = self.factory.makeDistroArchSeriesFilter(distroarchseries=das) | ||
732 | 118 | self.assertEqual(dasf, dasf_set.getByDistroArchSeries(das)) | ||
733 | 119 | |||
734 | 120 | def test_findByPackageset(self): | ||
735 | 121 | # findByPackageset returns any filters using a package set. | ||
736 | 122 | packageset = self.factory.makePackageset() | ||
737 | 123 | dasf_set = getUtility(IDistroArchSeriesFilterSet) | ||
738 | 124 | self.assertContentEqual([], dasf_set.findByPackageset(packageset)) | ||
739 | 125 | dasfs = [ | ||
740 | 126 | self.factory.makeDistroArchSeriesFilter(packageset=packageset) | ||
741 | 127 | for _ in range(2)] | ||
742 | 128 | self.assertContentEqual(dasfs, dasf_set.findByPackageset(packageset)) | ||
743 | 0 | 129 | ||
744 | === modified file 'lib/lp/testing/factory.py' | |||
745 | --- lib/lp/testing/factory.py 2019-09-05 13:23:34 +0000 | |||
746 | +++ lib/lp/testing/factory.py 2019-09-10 11:16:47 +0000 | |||
747 | @@ -297,6 +297,7 @@ | |||
748 | 297 | ArchivePurpose, | 297 | ArchivePurpose, |
749 | 298 | BinaryPackageFileType, | 298 | BinaryPackageFileType, |
750 | 299 | BinaryPackageFormat, | 299 | BinaryPackageFormat, |
751 | 300 | DistroArchSeriesFilterSense, | ||
752 | 300 | PackageDiffStatus, | 301 | PackageDiffStatus, |
753 | 301 | PackagePublishingPriority, | 302 | PackagePublishingPriority, |
754 | 302 | PackagePublishingStatus, | 303 | PackagePublishingStatus, |
755 | @@ -326,6 +327,7 @@ | |||
756 | 326 | from lp.soyuz.model.distributionsourcepackagecache import ( | 327 | from lp.soyuz.model.distributionsourcepackagecache import ( |
757 | 327 | DistributionSourcePackageCache, | 328 | DistributionSourcePackageCache, |
758 | 328 | ) | 329 | ) |
759 | 330 | from lp.soyuz.model.distroarchseriesfilter import DistroArchSeriesFilter | ||
760 | 329 | from lp.soyuz.model.files import BinaryPackageFile | 331 | from lp.soyuz.model.files import BinaryPackageFile |
761 | 330 | from lp.soyuz.model.livefsbuild import LiveFSFile | 332 | from lp.soyuz.model.livefsbuild import LiveFSFile |
762 | 331 | from lp.soyuz.model.packagediff import PackageDiff | 333 | from lp.soyuz.model.packagediff import PackageDiff |
763 | @@ -4202,6 +4204,27 @@ | |||
764 | 4202 | run_with_login(owner, lambda: package_set.add(packages)) | 4204 | run_with_login(owner, lambda: package_set.add(packages)) |
765 | 4203 | return package_set | 4205 | return package_set |
766 | 4204 | 4206 | ||
767 | 4207 | def makeDistroArchSeriesFilter(self, distroarchseries=None, | ||
768 | 4208 | packageset=None, | ||
769 | 4209 | sense=DistroArchSeriesFilterSense.INCLUDE, | ||
770 | 4210 | creator=None, date_created=DEFAULT): | ||
771 | 4211 | """Make a new `DistroArchSeriesFilter`.""" | ||
772 | 4212 | if distroarchseries is None: | ||
773 | 4213 | if packageset is not None: | ||
774 | 4214 | distroseries = packageset.distroseries | ||
775 | 4215 | else: | ||
776 | 4216 | distroseries = None | ||
777 | 4217 | distroarchseries = self.makeDistroArchSeries( | ||
778 | 4218 | distroseries=distroseries) | ||
779 | 4219 | if packageset is None: | ||
780 | 4220 | packageset = self.makePackageset( | ||
781 | 4221 | distroseries=distroarchseries.distroseries) | ||
782 | 4222 | if creator is None: | ||
783 | 4223 | creator = self.makePerson() | ||
784 | 4224 | return DistroArchSeriesFilter( | ||
785 | 4225 | distroarchseries=distroarchseries, packageset=packageset, | ||
786 | 4226 | sense=sense, creator=creator, date_created=date_created) | ||
787 | 4227 | |||
788 | 4205 | def getAnyPocket(self): | 4228 | def getAnyPocket(self): |
789 | 4206 | return PackagePublishingPocket.BACKPORTS | 4229 | return PackagePublishingPocket.BACKPORTS |
790 | 4207 | 4230 |