Merge ~pappacena/launchpad:pkg-upload-log-api into launchpad: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)
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://code.launchpad.net/~pappacena/launchpad/+git/launchpad/+merge/377897) with proper prerequisite repo and branch.

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 :

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.

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
diff --git a/lib/lp/_schema_circular_imports.py b/lib/lp/_schema_circular_imports.py
index 20db323..372744f 100644
--- a/lib/lp/_schema_circular_imports.py
+++ b/lib/lp/_schema_circular_imports.py
@@ -1,4 +1,4 @@
1# Copyright 2009-2019 Canonical Ltd. This software is licensed under the1# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Update the interface schema values due to circular imports.4"""Update the interface schema values due to circular imports.
diff --git a/lib/lp/registry/interfaces/persondistributionsourcepackage.py b/lib/lp/registry/interfaces/persondistributionsourcepackage.py
index 33ead04..d8d72bc 100644
--- a/lib/lp/registry/interfaces/persondistributionsourcepackage.py
+++ b/lib/lp/registry/interfaces/persondistributionsourcepackage.py
@@ -16,10 +16,10 @@ from zope.interface import (
16 )16 )
17from zope.schema import TextLine17from zope.schema import TextLine
1818
19from lp.registry.interfaces.person import IPerson
20from lp.registry.interfaces.distributionsourcepackage import (19from lp.registry.interfaces.distributionsourcepackage import (
21 IDistributionSourcePackage,20 IDistributionSourcePackage,
22 )21 )
22from lp.registry.interfaces.person import IPerson
2323
2424
25class IPersonDistributionSourcePackage(Interface):25class IPersonDistributionSourcePackage(Interface):
diff --git a/lib/lp/soyuz/browser/configure.zcml b/lib/lp/soyuz/browser/configure.zcml
index 1ce6b53..4ab2e41 100644
--- a/lib/lp/soyuz/browser/configure.zcml
+++ b/lib/lp/soyuz/browser/configure.zcml
@@ -1,4 +1,4 @@
1<!-- Copyright 2009-2019 Canonical Ltd. This software is licensed under the1<!-- Copyright 2009-2020 Canonical Ltd. This software is licensed under the
2 GNU Affero General Public License version 3 (see the file LICENSE).2 GNU Affero General Public License version 3 (see the file LICENSE).
3-->3-->
44
@@ -661,6 +661,11 @@
661 path_expression="string:+upload/${id}"661 path_expression="string:+upload/${id}"
662 attribute_to_parent="distroseries"662 attribute_to_parent="distroseries"
663 />663 />
664 <browser:url
665 for="lp.soyuz.interfaces.queue.IPackageUploadLog"
666 path_expression="string:+log/${id}"
667 attribute_to_parent="package_upload"
668 />
664 <browser:navigation669 <browser:navigation
665 module="lp.soyuz.browser.queue"670 module="lp.soyuz.browser.queue"
666 classes="PackageUploadNavigation"671 classes="PackageUploadNavigation"
diff --git a/lib/lp/soyuz/browser/queue.py b/lib/lp/soyuz/browser/queue.py
index 39177bd..520fd68 100644
--- a/lib/lp/soyuz/browser/queue.py
+++ b/lib/lp/soyuz/browser/queue.py
@@ -69,7 +69,6 @@ from lp.soyuz.model.files import (
69from lp.soyuz.model.packagecopyjob import PackageCopyJob69from lp.soyuz.model.packagecopyjob import PackageCopyJob
70from lp.soyuz.model.queue import (70from lp.soyuz.model.queue import (
71 PackageUploadBuild,71 PackageUploadBuild,
72 PackageUploadLog,
73 PackageUploadSource,72 PackageUploadSource,
74 )73 )
75from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease74from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
@@ -211,26 +210,6 @@ class QueueItemsView(LaunchpadView):
211 list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(210 list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
212 person_ids, need_validity=True))211 person_ids, need_validity=True))
213212
214 def _getPreloadedLogs(self, uploads):
215 """Returns a dict of preloaded PackageUploadLog
216
217 The keys from the returning dict are the package_upload_id, and the
218 values are lists of log entries
219 """
220 logs = load_referencing(
221 PackageUploadLog, uploads, ['package_upload_id'])
222
223 # Preload users from log entries
224 # Not using `need_icon` since the log's reviewers are always persons,
225 # and fetching icons should be only needed for teams
226 list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
227 [log.reviewer_id for log in logs],
228 need_validity=True))
229 logs_dict = defaultdict(list)
230 for log in logs:
231 logs_dict[log.package_upload_id].append(log)
232 return logs_dict
233
234 def decoratedQueueBatch(self):213 def decoratedQueueBatch(self):
235 """Return the current batch, converted to decorated objects.214 """Return the current batch, converted to decorated objects.
236215
@@ -244,12 +223,12 @@ class QueueItemsView(LaunchpadView):
244 return None223 return None
245224
246 upload_ids = [upload.id for upload in uploads]225 upload_ids = [upload.id for upload in uploads]
247 puses = load_referencing(
248 PackageUploadSource, uploads, ['packageuploadID'])
249 pubs = load_referencing(
250 PackageUploadBuild, uploads, ['packageuploadID'])
251226
252 logs_dict = self._getPreloadedLogs(uploads)227 # Both "u.sources" and "u.builds" below are preloaded by
228 # self.context.getPackageUploads (which uses PackageUploadSet.getAll)
229 # when building self.batchnav.
230 puses = sum([removeSecurityProxy(u.sources) for u in uploads], [])
231 pubs = sum([removeSecurityProxy(u.builds) for u in uploads], [])
253232
254 source_sprs = load_related(233 source_sprs = load_related(
255 SourcePackageRelease, puses, ['sourcepackagereleaseID'])234 SourcePackageRelease, puses, ['sourcepackagereleaseID'])
@@ -288,9 +267,7 @@ class QueueItemsView(LaunchpadView):
288267
289 return [268 return [
290 CompletePackageUpload(269 CompletePackageUpload(
291 item, build_upload_files, source_upload_files, package_sets,270 item, build_upload_files, source_upload_files, package_sets)
292 sorted(logs_dict[item.id], key=attrgetter("date_created"),
293 reverse=True))
294 for item in uploads]271 for item in uploads]
295272
296 def is_new(self, binarypackagerelease):273 def is_new(self, binarypackagerelease):
@@ -517,24 +494,18 @@ class CompletePackageUpload:
517 # (i.e. no proxying of __set__).494 # (i.e. no proxying of __set__).
518 pocket = None495 pocket = None
519 date_created = None496 date_created = None
520 sources = None
521 builds = None
522 logs = None
523 customfiles = None497 customfiles = None
524 contains_source = None498 contains_source = None
525 contains_build = None499 contains_build = None
526 sourcepackagerelease = None500 sourcepackagerelease = None
527501
528 def __init__(self, packageupload, build_upload_files,502 def __init__(self, packageupload, build_upload_files,
529 source_upload_files, package_sets, logs=None):503 source_upload_files, package_sets):
530 self.pocket = packageupload.pocket504 self.pocket = packageupload.pocket
531 self.date_created = packageupload.date_created505 self.date_created = packageupload.date_created
532 self.context = packageupload506 self.context = packageupload
533 self.sources = list(packageupload.sources)
534 self.contains_source = len(self.sources) > 0507 self.contains_source = len(self.sources) > 0
535 self.builds = list(packageupload.builds)
536 self.contains_build = len(self.builds) > 0508 self.contains_build = len(self.builds) > 0
537 self.logs = list(logs) if logs is not None else []
538 self.customfiles = list(packageupload.customfiles)509 self.customfiles = list(packageupload.customfiles)
539510
540 # Create a dictionary of binary files keyed by511 # Create a dictionary of binary files keyed by
diff --git a/lib/lp/soyuz/browser/tests/test_queue.py b/lib/lp/soyuz/browser/tests/test_queue.py
index 242e920..0e08739 100644
--- a/lib/lp/soyuz/browser/tests/test_queue.py
+++ b/lib/lp/soyuz/browser/tests/test_queue.py
@@ -438,7 +438,7 @@ class TestQueueItemsView(TestCaseWithFactory):
438 with StormStatementRecorder() as recorder:438 with StormStatementRecorder() as recorder:
439 view = self.makeView(distroseries, queue_admin)439 view = self.makeView(distroseries, queue_admin)
440 view()440 view()
441 self.assertThat(recorder, HasQueryCount(Equals(57)))441 self.assertThat(recorder, HasQueryCount(Equals(55)))
442442
443 def test_package_upload_with_logs_query_count(self):443 def test_package_upload_with_logs_query_count(self):
444 login(ADMIN_EMAIL)444 login(ADMIN_EMAIL)
diff --git a/lib/lp/soyuz/interfaces/queue.py b/lib/lp/soyuz/interfaces/queue.py
index 929dc45..7bda98a 100644
--- a/lib/lp/soyuz/interfaces/queue.py
+++ b/lib/lp/soyuz/interfaces/queue.py
@@ -26,6 +26,7 @@ __all__ = [
2626
27import httplib27import httplib
2828
29from lazr.lifecycle.snapshot import doNotSnapshot
29from lazr.restful.declarations import (30from lazr.restful.declarations import (
30 call_with,31 call_with,
31 error_status,32 error_status,
@@ -37,7 +38,10 @@ from lazr.restful.declarations import (
37 operation_parameters,38 operation_parameters,
38 REQUEST_USER,39 REQUEST_USER,
39 )40 )
40from lazr.restful.fields import Reference41from lazr.restful.fields import (
42 CollectionField,
43 Reference,
44 )
41from zope.interface import (45from zope.interface import (
42 Attribute,46 Attribute,
43 Interface,47 Interface,
@@ -56,6 +60,7 @@ from zope.security.interfaces import Unauthorized
56from lp import _60from lp import _
57from lp.registry.interfaces.person import IPerson61from lp.registry.interfaces.person import IPerson
58from lp.registry.interfaces.pocket import PackagePublishingPocket62from lp.registry.interfaces.pocket import PackagePublishingPocket
63from lp.services.webservice.apihelpers import patch_reference_property
59from lp.soyuz.enums import PackageUploadStatus64from lp.soyuz.enums import PackageUploadStatus
60from lp.soyuz.interfaces.packagecopyjob import IPackageCopyJob65from lp.soyuz.interfaces.packagecopyjob import IPackageCopyJob
6166
@@ -113,6 +118,44 @@ class IPackageUploadQueue(Interface):
113 """118 """
114119
115120
121class IPackageUploadLog(Interface):
122 """A log entry recording a change in a package upload's status."""
123
124 export_as_webservice_entry(publish_web_link=True, as_of="devel")
125
126 id = Int(title=_('ID'), required=True, readonly=True)
127
128 package_upload = exported(
129 Reference(
130 Interface, title=_("The package upload that generated this log"),
131 required=True, readonly=True))
132
133 date_created = exported(
134 Datetime(
135 title=_("When this action happened."), required=True,
136 readonly=True))
137
138 reviewer = exported(
139 Reference(
140 IPerson, title=_("Who did this action."),
141 required=True, readonly=True))
142
143 old_status = exported(
144 Choice(
145 vocabulary=PackageUploadStatus, description=_("Old status."),
146 required=True, readonly=True))
147
148 new_status = exported(
149 Choice(
150 vocabulary=PackageUploadStatus, description=_("New status."),
151 required=True, readonly=True))
152
153 comment = exported(
154 TextLine(
155 title=_("User's comment about this change."),
156 required=False, readonly=True))
157
158
116class IPackageUpload(Interface):159class IPackageUpload(Interface):
117 """A Queue item for the archive uploader."""160 """A Queue item for the archive uploader."""
118161
@@ -151,8 +194,6 @@ class IPackageUpload(Interface):
151 title=_('Date created'),194 title=_('Date created'),
152 description=_("The date this package upload was done.")))195 description=_("The date this package upload was done.")))
153196
154 logs = Attribute(_("The change log of this PackageUpload."))
155
156 changesfile = Attribute("The librarian alias for the changes file "197 changesfile = Attribute("The librarian alias for the changes file "
157 "associated with this upload")198 "associated with this upload")
158 changes_file_url = exported(199 changes_file_url = exported(
@@ -185,6 +226,14 @@ class IPackageUpload(Interface):
185 sources = Attribute("The queue sources associated with this queue item")226 sources = Attribute("The queue sources associated with this queue item")
186 builds = Attribute("The queue builds associated with the queue item")227 builds = Attribute("The queue builds associated with the queue item")
187228
229 logs = exported(
230 doNotSnapshot(
231 CollectionField(
232 title=_("The package upload logs"),
233 value_type=Reference(schema=IPackageUploadLog),
234 readonly=True)),
235 as_of="devel")
236
188 customfiles = Attribute("Custom upload files associated with this "237 customfiles = Attribute("Custom upload files associated with this "
189 "queue item")238 "queue item")
190 custom_file_urls = exported(239 custom_file_urls = exported(
@@ -495,6 +544,9 @@ class IPackageUpload(Interface):
495 """544 """
496545
497546
547patch_reference_property(IPackageUploadLog, 'package_upload', IPackageUpload)
548
549
498class IPackageUploadBuild(Interface):550class IPackageUploadBuild(Interface):
499 """A Queue item's related builds."""551 """A Queue item's related builds."""
500552
@@ -507,9 +559,7 @@ class IPackageUploadBuild(Interface):
507 readonly=False,559 readonly=False,
508 )560 )
509561
510 build = Int(562 build = Int(title=_("The related build"), required=True, readonly=False)
511 title=_("The related build"), required=True, readonly=False,
512 )
513563
514 def binaries():564 def binaries():
515 """Returns the properties of the binaries in this build.565 """Returns the properties of the binaries in this build.
@@ -552,8 +602,7 @@ class IPackageUploadSource(Interface):
552602
553 sourcepackagerelease = Int(603 sourcepackagerelease = Int(
554 title=_("The related source package release"), required=True,604 title=_("The related source package release"), required=True,
555 readonly=False,605 readonly=False)
556 )
557606
558 def getSourceAncestryForDiffs():607 def getSourceAncestryForDiffs():
559 """Return a suitable ancestry publication for this context.608 """Return a suitable ancestry publication for this context.
@@ -715,35 +764,6 @@ class IPackageUploadCustom(Interface):
715 """764 """
716765
717766
718class IPackageUploadLog(Interface):
719 """Entries of package upload status change"""
720
721 id = Int(title=_('ID'), required=True, readonly=True)
722
723 package_upload = Reference(
724 IPackageUpload,
725 title=_("Original package upload."), required=True, readonly=True)
726
727 date_created = Datetime(
728 title=_("When this action happened."), required=True, readonly=True)
729
730 reviewer = Reference(
731 IPerson, title=_("Who did this action."),
732 required=True, readonly=True)
733
734 old_status = Choice(
735 vocabulary=PackageUploadStatus, description=_("Old status."),
736 required=True, readonly=True)
737
738 new_status = Choice(
739 vocabulary=PackageUploadStatus, description=_("New status."),
740 required=True, readonly=True)
741
742 comment = TextLine(
743 title=_("User's comment about this change."),
744 required=False, readonly=True)
745
746
747class IPackageUploadSet(Interface):767class IPackageUploadSet(Interface):
748 """Represents a set of IPackageUploads"""768 """Represents a set of IPackageUploads"""
749769
diff --git a/lib/lp/soyuz/interfaces/webservice.py b/lib/lp/soyuz/interfaces/webservice.py
index 3620e81..90ef0e1 100644
--- a/lib/lp/soyuz/interfaces/webservice.py
+++ b/lib/lp/soyuz/interfaces/webservice.py
@@ -1,4 +1,4 @@
1# Copyright 2010-2019 Canonical Ltd. This software is licensed under the1# Copyright 2010-2020 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""All the interfaces that are exposed through the webservice.4"""All the interfaces that are exposed through the webservice.
@@ -35,6 +35,7 @@ __all__ = [
35 'ILiveFSBuild',35 'ILiveFSBuild',
36 'ILiveFSSet',36 'ILiveFSSet',
37 'IPackageUpload',37 'IPackageUpload',
38 'IPackageUploadLog',
38 'IPackageset',39 'IPackageset',
39 'IPackagesetSet',40 'IPackagesetSet',
40 'ISourcePackagePublishingHistory',41 'ISourcePackagePublishingHistory',
@@ -104,7 +105,10 @@ from lp.soyuz.interfaces.publishing import (
104 IBinaryPackagePublishingHistory,105 IBinaryPackagePublishingHistory,
105 ISourcePackagePublishingHistory,106 ISourcePackagePublishingHistory,
106 )107 )
107from lp.soyuz.interfaces.queue import IPackageUpload108from lp.soyuz.interfaces.queue import (
109 IPackageUpload,
110 IPackageUploadLog,
111 )
108112
109113
110_schema_circular_imports114_schema_circular_imports
diff --git a/lib/lp/soyuz/model/queue.py b/lib/lp/soyuz/model/queue.py
index 6a55fa7..91e92cd 100644
--- a/lib/lp/soyuz/model/queue.py
+++ b/lib/lp/soyuz/model/queue.py
@@ -12,7 +12,9 @@ __all__ = [
12 'PackageUploadSource',12 'PackageUploadSource',
13 ]13 ]
1414
15from collections import defaultdict
15from itertools import chain16from itertools import chain
17from operator import attrgetter
1618
17import pytz19import pytz
18from sqlobject import (20from sqlobject import (
@@ -42,6 +44,7 @@ from zope.interface import implementer
42from lp.app.errors import NotFoundError44from lp.app.errors import NotFoundError
43from lp.archiveuploader.tagfiles import parse_tagfile_content45from lp.archiveuploader.tagfiles import parse_tagfile_content
44from lp.registry.interfaces.gpg import IGPGKeySet46from lp.registry.interfaces.gpg import IGPGKeySet
47from lp.registry.interfaces.person import IPersonSet
45from lp.registry.interfaces.pocket import PackagePublishingPocket48from lp.registry.interfaces.pocket import PackagePublishingPocket
46from lp.registry.model.sourcepackagename import SourcePackageName49from lp.registry.model.sourcepackagename import SourcePackageName
47from lp.services.auditor.client import AuditorClient50from lp.services.auditor.client import AuditorClient
@@ -1600,8 +1603,10 @@ class PackageUploadSet:
1600 PackageUploadBuild, rows, ["packageuploadID"])1603 PackageUploadBuild, rows, ["packageuploadID"])
1601 pucs = load_referencing(1604 pucs = load_referencing(
1602 PackageUploadCustom, rows, ["packageuploadID"])1605 PackageUploadCustom, rows, ["packageuploadID"])
1606 logs = load_referencing(
1607 PackageUploadLog, rows, ["package_upload_id"])
16031608
1604 prefill_packageupload_caches(rows, puses, pubs, pucs)1609 prefill_packageupload_caches(rows, puses, pubs, pucs, logs)
16051610
1606 return DecoratedResultSet(query, pre_iter_hook=preload_hook)1611 return DecoratedResultSet(query, pre_iter_hook=preload_hook)
16071612
@@ -1622,18 +1627,32 @@ class PackageUploadSet:
1622 PackageUpload.package_copy_job_id.is_in(pcj_ids))1627 PackageUpload.package_copy_job_id.is_in(pcj_ids))
16231628
16241629
1625def prefill_packageupload_caches(uploads, puses, pubs, pucs):1630def prefill_packageupload_caches(uploads, puses, pubs, pucs, logs):
1626 # Circular imports.1631 # Circular imports.
1627 from lp.soyuz.model.archive import Archive1632 from lp.soyuz.model.archive import Archive
1628 from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild1633 from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
1629 from lp.soyuz.model.publishing import SourcePackagePublishingHistory1634 from lp.soyuz.model.publishing import SourcePackagePublishingHistory
1630 from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease1635 from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
16311636
1637 logs_per_pu = defaultdict(list)
1638 reviewer_ids = set()
1639 for log in logs:
1640 reviewer_ids.add(log.reviewer_id)
1641 logs_per_pu[log.package_upload_id].append(log)
1642
1643 # Preload reviewers of the logs.
1644 # We are not using `need_icon` here because reviewers are persons,
1645 # and icons are only available for teams.
1646 list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
1647 reviewer_ids, need_validity=True))
1648
1632 for pu in uploads:1649 for pu in uploads:
1633 cache = get_property_cache(pu)1650 cache = get_property_cache(pu)
1634 cache.sources = []1651 cache.sources = []
1635 cache.builds = []1652 cache.builds = []
1636 cache.customfiles = []1653 cache.customfiles = []
1654 cache.logs = sorted(
1655 logs_per_pu[pu.id], key=attrgetter("date_created"), reverse=True)
16371656
1638 for pus in puses:1657 for pus in puses:
1639 get_property_cache(pus.packageupload).sources.append(pus)1658 get_property_cache(pus.packageupload).sources.append(pus)
diff --git a/lib/lp/soyuz/tests/test_packageupload.py b/lib/lp/soyuz/tests/test_packageupload.py
index 0c83f33..6e78cdf 100644
--- a/lib/lp/soyuz/tests/test_packageupload.py
+++ b/lib/lp/soyuz/tests/test_packageupload.py
@@ -18,6 +18,7 @@ from lazr.restfulclient.errors import (
18 )18 )
19from testtools.matchers import (19from testtools.matchers import (
20 Equals,20 Equals,
21 MatchesListwise,
21 MatchesStructure,22 MatchesStructure,
22 )23 )
23import transaction24import transaction
@@ -1483,3 +1484,26 @@ class TestPackageUploadWebservice(TestCaseWithFactory):
1483 person, component=self.universe),1484 person, component=self.universe),
1484 5)1485 5)
1485 self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))1486 self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
1487
1488 def test_api_package_upload_log(self):
1489 # API clients can see upload logs of a source uploads.
1490 admin = self.makeQueueAdmin([self.universe])
1491 upload, ws_upload = self.makeSourcePackageUpload(
1492 admin, sourcepackagename="hello", component=self.universe)
1493 with person_logged_in(admin):
1494 upload.rejectFromQueue(admin, 'not a good change')
1495 upload.acceptFromQueue(admin)
1496
1497 logs = removeSecurityProxy(upload).logs
1498 ws_logs = ws_upload.logs
1499 self.assertEqual(len(ws_logs), len(logs))
1500 self.assertThat(ws_upload.logs, MatchesListwise([
1501 MatchesStructure(
1502 comment=Equals(log.comment),
1503 date_created=Equals(log.date_created),
1504 new_status=Equals(log.new_status.title),
1505 old_status=Equals(log.old_status.title),
1506 reviewer=MatchesStructure.byEquality(
1507 name=log.reviewer.name),
1508 package_upload=MatchesStructure.byEquality(id=ws_upload.id))
1509 for log in removeSecurityProxy(upload).logs]))