Merge lp:~michael.nelson/launchpad/496862-ppa-installable-binaries into lp:launchpad

Proposed by Michael Nelson
Status: Rejected
Rejected by: Michael Nelson
Proposed branch: lp:~michael.nelson/launchpad/496862-ppa-installable-binaries
Merge into: lp:launchpad
Diff against target: 675 lines (+240/-89)
10 files modified
lib/lp/soyuz/browser/archive.py (+79/-40)
lib/lp/soyuz/browser/tests/archive-views.txt (+51/-37)
lib/lp/soyuz/browser/tests/test_archive_view.py (+59/-0)
lib/lp/soyuz/doc/archive.txt (+14/-4)
lib/lp/soyuz/interfaces/archive.py (+7/-0)
lib/lp/soyuz/model/archive.py (+23/-1)
lib/lp/soyuz/templates/archive-copy-packages.pt (+1/-1)
lib/lp/soyuz/templates/archive-delete-packages.pt (+1/-1)
lib/lp/soyuz/templates/archive-index.pt (+1/-1)
lib/lp/soyuz/templates/archive-macros.pt (+4/-4)
To merge this branch: bzr merge lp:~michael.nelson/launchpad/496862-ppa-installable-binaries
Reviewer Review Type Date Requested Status
Jonathan Lange (community) Approve
Review via email: mp+21623@code.launchpad.net

Description of the change

This branch is the first of two to address bug 496862.

It adds an ArchiveArchFactory to create a vocabulary dynamically depending on the binaries published in the archive.

The ArchiveSourcePackageListViewBase class is refactored to ArchivePackagesViewBase so that it can be re-used for both source and binary packages.

Finally, the ArchiveView (for the PPA index page) is updated to present binary packages.

The next branch will switch the PPA index ui to present the binary packages.

To test:
bin/test -vv -t archive-views.txt -t archive.txt -t TestArchiveView

To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :
Download full text (4.6 KiB)

On Thu, Mar 18, 2010 at 9:24 AM, Michael Nelson
<email address hidden> wrote:
> Michael Nelson has proposed merging lp:~michael.nelson/launchpad/496862-ppa-installable-binaries into lp:launchpad/devel.
>
> Requested reviews:
>  Canonical Launchpad Engineering (launchpad)
>
>
> This branch is the first of two to address bug 496862.
>
> It adds an ArchiveArchFactory to create a vocabulary dynamically depending on the binaries published in the archive.
>
> The ArchiveSourcePackageListViewBase class is refactored to ArchivePackagesViewBase so that it can be re-used for both source and binary packages.
>
> Finally, the ArchiveView (for the PPA index page) is updated to present binary packages.
>
> The next branch will switch the PPA index ui to present the binary packages.
>
> To test:
> bin/test -vv -t archive-views.txt -t archive.txt -t TestArchiveView
>

Good patch. I like the tests and the renames.

Some minor issues with method names and docstring formatting and a
couple of questions.

jml

> --
> https://code.launchpad.net/~michael.nelson/launchpad/496862-ppa-installable-binaries/+merge/21623
> Your team Launchpad code reviewers from Canonical is subscribed to branch lp:launchpad/devel.
>
> === modified file 'lib/lp/soyuz/browser/archive.py'
> --- lib/lp/soyuz/browser/archive.py     2010-03-10 12:50:18 +0000
> +++ lib/lp/soyuz/browser/archive.py     2010-03-18 09:24:24 +0000
> @@ -589,6 +589,26 @@
>         return SimpleVocabulary(series_terms)
>
>
> +class ArchiveArchVocabularyFactory:
> +    """A factory for generating vocabularies of an archive's arches in
> +    a given distroseries."""
> +
> +    implements(IContextSourceBinder)
> +
> +    def __call__(self, context):
> +        """Return a vocabulary created dynamically from the context archive.
> +
> +        :param context: The archive used to generating vocabulary.
> +        """
> +        arch_terms = []
> +        for arch in context.arches_with_binaries:
> +            term = SimpleTerm(
> +                arch, token=arch.architecturetag,
> +                title=arch.displayname)
> +            arch_terms.append(term)
> +        return SimpleVocabulary(arch_terms)
> +

Why is this a class and not a function? Is the implements() bit really
important?

> === added file 'lib/lp/soyuz/browser/tests/test_archive_view.py'
> --- lib/lp/soyuz/browser/tests/test_archive_view.py     1970-01-01 00:00:00 +0000
> +++ lib/lp/soyuz/browser/tests/test_archive_view.py     2010-03-18 09:24:24 +0000
> @@ -0,0 +1,59 @@
> +# Copyright 2010 Canonical Ltd.  This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).
> +
> +"""Test the main PPA (user) view functionality."""
> +
> +__metaclass__ = type
> +
> +import unittest
> +
> +from canonical.launchpad.ftests import login
> +from canonical.testing import LaunchpadFunctionalLayer
> +from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
> +from lp.testing import TestCaseWithFactory
> +from lp.testing.views import create_initialized_view
> +
> +
> +class TestArchiveView(TestCaseWithFactory):
> +
> +    layer = LaunchpadFunctionalLayer
> +

You need the librarian?

> +    def setUp(self)...

Read more...

Revision history for this message
Jonathan Lange (jml) wrote :

On Thu, Mar 18, 2010 at 12:02 PM, Jonathan Lange <email address hidden> wrote:
...
>> +    def make_binaries_readable(self, binaries):
>
> Methods should be camelCase :( I hate that rule.
>

Per our recent discussion, this is comment is correct and the method
needs to change...

>> +    def test_batched_packages(self):
>
> test_batchedPackages :(
>

But this comment is not. The test can remain the same.

>> +    def test_batched_packages_filter_arch(self):
>
> testBatchedPackagesFilterArch
>

Likewise here.

jml

Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (4.2 KiB)

On Thu, Mar 18, 2010 at 12:03 PM, Jonathan Lange <email address hidden> wrote:
> On Thu, Mar 18, 2010 at 9:24 AM, Michael Nelson
...
>> === modified file 'lib/lp/soyuz/browser/archive.py'
>> --- lib/lp/soyuz/browser/archive.py     2010-03-10 12:50:18 +0000
>> +++ lib/lp/soyuz/browser/archive.py     2010-03-18 09:24:24 +0000
>> @@ -589,6 +589,26 @@
>>         return SimpleVocabulary(series_terms)
>>
>>
>> +class ArchiveArchVocabularyFactory:
>> +    """A factory for generating vocabularies of an archive's arches in
>> +    a given distroseries."""
>> +
>> +    implements(IContextSourceBinder)
>> +
>> +    def __call__(self, context):
>> +        """Return a vocabulary created dynamically from the context archive.
>> +
>> +        :param context: The archive used to generating vocabulary.
>> +        """
>> +        arch_terms = []
>> +        for arch in context.arches_with_binaries:
>> +            term = SimpleTerm(
>> +                arch, token=arch.architecturetag,
>> +                title=arch.displayname)
>> +            arch_terms.append(term)
>> +        return SimpleVocabulary(arch_terms)
>> +
>
> Why is this a class and not a function? Is the implements() bit really
> important?
>

Yep, but I used the zope.interface.directlyProvides as you mentioned,
and it worked well :)

>> === added file 'lib/lp/soyuz/browser/tests/test_archive_view.py'
>> --- lib/lp/soyuz/browser/tests/test_archive_view.py     1970-01-01 00:00:00 +0000
>> +++ lib/lp/soyuz/browser/tests/test_archive_view.py     2010-03-18 09:24:24 +0000
>> @@ -0,0 +1,59 @@
>> +# Copyright 2010 Canonical Ltd.  This software is licensed under the
>> +# GNU Affero General Public License version 3 (see the file LICENSE).
>> +
>> +"""Test the main PPA (user) view functionality."""
>> +
>> +__metaclass__ = type
>> +
>> +import unittest
>> +
>> +from canonical.launchpad.ftests import login
>> +from canonical.testing import LaunchpadFunctionalLayer
>> +from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
>> +from lp.testing import TestCaseWithFactory
>> +from lp.testing.views import create_initialized_view
>> +
>> +
>> +class TestArchiveView(TestCaseWithFactory):
>> +
>> +    layer = LaunchpadFunctionalLayer
>> +
>
> You need the librarian?

Yes, publishing test data with the SoyuzTestPublisher adds librarian
files for the tests.

>
>> +    def setUp(self):
>> +        """Create a ppa with some publishings for different arches
>> +        and in different distroseries."""
>
> Closing triple quotes on newline please.

Done.

>
>> +    def make_binaries_readable(self, binaries):
>
> Methods should be camelCase :( I hate that rule.

Done.

>
>> +    def test_batched_packages(self):
>
> test_batchedPackages :(

Not done, as your subsequent email.

>
>> +    def test_batched_packages_filter_arch(self):
>
> testBatchedPackagesFilterArch

And again.

>
>> === modified file 'lib/lp/soyuz/interfaces/archive.py'
>> --- lib/lp/soyuz/interfaces/archive.py  2010-03-17 16:45:38 +0000
>> +++ lib/lp/soyuz/interfaces/archive.py  2010-03-18 09:24:24 +0000
>> @@ -47,6 +47,7 @@
>>  from canonical.launchpad.interfaces.launchpad import IPrivacy
>>  from lp.registry.interfaces.role import IHasOwner
>> ...

Read more...

1=== modified file 'lib/lp/soyuz/browser/archive.py'
2--- lib/lp/soyuz/browser/archive.py 2010-03-18 09:08:28 +0000
3+++ lib/lp/soyuz/browser/archive.py 2010-03-18 17:01:59 +0000
4@@ -33,7 +33,7 @@
5 from zope.app.form.browser import TextAreaWidget
6 from zope.component import getUtility
7 from zope.formlib import form
8-from zope.interface import implements, Interface
9+from zope.interface import directlyProvides, implements, Interface
10 from zope.security.proxy import removeSecurityProxy
11 from zope.schema import Choice, List, TextLine
12 from zope.schema.interfaces import IContextSourceBinder
13@@ -568,45 +568,22 @@
14 return list(copy_requests)
15
16
17-class ArchiveSeriesVocabularyFactory:
18- """A factory for generating vocabularies of an archive's series."""
19-
20- implements(IContextSourceBinder)
21-
22- def __call__(self, context):
23- """Return a vocabulary created dynamically from the context archive.
24-
25- :param context: The context used to generate the vocabulary. This
26- is passed automatically by the zope machinery. Therefore
27- this factory can only be used in a class where the context is
28- an IArchive.
29- """
30- series_terms = []
31- for distroseries in context.series_with_sources:
32- series_terms.append(
33- SimpleTerm(distroseries, token=distroseries.name,
34- title=distroseries.displayname))
35- return SimpleVocabulary(series_terms)
36-
37-
38-class ArchiveArchVocabularyFactory:
39- """A factory for generating vocabularies of an archive's arches in
40- a given distroseries."""
41-
42- implements(IContextSourceBinder)
43-
44- def __call__(self, context):
45- """Return a vocabulary created dynamically from the context archive.
46-
47- :param context: The archive used to generating vocabulary.
48- """
49- arch_terms = []
50- for arch in context.arches_with_binaries:
51- term = SimpleTerm(
52- arch, token=arch.architecturetag,
53- title=arch.displayname)
54- arch_terms.append(term)
55- return SimpleVocabulary(arch_terms)
56+def archive_series_vocabulary_factory(context):
57+ """Return a vocabulary created dynamically from the context archive.
58+
59+ :param context: The context used to generate the vocabulary. This
60+ is passed automatically by the zope machinery. Therefore
61+ this factory can only be used in a class where the context is
62+ an IArchive.
63+ """
64+ series_terms = []
65+ for distroseries in context.series_with_sources:
66+ series_terms.append(
67+ SimpleTerm(distroseries, token=distroseries.name,
68+ title=distroseries.displayname))
69+ return SimpleVocabulary(series_terms)
70+
71+directlyProvides(archive_series_vocabulary_factory, IContextSourceBinder)
72
73
74 class SeriesFilterWidget(LaunchpadDropdownWidget):
75@@ -625,7 +602,7 @@
76 title=_("Package name contains"), required=False)
77
78 series_filter = Choice(
79- source=ArchiveSeriesVocabularyFactory(), required=False)
80+ source=archive_series_vocabulary_factory, required=False)
81
82 status_filter = Choice(vocabulary=SimpleVocabulary((
83 SimpleTerm(active_publishing_status, 'published', 'Published'),
84@@ -633,10 +610,26 @@
85 )), required=False)
86
87
88+def archive_arch_vocabulary_factory(context):
89+ """Return a vocabulary created dynamically from the context archive.
90+
91+ :param context: The archive used to generating vocabulary.
92+ """
93+ arch_terms = []
94+ for arch in context.arches_with_binaries:
95+ term = SimpleTerm(
96+ arch, token=arch.architecturetag,
97+ title=arch.displayname)
98+ arch_terms.append(term)
99+ return SimpleVocabulary(arch_terms)
100+
101+directlyProvides(archive_arch_vocabulary_factory, IContextSourceBinder)
102+
103+
104 class IArchiveBinaryPackageFilter(IPPAPackageFilter):
105 """The interface used for filtering binary packages."""
106 arch_filter = Choice(
107- source=ArchiveArchVocabularyFactory(), required=False)
108+ source=archive_arch_vocabulary_factory, required=False)
109
110
111 class ArchivePackagesViewBase(ArchiveViewBase, LaunchpadFormView):
112
113=== modified file 'lib/lp/soyuz/browser/tests/test_archive_view.py'
114--- lib/lp/soyuz/browser/tests/test_archive_view.py 2010-03-18 09:06:00 +0000
115+++ lib/lp/soyuz/browser/tests/test_archive_view.py 2010-03-18 17:14:42 +0000
116@@ -29,7 +29,7 @@
117
118 test_publisher.getPubBinaries(archive=self.ppa)
119
120- def make_binaries_readable(self, binaries):
121+ def makeBinariesReadable(self, binaries):
122 """Return a string representation of the binaries."""
123 return ", ".join([
124 "%s in %s" % (
125@@ -45,7 +45,7 @@
126 self.assertEqual(
127 "foo-bin in ubuntutest Breezy Badger Autotest i386, "
128 "foo-bin in ubuntutest Breezy Badger Autotest hppa",
129- self.make_binaries_readable(view.batched_packages))
130+ self.makeBinariesReadable(view.batched_packages))
131
132 def test_batched_packages_filter_arch(self):
133 view = create_initialized_view(
134@@ -53,7 +53,7 @@
135 query_string="field.arch_filter=i386")
136 self.assertEqual(
137 "foo-bin in ubuntutest Breezy Badger Autotest i386",
138- self.make_binaries_readable(view.batched_packages))
139+ self.makeBinariesReadable(view.batched_packages))
140
141 def test_suite():
142 return unittest.TestLoader().loadTestsFromName(__name__)
10544. By Michael Nelson

Refactored dynamic vocabularies to functions rather than classes.

10545. By Michael Nelson

test style method renames.

Revision history for this message
Jonathan Lange (jml) wrote :

All good :)

review: Approve

Unmerged revisions

10545. By Michael Nelson

test style method renames.

10544. By Michael Nelson

Refactored dynamic vocabularies to functions rather than classes.

10543. By Michael Nelson

Removed branch todos as they're done.

10542. By Michael Nelson

Removed lint

10541. By Michael Nelson

Added unit tests for ArchiveView.batched_packages.

10540. By Michael Nelson

Added filtered binary packages to the ArchiveView.

10539. By Michael Nelson

Adds IArchive.arches_with_binaries.

10538. By Michael Nelson

Added failing test for IArchive.arches_with_binaries.

10537. By Michael Nelson

A few more s/sources/packages renaming.

10536. By Michael Nelson

Initial renaming to re-use ArchivePackagesViewBase for both source and binary
package listings.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/soyuz/browser/archive.py'
2--- lib/lp/soyuz/browser/archive.py 2010-03-10 12:50:18 +0000
3+++ lib/lp/soyuz/browser/archive.py 2010-03-22 11:43:06 +0000
4@@ -33,7 +33,7 @@
5 from zope.app.form.browser import TextAreaWidget
6 from zope.component import getUtility
7 from zope.formlib import form
8-from zope.interface import implements, Interface
9+from zope.interface import directlyProvides, implements, Interface
10 from zope.security.proxy import removeSecurityProxy
11 from zope.schema import Choice, List, TextLine
12 from zope.schema.interfaces import IContextSourceBinder
13@@ -568,25 +568,22 @@
14 return list(copy_requests)
15
16
17-class ArchiveSeriesVocabularyFactory:
18- """A factory for generating vocabularies of an archive's series."""
19-
20- implements(IContextSourceBinder)
21-
22- def __call__(self, context):
23- """Return a vocabulary created dynamically from the context archive.
24-
25- :param context: The context used to generate the vocabulary. This
26- is passed automatically by the zope machinery. Therefore
27- this factory can only be used in a class where the context is
28- an IArchive.
29- """
30- series_terms = []
31- for distroseries in context.series_with_sources:
32- series_terms.append(
33- SimpleTerm(distroseries, token=distroseries.name,
34- title=distroseries.displayname))
35- return SimpleVocabulary(series_terms)
36+def archive_series_vocabulary_factory(context):
37+ """Return a vocabulary created dynamically from the context archive.
38+
39+ :param context: The context used to generate the vocabulary. This
40+ is passed automatically by the zope machinery. Therefore
41+ this factory can only be used in a class where the context is
42+ an IArchive.
43+ """
44+ series_terms = []
45+ for distroseries in context.series_with_sources:
46+ series_terms.append(
47+ SimpleTerm(distroseries, token=distroseries.name,
48+ title=distroseries.displayname))
49+ return SimpleVocabulary(series_terms)
50+
51+directlyProvides(archive_series_vocabulary_factory, IContextSourceBinder)
52
53
54 class SeriesFilterWidget(LaunchpadDropdownWidget):
55@@ -605,7 +602,7 @@
56 title=_("Package name contains"), required=False)
57
58 series_filter = Choice(
59- source=ArchiveSeriesVocabularyFactory(), required=False)
60+ source=archive_series_vocabulary_factory, required=False)
61
62 status_filter = Choice(vocabulary=SimpleVocabulary((
63 SimpleTerm(active_publishing_status, 'published', 'Published'),
64@@ -613,8 +610,30 @@
65 )), required=False)
66
67
68-class ArchiveSourcePackageListViewBase(ArchiveViewBase, LaunchpadFormView):
69- """A Form view for filtering and batching source packages."""
70+def archive_arch_vocabulary_factory(context):
71+ """Return a vocabulary created dynamically from the context archive.
72+
73+ :param context: The archive used to generating vocabulary.
74+ """
75+ arch_terms = []
76+ for arch in context.arches_with_binaries:
77+ term = SimpleTerm(
78+ arch, token=arch.architecturetag,
79+ title=arch.displayname)
80+ arch_terms.append(term)
81+ return SimpleVocabulary(arch_terms)
82+
83+directlyProvides(archive_arch_vocabulary_factory, IContextSourceBinder)
84+
85+
86+class IArchiveBinaryPackageFilter(IPPAPackageFilter):
87+ """The interface used for filtering binary packages."""
88+ arch_filter = Choice(
89+ source=archive_arch_vocabulary_factory, required=False)
90+
91+
92+class ArchivePackagesViewBase(ArchiveViewBase, LaunchpadFormView):
93+ """A form view for filtering and batching source or binary packages."""
94
95 schema = IPPAPackageFilter
96 custom_widget('series_filter', SeriesFilterWidget)
97@@ -622,7 +641,7 @@
98
99 # By default this view will not display the sources with selectable
100 # checkboxes, but subclasses can override as needed.
101- selectable_sources = False
102+ selectable_packages = False
103
104 @cachedproperty
105 def series_with_sources(self):
106@@ -680,7 +699,7 @@
107 self.getSelectedFilterValue('series_filter'))
108
109 @property
110- def filtered_sources(self):
111+ def filtered_packages(self):
112 """Return the source results for display after filtering."""
113 return self.context.getPublishedSources(
114 name=self.specified_name_filter,
115@@ -707,31 +726,33 @@
116 @cachedproperty
117 def batchnav(self):
118 """Return a batch navigator of the filtered sources."""
119- return BatchNavigator(self.filtered_sources, self.request)
120+ return BatchNavigator(self.filtered_packages, self.request)
121
122 @cachedproperty
123- def batched_sources(self):
124+ def batched_packages(self):
125 """Return the current batch of archive source publications."""
126 results = list(self.batchnav.currentBatch())
127 return ArchiveSourcePublications(results)
128
129 @cachedproperty
130- def has_sources_for_display(self):
131+ def has_packages_for_display(self):
132 """Whether or not the PPA has any source packages for display.
133
134 This is after any filtering or overriding of the sources() method.
135 """
136 # XXX cprov 20080708 bug=246200: use bool() when it gets fixed
137 # in storm.
138- return self.filtered_sources.count() > 0
139-
140-
141-class ArchiveView(ArchiveSourcePackageListViewBase):
142+ return self.filtered_packages.count() > 0
143+
144+
145+class ArchiveView(ArchivePackagesViewBase):
146 """Default Archive view class.
147
148 Implements useful actions and collects useful sets for the page template.
149 """
150
151+ schema = IArchiveBinaryPackageFilter
152+
153 __used_for__ = IArchive
154 implements(IArchiveIndexActionsMenu)
155
156@@ -744,6 +765,19 @@
157 super(ArchiveView, self).initialize()
158
159 @property
160+ def filtered_packages(self):
161+ """Return the binary results for display after filtering."""
162+ return self.context.getAllPublishedBinaries(
163+ name=self.specified_name_filter,
164+ status=self.getSelectedFilterValue('status_filter'),
165+ distroarchseries=self.getSelectedFilterValue('arch_filter'))
166+
167+ @cachedproperty
168+ def batched_packages(self):
169+ """Return the current batch of archive binary publications."""
170+ return self.batchnav.currentBatch()
171+
172+ @property
173 def displayname_edit_widget(self):
174 widget = TextLineEditorWidget(
175 self.context, 'displayname',
176@@ -777,6 +811,11 @@
177 return None
178
179 @property
180+ def default_arch_filter(self):
181+ """Return the architecture identified by the user-agent."""
182+ return None
183+
184+ @property
185 def archive_description_html(self):
186 """The archive's description as HTML."""
187 formatter = FormattersAPI
188@@ -889,7 +928,7 @@
189 }
190
191
192-class ArchivePackagesView(ArchiveSourcePackageListViewBase):
193+class ArchivePackagesView(ArchivePackagesViewBase):
194 """Detailed packages view for an archive."""
195 implements(IArchivePackagesActionMenu)
196
197@@ -915,12 +954,12 @@
198 return self.context.is_copy
199
200
201-class ArchiveSourceSelectionFormView(ArchiveSourcePackageListViewBase):
202+class ArchiveSourceSelectionFormView(ArchivePackagesViewBase):
203 """Base class to implement a source selection widget for PPAs."""
204
205 custom_widget('selected_sources', LabeledMultiCheckBoxWidget)
206
207- selectable_sources = True
208+ selectable_packages = True
209
210 def setNextURL(self):
211 """Set self.next_url based on current context.
212@@ -938,7 +977,7 @@
213 """
214 # To create the selected sources field, we need to define a
215 # vocabulary based on the currently selected sources (using self
216- # batched_sources) but this itself requires the current values of
217+ # batched_packages) but this itself requires the current values of
218 # the filtering widgets. So we setup the widgets, then add the
219 # extra field and create its widget too.
220 super(ArchiveSourceSelectionFormView, self).setUpWidgets()
221@@ -955,7 +994,7 @@
222
223 Ensure focus is only set if there are sources actually presented.
224 """
225- if not self.has_sources_for_display:
226+ if not self.has_packages_for_display:
227 return ''
228 return LaunchpadFormView.focusedElementScript(self)
229
230@@ -968,7 +1007,7 @@
231 """
232 terms = []
233
234- for pub in self.batched_sources:
235+ for pub in self.batched_packages:
236 terms.append(SimpleTerm(pub, str(pub.id), pub.displayname))
237 return form.Fields(
238 List(__name__='selected_sources',
239@@ -1010,10 +1049,10 @@
240 return None
241
242 @cachedproperty
243- def filtered_sources(self):
244+ def filtered_packages(self):
245 """Return the filtered results of publishing records for deletion.
246
247- This overrides ArchiveViewBase.filtered_sources to use a
248+ This overrides ArchiveViewBase.filtered_packages to use a
249 different method on the context specific to deletion records.
250 """
251 return self.context.getSourcesForDeletion(
252
253=== modified file 'lib/lp/soyuz/browser/tests/archive-views.txt'
254--- lib/lp/soyuz/browser/tests/archive-views.txt 2010-03-06 04:57:40 +0000
255+++ lib/lp/soyuz/browser/tests/archive-views.txt 2010-03-22 11:43:06 +0000
256@@ -165,27 +165,17 @@
257 used_css_class: green
258 used_percentage: 0.92
259
260-An ArchiveView provides a batched_sources property that can be used
261-to get the current batch of publishing records for an archive:
262-
263- >>> for publishing in ppa_archive_view.batched_sources:
264- ... print publishing.source_package_name
265- cdrkit
266- iceweasel
267- pmount
268-
269-The batched_sources property will also be filtered by distroseries when
270-appropriate:
271-
272- >>> filtered_view = create_initialized_view(
273- ... cprov.archive,
274- ... name="+index",
275- ... method='GET',
276- ... query_string='field.series_filter=warty')
277- >>> for publishing in filtered_view.batched_sources:
278- ... print publishing.source_package_name
279- iceweasel
280- pmount
281+An ArchiveView provides a batched_packages property that can be used
282+to get the current batch of installable binaries published for an archive:
283+
284+ >>> for pub in ppa_archive_view.batched_packages:
285+ ... print "%s in %s" % (
286+ ... pub.binary_package_name,
287+ ... pub.distroarchseries.displayname)
288+ mozilla-firefox in Ubuntu Warty hppa
289+ mozilla-firefox in Ubuntu Warty i386
290+ pmount in Ubuntu Warty hppa
291+ pmount in Ubuntu Warty i386
292
293 The context archive dependencies access is also encapsulated in
294 `ArchiveView` with the following aspects:
295@@ -312,7 +302,8 @@
296
297 >>> from canonical.database.constants import UTC_NOW
298 >>> login('foo.bar@canonical.com')
299- >>> view.filtered_sources[1].datepublished = UTC_NOW
300+ >>> iceweasel = view.context.getPublishedSources(name="iceweasel")[0]
301+ >>> iceweasel.datepublished = UTC_NOW
302 >>> login(ANONYMOUS)
303 >>> print_latest_updates(view.latest_updates)
304 iceweasel - Successfully built
305@@ -333,9 +324,10 @@
306 >>> import pytz
307 >>> thirtyone_days_ago = datetime.now(tz=pytz.UTC) - timedelta(31)
308 >>> login('foo.bar@canonical.com')
309- >>> view.filtered_sources[0].datecreated = UTC_NOW
310- >>> view.filtered_sources[1].datecreated = UTC_NOW
311- >>> view.filtered_sources[2].datecreated = thirtyone_days_ago
312+ >>> sources = view.context.getPublishedSources()
313+ >>> sources[0].datecreated = UTC_NOW
314+ >>> sources[1].datecreated = UTC_NOW
315+ >>> sources[2].datecreated = thirtyone_days_ago
316 >>> login(ANONYMOUS)
317
318 >>> view.num_updates_over_last_days()
319@@ -358,7 +350,7 @@
320 >>> from lp.buildmaster.interfaces.buildbase import BuildStatus
321 >>> from lp.soyuz.interfaces.build import IBuildSet
322 >>> warty_hppa = getUtility(IDistributionSet)['ubuntu']['warty']['hppa']
323- >>> source = view.filtered_sources[0]
324+ >>> source = sources[0]
325 >>> ignore = source.sourcepackagerelease.createBuild(
326 ... distroarchseries=warty_hppa, archive=view.context,
327 ... pocket=source.pocket)
328@@ -487,6 +479,28 @@
329 corresponding properties such as archive_url, build_counters etc.
330 (see ArchiveView above).
331
332+An ArchiveView provides a batched_packages property that can be used
333+to get the current batch of publishing records for an archive:
334+
335+ >>> for publishing in ppa_archive_view.batched_packages:
336+ ... print publishing.source_package_name
337+ cdrkit
338+ iceweasel
339+ pmount
340+
341+The batched_packages property will also be filtered by distroseries when
342+appropriate:
343+
344+ >>> filtered_view = create_initialized_view(
345+ ... cprov.archive,
346+ ... name="+packages",
347+ ... method='GET',
348+ ... query_string='field.series_filter=warty')
349+ >>> for publishing in filtered_view.batched_packages:
350+ ... print publishing.source_package_name
351+ iceweasel
352+ pmount
353+
354 Additionally, ArchivePackageView can display a string representation
355 of the series supported by this archive.
356
357@@ -530,10 +544,10 @@
358 We query the available PUBLISHED sources and use them to build the
359 'selected_sources' widget.
360
361- >>> [pub.id for pub in view.batched_sources]
362+ >>> [pub.id for pub in view.batched_packages]
363 [27, 28, 29]
364
365- >>> view.has_sources_for_display
366+ >>> view.has_packages_for_display
367 True
368
369 >>> len(view.widgets.get('selected_sources').vocabulary)
370@@ -543,7 +557,7 @@
371 the user can refine the available options presented. By default all
372 available sources are presented with empty filter.
373
374- >>> for pub in view.batched_sources:
375+ >>> for pub in view.batched_packages:
376 ... print pub.displayname
377 cdrkit 1.0 in breezy-autotest
378 iceweasel 1.0 in warty
379@@ -556,7 +570,7 @@
380 ... cprov.archive, name="+delete-packages",
381 ... query_string="field.name_filter=pmount")
382
383- >>> for pub in view.batched_sources:
384+ >>> for pub in view.batched_packages:
385 ... print pub.displayname
386 pmount 0.1-1 in warty
387
388@@ -568,7 +582,7 @@
389 ... cprov.archive, name="+delete-packages",
390 ... query_string="field.name_filter=%C3%A7")
391
392- >>> len(list(view.batched_sources))
393+ >>> len(list(view.batched_packages))
394 0
395
396 Similarly, the sources can be filtered by series:
397@@ -577,7 +591,7 @@
398 ... cprov.archive, name="+delete-packages",
399 ... query_string="field.series_filter=warty")
400
401- >>> for pub in view.batched_sources:
402+ >>> for pub in view.batched_packages:
403 ... print pub.displayname
404 iceweasel 1.0 in warty
405 pmount 0.1-1 in warty
406@@ -589,7 +603,7 @@
407 ... query_string="field.series_filter=warty",
408 ... form={'batch': '1', 'start': '1'})
409
410- >>> for pub in view.batched_sources:
411+ >>> for pub in view.batched_packages:
412 ... print pub.displayname
413 pmount 0.1-1 in warty
414
415@@ -606,7 +620,7 @@
416 ... 'field.selected_sources-empty-marker': 1,
417 ... })
418
419- >>> view.has_sources_for_display
420+ >>> view.has_packages_for_display
421 False
422
423 >>> import transaction
424@@ -626,7 +640,7 @@
425 ... 'field.selected_sources-empty-marker': 1,
426 ... })
427
428- >>> view.has_sources_for_display
429+ >>> view.has_packages_for_display
430 False
431
432 >>> len(view.errors)
433@@ -1036,7 +1050,7 @@
434 different default status filter (only published sources are presented
435 by default).
436
437- >>> view.has_sources_for_display
438+ >>> view.has_packages_for_display
439 False
440
441 In this case, the template can use the has_sources
442@@ -1054,7 +1068,7 @@
443 ... cprov.archive, name="+copy-packages",
444 ... query_string="field.status_filter=")
445
446- >>> [pub.status.name for pub in view.batched_sources]
447+ >>> [pub.status.name for pub in view.batched_packages]
448 ['DELETED', 'DELETED', 'DELETED']
449
450 This view contains three properties. The first is a list of the PPAs
451
452=== added file 'lib/lp/soyuz/browser/tests/test_archive_view.py'
453--- lib/lp/soyuz/browser/tests/test_archive_view.py 1970-01-01 00:00:00 +0000
454+++ lib/lp/soyuz/browser/tests/test_archive_view.py 2010-03-22 11:43:06 +0000
455@@ -0,0 +1,59 @@
456+# Copyright 2010 Canonical Ltd. This software is licensed under the
457+# GNU Affero General Public License version 3 (see the file LICENSE).
458+
459+"""Test the main PPA (user) view functionality."""
460+
461+__metaclass__ = type
462+
463+import unittest
464+
465+from canonical.launchpad.ftests import login
466+from canonical.testing import LaunchpadFunctionalLayer
467+from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
468+from lp.testing import TestCaseWithFactory
469+from lp.testing.views import create_initialized_view
470+
471+
472+class TestArchiveView(TestCaseWithFactory):
473+
474+ layer = LaunchpadFunctionalLayer
475+
476+ def setUp(self):
477+ """Create a ppa with some publishings for different arches
478+ and in different distroseries."""
479+ super(TestArchiveView, self).setUp()
480+ self.ppa = self.factory.makeArchive()
481+ login('admin@canonical.com')
482+ test_publisher = SoyuzTestPublisher()
483+ test_publisher.prepareBreezyAutotest()
484+
485+ test_publisher.getPubBinaries(archive=self.ppa)
486+
487+ def makeBinariesReadable(self, binaries):
488+ """Return a string representation of the binaries."""
489+ return ", ".join([
490+ "%s in %s" % (
491+ bin.binary_package_name,
492+ bin.distroarchseries.displayname
493+ ) for bin in binaries])
494+
495+ def test_batched_packages(self):
496+ # The batched_packages property of ArchiveView returns
497+ # a batch of binary publishings.
498+ view = create_initialized_view(
499+ self.ppa, name="+index", method="GET")
500+ self.assertEqual(
501+ "foo-bin in ubuntutest Breezy Badger Autotest i386, "
502+ "foo-bin in ubuntutest Breezy Badger Autotest hppa",
503+ self.makeBinariesReadable(view.batched_packages))
504+
505+ def test_batched_packages_filter_arch(self):
506+ view = create_initialized_view(
507+ self.ppa, name="+index", method="GET",
508+ query_string="field.arch_filter=i386")
509+ self.assertEqual(
510+ "foo-bin in ubuntutest Breezy Badger Autotest i386",
511+ self.makeBinariesReadable(view.batched_packages))
512+
513+def test_suite():
514+ return unittest.TestLoader().loadTestsFromName(__name__)
515
516=== modified file 'lib/lp/soyuz/doc/archive.txt'
517--- lib/lp/soyuz/doc/archive.txt 2010-03-13 00:32:40 +0000
518+++ lib/lp/soyuz/doc/archive.txt 2010-03-22 11:43:06 +0000
519@@ -158,10 +158,20 @@
520 >>> print cprov_archive.archive_url
521 http://ppa.launchpad.dev/cprov/ppa/ubuntu
522
523-Inquire what Distribution Series this archive has published sources to:
524-
525- >>> [s.name for s in cprov_archive.series_with_sources]
526- [u'breezy-autotest', u'warty']
527+We can find out to which Distribution Series this archive has published
528+sources:
529+
530+ >>> for series in cprov_archive.series_with_sources:
531+ ... print series.name
532+ breezy-autotest
533+ warty
534+
535+Or the DistroArchSeries to which this archive has published binaries:
536+
537+ >>> for arch in cprov_archive.arches_with_binaries:
538+ ... print arch.architecturetag
539+ hppa
540+ i386
541
542 'purpose' is a read-only attribute, it can't and shouldn't be modified
543 once a IArchive is created. Changing those values would affect the way
544
545=== modified file 'lib/lp/soyuz/interfaces/archive.py'
546--- lib/lp/soyuz/interfaces/archive.py 2010-03-17 16:45:38 +0000
547+++ lib/lp/soyuz/interfaces/archive.py 2010-03-22 11:43:06 +0000
548@@ -47,6 +47,7 @@
549 from canonical.launchpad.interfaces.launchpad import IPrivacy
550 from lp.registry.interfaces.role import IHasOwner
551 from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
552+from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries
553 from lp.registry.interfaces.gpg import IGPGKey
554 from lp.registry.interfaces.person import IPerson
555 from canonical.launchpad.validators.name import name_validator
556@@ -223,6 +224,12 @@
557
558 series_with_sources = Attribute(
559 "DistroSeries to which this archive has published sources")
560+ arches_with_binaries = CollectionField(
561+ title=_(
562+ "DistroArchSeries to which this archive has published "
563+ "binaries."),
564+ value_type=Reference(schema=IDistroArchSeries),
565+ readonly=True)
566 number_of_sources = Attribute(
567 'The number of sources published in the context archive.')
568 number_of_binaries = Attribute(
569
570=== modified file 'lib/lp/soyuz/model/archive.py'
571--- lib/lp/soyuz/model/archive.py 2010-03-17 04:05:42 +0000
572+++ lib/lp/soyuz/model/archive.py 2010-03-22 11:43:06 +0000
573@@ -205,7 +205,7 @@
574 for (family, archive_arch) in restricted_families:
575 if family == arm:
576 return (archive_arch is not None)
577- # ARM doesn't exist or isn't restricted. Either way, there is no
578+ # ARM doesn't exist or isn't restricted. Either way, there is no
579 # need for an explicit association.
580 return False
581
582@@ -286,6 +286,28 @@
583 distro_series, key=lambda a: Version(a.version), reverse=True)
584
585 @property
586+ def arches_with_binaries(self):
587+ """See `IArchive`."""
588+ store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
589+
590+ # Import DistroArchSeries here to avoid circular imports.
591+ from lp.soyuz.model.distroarchseries import DistroArchSeries
592+
593+ distro_arch_series = store.find(
594+ DistroArchSeries,
595+ (BinaryPackagePublishingHistory.distroarchseries ==
596+ DistroArchSeries.id),
597+ BinaryPackagePublishingHistory.archive == self,
598+ BinaryPackagePublishingHistory.status.is_in(
599+ active_publishing_status))
600+
601+ # Ensure the ordering is the same as presented by
602+ # DistroSeries.enabled_architectures.
603+ distro_arch_series.config(distinct=True)
604+ return distro_arch_series.order_by(
605+ DistroArchSeries.architecturetag)
606+
607+ @property
608 def dependencies(self):
609 query = """
610 ArchiveDependency.dependency = Archive.id AND
611
612=== modified file 'lib/lp/soyuz/templates/archive-copy-packages.pt'
613--- lib/lp/soyuz/templates/archive-copy-packages.pt 2009-09-10 11:53:18 +0000
614+++ lib/lp/soyuz/templates/archive-copy-packages.pt 2010-03-22 11:43:06 +0000
615@@ -68,7 +68,7 @@
616 </tal:selectable_sources_end>
617
618 <table style="margin-top: 1em;"
619- tal:condition="view/has_sources_for_display">
620+ tal:condition="view/has_packages_for_display">
621
622 <tal:archive
623 define="widget nocall:view/widgets/destination_archive|nothing"
624
625=== modified file 'lib/lp/soyuz/templates/archive-delete-packages.pt'
626--- lib/lp/soyuz/templates/archive-delete-packages.pt 2009-09-09 17:54:13 +0000
627+++ lib/lp/soyuz/templates/archive-delete-packages.pt 2010-03-22 11:43:06 +0000
628@@ -52,7 +52,7 @@
629 tal:content="structure error">Error message</div>
630 </tal:selectable_sources_end>
631
632- <tal:actions condition="view/has_sources_for_display"
633+ <tal:actions condition="view/has_packages_for_display"
634 define="widget nocall:view/widgets/deletion_comment;
635 field_name widget/context/__name__;
636 error python:view.getFieldError(field_name);">
637
638=== modified file 'lib/lp/soyuz/templates/archive-index.pt'
639--- lib/lp/soyuz/templates/archive-index.pt 2010-02-25 10:47:50 +0000
640+++ lib/lp/soyuz/templates/archive-index.pt 2010-03-22 11:43:06 +0000
641@@ -242,7 +242,7 @@
642 </form>
643 </h2>
644
645- <div style="padding-top: 1em;" tal:define="batch view/batched_sources">
646+ <div style="padding-top: 1em;" tal:define="batch view/batched_packages">
647
648 <tal:navigation_top
649 replace="structure view/batchnav/@@+navigation-links-upper" />
650
651=== modified file 'lib/lp/soyuz/templates/archive-macros.pt'
652--- lib/lp/soyuz/templates/archive-macros.pt 2009-12-03 18:33:22 +0000
653+++ lib/lp/soyuz/templates/archive-macros.pt 2010-03-22 11:43:06 +0000
654@@ -154,7 +154,7 @@
655 archive.
656 </tal:comment>
657
658- <div style="padding-top: 1em;" tal:define="batch view/batched_sources">
659+ <div style="padding-top: 1em;" tal:define="batch view/batched_packages">
660
661 <tal:navigation_top
662 replace="structure view/batchnav/@@+navigation-links-upper" />
663@@ -175,9 +175,9 @@
664 <tbody>
665 <tal:non_selectable repeat="publishing batch"
666 replace="structure publishing/@@+listing-archive-detailed"
667- condition="not:view/selectable_sources">
668- </tal:non_selectable>
669- <tal:selectable condition="view/selectable_sources">
670+ condition="not:view/selectable_packages">
671+ </tal:non_selectable>
672+ <tal:selectable condition="view/selectable_packages">
673 <tal:each_term define="widget nocall:view/widgets/selected_sources"
674 repeat="term widget/vocabulary">
675 <tal:block