Merge lp:~bac/launchpad/bug-39212 into lp:launchpad
- bug-39212
- Merge into devel
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Graham Binns | ||||||||
Approved revision: | no longer in the source branch. | ||||||||
Merged at revision: | 11478 | ||||||||
Proposed branch: | lp:~bac/launchpad/bug-39212 | ||||||||
Merge into: | lp:launchpad | ||||||||
Diff against target: |
601 lines (+264/-198) 3 files modified
lib/lp/registry/browser/person.py (+130/-73) lib/lp/registry/stories/person/xx-person-edit-wikis.txt (+86/-61) lib/lp/registry/templates/person-editwikinames.pt (+48/-64) |
||||||||
To merge this branch: | bzr merge lp:~bac/launchpad/bug-39212 | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Graham Binns (community) | code | Approve | |
Review via email: mp+34196@code.launchpad.net |
Commit message
Change +editwikinames to be a LaunchpadFormView and eliminate some data modification inconsistency issues.
Description of the change
= Summary =
Several of the person/+editthingy pages pre-date LaunchpadFormView. In
a misguided attempt to be super-friendly, the pages allowed in-place
editing of lots of data. Doing so ran the risk that internal
inconsistencies in the submitted batch would cause a failure.
== Proposed fix ==
Change +editwikinames to be a LaunchpadFormView and only allow existing
data to be displayed and marked for removal.
== Pre-implementation notes ==
None. Similar to the recent change to +editjabberids
== Implementation details ==
As above.
== Tests ==
bin/test -vvm lp.registry -t xx-person-
== Demo and Q/A ==
https:/
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/
lib/lp/
lib/lp/
Preview Diff
1 | === modified file 'lib/lp/registry/browser/person.py' |
2 | --- lib/lp/registry/browser/person.py 2010-08-30 06:38:53 +0000 |
3 | +++ lib/lp/registry/browser/person.py 2010-08-31 14:04:45 +0000 |
4 | @@ -219,6 +219,7 @@ |
5 | LaunchpadRadioWidgetWithDescription, |
6 | LocationWidget, |
7 | PasswordChangeWidget, |
8 | + URIWidget, |
9 | ) |
10 | from canonical.widgets.image import ImageChangeWidget |
11 | from canonical.widgets.itemswidgets import LabeledMultiCheckBoxWidget |
12 | @@ -297,7 +298,10 @@ |
13 | ITeamMembershipSet, |
14 | TeamMembershipStatus, |
15 | ) |
16 | -from lp.registry.interfaces.wikiname import IWikiNameSet |
17 | +from lp.registry.interfaces.wikiname import ( |
18 | + IWikiName, |
19 | + IWikiNameSet, |
20 | + ) |
21 | from lp.services.fields import LocationField |
22 | from lp.services.openid.adapters.openid import CurrentOpenIDEndPoint |
23 | from lp.services.openid.browser.openiddiscovery import ( |
24 | @@ -3498,35 +3502,76 @@ |
25 | sCoC_util.modifySignature(sig_id, self.user, comment, False) |
26 | |
27 | |
28 | -class PersonEditWikiNamesView(LaunchpadView): |
29 | +class PersonEditWikiNamesView(LaunchpadFormView): |
30 | """View for ~person/+editwikinames""" |
31 | |
32 | + schema = IWikiName |
33 | + fields = ['wiki', 'wikiname'] |
34 | + # Use custom widgets solely to get the width correct. The URIWidget has a |
35 | + # CSS class that does not respect the displayWidth, thus the need to use a |
36 | + # different cssClass. |
37 | + custom_widget('wiki', URIWidget, displayWidth=40, cssClass="textType") |
38 | + custom_widget('wikiname', TextWidget, displayWidth=40) |
39 | + |
40 | @property |
41 | def label(self): |
42 | return smartquote("%s's wiki names" % self.context.displayname) |
43 | |
44 | @property |
45 | - def cancel_url(self): |
46 | - return canonical_url(self.context, view_name="+edit") |
47 | + def next_url(self): |
48 | + return canonical_url(self.context) |
49 | + |
50 | + cancel_url = next_url |
51 | + |
52 | + def setUpFields(self): |
53 | + super(PersonEditWikiNamesView, self).setUpFields() |
54 | + if self.context.wiki_names.count() > 0: |
55 | + # Make the wiki and wiki_name entries optional on the edit page if |
56 | + # one or more ids already exist, which allows the removal of ids |
57 | + # without filling out the new wiki fields. |
58 | + wiki_field = self.form_fields['wiki'] |
59 | + wikiname_field = self.form_fields['wikiname'] |
60 | + # Copy the fields so as not to modify the interface. |
61 | + wiki_field.field = copy_field(wiki_field.field) |
62 | + wiki_field.field.required = False |
63 | + wikiname_field.field = copy_field(wikiname_field.field) |
64 | + wikiname_field.field.required = False |
65 | |
66 | def _validateWikiURL(self, url): |
67 | """Validate the URL. |
68 | |
69 | Make sure that the result is a valid URL with only the |
70 | - appropriate schemes. |
71 | + appropriate schemes. The url is assumed to be a string. |
72 | """ |
73 | + if url is None: |
74 | + return |
75 | try: |
76 | uri = URI(url) |
77 | if uri.scheme not in ('http', 'https'): |
78 | - self.error_message = structured( |
79 | - 'The URL scheme "%(scheme)s" is not allowed. ' |
80 | - 'Only http or https URLs may be used.', scheme=uri.scheme) |
81 | - return False |
82 | + self.setFieldError( |
83 | + 'wiki', |
84 | + structured( |
85 | + 'The URL scheme "%(scheme)s" is not allowed. ' |
86 | + 'Only http or https URLs may be used.', |
87 | + scheme=uri.scheme)) |
88 | except InvalidURIError: |
89 | - self.error_message = structured( |
90 | - '"%(url)s" is not a valid URL.', url=url) |
91 | - return False |
92 | - return True |
93 | + self.setFieldError( |
94 | + 'wiki', |
95 | + structured( |
96 | + '"%(url)s" is not a valid URL.', url=url)) |
97 | + |
98 | + def _validateWikiName(self, name): |
99 | + """Ensure the wikiname is valid. |
100 | + |
101 | + It must not be longer than 100 characters. Name is assumed to be a |
102 | + string. |
103 | + """ |
104 | + max_len = 100 |
105 | + if len(name) > max_len: |
106 | + self.setFieldError( |
107 | + 'wikiname', |
108 | + structured( |
109 | + 'The wiki name cannot exceed %d characters.' % max_len)) |
110 | |
111 | def _sanitizeWikiURL(self, url): |
112 | """Strip whitespaces and make sure :url ends in a single '/'.""" |
113 | @@ -3534,72 +3579,82 @@ |
114 | return url |
115 | return '%s/' % url.strip().rstrip('/') |
116 | |
117 | - def initialize(self): |
118 | - """Process the WikiNames form.""" |
119 | - self.error_message = None |
120 | - if self.request.method != "POST": |
121 | - # Nothing to do |
122 | - return |
123 | - |
124 | - form = self.request.form |
125 | - context = self.context |
126 | + def validate(self, data): |
127 | + # If there are already form errors then just show them. |
128 | + if self.errors: |
129 | + return |
130 | + wikiurl = self._sanitizeWikiURL(data.get('wiki')) |
131 | + wikiname = data.get('wikiname') |
132 | + if wikiurl or wikiname: |
133 | + if not wikiurl: |
134 | + self.setFieldError( |
135 | + 'wiki', |
136 | + structured( |
137 | + 'The Wiki URL must be specified.')) |
138 | + if not wikiname: |
139 | + self.setFieldError( |
140 | + 'wikiname', |
141 | + structured( |
142 | + 'The Wiki name must be specified.')) |
143 | + |
144 | + if self.errors: |
145 | + return |
146 | + |
147 | + if wikiurl is not None: |
148 | + self._validateWikiURL(wikiurl) |
149 | + if wikiname is not None: |
150 | + self._validateWikiName(wikiname) |
151 | + |
152 | + if self.errors: |
153 | + return |
154 | + |
155 | wikinameset = getUtility(IWikiNameSet) |
156 | - |
157 | - for w in context.wiki_names: |
158 | - # XXX: GuilhermeSalgado 2005-08-25: |
159 | - # We're exposing WikiName IDs here because that's the only |
160 | - # unique column we have. If we don't do this we'll have to |
161 | - # generate the field names using the WikiName.wiki and |
162 | - # WikiName.wikiname columns (because these two columns make |
163 | - # another unique identifier for WikiNames), but that's tricky and |
164 | - # not worth the extra work. |
165 | - if form.get('remove_%d' % w.id): |
166 | - w.destroySelf() |
167 | - else: |
168 | - wiki = self._sanitizeWikiURL(form.get('wiki_%d' % w.id)) |
169 | - wikiname = form.get('wikiname_%d' % w.id) |
170 | - if not (wiki and wikiname): |
171 | - self.error_message = structured( |
172 | - "Neither Wiki nor WikiName can be empty.") |
173 | - return |
174 | - if not self._validateWikiURL(wiki): |
175 | - return |
176 | - w.wiki = wiki |
177 | - w.wikiname = wikiname |
178 | - |
179 | - wiki = self._sanitizeWikiURL(form.get('newwiki')) |
180 | - wikiname = form.get('newwikiname') |
181 | - if wiki or wikiname: |
182 | - if wiki and wikiname: |
183 | - existingwiki = wikinameset.getByWikiAndName(wiki, wikiname) |
184 | - if existingwiki and existingwiki.person != context: |
185 | - owner_name = urllib.quote(existingwiki.person.name) |
186 | - merge_url = ( |
187 | - '%s/+requestmerge?field.dupe_person=%s' |
188 | - % (canonical_url(getUtility(IPersonSet)), owner_name)) |
189 | - self.error_message = structured( |
190 | + existingwiki = wikinameset.getByWikiAndName(wikiurl, wikiname) |
191 | + if existingwiki: |
192 | + if existingwiki.person != self.context: |
193 | + owner_name = urllib.quote(existingwiki.person.name) |
194 | + merge_url = ( |
195 | + '%s/+requestmerge?field.dupe_person=%s' |
196 | + % (canonical_url(getUtility(IPersonSet)), owner_name)) |
197 | + self.setFieldError( |
198 | + 'wikiname', |
199 | + structured( |
200 | 'The WikiName %s%s is already registered by ' |
201 | '<a href="%s">%s</a>. If you think this is a ' |
202 | 'duplicated account, you can <a href="%s">merge it' |
203 | '</a> into your account.', |
204 | - wiki, wikiname, canonical_url(existingwiki.person), |
205 | - existingwiki.person.displayname, merge_url) |
206 | - return |
207 | - |
208 | - elif existingwiki: |
209 | - self.error_message = structured( |
210 | - 'The WikiName %s%s already belongs to you.', |
211 | - wiki, wikiname) |
212 | - return |
213 | - if not self._validateWikiURL(wiki): |
214 | - return |
215 | - wikinameset.new(context, wiki, wikiname) |
216 | + wikiurl, wikiname, |
217 | + canonical_url(existingwiki.person), |
218 | + existingwiki.person.displayname, merge_url)) |
219 | else: |
220 | - self.newwiki = wiki |
221 | - self.newwikiname = wikiname |
222 | - self.error_message = structured( |
223 | - "Neither Wiki nor WikiName can be empty.") |
224 | - return |
225 | + # The person already has this wiki. |
226 | + self.setFieldError( |
227 | + 'wikiname', |
228 | + 'The WikiName %s%s already belongs to you.' % |
229 | + (wikiurl, wikiname)) |
230 | + |
231 | + def _save(self, wikiurl, wikiname): |
232 | + """Given a wikiurl and wikiname, attempt to save it. |
233 | + |
234 | + Verify someone else doesn't have it already. |
235 | + """ |
236 | + |
237 | + @action(_("Save Changes"), name="save") |
238 | + def save(self, action, data): |
239 | + """Process the wiki names form.""" |
240 | + form = self.request.form |
241 | + for obj in self.context.wiki_names: |
242 | + if form.get('remove_%s' % obj.id): |
243 | + obj.destroySelf() |
244 | + |
245 | + if not self.errors: |
246 | + wikiurl = self._sanitizeWikiURL(data.get('wiki')) |
247 | + wikiname = data.get('wikiname') |
248 | + # If either url or name are present then they both must be |
249 | + # entered. |
250 | + if wikiurl and wikiname: |
251 | + wikinameset = getUtility(IWikiNameSet) |
252 | + wikinameset.new(self.context, wikiurl, wikiname) |
253 | |
254 | |
255 | class PersonEditIRCNicknamesView(LaunchpadFormView): |
256 | @@ -3664,6 +3719,8 @@ |
257 | # ids already exist, which allows the removal of ids without |
258 | # filling out the new jabberid field. |
259 | jabber_field = self.form_fields['jabberid'] |
260 | + # Copy the field so as not to modify the interface. |
261 | + jabber_field.field = copy_field(jabber_field.field) |
262 | jabber_field.field.required = False |
263 | |
264 | @property |
265 | |
266 | === modified file 'lib/lp/registry/stories/person/xx-person-edit-wikis.txt' |
267 | --- lib/lp/registry/stories/person/xx-person-edit-wikis.txt 2009-11-15 20:27:01 +0000 |
268 | +++ lib/lp/registry/stories/person/xx-person-edit-wikis.txt 2010-08-31 14:04:45 +0000 |
269 | @@ -1,109 +1,134 @@ |
270 | -= Person's wikinames = |
271 | +Person's wikinames |
272 | +================== |
273 | |
274 | A person can have any number of WikiNames registered in Launchpad, and |
275 | they can be managed on the +editwikinames page. |
276 | |
277 | # A helper function for printing all wikinames of a person. |
278 | - >>> import re |
279 | - >>> def print_existing_wikinames(contents): |
280 | - ... soup = find_main_content(contents) |
281 | - ... wiki_url_inputs = soup.findAll( |
282 | - ... 'input', attrs={'name': re.compile(r'wiki_\d+')}) |
283 | - ... wiki_urls = [input['value'] for input in wiki_url_inputs] |
284 | - ... wiki_name_inputs = soup.findAll( |
285 | - ... 'input', attrs={'name': re.compile(r'wikiname_\d+')}) |
286 | - ... wiki_names = [input['value'] for input in wiki_name_inputs] |
287 | - ... wikis = zip(wiki_urls, wiki_names) |
288 | - ... print '\n'.join('%s%s' % (wiki_url, wiki_name) |
289 | - ... for wiki_url, wiki_name in wikis) |
290 | + >>> def print_existing_wikiurls(contents): |
291 | + ... trs = find_tags_by_class(contents, 'wikiurl') |
292 | + ... wikis = [] |
293 | + ... for tr in trs: |
294 | + ... td = tr.find('td') |
295 | + ... wikis.append(td.contents[0]) |
296 | + ... print '\n'.join(wikis) |
297 | + |
298 | + >>> def print_feedback(browser): |
299 | + ... print "\n".join(get_feedback_messages(browser.contents)) |
300 | |
301 | Mark already has one WikiName registered. |
302 | |
303 | >>> browser.addHeader('Authorization', 'Basic mark@example.com:test') |
304 | - >>> browser.open('http://localhost/~mark/+editwikinames') |
305 | - >>> print_existing_wikinames(browser.contents) |
306 | + >>> browser.open('http://launchpad.dev/~mark/+editwikinames') |
307 | + >>> print_existing_wikiurls(browser.contents) |
308 | https://wiki.ubuntu.com/MarkShuttleworth |
309 | |
310 | But he wants to register another one. |
311 | |
312 | - >>> browser.getControl(name='newwiki').value = 'http://foo.bar/wiki/' |
313 | - >>> browser.getControl(name='newwikiname').value = 'FooBar' |
314 | + >>> browser.getControl(name='field.wiki').value = 'http://foo.bar/wiki/' |
315 | + >>> browser.getControl(name='field.wikiname').value = 'FooBar' |
316 | >>> browser.getControl('Save Changes').click() |
317 | - >>> browser.url |
318 | - 'http://localhost/~mark/+editwikinames' |
319 | - >>> print_existing_wikinames(browser.contents) |
320 | + >>> print_feedback(browser) |
321 | + >>> print browser.url |
322 | + http://launchpad.dev/~mark |
323 | + >>> browser.open('http://launchpad.dev/~mark/+editwikinames') |
324 | + >>> print_existing_wikiurls(browser.contents) |
325 | http://foo.bar/wiki/FooBar |
326 | https://wiki.ubuntu.com/MarkShuttleworth |
327 | |
328 | He can't have two identical wiki names, though. |
329 | |
330 | - >>> browser.getControl(name='newwiki').value = 'http://foo.bar/wiki/' |
331 | - >>> browser.getControl(name='newwikiname').value = 'FooBar' |
332 | + >>> browser.getControl(name='field.wiki').value = 'http://foo.bar/wiki/' |
333 | + >>> browser.getControl(name='field.wikiname').value = 'FooBar' |
334 | >>> browser.getControl('Save Changes').click() |
335 | - >>> browser.url |
336 | - 'http://localhost/~mark/+editwikinames' |
337 | + >>> print browser.url |
338 | + http://launchpad.dev/%7Emark/+editwikinames |
339 | >>> for message in find_tags_by_class(browser.contents, 'message'): |
340 | ... print message.renderContents() |
341 | + There is 1 error. |
342 | The WikiName http://foo.bar/wiki/FooBar already belongs to you. |
343 | |
344 | Nor can he have a WikiName that is already registered in Launchpad. |
345 | |
346 | - >>> browser.getControl(name='newwiki').value = 'https://wiki.ubuntu.com/' |
347 | - >>> browser.getControl(name='newwikiname').value = 'GuilhermeSalgado' |
348 | + >>> browser.getControl(name='field.wiki').value = ( |
349 | + ... 'https://wiki.ubuntu.com/') |
350 | + >>> browser.getControl(name='field.wikiname').value = 'GuilhermeSalgado' |
351 | >>> browser.getControl('Save Changes').click() |
352 | - >>> browser.url |
353 | - 'http://localhost/~mark/+editwikinames' |
354 | + >>> print browser.url |
355 | + http://launchpad.dev/%7Emark/+editwikinames |
356 | >>> print "\n".join(get_feedback_messages(browser.contents)) |
357 | + There is 1 error. |
358 | The WikiName https://wiki.ubuntu.com/GuilhermeSalgado is already |
359 | registered by Guilherme Salgado. If you think this is a duplicated |
360 | account, you can merge it into your account. |
361 | |
362 | A WikiName's URL can't be empty nor invalid. |
363 | |
364 | - >>> browser.getControl(name='newwiki').value = '' |
365 | - >>> browser.getControl(name='newwikiname').value = 'FooBar' |
366 | - >>> browser.getControl('Save Changes').click() |
367 | - >>> browser.url |
368 | - 'http://localhost/~mark/+editwikinames' |
369 | - >>> print "\n".join(get_feedback_messages(browser.contents)) |
370 | - Neither Wiki nor WikiName can be empty. |
371 | - |
372 | - >>> browser.getControl(name='newwiki').value = '/this-is-not-a-url/' |
373 | - >>> browser.getControl(name='newwikiname').value = 'FooBar' |
374 | - >>> browser.getControl('Save Changes').click() |
375 | - >>> browser.url |
376 | - 'http://localhost/~mark/+editwikinames' |
377 | - >>> print "\n".join(get_feedback_messages(browser.contents)) |
378 | - "/this-is-not-a-url/" is not a valid URL. |
379 | + >>> browser.getControl(name='field.wiki').value = '' |
380 | + >>> browser.getControl(name='field.wikiname').value = 'FooBar' |
381 | + >>> browser.getControl('Save Changes').click() |
382 | + >>> print browser.url |
383 | + http://launchpad.dev/%7Emark/+editwikinames |
384 | + >>> print "\n".join(get_feedback_messages(browser.contents)) |
385 | + There is 1 error. |
386 | + The Wiki URL must be specified. |
387 | + |
388 | + >>> browser.getControl(name='field.wiki').value = '/this-is-not-a-url/' |
389 | + >>> browser.getControl(name='field.wikiname').value = 'FooBar' |
390 | + >>> browser.getControl('Save Changes').click() |
391 | + >>> print browser.url |
392 | + http://launchpad.dev/%7Emark/+editwikinames |
393 | + >>> print "\n".join(get_feedback_messages(browser.contents)) |
394 | + There is 1 error. |
395 | + "/this-is-not-a-url/" is not a valid URI |
396 | + |
397 | +And it can't be incredibly long. |
398 | + |
399 | + >>> browser.getControl(name='field.wiki').value = ( |
400 | + ... 'https://wiki.ubuntu.com/') |
401 | + >>> wikiname = "z" * 101 |
402 | + >>> browser.getControl(name='field.wikiname').value = wikiname |
403 | + >>> browser.getControl('Save Changes').click() |
404 | + >>> print browser.url |
405 | + http://launchpad.dev/%7Emark/+editwikinames |
406 | + >>> print "\n".join(get_feedback_messages(browser.contents)) |
407 | + There is 1 error. |
408 | + The wiki name cannot exceed 100 characters. |
409 | |
410 | The invalid value is escaped using HTML entities when displayed back to |
411 | the user. |
412 | |
413 | - >>> browser.getControl(name='newwiki').value = '<script>alert(1);</script>' |
414 | - >>> browser.getControl(name='newwikiname').value = 'FooBar' |
415 | + >>> browser.getControl(name='field.wiki').value = ( |
416 | + ... '<script>alert(1);</script>') |
417 | + >>> browser.getControl(name='field.wikiname').value = 'FooBar' |
418 | >>> browser.getControl('Save Changes').click() |
419 | - >>> browser.url |
420 | - 'http://localhost/~mark/+editwikinames' |
421 | + >>> print browser.url |
422 | + http://launchpad.dev/%7Emark/+editwikinames |
423 | >>> print "\n".join(get_feedback_messages(browser.contents)) |
424 | - "<script>alert(1);</script>/" is not a valid URL. |
425 | + There is 1 error. |
426 | + "<script>alert(1);</script>" is not a valid URI |
427 | |
428 | Only http and https URLs are allowed for wikis. |
429 | |
430 | - >>> browser.getControl(name='newwiki').value = "javascript:void" |
431 | - >>> browser.getControl(name='newwikiname').value = 'FooBar' |
432 | + >>> browser.getControl(name='field.wiki').value = "javascript:void" |
433 | + >>> browser.getControl(name='field.wikiname').value = 'FooBar' |
434 | >>> browser.getControl('Save Changes').click() |
435 | - >>> browser.url |
436 | - 'http://localhost/~mark/+editwikinames' |
437 | + >>> print browser.url |
438 | + http://launchpad.dev/%7Emark/+editwikinames |
439 | >>> print "\n".join(get_feedback_messages(browser.contents)) |
440 | - The URL scheme "javascript" is not allowed. Only http or https URLs may be |
441 | - used. |
442 | - |
443 | -Mark can remove any of his wiki names, and here he'll remove the one he |
444 | -just added. |
445 | - |
446 | + There is 1 error. |
447 | + The URI scheme "javascript" is not allowed. Only URIs with the following |
448 | + schemes may be used: http, https |
449 | + |
450 | +Mark can remove any of his wiki names. |
451 | + |
452 | + >>> browser.getControl(name='field.wiki').value = "" |
453 | + >>> browser.getControl(name='field.wikiname').value = '' |
454 | >>> browser.getControl('Remove', index=0).selected = True |
455 | >>> browser.getControl('Save Changes').click() |
456 | - >>> browser.url |
457 | - 'http://localhost/~mark/+editwikinames' |
458 | - >>> print_existing_wikinames(browser.contents) |
459 | + >>> print browser.url |
460 | + http://launchpad.dev/~mark |
461 | + >>> print "\n".join(get_feedback_messages(browser.contents)) |
462 | + >>> browser.open('http://launchpad.dev/~mark/+editwikinames') |
463 | + >>> print_existing_wikiurls(browser.contents) |
464 | https://wiki.ubuntu.com/MarkShuttleworth |
465 | |
466 | === modified file 'lib/lp/registry/templates/person-editwikinames.pt' |
467 | --- lib/lp/registry/templates/person-editwikinames.pt 2009-09-17 15:00:38 +0000 |
468 | +++ lib/lp/registry/templates/person-editwikinames.pt 2010-08-31 14:04:45 +0000 |
469 | @@ -6,14 +6,12 @@ |
470 | metal:use-macro="view/macro:page/main_only" |
471 | i18n:domain="launchpad" |
472 | > |
473 | - <body> |
474 | - |
475 | - <div metal:fill-slot="main"> |
476 | - <p tal:condition="view/error_message" |
477 | - tal:content="structure view/error_message/escapedtext" |
478 | - class="error message" /> |
479 | - |
480 | - <form name="edit_wikiname" action="" method="POST"> |
481 | +<body> |
482 | + |
483 | +<div metal:fill-slot="main"> |
484 | +<div metal:use-macro="context/@@launchpad_form/form"> |
485 | + |
486 | + <div metal:fill-slot="widgets"> |
487 | <table id="wikinames"> |
488 | |
489 | <tal:XXX condition="nothing"> |
490 | @@ -22,27 +20,19 @@ |
491 | # there's a bug preventing us from # updating our version of storm |
492 | # to trunk. |
493 | </tal:XXX> |
494 | - <tal:block condition="context/wiki_names/any"> |
495 | - |
496 | - <tr><td colspan="2"><h2>Wiki names</h2></td></tr> |
497 | - |
498 | - <tr> |
499 | - <td><label>Wiki base URL:</label></td> |
500 | - <td><label>Wiki name:</label></td> |
501 | - </tr> |
502 | - |
503 | - <tr tal:repeat="wiki context/wiki_names"> |
504 | - <td> |
505 | - <input type="text" style="margin-bottom: 0.5em;" |
506 | - tal:attributes="name string:wiki_${wiki/id}; |
507 | - value wiki/wiki" /> |
508 | - </td> |
509 | - <td> |
510 | - <input type="text" size="15" |
511 | - tal:attributes="name string:wikiname_${wiki/id}; |
512 | - value wiki/wikiname" /> |
513 | - </td> |
514 | - |
515 | + |
516 | + <tal:existing_wiki condition="context/wiki_names/any"> |
517 | + |
518 | + <tr> |
519 | + <td><label>Existing wiki names</label></td> |
520 | + </tr> |
521 | + |
522 | + <tr> |
523 | + <td><label>Wiki URL</label></td> |
524 | + </tr> |
525 | + |
526 | + <tr tal:repeat="wiki context/wiki_names" class="wikiurl"> |
527 | + <td tal:content="wiki/url"></td> |
528 | <td> |
529 | <label> |
530 | <input type="checkbox" value="Remove" |
531 | @@ -51,41 +41,35 @@ |
532 | </label> |
533 | </td> |
534 | </tr> |
535 | - |
536 | - </tal:block> |
537 | - |
538 | - <tr> |
539 | - <td colspan="2"><h2>New wiki name</h2></td> |
540 | - </tr> |
541 | - |
542 | - <tr> |
543 | - <td><label>Wiki Base URL:</label></td> |
544 | - <td><label>Wiki Name:</label></td> |
545 | - </tr> |
546 | - |
547 | - <tr> |
548 | - <td> |
549 | - <input name="newwiki" type="text" |
550 | - tal:attributes="value view/newwiki|nothing" /> |
551 | - </td> |
552 | - <td> |
553 | - <input name="newwikiname" type="text" size="15" |
554 | - tal:attributes="value view/newwikiname|nothing" /> |
555 | - </td> |
556 | - </tr> |
557 | - |
558 | - <tr> |
559 | - <td class="formHelp">Example: https://wiki.ubuntu.com/</td> |
560 | - <td class="formHelp">Example: YourName.</td> |
561 | - </tr> |
562 | - </table> |
563 | - |
564 | - <div class="actions"> |
565 | - <input type="submit" value="Save Changes" name="SAVE" /> |
566 | - or <a tal:attributes="href view/cancel_url">Cancel</a> |
567 | - </div> |
568 | - </form> |
569 | - </div> |
570 | + <tr style="height:2em;"><td></td></tr> |
571 | + </tal:existing_wiki> |
572 | + |
573 | + |
574 | + <tr> |
575 | + <td><label>New wiki name</label></td> |
576 | + </tr> |
577 | + |
578 | + <tal:widget define="widget nocall:view/widgets/wiki"> |
579 | + <metal:block use-macro="context/@@launchpad_form/widget_row" /> |
580 | + </tal:widget> |
581 | + |
582 | + <tr> |
583 | + <td class="formHelp">Example: https://wiki.ubuntu.com/</td> |
584 | + </tr> |
585 | + |
586 | + <tal:widget define="widget nocall:view/widgets/wikiname"> |
587 | + <metal:block use-macro="context/@@launchpad_form/widget_row" /> |
588 | + </tal:widget> |
589 | + |
590 | + <tr> |
591 | + <td class="formHelp">Example: YourName.</td> |
592 | + </tr> |
593 | + |
594 | + </table> |
595 | + </div> |
596 | + |
597 | +</div> |
598 | +</div> |
599 | |
600 | </body> |
601 | </html> |
Some things came up and were discussed on IRC. Else-wise, everything is fine (IRC transcript follows).
<gmb> bac, What's with the wacky indenting around line 93-96 of the diff?
<bac> gmb: dunno. let me look
gmb: dewhacked
<gmb> bac, Eyefankyoo
bac, ALso, line 109 needs to be indented by one more stop to be consistent.
<bac> gmb: i am going to open a but about URIWidget not respecting displayWidth, too.
gmb, yep, i caught that one too
<gmb> bac, Thanks x2
<bac> i think we have css that is harmful and should be removed, but i'm not certain of all of the areas affected.
<gmb> Fair neough.
* gmb switches lcoations; brb
<gmb> bac, I thought that ReST headers just had to have the underline rather than the overline, too.
e.g.
Person's wikinames
===============
(etc.)
<bac> gmb: really? i've often seen over/under
<gmb> bac, In that case, we have a problem with consistency
<bac> gmb: if you know better that's one less line...
<gmb> (Not least because I do underline only :))
<bac> gmb: duh
* gmb checks TestsStyleGuide
<gmb> bac, The example in TestsStyleGuide uses underline only, so let's standardise on that.
<bac> gmb: will do