Merge lp:~bac/launchpad/bug-39212 into lp:launchpad
- bug-39212
- Merge into devel
| Status: | Merged | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| Approved by: | Graham Binns on 2010-08-31 | ||||||||
| 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 | 2010-08-31 | Approve on 2010-08-31 |
|
Review via email:
|
|||
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