Merge lp:~julian-edwards/launchpad/first-ppa-name-bug-439305 into lp:launchpad

Proposed by Julian Edwards
Status: Merged
Merged at revision: not available
Proposed branch: lp:~julian-edwards/launchpad/first-ppa-name-bug-439305
Merge into: lp:launchpad
Diff against target: 426 lines
9 files modified
lib/lp/registry/model/person.py (+3/-8)
lib/lp/soyuz/browser/archive.py (+13/-3)
lib/lp/soyuz/doc/archive.txt (+18/-1)
lib/lp/soyuz/interfaces/archive.py (+17/-2)
lib/lp/soyuz/model/archive.py (+14/-1)
lib/lp/soyuz/stories/ppa/xx-copy-packages.txt (+1/-1)
lib/lp/soyuz/stories/ppa/xx-ppa-private-teams.txt (+2/-2)
lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt (+46/-30)
lib/lp/soyuz/templates/archive-activate.pt (+0/-4)
To merge this branch: bzr merge lp:~julian-edwards/launchpad/first-ppa-name-bug-439305
Reviewer Review Type Date Requested Status
Francis J. Lacoste (community) release-critical Approve
Eleanor Berger Pending
Review via email: mp+13213@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :

= Summary =
Allow first PPA created to have a name other than 'ppa'

== Proposed fix ==
The first PPA created always has a name of 'ppa' which is now unnecessary. It
was originally done this way so that we could migrate unnamed PPAs when adding
the multiple-PPAs feature and keep the old upload path.

== Pre-implementation notes ==
Chatted to Michael N, and I've refactored some of the Archive-specific queries
in model/person.py into IArchiveSet. The main isssue was that the
IPerson.archive property relied on the fact that the first PPA was created
with the 'ppa' name. It's now taking the first PPA it finds, ordered by ID.

== Implementation details ==
 * Added IArchiveSet method to query PPAs for a person and changed IPerson
code to use that.
 * Changed the first activation form to have the name field on it.
 * Drive-by fixes on the heading for the activation page.

== Tests ==
bin/test -cvvt person.txt -t xx-ppa-workflow.txt

== Demo and Q/A ==
See http://launchpad.dev/~name12/+activate-ppa

Admins can activate someone else's PPA so if you're logged in as Foo Bar then
you're good :)

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt
  lib/lp/soyuz/templates/archive-activate.pt
  lib/lp/soyuz/doc/archive.txt
  lib/lp/soyuz/interfaces/archive.py
  lib/lp/soyuz/model/archive.py
  lib/lp/registry/model/person.py
  lib/lp/soyuz/browser/archive.py

== Pylint notices ==

lib/lp/soyuz/interfaces/archive.py
    40: [F0401] Unable to import 'lazr.enum' (No module named enum)
    53: [F0401] Unable to import 'lazr.restful.declarations' (No module named
restful)
    59: [F0401] Unable to import 'lazr.restful.fields' (No module named
restful)

lib/lp/soyuz/model/archive.py
    14: [F0401] Unable to import 'lazr.lifecycle.event' (No module named
lifecycle)

Revision history for this message
Francis J. Lacoste (flacoste) :
review: Approve (release-critical)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/model/person.py'
2--- lib/lp/registry/model/person.py 2009-09-30 01:20:39 +0000
3+++ lib/lp/registry/model/person.py 2009-10-13 10:25:26 +0000
4@@ -77,7 +77,7 @@
5 from canonical.launchpad.interfaces.account import (
6 AccountCreationRationale, AccountStatus, IAccount, IAccountSet,
7 INACTIVE_ACCOUNT_STATUSES)
8-from lp.soyuz.interfaces.archive import ArchivePurpose, NoSuchPPA
9+from lp.soyuz.interfaces.archive import ArchivePurpose, IArchiveSet
10 from lp.soyuz.interfaces.archivepermission import (
11 IArchivePermissionSet)
12 from canonical.launchpad.interfaces.authtoken import LoginTokenType
13@@ -2286,8 +2286,7 @@
14 @property
15 def archive(self):
16 """See `IPerson`."""
17- return Archive.selectOneBy(
18- owner=self, purpose=ArchivePurpose.PPA, name='ppa')
19+ return getUtility(IArchiveSet).getPPAOwnedByPerson(self)
20
21 @property
22 def ppas(self):
23@@ -2297,11 +2296,7 @@
24
25 def getPPAByName(self, name):
26 """See `IPerson`."""
27- ppa = Archive.selectOneBy(
28- owner=self, purpose=ArchivePurpose.PPA, name=name)
29- if ppa is None:
30- raise NoSuchPPA(name)
31- return ppa
32+ return getUtility(IArchiveSet).getPPAOwnedByPerson(self, name)
33
34 def isBugContributor(self, user=None):
35 """See `IPerson`."""
36
37=== modified file 'lib/lp/soyuz/browser/archive.py'
38--- lib/lp/soyuz/browser/archive.py 2009-09-18 21:00:18 +0000
39+++ lib/lp/soyuz/browser/archive.py 2009-10-13 10:25:26 +0000
40@@ -1624,17 +1624,27 @@
41
42 schema = IPPAActivateForm
43 custom_widget('description', TextAreaWidget, height=3)
44+ label = "Personal Package Archive Activation"
45
46 @property
47 def ubuntu(self):
48 return getUtility(ILaunchpadCelebrities).ubuntu
49
50+ @property
51+ def initial_values(self):
52+ """Set up default values for form fields."""
53+ # Suggest a default value of "ppa" for the name for the
54+ # first PPA activation.
55+ if self.context.archive is None:
56+ return {'name': 'ppa'}
57+ return {}
58+
59 def setUpFields(self):
60 """Override `LaunchpadFormView`.
61
62 Reorder the fields in a way the make more sense to users and also
63- omit 'name' and present a checkbox for acknowledging the PPA-ToS
64- if the user is creating his first PPA.
65+ present a checkbox for acknowledging the PPA-ToS if the user is
66+ creating his first PPA.
67 """
68 LaunchpadFormView.setUpFields(self)
69
70@@ -1643,7 +1653,7 @@
71 'name', 'displayname', 'description')
72 else:
73 self.form_fields = self.form_fields.select(
74- 'displayname', 'accepted', 'description')
75+ 'name', 'displayname', 'accepted', 'description')
76
77 def validate(self, data):
78 """Ensure user has checked the 'accepted' checkbox."""
79
80=== modified file 'lib/lp/soyuz/doc/archive.txt'
81--- lib/lp/soyuz/doc/archive.txt 2009-10-09 16:03:02 +0000
82+++ lib/lp/soyuz/doc/archive.txt 2009-10-13 10:25:26 +0000
83@@ -1603,7 +1603,7 @@
84 PPA for Celso Providelo
85 PPA for Launchpad Buildd Admins
86
87-The same happens for specific upload rights granted on 3rd-part
88+The same happens for specific upload rights granted on 3rd-party
89 PPAs. When 'No Privileges' gets upload rights to Celso's PPA,
90 it gets listed by `getPPAsForUser`.
91
92@@ -1629,6 +1629,23 @@
93 >>> jblack_ppas.count()
94 0
95
96+Another similar method, getPPAOwnedByPersonUser(), will return the named PPA
97+owned by the person, or if the person is not supplied will default to the
98+first PPA that the person created.
99+
100+ >>> print archive_set.getPPAOwnedByPerson(cprov).displayname
101+ PPA for Celso Providelo
102+
103+ >>> print archive_set.getPPAOwnedByPerson(cprov, name="ppa").displayname
104+ PPA for Celso Providelo
105+
106+If the named PPA does not exist, a NoSuchPPA exception is raised.
107+
108+ >>> print archive_set.getPPAOwnedByPerson(cprov, name="goat").displayname
109+ Traceback (most recent call last):
110+ ...
111+ NoSuchPPA: No such ppa: 'goat'.
112+
113 The method getPrivatePPAs() will return a result set of all PPAs that are
114 private.
115
116
117=== modified file 'lib/lp/soyuz/interfaces/archive.py'
118--- lib/lp/soyuz/interfaces/archive.py 2009-09-23 07:41:13 +0000
119+++ lib/lp/soyuz/interfaces/archive.py 2009-10-13 10:25:26 +0000
120@@ -1075,11 +1075,14 @@
121
122 name = TextLine(
123 title=_("PPA name"), required=True, constraint=name_validator,
124- description=_("A unique name used to identify this PPA."))
125+ description=_("A unique name used to identify this PPA. It will "
126+ "form part of the URL to the archive repository."))
127
128 displayname = StrippedTextLine(
129 title=_("Displayname"), required=True,
130- description=_("Displayname for this PPA."))
131+ description=_("Displayname for this PPA. It will be used in "
132+ "the signing key's description if this is the "
133+ "first PPA for a person."))
134
135 description = Text(
136 title=_("PPA contents description"), required=False,
137@@ -1170,6 +1173,18 @@
138 def __iter__():
139 """Iterates over existent archives, including the main_archives."""
140
141+ def getPPAOwnedByPerson(person, name=None):
142+ """Return the named PPA owned by person.
143+
144+ :param person: An `IPerson`
145+ :param name: The PPA name required.
146+
147+ If the person is not supplied it will default to the
148+ first PPA that the person created.
149+
150+ :raises NoSuchPPA: if the named PPA does not exist.
151+ """
152+
153 def getPPAsForUser(user):
154 """Return all PPAs the given user can participate.
155
156
157=== modified file 'lib/lp/soyuz/model/archive.py'
158--- lib/lp/soyuz/model/archive.py 2009-10-09 16:03:02 +0000
159+++ lib/lp/soyuz/model/archive.py 2009-10-13 10:25:26 +0000
160@@ -56,7 +56,7 @@
161 AlreadySubscribed, ArchiveDependencyError, ArchiveNotPrivate,
162 ArchivePurpose, DistroSeriesNotFound, IArchive, IArchiveSet,
163 IDistributionArchive, InvalidComponent, IPPA, MAIN_ARCHIVE_PURPOSES,
164- PocketNotFound, VersionRequiresName, default_name_by_purpose)
165+ NoSuchPPA, PocketNotFound, VersionRequiresName, default_name_by_purpose)
166 from lp.soyuz.interfaces.archiveauthtoken import IArchiveAuthTokenSet
167 from lp.soyuz.interfaces.archivepermission import (
168 ArchivePermissionType, IArchivePermissionSet)
169@@ -1399,6 +1399,19 @@
170 return 0
171 return int(size)
172
173+ def getPPAOwnedByPerson(self, person, name=None):
174+ """See `IArchiveSet`."""
175+ store = Store.of(person)
176+ clause = [
177+ Archive.purpose == ArchivePurpose.PPA,
178+ Archive.owner == person]
179+ if name is not None:
180+ clause.append(Archive.name == name)
181+ result = store.find(Archive, *clause).order_by(Archive.id).first()
182+ if name is not None and result is None:
183+ raise NoSuchPPA(name)
184+ return result
185+
186 def getPPAsForUser(self, user):
187 """See `IArchiveSet`."""
188 # Avoiding circular imports.
189
190=== modified file 'lib/lp/soyuz/stories/ppa/xx-copy-packages.txt'
191--- lib/lp/soyuz/stories/ppa/xx-copy-packages.txt 2009-09-23 10:29:52 +0000
192+++ lib/lp/soyuz/stories/ppa/xx-copy-packages.txt 2009-10-13 10:25:26 +0000
193@@ -68,7 +68,7 @@
194
195 >>> jblack_browser.getLink('Create a new PPA').click()
196 >>> print jblack_browser.title
197- Activate Personal Package Archive...
198+ Personal Package Archive Activation : James Blackwell
199
200 >>> jblack_browser.getControl(
201 ... name="field.displayname").value = 'PPA for James Blackwell'
202
203=== modified file 'lib/lp/soyuz/stories/ppa/xx-ppa-private-teams.txt'
204--- lib/lp/soyuz/stories/ppa/xx-ppa-private-teams.txt 2009-09-18 15:24:30 +0000
205+++ lib/lp/soyuz/stories/ppa/xx-ppa-private-teams.txt 2009-10-13 10:25:26 +0000
206@@ -33,8 +33,8 @@
207 The form looks almost identical to that for a public team.
208
209 >>> browser.getLink('Create a new PPA').click()
210- >>> browser.title
211- 'Activate Personal Package Archive...
212+ >>> print browser.title
213+ Personal Package Archive Activation : ...Private Team...
214
215 There is, however, an extra bit of information indicating the new PPA
216 will be private.
217
218=== modified file 'lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt'
219--- lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt 2009-09-18 15:24:30 +0000
220+++ lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt 2009-10-13 10:25:26 +0000
221@@ -36,7 +36,7 @@
222 >>> sample_browser.getLink("Create a new PPA").click()
223
224 >>> print sample_browser.title
225- Activate Personal Package Archive...
226+ Personal Package Archive Activation : Sample Person
227
228 This page presents a pointer to the current PPA-ToS (terms of service)
229 with mandatory 'displayname' and checkbox ('accepted') fields. The
230@@ -54,7 +54,11 @@
231 Activating this PPA will block future renaming of Sample Person
232 ...
233
234-'Displayname' is required.
235+'PPA name' and 'Displayname' are required fields. For the first activated
236+PPA, the name is pre-filled with a suggestion of "ppa":
237+
238+ >>> print sample_browser.getControl(name="field.name").value
239+ ppa
240
241 >>> sample_browser.getControl("Activate").click()
242
243@@ -63,10 +67,12 @@
244 There is 1 error.
245 Required input is missing.
246
247-By submitting the form without acknowledge the PPA-ToS results in a
248+By submitting the form without acknowledging the PPA-ToS results in a
249 error with a specific message.
250
251 >>> sample_browser.getControl(
252+ ... name="field.name").value = 'sampleppa'
253+ >>> sample_browser.getControl(
254 ... name="field.displayname").value = 'Sample PPA'
255 >>> sample_browser.getControl("Activate").click()
256
257@@ -116,7 +122,7 @@
258 PPA description
259 Howdy, cowboys !
260
261-Empty 'description' fields are not be rendered.
262+Empty 'description' fields are not rendered.
263
264 >>> sample_browser.getLink("Change details").click()
265 >>> sample_browser.getControl(name="field.description").value = ('')
266@@ -143,7 +149,7 @@
267 There is 1 error.
268 Required input is missing.
269
270-Once the PPA is activated, Sample user account cannot be renamed
271+Once the PPA is activated, the Sample user account cannot be renamed
272 anymore. Changing the account name affects the PPA repository paths
273 and we don't have infrastructure in place to support that yet. See
274 more information about the feature in bug #87326.
275@@ -193,10 +199,12 @@
276
277 >>> sample_browser.getLink('Create a new PPA').click()
278
279- >>> sample_browser.title
280- 'Activate Personal Package Archive...
281+ >>> print sample_browser.title
282+ Personal Package Archive Activation : ...
283
284 >>> sample_browser.getControl(
285+ ... name="field.name").value = 'develppa'
286+ >>> sample_browser.getControl(
287 ... name="field.displayname").value = 'Devel PPA'
288 >>> sample_browser.getControl(name="field.accepted").value = True
289 >>> sample_browser.getControl(
290@@ -285,6 +293,7 @@
291 Create a new PPA
292
293 >>> admin_browser.getLink("Create a new PPA").click()
294+ >>> admin_browser.getControl(name="field.name").value = 'hackppa'
295 >>> admin_browser.getControl(
296 ... name="field.displayname").value = 'Hack PPA'
297 >>> admin_browser.getControl(name="field.accepted").value = True
298@@ -330,7 +339,7 @@
299 Trying to shortcut the URL as a non-privileged user does not work:
300
301 >>> sample_browser.open(
302- ... "http://launchpad.dev/~jblack/+archive/ppa/+admin")
303+ ... "http://launchpad.dev/~jblack/+archive/hackppa/+admin")
304 Traceback (most recent call last):
305 ...
306 Unauthorized...
307@@ -416,7 +425,8 @@
308
309 >>> limit = 2 ** 31 - 1
310
311- >>> admin_browser.open("http://launchpad.dev/~jblack/+archive/ppa/+admin")
312+ >>> admin_browser.open(
313+ ... "http://launchpad.dev/~jblack/+archive/hackppa/+admin")
314 >>> admin_browser.getControl(
315 ... name="field.authorized_size").value = str(limit)
316 >>> admin_browser.getControl("Save").click()
317@@ -470,11 +480,13 @@
318 Prepare the forms in both browsers to activate the default PPA for the
319 user 'Foo Bar'.
320
321+ >>> browser1.getControl(name="field.name").value = 'boomppa'
322 >>> browser1.getControl(name="field.displayname").value = 'Boom PPA'
323 >>> browser1.getControl(name="field.accepted").value = True
324 >>> browser1.getControl(
325 ... name="field.description").value = 'PPA rocks!'
326
327+ >>> browser2.getControl(name="field.name").value = 'boomppa'
328 >>> browser2.getControl(name="field.displayname").value = 'Boom PPA'
329 >>> browser2.getControl(name="field.accepted").value = True
330 >>> browser2.getControl(
331@@ -500,18 +512,16 @@
332 >>> for error in get_feedback_messages(browser2.contents):
333 ... print error
334 There is 1 error.
335- The default PPA is already activated.
336- Please specify a name for the new PPA and resubmit the form.
337-
338- >>> browser2.getControl(name="field.name").value
339- ''
340-
341-
342-== Activating a named PPA ==
343-
344-Users who already have a PPA may as well activate a second one.
345-
346-That's the case for Celso.
347+ You already have a PPA named 'boomppa'.
348+
349+ >>> print browser2.getControl(name="field.name").value
350+ boomppa
351+
352+
353+== Activating an additional PPA ==
354+
355+Users who already have a PPA may activate a second one. That's the case for
356+Celso.
357
358 >>> cprov_browser.open("http://launchpad.dev/~cprov")
359
360@@ -521,15 +531,15 @@
361 Create a new PPA
362
363 Celso can simply click on 'Create a new PPA' and will be presented to
364-the usual PPA activation form where the checkbox for acknowledge the
365-PPA-ToS is replaced by a 'name' field and a list of 'Existing PPAs'.
366-Launchpad requires an user to acknowledge the PPA-ToS only once for
367+the usual PPA activation form where the checkbox for acknowledging the
368+PPA-ToS is no longer present and a list of 'Existing PPAs' is presented.
369+Launchpad requires a user to acknowledge the PPA-ToS only once for
370 all his PPAs.
371
372 >>> cprov_browser.getLink("Create a new PPA").click()
373
374 >>> print cprov_browser.title
375- Activate Personal Package Archive...
376+ Personal Package Archive Activation : Celso Providelo
377
378 >>> print_tag_with_id(cprov_browser.contents, 'ppas')
379 Existing PPAs
380@@ -543,12 +553,14 @@
381 >>> print extract_text(
382 ... first_tag_by_class(cprov_browser.contents, 'form'))
383 PPA name:
384- A unique name used to identify this PPA.
385+ A unique name used to identify this PPA. It will form part of the URL
386+ to the archive repository.
387 Displayname:
388- Displayname for this PPA.
389+ Displayname for this PPA. It will be used in the signing key's
390+ description if this is the first PPA for a person.
391 PPA contents description: (Optional)
392- A short description of this PPA. URLs are allowed and will be
393- rendered as links.
394+ A short description of this PPA. URLs are allowed and will be rendered
395+ as links.
396
397 Note that, differently than the time when the first PPA was activated,
398 this time there is no warning about the fact that the context renaming
399@@ -559,7 +571,11 @@
400 ... first_tag_by_class(cprov_browser.contents, 'actions'))
401 or Cancel
402
403-If Celso does not fill 'PPA name' an error is raised.
404+The 'PPA name' field is not pre-filled and if Celso does not fill it then
405+an error is raised.
406+
407+ >>> print cprov_browser.getControl(name="field.name").value
408+ <BLANKLINE>
409
410 >>> cprov_browser.getControl(
411 ... name="field.displayname").value = 'Edge PPA'
412
413=== modified file 'lib/lp/soyuz/templates/archive-activate.pt'
414--- lib/lp/soyuz/templates/archive-activate.pt 2009-08-15 11:59:38 +0000
415+++ lib/lp/soyuz/templates/archive-activate.pt 2009-10-13 10:25:26 +0000
416@@ -8,10 +8,6 @@
417 >
418 <body>
419
420-<div metal:fill-slot="heading">
421- <h1>Personal Package Archive Activation</h1>
422-</div>
423-
424 <div metal:fill-slot="main">
425 <div class="top-portlet">
426 A PPA is a place where you can build and publish your own packages.