Merge lp:~salgado/launchpad/request-to-base-template-adapter into lp:launchpad/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
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) Approve
Michael Hudson-Doyle Approve
Review via email: mp+31982@code.launchpad.net

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
Michael Hudson-Doyle (mwhudson) wrote :

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 :-)

review: Approve
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://paste.ubuntu.com/476128/

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 TestVostokLayerToMainTemplateAdapter that tests IMainTemplate adapting a layer besides Vostok. That would be good to have.

-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
=== modified file '.bzrignore'
--- .bzrignore 2010-07-15 15:57:40 +0000
+++ .bzrignore 2010-08-12 22:16:35 +0000
@@ -56,7 +56,6 @@
56.bazaar56.bazaar
57.cache57.cache
58.subversion58.subversion
59lib/canonical/buildd/launchpad-files
60.testrepository59.testrepository
61.memcache.pid60.memcache.pid
62./pipes61./pipes
@@ -77,3 +76,4 @@
77lib/canonical/launchpad-buildd_*_source.build76lib/canonical/launchpad-buildd_*_source.build
78lib/canonical/launchpad-buildd_*_source.changes77lib/canonical/launchpad-buildd_*_source.changes
79lib/canonical/buildd/debian/*78lib/canonical/buildd/debian/*
79lib/canonical/buildd/launchpad-files/*
8080
=== renamed file 'daemons/buildd-slave-example.conf' => 'lib/canonical/buildd/buildd-slave-example.conf'
=== modified file 'lib/canonical/buildd/debian/changelog'
--- lib/canonical/buildd/debian/changelog 2010-08-05 21:12:36 +0000
+++ lib/canonical/buildd/debian/changelog 2010-08-12 22:16:35 +0000
@@ -1,9 +1,19 @@
1launchpad-buildd (68) UNRELEASED; urgency=low1launchpad-buildd (68) UNRELEASED; urgency=low
22
3 [ William Grant ]
3 * Take an 'arch_tag' argument, so the master can override the slave4 * Take an 'arch_tag' argument, so the master can override the slave
4 architecture.5 architecture.
56
6 -- William Grant <wgrant@ubuntu.com> Sun, 01 Aug 2010 22:00:32 +10007 [ Jelmer Vernooij ]
8
9 * Explicitly use source format 1.0.
10 * Add LSB information to init script.
11 * Use debhelper >= 5 (available in dapper, not yet deprecated in
12 maverick).
13 * Fix spelling in description.
14 * Install example buildd configuration.
15
16 -- Jelmer Vernooij <jelmer@canonical.com> Thu, 12 Aug 2010 17:04:14 +0200
717
8launchpad-buildd (67) hardy-cat; urgency=low18launchpad-buildd (67) hardy-cat; urgency=low
919
1020
=== added file 'lib/canonical/buildd/debian/compat'
--- lib/canonical/buildd/debian/compat 1970-01-01 00:00:00 +0000
+++ lib/canonical/buildd/debian/compat 2010-08-12 22:16:35 +0000
@@ -0,0 +1,1 @@
15
02
=== modified file 'lib/canonical/buildd/debian/control'
--- lib/canonical/buildd/debian/control 2010-05-19 15:50:27 +0000
+++ lib/canonical/buildd/debian/control 2010-08-12 22:16:35 +0000
@@ -3,15 +3,15 @@
3Priority: extra3Priority: extra
4Maintainer: Adam Conrad <adconrad@ubuntu.com>4Maintainer: Adam Conrad <adconrad@ubuntu.com>
5Standards-Version: 3.5.95Standards-Version: 3.5.9
6Build-Depends-Indep: debhelper (>= 4)6Build-Depends-Indep: debhelper (>= 5)
77
8Package: launchpad-buildd8Package: launchpad-buildd
9Section: misc9Section: misc
10Architecture: all10Architecture: all
11Depends: python-twisted-core, python-twisted-web, debootstrap, dpkg-dev, linux32, file, bzip2, sudo, ntpdate, adduser, apt-transport-https, lsb-release, apache211Depends: python-twisted-core, python-twisted-web, debootstrap, dpkg-dev, linux32, file, bzip2, sudo, ntpdate, adduser, apt-transport-https, lsb-release, apache2, ${misc:Depends}
12Description: Launchpad buildd slave12Description: Launchpad buildd slave
13 This is the launchpad buildd slave package. It contains everything needed to13 This is the launchpad buildd slave package. It contains everything needed to
14 get a launchpad buildd going apart from the database manipulation required to14 get a launchpad buildd going apart from the database manipulation required to
15 tell launchpad about the slave instance. If you are creating more than one15 tell launchpad about the slave instance. If you are creating more than one
16 slave instance on the same computer, be sure to give them independant configs16 slave instance on the same computer, be sure to give them independent configs
17 and independant filecaches etc.17 and independent filecaches etc.
1818
=== added file 'lib/canonical/buildd/debian/launchpad-buildd.examples'
--- lib/canonical/buildd/debian/launchpad-buildd.examples 1970-01-01 00:00:00 +0000
+++ lib/canonical/buildd/debian/launchpad-buildd.examples 2010-08-12 22:16:35 +0000
@@ -0,0 +1,1 @@
1buildd-slave-example.conf
02
=== modified file 'lib/canonical/buildd/debian/launchpad-buildd.init'
--- lib/canonical/buildd/debian/launchpad-buildd.init 2010-03-31 17:10:21 +0000
+++ lib/canonical/buildd/debian/launchpad-buildd.init 2010-08-12 22:16:35 +0000
@@ -8,6 +8,16 @@
8#8#
9# Author: Daniel Silverstone <daniel.silverstone@canonical.com>9# Author: Daniel Silverstone <daniel.silverstone@canonical.com>
1010
11### BEGIN INIT INFO
12# Provides: launchpad_buildd
13# Required-Start: $local_fs $network $syslog $time
14# Required-Stop: $local_fs $network $syslog $time
15# Default-Start: 2 3 4 5
16# Default-Stop: 0 1 6
17# X-Interactive: false
18# Short-Description: Start/stop launchpad buildds
19### END INIT INFO
20
11set -e21set -e
1222
13PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin23PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
1424
=== modified file 'lib/canonical/buildd/debian/rules'
--- lib/canonical/buildd/debian/rules 2010-07-18 08:49:02 +0000
+++ lib/canonical/buildd/debian/rules 2010-08-12 22:16:35 +0000
@@ -3,7 +3,6 @@
3# Copyright 2009, 2010 Canonical Ltd. This software is licensed under the3# Copyright 2009, 2010 Canonical Ltd. This software is licensed under the
4# GNU Affero General Public License version 3 (see the file LICENSE).4# GNU Affero General Public License version 3 (see the file LICENSE).
55
6export DH_COMPAT=4
7export DH_OPTIONS6export DH_OPTIONS
87
9# This is an incomplete debian rules file for making the launchpad-buildd deb8# This is an incomplete debian rules file for making the launchpad-buildd deb
@@ -41,6 +40,7 @@
41 etc/launchpad-buildd \40 etc/launchpad-buildd \
42 usr/share/launchpad-buildd/canonical/launchpad/daemons \41 usr/share/launchpad-buildd/canonical/launchpad/daemons \
43 usr/share/doc/launchpad-buildd42 usr/share/doc/launchpad-buildd
43 dh_installexamples
4444
45 # Do installs here45 # Do installs here
46 touch $(pytarget)/../launchpad/__init__.py46 touch $(pytarget)/../launchpad/__init__.py
@@ -89,10 +89,11 @@
89clean:89clean:
90 dh_clean90 dh_clean
9191
92package:92prepare:
93 mkdir -p launchpad-files
94 install -m644 $(daemons)/buildd-slave.tac launchpad-files/buildd-slave.tac93 install -m644 $(daemons)/buildd-slave.tac launchpad-files/buildd-slave.tac
95 cp ../launchpad/daemons/tachandler.py launchpad-files/tachandler.py94 cp ../launchpad/daemons/tachandler.py launchpad-files/tachandler.py
95
96package: prepare
96 debuild -uc -us -S97 debuild -uc -us -S
9798
98build:99build:
99100
=== added directory 'lib/canonical/buildd/debian/source'
=== added file 'lib/canonical/buildd/debian/source/format'
--- lib/canonical/buildd/debian/source/format 1970-01-01 00:00:00 +0000
+++ lib/canonical/buildd/debian/source/format 2010-08-12 22:16:35 +0000
@@ -0,0 +1,1 @@
11.0
02
=== added directory 'lib/canonical/buildd/launchpad-files'
=== modified file 'lib/canonical/launchpad/browser/multistep.py'
--- lib/canonical/launchpad/browser/multistep.py 2010-05-12 19:06:17 +0000
+++ lib/canonical/launchpad/browser/multistep.py 2010-08-12 22:16:35 +0000
@@ -93,6 +93,14 @@
93 view.total_steps = self.total_steps93 view.total_steps = self.total_steps
94 view.is_step = self.getIsStepDict()94 view.is_step = self.getIsStepDict()
95 self.step_number += 195 self.step_number += 1
96
97 action_required = None
98 for name in self.request.form.keys():
99 if name.startswith('field.actions.'):
100 action_required = (name, self.request.form[name])
101 break
102
103 action_taken = view.action_taken
96 while view.next_step is not None:104 while view.next_step is not None:
97 view = view.next_step(self.context, self.request)105 view = view.next_step(self.context, self.request)
98 assert isinstance(view, StepView), 'Not a StepView: %s' % view106 assert isinstance(view, StepView), 'Not a StepView: %s' % view
@@ -102,8 +110,19 @@
102 view.is_step = self.getIsStepDict()110 view.is_step = self.getIsStepDict()
103 self.step_number += 1111 self.step_number += 1
104 view.injectStepNameInRequest()112 view.injectStepNameInRequest()
113 if view.action_taken is not None:
114 action_taken = view.action_taken
115
105 self.view = view116 self.view = view
106117
118 if action_required is not None and action_taken is None:
119 # This is mostly useful for catching tests that pass
120 # in invalid form data via a dictionary instead of
121 # using a test browser.
122 raise AssertionError(
123 'MultiStepView did not find action for %s=%r'
124 % action_required)
125
107 def render(self):126 def render(self):
108 return self.view.render()127 return self.view.render()
109128
110129
=== modified file 'lib/canonical/launchpad/doc/tales-macro.txt'
--- lib/canonical/launchpad/doc/tales-macro.txt 2009-11-23 03:10:04 +0000
+++ lib/canonical/launchpad/doc/tales-macro.txt 2010-08-12 22:16:35 +0000
@@ -3,8 +3,9 @@
3Launchpad has a 'macro:' TALES namespace that offers controls over the3Launchpad has a 'macro:' TALES namespace that offers controls over the
4layout of the page.4layout of the page.
55
6 >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
6 >>> class FakeView(object):7 >>> class FakeView(object):
7 ... pass8 ... request = LaunchpadTestRequest()
89
9Templates should start by specifying the kind of pagetype they use.10Templates should start by specifying the kind of pagetype they use.
10That's done by using the 'macro:page' traversal. That expression returns11That's done by using the 'macro:page' traversal. That expression returns
@@ -50,6 +51,7 @@
50returns False for older views that do not.51returns False for older views that do not.
5152
52 >>> class LPView(object):53 >>> class LPView(object):
54 ... request = LaunchpadTestRequest()
53 ... def isBetaUser(self):55 ... def isBetaUser(self):
54 ... return True56 ... return True
5557
5658
=== modified file 'lib/canonical/launchpad/emailtemplates/product-other-license.txt'
--- lib/canonical/launchpad/emailtemplates/product-other-license.txt 2010-05-12 19:06:17 +0000
+++ lib/canonical/launchpad/emailtemplates/product-other-license.txt 2010-08-12 22:16:35 +0000
@@ -28,8 +28,9 @@
28questions.28questions.
2929
30Sometimes new projects are licensed as 'Other/Open Source' because the30Sometimes new projects are licensed as 'Other/Open Source' because the
31licensing decisions have not yet been made. If that is your situation we urge31licensing decisions have not yet been made. If that is your situation
32you to update the licensing in Launchpad as soon as you make that choice.32we urge you to update the licensing in Launchpad as soon as you make
33that choice.
3334
34If the license for your project needs to be corrected you can do so by35If the license for your project needs to be corrected you can do so by
35following the 'Change Details' link on your project's overview page.36following the 'Change Details' link on your project's overview page.
3637
=== modified file 'lib/canonical/launchpad/webapp/configure.zcml'
--- lib/canonical/launchpad/webapp/configure.zcml 2010-07-23 13:24:58 +0000
+++ lib/canonical/launchpad/webapp/configure.zcml 2010-08-12 22:16:35 +0000
@@ -711,6 +711,10 @@
711 />711 />
712712
713 <adapter713 <adapter
714 factory="canonical.launchpad.webapp.tales.LaunchpadLayerToMainTemplateAdapter"
715 />
716
717 <adapter
714 factory="canonical.launchpad.webapp.snapshot.snapshot_sql_result" />718 factory="canonical.launchpad.webapp.snapshot.snapshot_sql_result" />
715 <!-- It also works for the legacy SQLObject interface. -->719 <!-- It also works for the legacy SQLObject interface. -->
716 <adapter720 <adapter
717721
=== modified file 'lib/canonical/launchpad/webapp/launchpadform.py'
--- lib/canonical/launchpad/webapp/launchpadform.py 2010-06-23 23:07:10 +0000
+++ lib/canonical/launchpad/webapp/launchpadform.py 2010-08-12 22:16:35 +0000
@@ -74,6 +74,8 @@
7474
75 actions = ()75 actions = ()
7676
77 action_taken = None
78
77 render_context = False79 render_context = False
7880
79 form_result = None81 form_result = None
@@ -112,6 +114,7 @@
112 self.form_result = action.success(data)114 self.form_result = action.success(data)
113 if self.next_url:115 if self.next_url:
114 self.request.response.redirect(self.next_url)116 self.request.response.redirect(self.next_url)
117 self.action_taken = action
115118
116 def render(self):119 def render(self):
117 """Return the body of the response.120 """Return the body of the response.
118121
=== modified file 'lib/canonical/launchpad/webapp/tales.py'
--- lib/canonical/launchpad/webapp/tales.py 2010-07-30 06:08:54 +0000
+++ lib/canonical/launchpad/webapp/tales.py 2010-08-12 22:16:35 +0000
@@ -22,7 +22,7 @@
22from lazr.uri import URI22from lazr.uri import URI
2323
24from zope.interface import Interface, Attribute, implements24from zope.interface import Interface, Attribute, implements
25from zope.component import getUtility, queryAdapter, getMultiAdapter25from zope.component import adapts, getUtility, queryAdapter, getMultiAdapter
26from zope.app import zapi26from zope.app import zapi
27from zope.publisher.browser import BrowserView27from zope.publisher.browser import BrowserView
28from zope.publisher.interfaces import IApplicationRequest28from zope.publisher.interfaces import IApplicationRequest
@@ -31,6 +31,7 @@
31 ITraversable, IPathAdapter, TraversalError)31 ITraversable, IPathAdapter, TraversalError)
32from zope.security.interfaces import Unauthorized32from zope.security.interfaces import Unauthorized
33from zope.security.proxy import isinstance as zope_isinstance33from zope.security.proxy import isinstance as zope_isinstance
34from zope.schema import TextLine
3435
35import pytz36import pytz
36from z3c.ptcompat import ViewPageTemplateFile37from z3c.ptcompat import ViewPageTemplateFile
@@ -41,6 +42,7 @@
41 ISprint, LicenseStatus)42 ISprint, LicenseStatus)
42from canonical.launchpad.interfaces.launchpad import (43from canonical.launchpad.interfaces.launchpad import (
43 IHasIcon, IHasLogo, IHasMugshot, IPrivacy)44 IHasIcon, IHasLogo, IHasMugshot, IPrivacy)
45from canonical.launchpad.layers import LaunchpadLayer
44import canonical.launchpad.pagetitles46import canonical.launchpad.pagetitles
45from canonical.launchpad.webapp import canonical_url, urlappend47from canonical.launchpad.webapp import canonical_url, urlappend
46from canonical.launchpad.webapp.authorization import check_permission48from canonical.launchpad.webapp.authorization import check_permission
@@ -2297,6 +2299,20 @@
2297 return check_permission(name, self.context)2299 return check_permission(name, self.context)
22982300
22992301
2302class IMainTemplateFile(Interface):
2303 path = TextLine(title=u'The absolute path to this main template.')
2304
2305
2306class LaunchpadLayerToMainTemplateAdapter:
2307 adapts(LaunchpadLayer)
2308 implements(IMainTemplateFile)
2309
2310 def __init__(self, context):
2311 here = os.path.dirname(os.path.realpath(__file__))
2312 self.path = os.path.join(
2313 here, '../../../lp/app/templates/base-layout.pt')
2314
2315
2300class PageMacroDispatcher:2316class PageMacroDispatcher:
2301 """Selects a macro, while storing information about page layout.2317 """Selects a macro, while storing information about page layout.
23022318
@@ -2316,12 +2332,15 @@
23162332
2317 implements(ITraversable)2333 implements(ITraversable)
23182334
2319 base = ViewPageTemplateFile('../../../lp/app/templates/base-layout.pt')
2320
2321 def __init__(self, context):2335 def __init__(self, context):
2322 # The context of this object is a view object.2336 # The context of this object is a view object.
2323 self.context = context2337 self.context = context
23242338
2339 @property
2340 def base(self):
2341 return ViewPageTemplateFile(
2342 IMainTemplateFile(self.context.request).path)
2343
2325 def traverse(self, name, furtherPath):2344 def traverse(self, name, furtherPath):
2326 if name == 'page':2345 if name == 'page':
2327 if len(furtherPath) == 1:2346 if len(furtherPath) == 1:
23282347
=== added file 'lib/canonical/launchpad/webapp/tests/test_base_template.py'
--- lib/canonical/launchpad/webapp/tests/test_base_template.py 1970-01-01 00:00:00 +0000
+++ lib/canonical/launchpad/webapp/tests/test_base_template.py 2010-08-12 22:16:35 +0000
@@ -0,0 +1,29 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for Launchpad's 'view/macro:page' TALES adapter."""
5
6__metaclass__ = type
7
8from zope.component import getMultiAdapter
9from zope.traversing.interfaces import IPathAdapter
10
11from canonical.launchpad.webapp.publisher import rootObject
12from canonical.launchpad.webapp.servers import LaunchpadTestRequest
13from canonical.testing.layers import FunctionalLayer
14
15from lp.testing import TestCase
16
17
18class TestPageMacroDispatcher(TestCase):
19
20 layer = FunctionalLayer
21
22 def test_base_template(self):
23 # Requests on the launchpad.dev vhost use the Launchpad base template.
24 root_view = getMultiAdapter(
25 (rootObject, LaunchpadTestRequest()), name='index.html')
26 adapter = getMultiAdapter([root_view], IPathAdapter, name='macro')
27 self.assertIn('lp/app/templates', adapter.base.filename)
28 # The base template defines a 'master' macro as the adapter expects.
29 self.assertIn('master', adapter.base.macros.keys())
030
=== modified file 'lib/canonical/widgets/product.py'
--- lib/canonical/widgets/product.py 2010-06-21 04:08:54 +0000
+++ lib/canonical/widgets/product.py 2010-08-12 22:16:35 +0000
@@ -316,6 +316,7 @@
316 self, 'license_info', self.license_info, IInputWidget,316 self, 'license_info', self.license_info, IInputWidget,
317 prefix='field', value=initial_value,317 prefix='field', value=initial_value,
318 context=field.context)318 context=field.context)
319 self.source_package_release = None
319 # These will get filled in by _categorize(). They are the number of320 # These will get filled in by _categorize(). They are the number of
320 # selected licenses in the category. The actual count doesn't matter,321 # selected licenses in the category. The actual count doesn't matter,
321 # since if it's greater than 0 it will start opened. NOte that we322 # since if it's greater than 0 it will start opened. NOte that we
322323
=== modified file 'lib/canonical/widgets/templates/license.pt'
--- lib/canonical/widgets/templates/license.pt 2009-07-17 17:59:07 +0000
+++ lib/canonical/widgets/templates/license.pt 2010-08-12 22:16:35 +0000
@@ -42,7 +42,7 @@
42 // the slider depends on whether there are any checked42 // the slider depends on whether there are any checked
43 // licenses in that category. A '0' means 'no'.43 // licenses in that category. A '0' means 'no'.
44 var arrow = Y.get(arrow_name);44 var arrow = Y.get(arrow_name);
45 if (arrow.getAttribute('count') == '0') {45 if (arrow.getAttribute('start_expanded') == '0') {
46 target.slide = Y.lazr.effects.slide_in(table_name);46 target.slide = Y.lazr.effects.slide_in(table_name);
47 }47 }
48 else {48 else {
@@ -76,6 +76,7 @@
76 target.slide.run();76 target.slide.run();
77 }, target_name);77 }, target_name);
78 }78 }
79 make_slider({which: 'copyright'});
79 make_slider({which: 'recommended'});80 make_slider({which: 'recommended'});
80 make_slider({which: 'more'});81 make_slider({which: 'more'});
81 make_slider({which: 'deprecated'});82 make_slider({which: 'deprecated'});
@@ -136,6 +137,23 @@
136//]]>137//]]>
137</script>138</script>
138<div style="color: black">139<div style="color: black">
140 <tal:copyright condition="view/source_package_release">
141 <a href="" id="copyright-expand" class="js-action">
142 <img id="copyright-expand-arrow"
143 src="/@@/treeCollapsed"
144 title="Copyright info from source package"
145 alt="Copyright info from source package"
146 start_expanded="0"/>
147 Copyright info from source package
148 </a>
149 <div id="copyright">
150 <div
151 tal:content="structure view/source_package_release/@@+copyright"
152 style="overflow-x: hidden; overflow-y: auto;
153 max-width: 60em; max-height: 32em; background: #f7f7f7"
154 />
155 </div>
156 </tal:copyright>
139157
140 Select the license(s) under which you release your project.158 Select the license(s) under which you release your project.
141 <div tal:condition="view/allow_pending_license"159 <div tal:condition="view/allow_pending_license"
@@ -159,7 +177,7 @@
159 src="/@@/treeExpanded"177 src="/@@/treeExpanded"
160 title="Recommended open source licenses"178 title="Recommended open source licenses"
161 alt="Recommended open source licenses"179 alt="Recommended open source licenses"
162 tal:attributes="count view/recommended_count"/>180 tal:attributes="start_expanded view/recommended_count"/>
163 Recommended open source licenses181 Recommended open source licenses
164 </a>182 </a>
165 <input tal:replace="structure view/recommended" />183 <input tal:replace="structure view/recommended" />
@@ -168,7 +186,7 @@
168 src="/@@/treeCollapsed"186 src="/@@/treeCollapsed"
169 title="More open source licenses"187 title="More open source licenses"
170 alt="More open source licenses"188 alt="More open source licenses"
171 tal:attributes="count view/more_count"/>189 tal:attributes="start_expanded view/more_count"/>
172 More open source licenses190 More open source licenses
173 </a>191 </a>
174 <input tal:replace="structure view/more" />192 <input tal:replace="structure view/more" />
@@ -178,7 +196,7 @@
178 src="/@@/treeCollapsed"196 src="/@@/treeCollapsed"
179 title="Deprecated licenses"197 title="Deprecated licenses"
180 alt="Deprecated licenses"198 alt="Deprecated licenses"
181 tal:attributes="count view/deprecated_count"/>199 tal:attributes="start_expanded view/deprecated_count"/>
182 Deprecated licenses200 Deprecated licenses
183 </a>201 </a>
184 <input tal:replace="structure view/deprecated" />202 <input tal:replace="structure view/deprecated" />
@@ -188,7 +206,7 @@
188 src="/@@/treeCollapsed"206 src="/@@/treeCollapsed"
189 title="Other choices"207 title="Other choices"
190 alt="Other choices"208 alt="Other choices"
191 tal:attributes="count view/special_count"/>209 tal:attributes="start_expanded view/special_count"/>
192 Other choices210 Other choices
193 </a>211 </a>
194 <input tal:replace="structure view/special" />212 <input tal:replace="structure view/special" />
195213
=== added symlink 'lib/deb822.py'
=== target is u'../sourcecode/python-debian/lib/deb822.py'
=== added symlink 'lib/debian'
=== target is u'../sourcecode/python-debian/lib/debian'
=== modified file 'lib/lp/archivepublisher/config.py'
--- lib/lp/archivepublisher/config.py 2010-07-07 06:28:03 +0000
+++ lib/lp/archivepublisher/config.py 2010-08-12 22:16:35 +0000
@@ -120,18 +120,15 @@
120 config_segment["archtags"].append(120 config_segment["archtags"].append(
121 dar.architecturetag.encode('utf-8'))121 dar.architecturetag.encode('utf-8'))
122122
123 if not dr.lucilleconfig:123 if dr.lucilleconfig:
124 raise LucilleConfigError(124 strio = StringIO(dr.lucilleconfig.encode('utf-8'))
125 'No Lucille configuration section for %s' % dr.name)125 config_segment["config"] = ConfigParser()
126126 config_segment["config"].readfp(strio)
127 strio = StringIO(dr.lucilleconfig.encode('utf-8'))127 strio.close()
128 config_segment["config"] = ConfigParser()128 config_segment["components"] = config_segment["config"].get(
129 config_segment["config"].readfp(strio)129 "publishing", "components").split(" ")
130 strio.close()130
131 config_segment["components"] = config_segment["config"].get(131 self._distroseries[distroseries_name] = config_segment
132 "publishing", "components").split(" ")
133
134 self._distroseries[distroseries_name] = config_segment
135132
136 strio = StringIO(distribution.lucilleconfig.encode('utf-8'))133 strio = StringIO(distribution.lucilleconfig.encode('utf-8'))
137 self._distroconfig = ConfigParser()134 self._distroconfig = ConfigParser()
@@ -144,11 +141,19 @@
144 # Because dicts iterate for keys only; this works to get dr names141 # Because dicts iterate for keys only; this works to get dr names
145 return self._distroseries.keys()142 return self._distroseries.keys()
146143
144 def series(self, dr):
145 try:
146 return self._distroseries[dr]
147 except KeyError:
148 raise LucilleConfigError(
149 'No Lucille config section for %s in %s' %
150 (dr, self.distroName))
151
147 def archTagsForSeries(self, dr):152 def archTagsForSeries(self, dr):
148 return self._distroseries[dr]["archtags"]153 return self.series(dr)["archtags"]
149154
150 def componentsForSeries(self, dr):155 def componentsForSeries(self, dr):
151 return self._distroseries[dr]["components"]156 return self.series(dr)["components"]
152157
153 def _extractConfigInfo(self):158 def _extractConfigInfo(self):
154 """Extract configuration information into the attributes we use"""159 """Extract configuration information into the attributes we use"""
155160
=== modified file 'lib/lp/archivepublisher/ftparchive.py'
--- lib/lp/archivepublisher/ftparchive.py 2009-10-26 18:40:04 +0000
+++ lib/lp/archivepublisher/ftparchive.py 2010-08-12 22:16:35 +0000
@@ -138,6 +138,14 @@
138 self._config = config138 self._config = config
139 self._diskpool = diskpool139 self._diskpool = diskpool
140 self.distro = distro140 self.distro = distro
141 self.distroseries = []
142 for distroseries in self.distro.series:
143 if not distroseries.name in self._config.distroSeriesNames():
144 self.log.warning("Distroseries %s in %s doesn't have "
145 "a lucille configuration.", distroseries.name,
146 self.distro.name)
147 else:
148 self.distroseries.append(distroseries)
141 self.publisher = publisher149 self.publisher = publisher
142 self.release_files_needed = {}150 self.release_files_needed = {}
143151
@@ -185,7 +193,7 @@
185 # iterate over the pockets, and do the suffix check inside193 # iterate over the pockets, and do the suffix check inside
186 # createEmptyPocketRequest; that would also allow us to replace194 # createEmptyPocketRequest; that would also allow us to replace
187 # the == "" check we do there by a RELEASE match195 # the == "" check we do there by a RELEASE match
188 for distroseries in self.distro:196 for distroseries in self.distroseries:
189 components = self._config.componentsForSeries(distroseries.name)197 components = self._config.componentsForSeries(distroseries.name)
190 for pocket, suffix in pocketsuffix.items():198 for pocket, suffix in pocketsuffix.items():
191 if not fullpublish:199 if not fullpublish:
@@ -366,7 +374,7 @@
366374
367 def generateOverrides(self, fullpublish=False):375 def generateOverrides(self, fullpublish=False):
368 """Collect packages that need overrides, and generate them."""376 """Collect packages that need overrides, and generate them."""
369 for distroseries in self.distro.series:377 for distroseries in self.distroseries:
370 for pocket in PackagePublishingPocket.items:378 for pocket in PackagePublishingPocket.items:
371 if not fullpublish:379 if not fullpublish:
372 if not self.publisher.isDirty(distroseries, pocket):380 if not self.publisher.isDirty(distroseries, pocket):
@@ -629,7 +637,7 @@
629637
630 def generateFileLists(self, fullpublish=False):638 def generateFileLists(self, fullpublish=False):
631 """Collect currently published FilePublishings and write filelists."""639 """Collect currently published FilePublishings and write filelists."""
632 for distroseries in self.distro.series:640 for distroseries in self.distroseries:
633 for pocket in pocketsuffix:641 for pocket in pocketsuffix:
634 if not fullpublish:642 if not fullpublish:
635 if not self.publisher.isDirty(distroseries, pocket):643 if not self.publisher.isDirty(distroseries, pocket):
636644
=== modified file 'lib/lp/archivepublisher/tests/test_config.py'
--- lib/lp/archivepublisher/tests/test_config.py 2010-07-18 00:24:06 +0000
+++ lib/lp/archivepublisher/tests/test_config.py 2010-08-12 22:16:35 +0000
@@ -5,36 +5,48 @@
55
6__metaclass__ = type6__metaclass__ = type
77
8import unittest
9
10from zope.component import getUtility8from zope.component import getUtility
119
12from canonical.config import config10from canonical.config import config
13from canonical.launchpad.interfaces import IDistributionSet11from canonical.launchpad.interfaces import IDistributionSet
14from canonical.testing import LaunchpadZopelessLayer12from canonical.testing import LaunchpadZopelessLayer
1513
1614from lp.archivepublisher.config import Config, LucilleConfigError
17class TestConfig(unittest.TestCase):15from lp.testing import TestCaseWithFactory
16
17
18class TestConfig(TestCaseWithFactory):
18 layer = LaunchpadZopelessLayer19 layer = LaunchpadZopelessLayer
1920
20 def setUp(self):21 def setUp(self):
22 super(TestConfig, self).setUp()
21 self.layer.switchDbUser(config.archivepublisher.dbuser)23 self.layer.switchDbUser(config.archivepublisher.dbuser)
22 self.ubuntutest = getUtility(IDistributionSet)['ubuntutest']24 self.ubuntutest = getUtility(IDistributionSet)['ubuntutest']
2325
26 def testMissingDistroSeries(self):
27 distroseries = self.factory.makeDistroSeries(
28 distribution=self.ubuntutest, name="somename")
29 d = Config(self.ubuntutest)
30 dsns = d.distroSeriesNames()
31 self.assertEquals(len(dsns), 2)
32 self.assertEquals(dsns[0], "breezy-autotest")
33 self.assertEquals(dsns[1], "hoary-test")
34 self.assertRaises(LucilleConfigError,
35 d.archTagsForSeries, "somename")
36 self.assertRaises(LucilleConfigError,
37 d.archTagsForSeries, "unknown")
38
24 def testInstantiate(self):39 def testInstantiate(self):
25 """Config should instantiate"""40 """Config should instantiate"""
26 from lp.archivepublisher.config import Config
27 d = Config(self.ubuntutest)41 d = Config(self.ubuntutest)
2842
29 def testDistroName(self):43 def testDistroName(self):
30 """Config should be able to return the distroName"""44 """Config should be able to return the distroName"""
31 from lp.archivepublisher.config import Config
32 d = Config(self.ubuntutest)45 d = Config(self.ubuntutest)
33 self.assertEqual(d.distroName, "ubuntutest")46 self.assertEqual(d.distroName, "ubuntutest")
3447
35 def testDistroSeriesNames(self):48 def testDistroSeriesNames(self):
36 """Config should return two distroseries names"""49 """Config should return two distroseries names"""
37 from lp.archivepublisher.config import Config
38 d = Config(self.ubuntutest)50 d = Config(self.ubuntutest)
39 dsns = d.distroSeriesNames()51 dsns = d.distroSeriesNames()
40 self.assertEquals(len(dsns), 2)52 self.assertEquals(len(dsns), 2)
@@ -43,14 +55,12 @@
4355
44 def testArchTagsForSeries(self):56 def testArchTagsForSeries(self):
45 """Config should have the arch tags for the drs"""57 """Config should have the arch tags for the drs"""
46 from lp.archivepublisher.config import Config
47 d = Config(self.ubuntutest)58 d = Config(self.ubuntutest)
48 archs = d.archTagsForSeries("hoary-test")59 archs = d.archTagsForSeries("hoary-test")
49 self.assertEquals(len(archs), 2)60 self.assertEquals(len(archs), 2)
5061
51 def testDistroConfig(self):62 def testDistroConfig(self):
52 """Config should have parsed a distro config"""63 """Config should have parsed a distro config"""
53 from lp.archivepublisher.config import Config
54 d = Config(self.ubuntutest)64 d = Config(self.ubuntutest)
55 # NOTE: Add checks here when you add stuff in util.py65 # NOTE: Add checks here when you add stuff in util.py
56 self.assertEquals(d.stayofexecution, 5)66 self.assertEquals(d.stayofexecution, 5)
5767
=== modified file 'lib/lp/archivepublisher/tests/test_ftparchive.py'
--- lib/lp/archivepublisher/tests/test_ftparchive.py 2010-07-18 00:24:06 +0000
+++ lib/lp/archivepublisher/tests/test_ftparchive.py 2010-08-12 22:16:35 +0000
@@ -15,7 +15,7 @@
15from zope.component import getUtility15from zope.component import getUtility
1616
17from canonical.config import config17from canonical.config import config
18from canonical.launchpad.scripts.logger import QuietFakeLogger18from canonical.launchpad.scripts.logger import BufferLogger, QuietFakeLogger
19from canonical.testing import LaunchpadZopelessLayer19from canonical.testing import LaunchpadZopelessLayer
20from lp.archivepublisher.config import Config20from lp.archivepublisher.config import Config
21from lp.archivepublisher.diskpool import DiskPool21from lp.archivepublisher.diskpool import DiskPool
@@ -23,6 +23,7 @@
23from lp.archivepublisher.publishing import Publisher23from lp.archivepublisher.publishing import Publisher
24from lp.registry.interfaces.distribution import IDistributionSet24from lp.registry.interfaces.distribution import IDistributionSet
25from lp.registry.interfaces.pocket import PackagePublishingPocket25from lp.registry.interfaces.pocket import PackagePublishingPocket
26from lp.testing import TestCaseWithFactory
2627
2728
28def sanitize_apt_ftparchive_Sources_output(text):29def sanitize_apt_ftparchive_Sources_output(text):
@@ -55,10 +56,11 @@
55 return self._result[i:j]56 return self._result[i:j]
5657
5758
58class TestFTPArchive(unittest.TestCase):59class TestFTPArchive(TestCaseWithFactory):
59 layer = LaunchpadZopelessLayer60 layer = LaunchpadZopelessLayer
6061
61 def setUp(self):62 def setUp(self):
63 super(TestFTPArchive, self).setUp()
62 self.layer.switchDbUser(config.archivepublisher.dbuser)64 self.layer.switchDbUser(config.archivepublisher.dbuser)
6365
64 self._distribution = getUtility(IDistributionSet)['ubuntutest']66 self._distribution = getUtility(IDistributionSet)['ubuntutest']
@@ -79,6 +81,7 @@
79 self._publisher = SamplePublisher(self._archive)81 self._publisher = SamplePublisher(self._archive)
8082
81 def tearDown(self):83 def tearDown(self):
84 super(TestFTPArchive, self).tearDown()
82 shutil.rmtree(self._config.distroroot)85 shutil.rmtree(self._config.distroroot)
8386
84 def _verifyFile(self, filename, directory, output_filter=None):87 def _verifyFile(self, filename, directory, output_filter=None):
@@ -116,6 +119,19 @@
116 self._publisher)119 self._publisher)
117 return fa120 return fa
118121
122 def test_NoLucilleConfig(self):
123 # Distroseries without a lucille configuration get ignored
124 # and trigger a warning, they don't break the publisher
125 logger = BufferLogger()
126 publisher = Publisher(
127 logger, self._config, self._dp, self._archive)
128 self.factory.makeDistroSeries(self._distribution, name="somename")
129 fa = FTPArchiveHandler(logger, self._config, self._dp,
130 self._distribution, publisher)
131 fa.createEmptyPocketRequests(fullpublish=True)
132 self.assertEquals("WARNING: Distroseries somename in ubuntutest doesn't "
133 "have a lucille configuration.\n", logger.buffer.getvalue())
134
119 def test_getSourcesForOverrides(self):135 def test_getSourcesForOverrides(self):
120 # getSourcesForOverrides returns a list of tuples containing:136 # getSourcesForOverrides returns a list of tuples containing:
121 # (sourcename, suite, component, section)137 # (sourcename, suite, component, section)
122138
=== modified file 'lib/lp/archiveuploader/tests/__init__.py'
--- lib/lp/archiveuploader/tests/__init__.py 2010-05-04 15:38:08 +0000
+++ lib/lp/archiveuploader/tests/__init__.py 2010-08-12 22:16:35 +0000
@@ -1,6 +1,10 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for the archive uploader."""
5
6from __future__ import with_statement
7
4__metaclass__ = type8__metaclass__ = type
59
6__all__ = ['datadir', 'getPolicy', 'insertFakeChangesFile',10__all__ = ['datadir', 'getPolicy', 'insertFakeChangesFile',
@@ -25,6 +29,7 @@
25 raise ValueError("Path is not relative: %s" % path)29 raise ValueError("Path is not relative: %s" % path)
26 return os.path.join(here, 'data', path)30 return os.path.join(here, 'data', path)
2731
32
28def insertFakeChangesFile(fileID, path=None):33def insertFakeChangesFile(fileID, path=None):
29 """Insert a fake changes file into the librarian.34 """Insert a fake changes file into the librarian.
3035
@@ -34,11 +39,11 @@
34 """39 """
35 if path is None:40 if path is None:
36 path = datadir("ed-0.2-21/ed_0.2-21_source.changes")41 path = datadir("ed-0.2-21/ed_0.2-21_source.changes")
37 changes_file_obj = open(path, 'r')42 with open(path, 'r') as changes_file_obj:
38 test_changes_file = changes_file_obj.read()43 test_changes_file = changes_file_obj.read()
39 changes_file_obj.close()
40 fillLibrarianFile(fileID, content=test_changes_file)44 fillLibrarianFile(fileID, content=test_changes_file)
4145
46
42def insertFakeChangesFileForAllPackageUploads():47def insertFakeChangesFileForAllPackageUploads():
43 """Ensure all the PackageUpload records point to a valid changes file."""48 """Ensure all the PackageUpload records point to a valid changes file."""
44 for id in set(pu.changesfile.id for pu in PackageUploadSet()):49 for id in set(pu.changesfile.id for pu in PackageUploadSet()):
@@ -53,6 +58,7 @@
53 self.distroseries = distroseries58 self.distroseries = distroseries
54 self.buildid = buildid59 self.buildid = buildid
5560
61
56def getPolicy(name='anything', distro='ubuntu', distroseries=None,62def getPolicy(name='anything', distro='ubuntu', distroseries=None,
57 buildid=None):63 buildid=None):
58 """Build and return an Upload Policy for the given context."""64 """Build and return an Upload Policy for the given context."""
5965
=== modified file 'lib/lp/archiveuploader/tests/test_buildduploads.py'
--- lib/lp/archiveuploader/tests/test_buildduploads.py 2010-07-18 00:26:33 +0000
+++ lib/lp/archiveuploader/tests/test_buildduploads.py 2010-08-12 22:16:35 +0000
@@ -7,7 +7,6 @@
77
8from lp.archiveuploader.tests.test_securityuploads import (8from lp.archiveuploader.tests.test_securityuploads import (
9 TestStagedBinaryUploadBase)9 TestStagedBinaryUploadBase)
10from lp.archiveuploader.uploadprocessor import UploadProcessor
11from lp.registry.interfaces.pocket import PackagePublishingPocket10from lp.registry.interfaces.pocket import PackagePublishingPocket
12from canonical.database.constants import UTC_NOW11from canonical.database.constants import UTC_NOW
13from canonical.launchpad.interfaces import PackagePublishingStatus12from canonical.launchpad.interfaces import PackagePublishingStatus
@@ -84,8 +83,8 @@
84 """Setup an UploadProcessor instance for a given buildd context."""83 """Setup an UploadProcessor instance for a given buildd context."""
85 self.options.context = self.policy84 self.options.context = self.policy
86 self.options.buildid = str(build_candidate.id)85 self.options.buildid = str(build_candidate.id)
87 self.uploadprocessor = UploadProcessor(86 self.uploadprocessor = self.getUploadProcessor(
88 self.options, self.layer.txn, self.log)87 self.layer.txn)
8988
90 def testDelayedBinaryUpload(self):89 def testDelayedBinaryUpload(self):
91 """Check if Soyuz copes with delayed binary uploads.90 """Check if Soyuz copes with delayed binary uploads.
9291
=== modified file 'lib/lp/archiveuploader/tests/test_ppauploadprocessor.py'
--- lib/lp/archiveuploader/tests/test_ppauploadprocessor.py 2010-08-02 02:13:52 +0000
+++ lib/lp/archiveuploader/tests/test_ppauploadprocessor.py 2010-08-12 22:16:35 +0000
@@ -18,7 +18,6 @@
18from zope.security.proxy import removeSecurityProxy18from zope.security.proxy import removeSecurityProxy
1919
20from lp.app.errors import NotFoundError20from lp.app.errors import NotFoundError
21from lp.archiveuploader.uploadprocessor import UploadProcessor
22from lp.archiveuploader.tests.test_uploadprocessor import (21from lp.archiveuploader.tests.test_uploadprocessor import (
23 TestUploadProcessorBase)22 TestUploadProcessorBase)
24from canonical.config import config23from canonical.config import config
@@ -74,8 +73,7 @@
7473
75 # Set up the uploadprocessor with appropriate options and logger74 # Set up the uploadprocessor with appropriate options and logger
76 self.options.context = 'insecure'75 self.options.context = 'insecure'
77 self.uploadprocessor = UploadProcessor(76 self.uploadprocessor = self.getUploadProcessor(self.layer.txn)
78 self.options, self.layer.txn, self.log)
7977
80 def assertEmail(self, contents=None, recipients=None,78 def assertEmail(self, contents=None, recipients=None,
81 ppa_header='name16'):79 ppa_header='name16'):
@@ -1224,8 +1222,7 @@
12241222
1225 # Re-initialize uploadprocessor since it depends on the new1223 # Re-initialize uploadprocessor since it depends on the new
1226 # transaction reset by switchDbUser.1224 # transaction reset by switchDbUser.
1227 self.uploadprocessor = UploadProcessor(1225 self.uploadprocessor = self.getUploadProcessor(self.layer.txn)
1228 self.options, self.layer.txn, self.log)
12291226
1230 def testPPASizeQuotaSourceRejection(self):1227 def testPPASizeQuotaSourceRejection(self):
1231 """Verify the size quota check for PPA uploads.1228 """Verify the size quota check for PPA uploads.
12321229
=== modified file 'lib/lp/archiveuploader/tests/test_recipeuploads.py'
--- lib/lp/archiveuploader/tests/test_recipeuploads.py 2010-07-22 15:24:02 +0000
+++ lib/lp/archiveuploader/tests/test_recipeuploads.py 2010-08-12 22:16:35 +0000
@@ -12,7 +12,6 @@
1212
13from lp.archiveuploader.tests.test_uploadprocessor import (13from lp.archiveuploader.tests.test_uploadprocessor import (
14 TestUploadProcessorBase)14 TestUploadProcessorBase)
15from lp.archiveuploader.uploadprocessor import UploadProcessor
16from lp.buildmaster.interfaces.buildbase import BuildStatus15from lp.buildmaster.interfaces.buildbase import BuildStatus
17from lp.code.interfaces.sourcepackagerecipebuild import (16from lp.code.interfaces.sourcepackagerecipebuild import (
18 ISourcePackageRecipeBuildSource)17 ISourcePackageRecipeBuildSource)
@@ -42,8 +41,8 @@
42 self.options.context = 'recipe'41 self.options.context = 'recipe'
43 self.options.buildid = self.build.id42 self.options.buildid = self.build.id
4443
45 self.uploadprocessor = UploadProcessor(44 self.uploadprocessor = self.getUploadProcessor(
46 self.options, self.layer.txn, self.log)45 self.layer.txn)
4746
48 def testSetsBuildAndState(self):47 def testSetsBuildAndState(self):
49 # Ensure that the upload processor correctly links the SPR to48 # Ensure that the upload processor correctly links the SPR to
5049
=== modified file 'lib/lp/archiveuploader/tests/test_securityuploads.py'
--- lib/lp/archiveuploader/tests/test_securityuploads.py 2010-07-18 00:26:33 +0000
+++ lib/lp/archiveuploader/tests/test_securityuploads.py 2010-08-12 22:16:35 +0000
@@ -11,7 +11,6 @@
1111
12from lp.archiveuploader.tests.test_uploadprocessor import (12from lp.archiveuploader.tests.test_uploadprocessor import (
13 TestUploadProcessorBase)13 TestUploadProcessorBase)
14from lp.archiveuploader.uploadprocessor import UploadProcessor
15from lp.registry.interfaces.pocket import PackagePublishingPocket14from lp.registry.interfaces.pocket import PackagePublishingPocket
16from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild15from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
17from lp.soyuz.model.processor import ProcessorFamily16from lp.soyuz.model.processor import ProcessorFamily
@@ -70,8 +69,7 @@
70 self.options.context = self.policy69 self.options.context = self.policy
71 self.options.nomails = self.no_mails70 self.options.nomails = self.no_mails
72 # Set up the uploadprocessor with appropriate options and logger71 # Set up the uploadprocessor with appropriate options and logger
73 self.uploadprocessor = UploadProcessor(72 self.uploadprocessor = self.getUploadProcessor(self.layer.txn)
74 self.options, self.layer.txn, self.log)
75 self.builds_before_upload = BinaryPackageBuild.select().count()73 self.builds_before_upload = BinaryPackageBuild.select().count()
76 self.source_queue = None74 self.source_queue = None
77 self._uploadSource()75 self._uploadSource()
@@ -232,8 +230,7 @@
232 """230 """
233 build_candidate = self._createBuild('i386')231 build_candidate = self._createBuild('i386')
234 self.options.buildid = str(build_candidate.id)232 self.options.buildid = str(build_candidate.id)
235 self.uploadprocessor = UploadProcessor(233 self.uploadprocessor = self.getUploadProcessor(self.layer.txn)
236 self.options, self.layer.txn, self.log)
237234
238 build_used = self._uploadBinary('i386')235 build_used = self._uploadBinary('i386')
239236
@@ -254,8 +251,7 @@
254 """251 """
255 build_candidate = self._createBuild('hppa')252 build_candidate = self._createBuild('hppa')
256 self.options.buildid = str(build_candidate.id)253 self.options.buildid = str(build_candidate.id)
257 self.uploadprocessor = UploadProcessor(254 self.uploadprocessor = self.getUploadProcessor(self.layer.txn)
258 self.options, self.layer.txn, self.log)
259255
260 self.assertRaises(AssertionError, self._uploadBinary, 'i386')256 self.assertRaises(AssertionError, self._uploadBinary, 'i386')
261257
262258
=== modified file 'lib/lp/archiveuploader/tests/test_uploadprocessor.py'
--- lib/lp/archiveuploader/tests/test_uploadprocessor.py 2010-08-02 02:13:52 +0000
+++ lib/lp/archiveuploader/tests/test_uploadprocessor.py 2010-08-12 22:16:35 +0000
@@ -23,8 +23,10 @@
23from zope.security.proxy import removeSecurityProxy23from zope.security.proxy import removeSecurityProxy
2424
25from lp.app.errors import NotFoundError25from lp.app.errors import NotFoundError
26from lp.archiveuploader.uploadpolicy import AbstractUploadPolicy26from lp.archiveuploader.uploadpolicy import (AbstractUploadPolicy,
27 findPolicyByOptions)
27from lp.archiveuploader.uploadprocessor import UploadProcessor28from lp.archiveuploader.uploadprocessor import UploadProcessor
29from lp.buildmaster.interfaces.buildbase import BuildStatus
28from canonical.config import config30from canonical.config import config
29from canonical.database.constants import UTC_NOW31from canonical.database.constants import UTC_NOW
30from lp.soyuz.model.archivepermission import ArchivePermission32from lp.soyuz.model.archivepermission import ArchivePermission
@@ -59,7 +61,7 @@
59 ISourcePackageNameSet)61 ISourcePackageNameSet)
60from lp.services.mail import stub62from lp.services.mail import stub
61from canonical.launchpad.testing.fakepackager import FakePackager63from canonical.launchpad.testing.fakepackager import FakePackager
62from lp.testing import TestCaseWithFactory64from lp.testing import TestCase, TestCaseWithFactory
63from lp.testing.mail_helpers import pop_notifications65from lp.testing.mail_helpers import pop_notifications
64from canonical.launchpad.webapp.errorlog import ErrorReportingUtility66from canonical.launchpad.webapp.errorlog import ErrorReportingUtility
65from canonical.testing import LaunchpadZopelessLayer67from canonical.testing import LaunchpadZopelessLayer
@@ -113,7 +115,8 @@
113 super(TestUploadProcessorBase, self).setUp()115 super(TestUploadProcessorBase, self).setUp()
114116
115 self.queue_folder = tempfile.mkdtemp()117 self.queue_folder = tempfile.mkdtemp()
116 os.makedirs(os.path.join(self.queue_folder, "incoming"))118 self.incoming_folder = os.path.join(self.queue_folder, "incoming")
119 os.makedirs(self.incoming_folder)
117120
118 self.test_files_dir = os.path.join(config.root,121 self.test_files_dir = os.path.join(config.root,
119 "lib/lp/archiveuploader/tests/data/suite")122 "lib/lp/archiveuploader/tests/data/suite")
@@ -139,6 +142,30 @@
139 shutil.rmtree(self.queue_folder)142 shutil.rmtree(self.queue_folder)
140 super(TestUploadProcessorBase, self).tearDown()143 super(TestUploadProcessorBase, self).tearDown()
141144
145 def getUploadProcessor(self, txn):
146 def getPolicy(distro):
147 self.options.distro = distro.name
148 return findPolicyByOptions(self.options)
149 return UploadProcessor(
150 self.options.base_fsroot, self.options.dryrun,
151 self.options.nomails,
152 self.options.keep, getPolicy, txn, self.log)
153
154 def publishPackage(self, packagename, version, source=True,
155 archive=None):
156 """Publish a single package that is currently NEW in the queue."""
157 queue_items = self.breezy.getQueueItems(
158 status=PackageUploadStatus.NEW, name=packagename,
159 version=version, exact_match=True, archive=archive)
160 self.assertEqual(queue_items.count(), 1)
161 queue_item = queue_items[0]
162 queue_item.setAccepted()
163 if source:
164 pubrec = queue_item.sources[0].publish(self.log)
165 else:
166 pubrec = queue_item.builds[0].publish(self.log)
167 return pubrec
168
142 def assertLogContains(self, line):169 def assertLogContains(self, line):
143 """Assert if a given line is present in the log messages."""170 """Assert if a given line is present in the log messages."""
144 self.assertTrue(line in self.log.lines,171 self.assertTrue(line in self.log.lines,
@@ -208,25 +235,29 @@
208 filename, len(content), StringIO(content),235 filename, len(content), StringIO(content),
209 'application/x-gtar')236 'application/x-gtar')
210237
211 def queueUpload(self, upload_name, relative_path="", test_files_dir=None):238 def queueUpload(self, upload_name, relative_path="", test_files_dir=None,
239 queue_entry=None):
212 """Queue one of our test uploads.240 """Queue one of our test uploads.
213241
214 upload_name is the name of the test upload directory. It is also242 upload_name is the name of the test upload directory. If there
243 is no explicit queue entry name specified, it is also
215 the name of the queue entry directory we create.244 the name of the queue entry directory we create.
216 relative_path is the path to create inside the upload, eg245 relative_path is the path to create inside the upload, eg
217 ubuntu/~malcc/default. If not specified, defaults to "".246 ubuntu/~malcc/default. If not specified, defaults to "".
218247
219 Return the path to the upload queue entry directory created.248 Return the path to the upload queue entry directory created.
220 """249 """
250 if queue_entry is None:
251 queue_entry = upload_name
221 target_path = os.path.join(252 target_path = os.path.join(
222 self.queue_folder, "incoming", upload_name, relative_path)253 self.incoming_folder, queue_entry, relative_path)
223 if test_files_dir is None:254 if test_files_dir is None:
224 test_files_dir = self.test_files_dir255 test_files_dir = self.test_files_dir
225 upload_dir = os.path.join(test_files_dir, upload_name)256 upload_dir = os.path.join(test_files_dir, upload_name)
226 if relative_path:257 if relative_path:
227 os.makedirs(os.path.dirname(target_path))258 os.makedirs(os.path.dirname(target_path))
228 shutil.copytree(upload_dir, target_path)259 shutil.copytree(upload_dir, target_path)
229 return os.path.join(self.queue_folder, "incoming", upload_name)260 return os.path.join(self.incoming_folder, queue_entry)
230261
231 def processUpload(self, processor, upload_dir):262 def processUpload(self, processor, upload_dir):
232 """Process an upload queue entry directory.263 """Process an upload queue entry directory.
@@ -248,8 +279,7 @@
248 self.layer.txn.commit()279 self.layer.txn.commit()
249 if policy is not None:280 if policy is not None:
250 self.options.context = policy281 self.options.context = policy
251 return UploadProcessor(282 return self.getUploadProcessor(self.layer.txn)
252 self.options, self.layer.txn, self.log)
253283
254 def assertEmail(self, contents=None, recipients=None):284 def assertEmail(self, contents=None, recipients=None):
255 """Check last email content and recipients.285 """Check last email content and recipients.
@@ -341,24 +371,9 @@
341 "Expected acceptance email not rejection. Actually Got:\n%s"371 "Expected acceptance email not rejection. Actually Got:\n%s"
342 % raw_msg)372 % raw_msg)
343373
344 def _publishPackage(self, packagename, version, source=True,
345 archive=None):
346 """Publish a single package that is currently NEW in the queue."""
347 queue_items = self.breezy.getQueueItems(
348 status=PackageUploadStatus.NEW, name=packagename,
349 version=version, exact_match=True, archive=archive)
350 self.assertEqual(queue_items.count(), 1)
351 queue_item = queue_items[0]
352 queue_item.setAccepted()
353 if source:
354 pubrec = queue_item.sources[0].publish(self.log)
355 else:
356 pubrec = queue_item.builds[0].publish(self.log)
357 return pubrec
358
359 def testInstantiate(self):374 def testInstantiate(self):
360 """UploadProcessor should instantiate"""375 """UploadProcessor should instantiate"""
361 up = UploadProcessor(self.options, None, self.log)376 up = self.getUploadProcessor(None)
362377
363 def testLocateDirectories(self):378 def testLocateDirectories(self):
364 """Return a sorted list of subdirs in a directory.379 """Return a sorted list of subdirs in a directory.
@@ -372,7 +387,7 @@
372 os.mkdir("%s/dir1" % testdir)387 os.mkdir("%s/dir1" % testdir)
373 os.mkdir("%s/dir2" % testdir)388 os.mkdir("%s/dir2" % testdir)
374389
375 up = UploadProcessor(self.options, None, self.log)390 up = self.getUploadProcessor(None)
376 located_dirs = up.locateDirectories(testdir)391 located_dirs = up.locateDirectories(testdir)
377 self.assertEqual(located_dirs, ['dir1', 'dir2', 'dir3'])392 self.assertEqual(located_dirs, ['dir1', 'dir2', 'dir3'])
378 finally:393 finally:
@@ -390,7 +405,7 @@
390 open("%s/2_source.changes" % testdir, "w").close()405 open("%s/2_source.changes" % testdir, "w").close()
391 open("%s/3.not_changes" % testdir, "w").close()406 open("%s/3.not_changes" % testdir, "w").close()
392407
393 up = UploadProcessor(self.options, None, self.log)408 up = self.getUploadProcessor(None)
394 located_files = up.locateChangesFiles(testdir)409 located_files = up.locateChangesFiles(testdir)
395 self.assertEqual(410 self.assertEqual(
396 located_files, ["2_source.changes", "1.changes"])411 located_files, ["2_source.changes", "1.changes"])
@@ -418,7 +433,7 @@
418433
419 # Move it434 # Move it
420 self.options.base_fsroot = testdir435 self.options.base_fsroot = testdir
421 up = UploadProcessor(self.options, None, self.log)436 up = self.getUploadProcessor(None)
422 up.moveUpload(upload, target_name)437 up.moveUpload(upload, target_name)
423438
424 # Check it moved439 # Check it moved
@@ -439,7 +454,7 @@
439454
440 # Remove it455 # Remove it
441 self.options.base_fsroot = testdir456 self.options.base_fsroot = testdir
442 up = UploadProcessor(self.options, None, self.log)457 up = self.getUploadProcessor(None)
443 up.moveProcessedUpload(upload, "accepted")458 up.moveProcessedUpload(upload, "accepted")
444459
445 # Check it was removed, not moved460 # Check it was removed, not moved
@@ -462,7 +477,7 @@
462477
463 # Move it478 # Move it
464 self.options.base_fsroot = testdir479 self.options.base_fsroot = testdir
465 up = UploadProcessor(self.options, None, self.log)480 up = self.getUploadProcessor(None)
466 up.moveProcessedUpload(upload, "rejected")481 up.moveProcessedUpload(upload, "rejected")
467482
468 # Check it moved483 # Check it moved
@@ -485,7 +500,7 @@
485500
486 # Remove it501 # Remove it
487 self.options.base_fsroot = testdir502 self.options.base_fsroot = testdir
488 up = UploadProcessor(self.options, None, self.log)503 up = self.getUploadProcessor(None)
489 up.removeUpload(upload)504 up.removeUpload(upload)
490505
491 # Check it was removed, not moved506 # Check it was removed, not moved
@@ -498,7 +513,7 @@
498513
499 def testOrderFilenames(self):514 def testOrderFilenames(self):
500 """orderFilenames sorts _source.changes ahead of other files."""515 """orderFilenames sorts _source.changes ahead of other files."""
501 up = UploadProcessor(self.options, None, self.log)516 up = self.getUploadProcessor(None)
502517
503 self.assertEqual(["d_source.changes", "a", "b", "c"],518 self.assertEqual(["d_source.changes", "a", "b", "c"],
504 up.orderFilenames(["b", "a", "d_source.changes", "c"]))519 up.orderFilenames(["b", "a", "d_source.changes", "c"]))
@@ -522,8 +537,7 @@
522 # Register our broken upload policy537 # Register our broken upload policy
523 AbstractUploadPolicy._registerPolicy(BrokenUploadPolicy)538 AbstractUploadPolicy._registerPolicy(BrokenUploadPolicy)
524 self.options.context = 'broken'539 self.options.context = 'broken'
525 uploadprocessor = UploadProcessor(540 uploadprocessor = self.getUploadProcessor(self.layer.txn)
526 self.options, self.layer.txn, self.log)
527541
528 # Upload a package to Breezy.542 # Upload a package to Breezy.
529 upload_dir = self.queueUpload("baz_1.0-1")543 upload_dir = self.queueUpload("baz_1.0-1")
@@ -634,7 +648,7 @@
634 # Upload 'bar-1.0-1' source and binary to ubuntu/breezy.648 # Upload 'bar-1.0-1' source and binary to ubuntu/breezy.
635 upload_dir = self.queueUpload("bar_1.0-1")649 upload_dir = self.queueUpload("bar_1.0-1")
636 self.processUpload(uploadprocessor, upload_dir)650 self.processUpload(uploadprocessor, upload_dir)
637 bar_source_pub = self._publishPackage('bar', '1.0-1')651 bar_source_pub = self.publishPackage('bar', '1.0-1')
638 [bar_original_build] = bar_source_pub.createMissingBuilds()652 [bar_original_build] = bar_source_pub.createMissingBuilds()
639653
640 # Move the source from the accepted queue.654 # Move the source from the accepted queue.
@@ -653,7 +667,7 @@
653 self.processUpload(uploadprocessor, upload_dir)667 self.processUpload(uploadprocessor, upload_dir)
654 self.assertEqual(668 self.assertEqual(
655 uploadprocessor.last_processed_upload.is_rejected, False)669 uploadprocessor.last_processed_upload.is_rejected, False)
656 bar_bin_pubs = self._publishPackage('bar', '1.0-1', source=False)670 bar_bin_pubs = self.publishPackage('bar', '1.0-1', source=False)
657 # Mangle its publishing component to "restricted" so we can check671 # Mangle its publishing component to "restricted" so we can check
658 # the copy archive ancestry override later.672 # the copy archive ancestry override later.
659 restricted = getUtility(IComponentSet)["restricted"]673 restricted = getUtility(IComponentSet)["restricted"]
@@ -746,14 +760,14 @@
746 # Upload 'bar-1.0-1' source and binary to ubuntu/breezy.760 # Upload 'bar-1.0-1' source and binary to ubuntu/breezy.
747 upload_dir = self.queueUpload("bar_1.0-1")761 upload_dir = self.queueUpload("bar_1.0-1")
748 self.processUpload(uploadprocessor, upload_dir)762 self.processUpload(uploadprocessor, upload_dir)
749 bar_source_pub = self._publishPackage('bar', '1.0-1')763 bar_source_pub = self.publishPackage('bar', '1.0-1')
750 [bar_original_build] = bar_source_pub.createMissingBuilds()764 [bar_original_build] = bar_source_pub.createMissingBuilds()
751765
752 self.options.context = 'buildd'766 self.options.context = 'buildd'
753 self.options.buildid = bar_original_build.id767 self.options.buildid = bar_original_build.id
754 upload_dir = self.queueUpload("bar_1.0-1_binary")768 upload_dir = self.queueUpload("bar_1.0-1_binary")
755 self.processUpload(uploadprocessor, upload_dir)769 self.processUpload(uploadprocessor, upload_dir)
756 [bar_binary_pub] = self._publishPackage("bar", "1.0-1", source=False)770 [bar_binary_pub] = self.publishPackage("bar", "1.0-1", source=False)
757771
758 # Prepare ubuntu/breezy-autotest to build sources in i386.772 # Prepare ubuntu/breezy-autotest to build sources in i386.
759 breezy_autotest = self.ubuntu['breezy-autotest']773 breezy_autotest = self.ubuntu['breezy-autotest']
@@ -803,7 +817,7 @@
803 # Upload 'bar-1.0-1' source and binary to ubuntu/breezy.817 # Upload 'bar-1.0-1' source and binary to ubuntu/breezy.
804 upload_dir = self.queueUpload("bar_1.0-1")818 upload_dir = self.queueUpload("bar_1.0-1")
805 self.processUpload(uploadprocessor, upload_dir)819 self.processUpload(uploadprocessor, upload_dir)
806 bar_source_old = self._publishPackage('bar', '1.0-1')820 bar_source_old = self.publishPackage('bar', '1.0-1')
807821
808 # Upload 'bar-1.0-1' source and binary to ubuntu/breezy.822 # Upload 'bar-1.0-1' source and binary to ubuntu/breezy.
809 upload_dir = self.queueUpload("bar_1.0-2")823 upload_dir = self.queueUpload("bar_1.0-2")
@@ -816,7 +830,7 @@
816 self.options.buildid = bar_original_build.id830 self.options.buildid = bar_original_build.id
817 upload_dir = self.queueUpload("bar_1.0-2_binary")831 upload_dir = self.queueUpload("bar_1.0-2_binary")
818 self.processUpload(uploadprocessor, upload_dir)832 self.processUpload(uploadprocessor, upload_dir)
819 [bar_binary_pub] = self._publishPackage("bar", "1.0-2", source=False)833 [bar_binary_pub] = self.publishPackage("bar", "1.0-2", source=False)
820834
821 # Create a COPY archive for building in non-virtual builds.835 # Create a COPY archive for building in non-virtual builds.
822 uploader = getUtility(IPersonSet).getByName('name16')836 uploader = getUtility(IPersonSet).getByName('name16')
@@ -971,7 +985,7 @@
971 partner_archive = getUtility(IArchiveSet).getByDistroPurpose(985 partner_archive = getUtility(IArchiveSet).getByDistroPurpose(
972 self.ubuntu, ArchivePurpose.PARTNER)986 self.ubuntu, ArchivePurpose.PARTNER)
973 self.assertTrue(partner_archive)987 self.assertTrue(partner_archive)
974 self._publishPackage("foocomm", "1.0-1", archive=partner_archive)988 self.publishPackage("foocomm", "1.0-1", archive=partner_archive)
975989
976 # Check the publishing record's archive and component.990 # Check the publishing record's archive and component.
977 foocomm_spph = SourcePackagePublishingHistory.selectOneBy(991 foocomm_spph = SourcePackagePublishingHistory.selectOneBy(
@@ -1015,7 +1029,7 @@
1015 self.assertEqual(foocomm_bpr.component.name, 'partner')1029 self.assertEqual(foocomm_bpr.component.name, 'partner')
10161030
1017 # Publish the upload so we can check the publishing record.1031 # Publish the upload so we can check the publishing record.
1018 self._publishPackage("foocomm", "1.0-1", source=False)1032 self.publishPackage("foocomm", "1.0-1", source=False)
10191033
1020 # Check the publishing record's archive and component.1034 # Check the publishing record's archive and component.
1021 foocomm_bpph = BinaryPackagePublishingHistory.selectOneBy(1035 foocomm_bpph = BinaryPackagePublishingHistory.selectOneBy(
@@ -1054,14 +1068,14 @@
1054 # Accept and publish the upload.1068 # Accept and publish the upload.
1055 partner_archive = getUtility(IArchiveSet).getByDistroPurpose(1069 partner_archive = getUtility(IArchiveSet).getByDistroPurpose(
1056 self.ubuntu, ArchivePurpose.PARTNER)1070 self.ubuntu, ArchivePurpose.PARTNER)
1057 self._publishPackage("foocomm", "1.0-1", archive=partner_archive)1071 self.publishPackage("foocomm", "1.0-1", archive=partner_archive)
10581072
1059 # Now do the same thing with a binary package.1073 # Now do the same thing with a binary package.
1060 upload_dir = self.queueUpload("foocomm_1.0-1_binary")1074 upload_dir = self.queueUpload("foocomm_1.0-1_binary")
1061 self.processUpload(uploadprocessor, upload_dir)1075 self.processUpload(uploadprocessor, upload_dir)
10621076
1063 # Accept and publish the upload.1077 # Accept and publish the upload.
1064 self._publishPackage("foocomm", "1.0-1", source=False,1078 self.publishPackage("foocomm", "1.0-1", source=False,
1065 archive=partner_archive)1079 archive=partner_archive)
10661080
1067 # Upload the next source version of the package.1081 # Upload the next source version of the package.
@@ -1105,8 +1119,7 @@
1105 self.breezy.status = SeriesStatus.CURRENT1119 self.breezy.status = SeriesStatus.CURRENT
1106 self.layer.txn.commit()1120 self.layer.txn.commit()
1107 self.options.context = 'insecure'1121 self.options.context = 'insecure'
1108 uploadprocessor = UploadProcessor(1122 uploadprocessor = self.getUploadProcessor(self.layer.txn)
1109 self.options, self.layer.txn, self.log)
11101123
1111 # Upload a package for Breezy.1124 # Upload a package for Breezy.
1112 upload_dir = self.queueUpload("foocomm_1.0-1_proposed")1125 upload_dir = self.queueUpload("foocomm_1.0-1_proposed")
@@ -1124,8 +1137,7 @@
1124 self.breezy.status = SeriesStatus.CURRENT1137 self.breezy.status = SeriesStatus.CURRENT
1125 self.layer.txn.commit()1138 self.layer.txn.commit()
1126 self.options.context = 'insecure'1139 self.options.context = 'insecure'
1127 uploadprocessor = UploadProcessor(1140 uploadprocessor = self.getUploadProcessor(self.layer.txn)
1128 self.options, self.layer.txn, self.log)
11291141
1130 # Upload a package for Breezy.1142 # Upload a package for Breezy.
1131 upload_dir = self.queueUpload("foocomm_1.0-1")1143 upload_dir = self.queueUpload("foocomm_1.0-1")
@@ -1140,8 +1152,7 @@
1140 pocket and ensure it fails."""1152 pocket and ensure it fails."""
1141 # Set up the uploadprocessor with appropriate options and logger.1153 # Set up the uploadprocessor with appropriate options and logger.
1142 self.options.context = 'insecure'1154 self.options.context = 'insecure'
1143 uploadprocessor = UploadProcessor(1155 uploadprocessor = self.getUploadProcessor(self.layer.txn)
1144 self.options, self.layer.txn, self.log)
11451156
1146 # Upload a package for Breezy.1157 # Upload a package for Breezy.
1147 upload_dir = self.queueUpload("foocomm_1.0-1_updates")1158 upload_dir = self.queueUpload("foocomm_1.0-1_updates")
@@ -1302,8 +1313,7 @@
1302 used.1313 used.
1303 That exception will then initiate the creation of an OOPS report.1314 That exception will then initiate the creation of an OOPS report.
1304 """1315 """
1305 processor = UploadProcessor(1316 processor = self.getUploadProcessor(self.layer.txn)
1306 self.options, self.layer.txn, self.log)
13071317
1308 upload_dir = self.queueUpload("foocomm_1.0-1_proposed")1318 upload_dir = self.queueUpload("foocomm_1.0-1_proposed")
1309 bogus_changesfile_data = '''1319 bogus_changesfile_data = '''
@@ -1346,8 +1356,7 @@
1346 self.setupBreezy()1356 self.setupBreezy()
1347 self.layer.txn.commit()1357 self.layer.txn.commit()
1348 self.options.context = 'absolutely-anything'1358 self.options.context = 'absolutely-anything'
1349 uploadprocessor = UploadProcessor(1359 uploadprocessor = self.getUploadProcessor(self.layer.txn)
1350 self.options, self.layer.txn, self.log)
13511360
1352 # Upload the source first to enable the binary later:1361 # Upload the source first to enable the binary later:
1353 upload_dir = self.queueUpload("bar_1.0-1_lzma")1362 upload_dir = self.queueUpload("bar_1.0-1_lzma")
@@ -1357,7 +1366,7 @@
1357 self.assertTrue(1366 self.assertTrue(
1358 "rejected" not in raw_msg,1367 "rejected" not in raw_msg,
1359 "Failed to upload bar source:\n%s" % raw_msg)1368 "Failed to upload bar source:\n%s" % raw_msg)
1360 self._publishPackage("bar", "1.0-1")1369 self.publishPackage("bar", "1.0-1")
1361 # Clear out emails generated during upload.1370 # Clear out emails generated during upload.
1362 ignore = pop_notifications()1371 ignore = pop_notifications()
13631372
@@ -1456,15 +1465,14 @@
1456 permission=ArchivePermissionType.UPLOAD, person=uploader,1465 permission=ArchivePermissionType.UPLOAD, person=uploader,
1457 component=restricted)1466 component=restricted)
14581467
1459 uploadprocessor = UploadProcessor(1468 uploadprocessor = self.getUploadProcessor(self.layer.txn)
1460 self.options, self.layer.txn, self.log)
14611469
1462 # Upload the first version and accept it to make it known in1470 # Upload the first version and accept it to make it known in
1463 # Ubuntu. The uploader has rights to upload NEW packages to1471 # Ubuntu. The uploader has rights to upload NEW packages to
1464 # components that he does not have direct rights to.1472 # components that he does not have direct rights to.
1465 upload_dir = self.queueUpload("bar_1.0-1")1473 upload_dir = self.queueUpload("bar_1.0-1")
1466 self.processUpload(uploadprocessor, upload_dir)1474 self.processUpload(uploadprocessor, upload_dir)
1467 bar_source_pub = self._publishPackage('bar', '1.0-1')1475 bar_source_pub = self.publishPackage('bar', '1.0-1')
1468 # Clear out emails generated during upload.1476 # Clear out emails generated during upload.
1469 ignore = pop_notifications()1477 ignore = pop_notifications()
14701478
@@ -1509,15 +1517,14 @@
1509 permission=ArchivePermissionType.UPLOAD, person=uploader,1517 permission=ArchivePermissionType.UPLOAD, person=uploader,
1510 component=restricted)1518 component=restricted)
15111519
1512 uploadprocessor = UploadProcessor(1520 uploadprocessor = self.getUploadProcessor(self.layer.txn)
1513 self.options, self.layer.txn, self.log)
15141521
1515 # Upload the first version and accept it to make it known in1522 # Upload the first version and accept it to make it known in
1516 # Ubuntu. The uploader has rights to upload NEW packages to1523 # Ubuntu. The uploader has rights to upload NEW packages to
1517 # components that he does not have direct rights to.1524 # components that he does not have direct rights to.
1518 upload_dir = self.queueUpload("bar_1.0-1")1525 upload_dir = self.queueUpload("bar_1.0-1")
1519 self.processUpload(uploadprocessor, upload_dir)1526 self.processUpload(uploadprocessor, upload_dir)
1520 bar_source_pub = self._publishPackage('bar', '1.0-1')1527 bar_source_pub = self.publishPackage('bar', '1.0-1')
1521 # Clear out emails generated during upload.1528 # Clear out emails generated during upload.
1522 ignore = pop_notifications()1529 ignore = pop_notifications()
15231530
@@ -1590,8 +1597,7 @@
1590 # with pointer to the Soyuz questions in Launchpad and the1597 # with pointer to the Soyuz questions in Launchpad and the
1591 # reason why the message was sent to the current recipients.1598 # reason why the message was sent to the current recipients.
1592 self.setupBreezy()1599 self.setupBreezy()
1593 uploadprocessor = UploadProcessor(1600 uploadprocessor = self.getUploadProcessor(self.layer.txn)
1594 self.options, self.layer.txn, self.log)
15951601
1596 upload_dir = self.queueUpload("bar_1.0-1", "boing")1602 upload_dir = self.queueUpload("bar_1.0-1", "boing")
1597 self.processUpload(uploadprocessor, upload_dir)1603 self.processUpload(uploadprocessor, upload_dir)
@@ -1636,8 +1642,7 @@
1636 self.setupBreezy()1642 self.setupBreezy()
1637 self.layer.txn.commit()1643 self.layer.txn.commit()
1638 self.options.context = 'absolutely-anything'1644 self.options.context = 'absolutely-anything'
1639 uploadprocessor = UploadProcessor(1645 uploadprocessor = self.getUploadProcessor(self.layer.txn)
1640 self.options, self.layer.txn, self.log)
16411646
1642 # Upload the source.1647 # Upload the source.
1643 upload_dir = self.queueUpload("bar_1.0-1_3.0-quilt")1648 upload_dir = self.queueUpload("bar_1.0-1_3.0-quilt")
@@ -1655,8 +1660,7 @@
1655 permitted_formats=[SourcePackageFormat.FORMAT_3_0_QUILT])1660 permitted_formats=[SourcePackageFormat.FORMAT_3_0_QUILT])
1656 self.layer.txn.commit()1661 self.layer.txn.commit()
1657 self.options.context = 'absolutely-anything'1662 self.options.context = 'absolutely-anything'
1658 uploadprocessor = UploadProcessor(1663 uploadprocessor = self.getUploadProcessor(self.layer.txn)
1659 self.options, self.layer.txn, self.log)
16601664
1661 # Upload the source.1665 # Upload the source.
1662 upload_dir = self.queueUpload("bar_1.0-1_3.0-quilt")1666 upload_dir = self.queueUpload("bar_1.0-1_3.0-quilt")
@@ -1666,7 +1670,7 @@
1666 self.assertTrue(1670 self.assertTrue(
1667 "rejected" not in raw_msg,1671 "rejected" not in raw_msg,
1668 "Failed to upload bar source:\n%s" % raw_msg)1672 "Failed to upload bar source:\n%s" % raw_msg)
1669 spph = self._publishPackage("bar", "1.0-1")1673 spph = self.publishPackage("bar", "1.0-1")
16701674
1671 self.assertEquals(1675 self.assertEquals(
1672 sorted((sprf.libraryfile.filename, sprf.filetype)1676 sorted((sprf.libraryfile.filename, sprf.filetype)
@@ -1689,8 +1693,7 @@
1689 permitted_formats=[SourcePackageFormat.FORMAT_3_0_QUILT])1693 permitted_formats=[SourcePackageFormat.FORMAT_3_0_QUILT])
1690 self.layer.txn.commit()1694 self.layer.txn.commit()
1691 self.options.context = 'absolutely-anything'1695 self.options.context = 'absolutely-anything'
1692 uploadprocessor = UploadProcessor(1696 uploadprocessor = self.getUploadProcessor(self.layer.txn)
1693 self.options, self.layer.txn, self.log)
16941697
1695 # Upload the first source.1698 # Upload the first source.
1696 upload_dir = self.queueUpload("bar_1.0-1_3.0-quilt")1699 upload_dir = self.queueUpload("bar_1.0-1_3.0-quilt")
@@ -1700,7 +1703,7 @@
1700 self.assertTrue(1703 self.assertTrue(
1701 "rejected" not in raw_msg,1704 "rejected" not in raw_msg,
1702 "Failed to upload bar source:\n%s" % raw_msg)1705 "Failed to upload bar source:\n%s" % raw_msg)
1703 spph = self._publishPackage("bar", "1.0-1")1706 spph = self.publishPackage("bar", "1.0-1")
17041707
1705 # Upload another source sharing the same (component) orig.1708 # Upload another source sharing the same (component) orig.
1706 upload_dir = self.queueUpload("bar_1.0-2_3.0-quilt_without_orig")1709 upload_dir = self.queueUpload("bar_1.0-2_3.0-quilt_without_orig")
@@ -1728,8 +1731,7 @@
1728 permitted_formats=[SourcePackageFormat.FORMAT_3_0_NATIVE])1731 permitted_formats=[SourcePackageFormat.FORMAT_3_0_NATIVE])
1729 self.layer.txn.commit()1732 self.layer.txn.commit()
1730 self.options.context = 'absolutely-anything'1733 self.options.context = 'absolutely-anything'
1731 uploadprocessor = UploadProcessor(1734 uploadprocessor = self.getUploadProcessor(self.layer.txn)
1732 self.options, self.layer.txn, self.log)
17331735
1734 # Upload the source.1736 # Upload the source.
1735 upload_dir = self.queueUpload("bar_1.0_3.0-native")1737 upload_dir = self.queueUpload("bar_1.0_3.0-native")
@@ -1739,7 +1741,7 @@
1739 self.assertTrue(1741 self.assertTrue(
1740 "rejected" not in raw_msg,1742 "rejected" not in raw_msg,
1741 "Failed to upload bar source:\n%s" % raw_msg)1743 "Failed to upload bar source:\n%s" % raw_msg)
1742 spph = self._publishPackage("bar", "1.0")1744 spph = self.publishPackage("bar", "1.0")
17431745
1744 self.assertEquals(1746 self.assertEquals(
1745 sorted((sprf.libraryfile.filename, sprf.filetype)1747 sorted((sprf.libraryfile.filename, sprf.filetype)
@@ -1754,8 +1756,7 @@
1754 self.setupBreezy()1756 self.setupBreezy()
1755 self.layer.txn.commit()1757 self.layer.txn.commit()
1756 self.options.context = 'absolutely-anything'1758 self.options.context = 'absolutely-anything'
1757 uploadprocessor = UploadProcessor(1759 uploadprocessor = self.getUploadProcessor(self.layer.txn)
1758 self.options, self.layer.txn, self.log)
17591760
1760 # Upload the source.1761 # Upload the source.
1761 upload_dir = self.queueUpload("bar_1.0-1_1.0-bzip2")1762 upload_dir = self.queueUpload("bar_1.0-1_1.0-bzip2")
@@ -1772,8 +1773,7 @@
1772 self.setupBreezy()1773 self.setupBreezy()
1773 breezy = self.ubuntu['breezy']1774 breezy = self.ubuntu['breezy']
1774 breezy.status = SeriesStatus.CURRENT1775 breezy.status = SeriesStatus.CURRENT
1775 uploadprocessor = UploadProcessor(1776 uploadprocessor = self.getUploadProcessor(self.layer.txn)
1776 self.options, self.layer.txn, self.log)
17771777
1778 upload_dir = self.queueUpload("bar_1.0-1")1778 upload_dir = self.queueUpload("bar_1.0-1")
1779 self.processUpload(uploadprocessor, upload_dir)1779 self.processUpload(uploadprocessor, upload_dir)
17801780
=== modified file 'lib/lp/archiveuploader/uploadprocessor.py'
--- lib/lp/archiveuploader/uploadprocessor.py 2010-08-04 00:30:56 +0000
+++ lib/lp/archiveuploader/uploadprocessor.py 2010-08-12 22:16:35 +0000
@@ -60,7 +60,7 @@
60from lp.archiveuploader.nascentupload import (60from lp.archiveuploader.nascentupload import (
61 NascentUpload, FatalUploadError, EarlyReturnUploadError)61 NascentUpload, FatalUploadError, EarlyReturnUploadError)
62from lp.archiveuploader.uploadpolicy import (62from lp.archiveuploader.uploadpolicy import (
63 findPolicyByOptions, UploadPolicyError)63 UploadPolicyError)
64from lp.soyuz.interfaces.archive import IArchiveSet, NoSuchPPA64from lp.soyuz.interfaces.archive import IArchiveSet, NoSuchPPA
65from lp.registry.interfaces.distribution import IDistributionSet65from lp.registry.interfaces.distribution import IDistributionSet
66from lp.registry.interfaces.person import IPersonSet66from lp.registry.interfaces.person import IPersonSet
@@ -108,16 +108,33 @@
108class UploadProcessor:108class UploadProcessor:
109 """Responsible for processing uploads. See module docstring."""109 """Responsible for processing uploads. See module docstring."""
110110
111 def __init__(self, options, ztm, log):111 def __init__(self, base_fsroot, dry_run, no_mails, keep, policy_for_distro,
112 self.options = options112 ztm, log):
113 """Create a new upload processor.
114
115 :param base_fsroot: Root path for queue to use
116 :param dry_run: Run but don't commit changes to database
117 :param no_mails: Don't send out any emails
118 :param builds: Interpret leaf names as build ids
119 :param keep: Leave the files in place, don't move them away
120 :param policy_for_distro: callback to obtain Policy object for a
121 distribution
122 :param ztm: Database transaction to use
123 :param log: Logger to use for reporting
124 """
125 self.base_fsroot = base_fsroot
126 self.dry_run = dry_run
127 self.keep = keep
128 self.last_processed_upload = None
129 self.log = log
130 self.no_mails = no_mails
131 self._getPolicyForDistro = policy_for_distro
113 self.ztm = ztm132 self.ztm = ztm
114 self.log = log
115 self.last_processed_upload = None
116133
117 def processUploadQueue(self):134 def processUploadQueue(self, leaf_name=None):
118 """Search for uploads, and process them.135 """Search for uploads, and process them.
119136
120 Uploads are searched for in the 'incoming' directory inside the137 Uploads are searched for in the 'incoming' directory inside the
121 base_fsroot.138 base_fsroot.
122139
123 This method also creates the 'incoming', 'accepted', 'rejected', and140 This method also creates the 'incoming', 'accepted', 'rejected', and
@@ -127,19 +144,22 @@
127 self.log.debug("Beginning processing")144 self.log.debug("Beginning processing")
128145
129 for subdir in ["incoming", "accepted", "rejected", "failed"]:146 for subdir in ["incoming", "accepted", "rejected", "failed"]:
130 full_subdir = os.path.join(self.options.base_fsroot, subdir)147 full_subdir = os.path.join(self.base_fsroot, subdir)
131 if not os.path.exists(full_subdir):148 if not os.path.exists(full_subdir):
132 self.log.debug("Creating directory %s" % full_subdir)149 self.log.debug("Creating directory %s" % full_subdir)
133 os.mkdir(full_subdir)150 os.mkdir(full_subdir)
134151
135 fsroot = os.path.join(self.options.base_fsroot, "incoming")152 fsroot = os.path.join(self.base_fsroot, "incoming")
136 uploads_to_process = self.locateDirectories(fsroot)153 uploads_to_process = self.locateDirectories(fsroot)
137 self.log.debug("Checked in %s, found %s"154 self.log.debug("Checked in %s, found %s"
138 % (fsroot, uploads_to_process))155 % (fsroot, uploads_to_process))
139 for upload in uploads_to_process:156 for upload in uploads_to_process:
140 self.log.debug("Considering upload %s" % upload)157 self.log.debug("Considering upload %s" % upload)
158 if leaf_name is not None and upload != leaf_name:
159 self.log.debug("Skipping %s -- does not match %s" % (
160 upload, leaf_name))
161 continue
141 self.processUpload(fsroot, upload)162 self.processUpload(fsroot, upload)
142
143 finally:163 finally:
144 self.log.debug("Rolling back any remaining transactions.")164 self.log.debug("Rolling back any remaining transactions.")
145 self.ztm.abort()165 self.ztm.abort()
@@ -152,16 +172,7 @@
152 is 'failed', otherwise it is the worst of the results from the172 is 'failed', otherwise it is the worst of the results from the
153 individual changes files, in order 'failed', 'rejected', 'accepted'.173 individual changes files, in order 'failed', 'rejected', 'accepted'.
154174
155 If the leafname option is set but its value is not the same as the
156 name of the upload directory, skip it entirely.
157
158 """175 """
159 if (self.options.leafname is not None and
160 upload != self.options.leafname):
161 self.log.debug("Skipping %s -- does not match %s" % (
162 upload, self.options.leafname))
163 return
164
165 upload_path = os.path.join(fsroot, upload)176 upload_path = os.path.join(fsroot, upload)
166 changes_files = self.locateChangesFiles(upload_path)177 changes_files = self.locateChangesFiles(upload_path)
167178
@@ -242,7 +253,7 @@
242 # Skip lockfile deletion, see similar code in lp.poppy.hooks.253 # Skip lockfile deletion, see similar code in lp.poppy.hooks.
243 fsroot_lock.release(skip_delete=True)254 fsroot_lock.release(skip_delete=True)
244255
245 sorted_dir_names = sorted(256 sorted_dir_names = sorted(
246 dir_name257 dir_name
247 for dir_name in dir_names258 for dir_name in dir_names
248 if os.path.isdir(os.path.join(fsroot, dir_name)))259 if os.path.isdir(os.path.join(fsroot, dir_name)))
@@ -321,8 +332,7 @@
321 "https://help.launchpad.net/Packaging/PPA#Uploading "332 "https://help.launchpad.net/Packaging/PPA#Uploading "
322 "and update your configuration.")))333 "and update your configuration.")))
323 self.log.debug("Finding fresh policy")334 self.log.debug("Finding fresh policy")
324 self.options.distro = distribution.name335 policy = self._getPolicyForDistro(distribution)
325 policy = findPolicyByOptions(self.options)
326 policy.archive = archive336 policy.archive = archive
327337
328 # DistroSeries overriding respect the following precedence:338 # DistroSeries overriding respect the following precedence:
@@ -396,7 +406,7 @@
396 # when transaction is committed) this will cause any emails sent406 # when transaction is committed) this will cause any emails sent
397 # sent by do_reject to be lost.407 # sent by do_reject to be lost.
398 notify = True408 notify = True
399 if self.options.dryrun or self.options.nomails:409 if self.dry_run or self.no_mails:
400 notify = False410 notify = False
401 if upload.is_rejected:411 if upload.is_rejected:
402 result = UploadStatusEnum.REJECTED412 result = UploadStatusEnum.REJECTED
@@ -415,7 +425,7 @@
415 for msg in upload.rejections:425 for msg in upload.rejections:
416 self.log.warn("\t%s" % msg)426 self.log.warn("\t%s" % msg)
417427
418 if self.options.dryrun:428 if self.dry_run:
419 self.log.info("Dry run, aborting transaction.")429 self.log.info("Dry run, aborting transaction.")
420 self.ztm.abort()430 self.ztm.abort()
421 else:431 else:
@@ -434,7 +444,7 @@
434 This includes moving the given upload directory and moving the444 This includes moving the given upload directory and moving the
435 matching .distro file, if it exists.445 matching .distro file, if it exists.
436 """446 """
437 if self.options.keep or self.options.dryrun:447 if self.keep or self.dry_run:
438 self.log.debug("Keeping contents untouched")448 self.log.debug("Keeping contents untouched")
439 return449 return
440450
@@ -462,21 +472,21 @@
462 This includes moving the given upload directory and moving the472 This includes moving the given upload directory and moving the
463 matching .distro file, if it exists.473 matching .distro file, if it exists.
464 """474 """
465 if self.options.keep or self.options.dryrun:475 if self.keep or self.dry_run:
466 self.log.debug("Keeping contents untouched")476 self.log.debug("Keeping contents untouched")
467 return477 return
468478
469 pathname = os.path.basename(upload)479 pathname = os.path.basename(upload)
470480
471 target_path = os.path.join(481 target_path = os.path.join(
472 self.options.base_fsroot, subdir_name, pathname)482 self.base_fsroot, subdir_name, pathname)
473 self.log.debug("Moving upload directory %s to %s" %483 self.log.debug("Moving upload directory %s to %s" %
474 (upload, target_path))484 (upload, target_path))
475 shutil.move(upload, target_path)485 shutil.move(upload, target_path)
476486
477 distro_filename = upload + ".distro"487 distro_filename = upload + ".distro"
478 if os.path.isfile(distro_filename):488 if os.path.isfile(distro_filename):
479 target_path = os.path.join(self.options.base_fsroot, subdir_name,489 target_path = os.path.join(self.base_fsroot, subdir_name,
480 os.path.basename(distro_filename))490 os.path.basename(distro_filename))
481 self.log.debug("Moving distro file %s to %s" % (distro_filename,491 self.log.debug("Moving distro file %s to %s" % (distro_filename,
482 target_path))492 target_path))
483493
=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py 2010-08-04 04:07:21 +0000
+++ lib/lp/registry/browser/product.py 2010-08-12 22:16:35 +0000
@@ -83,6 +83,7 @@
83from lp.registry.interfaces.pillar import IPillarNameSet83from lp.registry.interfaces.pillar import IPillarNameSet
84from lp.registry.interfaces.product import IProductReviewSearch, License84from lp.registry.interfaces.product import IProductReviewSearch, License
85from lp.registry.interfaces.series import SeriesStatus85from lp.registry.interfaces.series import SeriesStatus
86from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
86from lp.registry.interfaces.product import (87from lp.registry.interfaces.product import (
87 IProduct, IProductSet, LicenseStatus)88 IProduct, IProductSet, LicenseStatus)
88from lp.registry.interfaces.productrelease import (89from lp.registry.interfaces.productrelease import (
@@ -120,7 +121,7 @@
120from canonical.launchpad.webapp.breadcrumb import Breadcrumb121from canonical.launchpad.webapp.breadcrumb import Breadcrumb
121from canonical.launchpad.webapp.launchpadform import (122from canonical.launchpad.webapp.launchpadform import (
122 action, custom_widget, LaunchpadEditFormView, LaunchpadFormView,123 action, custom_widget, LaunchpadEditFormView, LaunchpadFormView,
123 ReturnToReferrerMixin)124 ReturnToReferrerMixin, safe_action)
124from canonical.launchpad.webapp.menu import NavigationMenu125from canonical.launchpad.webapp.menu import NavigationMenu
125from canonical.launchpad.webapp.tales import MenuAPI126from canonical.launchpad.webapp.tales import MenuAPI
126from canonical.widgets.popup import PersonPickerWidget127from canonical.widgets.popup import PersonPickerWidget
@@ -1833,6 +1834,17 @@
1833 return canonical_url(self.product)1834 return canonical_url(self.product)
18341835
18351836
1837def create_source_package_fields():
1838 return form.Fields(
1839 Choice(__name__='source_package_name',
1840 vocabulary='SourcePackageName',
1841 required=False),
1842 Choice(__name__='distroseries',
1843 vocabulary='DistroSeries',
1844 required=False),
1845 )
1846
1847
1836class ProjectAddStepOne(StepView):1848class ProjectAddStepOne(StepView):
1837 """product/+new view class for creating a new project."""1849 """product/+new view class for creating a new project."""
18381850
@@ -1849,6 +1861,29 @@
1849 step_description = 'Project basics'1861 step_description = 'Project basics'
1850 search_results_count = 01862 search_results_count = 0
18511863
1864 def setUpFields(self):
1865 """See `LaunchpadFormView`."""
1866 super(ProjectAddStepOne, self).setUpFields()
1867 self.form_fields = (
1868 self.form_fields +
1869 create_source_package_fields())
1870
1871 def setUpWidgets(self):
1872 """See `LaunchpadFormView`."""
1873 super(ProjectAddStepOne, self).setUpWidgets()
1874 self.widgets['source_package_name'].visible = False
1875 self.widgets['distroseries'].visible = False
1876
1877 @property
1878 def _return_url(self):
1879 """This view is using the hidden _return_url field.
1880
1881 It is not using the `ReturnToReferrerMixin`, since none
1882 of its other code is used, because multistep views can't
1883 have next_url set until the form submission succeeds.
1884 """
1885 return self.request.form.get('_return_url')
1886
1852 @property1887 @property
1853 def _next_step(self):1888 def _next_step(self):
1854 """Define the next step.1889 """Define the next step.
@@ -1862,9 +1897,10 @@
1862 def main_action(self, data):1897 def main_action(self, data):
1863 """See `MultiStepView`."""1898 """See `MultiStepView`."""
1864 self.next_step = self._next_step1899 self.next_step = self._next_step
1865 self.request.form['displayname'] = data['displayname']1900
1866 self.request.form['name'] = data['name'].lower()1901 # Make this a safe_action, so that the sourcepackage page can skip
1867 self.request.form['summary'] = data['summary']1902 # the first step with a link (GET request) providing form values.
1903 continue_action = safe_action(StepView.continue_action)
18681904
18691905
1870class ProjectAddStepTwo(StepView, ProductLicenseMixin, ReturnToReferrerMixin):1906class ProjectAddStepTwo(StepView, ProductLicenseMixin, ReturnToReferrerMixin):
@@ -1873,7 +1909,6 @@
1873 _field_names = ['displayname', 'name', 'title', 'summary',1909 _field_names = ['displayname', 'name', 'title', 'summary',
1874 'description', 'licenses', 'license_info',1910 'description', 'licenses', 'license_info',
1875 ]1911 ]
1876 main_action_label = u'Complete Registration'
1877 schema = IProduct1912 schema = IProduct
1878 step_name = 'projectaddstep2'1913 step_name = 'projectaddstep2'
1879 template = ViewPageTemplateFile('../templates/product-new.pt')1914 template = ViewPageTemplateFile('../templates/product-new.pt')
@@ -1887,6 +1922,25 @@
1887 custom_widget('license_info', GhostWidget)1922 custom_widget('license_info', GhostWidget)
18881923
1889 @property1924 @property
1925 def main_action_label(self):
1926 if self.source_package_name is None:
1927 return u'Complete Registration'
1928 else:
1929 return u'Complete registration and link to %s package' % (
1930 self.source_package_name.name,
1931 )
1932
1933 @property
1934 def _return_url(self):
1935 """This view is using the hidden _return_url field.
1936
1937 It is not using the `ReturnToReferrerMixin`, since none
1938 of its other code is used, because multistep views can't
1939 have next_url set until the form submission succeeds.
1940 """
1941 return self.request.form.get('_return_url')
1942
1943 @property
1890 def step_description(self):1944 def step_description(self):
1891 """See `MultiStepView`."""1945 """See `MultiStepView`."""
1892 if self.search_results_count > 0:1946 if self.search_results_count > 0:
@@ -1897,7 +1951,8 @@
1897 """See `LaunchpadFormView`."""1951 """See `LaunchpadFormView`."""
1898 super(ProjectAddStepTwo, self).setUpFields()1952 super(ProjectAddStepTwo, self).setUpFields()
1899 self.form_fields = (self.form_fields +1953 self.form_fields = (self.form_fields +
1900 self._createDisclaimMaintainerField())1954 self._createDisclaimMaintainerField() +
1955 create_source_package_fields())
19011956
1902 def _createDisclaimMaintainerField(self):1957 def _createDisclaimMaintainerField(self):
1903 """Return a Bool field for disclaiming maintainer.1958 """Return a Bool field for disclaiming maintainer.
@@ -1930,12 +1985,39 @@
1930 "this will be the project's URL.")1985 "this will be the project's URL.")
1931 self.widgets['displayname'].visible = False1986 self.widgets['displayname'].visible = False
19321987
1988 self.widgets['source_package_name'].visible = False
1989 self.widgets['distroseries'].visible = False
1990
1991 # Set the source_package_release attribute on the licenses
1992 # widget, so that the source package's copyright info can be
1993 # displayed.
1994 ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
1995 if self.source_package_name is not None:
1996 release_list = ubuntu.getCurrentSourceReleases(
1997 [self.source_package_name])
1998 if len(release_list) != 0:
1999 self.widgets['licenses'].source_package_release = (
2000 release_list.items()[0][1])
2001
2002 @property
2003 def source_package_name(self):
2004 # setUpWidgets() doesn't have access to the data dictionary,
2005 # so the source package name needs to be converted from a string
2006 # into an object here.
2007 package_name_string = self.request.form.get(
2008 'field.source_package_name')
2009 if package_name_string is None:
2010 return None
2011 else:
2012 return getUtility(ISourcePackageNameSet).queryByName(
2013 package_name_string)
2014
1933 @cachedproperty2015 @cachedproperty
1934 def _search_string(self):2016 def _search_string(self):
1935 """Return the ORed terms to match."""2017 """Return the ORed terms to match."""
1936 search_text = SPACE.join((self.request.form['name'],2018 search_text = SPACE.join((self.request.form['field.name'],
1937 self.request.form['displayname'],2019 self.request.form['field.displayname'],
1938 self.request.form['summary']))2020 self.request.form['field.summary']))
1939 # OR all the terms together.2021 # OR all the terms together.
1940 return OR.join(search_text.split())2022 return OR.join(search_text.split())
19412023
@@ -1972,7 +2054,8 @@
1972 def label(self):2054 def label(self):
1973 """See `LaunchpadFormView`."""2055 """See `LaunchpadFormView`."""
1974 return 'Register %s (%s) in Launchpad' % (2056 return 'Register %s (%s) in Launchpad' % (
1975 self.request.form['displayname'], self.request.form['name'])2057 self.request.form['field.displayname'],
2058 self.request.form['field.name'])
19762059
1977 def create_product(self, data):2060 def create_product(self, data):
1978 """Create the product from the user data."""2061 """Create the product from the user data."""
@@ -1996,12 +2079,28 @@
1996 license_info=data['license_info'],2079 license_info=data['license_info'],
1997 project=project)2080 project=project)
19982081
2082 def link_source_package(self, product, data):
2083 if (data.get('distroseries') is not None
2084 and self.source_package_name is not None):
2085 source_package = data['distroseries'].getSourcePackage(
2086 self.source_package_name)
2087 source_package.setPackaging(
2088 product.development_focus, self.user)
2089 self.request.response.addInfoNotification(
2090 'Linked %s project to %s source package.' % (
2091 product.displayname, self.source_package_name.name))
2092
1999 def main_action(self, data):2093 def main_action(self, data):
2000 """See `MultiStepView`."""2094 """See `MultiStepView`."""
2001 self.product = self.create_product(data)2095 self.product = self.create_product(data)
2002 self.notifyCommercialMailingList()2096 self.notifyCommercialMailingList()
2003 notify(ObjectCreatedEvent(self.product))2097 notify(ObjectCreatedEvent(self.product))
2004 self.next_url = canonical_url(self.product)2098 self.link_source_package(self.product, data)
2099
2100 if self._return_url is None:
2101 self.next_url = canonical_url(self.product)
2102 else:
2103 self.next_url = self._return_url
20052104
20062105
2007class ProductAddView(MultiStepView):2106class ProductAddView(MultiStepView):
@@ -2027,7 +2126,7 @@
20272126
2028 driver = copy_field(IProduct['driver'])2127 driver = copy_field(IProduct['driver'])
20292128
2030 transfer_to_registry = Bool(2129 transfer_to_registry = Bool(
2031 title=_("I do not want to maintain this project"),2130 title=_("I do not want to maintain this project"),
2032 required=False,2131 required=False,
2033 description=_(2132 description=_(
20342133
=== modified file 'lib/lp/registry/browser/sourcepackage.py'
--- lib/lp/registry/browser/sourcepackage.py 2010-07-02 14:34:58 +0000
+++ lib/lp/registry/browser/sourcepackage.py 2010-08-12 22:16:35 +0000
@@ -19,6 +19,8 @@
1919
20from apt_pkg import ParseSrcDepends20from apt_pkg import ParseSrcDepends
21from cgi import escape21from cgi import escape
22import string
23import urllib
22from z3c.ptcompat import ViewPageTemplateFile24from z3c.ptcompat import ViewPageTemplateFile
23from zope.app.form.browser import DropdownWidget25from zope.app.form.browser import DropdownWidget
24from zope.app.form.interfaces import IInputWidget26from zope.app.form.interfaces import IInputWidget
@@ -35,12 +37,14 @@
3537
36from canonical.launchpad import helpers38from canonical.launchpad import helpers
37from canonical.launchpad.browser.multistep import MultiStepView, StepView39from canonical.launchpad.browser.multistep import MultiStepView, StepView
40
38from lp.bugs.browser.bugtask import BugTargetTraversalMixin41from lp.bugs.browser.bugtask import BugTargetTraversalMixin
39from canonical.launchpad.browser.packagerelationship import (42from canonical.launchpad.browser.packagerelationship import (
40 relationship_builder)43 relationship_builder)
41from lp.answers.browser.questiontarget import (44from lp.answers.browser.questiontarget import (
42 QuestionTargetFacetMixin, QuestionTargetAnswersMenu)45 QuestionTargetFacetMixin, QuestionTargetAnswersMenu)
43from lp.services.worlddata.interfaces.country import ICountry46from lp.services.worlddata.interfaces.country import ICountry
47from lp.registry.browser.product import ProjectAddStepOne
44from lp.registry.interfaces.packaging import IPackaging, IPackagingUtil48from lp.registry.interfaces.packaging import IPackaging, IPackagingUtil
45from lp.registry.interfaces.pocket import PackagePublishingPocket49from lp.registry.interfaces.pocket import PackagePublishingPocket
46from lp.registry.interfaces.product import IProductSet50from lp.registry.interfaces.product import IProductSet
@@ -62,6 +66,35 @@
62from canonical.lazr.utils import smartquote66from canonical.lazr.utils import smartquote
6367
6468
69def get_register_upstream_url(source_package):
70 displayname = string.capwords(source_package.name.replace('-', ' '))
71 distroseries_string = "%s/%s" % (
72 source_package.distroseries.distribution.name,
73 source_package.distroseries.name)
74 params = {
75 '_return_url': canonical_url(source_package),
76 'field.source_package_name': source_package.sourcepackagename.name,
77 'field.distroseries': distroseries_string,
78 'field.name': source_package.name,
79 'field.displayname': displayname,
80 'field.title': displayname,
81 'field.__visited_steps__': ProjectAddStepOne.step_name,
82 'field.actions.continue': 'Continue',
83 }
84 if len(source_package.releases) == 0:
85 params['field.summary'] = ''
86 else:
87 # This is based on the SourcePackageName.summary attribute, but
88 # it eliminates the binary.name and duplicate summary lines.
89 summary_set = set()
90 for binary in source_package.releases[0].sample_binary_packages:
91 summary_set.add(binary.summary)
92 params['field.summary'] = '\n'.join(sorted(summary_set))
93 query_string = urllib.urlencode(
94 sorted(params.items()), doseq=True)
95 return '/projects/+new?%s' % query_string
96
97
65class SourcePackageNavigation(GetitemNavigation, BugTargetTraversalMixin):98class SourcePackageNavigation(GetitemNavigation, BugTargetTraversalMixin):
6699
67 usedfor = ISourcePackage100 usedfor = ISourcePackage
@@ -190,6 +223,10 @@
190 self.next_step = SourcePackageChangeUpstreamStepTwo223 self.next_step = SourcePackageChangeUpstreamStepTwo
191 self.request.form['product'] = data['product']224 self.request.form['product'] = data['product']
192225
226 @property
227 def register_upstream_url(self):
228 return get_register_upstream_url(self.context)
229
193230
194class SourcePackageChangeUpstreamStepTwo(ReturnToReferrerMixin, StepView):231class SourcePackageChangeUpstreamStepTwo(ReturnToReferrerMixin, StepView):
195 """A view to set the `IProductSeries` of a sourcepackage."""232 """A view to set the `IProductSeries` of a sourcepackage."""
@@ -345,7 +382,7 @@
345 def processForm(self):382 def processForm(self):
346 # look for an update to any of the things we track383 # look for an update to any of the things we track
347 form = self.request.form384 form = self.request.form
348 if form.has_key('packaging'):385 if 'packaging' in form:
349 if self.productseries_widget.hasValidInput():386 if self.productseries_widget.hasValidInput():
350 new_ps = self.productseries_widget.getInputValue()387 new_ps = self.productseries_widget.getInputValue()
351 # we need to create or update the packaging388 # we need to create or update the packaging
@@ -445,6 +482,7 @@
445 initial_focus_widget = None482 initial_focus_widget = None
446 max_suggestions = 9483 max_suggestions = 9
447 other_upstream = object()484 other_upstream = object()
485 register_upstream = object()
448486
449 def setUpFields(self):487 def setUpFields(self):
450 """See `LaunchpadFormView`."""488 """See `LaunchpadFormView`."""
@@ -467,9 +505,12 @@
467 vocab_terms.append(SimpleTerm(product, product.name, description))505 vocab_terms.append(SimpleTerm(product, product.name, description))
468 # Add an option to represent the user's decision to choose a506 # Add an option to represent the user's decision to choose a
469 # different project. Note that project names cannot be uppercase.507 # different project. Note that project names cannot be uppercase.
470 description = 'Choose another upstream project'508 vocab_terms.append(
471 vocab_terms.append(509 SimpleTerm(self.other_upstream, 'OTHER_UPSTREAM',
472 SimpleTerm(self.other_upstream, 'OTHER_UPSTREAM', description))510 'Choose another upstream project'))
511 vocab_terms.append(
512 SimpleTerm(self.register_upstream, 'REGISTER_UPSTREAM',
513 'Register the upstream project'))
473 upstream_vocabulary = SimpleVocabulary(vocab_terms)514 upstream_vocabulary = SimpleVocabulary(vocab_terms)
474515
475 self.form_fields = Fields(516 self.form_fields = Fields(
@@ -487,6 +528,11 @@
487 self.next_url = canonical_url(528 self.next_url = canonical_url(
488 self.context, view_name="+edit-packaging")529 self.context, view_name="+edit-packaging")
489 return530 return
531 elif upstream is self.register_upstream:
532 # The user wants to create a new project.
533 url = get_register_upstream_url(self.context)
534 self.request.response.redirect(url)
535 return
490 self.context.setPackaging(upstream.development_focus, self.user)536 self.context.setPackaging(upstream.development_focus, self.user)
491 self.request.response.addInfoNotification(537 self.request.response.addInfoNotification(
492 'The project %s was linked to this source package.' %538 'The project %s was linked to this source package.' %
493539
=== modified file 'lib/lp/registry/browser/tests/project-add-views.txt'
--- lib/lp/registry/browser/tests/project-add-views.txt 2010-05-25 04:41:12 +0000
+++ lib/lp/registry/browser/tests/project-add-views.txt 2010-08-12 22:16:35 +0000
@@ -15,25 +15,24 @@
15are forwarded in the form data to the second step. The title is also15are forwarded in the form data to the second step. The title is also
16forwarded, but is only required by the Zope machinery, not the view.16forwarded, but is only required by the Zope machinery, not the view.
1717
18 >>> form = {'field.actions.continue': 'Continue'}18 >>> from lp.registry.browser.product import ProjectAddStepOne
19 >>> form = {
20 ... 'field.actions.continue': 'Continue',
21 ... 'field.__visited_steps__': ProjectAddStepOne.step_name,
22 ... 'field.displayname': '',
23 ... 'field.name': '',
24 ... 'field.summary': '',
25 ... }
1926
20 >>> view = create_initialized_view(product_set, name='+new', form=form)27 >>> view = create_initialized_view(product_set, name='+new', form=form)
21 Traceback (most recent call last):28 >>> for error in view.view.errors:
22 ...29 ... print error
23 KeyError: 'displayname'30 ('displayname', 'Name', RequiredMissing())
31 ('name', 'URL', RequiredMissing())
32 ('summary', u'Summary', RequiredMissing())
2433
25 >>> form['field.displayname'] = 'Snowdog'34 >>> form['field.displayname'] = 'Snowdog'
26 >>> view = create_initialized_view(product_set, name='+new', form=form)
27 Traceback (most recent call last):
28 ...
29 KeyError: 'name'
30
31 >>> form['field.name'] = 'snowdog'35 >>> form['field.name'] = 'snowdog'
32 >>> view = create_initialized_view(product_set, name='+new', form=form)
33 Traceback (most recent call last):
34 ...
35 KeyError: 'summary'
36
37 >>> form['field.summary'] = 'By-tor and the Snowdog'36 >>> form['field.summary'] = 'By-tor and the Snowdog'
38 >>> view = create_initialized_view(product_set, name='+new', form=form)37 >>> view = create_initialized_view(product_set, name='+new', form=form)
3938
@@ -44,7 +43,6 @@
44 # steps individually.43 # steps individually.
4544
46 >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest45 >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
47 >>> from lp.registry.browser.product import ProjectAddStepOne
4846
49 >>> form['field.__visited_steps__'] = ProjectAddStepOne.step_name47 >>> form['field.__visited_steps__'] = ProjectAddStepOne.step_name
50 >>> request = LaunchpadTestRequest(form=form, method='POST')48 >>> request = LaunchpadTestRequest(form=form, method='POST')
@@ -63,10 +61,12 @@
6361
64 >>> from lp.registry.browser.product import ProjectAddStepTwo62 >>> from lp.registry.browser.product import ProjectAddStepTwo
65 >>> form = {63 >>> form = {
66 ... 'displayname': 'Snowdog',64 ... 'field.actions.continue': 'Continue',
67 ... 'name': 'snowdog',65 ... 'field.__visited_steps__': ProjectAddStepTwo.step_name,
68 ... 'title': 'The Snowdog',66 ... 'field.displayname': 'Snowdog',
69 ... 'summary': 'By-tor and the Snowdog',67 ... 'field.name': 'snowdog',
68 ... 'field.title': 'The Snowdog',
69 ... 'field.summary': 'By-tor and the Snowdog',
70 ... }70 ... }
7171
72 >>> request = LaunchpadTestRequest(form=form, method='POST')72 >>> request = LaunchpadTestRequest(form=form, method='POST')
@@ -90,7 +90,7 @@
90existing projects for possible matches. By tweaking the project summary, we90existing projects for possible matches. By tweaking the project summary, we
91can see that there are search results available.91can see that there are search results available.
9292
93 >>> form['summary'] = 'My Snowdog ate your Firefox'93 >>> form['field.summary'] = 'My Snowdog ate your Firefox'
9494
95 >>> request = LaunchpadTestRequest(form=form, method='POST')95 >>> request = LaunchpadTestRequest(form=form, method='POST')
96 >>> view = ProjectAddStepTwo(product_set, request)96 >>> view = ProjectAddStepTwo(product_set, request)
@@ -229,9 +229,9 @@
229 questions.229 questions.
230 <BLANKLINE>230 <BLANKLINE>
231 Sometimes new projects are licensed as 'Other/Open Source' because the231 Sometimes new projects are licensed as 'Other/Open Source' because the
232 licensing decisions have not yet been made. If that is your situation we u=232 licensing decisions have not yet been made. If that is your situation
233 rge233 we urge you to update the licensing in Launchpad as soon as you make
234 you to update the licensing in Launchpad as soon as you make that choice.234 that choice.
235 <BLANKLINE>235 <BLANKLINE>
236 If the license for your project needs to be corrected you can do so by236 If the license for your project needs to be corrected you can do so by
237 following the 'Change Details' link on your project's overview page.237 following the 'Change Details' link on your project's overview page.
238238
=== modified file 'lib/lp/registry/browser/tests/sourcepackage-views.txt'
--- lib/lp/registry/browser/tests/sourcepackage-views.txt 2010-05-13 18:55:10 +0000
+++ lib/lp/registry/browser/tests/sourcepackage-views.txt 2010-08-12 22:16:35 +0000
@@ -119,6 +119,7 @@
119empty.119empty.
120120
121 >>> form = {121 >>> form = {
122 ... 'field.__visited_steps__': 'sourcepackage_change_upstream_step1',
122 ... 'field.product': '',123 ... 'field.product': '',
123 ... 'field.actions.continue': 'Continue',124 ... 'field.actions.continue': 'Continue',
124 ... }125 ... }
@@ -133,12 +134,18 @@
133but there is no notification message that the upstream link was updated.134but there is no notification message that the upstream link was updated.
134135
135 >>> form = {136 >>> form = {
136 ... 'field.productseries': 'bonkers/crazy',137 ... 'field.__visited_steps__': 'sourcepackage_change_upstream_step2',
137 ... 'field.actions.change': 'Change',138 ... 'field.product': 'bonkers',
139 ... 'field.productseries': 'crazy',
140 ... 'field.actions.continue': 'Continue',
138 ... }141 ... }
139 >>> view = create_initialized_view(142 >>> view = create_initialized_view(
140 ... package, name='+edit-packaging', form=form,143 ... package, name='+edit-packaging', form=form,
141 ... principal=product.owner)144 ... principal=product.owner)
145 >>> print view.view
146 <...SourcePackageChangeUpstreamStepTwo object...>
147 >>> print view.view.next_url
148 http://launchpad.dev/youbuntu/busy/+source/bonkers
142 >>> view.view.errors149 >>> view.view.errors
143 []150 []
144151
@@ -199,6 +206,7 @@
199 Registered upstream project:206 Registered upstream project:
200 Lernid207 Lernid
201 Choose another upstream project208 Choose another upstream project
209 Register the upstream project
202210
203The form does not steal focus because it is not the primary purpose of the211The form does not steal focus because it is not the primary purpose of the
204page.212page.
@@ -229,6 +237,7 @@
229 Lernid...237 Lernid...
230 Lernid Dev...238 Lernid Dev...
231 Choose another upstream project239 Choose another upstream project
240 Register the upstream project
232241
233Choosing the "Choose another upstream project" option redirects the user242Choosing the "Choose another upstream project" option redirects the user
234to the +edit-packaging page where the user can search for a project.243to the +edit-packaging page where the user can search for a project.
@@ -259,7 +268,8 @@
259 ... name='stinkyseries', product=product)268 ... name='stinkyseries', product=product)
260 >>> distroseries = factory.makeDistroRelease(name='wonky',269 >>> distroseries = factory.makeDistroRelease(name='wonky',
261 ... distribution=distribution)270 ... distribution=distribution)
262 >>> sourcepackagename = factory.makeSourcePackageName(name='stinkypackage')271 >>> sourcepackagename = factory.makeSourcePackageName(
272 ... name='stinkypackage')
263 >>> package = factory.makeSourcePackage(273 >>> package = factory.makeSourcePackage(
264 ... sourcepackagename=sourcepackagename, distroseries=distroseries)274 ... sourcepackagename=sourcepackagename, distroseries=distroseries)
265275
@@ -360,3 +370,4 @@
360 match for this source package. Can you help us find one?370 match for this source package. Can you help us find one?
361 Registered upstream project:371 Registered upstream project:
362 Choose another upstream project372 Choose another upstream project
373 Register the upstream project
363374
=== added file 'lib/lp/registry/browser/tests/test_sourcepackage_views.py'
--- lib/lp/registry/browser/tests/test_sourcepackage_views.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/browser/tests/test_sourcepackage_views.py 2010-08-12 22:16:35 +0000
@@ -0,0 +1,146 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for SourcePackage view code."""
5
6__metaclass__ = type
7
8import cgi
9import urllib
10
11from zope.component import getUtility
12from zope.interface import implements
13
14from canonical.testing import DatabaseFunctionalLayer
15
16
17from lp.registry.browser.sourcepackage import get_register_upstream_url
18from lp.registry.interfaces.distribution import IDistribution
19from lp.registry.interfaces.distroseries import (
20 IDistroSeries, IDistroSeriesSet)
21from lp.registry.interfaces.sourcepackage import ISourcePackage
22from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
23from lp.testing import TestCaseWithFactory
24
25
26class TestSourcePackageViewHelpers(TestCaseWithFactory):
27 """Tests for SourcePackage view helper functions."""
28
29 layer = DatabaseFunctionalLayer
30
31 def test_get_register_upstream_url_displayname(self):
32 distroseries = self.factory.makeDistroRelease(
33 distribution=self.factory.makeDistribution(name='zoobuntu'),
34 name='walrus')
35 source_package = self.factory.makeSourcePackage(
36 distroseries=distroseries,
37 sourcepackagename='python-super-package')
38 url = get_register_upstream_url(source_package)
39 expected_base = '/projects/+new'
40 expected_params = [
41 ('_return_url',
42 'http://launchpad.dev/zoobuntu/walrus/'
43 '+source/python-super-package'),
44 ('field.__visited_steps__', 'projectaddstep1'),
45 ('field.actions.continue', 'Continue'),
46 # The sourcepackagename 'python-super-package' is split on
47 # the hyphens, and each word is capitalized.
48 ('field.displayname', 'Python Super Package'),
49 ('field.distroseries', 'zoobuntu/walrus'),
50 ('field.name', 'python-super-package'),
51 # The summary is missing, since the source package doesn't
52 # have a binary package release, and parse_qsl() excludes
53 # empty params.
54 ('field.source_package_name', 'python-super-package'),
55 ('field.title', 'Python Super Package'),
56 ]
57 base, query = urllib.splitquery(url)
58 params = cgi.parse_qsl(query)
59 self.assertEqual((expected_base, expected_params),
60 (base, params))
61
62 def test_get_register_upstream_url_summary(self):
63 test_publisher = SoyuzTestPublisher()
64 test_data = test_publisher.makeSourcePackageWithBinaryPackageRelease()
65 source_package_name = (
66 test_data['source_package'].sourcepackagename.name)
67 distroseries_id = test_data['distroseries'].id
68 test_publisher.updateDistroSeriesPackageCache(
69 test_data['distroseries'])
70
71 # updateDistroSeriesPackageCache reconnects the db, so the
72 # objects need to be reloaded.
73 distroseries = getUtility(IDistroSeriesSet).get(distroseries_id)
74 source_package = distroseries.getSourcePackage(source_package_name)
75 url = get_register_upstream_url(source_package)
76 expected_base = '/projects/+new'
77 expected_params = [
78 ('_return_url',
79 'http://launchpad.dev/youbuntu/busy/+source/bonkers'),
80 ('field.__visited_steps__', 'projectaddstep1'),
81 ('field.actions.continue', 'Continue'),
82 ('field.displayname', 'Bonkers'),
83 ('field.distroseries', 'youbuntu/busy'),
84 ('field.name', 'bonkers'),
85 ('field.source_package_name', 'bonkers'),
86 ('field.summary', 'summary for flubber-bin\n'
87 + 'summary for flubber-lib'),
88 ('field.title', 'Bonkers'),
89 ]
90 base, query = urllib.splitquery(url)
91 params = cgi.parse_qsl(query)
92 self.assertEqual((expected_base, expected_params),
93 (base, params))
94
95 def test_get_register_upstream_url_summary_duplicates(self):
96
97 class Faker:
98 # Fakes attributes easily.
99 def __init__(self, **kw):
100 self.__dict__.update(kw)
101
102 class FakeSourcePackage(Faker):
103 # Interface necessary for canonical_url() call in
104 # get_register_upstream_url().
105 implements(ISourcePackage)
106
107 class FakeDistroSeries(Faker):
108 implements(IDistroSeries)
109
110 class FakeDistribution(Faker):
111 implements(IDistribution)
112
113 releases = Faker(sample_binary_packages=[
114 Faker(summary='summary for foo'),
115 Faker(summary='summary for bar'),
116 Faker(summary='summary for baz'),
117 Faker(summary='summary for baz'),
118 ])
119 source_package = FakeSourcePackage(
120 name='foo',
121 sourcepackagename=Faker(name='foo'),
122 distroseries=FakeDistroSeries(
123 name='walrus',
124 distribution=FakeDistribution(name='zoobuntu')),
125 releases=[releases])
126
127 url = get_register_upstream_url(source_package)
128 expected_base = '/projects/+new'
129 expected_params = [
130 ('_return_url',
131 'http://launchpad.dev/zoobuntu/walrus/+source/foo'),
132 ('field.__visited_steps__', 'projectaddstep1'),
133 ('field.actions.continue', 'Continue'),
134 ('field.displayname', 'Foo'),
135 ('field.distroseries', 'zoobuntu/walrus'),
136 ('field.name', 'foo'),
137 ('field.source_package_name', 'foo'),
138 ('field.summary', 'summary for bar\n'
139 + 'summary for baz\n'
140 + 'summary for foo'),
141 ('field.title', 'Foo'),
142 ]
143 base, query = urllib.splitquery(url)
144 params = cgi.parse_qsl(query)
145 self.assertEqual((expected_base, expected_params),
146 (base, params))
0147
=== modified file 'lib/lp/registry/model/sourcepackage.py'
--- lib/lp/registry/model/sourcepackage.py 2010-08-02 21:38:00 +0000
+++ lib/lp/registry/model/sourcepackage.py 2010-08-12 22:16:35 +0000
@@ -39,7 +39,6 @@
39from lp.registry.model.packaging import Packaging39from lp.registry.model.packaging import Packaging
40from lp.translations.model.potemplate import (40from lp.translations.model.potemplate import (
41 HasTranslationTemplatesMixin,41 HasTranslationTemplatesMixin,
42 POTemplate,
43 TranslationTemplatesCollection)42 TranslationTemplatesCollection)
44from canonical.launchpad.interfaces.lpstorm import IStore43from canonical.launchpad.interfaces.lpstorm import IStore
45from lp.soyuz.model.publishing import (44from lp.soyuz.model.publishing import (
@@ -52,7 +51,6 @@
52 SourcePackageRelease)51 SourcePackageRelease)
53from lp.translations.model.translationimportqueue import (52from lp.translations.model.translationimportqueue import (
54 HasTranslationImportsMixin)53 HasTranslationImportsMixin)
55from canonical.launchpad.helpers import shortlist
56from lp.soyuz.interfaces.buildrecords import IHasBuildRecords54from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
57from lp.registry.interfaces.packaging import PackagingType55from lp.registry.interfaces.packaging import PackagingType
58from lp.registry.interfaces.distribution import NoPartnerArchive56from lp.registry.interfaces.distribution import NoPartnerArchive
5957
=== modified file 'lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt'
--- lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt 2010-05-18 17:05:29 +0000
+++ lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt 2010-08-12 22:16:35 +0000
@@ -1,4 +1,15 @@
1= Packaging =1Packaging
2=========
3
4Create test data.
5
6 >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
7 >>> test_publisher = SoyuzTestPublisher()
8 >>> login('admin@canonical.com')
9 >>> test_data = test_publisher.makeSourcePackageWithBinaryPackageRelease()
10 >>> test_publisher.updateDistroSeriesPackageCache(
11 ... test_data['distroseries'])
12 >>> logout()
213
3No Privileges Person visit the distroseries upstream links page for Hoary14No Privileges Person visit the distroseries upstream links page for Hoary
4and sees that pmount is not linked.15and sees that pmount is not linked.
@@ -20,6 +31,7 @@
20 match for this source package. Can you help us find one?31 match for this source package. Can you help us find one?
21 Registered upstream project:32 Registered upstream project:
22 Choose another upstream project33 Choose another upstream project
34 Register the upstream project
2335
24No Privileges Person knows that the pmount package comes from the thunderbird36No Privileges Person knows that the pmount package comes from the thunderbird
25project. He sets the upstream packaging link and sees that it is set.37project. He sets the upstream packaging link and sees that it is set.
@@ -58,3 +70,93 @@
58 ... user_browser.contents, 'packages_list'))70 ... user_browser.contents, 'packages_list'))
59 The Hoary Hedgehog Release (active development) ...71 The Hoary Hedgehog Release (active development) ...
60 0.1-2 release (main) ... weeks ago72 0.1-2 release (main) ... weeks ago
73
74Register a project from a source package
75----------------------------------------
76
77If an upstream project doesn't already exist in Launchpad, it can
78be registered with data from the source package prefilling the first
79step of the multistep form.
80
81 >>> user_browser.open(
82 ... 'http://launchpad.dev/youbuntu/busy/+source/bonkers')
83 >>> user_browser.getControl(
84 ... 'Register the upstream project').selected = True
85 >>> user_browser.getControl("Link to Upstream Project").click()
86 >>> print user_browser.url.replace('&', '\n&')
87 http://launchpad.dev/projects/+new?_return_url=http...%2Bsource%2Fbonkers
88 &field.__visited_steps__=projectaddstep1
89 &field.actions.continue=Continue
90 &field.displayname=Bonkers
91 &field.distroseries=youbuntu%2Fbusy
92 &field.name=bonkers
93 &field.source_package_name=bonkers
94 &field.summary=summary+for+flubber-bin%0Asummary+for+flubber-lib
95 &field.title=Bonkers
96 >>> print user_browser.getControl(name='field.name').value
97 bonkers
98 >>> print user_browser.getControl(name='field.displayname').value
99 Bonkers
100 >>> print user_browser.getControl(name='field.title').value
101 Bonkers
102 >>> print user_browser.getControl(name='field.summary').value
103 summary for flubber-bin
104 summary for flubber-lib
105 >>> print extract_text(
106 ... find_tag_by_id(user_browser.contents, 'step-title'))
107 Step 2 (of 2): Check for duplicate projects
108
109If the user selects "Choose another upstream project" and then finds out
110that the project doesn't exist, there is a also a link on the
111+edit-packaging page to register the project.
112
113 >>> user_browser.open(
114 ... 'http://launchpad.dev/youbuntu/busy/+source/bonkers/')
115 >>> user_browser.getControl(
116 ... 'Choose another upstream project').selected = True
117 >>> user_browser.getControl("Link to Upstream Project").click()
118 >>> print user_browser.url
119 http://launchpad.dev/youbuntu/busy/+source/bonkers/+edit-packaging
120
121 >>> user_browser.getLink("Register the upstream project").click()
122 >>> print user_browser.url.replace('&', '\n&')
123 http://launchpad.dev/projects/+new?_return_url=http...%2Bsource%2Fbonkers
124 &field.__visited_steps__=projectaddstep1
125 &field.actions.continue=Continue
126 &field.displayname=Bonkers
127 &field.distroseries=youbuntu%2Fbusy
128 &field.name=bonkers
129 &field.source_package_name=bonkers
130 &field.summary=summary+for+flubber-bin%0Asummary+for+flubber-lib
131 &field.title=Bonkers
132 >>> print user_browser.getControl(name='field.name').value
133 bonkers
134 >>> print user_browser.getControl(name='field.displayname').value
135 Bonkers
136 >>> print user_browser.getControl(name='field.title').value
137 Bonkers
138 >>> print user_browser.getControl(name='field.summary').value
139 summary for flubber-bin
140 summary for flubber-lib
141 >>> print extract_text(
142 ... find_tag_by_id(user_browser.contents, 'step-title'))
143 Step 2 (of 2): Check for duplicate projects
144
145If there are no problems with the prefilled data, then the license
146just needs to be selected. The user will then be redirected back
147to the source package page and an informational message will be displayed.
148
149 >>> user_browser.getControl(name='field.licenses').value = ['BSD']
150 >>> user_browser.getControl(
151 ... "Complete registration and link to bonkers package").click()
152 >>> print user_browser.url
153 http://launchpad.dev/youbuntu/busy/+source/bonkers
154 >>> for tag in find_tags_by_class(
155 ... user_browser.contents, 'informational message'):
156 ... print extract_text(tag)
157 Linked Bonkers project to bonkers source package.
158 >>> print extract_text(
159 ... find_tag_by_id(user_browser.contents, 'upstreams'))
160 Bonkers &rArr; trunk
161 Change upstream link
162 Remove upstream link...
61163
=== modified file 'lib/lp/registry/templates/product-new.pt'
--- lib/lp/registry/templates/product-new.pt 2010-05-12 19:06:17 +0000
+++ lib/lp/registry/templates/product-new.pt 2010-08-12 22:16:35 +0000
@@ -299,8 +299,8 @@
299 <img src="/@@/info" />299 <img src="/@@/info" />
300 There are similar projects already registered in Launchpad.300 There are similar projects already registered in Launchpad.
301 Is project301 Is project
302 <strong><tal:displayname tal:replace="view/request/displayname" />302 <strong><tal:displayname tal:replace="view/request/field.displayname" />
303 (<tal:name tal:replace="view/request/name" />)</strong>303 (<tal:name tal:replace="view/request/field.name" />)</strong>
304 one of these?304 one of these?
305 </div>305 </div>
306306
@@ -326,8 +326,8 @@
326 tal:condition="view/search_results_count"326 tal:condition="view/search_results_count"
327 >Registration details</h3>327 >Registration details</h3>
328 Select the licenses for project328 Select the licenses for project
329 <strong><tal:displayname tal:replace="view/request/displayname" />329 <strong><tal:displayname tal:replace="view/request/field.displayname" />
330 (<tal:name tal:replace="view/request/name" />)</strong>330 (<tal:name tal:replace="view/request/field.name" />)</strong>
331 and complete the registration. You may also update the project's331 and complete the registration. You may also update the project's
332 title and summary.332 title and summary.
333 </div>333 </div>
334334
=== modified file 'lib/lp/registry/templates/sourcepackage-edit-packaging.pt'
--- lib/lp/registry/templates/sourcepackage-edit-packaging.pt 2010-02-16 17:37:36 +0000
+++ lib/lp/registry/templates/sourcepackage-edit-packaging.pt 2010-08-12 22:16:35 +0000
@@ -27,6 +27,26 @@
27 If you need a new series created, contact the owner of27 If you need a new series created, contact the owner of
28 <a tal:content="structure view/product/fmt:link"/>.28 <a tal:content="structure view/product/fmt:link"/>.
29 </div>29 </div>
30
31 <div metal:fill-slot="buttons">
32 <input tal:repeat="action view/actions"
33 tal:replace="structure action/render"
34 />
35 &nbsp;or&nbsp;
36 <tal:comment condition="nothing">
37 This template is for a multistep view, and only the first
38 step provides the register_upstream_url.
39 </tal:comment>
40 <a id="register-upstream-link"
41 tal:condition="view/register_upstream_url | nothing"
42 tal:attributes="href view/register_upstream_url">
43 Register the upstream project
44 </a>
45 <tal:has-cancel-link condition="view/cancel_url">
46 &nbsp;or&nbsp;
47 <a tal:attributes="href view/cancel_url">Cancel</a>
48 </tal:has-cancel-link>
49 </div>
30 </div>50 </div>
3151
32</div>52</div>
3353
=== modified file 'lib/lp/soyuz/scripts/soyuz_process_upload.py'
--- lib/lp/soyuz/scripts/soyuz_process_upload.py 2010-05-04 15:38:08 +0000
+++ lib/lp/soyuz/scripts/soyuz_process_upload.py 2010-08-12 22:16:35 +0000
@@ -8,6 +8,7 @@
88
9import os9import os
1010
11from lp.archiveuploader.uploadpolicy import findPolicyByOptions
11from lp.archiveuploader.uploadprocessor import UploadProcessor12from lp.archiveuploader.uploadprocessor import UploadProcessor
12from lp.services.scripts.base import (13from lp.services.scripts.base import (
13 LaunchpadCronScript, LaunchpadScriptFailure)14 LaunchpadCronScript, LaunchpadScriptFailure)
@@ -74,8 +75,13 @@
74 "%s is not a directory" % self.options.base_fsroot)75 "%s is not a directory" % self.options.base_fsroot)
7576
76 self.logger.debug("Initialising connection.")77 self.logger.debug("Initialising connection.")
77 UploadProcessor(78 def getPolicy(distro):
78 self.options, self.txn, self.logger).processUploadQueue()79 self.options.distro = distro.name
80 return findPolicyByOptions(self.options)
81 processor = UploadProcessor(self.options.base_fsroot,
82 self.options.dryrun, self.options.nomails, self.options.keep,
83 getPolicy, self.txn, self.logger)
84 processor.processUploadQueue(self.options.leafname)
7985
80 @property86 @property
81 def lockfilename(self):87 def lockfilename(self):
8288
=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
--- lib/lp/soyuz/tests/test_publishing.py 2010-08-07 00:36:52 +0000
+++ lib/lp/soyuz/tests/test_publishing.py 2010-08-12 22:16:35 +0000
@@ -10,6 +10,7 @@
10from StringIO import StringIO10from StringIO import StringIO
11import tempfile11import tempfile
1212
13import transaction
13import pytz14import pytz
14from zope.component import getUtility15from zope.component import getUtility
15from zope.security.proxy import removeSecurityProxy16from zope.security.proxy import removeSecurityProxy
@@ -18,13 +19,16 @@
18from canonical.database.constants import UTC_NOW19from canonical.database.constants import UTC_NOW
19from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet20from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
20from canonical.launchpad.webapp.errorlog import ErrorReportingUtility21from canonical.launchpad.webapp.errorlog import ErrorReportingUtility
22from canonical.testing.layers import reconnect_stores
21from canonical.testing import (23from canonical.testing import (
22 DatabaseFunctionalLayer, LaunchpadZopelessLayer)24 DatabaseFunctionalLayer, LaunchpadZopelessLayer)
25
23from lp.app.errors import NotFoundError26from lp.app.errors import NotFoundError
24from lp.archivepublisher.config import Config27from lp.archivepublisher.config import Config
25from lp.archivepublisher.diskpool import DiskPool28from lp.archivepublisher.diskpool import DiskPool
26from lp.buildmaster.interfaces.buildbase import BuildStatus29from lp.buildmaster.interfaces.buildbase import BuildStatus
27from lp.registry.interfaces.distribution import IDistributionSet30from lp.registry.interfaces.distribution import IDistributionSet
31from lp.registry.interfaces.distroseries import IDistroSeriesSet
28from lp.registry.interfaces.person import IPersonSet32from lp.registry.interfaces.person import IPersonSet
29from lp.registry.interfaces.pocket import PackagePublishingPocket33from lp.registry.interfaces.pocket import PackagePublishingPocket
30from lp.registry.interfaces.sourcepackage import SourcePackageUrgency34from lp.registry.interfaces.sourcepackage import SourcePackageUrgency
@@ -476,6 +480,67 @@
476480
477 return source481 return source
478482
483 def makeSourcePackageWithBinaryPackageRelease(self):
484 """Make test data for SourcePackage.summary.
485
486 The distroseries that is returned from this method needs to be
487 passed into updateDistroseriesPackageCache() so that
488 SourcePackage.summary can be populated.
489 """
490 distribution = self.factory.makeDistribution(
491 name='youbuntu', displayname='Youbuntu')
492 distroseries = self.factory.makeDistroRelease(name='busy',
493 distribution=distribution)
494 source_package_name = self.factory.makeSourcePackageName(
495 name='bonkers')
496 source_package = self.factory.makeSourcePackage(
497 sourcepackagename=source_package_name,
498 distroseries=distroseries)
499 component = self.factory.makeComponent('multiverse')
500 das = self.factory.makeDistroArchSeries(
501 distroseries=distroseries)
502 spph = self.factory.makeSourcePackagePublishingHistory(
503 sourcepackagename=source_package_name,
504 distroseries=distroseries,
505 component=component)
506
507 for name in ('flubber-bin', 'flubber-lib'):
508 binary_package_name = self.factory.makeBinaryPackageName(name)
509 build = self.factory.makeBinaryPackageBuild(
510 source_package_release=spph.sourcepackagerelease,
511 archive=self.factory.makeArchive(),
512 distroarchseries=das)
513 bpr = self.factory.makeBinaryPackageRelease(
514 binarypackagename=binary_package_name,
515 summary='summary for %s' % name,
516 build=build, component=component)
517 bpph = self.factory.makeBinaryPackagePublishingHistory(
518 binarypackagerelease=bpr, distroarchseries=das)
519 return dict(
520 distroseries=distroseries,
521 source_package=source_package)
522
523 def updateDistroSeriesPackageCache(
524 self, distroseries, restore_db_connection='launchpad'):
525 # XXX: EdwinGrubbs 2010-08-04 bug=396419. Currently there is no
526 # test api call to switchDbUser that works for non-zopeless layers.
527 # When bug 396419 is fixed, we can instead use
528 # DatabaseLayer.switchDbUser() instead of reconnect_stores()
529 transaction.commit()
530 reconnect_stores(config.statistician.dbuser)
531 distroseries = getUtility(IDistroSeriesSet).get(distroseries.id)
532
533 class TestLogger:
534 # Silent logger.
535 def debug(self, msg):
536 pass
537 distroseries.updateCompletePackageCache(
538 archive=distroseries.distribution.main_archive,
539 ztm=transaction,
540 log=TestLogger())
541 transaction.commit()
542 reconnect_stores(restore_db_connection)
543
479544
480class TestNativePublishingBase(TestCaseWithFactory, SoyuzTestPublisher):545class TestNativePublishingBase(TestCaseWithFactory, SoyuzTestPublisher):
481 layer = LaunchpadZopelessLayer546 layer = LaunchpadZopelessLayer
482547
=== modified file 'lib/lp/vostok/browser/configure.zcml'
--- lib/lp/vostok/browser/configure.zcml 2010-08-12 22:16:33 +0000
+++ lib/lp/vostok/browser/configure.zcml 2010-08-12 22:16:35 +0000
@@ -5,14 +5,6 @@
5 xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"5 xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
6 i18n_domain="launchpad">6 i18n_domain="launchpad">
77
8 <browser:page
9 for="*"
10 name="main_template"
11 template="../templates/main-template.pt"
12 permission="zope.Public"
13 layer="lp.vostok.publisher.VostokLayer"
14 />
15
16 <browser:defaultView8 <browser:defaultView
17 for="lp.vostok.publisher.IVostokRoot"9 for="lp.vostok.publisher.IVostokRoot"
18 name="+index"10 name="+index"
@@ -32,4 +24,6 @@
32 classes="VostokRootNavigation"24 classes="VostokRootNavigation"
33 />25 />
3426
27 <adapter factory="lp.vostok.browser.root.VostokLayerToMainTemplateAdapter" />
28
35</configure>29</configure>
3630
=== modified file 'lib/lp/vostok/browser/root.py'
--- lib/lp/vostok/browser/root.py 2010-07-30 03:50:17 +0000
+++ lib/lp/vostok/browser/root.py 2010-08-12 22:16:35 +0000
@@ -6,19 +6,37 @@
6__metaclass__ = type6__metaclass__ = type
7__all__ = [7__all__ = [
8 'VostokRootView',8 'VostokRootView',
9 'VostokLayerToMainTemplateAdapter',
9 ]10 ]
1011
11from zope.component import getUtility12import os
13
14from zope.component import adapts, getUtility
15from zope.interface import implements
1216
13from canonical.launchpad.webapp import LaunchpadView17from canonical.launchpad.webapp import LaunchpadView
18from canonical.launchpad.webapp.tales import IMainTemplateFile
1419
15from lp.registry.interfaces.distribution import IDistributionSet20from lp.registry.interfaces.distribution import IDistributionSet
1621
22from lp.vostok.publisher import VostokLayer
23
1724
18class VostokRootView(LaunchpadView):25class VostokRootView(LaunchpadView):
19 """The view for the Vostok root object."""26 """The view for the Vostok root object."""
2027
28 page_title = 'Vostok'
29
21 @property30 @property
22 def distributions(self):31 def distributions(self):
23 """An iterable of all registered distributions."""32 """An iterable of all registered distributions."""
24 return getUtility(IDistributionSet)33 return getUtility(IDistributionSet)
34
35
36class VostokLayerToMainTemplateAdapter:
37 adapts(VostokLayer)
38 implements(IMainTemplateFile)
39
40 def __init__(self, context):
41 here = os.path.dirname(os.path.realpath(__file__))
42 self.path = os.path.join(here, '../templates/main-template.pt')
2543
=== renamed file 'lib/lp/vostok/browser/tests/test_main_template.py' => 'lib/lp/vostok/browser/tests/test_base_template.py'
--- lib/lp/vostok/browser/tests/test_main_template.py 2010-07-29 04:38:37 +0000
+++ lib/lp/vostok/browser/tests/test_base_template.py 2010-08-12 22:16:35 +0000
@@ -1,33 +1,31 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for the vostok 'main_template'."""4"""Tests for the vostok 'view/macro:page' TALES adapter."""
55
6__metaclass__ = type6__metaclass__ = type
77
8import unittest
9
10from zope.component import getMultiAdapter8from zope.component import getMultiAdapter
9from zope.traversing.interfaces import IPathAdapter
1110
12from canonical.testing.layers import FunctionalLayer11from canonical.testing.layers import FunctionalLayer
1312
14from lp.testing import TestCase13from lp.testing import TestCase
15from lp.vostok.browser.tests.request import VostokTestRequest14from lp.vostok.browser.tests.request import VostokTestRequest
1615from lp.vostok.publisher import VostokRoot
1716
18class TestMainTemplate(TestCase):17
19 """Tests for our main template."""18class TestPageMacroDispatcher(TestCase):
2019
21 layer = FunctionalLayer20 layer = FunctionalLayer
2221
23 def test_main_template_defines_master_macro(self):22 def test_base_template(self):
24 # The main template, which is registered as a view for any object at23 # For requests on the vostok vhost (i.e. IVostokLayer requests), the
25 # all when in the VostokLayer, defines a 'master' macro.24 # base template used is the vostok one.
26 adapter = getMultiAdapter(25 root_view = getMultiAdapter(
27 (None, VostokTestRequest()), name='main_template')26 (VostokRoot(), VostokTestRequest()), name='+index')
28 self.assertEqual(['master'], adapter.index.macros.keys())27 adapter = getMultiAdapter([root_view], IPathAdapter, name='macro')
29 self.assertIn('lp/vostok', adapter.index.filename)28 self.assertIn('lp/vostok', adapter.base.filename)
3029 # The vostok base template defines a 'master' macro as the adapter
3130 # expects.
32def test_suite():31 self.assertIn('master', adapter.base.macros.keys())
33 return unittest.TestLoader().loadTestsFromName(__name__)
3432
=== modified file 'lib/lp/vostok/browser/tests/test_root.py'
--- lib/lp/vostok/browser/tests/test_root.py 2010-07-30 03:50:17 +0000
+++ lib/lp/vostok/browser/tests/test_root.py 2010-08-12 22:16:35 +0000
@@ -5,12 +5,14 @@
55
6__metaclass__ = type6__metaclass__ = type
77
8import os
8import unittest9import unittest
910
10from zope.app.publisher.browser import getDefaultViewName11from zope.app.publisher.browser import getDefaultViewName
1112
12from canonical.testing.layers import DatabaseFunctionalLayer, FunctionalLayer13from canonical.testing.layers import DatabaseFunctionalLayer, FunctionalLayer
13from canonical.launchpad.testing.pages import extract_text, find_tag_by_id14from canonical.launchpad.testing.pages import extract_text, find_tag_by_id
15from canonical.launchpad.webapp.tales import IMainTemplateFile
1416
15from lp.testing import TestCase, TestCaseWithFactory17from lp.testing import TestCase, TestCaseWithFactory
16from lp.testing.views import create_initialized_view18from lp.testing.views import create_initialized_view
@@ -19,7 +21,6 @@
19from lp.vostok.publisher import VostokLayer, VostokRoot21from lp.vostok.publisher import VostokLayer, VostokRoot
2022
2123
22
23class TestRootRegistrations(TestCase):24class TestRootRegistrations(TestCase):
24 """Test the registration of views for `VostokRoot`."""25 """Test the registration of views for `VostokRoot`."""
2526
@@ -72,5 +73,15 @@
72 self.assertEqual(distro.displayname, extract_text(link))73 self.assertEqual(distro.displayname, extract_text(link))
7374
7475
76class TestVostokLayerToMainTemplateAdapter(TestCase):
77
78 layer = FunctionalLayer
79
80 def test_path(self):
81 main_template_path = IMainTemplateFile(VostokTestRequest()).path
82 self.assertIn('lp/vostok', main_template_path)
83 self.assertTrue(os.path.isfile(main_template_path))
84
85
75def test_suite():86def test_suite():
76 return unittest.TestLoader().loadTestsFromName(__name__)87 return unittest.TestLoader().loadTestsFromName(__name__)
7788
=== modified file 'lib/lp/vostok/templates/main-template.pt'
--- lib/lp/vostok/templates/main-template.pt 2010-07-30 04:38:05 +0000
+++ lib/lp/vostok/templates/main-template.pt 2010-08-12 22:16:35 +0000
@@ -3,13 +3,16 @@
3 xmlns:tal="http://xml.zope.org/namespaces/tal"3 xmlns:tal="http://xml.zope.org/namespaces/tal"
4 define-macro="master"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">4 define-macro="master"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
5<html xmlns="http://www.w3.org/1999/xhtml">5<html xmlns="http://www.w3.org/1999/xhtml">
6
6 <head>7 <head>
7 <!-- Obviously, we'll need to do something better here. -->8
8 <title>Vostok page</title>9 <title tal:content="view/fmt:pagetitle">Page Title</title>
10
9 </head>11 </head>
12
10 <body>13 <body>
11 <h1 metal:define-slot="heading" />14 <h1 metal:define-slot="heading" />
12 <div metal:define-slot="content" />15 <div metal:define-slot="main" />
13 </body>16 </body>
14</html>17</html>
15</metal:page>18</metal:page>
1619
=== modified file 'lib/lp/vostok/templates/root.pt'
--- lib/lp/vostok/templates/root.pt 2010-07-15 10:11:03 +0000
+++ lib/lp/vostok/templates/root.pt 2010-08-12 22:16:35 +0000
@@ -3,13 +3,13 @@
3 xmlns:tal="http://xml.zope.org/namespaces/tal"3 xmlns:tal="http://xml.zope.org/namespaces/tal"
4 xmlns:metal="http://xml.zope.org/namespaces/metal"4 xmlns:metal="http://xml.zope.org/namespaces/metal"
5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
6 metal:use-macro="context/@@main_template/master"6 metal:use-macro="view/macro:page/main_only"
7 i18n:domain="vostok">7 i18n:domain="vostok">
8 <body>8 <body>
9 <tal:heading metal:fill-slot="heading">9 <tal:heading metal:fill-slot="heading">
10 <h1>Vostok</h1>10 <h1>Vostok</h1>
11 </tal:heading>11 </tal:heading>
12 <tal:content metal:fill-slot="content">12 <tal:content metal:fill-slot="main">
13 <ul id="distro-list">13 <ul id="distro-list">
14 <tal:loop tal:repeat="distro view/distributions">14 <tal:loop tal:repeat="distro view/distributions">
15 <li tal:content="structure distro/fmt:link" />15 <li tal:content="structure distro/fmt:link" />
1616
=== modified file 'utilities/sourcedeps.conf'
--- utilities/sourcedeps.conf 2010-08-03 14:59:22 +0000
+++ utilities/sourcedeps.conf 2010-08-12 22:16:35 +0000
@@ -12,5 +12,6 @@
12pygettextpo lp:~launchpad-pqm/pygettextpo/trunk;revno=2412pygettextpo lp:~launchpad-pqm/pygettextpo/trunk;revno=24
13pygpgme lp:~launchpad-pqm/pygpgme/devel;revno=4913pygpgme lp:~launchpad-pqm/pygpgme/devel;revno=49
14subvertpy lp:~launchpad-pqm/subvertpy/trunk;revno=204214subvertpy lp:~launchpad-pqm/subvertpy/trunk;revno=2042
15python-debian lp:~launchpad-pqm/python-debian/devel;revno=185
15testresources lp:~launchpad-pqm/testresources/dev;revno=1616testresources lp:~launchpad-pqm/testresources/dev;revno=16
16shipit lp:~launchpad-pqm/shipit/trunk;revno=8909 optional17shipit lp:~launchpad-pqm/shipit/trunk;revno=8909 optional

Subscribers

People subscribed via source and target branches

to status/vote changes: