Merge lp:~julian-edwards/launchpad/first-ppa-name-bug-439305 into lp:launchpad
- first-ppa-name-bug-439305
- Merge into devel
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Francis J. Lacoste (community) | release-critical | Approve | |
Eleanor Berger | Pending | ||
Review via email: mp+13213@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote : | # |
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. |
= 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 == launchpad. dev/~name12/ +activate- ppa
See http://
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: soyuz/stories/ ppa/xx- ppa-workflow. txt soyuz/templates /archive- activate. pt soyuz/doc/ archive. txt soyuz/interface s/archive. py soyuz/model/ archive. py registry/ model/person. py soyuz/browser/ archive. py
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
== Pylint notices ==
lib/lp/ soyuz/interface s/archive. py declarations' (No module named fields' (No module named
40: [F0401] Unable to import 'lazr.enum' (No module named enum)
53: [F0401] Unable to import 'lazr.restful.
restful)
59: [F0401] Unable to import 'lazr.restful.
restful)
lib/lp/ soyuz/model/ archive. py .event' (No module named
14: [F0401] Unable to import 'lazr.lifecycle
lifecycle)