Merge ~pappacena/launchpad:pkg-upload-log-api into launchpad:master
- Git
- lp:~pappacena/launchpad
- pkg-upload-log-api
- Merge into master
Proposed by
Thiago F. Pappacena
Status: | Merged |
---|---|
Approved by: | Colin Watson |
Approved revision: | 3306b0830f0e41d583c33c69ee887aa3af1144dd |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~pappacena/launchpad:pkg-upload-log-api |
Merge into: | launchpad:master |
Prerequisite: | ~pappacena/launchpad:archive-queue-audit-trail |
Diff against target: |
460 lines (+124/-81) 9 files modified
lib/lp/_schema_circular_imports.py (+1/-1) lib/lp/registry/interfaces/persondistributionsourcepackage.py (+1/-1) lib/lp/soyuz/browser/configure.zcml (+6/-1) lib/lp/soyuz/browser/queue.py (+7/-36) lib/lp/soyuz/browser/tests/test_queue.py (+1/-1) lib/lp/soyuz/interfaces/queue.py (+57/-37) lib/lp/soyuz/interfaces/webservice.py (+6/-2) lib/lp/soyuz/model/queue.py (+21/-2) lib/lp/soyuz/tests/test_packageupload.py (+24/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+378124@code.launchpad.net |
Commit message
API for Package Upload logs
Description of the change
Reopening MP (https:/
To post a comment you must log in.
- 29165ff... by Thiago F. Pappacena
-
fixing import formatting
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
- 16720db... by Thiago F. Pappacena
-
Merge branch 'master' into pkg-upload-log-api
- 09ab18f... by Thiago F. Pappacena
-
minor style adjustments
Revision history for this message
Thiago F. Pappacena (pappacena) wrote : | # |
Revision history for this message
Colin Watson (cjwatson) : | # |
- 3306b08... by Thiago F. Pappacena
-
removing from interface ID attributes
Revision history for this message
Thiago F. Pappacena (pappacena) wrote : | # |
Pushing changes
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/_schema_circular_imports.py b/lib/lp/_schema_circular_imports.py | |||
2 | index 20db323..372744f 100644 | |||
3 | --- a/lib/lp/_schema_circular_imports.py | |||
4 | +++ b/lib/lp/_schema_circular_imports.py | |||
5 | @@ -1,4 +1,4 @@ | |||
7 | 1 | # Copyright 2009-2019 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2020 Canonical Ltd. This software is licensed under the |
8 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
9 | 3 | 3 | ||
10 | 4 | """Update the interface schema values due to circular imports. | 4 | """Update the interface schema values due to circular imports. |
11 | diff --git a/lib/lp/registry/interfaces/persondistributionsourcepackage.py b/lib/lp/registry/interfaces/persondistributionsourcepackage.py | |||
12 | index 33ead04..d8d72bc 100644 | |||
13 | --- a/lib/lp/registry/interfaces/persondistributionsourcepackage.py | |||
14 | +++ b/lib/lp/registry/interfaces/persondistributionsourcepackage.py | |||
15 | @@ -16,10 +16,10 @@ from zope.interface import ( | |||
16 | 16 | ) | 16 | ) |
17 | 17 | from zope.schema import TextLine | 17 | from zope.schema import TextLine |
18 | 18 | 18 | ||
19 | 19 | from lp.registry.interfaces.person import IPerson | ||
20 | 20 | from lp.registry.interfaces.distributionsourcepackage import ( | 19 | from lp.registry.interfaces.distributionsourcepackage import ( |
21 | 21 | IDistributionSourcePackage, | 20 | IDistributionSourcePackage, |
22 | 22 | ) | 21 | ) |
23 | 22 | from lp.registry.interfaces.person import IPerson | ||
24 | 23 | 23 | ||
25 | 24 | 24 | ||
26 | 25 | class IPersonDistributionSourcePackage(Interface): | 25 | class IPersonDistributionSourcePackage(Interface): |
27 | diff --git a/lib/lp/soyuz/browser/configure.zcml b/lib/lp/soyuz/browser/configure.zcml | |||
28 | index 1ce6b53..4ab2e41 100644 | |||
29 | --- a/lib/lp/soyuz/browser/configure.zcml | |||
30 | +++ b/lib/lp/soyuz/browser/configure.zcml | |||
31 | @@ -1,4 +1,4 @@ | |||
33 | 1 | <!-- Copyright 2009-2019 Canonical Ltd. This software is licensed under the | 1 | <!-- Copyright 2009-2020 Canonical Ltd. This software is licensed under the |
34 | 2 | GNU Affero General Public License version 3 (see the file LICENSE). | 2 | GNU Affero General Public License version 3 (see the file LICENSE). |
35 | 3 | --> | 3 | --> |
36 | 4 | 4 | ||
37 | @@ -661,6 +661,11 @@ | |||
38 | 661 | path_expression="string:+upload/${id}" | 661 | path_expression="string:+upload/${id}" |
39 | 662 | attribute_to_parent="distroseries" | 662 | attribute_to_parent="distroseries" |
40 | 663 | /> | 663 | /> |
41 | 664 | <browser:url | ||
42 | 665 | for="lp.soyuz.interfaces.queue.IPackageUploadLog" | ||
43 | 666 | path_expression="string:+log/${id}" | ||
44 | 667 | attribute_to_parent="package_upload" | ||
45 | 668 | /> | ||
46 | 664 | <browser:navigation | 669 | <browser:navigation |
47 | 665 | module="lp.soyuz.browser.queue" | 670 | module="lp.soyuz.browser.queue" |
48 | 666 | classes="PackageUploadNavigation" | 671 | classes="PackageUploadNavigation" |
49 | diff --git a/lib/lp/soyuz/browser/queue.py b/lib/lp/soyuz/browser/queue.py | |||
50 | index 39177bd..520fd68 100644 | |||
51 | --- a/lib/lp/soyuz/browser/queue.py | |||
52 | +++ b/lib/lp/soyuz/browser/queue.py | |||
53 | @@ -69,7 +69,6 @@ from lp.soyuz.model.files import ( | |||
54 | 69 | from lp.soyuz.model.packagecopyjob import PackageCopyJob | 69 | from lp.soyuz.model.packagecopyjob import PackageCopyJob |
55 | 70 | from lp.soyuz.model.queue import ( | 70 | from lp.soyuz.model.queue import ( |
56 | 71 | PackageUploadBuild, | 71 | PackageUploadBuild, |
57 | 72 | PackageUploadLog, | ||
58 | 73 | PackageUploadSource, | 72 | PackageUploadSource, |
59 | 74 | ) | 73 | ) |
60 | 75 | from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease | 74 | from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease |
61 | @@ -211,26 +210,6 @@ class QueueItemsView(LaunchpadView): | |||
62 | 211 | list(getUtility(IPersonSet).getPrecachedPersonsFromIDs( | 210 | list(getUtility(IPersonSet).getPrecachedPersonsFromIDs( |
63 | 212 | person_ids, need_validity=True)) | 211 | person_ids, need_validity=True)) |
64 | 213 | 212 | ||
65 | 214 | def _getPreloadedLogs(self, uploads): | ||
66 | 215 | """Returns a dict of preloaded PackageUploadLog | ||
67 | 216 | |||
68 | 217 | The keys from the returning dict are the package_upload_id, and the | ||
69 | 218 | values are lists of log entries | ||
70 | 219 | """ | ||
71 | 220 | logs = load_referencing( | ||
72 | 221 | PackageUploadLog, uploads, ['package_upload_id']) | ||
73 | 222 | |||
74 | 223 | # Preload users from log entries | ||
75 | 224 | # Not using `need_icon` since the log's reviewers are always persons, | ||
76 | 225 | # and fetching icons should be only needed for teams | ||
77 | 226 | list(getUtility(IPersonSet).getPrecachedPersonsFromIDs( | ||
78 | 227 | [log.reviewer_id for log in logs], | ||
79 | 228 | need_validity=True)) | ||
80 | 229 | logs_dict = defaultdict(list) | ||
81 | 230 | for log in logs: | ||
82 | 231 | logs_dict[log.package_upload_id].append(log) | ||
83 | 232 | return logs_dict | ||
84 | 233 | |||
85 | 234 | def decoratedQueueBatch(self): | 213 | def decoratedQueueBatch(self): |
86 | 235 | """Return the current batch, converted to decorated objects. | 214 | """Return the current batch, converted to decorated objects. |
87 | 236 | 215 | ||
88 | @@ -244,12 +223,12 @@ class QueueItemsView(LaunchpadView): | |||
89 | 244 | return None | 223 | return None |
90 | 245 | 224 | ||
91 | 246 | upload_ids = [upload.id for upload in uploads] | 225 | upload_ids = [upload.id for upload in uploads] |
92 | 247 | puses = load_referencing( | ||
93 | 248 | PackageUploadSource, uploads, ['packageuploadID']) | ||
94 | 249 | pubs = load_referencing( | ||
95 | 250 | PackageUploadBuild, uploads, ['packageuploadID']) | ||
96 | 251 | 226 | ||
98 | 252 | logs_dict = self._getPreloadedLogs(uploads) | 227 | # Both "u.sources" and "u.builds" below are preloaded by |
99 | 228 | # self.context.getPackageUploads (which uses PackageUploadSet.getAll) | ||
100 | 229 | # when building self.batchnav. | ||
101 | 230 | puses = sum([removeSecurityProxy(u.sources) for u in uploads], []) | ||
102 | 231 | pubs = sum([removeSecurityProxy(u.builds) for u in uploads], []) | ||
103 | 253 | 232 | ||
104 | 254 | source_sprs = load_related( | 233 | source_sprs = load_related( |
105 | 255 | SourcePackageRelease, puses, ['sourcepackagereleaseID']) | 234 | SourcePackageRelease, puses, ['sourcepackagereleaseID']) |
106 | @@ -288,9 +267,7 @@ class QueueItemsView(LaunchpadView): | |||
107 | 288 | 267 | ||
108 | 289 | return [ | 268 | return [ |
109 | 290 | CompletePackageUpload( | 269 | CompletePackageUpload( |
113 | 291 | item, build_upload_files, source_upload_files, package_sets, | 270 | item, build_upload_files, source_upload_files, package_sets) |
111 | 292 | sorted(logs_dict[item.id], key=attrgetter("date_created"), | ||
112 | 293 | reverse=True)) | ||
114 | 294 | for item in uploads] | 271 | for item in uploads] |
115 | 295 | 272 | ||
116 | 296 | def is_new(self, binarypackagerelease): | 273 | def is_new(self, binarypackagerelease): |
117 | @@ -517,24 +494,18 @@ class CompletePackageUpload: | |||
118 | 517 | # (i.e. no proxying of __set__). | 494 | # (i.e. no proxying of __set__). |
119 | 518 | pocket = None | 495 | pocket = None |
120 | 519 | date_created = None | 496 | date_created = None |
121 | 520 | sources = None | ||
122 | 521 | builds = None | ||
123 | 522 | logs = None | ||
124 | 523 | customfiles = None | 497 | customfiles = None |
125 | 524 | contains_source = None | 498 | contains_source = None |
126 | 525 | contains_build = None | 499 | contains_build = None |
127 | 526 | sourcepackagerelease = None | 500 | sourcepackagerelease = None |
128 | 527 | 501 | ||
129 | 528 | def __init__(self, packageupload, build_upload_files, | 502 | def __init__(self, packageupload, build_upload_files, |
131 | 529 | source_upload_files, package_sets, logs=None): | 503 | source_upload_files, package_sets): |
132 | 530 | self.pocket = packageupload.pocket | 504 | self.pocket = packageupload.pocket |
133 | 531 | self.date_created = packageupload.date_created | 505 | self.date_created = packageupload.date_created |
134 | 532 | self.context = packageupload | 506 | self.context = packageupload |
135 | 533 | self.sources = list(packageupload.sources) | ||
136 | 534 | self.contains_source = len(self.sources) > 0 | 507 | self.contains_source = len(self.sources) > 0 |
137 | 535 | self.builds = list(packageupload.builds) | ||
138 | 536 | self.contains_build = len(self.builds) > 0 | 508 | self.contains_build = len(self.builds) > 0 |
139 | 537 | self.logs = list(logs) if logs is not None else [] | ||
140 | 538 | self.customfiles = list(packageupload.customfiles) | 509 | self.customfiles = list(packageupload.customfiles) |
141 | 539 | 510 | ||
142 | 540 | # Create a dictionary of binary files keyed by | 511 | # Create a dictionary of binary files keyed by |
143 | diff --git a/lib/lp/soyuz/browser/tests/test_queue.py b/lib/lp/soyuz/browser/tests/test_queue.py | |||
144 | index 242e920..0e08739 100644 | |||
145 | --- a/lib/lp/soyuz/browser/tests/test_queue.py | |||
146 | +++ b/lib/lp/soyuz/browser/tests/test_queue.py | |||
147 | @@ -438,7 +438,7 @@ class TestQueueItemsView(TestCaseWithFactory): | |||
148 | 438 | with StormStatementRecorder() as recorder: | 438 | with StormStatementRecorder() as recorder: |
149 | 439 | view = self.makeView(distroseries, queue_admin) | 439 | view = self.makeView(distroseries, queue_admin) |
150 | 440 | view() | 440 | view() |
152 | 441 | self.assertThat(recorder, HasQueryCount(Equals(57))) | 441 | self.assertThat(recorder, HasQueryCount(Equals(55))) |
153 | 442 | 442 | ||
154 | 443 | def test_package_upload_with_logs_query_count(self): | 443 | def test_package_upload_with_logs_query_count(self): |
155 | 444 | login(ADMIN_EMAIL) | 444 | login(ADMIN_EMAIL) |
156 | diff --git a/lib/lp/soyuz/interfaces/queue.py b/lib/lp/soyuz/interfaces/queue.py | |||
157 | index 929dc45..7bda98a 100644 | |||
158 | --- a/lib/lp/soyuz/interfaces/queue.py | |||
159 | +++ b/lib/lp/soyuz/interfaces/queue.py | |||
160 | @@ -26,6 +26,7 @@ __all__ = [ | |||
161 | 26 | 26 | ||
162 | 27 | import httplib | 27 | import httplib |
163 | 28 | 28 | ||
164 | 29 | from lazr.lifecycle.snapshot import doNotSnapshot | ||
165 | 29 | from lazr.restful.declarations import ( | 30 | from lazr.restful.declarations import ( |
166 | 30 | call_with, | 31 | call_with, |
167 | 31 | error_status, | 32 | error_status, |
168 | @@ -37,7 +38,10 @@ from lazr.restful.declarations import ( | |||
169 | 37 | operation_parameters, | 38 | operation_parameters, |
170 | 38 | REQUEST_USER, | 39 | REQUEST_USER, |
171 | 39 | ) | 40 | ) |
173 | 40 | from lazr.restful.fields import Reference | 41 | from lazr.restful.fields import ( |
174 | 42 | CollectionField, | ||
175 | 43 | Reference, | ||
176 | 44 | ) | ||
177 | 41 | from zope.interface import ( | 45 | from zope.interface import ( |
178 | 42 | Attribute, | 46 | Attribute, |
179 | 43 | Interface, | 47 | Interface, |
180 | @@ -56,6 +60,7 @@ from zope.security.interfaces import Unauthorized | |||
181 | 56 | from lp import _ | 60 | from lp import _ |
182 | 57 | from lp.registry.interfaces.person import IPerson | 61 | from lp.registry.interfaces.person import IPerson |
183 | 58 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 62 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
184 | 63 | from lp.services.webservice.apihelpers import patch_reference_property | ||
185 | 59 | from lp.soyuz.enums import PackageUploadStatus | 64 | from lp.soyuz.enums import PackageUploadStatus |
186 | 60 | from lp.soyuz.interfaces.packagecopyjob import IPackageCopyJob | 65 | from lp.soyuz.interfaces.packagecopyjob import IPackageCopyJob |
187 | 61 | 66 | ||
188 | @@ -113,6 +118,44 @@ class IPackageUploadQueue(Interface): | |||
189 | 113 | """ | 118 | """ |
190 | 114 | 119 | ||
191 | 115 | 120 | ||
192 | 121 | class IPackageUploadLog(Interface): | ||
193 | 122 | """A log entry recording a change in a package upload's status.""" | ||
194 | 123 | |||
195 | 124 | export_as_webservice_entry(publish_web_link=True, as_of="devel") | ||
196 | 125 | |||
197 | 126 | id = Int(title=_('ID'), required=True, readonly=True) | ||
198 | 127 | |||
199 | 128 | package_upload = exported( | ||
200 | 129 | Reference( | ||
201 | 130 | Interface, title=_("The package upload that generated this log"), | ||
202 | 131 | required=True, readonly=True)) | ||
203 | 132 | |||
204 | 133 | date_created = exported( | ||
205 | 134 | Datetime( | ||
206 | 135 | title=_("When this action happened."), required=True, | ||
207 | 136 | readonly=True)) | ||
208 | 137 | |||
209 | 138 | reviewer = exported( | ||
210 | 139 | Reference( | ||
211 | 140 | IPerson, title=_("Who did this action."), | ||
212 | 141 | required=True, readonly=True)) | ||
213 | 142 | |||
214 | 143 | old_status = exported( | ||
215 | 144 | Choice( | ||
216 | 145 | vocabulary=PackageUploadStatus, description=_("Old status."), | ||
217 | 146 | required=True, readonly=True)) | ||
218 | 147 | |||
219 | 148 | new_status = exported( | ||
220 | 149 | Choice( | ||
221 | 150 | vocabulary=PackageUploadStatus, description=_("New status."), | ||
222 | 151 | required=True, readonly=True)) | ||
223 | 152 | |||
224 | 153 | comment = exported( | ||
225 | 154 | TextLine( | ||
226 | 155 | title=_("User's comment about this change."), | ||
227 | 156 | required=False, readonly=True)) | ||
228 | 157 | |||
229 | 158 | |||
230 | 116 | class IPackageUpload(Interface): | 159 | class IPackageUpload(Interface): |
231 | 117 | """A Queue item for the archive uploader.""" | 160 | """A Queue item for the archive uploader.""" |
232 | 118 | 161 | ||
233 | @@ -151,8 +194,6 @@ class IPackageUpload(Interface): | |||
234 | 151 | title=_('Date created'), | 194 | title=_('Date created'), |
235 | 152 | description=_("The date this package upload was done."))) | 195 | description=_("The date this package upload was done."))) |
236 | 153 | 196 | ||
237 | 154 | logs = Attribute(_("The change log of this PackageUpload.")) | ||
238 | 155 | |||
239 | 156 | changesfile = Attribute("The librarian alias for the changes file " | 197 | changesfile = Attribute("The librarian alias for the changes file " |
240 | 157 | "associated with this upload") | 198 | "associated with this upload") |
241 | 158 | changes_file_url = exported( | 199 | changes_file_url = exported( |
242 | @@ -185,6 +226,14 @@ class IPackageUpload(Interface): | |||
243 | 185 | sources = Attribute("The queue sources associated with this queue item") | 226 | sources = Attribute("The queue sources associated with this queue item") |
244 | 186 | builds = Attribute("The queue builds associated with the queue item") | 227 | builds = Attribute("The queue builds associated with the queue item") |
245 | 187 | 228 | ||
246 | 229 | logs = exported( | ||
247 | 230 | doNotSnapshot( | ||
248 | 231 | CollectionField( | ||
249 | 232 | title=_("The package upload logs"), | ||
250 | 233 | value_type=Reference(schema=IPackageUploadLog), | ||
251 | 234 | readonly=True)), | ||
252 | 235 | as_of="devel") | ||
253 | 236 | |||
254 | 188 | customfiles = Attribute("Custom upload files associated with this " | 237 | customfiles = Attribute("Custom upload files associated with this " |
255 | 189 | "queue item") | 238 | "queue item") |
256 | 190 | custom_file_urls = exported( | 239 | custom_file_urls = exported( |
257 | @@ -495,6 +544,9 @@ class IPackageUpload(Interface): | |||
258 | 495 | """ | 544 | """ |
259 | 496 | 545 | ||
260 | 497 | 546 | ||
261 | 547 | patch_reference_property(IPackageUploadLog, 'package_upload', IPackageUpload) | ||
262 | 548 | |||
263 | 549 | |||
264 | 498 | class IPackageUploadBuild(Interface): | 550 | class IPackageUploadBuild(Interface): |
265 | 499 | """A Queue item's related builds.""" | 551 | """A Queue item's related builds.""" |
266 | 500 | 552 | ||
267 | @@ -507,9 +559,7 @@ class IPackageUploadBuild(Interface): | |||
268 | 507 | readonly=False, | 559 | readonly=False, |
269 | 508 | ) | 560 | ) |
270 | 509 | 561 | ||
274 | 510 | build = Int( | 562 | build = Int(title=_("The related build"), required=True, readonly=False) |
272 | 511 | title=_("The related build"), required=True, readonly=False, | ||
273 | 512 | ) | ||
275 | 513 | 563 | ||
276 | 514 | def binaries(): | 564 | def binaries(): |
277 | 515 | """Returns the properties of the binaries in this build. | 565 | """Returns the properties of the binaries in this build. |
278 | @@ -552,8 +602,7 @@ class IPackageUploadSource(Interface): | |||
279 | 552 | 602 | ||
280 | 553 | sourcepackagerelease = Int( | 603 | sourcepackagerelease = Int( |
281 | 554 | title=_("The related source package release"), required=True, | 604 | title=_("The related source package release"), required=True, |
284 | 555 | readonly=False, | 605 | readonly=False) |
283 | 556 | ) | ||
285 | 557 | 606 | ||
286 | 558 | def getSourceAncestryForDiffs(): | 607 | def getSourceAncestryForDiffs(): |
287 | 559 | """Return a suitable ancestry publication for this context. | 608 | """Return a suitable ancestry publication for this context. |
288 | @@ -715,35 +764,6 @@ class IPackageUploadCustom(Interface): | |||
289 | 715 | """ | 764 | """ |
290 | 716 | 765 | ||
291 | 717 | 766 | ||
292 | 718 | class IPackageUploadLog(Interface): | ||
293 | 719 | """Entries of package upload status change""" | ||
294 | 720 | |||
295 | 721 | id = Int(title=_('ID'), required=True, readonly=True) | ||
296 | 722 | |||
297 | 723 | package_upload = Reference( | ||
298 | 724 | IPackageUpload, | ||
299 | 725 | title=_("Original package upload."), required=True, readonly=True) | ||
300 | 726 | |||
301 | 727 | date_created = Datetime( | ||
302 | 728 | title=_("When this action happened."), required=True, readonly=True) | ||
303 | 729 | |||
304 | 730 | reviewer = Reference( | ||
305 | 731 | IPerson, title=_("Who did this action."), | ||
306 | 732 | required=True, readonly=True) | ||
307 | 733 | |||
308 | 734 | old_status = Choice( | ||
309 | 735 | vocabulary=PackageUploadStatus, description=_("Old status."), | ||
310 | 736 | required=True, readonly=True) | ||
311 | 737 | |||
312 | 738 | new_status = Choice( | ||
313 | 739 | vocabulary=PackageUploadStatus, description=_("New status."), | ||
314 | 740 | required=True, readonly=True) | ||
315 | 741 | |||
316 | 742 | comment = TextLine( | ||
317 | 743 | title=_("User's comment about this change."), | ||
318 | 744 | required=False, readonly=True) | ||
319 | 745 | |||
320 | 746 | |||
321 | 747 | class IPackageUploadSet(Interface): | 767 | class IPackageUploadSet(Interface): |
322 | 748 | """Represents a set of IPackageUploads""" | 768 | """Represents a set of IPackageUploads""" |
323 | 749 | 769 | ||
324 | diff --git a/lib/lp/soyuz/interfaces/webservice.py b/lib/lp/soyuz/interfaces/webservice.py | |||
325 | index 3620e81..90ef0e1 100644 | |||
326 | --- a/lib/lp/soyuz/interfaces/webservice.py | |||
327 | +++ b/lib/lp/soyuz/interfaces/webservice.py | |||
328 | @@ -1,4 +1,4 @@ | |||
330 | 1 | # Copyright 2010-2019 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2010-2020 Canonical Ltd. This software is licensed under the |
331 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
332 | 3 | 3 | ||
333 | 4 | """All the interfaces that are exposed through the webservice. | 4 | """All the interfaces that are exposed through the webservice. |
334 | @@ -35,6 +35,7 @@ __all__ = [ | |||
335 | 35 | 'ILiveFSBuild', | 35 | 'ILiveFSBuild', |
336 | 36 | 'ILiveFSSet', | 36 | 'ILiveFSSet', |
337 | 37 | 'IPackageUpload', | 37 | 'IPackageUpload', |
338 | 38 | 'IPackageUploadLog', | ||
339 | 38 | 'IPackageset', | 39 | 'IPackageset', |
340 | 39 | 'IPackagesetSet', | 40 | 'IPackagesetSet', |
341 | 40 | 'ISourcePackagePublishingHistory', | 41 | 'ISourcePackagePublishingHistory', |
342 | @@ -104,7 +105,10 @@ from lp.soyuz.interfaces.publishing import ( | |||
343 | 104 | IBinaryPackagePublishingHistory, | 105 | IBinaryPackagePublishingHistory, |
344 | 105 | ISourcePackagePublishingHistory, | 106 | ISourcePackagePublishingHistory, |
345 | 106 | ) | 107 | ) |
347 | 107 | from lp.soyuz.interfaces.queue import IPackageUpload | 108 | from lp.soyuz.interfaces.queue import ( |
348 | 109 | IPackageUpload, | ||
349 | 110 | IPackageUploadLog, | ||
350 | 111 | ) | ||
351 | 108 | 112 | ||
352 | 109 | 113 | ||
353 | 110 | _schema_circular_imports | 114 | _schema_circular_imports |
354 | diff --git a/lib/lp/soyuz/model/queue.py b/lib/lp/soyuz/model/queue.py | |||
355 | index 6a55fa7..91e92cd 100644 | |||
356 | --- a/lib/lp/soyuz/model/queue.py | |||
357 | +++ b/lib/lp/soyuz/model/queue.py | |||
358 | @@ -12,7 +12,9 @@ __all__ = [ | |||
359 | 12 | 'PackageUploadSource', | 12 | 'PackageUploadSource', |
360 | 13 | ] | 13 | ] |
361 | 14 | 14 | ||
362 | 15 | from collections import defaultdict | ||
363 | 15 | from itertools import chain | 16 | from itertools import chain |
364 | 17 | from operator import attrgetter | ||
365 | 16 | 18 | ||
366 | 17 | import pytz | 19 | import pytz |
367 | 18 | from sqlobject import ( | 20 | from sqlobject import ( |
368 | @@ -42,6 +44,7 @@ from zope.interface import implementer | |||
369 | 42 | from lp.app.errors import NotFoundError | 44 | from lp.app.errors import NotFoundError |
370 | 43 | from lp.archiveuploader.tagfiles import parse_tagfile_content | 45 | from lp.archiveuploader.tagfiles import parse_tagfile_content |
371 | 44 | from lp.registry.interfaces.gpg import IGPGKeySet | 46 | from lp.registry.interfaces.gpg import IGPGKeySet |
372 | 47 | from lp.registry.interfaces.person import IPersonSet | ||
373 | 45 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 48 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
374 | 46 | from lp.registry.model.sourcepackagename import SourcePackageName | 49 | from lp.registry.model.sourcepackagename import SourcePackageName |
375 | 47 | from lp.services.auditor.client import AuditorClient | 50 | from lp.services.auditor.client import AuditorClient |
376 | @@ -1600,8 +1603,10 @@ class PackageUploadSet: | |||
377 | 1600 | PackageUploadBuild, rows, ["packageuploadID"]) | 1603 | PackageUploadBuild, rows, ["packageuploadID"]) |
378 | 1601 | pucs = load_referencing( | 1604 | pucs = load_referencing( |
379 | 1602 | PackageUploadCustom, rows, ["packageuploadID"]) | 1605 | PackageUploadCustom, rows, ["packageuploadID"]) |
380 | 1606 | logs = load_referencing( | ||
381 | 1607 | PackageUploadLog, rows, ["package_upload_id"]) | ||
382 | 1603 | 1608 | ||
384 | 1604 | prefill_packageupload_caches(rows, puses, pubs, pucs) | 1609 | prefill_packageupload_caches(rows, puses, pubs, pucs, logs) |
385 | 1605 | 1610 | ||
386 | 1606 | return DecoratedResultSet(query, pre_iter_hook=preload_hook) | 1611 | return DecoratedResultSet(query, pre_iter_hook=preload_hook) |
387 | 1607 | 1612 | ||
388 | @@ -1622,18 +1627,32 @@ class PackageUploadSet: | |||
389 | 1622 | PackageUpload.package_copy_job_id.is_in(pcj_ids)) | 1627 | PackageUpload.package_copy_job_id.is_in(pcj_ids)) |
390 | 1623 | 1628 | ||
391 | 1624 | 1629 | ||
393 | 1625 | def prefill_packageupload_caches(uploads, puses, pubs, pucs): | 1630 | def prefill_packageupload_caches(uploads, puses, pubs, pucs, logs): |
394 | 1626 | # Circular imports. | 1631 | # Circular imports. |
395 | 1627 | from lp.soyuz.model.archive import Archive | 1632 | from lp.soyuz.model.archive import Archive |
396 | 1628 | from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild | 1633 | from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild |
397 | 1629 | from lp.soyuz.model.publishing import SourcePackagePublishingHistory | 1634 | from lp.soyuz.model.publishing import SourcePackagePublishingHistory |
398 | 1630 | from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease | 1635 | from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease |
399 | 1631 | 1636 | ||
400 | 1637 | logs_per_pu = defaultdict(list) | ||
401 | 1638 | reviewer_ids = set() | ||
402 | 1639 | for log in logs: | ||
403 | 1640 | reviewer_ids.add(log.reviewer_id) | ||
404 | 1641 | logs_per_pu[log.package_upload_id].append(log) | ||
405 | 1642 | |||
406 | 1643 | # Preload reviewers of the logs. | ||
407 | 1644 | # We are not using `need_icon` here because reviewers are persons, | ||
408 | 1645 | # and icons are only available for teams. | ||
409 | 1646 | list(getUtility(IPersonSet).getPrecachedPersonsFromIDs( | ||
410 | 1647 | reviewer_ids, need_validity=True)) | ||
411 | 1648 | |||
412 | 1632 | for pu in uploads: | 1649 | for pu in uploads: |
413 | 1633 | cache = get_property_cache(pu) | 1650 | cache = get_property_cache(pu) |
414 | 1634 | cache.sources = [] | 1651 | cache.sources = [] |
415 | 1635 | cache.builds = [] | 1652 | cache.builds = [] |
416 | 1636 | cache.customfiles = [] | 1653 | cache.customfiles = [] |
417 | 1654 | cache.logs = sorted( | ||
418 | 1655 | logs_per_pu[pu.id], key=attrgetter("date_created"), reverse=True) | ||
419 | 1637 | 1656 | ||
420 | 1638 | for pus in puses: | 1657 | for pus in puses: |
421 | 1639 | get_property_cache(pus.packageupload).sources.append(pus) | 1658 | get_property_cache(pus.packageupload).sources.append(pus) |
422 | diff --git a/lib/lp/soyuz/tests/test_packageupload.py b/lib/lp/soyuz/tests/test_packageupload.py | |||
423 | index 0c83f33..6e78cdf 100644 | |||
424 | --- a/lib/lp/soyuz/tests/test_packageupload.py | |||
425 | +++ b/lib/lp/soyuz/tests/test_packageupload.py | |||
426 | @@ -18,6 +18,7 @@ from lazr.restfulclient.errors import ( | |||
427 | 18 | ) | 18 | ) |
428 | 19 | from testtools.matchers import ( | 19 | from testtools.matchers import ( |
429 | 20 | Equals, | 20 | Equals, |
430 | 21 | MatchesListwise, | ||
431 | 21 | MatchesStructure, | 22 | MatchesStructure, |
432 | 22 | ) | 23 | ) |
433 | 23 | import transaction | 24 | import transaction |
434 | @@ -1483,3 +1484,26 @@ class TestPackageUploadWebservice(TestCaseWithFactory): | |||
435 | 1483 | person, component=self.universe), | 1484 | person, component=self.universe), |
436 | 1484 | 5) | 1485 | 5) |
437 | 1485 | self.assertThat(recorder2, HasQueryCount.byEquality(recorder1)) | 1486 | self.assertThat(recorder2, HasQueryCount.byEquality(recorder1)) |
438 | 1487 | |||
439 | 1488 | def test_api_package_upload_log(self): | ||
440 | 1489 | # API clients can see upload logs of a source uploads. | ||
441 | 1490 | admin = self.makeQueueAdmin([self.universe]) | ||
442 | 1491 | upload, ws_upload = self.makeSourcePackageUpload( | ||
443 | 1492 | admin, sourcepackagename="hello", component=self.universe) | ||
444 | 1493 | with person_logged_in(admin): | ||
445 | 1494 | upload.rejectFromQueue(admin, 'not a good change') | ||
446 | 1495 | upload.acceptFromQueue(admin) | ||
447 | 1496 | |||
448 | 1497 | logs = removeSecurityProxy(upload).logs | ||
449 | 1498 | ws_logs = ws_upload.logs | ||
450 | 1499 | self.assertEqual(len(ws_logs), len(logs)) | ||
451 | 1500 | self.assertThat(ws_upload.logs, MatchesListwise([ | ||
452 | 1501 | MatchesStructure( | ||
453 | 1502 | comment=Equals(log.comment), | ||
454 | 1503 | date_created=Equals(log.date_created), | ||
455 | 1504 | new_status=Equals(log.new_status.title), | ||
456 | 1505 | old_status=Equals(log.old_status.title), | ||
457 | 1506 | reviewer=MatchesStructure.byEquality( | ||
458 | 1507 | name=log.reviewer.name), | ||
459 | 1508 | package_upload=MatchesStructure.byEquality(id=ws_upload.id)) | ||
460 | 1509 | for log in removeSecurityProxy(upload).logs])) |
I'm pushing the changes. There is still one open topic about having the "ID" attributes at interface level that I would like your feedback, cjwatson.