Merge lp:~salgado/launchpad/request-to-base-template-adapter into lp:launchpad/db-devel
- request-to-base-template-adapter
- Merge into db-devel
Proposed by
Guilherme Salgado
Status: | Merged |
---|---|
Merged at revision: | 9654 |
Proposed branch: | lp:~salgado/launchpad/request-to-base-template-adapter |
Merge into: | lp:launchpad/db-devel |
Prerequisite: | lp:~mwhudson/launchpad/vostok-traverse-distro |
Diff against target: |
2703 lines (+943/-259) 46 files modified
.bzrignore (+1/-1) lib/canonical/buildd/debian/changelog (+11/-1) lib/canonical/buildd/debian/compat (+1/-0) lib/canonical/buildd/debian/control (+4/-4) lib/canonical/buildd/debian/launchpad-buildd.examples (+1/-0) lib/canonical/buildd/debian/launchpad-buildd.init (+10/-0) lib/canonical/buildd/debian/rules (+4/-3) lib/canonical/buildd/debian/source/format (+1/-0) lib/canonical/launchpad/browser/multistep.py (+19/-0) lib/canonical/launchpad/doc/tales-macro.txt (+3/-1) lib/canonical/launchpad/emailtemplates/product-other-license.txt (+3/-2) lib/canonical/launchpad/webapp/configure.zcml (+4/-0) lib/canonical/launchpad/webapp/launchpadform.py (+3/-0) lib/canonical/launchpad/webapp/tales.py (+22/-3) lib/canonical/launchpad/webapp/tests/test_base_template.py (+29/-0) lib/canonical/widgets/product.py (+1/-0) lib/canonical/widgets/templates/license.pt (+23/-5) lib/lp/archivepublisher/config.py (+19/-14) lib/lp/archivepublisher/ftparchive.py (+11/-3) lib/lp/archivepublisher/tests/test_config.py (+19/-9) lib/lp/archivepublisher/tests/test_ftparchive.py (+18/-2) lib/lp/archiveuploader/tests/__init__.py (+9/-3) lib/lp/archiveuploader/tests/test_buildduploads.py (+2/-3) lib/lp/archiveuploader/tests/test_ppauploadprocessor.py (+2/-5) lib/lp/archiveuploader/tests/test_recipeuploads.py (+2/-3) lib/lp/archiveuploader/tests/test_securityuploads.py (+3/-7) lib/lp/archiveuploader/tests/test_uploadprocessor.py (+78/-78) lib/lp/archiveuploader/uploadprocessor.py (+38/-28) lib/lp/registry/browser/product.py (+111/-12) lib/lp/registry/browser/sourcepackage.py (+50/-4) lib/lp/registry/browser/tests/project-add-views.txt (+23/-23) lib/lp/registry/browser/tests/sourcepackage-views.txt (+14/-3) lib/lp/registry/browser/tests/test_sourcepackage_views.py (+146/-0) lib/lp/registry/model/sourcepackage.py (+0/-2) lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt (+103/-1) lib/lp/registry/templates/product-new.pt (+4/-4) lib/lp/registry/templates/sourcepackage-edit-packaging.pt (+20/-0) lib/lp/soyuz/scripts/soyuz_process_upload.py (+8/-2) lib/lp/soyuz/tests/test_publishing.py (+65/-0) lib/lp/vostok/browser/configure.zcml (+2/-8) lib/lp/vostok/browser/root.py (+19/-1) lib/lp/vostok/browser/tests/test_base_template.py (+16/-18) lib/lp/vostok/browser/tests/test_root.py (+12/-1) lib/lp/vostok/templates/main-template.pt (+6/-3) lib/lp/vostok/templates/root.pt (+2/-2) utilities/sourcedeps.conf (+1/-0) |
To merge this branch: | bzr merge lp:~salgado/launchpad/request-to-base-template-adapter |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Edwin Grubbs (community) | Approve | ||
Michael Hudson-Doyle | Approve | ||
Review via email: mp+31982@code.launchpad.net |
Commit message
Description of the change
This branch makes it possible to use vostok's base template with any of Launchpad's templates.
It also makes all existing pages accessed using the vostok.dev vhost use vostok's base template.
To post a comment you must log in.
Revision history for this message
Guilherme Salgado (salgado) wrote : | # |
One thing I realized now is that we no longer need the main_template
view registered, so I got rid of that and changed the test to test the
right thing: http://
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote : | # |
That looks fine too :-)
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote : | # |
Hi Salgado,
This branch looks good, but I don't see a test like TestVostokLayer
-Edwin
review:
Approve
Revision history for this message
Guilherme Salgado (salgado) wrote : | # |
Fair enough; I've added one. Thanks for the review
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2010-07-15 15:57:40 +0000 |
3 | +++ .bzrignore 2010-08-12 22:16:35 +0000 |
4 | @@ -56,7 +56,6 @@ |
5 | .bazaar |
6 | .cache |
7 | .subversion |
8 | -lib/canonical/buildd/launchpad-files |
9 | .testrepository |
10 | .memcache.pid |
11 | ./pipes |
12 | @@ -77,3 +76,4 @@ |
13 | lib/canonical/launchpad-buildd_*_source.build |
14 | lib/canonical/launchpad-buildd_*_source.changes |
15 | lib/canonical/buildd/debian/* |
16 | +lib/canonical/buildd/launchpad-files/* |
17 | |
18 | === renamed file 'daemons/buildd-slave-example.conf' => 'lib/canonical/buildd/buildd-slave-example.conf' |
19 | === modified file 'lib/canonical/buildd/debian/changelog' |
20 | --- lib/canonical/buildd/debian/changelog 2010-08-05 21:12:36 +0000 |
21 | +++ lib/canonical/buildd/debian/changelog 2010-08-12 22:16:35 +0000 |
22 | @@ -1,9 +1,19 @@ |
23 | launchpad-buildd (68) UNRELEASED; urgency=low |
24 | |
25 | + [ William Grant ] |
26 | * Take an 'arch_tag' argument, so the master can override the slave |
27 | architecture. |
28 | |
29 | - -- William Grant <wgrant@ubuntu.com> Sun, 01 Aug 2010 22:00:32 +1000 |
30 | + [ Jelmer Vernooij ] |
31 | + |
32 | + * Explicitly use source format 1.0. |
33 | + * Add LSB information to init script. |
34 | + * Use debhelper >= 5 (available in dapper, not yet deprecated in |
35 | + maverick). |
36 | + * Fix spelling in description. |
37 | + * Install example buildd configuration. |
38 | + |
39 | + -- Jelmer Vernooij <jelmer@canonical.com> Thu, 12 Aug 2010 17:04:14 +0200 |
40 | |
41 | launchpad-buildd (67) hardy-cat; urgency=low |
42 | |
43 | |
44 | === added file 'lib/canonical/buildd/debian/compat' |
45 | --- lib/canonical/buildd/debian/compat 1970-01-01 00:00:00 +0000 |
46 | +++ lib/canonical/buildd/debian/compat 2010-08-12 22:16:35 +0000 |
47 | @@ -0,0 +1,1 @@ |
48 | +5 |
49 | |
50 | === modified file 'lib/canonical/buildd/debian/control' |
51 | --- lib/canonical/buildd/debian/control 2010-05-19 15:50:27 +0000 |
52 | +++ lib/canonical/buildd/debian/control 2010-08-12 22:16:35 +0000 |
53 | @@ -3,15 +3,15 @@ |
54 | Priority: extra |
55 | Maintainer: Adam Conrad <adconrad@ubuntu.com> |
56 | Standards-Version: 3.5.9 |
57 | -Build-Depends-Indep: debhelper (>= 4) |
58 | +Build-Depends-Indep: debhelper (>= 5) |
59 | |
60 | Package: launchpad-buildd |
61 | Section: misc |
62 | Architecture: all |
63 | -Depends: python-twisted-core, python-twisted-web, debootstrap, dpkg-dev, linux32, file, bzip2, sudo, ntpdate, adduser, apt-transport-https, lsb-release, apache2 |
64 | +Depends: python-twisted-core, python-twisted-web, debootstrap, dpkg-dev, linux32, file, bzip2, sudo, ntpdate, adduser, apt-transport-https, lsb-release, apache2, ${misc:Depends} |
65 | Description: Launchpad buildd slave |
66 | This is the launchpad buildd slave package. It contains everything needed to |
67 | get a launchpad buildd going apart from the database manipulation required to |
68 | tell launchpad about the slave instance. If you are creating more than one |
69 | - slave instance on the same computer, be sure to give them independant configs |
70 | - and independant filecaches etc. |
71 | + slave instance on the same computer, be sure to give them independent configs |
72 | + and independent filecaches etc. |
73 | |
74 | === added file 'lib/canonical/buildd/debian/launchpad-buildd.examples' |
75 | --- lib/canonical/buildd/debian/launchpad-buildd.examples 1970-01-01 00:00:00 +0000 |
76 | +++ lib/canonical/buildd/debian/launchpad-buildd.examples 2010-08-12 22:16:35 +0000 |
77 | @@ -0,0 +1,1 @@ |
78 | +buildd-slave-example.conf |
79 | |
80 | === modified file 'lib/canonical/buildd/debian/launchpad-buildd.init' |
81 | --- lib/canonical/buildd/debian/launchpad-buildd.init 2010-03-31 17:10:21 +0000 |
82 | +++ lib/canonical/buildd/debian/launchpad-buildd.init 2010-08-12 22:16:35 +0000 |
83 | @@ -8,6 +8,16 @@ |
84 | # |
85 | # Author: Daniel Silverstone <daniel.silverstone@canonical.com> |
86 | |
87 | +### BEGIN INIT INFO |
88 | +# Provides: launchpad_buildd |
89 | +# Required-Start: $local_fs $network $syslog $time |
90 | +# Required-Stop: $local_fs $network $syslog $time |
91 | +# Default-Start: 2 3 4 5 |
92 | +# Default-Stop: 0 1 6 |
93 | +# X-Interactive: false |
94 | +# Short-Description: Start/stop launchpad buildds |
95 | +### END INIT INFO |
96 | + |
97 | set -e |
98 | |
99 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin |
100 | |
101 | === modified file 'lib/canonical/buildd/debian/rules' |
102 | --- lib/canonical/buildd/debian/rules 2010-07-18 08:49:02 +0000 |
103 | +++ lib/canonical/buildd/debian/rules 2010-08-12 22:16:35 +0000 |
104 | @@ -3,7 +3,6 @@ |
105 | # Copyright 2009, 2010 Canonical Ltd. This software is licensed under the |
106 | # GNU Affero General Public License version 3 (see the file LICENSE). |
107 | |
108 | -export DH_COMPAT=4 |
109 | export DH_OPTIONS |
110 | |
111 | # This is an incomplete debian rules file for making the launchpad-buildd deb |
112 | @@ -41,6 +40,7 @@ |
113 | etc/launchpad-buildd \ |
114 | usr/share/launchpad-buildd/canonical/launchpad/daemons \ |
115 | usr/share/doc/launchpad-buildd |
116 | + dh_installexamples |
117 | |
118 | # Do installs here |
119 | touch $(pytarget)/../launchpad/__init__.py |
120 | @@ -89,10 +89,11 @@ |
121 | clean: |
122 | dh_clean |
123 | |
124 | -package: |
125 | - mkdir -p launchpad-files |
126 | +prepare: |
127 | install -m644 $(daemons)/buildd-slave.tac launchpad-files/buildd-slave.tac |
128 | cp ../launchpad/daemons/tachandler.py launchpad-files/tachandler.py |
129 | + |
130 | +package: prepare |
131 | debuild -uc -us -S |
132 | |
133 | build: |
134 | |
135 | === added directory 'lib/canonical/buildd/debian/source' |
136 | === added file 'lib/canonical/buildd/debian/source/format' |
137 | --- lib/canonical/buildd/debian/source/format 1970-01-01 00:00:00 +0000 |
138 | +++ lib/canonical/buildd/debian/source/format 2010-08-12 22:16:35 +0000 |
139 | @@ -0,0 +1,1 @@ |
140 | +1.0 |
141 | |
142 | === added directory 'lib/canonical/buildd/launchpad-files' |
143 | === modified file 'lib/canonical/launchpad/browser/multistep.py' |
144 | --- lib/canonical/launchpad/browser/multistep.py 2010-05-12 19:06:17 +0000 |
145 | +++ lib/canonical/launchpad/browser/multistep.py 2010-08-12 22:16:35 +0000 |
146 | @@ -93,6 +93,14 @@ |
147 | view.total_steps = self.total_steps |
148 | view.is_step = self.getIsStepDict() |
149 | self.step_number += 1 |
150 | + |
151 | + action_required = None |
152 | + for name in self.request.form.keys(): |
153 | + if name.startswith('field.actions.'): |
154 | + action_required = (name, self.request.form[name]) |
155 | + break |
156 | + |
157 | + action_taken = view.action_taken |
158 | while view.next_step is not None: |
159 | view = view.next_step(self.context, self.request) |
160 | assert isinstance(view, StepView), 'Not a StepView: %s' % view |
161 | @@ -102,8 +110,19 @@ |
162 | view.is_step = self.getIsStepDict() |
163 | self.step_number += 1 |
164 | view.injectStepNameInRequest() |
165 | + if view.action_taken is not None: |
166 | + action_taken = view.action_taken |
167 | + |
168 | self.view = view |
169 | |
170 | + if action_required is not None and action_taken is None: |
171 | + # This is mostly useful for catching tests that pass |
172 | + # in invalid form data via a dictionary instead of |
173 | + # using a test browser. |
174 | + raise AssertionError( |
175 | + 'MultiStepView did not find action for %s=%r' |
176 | + % action_required) |
177 | + |
178 | def render(self): |
179 | return self.view.render() |
180 | |
181 | |
182 | === modified file 'lib/canonical/launchpad/doc/tales-macro.txt' |
183 | --- lib/canonical/launchpad/doc/tales-macro.txt 2009-11-23 03:10:04 +0000 |
184 | +++ lib/canonical/launchpad/doc/tales-macro.txt 2010-08-12 22:16:35 +0000 |
185 | @@ -3,8 +3,9 @@ |
186 | Launchpad has a 'macro:' TALES namespace that offers controls over the |
187 | layout of the page. |
188 | |
189 | + >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest |
190 | >>> class FakeView(object): |
191 | - ... pass |
192 | + ... request = LaunchpadTestRequest() |
193 | |
194 | Templates should start by specifying the kind of pagetype they use. |
195 | That's done by using the 'macro:page' traversal. That expression returns |
196 | @@ -50,6 +51,7 @@ |
197 | returns False for older views that do not. |
198 | |
199 | >>> class LPView(object): |
200 | + ... request = LaunchpadTestRequest() |
201 | ... def isBetaUser(self): |
202 | ... return True |
203 | |
204 | |
205 | === modified file 'lib/canonical/launchpad/emailtemplates/product-other-license.txt' |
206 | --- lib/canonical/launchpad/emailtemplates/product-other-license.txt 2010-05-12 19:06:17 +0000 |
207 | +++ lib/canonical/launchpad/emailtemplates/product-other-license.txt 2010-08-12 22:16:35 +0000 |
208 | @@ -28,8 +28,9 @@ |
209 | questions. |
210 | |
211 | Sometimes new projects are licensed as 'Other/Open Source' because the |
212 | -licensing decisions have not yet been made. If that is your situation we urge |
213 | -you to update the licensing in Launchpad as soon as you make that choice. |
214 | +licensing decisions have not yet been made. If that is your situation |
215 | +we urge you to update the licensing in Launchpad as soon as you make |
216 | +that choice. |
217 | |
218 | If the license for your project needs to be corrected you can do so by |
219 | following the 'Change Details' link on your project's overview page. |
220 | |
221 | === modified file 'lib/canonical/launchpad/webapp/configure.zcml' |
222 | --- lib/canonical/launchpad/webapp/configure.zcml 2010-07-23 13:24:58 +0000 |
223 | +++ lib/canonical/launchpad/webapp/configure.zcml 2010-08-12 22:16:35 +0000 |
224 | @@ -711,6 +711,10 @@ |
225 | /> |
226 | |
227 | <adapter |
228 | + factory="canonical.launchpad.webapp.tales.LaunchpadLayerToMainTemplateAdapter" |
229 | + /> |
230 | + |
231 | + <adapter |
232 | factory="canonical.launchpad.webapp.snapshot.snapshot_sql_result" /> |
233 | <!-- It also works for the legacy SQLObject interface. --> |
234 | <adapter |
235 | |
236 | === modified file 'lib/canonical/launchpad/webapp/launchpadform.py' |
237 | --- lib/canonical/launchpad/webapp/launchpadform.py 2010-06-23 23:07:10 +0000 |
238 | +++ lib/canonical/launchpad/webapp/launchpadform.py 2010-08-12 22:16:35 +0000 |
239 | @@ -74,6 +74,8 @@ |
240 | |
241 | actions = () |
242 | |
243 | + action_taken = None |
244 | + |
245 | render_context = False |
246 | |
247 | form_result = None |
248 | @@ -112,6 +114,7 @@ |
249 | self.form_result = action.success(data) |
250 | if self.next_url: |
251 | self.request.response.redirect(self.next_url) |
252 | + self.action_taken = action |
253 | |
254 | def render(self): |
255 | """Return the body of the response. |
256 | |
257 | === modified file 'lib/canonical/launchpad/webapp/tales.py' |
258 | --- lib/canonical/launchpad/webapp/tales.py 2010-07-30 06:08:54 +0000 |
259 | +++ lib/canonical/launchpad/webapp/tales.py 2010-08-12 22:16:35 +0000 |
260 | @@ -22,7 +22,7 @@ |
261 | from lazr.uri import URI |
262 | |
263 | from zope.interface import Interface, Attribute, implements |
264 | -from zope.component import getUtility, queryAdapter, getMultiAdapter |
265 | +from zope.component import adapts, getUtility, queryAdapter, getMultiAdapter |
266 | from zope.app import zapi |
267 | from zope.publisher.browser import BrowserView |
268 | from zope.publisher.interfaces import IApplicationRequest |
269 | @@ -31,6 +31,7 @@ |
270 | ITraversable, IPathAdapter, TraversalError) |
271 | from zope.security.interfaces import Unauthorized |
272 | from zope.security.proxy import isinstance as zope_isinstance |
273 | +from zope.schema import TextLine |
274 | |
275 | import pytz |
276 | from z3c.ptcompat import ViewPageTemplateFile |
277 | @@ -41,6 +42,7 @@ |
278 | ISprint, LicenseStatus) |
279 | from canonical.launchpad.interfaces.launchpad import ( |
280 | IHasIcon, IHasLogo, IHasMugshot, IPrivacy) |
281 | +from canonical.launchpad.layers import LaunchpadLayer |
282 | import canonical.launchpad.pagetitles |
283 | from canonical.launchpad.webapp import canonical_url, urlappend |
284 | from canonical.launchpad.webapp.authorization import check_permission |
285 | @@ -2297,6 +2299,20 @@ |
286 | return check_permission(name, self.context) |
287 | |
288 | |
289 | +class IMainTemplateFile(Interface): |
290 | + path = TextLine(title=u'The absolute path to this main template.') |
291 | + |
292 | + |
293 | +class LaunchpadLayerToMainTemplateAdapter: |
294 | + adapts(LaunchpadLayer) |
295 | + implements(IMainTemplateFile) |
296 | + |
297 | + def __init__(self, context): |
298 | + here = os.path.dirname(os.path.realpath(__file__)) |
299 | + self.path = os.path.join( |
300 | + here, '../../../lp/app/templates/base-layout.pt') |
301 | + |
302 | + |
303 | class PageMacroDispatcher: |
304 | """Selects a macro, while storing information about page layout. |
305 | |
306 | @@ -2316,12 +2332,15 @@ |
307 | |
308 | implements(ITraversable) |
309 | |
310 | - base = ViewPageTemplateFile('../../../lp/app/templates/base-layout.pt') |
311 | - |
312 | def __init__(self, context): |
313 | # The context of this object is a view object. |
314 | self.context = context |
315 | |
316 | + @property |
317 | + def base(self): |
318 | + return ViewPageTemplateFile( |
319 | + IMainTemplateFile(self.context.request).path) |
320 | + |
321 | def traverse(self, name, furtherPath): |
322 | if name == 'page': |
323 | if len(furtherPath) == 1: |
324 | |
325 | === added file 'lib/canonical/launchpad/webapp/tests/test_base_template.py' |
326 | --- lib/canonical/launchpad/webapp/tests/test_base_template.py 1970-01-01 00:00:00 +0000 |
327 | +++ lib/canonical/launchpad/webapp/tests/test_base_template.py 2010-08-12 22:16:35 +0000 |
328 | @@ -0,0 +1,29 @@ |
329 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
330 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
331 | + |
332 | +"""Tests for Launchpad's 'view/macro:page' TALES adapter.""" |
333 | + |
334 | +__metaclass__ = type |
335 | + |
336 | +from zope.component import getMultiAdapter |
337 | +from zope.traversing.interfaces import IPathAdapter |
338 | + |
339 | +from canonical.launchpad.webapp.publisher import rootObject |
340 | +from canonical.launchpad.webapp.servers import LaunchpadTestRequest |
341 | +from canonical.testing.layers import FunctionalLayer |
342 | + |
343 | +from lp.testing import TestCase |
344 | + |
345 | + |
346 | +class TestPageMacroDispatcher(TestCase): |
347 | + |
348 | + layer = FunctionalLayer |
349 | + |
350 | + def test_base_template(self): |
351 | + # Requests on the launchpad.dev vhost use the Launchpad base template. |
352 | + root_view = getMultiAdapter( |
353 | + (rootObject, LaunchpadTestRequest()), name='index.html') |
354 | + adapter = getMultiAdapter([root_view], IPathAdapter, name='macro') |
355 | + self.assertIn('lp/app/templates', adapter.base.filename) |
356 | + # The base template defines a 'master' macro as the adapter expects. |
357 | + self.assertIn('master', adapter.base.macros.keys()) |
358 | |
359 | === modified file 'lib/canonical/widgets/product.py' |
360 | --- lib/canonical/widgets/product.py 2010-06-21 04:08:54 +0000 |
361 | +++ lib/canonical/widgets/product.py 2010-08-12 22:16:35 +0000 |
362 | @@ -316,6 +316,7 @@ |
363 | self, 'license_info', self.license_info, IInputWidget, |
364 | prefix='field', value=initial_value, |
365 | context=field.context) |
366 | + self.source_package_release = None |
367 | # These will get filled in by _categorize(). They are the number of |
368 | # selected licenses in the category. The actual count doesn't matter, |
369 | # since if it's greater than 0 it will start opened. NOte that we |
370 | |
371 | === modified file 'lib/canonical/widgets/templates/license.pt' |
372 | --- lib/canonical/widgets/templates/license.pt 2009-07-17 17:59:07 +0000 |
373 | +++ lib/canonical/widgets/templates/license.pt 2010-08-12 22:16:35 +0000 |
374 | @@ -42,7 +42,7 @@ |
375 | // the slider depends on whether there are any checked |
376 | // licenses in that category. A '0' means 'no'. |
377 | var arrow = Y.get(arrow_name); |
378 | - if (arrow.getAttribute('count') == '0') { |
379 | + if (arrow.getAttribute('start_expanded') == '0') { |
380 | target.slide = Y.lazr.effects.slide_in(table_name); |
381 | } |
382 | else { |
383 | @@ -76,6 +76,7 @@ |
384 | target.slide.run(); |
385 | }, target_name); |
386 | } |
387 | + make_slider({which: 'copyright'}); |
388 | make_slider({which: 'recommended'}); |
389 | make_slider({which: 'more'}); |
390 | make_slider({which: 'deprecated'}); |
391 | @@ -136,6 +137,23 @@ |
392 | //]]> |
393 | </script> |
394 | <div style="color: black"> |
395 | + <tal:copyright condition="view/source_package_release"> |
396 | + <a href="" id="copyright-expand" class="js-action"> |
397 | + <img id="copyright-expand-arrow" |
398 | + src="/@@/treeCollapsed" |
399 | + title="Copyright info from source package" |
400 | + alt="Copyright info from source package" |
401 | + start_expanded="0"/> |
402 | + Copyright info from source package |
403 | + </a> |
404 | + <div id="copyright"> |
405 | + <div |
406 | + tal:content="structure view/source_package_release/@@+copyright" |
407 | + style="overflow-x: hidden; overflow-y: auto; |
408 | + max-width: 60em; max-height: 32em; background: #f7f7f7" |
409 | + /> |
410 | + </div> |
411 | + </tal:copyright> |
412 | |
413 | Select the license(s) under which you release your project. |
414 | <div tal:condition="view/allow_pending_license" |
415 | @@ -159,7 +177,7 @@ |
416 | src="/@@/treeExpanded" |
417 | title="Recommended open source licenses" |
418 | alt="Recommended open source licenses" |
419 | - tal:attributes="count view/recommended_count"/> |
420 | + tal:attributes="start_expanded view/recommended_count"/> |
421 | Recommended open source licenses |
422 | </a> |
423 | <input tal:replace="structure view/recommended" /> |
424 | @@ -168,7 +186,7 @@ |
425 | src="/@@/treeCollapsed" |
426 | title="More open source licenses" |
427 | alt="More open source licenses" |
428 | - tal:attributes="count view/more_count"/> |
429 | + tal:attributes="start_expanded view/more_count"/> |
430 | More open source licenses |
431 | </a> |
432 | <input tal:replace="structure view/more" /> |
433 | @@ -178,7 +196,7 @@ |
434 | src="/@@/treeCollapsed" |
435 | title="Deprecated licenses" |
436 | alt="Deprecated licenses" |
437 | - tal:attributes="count view/deprecated_count"/> |
438 | + tal:attributes="start_expanded view/deprecated_count"/> |
439 | Deprecated licenses |
440 | </a> |
441 | <input tal:replace="structure view/deprecated" /> |
442 | @@ -188,7 +206,7 @@ |
443 | src="/@@/treeCollapsed" |
444 | title="Other choices" |
445 | alt="Other choices" |
446 | - tal:attributes="count view/special_count"/> |
447 | + tal:attributes="start_expanded view/special_count"/> |
448 | Other choices |
449 | </a> |
450 | <input tal:replace="structure view/special" /> |
451 | |
452 | === added symlink 'lib/deb822.py' |
453 | === target is u'../sourcecode/python-debian/lib/deb822.py' |
454 | === added symlink 'lib/debian' |
455 | === target is u'../sourcecode/python-debian/lib/debian' |
456 | === modified file 'lib/lp/archivepublisher/config.py' |
457 | --- lib/lp/archivepublisher/config.py 2010-07-07 06:28:03 +0000 |
458 | +++ lib/lp/archivepublisher/config.py 2010-08-12 22:16:35 +0000 |
459 | @@ -120,18 +120,15 @@ |
460 | config_segment["archtags"].append( |
461 | dar.architecturetag.encode('utf-8')) |
462 | |
463 | - if not dr.lucilleconfig: |
464 | - raise LucilleConfigError( |
465 | - 'No Lucille configuration section for %s' % dr.name) |
466 | - |
467 | - strio = StringIO(dr.lucilleconfig.encode('utf-8')) |
468 | - config_segment["config"] = ConfigParser() |
469 | - config_segment["config"].readfp(strio) |
470 | - strio.close() |
471 | - config_segment["components"] = config_segment["config"].get( |
472 | - "publishing", "components").split(" ") |
473 | - |
474 | - self._distroseries[distroseries_name] = config_segment |
475 | + if dr.lucilleconfig: |
476 | + strio = StringIO(dr.lucilleconfig.encode('utf-8')) |
477 | + config_segment["config"] = ConfigParser() |
478 | + config_segment["config"].readfp(strio) |
479 | + strio.close() |
480 | + config_segment["components"] = config_segment["config"].get( |
481 | + "publishing", "components").split(" ") |
482 | + |
483 | + self._distroseries[distroseries_name] = config_segment |
484 | |
485 | strio = StringIO(distribution.lucilleconfig.encode('utf-8')) |
486 | self._distroconfig = ConfigParser() |
487 | @@ -144,11 +141,19 @@ |
488 | # Because dicts iterate for keys only; this works to get dr names |
489 | return self._distroseries.keys() |
490 | |
491 | + def series(self, dr): |
492 | + try: |
493 | + return self._distroseries[dr] |
494 | + except KeyError: |
495 | + raise LucilleConfigError( |
496 | + 'No Lucille config section for %s in %s' % |
497 | + (dr, self.distroName)) |
498 | + |
499 | def archTagsForSeries(self, dr): |
500 | - return self._distroseries[dr]["archtags"] |
501 | + return self.series(dr)["archtags"] |
502 | |
503 | def componentsForSeries(self, dr): |
504 | - return self._distroseries[dr]["components"] |
505 | + return self.series(dr)["components"] |
506 | |
507 | def _extractConfigInfo(self): |
508 | """Extract configuration information into the attributes we use""" |
509 | |
510 | === modified file 'lib/lp/archivepublisher/ftparchive.py' |
511 | --- lib/lp/archivepublisher/ftparchive.py 2009-10-26 18:40:04 +0000 |
512 | +++ lib/lp/archivepublisher/ftparchive.py 2010-08-12 22:16:35 +0000 |
513 | @@ -138,6 +138,14 @@ |
514 | self._config = config |
515 | self._diskpool = diskpool |
516 | self.distro = distro |
517 | + self.distroseries = [] |
518 | + for distroseries in self.distro.series: |
519 | + if not distroseries.name in self._config.distroSeriesNames(): |
520 | + self.log.warning("Distroseries %s in %s doesn't have " |
521 | + "a lucille configuration.", distroseries.name, |
522 | + self.distro.name) |
523 | + else: |
524 | + self.distroseries.append(distroseries) |
525 | self.publisher = publisher |
526 | self.release_files_needed = {} |
527 | |
528 | @@ -185,7 +193,7 @@ |
529 | # iterate over the pockets, and do the suffix check inside |
530 | # createEmptyPocketRequest; that would also allow us to replace |
531 | # the == "" check we do there by a RELEASE match |
532 | - for distroseries in self.distro: |
533 | + for distroseries in self.distroseries: |
534 | components = self._config.componentsForSeries(distroseries.name) |
535 | for pocket, suffix in pocketsuffix.items(): |
536 | if not fullpublish: |
537 | @@ -366,7 +374,7 @@ |
538 | |
539 | def generateOverrides(self, fullpublish=False): |
540 | """Collect packages that need overrides, and generate them.""" |
541 | - for distroseries in self.distro.series: |
542 | + for distroseries in self.distroseries: |
543 | for pocket in PackagePublishingPocket.items: |
544 | if not fullpublish: |
545 | if not self.publisher.isDirty(distroseries, pocket): |
546 | @@ -629,7 +637,7 @@ |
547 | |
548 | def generateFileLists(self, fullpublish=False): |
549 | """Collect currently published FilePublishings and write filelists.""" |
550 | - for distroseries in self.distro.series: |
551 | + for distroseries in self.distroseries: |
552 | for pocket in pocketsuffix: |
553 | if not fullpublish: |
554 | if not self.publisher.isDirty(distroseries, pocket): |
555 | |
556 | === modified file 'lib/lp/archivepublisher/tests/test_config.py' |
557 | --- lib/lp/archivepublisher/tests/test_config.py 2010-07-18 00:24:06 +0000 |
558 | +++ lib/lp/archivepublisher/tests/test_config.py 2010-08-12 22:16:35 +0000 |
559 | @@ -5,36 +5,48 @@ |
560 | |
561 | __metaclass__ = type |
562 | |
563 | -import unittest |
564 | - |
565 | from zope.component import getUtility |
566 | |
567 | from canonical.config import config |
568 | from canonical.launchpad.interfaces import IDistributionSet |
569 | from canonical.testing import LaunchpadZopelessLayer |
570 | |
571 | - |
572 | -class TestConfig(unittest.TestCase): |
573 | +from lp.archivepublisher.config import Config, LucilleConfigError |
574 | +from lp.testing import TestCaseWithFactory |
575 | + |
576 | + |
577 | +class TestConfig(TestCaseWithFactory): |
578 | layer = LaunchpadZopelessLayer |
579 | |
580 | def setUp(self): |
581 | + super(TestConfig, self).setUp() |
582 | self.layer.switchDbUser(config.archivepublisher.dbuser) |
583 | self.ubuntutest = getUtility(IDistributionSet)['ubuntutest'] |
584 | |
585 | + def testMissingDistroSeries(self): |
586 | + distroseries = self.factory.makeDistroSeries( |
587 | + distribution=self.ubuntutest, name="somename") |
588 | + d = Config(self.ubuntutest) |
589 | + dsns = d.distroSeriesNames() |
590 | + self.assertEquals(len(dsns), 2) |
591 | + self.assertEquals(dsns[0], "breezy-autotest") |
592 | + self.assertEquals(dsns[1], "hoary-test") |
593 | + self.assertRaises(LucilleConfigError, |
594 | + d.archTagsForSeries, "somename") |
595 | + self.assertRaises(LucilleConfigError, |
596 | + d.archTagsForSeries, "unknown") |
597 | + |
598 | def testInstantiate(self): |
599 | """Config should instantiate""" |
600 | - from lp.archivepublisher.config import Config |
601 | d = Config(self.ubuntutest) |
602 | |
603 | def testDistroName(self): |
604 | """Config should be able to return the distroName""" |
605 | - from lp.archivepublisher.config import Config |
606 | d = Config(self.ubuntutest) |
607 | self.assertEqual(d.distroName, "ubuntutest") |
608 | |
609 | def testDistroSeriesNames(self): |
610 | """Config should return two distroseries names""" |
611 | - from lp.archivepublisher.config import Config |
612 | d = Config(self.ubuntutest) |
613 | dsns = d.distroSeriesNames() |
614 | self.assertEquals(len(dsns), 2) |
615 | @@ -43,14 +55,12 @@ |
616 | |
617 | def testArchTagsForSeries(self): |
618 | """Config should have the arch tags for the drs""" |
619 | - from lp.archivepublisher.config import Config |
620 | d = Config(self.ubuntutest) |
621 | archs = d.archTagsForSeries("hoary-test") |
622 | self.assertEquals(len(archs), 2) |
623 | |
624 | def testDistroConfig(self): |
625 | """Config should have parsed a distro config""" |
626 | - from lp.archivepublisher.config import Config |
627 | d = Config(self.ubuntutest) |
628 | # NOTE: Add checks here when you add stuff in util.py |
629 | self.assertEquals(d.stayofexecution, 5) |
630 | |
631 | === modified file 'lib/lp/archivepublisher/tests/test_ftparchive.py' |
632 | --- lib/lp/archivepublisher/tests/test_ftparchive.py 2010-07-18 00:24:06 +0000 |
633 | +++ lib/lp/archivepublisher/tests/test_ftparchive.py 2010-08-12 22:16:35 +0000 |
634 | @@ -15,7 +15,7 @@ |
635 | from zope.component import getUtility |
636 | |
637 | from canonical.config import config |
638 | -from canonical.launchpad.scripts.logger import QuietFakeLogger |
639 | +from canonical.launchpad.scripts.logger import BufferLogger, QuietFakeLogger |
640 | from canonical.testing import LaunchpadZopelessLayer |
641 | from lp.archivepublisher.config import Config |
642 | from lp.archivepublisher.diskpool import DiskPool |
643 | @@ -23,6 +23,7 @@ |
644 | from lp.archivepublisher.publishing import Publisher |
645 | from lp.registry.interfaces.distribution import IDistributionSet |
646 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
647 | +from lp.testing import TestCaseWithFactory |
648 | |
649 | |
650 | def sanitize_apt_ftparchive_Sources_output(text): |
651 | @@ -55,10 +56,11 @@ |
652 | return self._result[i:j] |
653 | |
654 | |
655 | -class TestFTPArchive(unittest.TestCase): |
656 | +class TestFTPArchive(TestCaseWithFactory): |
657 | layer = LaunchpadZopelessLayer |
658 | |
659 | def setUp(self): |
660 | + super(TestFTPArchive, self).setUp() |
661 | self.layer.switchDbUser(config.archivepublisher.dbuser) |
662 | |
663 | self._distribution = getUtility(IDistributionSet)['ubuntutest'] |
664 | @@ -79,6 +81,7 @@ |
665 | self._publisher = SamplePublisher(self._archive) |
666 | |
667 | def tearDown(self): |
668 | + super(TestFTPArchive, self).tearDown() |
669 | shutil.rmtree(self._config.distroroot) |
670 | |
671 | def _verifyFile(self, filename, directory, output_filter=None): |
672 | @@ -116,6 +119,19 @@ |
673 | self._publisher) |
674 | return fa |
675 | |
676 | + def test_NoLucilleConfig(self): |
677 | + # Distroseries without a lucille configuration get ignored |
678 | + # and trigger a warning, they don't break the publisher |
679 | + logger = BufferLogger() |
680 | + publisher = Publisher( |
681 | + logger, self._config, self._dp, self._archive) |
682 | + self.factory.makeDistroSeries(self._distribution, name="somename") |
683 | + fa = FTPArchiveHandler(logger, self._config, self._dp, |
684 | + self._distribution, publisher) |
685 | + fa.createEmptyPocketRequests(fullpublish=True) |
686 | + self.assertEquals("WARNING: Distroseries somename in ubuntutest doesn't " |
687 | + "have a lucille configuration.\n", logger.buffer.getvalue()) |
688 | + |
689 | def test_getSourcesForOverrides(self): |
690 | # getSourcesForOverrides returns a list of tuples containing: |
691 | # (sourcename, suite, component, section) |
692 | |
693 | === modified file 'lib/lp/archiveuploader/tests/__init__.py' |
694 | --- lib/lp/archiveuploader/tests/__init__.py 2010-05-04 15:38:08 +0000 |
695 | +++ lib/lp/archiveuploader/tests/__init__.py 2010-08-12 22:16:35 +0000 |
696 | @@ -1,6 +1,10 @@ |
697 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
698 | # GNU Affero General Public License version 3 (see the file LICENSE). |
699 | |
700 | +"""Tests for the archive uploader.""" |
701 | + |
702 | +from __future__ import with_statement |
703 | + |
704 | __metaclass__ = type |
705 | |
706 | __all__ = ['datadir', 'getPolicy', 'insertFakeChangesFile', |
707 | @@ -25,6 +29,7 @@ |
708 | raise ValueError("Path is not relative: %s" % path) |
709 | return os.path.join(here, 'data', path) |
710 | |
711 | + |
712 | def insertFakeChangesFile(fileID, path=None): |
713 | """Insert a fake changes file into the librarian. |
714 | |
715 | @@ -34,11 +39,11 @@ |
716 | """ |
717 | if path is None: |
718 | path = datadir("ed-0.2-21/ed_0.2-21_source.changes") |
719 | - changes_file_obj = open(path, 'r') |
720 | - test_changes_file = changes_file_obj.read() |
721 | - changes_file_obj.close() |
722 | + with open(path, 'r') as changes_file_obj: |
723 | + test_changes_file = changes_file_obj.read() |
724 | fillLibrarianFile(fileID, content=test_changes_file) |
725 | |
726 | + |
727 | def insertFakeChangesFileForAllPackageUploads(): |
728 | """Ensure all the PackageUpload records point to a valid changes file.""" |
729 | for id in set(pu.changesfile.id for pu in PackageUploadSet()): |
730 | @@ -53,6 +58,7 @@ |
731 | self.distroseries = distroseries |
732 | self.buildid = buildid |
733 | |
734 | + |
735 | def getPolicy(name='anything', distro='ubuntu', distroseries=None, |
736 | buildid=None): |
737 | """Build and return an Upload Policy for the given context.""" |
738 | |
739 | === modified file 'lib/lp/archiveuploader/tests/test_buildduploads.py' |
740 | --- lib/lp/archiveuploader/tests/test_buildduploads.py 2010-07-18 00:26:33 +0000 |
741 | +++ lib/lp/archiveuploader/tests/test_buildduploads.py 2010-08-12 22:16:35 +0000 |
742 | @@ -7,7 +7,6 @@ |
743 | |
744 | from lp.archiveuploader.tests.test_securityuploads import ( |
745 | TestStagedBinaryUploadBase) |
746 | -from lp.archiveuploader.uploadprocessor import UploadProcessor |
747 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
748 | from canonical.database.constants import UTC_NOW |
749 | from canonical.launchpad.interfaces import PackagePublishingStatus |
750 | @@ -84,8 +83,8 @@ |
751 | """Setup an UploadProcessor instance for a given buildd context.""" |
752 | self.options.context = self.policy |
753 | self.options.buildid = str(build_candidate.id) |
754 | - self.uploadprocessor = UploadProcessor( |
755 | - self.options, self.layer.txn, self.log) |
756 | + self.uploadprocessor = self.getUploadProcessor( |
757 | + self.layer.txn) |
758 | |
759 | def testDelayedBinaryUpload(self): |
760 | """Check if Soyuz copes with delayed binary uploads. |
761 | |
762 | === modified file 'lib/lp/archiveuploader/tests/test_ppauploadprocessor.py' |
763 | --- lib/lp/archiveuploader/tests/test_ppauploadprocessor.py 2010-08-02 02:13:52 +0000 |
764 | +++ lib/lp/archiveuploader/tests/test_ppauploadprocessor.py 2010-08-12 22:16:35 +0000 |
765 | @@ -18,7 +18,6 @@ |
766 | from zope.security.proxy import removeSecurityProxy |
767 | |
768 | from lp.app.errors import NotFoundError |
769 | -from lp.archiveuploader.uploadprocessor import UploadProcessor |
770 | from lp.archiveuploader.tests.test_uploadprocessor import ( |
771 | TestUploadProcessorBase) |
772 | from canonical.config import config |
773 | @@ -74,8 +73,7 @@ |
774 | |
775 | # Set up the uploadprocessor with appropriate options and logger |
776 | self.options.context = 'insecure' |
777 | - self.uploadprocessor = UploadProcessor( |
778 | - self.options, self.layer.txn, self.log) |
779 | + self.uploadprocessor = self.getUploadProcessor(self.layer.txn) |
780 | |
781 | def assertEmail(self, contents=None, recipients=None, |
782 | ppa_header='name16'): |
783 | @@ -1224,8 +1222,7 @@ |
784 | |
785 | # Re-initialize uploadprocessor since it depends on the new |
786 | # transaction reset by switchDbUser. |
787 | - self.uploadprocessor = UploadProcessor( |
788 | - self.options, self.layer.txn, self.log) |
789 | + self.uploadprocessor = self.getUploadProcessor(self.layer.txn) |
790 | |
791 | def testPPASizeQuotaSourceRejection(self): |
792 | """Verify the size quota check for PPA uploads. |
793 | |
794 | === modified file 'lib/lp/archiveuploader/tests/test_recipeuploads.py' |
795 | --- lib/lp/archiveuploader/tests/test_recipeuploads.py 2010-07-22 15:24:02 +0000 |
796 | +++ lib/lp/archiveuploader/tests/test_recipeuploads.py 2010-08-12 22:16:35 +0000 |
797 | @@ -12,7 +12,6 @@ |
798 | |
799 | from lp.archiveuploader.tests.test_uploadprocessor import ( |
800 | TestUploadProcessorBase) |
801 | -from lp.archiveuploader.uploadprocessor import UploadProcessor |
802 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
803 | from lp.code.interfaces.sourcepackagerecipebuild import ( |
804 | ISourcePackageRecipeBuildSource) |
805 | @@ -42,8 +41,8 @@ |
806 | self.options.context = 'recipe' |
807 | self.options.buildid = self.build.id |
808 | |
809 | - self.uploadprocessor = UploadProcessor( |
810 | - self.options, self.layer.txn, self.log) |
811 | + self.uploadprocessor = self.getUploadProcessor( |
812 | + self.layer.txn) |
813 | |
814 | def testSetsBuildAndState(self): |
815 | # Ensure that the upload processor correctly links the SPR to |
816 | |
817 | === modified file 'lib/lp/archiveuploader/tests/test_securityuploads.py' |
818 | --- lib/lp/archiveuploader/tests/test_securityuploads.py 2010-07-18 00:26:33 +0000 |
819 | +++ lib/lp/archiveuploader/tests/test_securityuploads.py 2010-08-12 22:16:35 +0000 |
820 | @@ -11,7 +11,6 @@ |
821 | |
822 | from lp.archiveuploader.tests.test_uploadprocessor import ( |
823 | TestUploadProcessorBase) |
824 | -from lp.archiveuploader.uploadprocessor import UploadProcessor |
825 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
826 | from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild |
827 | from lp.soyuz.model.processor import ProcessorFamily |
828 | @@ -70,8 +69,7 @@ |
829 | self.options.context = self.policy |
830 | self.options.nomails = self.no_mails |
831 | # Set up the uploadprocessor with appropriate options and logger |
832 | - self.uploadprocessor = UploadProcessor( |
833 | - self.options, self.layer.txn, self.log) |
834 | + self.uploadprocessor = self.getUploadProcessor(self.layer.txn) |
835 | self.builds_before_upload = BinaryPackageBuild.select().count() |
836 | self.source_queue = None |
837 | self._uploadSource() |
838 | @@ -232,8 +230,7 @@ |
839 | """ |
840 | build_candidate = self._createBuild('i386') |
841 | self.options.buildid = str(build_candidate.id) |
842 | - self.uploadprocessor = UploadProcessor( |
843 | - self.options, self.layer.txn, self.log) |
844 | + self.uploadprocessor = self.getUploadProcessor(self.layer.txn) |
845 | |
846 | build_used = self._uploadBinary('i386') |
847 | |
848 | @@ -254,8 +251,7 @@ |
849 | """ |
850 | build_candidate = self._createBuild('hppa') |
851 | self.options.buildid = str(build_candidate.id) |
852 | - self.uploadprocessor = UploadProcessor( |
853 | - self.options, self.layer.txn, self.log) |
854 | + self.uploadprocessor = self.getUploadProcessor(self.layer.txn) |
855 | |
856 | self.assertRaises(AssertionError, self._uploadBinary, 'i386') |
857 | |
858 | |
859 | === modified file 'lib/lp/archiveuploader/tests/test_uploadprocessor.py' |
860 | --- lib/lp/archiveuploader/tests/test_uploadprocessor.py 2010-08-02 02:13:52 +0000 |
861 | +++ lib/lp/archiveuploader/tests/test_uploadprocessor.py 2010-08-12 22:16:35 +0000 |
862 | @@ -23,8 +23,10 @@ |
863 | from zope.security.proxy import removeSecurityProxy |
864 | |
865 | from lp.app.errors import NotFoundError |
866 | -from lp.archiveuploader.uploadpolicy import AbstractUploadPolicy |
867 | +from lp.archiveuploader.uploadpolicy import (AbstractUploadPolicy, |
868 | + findPolicyByOptions) |
869 | from lp.archiveuploader.uploadprocessor import UploadProcessor |
870 | +from lp.buildmaster.interfaces.buildbase import BuildStatus |
871 | from canonical.config import config |
872 | from canonical.database.constants import UTC_NOW |
873 | from lp.soyuz.model.archivepermission import ArchivePermission |
874 | @@ -59,7 +61,7 @@ |
875 | ISourcePackageNameSet) |
876 | from lp.services.mail import stub |
877 | from canonical.launchpad.testing.fakepackager import FakePackager |
878 | -from lp.testing import TestCaseWithFactory |
879 | +from lp.testing import TestCase, TestCaseWithFactory |
880 | from lp.testing.mail_helpers import pop_notifications |
881 | from canonical.launchpad.webapp.errorlog import ErrorReportingUtility |
882 | from canonical.testing import LaunchpadZopelessLayer |
883 | @@ -113,7 +115,8 @@ |
884 | super(TestUploadProcessorBase, self).setUp() |
885 | |
886 | self.queue_folder = tempfile.mkdtemp() |
887 | - os.makedirs(os.path.join(self.queue_folder, "incoming")) |
888 | + self.incoming_folder = os.path.join(self.queue_folder, "incoming") |
889 | + os.makedirs(self.incoming_folder) |
890 | |
891 | self.test_files_dir = os.path.join(config.root, |
892 | "lib/lp/archiveuploader/tests/data/suite") |
893 | @@ -139,6 +142,30 @@ |
894 | shutil.rmtree(self.queue_folder) |
895 | super(TestUploadProcessorBase, self).tearDown() |
896 | |
897 | + def getUploadProcessor(self, txn): |
898 | + def getPolicy(distro): |
899 | + self.options.distro = distro.name |
900 | + return findPolicyByOptions(self.options) |
901 | + return UploadProcessor( |
902 | + self.options.base_fsroot, self.options.dryrun, |
903 | + self.options.nomails, |
904 | + self.options.keep, getPolicy, txn, self.log) |
905 | + |
906 | + def publishPackage(self, packagename, version, source=True, |
907 | + archive=None): |
908 | + """Publish a single package that is currently NEW in the queue.""" |
909 | + queue_items = self.breezy.getQueueItems( |
910 | + status=PackageUploadStatus.NEW, name=packagename, |
911 | + version=version, exact_match=True, archive=archive) |
912 | + self.assertEqual(queue_items.count(), 1) |
913 | + queue_item = queue_items[0] |
914 | + queue_item.setAccepted() |
915 | + if source: |
916 | + pubrec = queue_item.sources[0].publish(self.log) |
917 | + else: |
918 | + pubrec = queue_item.builds[0].publish(self.log) |
919 | + return pubrec |
920 | + |
921 | def assertLogContains(self, line): |
922 | """Assert if a given line is present in the log messages.""" |
923 | self.assertTrue(line in self.log.lines, |
924 | @@ -208,25 +235,29 @@ |
925 | filename, len(content), StringIO(content), |
926 | 'application/x-gtar') |
927 | |
928 | - def queueUpload(self, upload_name, relative_path="", test_files_dir=None): |
929 | + def queueUpload(self, upload_name, relative_path="", test_files_dir=None, |
930 | + queue_entry=None): |
931 | """Queue one of our test uploads. |
932 | |
933 | - upload_name is the name of the test upload directory. It is also |
934 | + upload_name is the name of the test upload directory. If there |
935 | + is no explicit queue entry name specified, it is also |
936 | the name of the queue entry directory we create. |
937 | relative_path is the path to create inside the upload, eg |
938 | ubuntu/~malcc/default. If not specified, defaults to "". |
939 | |
940 | Return the path to the upload queue entry directory created. |
941 | """ |
942 | + if queue_entry is None: |
943 | + queue_entry = upload_name |
944 | target_path = os.path.join( |
945 | - self.queue_folder, "incoming", upload_name, relative_path) |
946 | + self.incoming_folder, queue_entry, relative_path) |
947 | if test_files_dir is None: |
948 | test_files_dir = self.test_files_dir |
949 | upload_dir = os.path.join(test_files_dir, upload_name) |
950 | if relative_path: |
951 | os.makedirs(os.path.dirname(target_path)) |
952 | shutil.copytree(upload_dir, target_path) |
953 | - return os.path.join(self.queue_folder, "incoming", upload_name) |
954 | + return os.path.join(self.incoming_folder, queue_entry) |
955 | |
956 | def processUpload(self, processor, upload_dir): |
957 | """Process an upload queue entry directory. |
958 | @@ -248,8 +279,7 @@ |
959 | self.layer.txn.commit() |
960 | if policy is not None: |
961 | self.options.context = policy |
962 | - return UploadProcessor( |
963 | - self.options, self.layer.txn, self.log) |
964 | + return self.getUploadProcessor(self.layer.txn) |
965 | |
966 | def assertEmail(self, contents=None, recipients=None): |
967 | """Check last email content and recipients. |
968 | @@ -341,24 +371,9 @@ |
969 | "Expected acceptance email not rejection. Actually Got:\n%s" |
970 | % raw_msg) |
971 | |
972 | - def _publishPackage(self, packagename, version, source=True, |
973 | - archive=None): |
974 | - """Publish a single package that is currently NEW in the queue.""" |
975 | - queue_items = self.breezy.getQueueItems( |
976 | - status=PackageUploadStatus.NEW, name=packagename, |
977 | - version=version, exact_match=True, archive=archive) |
978 | - self.assertEqual(queue_items.count(), 1) |
979 | - queue_item = queue_items[0] |
980 | - queue_item.setAccepted() |
981 | - if source: |
982 | - pubrec = queue_item.sources[0].publish(self.log) |
983 | - else: |
984 | - pubrec = queue_item.builds[0].publish(self.log) |
985 | - return pubrec |
986 | - |
987 | def testInstantiate(self): |
988 | """UploadProcessor should instantiate""" |
989 | - up = UploadProcessor(self.options, None, self.log) |
990 | + up = self.getUploadProcessor(None) |
991 | |
992 | def testLocateDirectories(self): |
993 | """Return a sorted list of subdirs in a directory. |
994 | @@ -372,7 +387,7 @@ |
995 | os.mkdir("%s/dir1" % testdir) |
996 | os.mkdir("%s/dir2" % testdir) |
997 | |
998 | - up = UploadProcessor(self.options, None, self.log) |
999 | + up = self.getUploadProcessor(None) |
1000 | located_dirs = up.locateDirectories(testdir) |
1001 | self.assertEqual(located_dirs, ['dir1', 'dir2', 'dir3']) |
1002 | finally: |
1003 | @@ -390,7 +405,7 @@ |
1004 | open("%s/2_source.changes" % testdir, "w").close() |
1005 | open("%s/3.not_changes" % testdir, "w").close() |
1006 | |
1007 | - up = UploadProcessor(self.options, None, self.log) |
1008 | + up = self.getUploadProcessor(None) |
1009 | located_files = up.locateChangesFiles(testdir) |
1010 | self.assertEqual( |
1011 | located_files, ["2_source.changes", "1.changes"]) |
1012 | @@ -418,7 +433,7 @@ |
1013 | |
1014 | # Move it |
1015 | self.options.base_fsroot = testdir |
1016 | - up = UploadProcessor(self.options, None, self.log) |
1017 | + up = self.getUploadProcessor(None) |
1018 | up.moveUpload(upload, target_name) |
1019 | |
1020 | # Check it moved |
1021 | @@ -439,7 +454,7 @@ |
1022 | |
1023 | # Remove it |
1024 | self.options.base_fsroot = testdir |
1025 | - up = UploadProcessor(self.options, None, self.log) |
1026 | + up = self.getUploadProcessor(None) |
1027 | up.moveProcessedUpload(upload, "accepted") |
1028 | |
1029 | # Check it was removed, not moved |
1030 | @@ -462,7 +477,7 @@ |
1031 | |
1032 | # Move it |
1033 | self.options.base_fsroot = testdir |
1034 | - up = UploadProcessor(self.options, None, self.log) |
1035 | + up = self.getUploadProcessor(None) |
1036 | up.moveProcessedUpload(upload, "rejected") |
1037 | |
1038 | # Check it moved |
1039 | @@ -485,7 +500,7 @@ |
1040 | |
1041 | # Remove it |
1042 | self.options.base_fsroot = testdir |
1043 | - up = UploadProcessor(self.options, None, self.log) |
1044 | + up = self.getUploadProcessor(None) |
1045 | up.removeUpload(upload) |
1046 | |
1047 | # Check it was removed, not moved |
1048 | @@ -498,7 +513,7 @@ |
1049 | |
1050 | def testOrderFilenames(self): |
1051 | """orderFilenames sorts _source.changes ahead of other files.""" |
1052 | - up = UploadProcessor(self.options, None, self.log) |
1053 | + up = self.getUploadProcessor(None) |
1054 | |
1055 | self.assertEqual(["d_source.changes", "a", "b", "c"], |
1056 | up.orderFilenames(["b", "a", "d_source.changes", "c"])) |
1057 | @@ -522,8 +537,7 @@ |
1058 | # Register our broken upload policy |
1059 | AbstractUploadPolicy._registerPolicy(BrokenUploadPolicy) |
1060 | self.options.context = 'broken' |
1061 | - uploadprocessor = UploadProcessor( |
1062 | - self.options, self.layer.txn, self.log) |
1063 | + uploadprocessor = self.getUploadProcessor(self.layer.txn) |
1064 | |
1065 | # Upload a package to Breezy. |
1066 | upload_dir = self.queueUpload("baz_1.0-1") |
1067 | @@ -634,7 +648,7 @@ |
1068 | # Upload 'bar-1.0-1' source and binary to ubuntu/breezy. |
1069 | upload_dir = self.queueUpload("bar_1.0-1") |
1070 | self.processUpload(uploadprocessor, upload_dir) |
1071 | - bar_source_pub = self._publishPackage('bar', '1.0-1') |
1072 | + bar_source_pub = self.publishPackage('bar', '1.0-1') |
1073 | [bar_original_build] = bar_source_pub.createMissingBuilds() |
1074 | |
1075 | # Move the source from the accepted queue. |
1076 | @@ -653,7 +667,7 @@ |
1077 | self.processUpload(uploadprocessor, upload_dir) |
1078 | self.assertEqual( |
1079 | uploadprocessor.last_processed_upload.is_rejected, False) |
1080 | - bar_bin_pubs = self._publishPackage('bar', '1.0-1', source=False) |
1081 | + bar_bin_pubs = self.publishPackage('bar', '1.0-1', source=False) |
1082 | # Mangle its publishing component to "restricted" so we can check |
1083 | # the copy archive ancestry override later. |
1084 | restricted = getUtility(IComponentSet)["restricted"] |
1085 | @@ -746,14 +760,14 @@ |
1086 | # Upload 'bar-1.0-1' source and binary to ubuntu/breezy. |
1087 | upload_dir = self.queueUpload("bar_1.0-1") |
1088 | self.processUpload(uploadprocessor, upload_dir) |
1089 | - bar_source_pub = self._publishPackage('bar', '1.0-1') |
1090 | + bar_source_pub = self.publishPackage('bar', '1.0-1') |
1091 | [bar_original_build] = bar_source_pub.createMissingBuilds() |
1092 | |
1093 | self.options.context = 'buildd' |
1094 | self.options.buildid = bar_original_build.id |
1095 | upload_dir = self.queueUpload("bar_1.0-1_binary") |
1096 | self.processUpload(uploadprocessor, upload_dir) |
1097 | - [bar_binary_pub] = self._publishPackage("bar", "1.0-1", source=False) |
1098 | + [bar_binary_pub] = self.publishPackage("bar", "1.0-1", source=False) |
1099 | |
1100 | # Prepare ubuntu/breezy-autotest to build sources in i386. |
1101 | breezy_autotest = self.ubuntu['breezy-autotest'] |
1102 | @@ -803,7 +817,7 @@ |
1103 | # Upload 'bar-1.0-1' source and binary to ubuntu/breezy. |
1104 | upload_dir = self.queueUpload("bar_1.0-1") |
1105 | self.processUpload(uploadprocessor, upload_dir) |
1106 | - bar_source_old = self._publishPackage('bar', '1.0-1') |
1107 | + bar_source_old = self.publishPackage('bar', '1.0-1') |
1108 | |
1109 | # Upload 'bar-1.0-1' source and binary to ubuntu/breezy. |
1110 | upload_dir = self.queueUpload("bar_1.0-2") |
1111 | @@ -816,7 +830,7 @@ |
1112 | self.options.buildid = bar_original_build.id |
1113 | upload_dir = self.queueUpload("bar_1.0-2_binary") |
1114 | self.processUpload(uploadprocessor, upload_dir) |
1115 | - [bar_binary_pub] = self._publishPackage("bar", "1.0-2", source=False) |
1116 | + [bar_binary_pub] = self.publishPackage("bar", "1.0-2", source=False) |
1117 | |
1118 | # Create a COPY archive for building in non-virtual builds. |
1119 | uploader = getUtility(IPersonSet).getByName('name16') |
1120 | @@ -971,7 +985,7 @@ |
1121 | partner_archive = getUtility(IArchiveSet).getByDistroPurpose( |
1122 | self.ubuntu, ArchivePurpose.PARTNER) |
1123 | self.assertTrue(partner_archive) |
1124 | - self._publishPackage("foocomm", "1.0-1", archive=partner_archive) |
1125 | + self.publishPackage("foocomm", "1.0-1", archive=partner_archive) |
1126 | |
1127 | # Check the publishing record's archive and component. |
1128 | foocomm_spph = SourcePackagePublishingHistory.selectOneBy( |
1129 | @@ -1015,7 +1029,7 @@ |
1130 | self.assertEqual(foocomm_bpr.component.name, 'partner') |
1131 | |
1132 | # Publish the upload so we can check the publishing record. |
1133 | - self._publishPackage("foocomm", "1.0-1", source=False) |
1134 | + self.publishPackage("foocomm", "1.0-1", source=False) |
1135 | |
1136 | # Check the publishing record's archive and component. |
1137 | foocomm_bpph = BinaryPackagePublishingHistory.selectOneBy( |
1138 | @@ -1054,14 +1068,14 @@ |
1139 | # Accept and publish the upload. |
1140 | partner_archive = getUtility(IArchiveSet).getByDistroPurpose( |
1141 | self.ubuntu, ArchivePurpose.PARTNER) |
1142 | - self._publishPackage("foocomm", "1.0-1", archive=partner_archive) |
1143 | + self.publishPackage("foocomm", "1.0-1", archive=partner_archive) |
1144 | |
1145 | # Now do the same thing with a binary package. |
1146 | upload_dir = self.queueUpload("foocomm_1.0-1_binary") |
1147 | self.processUpload(uploadprocessor, upload_dir) |
1148 | |
1149 | # Accept and publish the upload. |
1150 | - self._publishPackage("foocomm", "1.0-1", source=False, |
1151 | + self.publishPackage("foocomm", "1.0-1", source=False, |
1152 | archive=partner_archive) |
1153 | |
1154 | # Upload the next source version of the package. |
1155 | @@ -1105,8 +1119,7 @@ |
1156 | self.breezy.status = SeriesStatus.CURRENT |
1157 | self.layer.txn.commit() |
1158 | self.options.context = 'insecure' |
1159 | - uploadprocessor = UploadProcessor( |
1160 | - self.options, self.layer.txn, self.log) |
1161 | + uploadprocessor = self.getUploadProcessor(self.layer.txn) |
1162 | |
1163 | # Upload a package for Breezy. |
1164 | upload_dir = self.queueUpload("foocomm_1.0-1_proposed") |
1165 | @@ -1124,8 +1137,7 @@ |
1166 | self.breezy.status = SeriesStatus.CURRENT |
1167 | self.layer.txn.commit() |
1168 | self.options.context = 'insecure' |
1169 | - uploadprocessor = UploadProcessor( |
1170 | - self.options, self.layer.txn, self.log) |
1171 | + uploadprocessor = self.getUploadProcessor(self.layer.txn) |
1172 | |
1173 | # Upload a package for Breezy. |
1174 | upload_dir = self.queueUpload("foocomm_1.0-1") |
1175 | @@ -1140,8 +1152,7 @@ |
1176 | pocket and ensure it fails.""" |
1177 | # Set up the uploadprocessor with appropriate options and logger. |
1178 | self.options.context = 'insecure' |
1179 | - uploadprocessor = UploadProcessor( |
1180 | - self.options, self.layer.txn, self.log) |
1181 | + uploadprocessor = self.getUploadProcessor(self.layer.txn) |
1182 | |
1183 | # Upload a package for Breezy. |
1184 | upload_dir = self.queueUpload("foocomm_1.0-1_updates") |
1185 | @@ -1302,8 +1313,7 @@ |
1186 | used. |
1187 | That exception will then initiate the creation of an OOPS report. |
1188 | """ |
1189 | - processor = UploadProcessor( |
1190 | - self.options, self.layer.txn, self.log) |
1191 | + processor = self.getUploadProcessor(self.layer.txn) |
1192 | |
1193 | upload_dir = self.queueUpload("foocomm_1.0-1_proposed") |
1194 | bogus_changesfile_data = ''' |
1195 | @@ -1346,8 +1356,7 @@ |
1196 | self.setupBreezy() |
1197 | self.layer.txn.commit() |
1198 | self.options.context = 'absolutely-anything' |
1199 | - uploadprocessor = UploadProcessor( |
1200 | - self.options, self.layer.txn, self.log) |
1201 | + uploadprocessor = self.getUploadProcessor(self.layer.txn) |
1202 | |
1203 | # Upload the source first to enable the binary later: |
1204 | upload_dir = self.queueUpload("bar_1.0-1_lzma") |
1205 | @@ -1357,7 +1366,7 @@ |
1206 | self.assertTrue( |
1207 | "rejected" not in raw_msg, |
1208 | "Failed to upload bar source:\n%s" % raw_msg) |
1209 | - self._publishPackage("bar", "1.0-1") |
1210 | + self.publishPackage("bar", "1.0-1") |
1211 | # Clear out emails generated during upload. |
1212 | ignore = pop_notifications() |
1213 | |
1214 | @@ -1456,15 +1465,14 @@ |
1215 | permission=ArchivePermissionType.UPLOAD, person=uploader, |
1216 | component=restricted) |
1217 | |
1218 | - uploadprocessor = UploadProcessor( |
1219 | - self.options, self.layer.txn, self.log) |
1220 | + uploadprocessor = self.getUploadProcessor(self.layer.txn) |
1221 | |
1222 | # Upload the first version and accept it to make it known in |
1223 | # Ubuntu. The uploader has rights to upload NEW packages to |
1224 | # components that he does not have direct rights to. |
1225 | upload_dir = self.queueUpload("bar_1.0-1") |
1226 | self.processUpload(uploadprocessor, upload_dir) |
1227 | - bar_source_pub = self._publishPackage('bar', '1.0-1') |
1228 | + bar_source_pub = self.publishPackage('bar', '1.0-1') |
1229 | # Clear out emails generated during upload. |
1230 | ignore = pop_notifications() |
1231 | |
1232 | @@ -1509,15 +1517,14 @@ |
1233 | permission=ArchivePermissionType.UPLOAD, person=uploader, |
1234 | component=restricted) |
1235 | |
1236 | - uploadprocessor = UploadProcessor( |
1237 | - self.options, self.layer.txn, self.log) |
1238 | + uploadprocessor = self.getUploadProcessor(self.layer.txn) |
1239 | |
1240 | # Upload the first version and accept it to make it known in |
1241 | # Ubuntu. The uploader has rights to upload NEW packages to |
1242 | # components that he does not have direct rights to. |
1243 | upload_dir = self.queueUpload("bar_1.0-1") |
1244 | self.processUpload(uploadprocessor, upload_dir) |
1245 | - bar_source_pub = self._publishPackage('bar', '1.0-1') |
1246 | + bar_source_pub = self.publishPackage('bar', '1.0-1') |
1247 | # Clear out emails generated during upload. |
1248 | ignore = pop_notifications() |
1249 | |
1250 | @@ -1590,8 +1597,7 @@ |
1251 | # with pointer to the Soyuz questions in Launchpad and the |
1252 | # reason why the message was sent to the current recipients. |
1253 | self.setupBreezy() |
1254 | - uploadprocessor = UploadProcessor( |
1255 | - self.options, self.layer.txn, self.log) |
1256 | + uploadprocessor = self.getUploadProcessor(self.layer.txn) |
1257 | |
1258 | upload_dir = self.queueUpload("bar_1.0-1", "boing") |
1259 | self.processUpload(uploadprocessor, upload_dir) |
1260 | @@ -1636,8 +1642,7 @@ |
1261 | self.setupBreezy() |
1262 | self.layer.txn.commit() |
1263 | self.options.context = 'absolutely-anything' |
1264 | - uploadprocessor = UploadProcessor( |
1265 | - self.options, self.layer.txn, self.log) |
1266 | + uploadprocessor = self.getUploadProcessor(self.layer.txn) |
1267 | |
1268 | # Upload the source. |
1269 | upload_dir = self.queueUpload("bar_1.0-1_3.0-quilt") |
1270 | @@ -1655,8 +1660,7 @@ |
1271 | permitted_formats=[SourcePackageFormat.FORMAT_3_0_QUILT]) |
1272 | self.layer.txn.commit() |
1273 | self.options.context = 'absolutely-anything' |
1274 | - uploadprocessor = UploadProcessor( |
1275 | - self.options, self.layer.txn, self.log) |
1276 | + uploadprocessor = self.getUploadProcessor(self.layer.txn) |
1277 | |
1278 | # Upload the source. |
1279 | upload_dir = self.queueUpload("bar_1.0-1_3.0-quilt") |
1280 | @@ -1666,7 +1670,7 @@ |
1281 | self.assertTrue( |
1282 | "rejected" not in raw_msg, |
1283 | "Failed to upload bar source:\n%s" % raw_msg) |
1284 | - spph = self._publishPackage("bar", "1.0-1") |
1285 | + spph = self.publishPackage("bar", "1.0-1") |
1286 | |
1287 | self.assertEquals( |
1288 | sorted((sprf.libraryfile.filename, sprf.filetype) |
1289 | @@ -1689,8 +1693,7 @@ |
1290 | permitted_formats=[SourcePackageFormat.FORMAT_3_0_QUILT]) |
1291 | self.layer.txn.commit() |
1292 | self.options.context = 'absolutely-anything' |
1293 | - uploadprocessor = UploadProcessor( |
1294 | - self.options, self.layer.txn, self.log) |
1295 | + uploadprocessor = self.getUploadProcessor(self.layer.txn) |
1296 | |
1297 | # Upload the first source. |
1298 | upload_dir = self.queueUpload("bar_1.0-1_3.0-quilt") |
1299 | @@ -1700,7 +1703,7 @@ |
1300 | self.assertTrue( |
1301 | "rejected" not in raw_msg, |
1302 | "Failed to upload bar source:\n%s" % raw_msg) |
1303 | - spph = self._publishPackage("bar", "1.0-1") |
1304 | + spph = self.publishPackage("bar", "1.0-1") |
1305 | |
1306 | # Upload another source sharing the same (component) orig. |
1307 | upload_dir = self.queueUpload("bar_1.0-2_3.0-quilt_without_orig") |
1308 | @@ -1728,8 +1731,7 @@ |
1309 | permitted_formats=[SourcePackageFormat.FORMAT_3_0_NATIVE]) |
1310 | self.layer.txn.commit() |
1311 | self.options.context = 'absolutely-anything' |
1312 | - uploadprocessor = UploadProcessor( |
1313 | - self.options, self.layer.txn, self.log) |
1314 | + uploadprocessor = self.getUploadProcessor(self.layer.txn) |
1315 | |
1316 | # Upload the source. |
1317 | upload_dir = self.queueUpload("bar_1.0_3.0-native") |
1318 | @@ -1739,7 +1741,7 @@ |
1319 | self.assertTrue( |
1320 | "rejected" not in raw_msg, |
1321 | "Failed to upload bar source:\n%s" % raw_msg) |
1322 | - spph = self._publishPackage("bar", "1.0") |
1323 | + spph = self.publishPackage("bar", "1.0") |
1324 | |
1325 | self.assertEquals( |
1326 | sorted((sprf.libraryfile.filename, sprf.filetype) |
1327 | @@ -1754,8 +1756,7 @@ |
1328 | self.setupBreezy() |
1329 | self.layer.txn.commit() |
1330 | self.options.context = 'absolutely-anything' |
1331 | - uploadprocessor = UploadProcessor( |
1332 | - self.options, self.layer.txn, self.log) |
1333 | + uploadprocessor = self.getUploadProcessor(self.layer.txn) |
1334 | |
1335 | # Upload the source. |
1336 | upload_dir = self.queueUpload("bar_1.0-1_1.0-bzip2") |
1337 | @@ -1772,8 +1773,7 @@ |
1338 | self.setupBreezy() |
1339 | breezy = self.ubuntu['breezy'] |
1340 | breezy.status = SeriesStatus.CURRENT |
1341 | - uploadprocessor = UploadProcessor( |
1342 | - self.options, self.layer.txn, self.log) |
1343 | + uploadprocessor = self.getUploadProcessor(self.layer.txn) |
1344 | |
1345 | upload_dir = self.queueUpload("bar_1.0-1") |
1346 | self.processUpload(uploadprocessor, upload_dir) |
1347 | |
1348 | === modified file 'lib/lp/archiveuploader/uploadprocessor.py' |
1349 | --- lib/lp/archiveuploader/uploadprocessor.py 2010-08-04 00:30:56 +0000 |
1350 | +++ lib/lp/archiveuploader/uploadprocessor.py 2010-08-12 22:16:35 +0000 |
1351 | @@ -60,7 +60,7 @@ |
1352 | from lp.archiveuploader.nascentupload import ( |
1353 | NascentUpload, FatalUploadError, EarlyReturnUploadError) |
1354 | from lp.archiveuploader.uploadpolicy import ( |
1355 | - findPolicyByOptions, UploadPolicyError) |
1356 | + UploadPolicyError) |
1357 | from lp.soyuz.interfaces.archive import IArchiveSet, NoSuchPPA |
1358 | from lp.registry.interfaces.distribution import IDistributionSet |
1359 | from lp.registry.interfaces.person import IPersonSet |
1360 | @@ -108,16 +108,33 @@ |
1361 | class UploadProcessor: |
1362 | """Responsible for processing uploads. See module docstring.""" |
1363 | |
1364 | - def __init__(self, options, ztm, log): |
1365 | - self.options = options |
1366 | + def __init__(self, base_fsroot, dry_run, no_mails, keep, policy_for_distro, |
1367 | + ztm, log): |
1368 | + """Create a new upload processor. |
1369 | + |
1370 | + :param base_fsroot: Root path for queue to use |
1371 | + :param dry_run: Run but don't commit changes to database |
1372 | + :param no_mails: Don't send out any emails |
1373 | + :param builds: Interpret leaf names as build ids |
1374 | + :param keep: Leave the files in place, don't move them away |
1375 | + :param policy_for_distro: callback to obtain Policy object for a |
1376 | + distribution |
1377 | + :param ztm: Database transaction to use |
1378 | + :param log: Logger to use for reporting |
1379 | + """ |
1380 | + self.base_fsroot = base_fsroot |
1381 | + self.dry_run = dry_run |
1382 | + self.keep = keep |
1383 | + self.last_processed_upload = None |
1384 | + self.log = log |
1385 | + self.no_mails = no_mails |
1386 | + self._getPolicyForDistro = policy_for_distro |
1387 | self.ztm = ztm |
1388 | - self.log = log |
1389 | - self.last_processed_upload = None |
1390 | |
1391 | - def processUploadQueue(self): |
1392 | + def processUploadQueue(self, leaf_name=None): |
1393 | """Search for uploads, and process them. |
1394 | |
1395 | - Uploads are searched for in the 'incoming' directory inside the |
1396 | + Uploads are searched for in the 'incoming' directory inside the |
1397 | base_fsroot. |
1398 | |
1399 | This method also creates the 'incoming', 'accepted', 'rejected', and |
1400 | @@ -127,19 +144,22 @@ |
1401 | self.log.debug("Beginning processing") |
1402 | |
1403 | for subdir in ["incoming", "accepted", "rejected", "failed"]: |
1404 | - full_subdir = os.path.join(self.options.base_fsroot, subdir) |
1405 | + full_subdir = os.path.join(self.base_fsroot, subdir) |
1406 | if not os.path.exists(full_subdir): |
1407 | self.log.debug("Creating directory %s" % full_subdir) |
1408 | os.mkdir(full_subdir) |
1409 | |
1410 | - fsroot = os.path.join(self.options.base_fsroot, "incoming") |
1411 | + fsroot = os.path.join(self.base_fsroot, "incoming") |
1412 | uploads_to_process = self.locateDirectories(fsroot) |
1413 | self.log.debug("Checked in %s, found %s" |
1414 | % (fsroot, uploads_to_process)) |
1415 | for upload in uploads_to_process: |
1416 | self.log.debug("Considering upload %s" % upload) |
1417 | + if leaf_name is not None and upload != leaf_name: |
1418 | + self.log.debug("Skipping %s -- does not match %s" % ( |
1419 | + upload, leaf_name)) |
1420 | + continue |
1421 | self.processUpload(fsroot, upload) |
1422 | - |
1423 | finally: |
1424 | self.log.debug("Rolling back any remaining transactions.") |
1425 | self.ztm.abort() |
1426 | @@ -152,16 +172,7 @@ |
1427 | is 'failed', otherwise it is the worst of the results from the |
1428 | individual changes files, in order 'failed', 'rejected', 'accepted'. |
1429 | |
1430 | - If the leafname option is set but its value is not the same as the |
1431 | - name of the upload directory, skip it entirely. |
1432 | - |
1433 | """ |
1434 | - if (self.options.leafname is not None and |
1435 | - upload != self.options.leafname): |
1436 | - self.log.debug("Skipping %s -- does not match %s" % ( |
1437 | - upload, self.options.leafname)) |
1438 | - return |
1439 | - |
1440 | upload_path = os.path.join(fsroot, upload) |
1441 | changes_files = self.locateChangesFiles(upload_path) |
1442 | |
1443 | @@ -242,7 +253,7 @@ |
1444 | # Skip lockfile deletion, see similar code in lp.poppy.hooks. |
1445 | fsroot_lock.release(skip_delete=True) |
1446 | |
1447 | - sorted_dir_names = sorted( |
1448 | + sorted_dir_names = sorted( |
1449 | dir_name |
1450 | for dir_name in dir_names |
1451 | if os.path.isdir(os.path.join(fsroot, dir_name))) |
1452 | @@ -321,8 +332,7 @@ |
1453 | "https://help.launchpad.net/Packaging/PPA#Uploading " |
1454 | "and update your configuration."))) |
1455 | self.log.debug("Finding fresh policy") |
1456 | - self.options.distro = distribution.name |
1457 | - policy = findPolicyByOptions(self.options) |
1458 | + policy = self._getPolicyForDistro(distribution) |
1459 | policy.archive = archive |
1460 | |
1461 | # DistroSeries overriding respect the following precedence: |
1462 | @@ -396,7 +406,7 @@ |
1463 | # when transaction is committed) this will cause any emails sent |
1464 | # sent by do_reject to be lost. |
1465 | notify = True |
1466 | - if self.options.dryrun or self.options.nomails: |
1467 | + if self.dry_run or self.no_mails: |
1468 | notify = False |
1469 | if upload.is_rejected: |
1470 | result = UploadStatusEnum.REJECTED |
1471 | @@ -415,7 +425,7 @@ |
1472 | for msg in upload.rejections: |
1473 | self.log.warn("\t%s" % msg) |
1474 | |
1475 | - if self.options.dryrun: |
1476 | + if self.dry_run: |
1477 | self.log.info("Dry run, aborting transaction.") |
1478 | self.ztm.abort() |
1479 | else: |
1480 | @@ -434,7 +444,7 @@ |
1481 | This includes moving the given upload directory and moving the |
1482 | matching .distro file, if it exists. |
1483 | """ |
1484 | - if self.options.keep or self.options.dryrun: |
1485 | + if self.keep or self.dry_run: |
1486 | self.log.debug("Keeping contents untouched") |
1487 | return |
1488 | |
1489 | @@ -462,21 +472,21 @@ |
1490 | This includes moving the given upload directory and moving the |
1491 | matching .distro file, if it exists. |
1492 | """ |
1493 | - if self.options.keep or self.options.dryrun: |
1494 | + if self.keep or self.dry_run: |
1495 | self.log.debug("Keeping contents untouched") |
1496 | return |
1497 | |
1498 | pathname = os.path.basename(upload) |
1499 | |
1500 | target_path = os.path.join( |
1501 | - self.options.base_fsroot, subdir_name, pathname) |
1502 | + self.base_fsroot, subdir_name, pathname) |
1503 | self.log.debug("Moving upload directory %s to %s" % |
1504 | (upload, target_path)) |
1505 | shutil.move(upload, target_path) |
1506 | |
1507 | distro_filename = upload + ".distro" |
1508 | if os.path.isfile(distro_filename): |
1509 | - target_path = os.path.join(self.options.base_fsroot, subdir_name, |
1510 | + target_path = os.path.join(self.base_fsroot, subdir_name, |
1511 | os.path.basename(distro_filename)) |
1512 | self.log.debug("Moving distro file %s to %s" % (distro_filename, |
1513 | target_path)) |
1514 | |
1515 | === modified file 'lib/lp/registry/browser/product.py' |
1516 | --- lib/lp/registry/browser/product.py 2010-08-04 04:07:21 +0000 |
1517 | +++ lib/lp/registry/browser/product.py 2010-08-12 22:16:35 +0000 |
1518 | @@ -83,6 +83,7 @@ |
1519 | from lp.registry.interfaces.pillar import IPillarNameSet |
1520 | from lp.registry.interfaces.product import IProductReviewSearch, License |
1521 | from lp.registry.interfaces.series import SeriesStatus |
1522 | +from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet |
1523 | from lp.registry.interfaces.product import ( |
1524 | IProduct, IProductSet, LicenseStatus) |
1525 | from lp.registry.interfaces.productrelease import ( |
1526 | @@ -120,7 +121,7 @@ |
1527 | from canonical.launchpad.webapp.breadcrumb import Breadcrumb |
1528 | from canonical.launchpad.webapp.launchpadform import ( |
1529 | action, custom_widget, LaunchpadEditFormView, LaunchpadFormView, |
1530 | - ReturnToReferrerMixin) |
1531 | + ReturnToReferrerMixin, safe_action) |
1532 | from canonical.launchpad.webapp.menu import NavigationMenu |
1533 | from canonical.launchpad.webapp.tales import MenuAPI |
1534 | from canonical.widgets.popup import PersonPickerWidget |
1535 | @@ -1833,6 +1834,17 @@ |
1536 | return canonical_url(self.product) |
1537 | |
1538 | |
1539 | +def create_source_package_fields(): |
1540 | + return form.Fields( |
1541 | + Choice(__name__='source_package_name', |
1542 | + vocabulary='SourcePackageName', |
1543 | + required=False), |
1544 | + Choice(__name__='distroseries', |
1545 | + vocabulary='DistroSeries', |
1546 | + required=False), |
1547 | + ) |
1548 | + |
1549 | + |
1550 | class ProjectAddStepOne(StepView): |
1551 | """product/+new view class for creating a new project.""" |
1552 | |
1553 | @@ -1849,6 +1861,29 @@ |
1554 | step_description = 'Project basics' |
1555 | search_results_count = 0 |
1556 | |
1557 | + def setUpFields(self): |
1558 | + """See `LaunchpadFormView`.""" |
1559 | + super(ProjectAddStepOne, self).setUpFields() |
1560 | + self.form_fields = ( |
1561 | + self.form_fields + |
1562 | + create_source_package_fields()) |
1563 | + |
1564 | + def setUpWidgets(self): |
1565 | + """See `LaunchpadFormView`.""" |
1566 | + super(ProjectAddStepOne, self).setUpWidgets() |
1567 | + self.widgets['source_package_name'].visible = False |
1568 | + self.widgets['distroseries'].visible = False |
1569 | + |
1570 | + @property |
1571 | + def _return_url(self): |
1572 | + """This view is using the hidden _return_url field. |
1573 | + |
1574 | + It is not using the `ReturnToReferrerMixin`, since none |
1575 | + of its other code is used, because multistep views can't |
1576 | + have next_url set until the form submission succeeds. |
1577 | + """ |
1578 | + return self.request.form.get('_return_url') |
1579 | + |
1580 | @property |
1581 | def _next_step(self): |
1582 | """Define the next step. |
1583 | @@ -1862,9 +1897,10 @@ |
1584 | def main_action(self, data): |
1585 | """See `MultiStepView`.""" |
1586 | self.next_step = self._next_step |
1587 | - self.request.form['displayname'] = data['displayname'] |
1588 | - self.request.form['name'] = data['name'].lower() |
1589 | - self.request.form['summary'] = data['summary'] |
1590 | + |
1591 | + # Make this a safe_action, so that the sourcepackage page can skip |
1592 | + # the first step with a link (GET request) providing form values. |
1593 | + continue_action = safe_action(StepView.continue_action) |
1594 | |
1595 | |
1596 | class ProjectAddStepTwo(StepView, ProductLicenseMixin, ReturnToReferrerMixin): |
1597 | @@ -1873,7 +1909,6 @@ |
1598 | _field_names = ['displayname', 'name', 'title', 'summary', |
1599 | 'description', 'licenses', 'license_info', |
1600 | ] |
1601 | - main_action_label = u'Complete Registration' |
1602 | schema = IProduct |
1603 | step_name = 'projectaddstep2' |
1604 | template = ViewPageTemplateFile('../templates/product-new.pt') |
1605 | @@ -1887,6 +1922,25 @@ |
1606 | custom_widget('license_info', GhostWidget) |
1607 | |
1608 | @property |
1609 | + def main_action_label(self): |
1610 | + if self.source_package_name is None: |
1611 | + return u'Complete Registration' |
1612 | + else: |
1613 | + return u'Complete registration and link to %s package' % ( |
1614 | + self.source_package_name.name, |
1615 | + ) |
1616 | + |
1617 | + @property |
1618 | + def _return_url(self): |
1619 | + """This view is using the hidden _return_url field. |
1620 | + |
1621 | + It is not using the `ReturnToReferrerMixin`, since none |
1622 | + of its other code is used, because multistep views can't |
1623 | + have next_url set until the form submission succeeds. |
1624 | + """ |
1625 | + return self.request.form.get('_return_url') |
1626 | + |
1627 | + @property |
1628 | def step_description(self): |
1629 | """See `MultiStepView`.""" |
1630 | if self.search_results_count > 0: |
1631 | @@ -1897,7 +1951,8 @@ |
1632 | """See `LaunchpadFormView`.""" |
1633 | super(ProjectAddStepTwo, self).setUpFields() |
1634 | self.form_fields = (self.form_fields + |
1635 | - self._createDisclaimMaintainerField()) |
1636 | + self._createDisclaimMaintainerField() + |
1637 | + create_source_package_fields()) |
1638 | |
1639 | def _createDisclaimMaintainerField(self): |
1640 | """Return a Bool field for disclaiming maintainer. |
1641 | @@ -1930,12 +1985,39 @@ |
1642 | "this will be the project's URL.") |
1643 | self.widgets['displayname'].visible = False |
1644 | |
1645 | + self.widgets['source_package_name'].visible = False |
1646 | + self.widgets['distroseries'].visible = False |
1647 | + |
1648 | + # Set the source_package_release attribute on the licenses |
1649 | + # widget, so that the source package's copyright info can be |
1650 | + # displayed. |
1651 | + ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
1652 | + if self.source_package_name is not None: |
1653 | + release_list = ubuntu.getCurrentSourceReleases( |
1654 | + [self.source_package_name]) |
1655 | + if len(release_list) != 0: |
1656 | + self.widgets['licenses'].source_package_release = ( |
1657 | + release_list.items()[0][1]) |
1658 | + |
1659 | + @property |
1660 | + def source_package_name(self): |
1661 | + # setUpWidgets() doesn't have access to the data dictionary, |
1662 | + # so the source package name needs to be converted from a string |
1663 | + # into an object here. |
1664 | + package_name_string = self.request.form.get( |
1665 | + 'field.source_package_name') |
1666 | + if package_name_string is None: |
1667 | + return None |
1668 | + else: |
1669 | + return getUtility(ISourcePackageNameSet).queryByName( |
1670 | + package_name_string) |
1671 | + |
1672 | @cachedproperty |
1673 | def _search_string(self): |
1674 | """Return the ORed terms to match.""" |
1675 | - search_text = SPACE.join((self.request.form['name'], |
1676 | - self.request.form['displayname'], |
1677 | - self.request.form['summary'])) |
1678 | + search_text = SPACE.join((self.request.form['field.name'], |
1679 | + self.request.form['field.displayname'], |
1680 | + self.request.form['field.summary'])) |
1681 | # OR all the terms together. |
1682 | return OR.join(search_text.split()) |
1683 | |
1684 | @@ -1972,7 +2054,8 @@ |
1685 | def label(self): |
1686 | """See `LaunchpadFormView`.""" |
1687 | return 'Register %s (%s) in Launchpad' % ( |
1688 | - self.request.form['displayname'], self.request.form['name']) |
1689 | + self.request.form['field.displayname'], |
1690 | + self.request.form['field.name']) |
1691 | |
1692 | def create_product(self, data): |
1693 | """Create the product from the user data.""" |
1694 | @@ -1996,12 +2079,28 @@ |
1695 | license_info=data['license_info'], |
1696 | project=project) |
1697 | |
1698 | + def link_source_package(self, product, data): |
1699 | + if (data.get('distroseries') is not None |
1700 | + and self.source_package_name is not None): |
1701 | + source_package = data['distroseries'].getSourcePackage( |
1702 | + self.source_package_name) |
1703 | + source_package.setPackaging( |
1704 | + product.development_focus, self.user) |
1705 | + self.request.response.addInfoNotification( |
1706 | + 'Linked %s project to %s source package.' % ( |
1707 | + product.displayname, self.source_package_name.name)) |
1708 | + |
1709 | def main_action(self, data): |
1710 | """See `MultiStepView`.""" |
1711 | self.product = self.create_product(data) |
1712 | self.notifyCommercialMailingList() |
1713 | notify(ObjectCreatedEvent(self.product)) |
1714 | - self.next_url = canonical_url(self.product) |
1715 | + self.link_source_package(self.product, data) |
1716 | + |
1717 | + if self._return_url is None: |
1718 | + self.next_url = canonical_url(self.product) |
1719 | + else: |
1720 | + self.next_url = self._return_url |
1721 | |
1722 | |
1723 | class ProductAddView(MultiStepView): |
1724 | @@ -2027,7 +2126,7 @@ |
1725 | |
1726 | driver = copy_field(IProduct['driver']) |
1727 | |
1728 | - transfer_to_registry = Bool( |
1729 | + transfer_to_registry = Bool( |
1730 | title=_("I do not want to maintain this project"), |
1731 | required=False, |
1732 | description=_( |
1733 | |
1734 | === modified file 'lib/lp/registry/browser/sourcepackage.py' |
1735 | --- lib/lp/registry/browser/sourcepackage.py 2010-07-02 14:34:58 +0000 |
1736 | +++ lib/lp/registry/browser/sourcepackage.py 2010-08-12 22:16:35 +0000 |
1737 | @@ -19,6 +19,8 @@ |
1738 | |
1739 | from apt_pkg import ParseSrcDepends |
1740 | from cgi import escape |
1741 | +import string |
1742 | +import urllib |
1743 | from z3c.ptcompat import ViewPageTemplateFile |
1744 | from zope.app.form.browser import DropdownWidget |
1745 | from zope.app.form.interfaces import IInputWidget |
1746 | @@ -35,12 +37,14 @@ |
1747 | |
1748 | from canonical.launchpad import helpers |
1749 | from canonical.launchpad.browser.multistep import MultiStepView, StepView |
1750 | + |
1751 | from lp.bugs.browser.bugtask import BugTargetTraversalMixin |
1752 | from canonical.launchpad.browser.packagerelationship import ( |
1753 | relationship_builder) |
1754 | from lp.answers.browser.questiontarget import ( |
1755 | QuestionTargetFacetMixin, QuestionTargetAnswersMenu) |
1756 | from lp.services.worlddata.interfaces.country import ICountry |
1757 | +from lp.registry.browser.product import ProjectAddStepOne |
1758 | from lp.registry.interfaces.packaging import IPackaging, IPackagingUtil |
1759 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
1760 | from lp.registry.interfaces.product import IProductSet |
1761 | @@ -62,6 +66,35 @@ |
1762 | from canonical.lazr.utils import smartquote |
1763 | |
1764 | |
1765 | +def get_register_upstream_url(source_package): |
1766 | + displayname = string.capwords(source_package.name.replace('-', ' ')) |
1767 | + distroseries_string = "%s/%s" % ( |
1768 | + source_package.distroseries.distribution.name, |
1769 | + source_package.distroseries.name) |
1770 | + params = { |
1771 | + '_return_url': canonical_url(source_package), |
1772 | + 'field.source_package_name': source_package.sourcepackagename.name, |
1773 | + 'field.distroseries': distroseries_string, |
1774 | + 'field.name': source_package.name, |
1775 | + 'field.displayname': displayname, |
1776 | + 'field.title': displayname, |
1777 | + 'field.__visited_steps__': ProjectAddStepOne.step_name, |
1778 | + 'field.actions.continue': 'Continue', |
1779 | + } |
1780 | + if len(source_package.releases) == 0: |
1781 | + params['field.summary'] = '' |
1782 | + else: |
1783 | + # This is based on the SourcePackageName.summary attribute, but |
1784 | + # it eliminates the binary.name and duplicate summary lines. |
1785 | + summary_set = set() |
1786 | + for binary in source_package.releases[0].sample_binary_packages: |
1787 | + summary_set.add(binary.summary) |
1788 | + params['field.summary'] = '\n'.join(sorted(summary_set)) |
1789 | + query_string = urllib.urlencode( |
1790 | + sorted(params.items()), doseq=True) |
1791 | + return '/projects/+new?%s' % query_string |
1792 | + |
1793 | + |
1794 | class SourcePackageNavigation(GetitemNavigation, BugTargetTraversalMixin): |
1795 | |
1796 | usedfor = ISourcePackage |
1797 | @@ -190,6 +223,10 @@ |
1798 | self.next_step = SourcePackageChangeUpstreamStepTwo |
1799 | self.request.form['product'] = data['product'] |
1800 | |
1801 | + @property |
1802 | + def register_upstream_url(self): |
1803 | + return get_register_upstream_url(self.context) |
1804 | + |
1805 | |
1806 | class SourcePackageChangeUpstreamStepTwo(ReturnToReferrerMixin, StepView): |
1807 | """A view to set the `IProductSeries` of a sourcepackage.""" |
1808 | @@ -345,7 +382,7 @@ |
1809 | def processForm(self): |
1810 | # look for an update to any of the things we track |
1811 | form = self.request.form |
1812 | - if form.has_key('packaging'): |
1813 | + if 'packaging' in form: |
1814 | if self.productseries_widget.hasValidInput(): |
1815 | new_ps = self.productseries_widget.getInputValue() |
1816 | # we need to create or update the packaging |
1817 | @@ -445,6 +482,7 @@ |
1818 | initial_focus_widget = None |
1819 | max_suggestions = 9 |
1820 | other_upstream = object() |
1821 | + register_upstream = object() |
1822 | |
1823 | def setUpFields(self): |
1824 | """See `LaunchpadFormView`.""" |
1825 | @@ -467,9 +505,12 @@ |
1826 | vocab_terms.append(SimpleTerm(product, product.name, description)) |
1827 | # Add an option to represent the user's decision to choose a |
1828 | # different project. Note that project names cannot be uppercase. |
1829 | - description = 'Choose another upstream project' |
1830 | - vocab_terms.append( |
1831 | - SimpleTerm(self.other_upstream, 'OTHER_UPSTREAM', description)) |
1832 | + vocab_terms.append( |
1833 | + SimpleTerm(self.other_upstream, 'OTHER_UPSTREAM', |
1834 | + 'Choose another upstream project')) |
1835 | + vocab_terms.append( |
1836 | + SimpleTerm(self.register_upstream, 'REGISTER_UPSTREAM', |
1837 | + 'Register the upstream project')) |
1838 | upstream_vocabulary = SimpleVocabulary(vocab_terms) |
1839 | |
1840 | self.form_fields = Fields( |
1841 | @@ -487,6 +528,11 @@ |
1842 | self.next_url = canonical_url( |
1843 | self.context, view_name="+edit-packaging") |
1844 | return |
1845 | + elif upstream is self.register_upstream: |
1846 | + # The user wants to create a new project. |
1847 | + url = get_register_upstream_url(self.context) |
1848 | + self.request.response.redirect(url) |
1849 | + return |
1850 | self.context.setPackaging(upstream.development_focus, self.user) |
1851 | self.request.response.addInfoNotification( |
1852 | 'The project %s was linked to this source package.' % |
1853 | |
1854 | === modified file 'lib/lp/registry/browser/tests/project-add-views.txt' |
1855 | --- lib/lp/registry/browser/tests/project-add-views.txt 2010-05-25 04:41:12 +0000 |
1856 | +++ lib/lp/registry/browser/tests/project-add-views.txt 2010-08-12 22:16:35 +0000 |
1857 | @@ -15,25 +15,24 @@ |
1858 | are forwarded in the form data to the second step. The title is also |
1859 | forwarded, but is only required by the Zope machinery, not the view. |
1860 | |
1861 | - >>> form = {'field.actions.continue': 'Continue'} |
1862 | + >>> from lp.registry.browser.product import ProjectAddStepOne |
1863 | + >>> form = { |
1864 | + ... 'field.actions.continue': 'Continue', |
1865 | + ... 'field.__visited_steps__': ProjectAddStepOne.step_name, |
1866 | + ... 'field.displayname': '', |
1867 | + ... 'field.name': '', |
1868 | + ... 'field.summary': '', |
1869 | + ... } |
1870 | |
1871 | >>> view = create_initialized_view(product_set, name='+new', form=form) |
1872 | - Traceback (most recent call last): |
1873 | - ... |
1874 | - KeyError: 'displayname' |
1875 | + >>> for error in view.view.errors: |
1876 | + ... print error |
1877 | + ('displayname', 'Name', RequiredMissing()) |
1878 | + ('name', 'URL', RequiredMissing()) |
1879 | + ('summary', u'Summary', RequiredMissing()) |
1880 | |
1881 | >>> form['field.displayname'] = 'Snowdog' |
1882 | - >>> view = create_initialized_view(product_set, name='+new', form=form) |
1883 | - Traceback (most recent call last): |
1884 | - ... |
1885 | - KeyError: 'name' |
1886 | - |
1887 | >>> form['field.name'] = 'snowdog' |
1888 | - >>> view = create_initialized_view(product_set, name='+new', form=form) |
1889 | - Traceback (most recent call last): |
1890 | - ... |
1891 | - KeyError: 'summary' |
1892 | - |
1893 | >>> form['field.summary'] = 'By-tor and the Snowdog' |
1894 | >>> view = create_initialized_view(product_set, name='+new', form=form) |
1895 | |
1896 | @@ -44,7 +43,6 @@ |
1897 | # steps individually. |
1898 | |
1899 | >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest |
1900 | - >>> from lp.registry.browser.product import ProjectAddStepOne |
1901 | |
1902 | >>> form['field.__visited_steps__'] = ProjectAddStepOne.step_name |
1903 | >>> request = LaunchpadTestRequest(form=form, method='POST') |
1904 | @@ -63,10 +61,12 @@ |
1905 | |
1906 | >>> from lp.registry.browser.product import ProjectAddStepTwo |
1907 | >>> form = { |
1908 | - ... 'displayname': 'Snowdog', |
1909 | - ... 'name': 'snowdog', |
1910 | - ... 'title': 'The Snowdog', |
1911 | - ... 'summary': 'By-tor and the Snowdog', |
1912 | + ... 'field.actions.continue': 'Continue', |
1913 | + ... 'field.__visited_steps__': ProjectAddStepTwo.step_name, |
1914 | + ... 'field.displayname': 'Snowdog', |
1915 | + ... 'field.name': 'snowdog', |
1916 | + ... 'field.title': 'The Snowdog', |
1917 | + ... 'field.summary': 'By-tor and the Snowdog', |
1918 | ... } |
1919 | |
1920 | >>> request = LaunchpadTestRequest(form=form, method='POST') |
1921 | @@ -90,7 +90,7 @@ |
1922 | existing projects for possible matches. By tweaking the project summary, we |
1923 | can see that there are search results available. |
1924 | |
1925 | - >>> form['summary'] = 'My Snowdog ate your Firefox' |
1926 | + >>> form['field.summary'] = 'My Snowdog ate your Firefox' |
1927 | |
1928 | >>> request = LaunchpadTestRequest(form=form, method='POST') |
1929 | >>> view = ProjectAddStepTwo(product_set, request) |
1930 | @@ -229,9 +229,9 @@ |
1931 | questions. |
1932 | <BLANKLINE> |
1933 | Sometimes new projects are licensed as 'Other/Open Source' because the |
1934 | - licensing decisions have not yet been made. If that is your situation we u= |
1935 | - rge |
1936 | - you to update the licensing in Launchpad as soon as you make that choice. |
1937 | + licensing decisions have not yet been made. If that is your situation |
1938 | + we urge you to update the licensing in Launchpad as soon as you make |
1939 | + that choice. |
1940 | <BLANKLINE> |
1941 | If the license for your project needs to be corrected you can do so by |
1942 | following the 'Change Details' link on your project's overview page. |
1943 | |
1944 | === modified file 'lib/lp/registry/browser/tests/sourcepackage-views.txt' |
1945 | --- lib/lp/registry/browser/tests/sourcepackage-views.txt 2010-05-13 18:55:10 +0000 |
1946 | +++ lib/lp/registry/browser/tests/sourcepackage-views.txt 2010-08-12 22:16:35 +0000 |
1947 | @@ -119,6 +119,7 @@ |
1948 | empty. |
1949 | |
1950 | >>> form = { |
1951 | + ... 'field.__visited_steps__': 'sourcepackage_change_upstream_step1', |
1952 | ... 'field.product': '', |
1953 | ... 'field.actions.continue': 'Continue', |
1954 | ... } |
1955 | @@ -133,12 +134,18 @@ |
1956 | but there is no notification message that the upstream link was updated. |
1957 | |
1958 | >>> form = { |
1959 | - ... 'field.productseries': 'bonkers/crazy', |
1960 | - ... 'field.actions.change': 'Change', |
1961 | + ... 'field.__visited_steps__': 'sourcepackage_change_upstream_step2', |
1962 | + ... 'field.product': 'bonkers', |
1963 | + ... 'field.productseries': 'crazy', |
1964 | + ... 'field.actions.continue': 'Continue', |
1965 | ... } |
1966 | >>> view = create_initialized_view( |
1967 | ... package, name='+edit-packaging', form=form, |
1968 | ... principal=product.owner) |
1969 | + >>> print view.view |
1970 | + <...SourcePackageChangeUpstreamStepTwo object...> |
1971 | + >>> print view.view.next_url |
1972 | + http://launchpad.dev/youbuntu/busy/+source/bonkers |
1973 | >>> view.view.errors |
1974 | [] |
1975 | |
1976 | @@ -199,6 +206,7 @@ |
1977 | Registered upstream project: |
1978 | Lernid |
1979 | Choose another upstream project |
1980 | + Register the upstream project |
1981 | |
1982 | The form does not steal focus because it is not the primary purpose of the |
1983 | page. |
1984 | @@ -229,6 +237,7 @@ |
1985 | Lernid... |
1986 | Lernid Dev... |
1987 | Choose another upstream project |
1988 | + Register the upstream project |
1989 | |
1990 | Choosing the "Choose another upstream project" option redirects the user |
1991 | to the +edit-packaging page where the user can search for a project. |
1992 | @@ -259,7 +268,8 @@ |
1993 | ... name='stinkyseries', product=product) |
1994 | >>> distroseries = factory.makeDistroRelease(name='wonky', |
1995 | ... distribution=distribution) |
1996 | - >>> sourcepackagename = factory.makeSourcePackageName(name='stinkypackage') |
1997 | + >>> sourcepackagename = factory.makeSourcePackageName( |
1998 | + ... name='stinkypackage') |
1999 | >>> package = factory.makeSourcePackage( |
2000 | ... sourcepackagename=sourcepackagename, distroseries=distroseries) |
2001 | |
2002 | @@ -360,3 +370,4 @@ |
2003 | match for this source package. Can you help us find one? |
2004 | Registered upstream project: |
2005 | Choose another upstream project |
2006 | + Register the upstream project |
2007 | |
2008 | === added file 'lib/lp/registry/browser/tests/test_sourcepackage_views.py' |
2009 | --- lib/lp/registry/browser/tests/test_sourcepackage_views.py 1970-01-01 00:00:00 +0000 |
2010 | +++ lib/lp/registry/browser/tests/test_sourcepackage_views.py 2010-08-12 22:16:35 +0000 |
2011 | @@ -0,0 +1,146 @@ |
2012 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
2013 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
2014 | + |
2015 | +"""Tests for SourcePackage view code.""" |
2016 | + |
2017 | +__metaclass__ = type |
2018 | + |
2019 | +import cgi |
2020 | +import urllib |
2021 | + |
2022 | +from zope.component import getUtility |
2023 | +from zope.interface import implements |
2024 | + |
2025 | +from canonical.testing import DatabaseFunctionalLayer |
2026 | + |
2027 | + |
2028 | +from lp.registry.browser.sourcepackage import get_register_upstream_url |
2029 | +from lp.registry.interfaces.distribution import IDistribution |
2030 | +from lp.registry.interfaces.distroseries import ( |
2031 | + IDistroSeries, IDistroSeriesSet) |
2032 | +from lp.registry.interfaces.sourcepackage import ISourcePackage |
2033 | +from lp.soyuz.tests.test_publishing import SoyuzTestPublisher |
2034 | +from lp.testing import TestCaseWithFactory |
2035 | + |
2036 | + |
2037 | +class TestSourcePackageViewHelpers(TestCaseWithFactory): |
2038 | + """Tests for SourcePackage view helper functions.""" |
2039 | + |
2040 | + layer = DatabaseFunctionalLayer |
2041 | + |
2042 | + def test_get_register_upstream_url_displayname(self): |
2043 | + distroseries = self.factory.makeDistroRelease( |
2044 | + distribution=self.factory.makeDistribution(name='zoobuntu'), |
2045 | + name='walrus') |
2046 | + source_package = self.factory.makeSourcePackage( |
2047 | + distroseries=distroseries, |
2048 | + sourcepackagename='python-super-package') |
2049 | + url = get_register_upstream_url(source_package) |
2050 | + expected_base = '/projects/+new' |
2051 | + expected_params = [ |
2052 | + ('_return_url', |
2053 | + 'http://launchpad.dev/zoobuntu/walrus/' |
2054 | + '+source/python-super-package'), |
2055 | + ('field.__visited_steps__', 'projectaddstep1'), |
2056 | + ('field.actions.continue', 'Continue'), |
2057 | + # The sourcepackagename 'python-super-package' is split on |
2058 | + # the hyphens, and each word is capitalized. |
2059 | + ('field.displayname', 'Python Super Package'), |
2060 | + ('field.distroseries', 'zoobuntu/walrus'), |
2061 | + ('field.name', 'python-super-package'), |
2062 | + # The summary is missing, since the source package doesn't |
2063 | + # have a binary package release, and parse_qsl() excludes |
2064 | + # empty params. |
2065 | + ('field.source_package_name', 'python-super-package'), |
2066 | + ('field.title', 'Python Super Package'), |
2067 | + ] |
2068 | + base, query = urllib.splitquery(url) |
2069 | + params = cgi.parse_qsl(query) |
2070 | + self.assertEqual((expected_base, expected_params), |
2071 | + (base, params)) |
2072 | + |
2073 | + def test_get_register_upstream_url_summary(self): |
2074 | + test_publisher = SoyuzTestPublisher() |
2075 | + test_data = test_publisher.makeSourcePackageWithBinaryPackageRelease() |
2076 | + source_package_name = ( |
2077 | + test_data['source_package'].sourcepackagename.name) |
2078 | + distroseries_id = test_data['distroseries'].id |
2079 | + test_publisher.updateDistroSeriesPackageCache( |
2080 | + test_data['distroseries']) |
2081 | + |
2082 | + # updateDistroSeriesPackageCache reconnects the db, so the |
2083 | + # objects need to be reloaded. |
2084 | + distroseries = getUtility(IDistroSeriesSet).get(distroseries_id) |
2085 | + source_package = distroseries.getSourcePackage(source_package_name) |
2086 | + url = get_register_upstream_url(source_package) |
2087 | + expected_base = '/projects/+new' |
2088 | + expected_params = [ |
2089 | + ('_return_url', |
2090 | + 'http://launchpad.dev/youbuntu/busy/+source/bonkers'), |
2091 | + ('field.__visited_steps__', 'projectaddstep1'), |
2092 | + ('field.actions.continue', 'Continue'), |
2093 | + ('field.displayname', 'Bonkers'), |
2094 | + ('field.distroseries', 'youbuntu/busy'), |
2095 | + ('field.name', 'bonkers'), |
2096 | + ('field.source_package_name', 'bonkers'), |
2097 | + ('field.summary', 'summary for flubber-bin\n' |
2098 | + + 'summary for flubber-lib'), |
2099 | + ('field.title', 'Bonkers'), |
2100 | + ] |
2101 | + base, query = urllib.splitquery(url) |
2102 | + params = cgi.parse_qsl(query) |
2103 | + self.assertEqual((expected_base, expected_params), |
2104 | + (base, params)) |
2105 | + |
2106 | + def test_get_register_upstream_url_summary_duplicates(self): |
2107 | + |
2108 | + class Faker: |
2109 | + # Fakes attributes easily. |
2110 | + def __init__(self, **kw): |
2111 | + self.__dict__.update(kw) |
2112 | + |
2113 | + class FakeSourcePackage(Faker): |
2114 | + # Interface necessary for canonical_url() call in |
2115 | + # get_register_upstream_url(). |
2116 | + implements(ISourcePackage) |
2117 | + |
2118 | + class FakeDistroSeries(Faker): |
2119 | + implements(IDistroSeries) |
2120 | + |
2121 | + class FakeDistribution(Faker): |
2122 | + implements(IDistribution) |
2123 | + |
2124 | + releases = Faker(sample_binary_packages=[ |
2125 | + Faker(summary='summary for foo'), |
2126 | + Faker(summary='summary for bar'), |
2127 | + Faker(summary='summary for baz'), |
2128 | + Faker(summary='summary for baz'), |
2129 | + ]) |
2130 | + source_package = FakeSourcePackage( |
2131 | + name='foo', |
2132 | + sourcepackagename=Faker(name='foo'), |
2133 | + distroseries=FakeDistroSeries( |
2134 | + name='walrus', |
2135 | + distribution=FakeDistribution(name='zoobuntu')), |
2136 | + releases=[releases]) |
2137 | + |
2138 | + url = get_register_upstream_url(source_package) |
2139 | + expected_base = '/projects/+new' |
2140 | + expected_params = [ |
2141 | + ('_return_url', |
2142 | + 'http://launchpad.dev/zoobuntu/walrus/+source/foo'), |
2143 | + ('field.__visited_steps__', 'projectaddstep1'), |
2144 | + ('field.actions.continue', 'Continue'), |
2145 | + ('field.displayname', 'Foo'), |
2146 | + ('field.distroseries', 'zoobuntu/walrus'), |
2147 | + ('field.name', 'foo'), |
2148 | + ('field.source_package_name', 'foo'), |
2149 | + ('field.summary', 'summary for bar\n' |
2150 | + + 'summary for baz\n' |
2151 | + + 'summary for foo'), |
2152 | + ('field.title', 'Foo'), |
2153 | + ] |
2154 | + base, query = urllib.splitquery(url) |
2155 | + params = cgi.parse_qsl(query) |
2156 | + self.assertEqual((expected_base, expected_params), |
2157 | + (base, params)) |
2158 | |
2159 | === modified file 'lib/lp/registry/model/sourcepackage.py' |
2160 | --- lib/lp/registry/model/sourcepackage.py 2010-08-02 21:38:00 +0000 |
2161 | +++ lib/lp/registry/model/sourcepackage.py 2010-08-12 22:16:35 +0000 |
2162 | @@ -39,7 +39,6 @@ |
2163 | from lp.registry.model.packaging import Packaging |
2164 | from lp.translations.model.potemplate import ( |
2165 | HasTranslationTemplatesMixin, |
2166 | - POTemplate, |
2167 | TranslationTemplatesCollection) |
2168 | from canonical.launchpad.interfaces.lpstorm import IStore |
2169 | from lp.soyuz.model.publishing import ( |
2170 | @@ -52,7 +51,6 @@ |
2171 | SourcePackageRelease) |
2172 | from lp.translations.model.translationimportqueue import ( |
2173 | HasTranslationImportsMixin) |
2174 | -from canonical.launchpad.helpers import shortlist |
2175 | from lp.soyuz.interfaces.buildrecords import IHasBuildRecords |
2176 | from lp.registry.interfaces.packaging import PackagingType |
2177 | from lp.registry.interfaces.distribution import NoPartnerArchive |
2178 | |
2179 | === modified file 'lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt' |
2180 | --- lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt 2010-05-18 17:05:29 +0000 |
2181 | +++ lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt 2010-08-12 22:16:35 +0000 |
2182 | @@ -1,4 +1,15 @@ |
2183 | -= Packaging = |
2184 | +Packaging |
2185 | +========= |
2186 | + |
2187 | +Create test data. |
2188 | + |
2189 | + >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher |
2190 | + >>> test_publisher = SoyuzTestPublisher() |
2191 | + >>> login('admin@canonical.com') |
2192 | + >>> test_data = test_publisher.makeSourcePackageWithBinaryPackageRelease() |
2193 | + >>> test_publisher.updateDistroSeriesPackageCache( |
2194 | + ... test_data['distroseries']) |
2195 | + >>> logout() |
2196 | |
2197 | No Privileges Person visit the distroseries upstream links page for Hoary |
2198 | and sees that pmount is not linked. |
2199 | @@ -20,6 +31,7 @@ |
2200 | match for this source package. Can you help us find one? |
2201 | Registered upstream project: |
2202 | Choose another upstream project |
2203 | + Register the upstream project |
2204 | |
2205 | No Privileges Person knows that the pmount package comes from the thunderbird |
2206 | project. He sets the upstream packaging link and sees that it is set. |
2207 | @@ -58,3 +70,93 @@ |
2208 | ... user_browser.contents, 'packages_list')) |
2209 | The Hoary Hedgehog Release (active development) ... |
2210 | 0.1-2 release (main) ... weeks ago |
2211 | + |
2212 | +Register a project from a source package |
2213 | +---------------------------------------- |
2214 | + |
2215 | +If an upstream project doesn't already exist in Launchpad, it can |
2216 | +be registered with data from the source package prefilling the first |
2217 | +step of the multistep form. |
2218 | + |
2219 | + >>> user_browser.open( |
2220 | + ... 'http://launchpad.dev/youbuntu/busy/+source/bonkers') |
2221 | + >>> user_browser.getControl( |
2222 | + ... 'Register the upstream project').selected = True |
2223 | + >>> user_browser.getControl("Link to Upstream Project").click() |
2224 | + >>> print user_browser.url.replace('&', '\n&') |
2225 | + http://launchpad.dev/projects/+new?_return_url=http...%2Bsource%2Fbonkers |
2226 | + &field.__visited_steps__=projectaddstep1 |
2227 | + &field.actions.continue=Continue |
2228 | + &field.displayname=Bonkers |
2229 | + &field.distroseries=youbuntu%2Fbusy |
2230 | + &field.name=bonkers |
2231 | + &field.source_package_name=bonkers |
2232 | + &field.summary=summary+for+flubber-bin%0Asummary+for+flubber-lib |
2233 | + &field.title=Bonkers |
2234 | + >>> print user_browser.getControl(name='field.name').value |
2235 | + bonkers |
2236 | + >>> print user_browser.getControl(name='field.displayname').value |
2237 | + Bonkers |
2238 | + >>> print user_browser.getControl(name='field.title').value |
2239 | + Bonkers |
2240 | + >>> print user_browser.getControl(name='field.summary').value |
2241 | + summary for flubber-bin |
2242 | + summary for flubber-lib |
2243 | + >>> print extract_text( |
2244 | + ... find_tag_by_id(user_browser.contents, 'step-title')) |
2245 | + Step 2 (of 2): Check for duplicate projects |
2246 | + |
2247 | +If the user selects "Choose another upstream project" and then finds out |
2248 | +that the project doesn't exist, there is a also a link on the |
2249 | ++edit-packaging page to register the project. |
2250 | + |
2251 | + >>> user_browser.open( |
2252 | + ... 'http://launchpad.dev/youbuntu/busy/+source/bonkers/') |
2253 | + >>> user_browser.getControl( |
2254 | + ... 'Choose another upstream project').selected = True |
2255 | + >>> user_browser.getControl("Link to Upstream Project").click() |
2256 | + >>> print user_browser.url |
2257 | + http://launchpad.dev/youbuntu/busy/+source/bonkers/+edit-packaging |
2258 | + |
2259 | + >>> user_browser.getLink("Register the upstream project").click() |
2260 | + >>> print user_browser.url.replace('&', '\n&') |
2261 | + http://launchpad.dev/projects/+new?_return_url=http...%2Bsource%2Fbonkers |
2262 | + &field.__visited_steps__=projectaddstep1 |
2263 | + &field.actions.continue=Continue |
2264 | + &field.displayname=Bonkers |
2265 | + &field.distroseries=youbuntu%2Fbusy |
2266 | + &field.name=bonkers |
2267 | + &field.source_package_name=bonkers |
2268 | + &field.summary=summary+for+flubber-bin%0Asummary+for+flubber-lib |
2269 | + &field.title=Bonkers |
2270 | + >>> print user_browser.getControl(name='field.name').value |
2271 | + bonkers |
2272 | + >>> print user_browser.getControl(name='field.displayname').value |
2273 | + Bonkers |
2274 | + >>> print user_browser.getControl(name='field.title').value |
2275 | + Bonkers |
2276 | + >>> print user_browser.getControl(name='field.summary').value |
2277 | + summary for flubber-bin |
2278 | + summary for flubber-lib |
2279 | + >>> print extract_text( |
2280 | + ... find_tag_by_id(user_browser.contents, 'step-title')) |
2281 | + Step 2 (of 2): Check for duplicate projects |
2282 | + |
2283 | +If there are no problems with the prefilled data, then the license |
2284 | +just needs to be selected. The user will then be redirected back |
2285 | +to the source package page and an informational message will be displayed. |
2286 | + |
2287 | + >>> user_browser.getControl(name='field.licenses').value = ['BSD'] |
2288 | + >>> user_browser.getControl( |
2289 | + ... "Complete registration and link to bonkers package").click() |
2290 | + >>> print user_browser.url |
2291 | + http://launchpad.dev/youbuntu/busy/+source/bonkers |
2292 | + >>> for tag in find_tags_by_class( |
2293 | + ... user_browser.contents, 'informational message'): |
2294 | + ... print extract_text(tag) |
2295 | + Linked Bonkers project to bonkers source package. |
2296 | + >>> print extract_text( |
2297 | + ... find_tag_by_id(user_browser.contents, 'upstreams')) |
2298 | + Bonkers ⇒ trunk |
2299 | + Change upstream link |
2300 | + Remove upstream link... |
2301 | |
2302 | === modified file 'lib/lp/registry/templates/product-new.pt' |
2303 | --- lib/lp/registry/templates/product-new.pt 2010-05-12 19:06:17 +0000 |
2304 | +++ lib/lp/registry/templates/product-new.pt 2010-08-12 22:16:35 +0000 |
2305 | @@ -299,8 +299,8 @@ |
2306 | <img src="/@@/info" /> |
2307 | There are similar projects already registered in Launchpad. |
2308 | Is project |
2309 | - <strong><tal:displayname tal:replace="view/request/displayname" /> |
2310 | - (<tal:name tal:replace="view/request/name" />)</strong> |
2311 | + <strong><tal:displayname tal:replace="view/request/field.displayname" /> |
2312 | + (<tal:name tal:replace="view/request/field.name" />)</strong> |
2313 | one of these? |
2314 | </div> |
2315 | |
2316 | @@ -326,8 +326,8 @@ |
2317 | tal:condition="view/search_results_count" |
2318 | >Registration details</h3> |
2319 | Select the licenses for project |
2320 | - <strong><tal:displayname tal:replace="view/request/displayname" /> |
2321 | - (<tal:name tal:replace="view/request/name" />)</strong> |
2322 | + <strong><tal:displayname tal:replace="view/request/field.displayname" /> |
2323 | + (<tal:name tal:replace="view/request/field.name" />)</strong> |
2324 | and complete the registration. You may also update the project's |
2325 | title and summary. |
2326 | </div> |
2327 | |
2328 | === modified file 'lib/lp/registry/templates/sourcepackage-edit-packaging.pt' |
2329 | --- lib/lp/registry/templates/sourcepackage-edit-packaging.pt 2010-02-16 17:37:36 +0000 |
2330 | +++ lib/lp/registry/templates/sourcepackage-edit-packaging.pt 2010-08-12 22:16:35 +0000 |
2331 | @@ -27,6 +27,26 @@ |
2332 | If you need a new series created, contact the owner of |
2333 | <a tal:content="structure view/product/fmt:link"/>. |
2334 | </div> |
2335 | + |
2336 | + <div metal:fill-slot="buttons"> |
2337 | + <input tal:repeat="action view/actions" |
2338 | + tal:replace="structure action/render" |
2339 | + /> |
2340 | + or |
2341 | + <tal:comment condition="nothing"> |
2342 | + This template is for a multistep view, and only the first |
2343 | + step provides the register_upstream_url. |
2344 | + </tal:comment> |
2345 | + <a id="register-upstream-link" |
2346 | + tal:condition="view/register_upstream_url | nothing" |
2347 | + tal:attributes="href view/register_upstream_url"> |
2348 | + Register the upstream project |
2349 | + </a> |
2350 | + <tal:has-cancel-link condition="view/cancel_url"> |
2351 | + or |
2352 | + <a tal:attributes="href view/cancel_url">Cancel</a> |
2353 | + </tal:has-cancel-link> |
2354 | + </div> |
2355 | </div> |
2356 | |
2357 | </div> |
2358 | |
2359 | === modified file 'lib/lp/soyuz/scripts/soyuz_process_upload.py' |
2360 | --- lib/lp/soyuz/scripts/soyuz_process_upload.py 2010-05-04 15:38:08 +0000 |
2361 | +++ lib/lp/soyuz/scripts/soyuz_process_upload.py 2010-08-12 22:16:35 +0000 |
2362 | @@ -8,6 +8,7 @@ |
2363 | |
2364 | import os |
2365 | |
2366 | +from lp.archiveuploader.uploadpolicy import findPolicyByOptions |
2367 | from lp.archiveuploader.uploadprocessor import UploadProcessor |
2368 | from lp.services.scripts.base import ( |
2369 | LaunchpadCronScript, LaunchpadScriptFailure) |
2370 | @@ -74,8 +75,13 @@ |
2371 | "%s is not a directory" % self.options.base_fsroot) |
2372 | |
2373 | self.logger.debug("Initialising connection.") |
2374 | - UploadProcessor( |
2375 | - self.options, self.txn, self.logger).processUploadQueue() |
2376 | + def getPolicy(distro): |
2377 | + self.options.distro = distro.name |
2378 | + return findPolicyByOptions(self.options) |
2379 | + processor = UploadProcessor(self.options.base_fsroot, |
2380 | + self.options.dryrun, self.options.nomails, self.options.keep, |
2381 | + getPolicy, self.txn, self.logger) |
2382 | + processor.processUploadQueue(self.options.leafname) |
2383 | |
2384 | @property |
2385 | def lockfilename(self): |
2386 | |
2387 | === modified file 'lib/lp/soyuz/tests/test_publishing.py' |
2388 | --- lib/lp/soyuz/tests/test_publishing.py 2010-08-07 00:36:52 +0000 |
2389 | +++ lib/lp/soyuz/tests/test_publishing.py 2010-08-12 22:16:35 +0000 |
2390 | @@ -10,6 +10,7 @@ |
2391 | from StringIO import StringIO |
2392 | import tempfile |
2393 | |
2394 | +import transaction |
2395 | import pytz |
2396 | from zope.component import getUtility |
2397 | from zope.security.proxy import removeSecurityProxy |
2398 | @@ -18,13 +19,16 @@ |
2399 | from canonical.database.constants import UTC_NOW |
2400 | from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet |
2401 | from canonical.launchpad.webapp.errorlog import ErrorReportingUtility |
2402 | +from canonical.testing.layers import reconnect_stores |
2403 | from canonical.testing import ( |
2404 | DatabaseFunctionalLayer, LaunchpadZopelessLayer) |
2405 | + |
2406 | from lp.app.errors import NotFoundError |
2407 | from lp.archivepublisher.config import Config |
2408 | from lp.archivepublisher.diskpool import DiskPool |
2409 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
2410 | from lp.registry.interfaces.distribution import IDistributionSet |
2411 | +from lp.registry.interfaces.distroseries import IDistroSeriesSet |
2412 | from lp.registry.interfaces.person import IPersonSet |
2413 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
2414 | from lp.registry.interfaces.sourcepackage import SourcePackageUrgency |
2415 | @@ -476,6 +480,67 @@ |
2416 | |
2417 | return source |
2418 | |
2419 | + def makeSourcePackageWithBinaryPackageRelease(self): |
2420 | + """Make test data for SourcePackage.summary. |
2421 | + |
2422 | + The distroseries that is returned from this method needs to be |
2423 | + passed into updateDistroseriesPackageCache() so that |
2424 | + SourcePackage.summary can be populated. |
2425 | + """ |
2426 | + distribution = self.factory.makeDistribution( |
2427 | + name='youbuntu', displayname='Youbuntu') |
2428 | + distroseries = self.factory.makeDistroRelease(name='busy', |
2429 | + distribution=distribution) |
2430 | + source_package_name = self.factory.makeSourcePackageName( |
2431 | + name='bonkers') |
2432 | + source_package = self.factory.makeSourcePackage( |
2433 | + sourcepackagename=source_package_name, |
2434 | + distroseries=distroseries) |
2435 | + component = self.factory.makeComponent('multiverse') |
2436 | + das = self.factory.makeDistroArchSeries( |
2437 | + distroseries=distroseries) |
2438 | + spph = self.factory.makeSourcePackagePublishingHistory( |
2439 | + sourcepackagename=source_package_name, |
2440 | + distroseries=distroseries, |
2441 | + component=component) |
2442 | + |
2443 | + for name in ('flubber-bin', 'flubber-lib'): |
2444 | + binary_package_name = self.factory.makeBinaryPackageName(name) |
2445 | + build = self.factory.makeBinaryPackageBuild( |
2446 | + source_package_release=spph.sourcepackagerelease, |
2447 | + archive=self.factory.makeArchive(), |
2448 | + distroarchseries=das) |
2449 | + bpr = self.factory.makeBinaryPackageRelease( |
2450 | + binarypackagename=binary_package_name, |
2451 | + summary='summary for %s' % name, |
2452 | + build=build, component=component) |
2453 | + bpph = self.factory.makeBinaryPackagePublishingHistory( |
2454 | + binarypackagerelease=bpr, distroarchseries=das) |
2455 | + return dict( |
2456 | + distroseries=distroseries, |
2457 | + source_package=source_package) |
2458 | + |
2459 | + def updateDistroSeriesPackageCache( |
2460 | + self, distroseries, restore_db_connection='launchpad'): |
2461 | + # XXX: EdwinGrubbs 2010-08-04 bug=396419. Currently there is no |
2462 | + # test api call to switchDbUser that works for non-zopeless layers. |
2463 | + # When bug 396419 is fixed, we can instead use |
2464 | + # DatabaseLayer.switchDbUser() instead of reconnect_stores() |
2465 | + transaction.commit() |
2466 | + reconnect_stores(config.statistician.dbuser) |
2467 | + distroseries = getUtility(IDistroSeriesSet).get(distroseries.id) |
2468 | + |
2469 | + class TestLogger: |
2470 | + # Silent logger. |
2471 | + def debug(self, msg): |
2472 | + pass |
2473 | + distroseries.updateCompletePackageCache( |
2474 | + archive=distroseries.distribution.main_archive, |
2475 | + ztm=transaction, |
2476 | + log=TestLogger()) |
2477 | + transaction.commit() |
2478 | + reconnect_stores(restore_db_connection) |
2479 | + |
2480 | |
2481 | class TestNativePublishingBase(TestCaseWithFactory, SoyuzTestPublisher): |
2482 | layer = LaunchpadZopelessLayer |
2483 | |
2484 | === modified file 'lib/lp/vostok/browser/configure.zcml' |
2485 | --- lib/lp/vostok/browser/configure.zcml 2010-08-12 22:16:33 +0000 |
2486 | +++ lib/lp/vostok/browser/configure.zcml 2010-08-12 22:16:35 +0000 |
2487 | @@ -5,14 +5,6 @@ |
2488 | xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc" |
2489 | i18n_domain="launchpad"> |
2490 | |
2491 | - <browser:page |
2492 | - for="*" |
2493 | - name="main_template" |
2494 | - template="../templates/main-template.pt" |
2495 | - permission="zope.Public" |
2496 | - layer="lp.vostok.publisher.VostokLayer" |
2497 | - /> |
2498 | - |
2499 | <browser:defaultView |
2500 | for="lp.vostok.publisher.IVostokRoot" |
2501 | name="+index" |
2502 | @@ -32,4 +24,6 @@ |
2503 | classes="VostokRootNavigation" |
2504 | /> |
2505 | |
2506 | + <adapter factory="lp.vostok.browser.root.VostokLayerToMainTemplateAdapter" /> |
2507 | + |
2508 | </configure> |
2509 | |
2510 | === modified file 'lib/lp/vostok/browser/root.py' |
2511 | --- lib/lp/vostok/browser/root.py 2010-07-30 03:50:17 +0000 |
2512 | +++ lib/lp/vostok/browser/root.py 2010-08-12 22:16:35 +0000 |
2513 | @@ -6,19 +6,37 @@ |
2514 | __metaclass__ = type |
2515 | __all__ = [ |
2516 | 'VostokRootView', |
2517 | + 'VostokLayerToMainTemplateAdapter', |
2518 | ] |
2519 | |
2520 | -from zope.component import getUtility |
2521 | +import os |
2522 | + |
2523 | +from zope.component import adapts, getUtility |
2524 | +from zope.interface import implements |
2525 | |
2526 | from canonical.launchpad.webapp import LaunchpadView |
2527 | +from canonical.launchpad.webapp.tales import IMainTemplateFile |
2528 | |
2529 | from lp.registry.interfaces.distribution import IDistributionSet |
2530 | |
2531 | +from lp.vostok.publisher import VostokLayer |
2532 | + |
2533 | |
2534 | class VostokRootView(LaunchpadView): |
2535 | """The view for the Vostok root object.""" |
2536 | |
2537 | + page_title = 'Vostok' |
2538 | + |
2539 | @property |
2540 | def distributions(self): |
2541 | """An iterable of all registered distributions.""" |
2542 | return getUtility(IDistributionSet) |
2543 | + |
2544 | + |
2545 | +class VostokLayerToMainTemplateAdapter: |
2546 | + adapts(VostokLayer) |
2547 | + implements(IMainTemplateFile) |
2548 | + |
2549 | + def __init__(self, context): |
2550 | + here = os.path.dirname(os.path.realpath(__file__)) |
2551 | + self.path = os.path.join(here, '../templates/main-template.pt') |
2552 | |
2553 | === renamed file 'lib/lp/vostok/browser/tests/test_main_template.py' => 'lib/lp/vostok/browser/tests/test_base_template.py' |
2554 | --- lib/lp/vostok/browser/tests/test_main_template.py 2010-07-29 04:38:37 +0000 |
2555 | +++ lib/lp/vostok/browser/tests/test_base_template.py 2010-08-12 22:16:35 +0000 |
2556 | @@ -1,33 +1,31 @@ |
2557 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
2558 | # GNU Affero General Public License version 3 (see the file LICENSE). |
2559 | |
2560 | -"""Tests for the vostok 'main_template'.""" |
2561 | +"""Tests for the vostok 'view/macro:page' TALES adapter.""" |
2562 | |
2563 | __metaclass__ = type |
2564 | |
2565 | -import unittest |
2566 | - |
2567 | from zope.component import getMultiAdapter |
2568 | +from zope.traversing.interfaces import IPathAdapter |
2569 | |
2570 | from canonical.testing.layers import FunctionalLayer |
2571 | |
2572 | from lp.testing import TestCase |
2573 | from lp.vostok.browser.tests.request import VostokTestRequest |
2574 | - |
2575 | - |
2576 | -class TestMainTemplate(TestCase): |
2577 | - """Tests for our main template.""" |
2578 | +from lp.vostok.publisher import VostokRoot |
2579 | + |
2580 | + |
2581 | +class TestPageMacroDispatcher(TestCase): |
2582 | |
2583 | layer = FunctionalLayer |
2584 | |
2585 | - def test_main_template_defines_master_macro(self): |
2586 | - # The main template, which is registered as a view for any object at |
2587 | - # all when in the VostokLayer, defines a 'master' macro. |
2588 | - adapter = getMultiAdapter( |
2589 | - (None, VostokTestRequest()), name='main_template') |
2590 | - self.assertEqual(['master'], adapter.index.macros.keys()) |
2591 | - self.assertIn('lp/vostok', adapter.index.filename) |
2592 | - |
2593 | - |
2594 | -def test_suite(): |
2595 | - return unittest.TestLoader().loadTestsFromName(__name__) |
2596 | + def test_base_template(self): |
2597 | + # For requests on the vostok vhost (i.e. IVostokLayer requests), the |
2598 | + # base template used is the vostok one. |
2599 | + root_view = getMultiAdapter( |
2600 | + (VostokRoot(), VostokTestRequest()), name='+index') |
2601 | + adapter = getMultiAdapter([root_view], IPathAdapter, name='macro') |
2602 | + self.assertIn('lp/vostok', adapter.base.filename) |
2603 | + # The vostok base template defines a 'master' macro as the adapter |
2604 | + # expects. |
2605 | + self.assertIn('master', adapter.base.macros.keys()) |
2606 | |
2607 | === modified file 'lib/lp/vostok/browser/tests/test_root.py' |
2608 | --- lib/lp/vostok/browser/tests/test_root.py 2010-07-30 03:50:17 +0000 |
2609 | +++ lib/lp/vostok/browser/tests/test_root.py 2010-08-12 22:16:35 +0000 |
2610 | @@ -5,12 +5,14 @@ |
2611 | |
2612 | __metaclass__ = type |
2613 | |
2614 | +import os |
2615 | import unittest |
2616 | |
2617 | from zope.app.publisher.browser import getDefaultViewName |
2618 | |
2619 | from canonical.testing.layers import DatabaseFunctionalLayer, FunctionalLayer |
2620 | from canonical.launchpad.testing.pages import extract_text, find_tag_by_id |
2621 | +from canonical.launchpad.webapp.tales import IMainTemplateFile |
2622 | |
2623 | from lp.testing import TestCase, TestCaseWithFactory |
2624 | from lp.testing.views import create_initialized_view |
2625 | @@ -19,7 +21,6 @@ |
2626 | from lp.vostok.publisher import VostokLayer, VostokRoot |
2627 | |
2628 | |
2629 | - |
2630 | class TestRootRegistrations(TestCase): |
2631 | """Test the registration of views for `VostokRoot`.""" |
2632 | |
2633 | @@ -72,5 +73,15 @@ |
2634 | self.assertEqual(distro.displayname, extract_text(link)) |
2635 | |
2636 | |
2637 | +class TestVostokLayerToMainTemplateAdapter(TestCase): |
2638 | + |
2639 | + layer = FunctionalLayer |
2640 | + |
2641 | + def test_path(self): |
2642 | + main_template_path = IMainTemplateFile(VostokTestRequest()).path |
2643 | + self.assertIn('lp/vostok', main_template_path) |
2644 | + self.assertTrue(os.path.isfile(main_template_path)) |
2645 | + |
2646 | + |
2647 | def test_suite(): |
2648 | return unittest.TestLoader().loadTestsFromName(__name__) |
2649 | |
2650 | === modified file 'lib/lp/vostok/templates/main-template.pt' |
2651 | --- lib/lp/vostok/templates/main-template.pt 2010-07-30 04:38:05 +0000 |
2652 | +++ lib/lp/vostok/templates/main-template.pt 2010-08-12 22:16:35 +0000 |
2653 | @@ -3,13 +3,16 @@ |
2654 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
2655 | define-macro="master"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
2656 | <html xmlns="http://www.w3.org/1999/xhtml"> |
2657 | + |
2658 | <head> |
2659 | - <!-- Obviously, we'll need to do something better here. --> |
2660 | - <title>Vostok page</title> |
2661 | + |
2662 | + <title tal:content="view/fmt:pagetitle">Page Title</title> |
2663 | + |
2664 | </head> |
2665 | + |
2666 | <body> |
2667 | <h1 metal:define-slot="heading" /> |
2668 | - <div metal:define-slot="content" /> |
2669 | + <div metal:define-slot="main" /> |
2670 | </body> |
2671 | </html> |
2672 | </metal:page> |
2673 | |
2674 | === modified file 'lib/lp/vostok/templates/root.pt' |
2675 | --- lib/lp/vostok/templates/root.pt 2010-07-15 10:11:03 +0000 |
2676 | +++ lib/lp/vostok/templates/root.pt 2010-08-12 22:16:35 +0000 |
2677 | @@ -3,13 +3,13 @@ |
2678 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
2679 | xmlns:metal="http://xml.zope.org/namespaces/metal" |
2680 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
2681 | - metal:use-macro="context/@@main_template/master" |
2682 | + metal:use-macro="view/macro:page/main_only" |
2683 | i18n:domain="vostok"> |
2684 | <body> |
2685 | <tal:heading metal:fill-slot="heading"> |
2686 | <h1>Vostok</h1> |
2687 | </tal:heading> |
2688 | - <tal:content metal:fill-slot="content"> |
2689 | + <tal:content metal:fill-slot="main"> |
2690 | <ul id="distro-list"> |
2691 | <tal:loop tal:repeat="distro view/distributions"> |
2692 | <li tal:content="structure distro/fmt:link" /> |
2693 | |
2694 | === modified file 'utilities/sourcedeps.conf' |
2695 | --- utilities/sourcedeps.conf 2010-08-03 14:59:22 +0000 |
2696 | +++ utilities/sourcedeps.conf 2010-08-12 22:16:35 +0000 |
2697 | @@ -12,5 +12,6 @@ |
2698 | pygettextpo lp:~launchpad-pqm/pygettextpo/trunk;revno=24 |
2699 | pygpgme lp:~launchpad-pqm/pygpgme/devel;revno=49 |
2700 | subvertpy lp:~launchpad-pqm/subvertpy/trunk;revno=2042 |
2701 | +python-debian lp:~launchpad-pqm/python-debian/devel;revno=185 |
2702 | testresources lp:~launchpad-pqm/testresources/dev;revno=16 |
2703 | shipit lp:~launchpad-pqm/shipit/trunk;revno=8909 optional |
This all looks good to me. I suppose that you might want to ask Gary or Curtis what they think. It's not like you can land this for another few days anyway :-)