Mercurial > p > roundup > code
comparison roundup/cgi/form_parser.py @ 6062:ed19b123a7ac
flake8 whitespace/formatting changes.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Fri, 17 Jan 2020 20:28:11 -0500 |
| parents | 936275dfe1fa |
| children | 408fd477761f |
comparison
equal
deleted
inserted
replaced
| 6061:82816000aef3 | 6062:ed19b123a7ac |
|---|---|
| 1 import re, mimetypes | 1 import re, mimetypes |
| 2 | 2 |
| 3 from roundup import hyperdb, date, password | 3 from roundup import hyperdb, date, password |
| 4 from roundup.cgi import templating | 4 from roundup.cgi import templating |
| 5 from roundup.cgi.exceptions import FormError | 5 from roundup.cgi.exceptions import FormError |
| 6 | |
| 6 | 7 |
| 7 class FormParser: | 8 class FormParser: |
| 8 # edit form variable handling (see unit tests) | 9 # edit form variable handling (see unit tests) |
| 9 FV_LABELS = r''' | 10 FV_LABELS = r''' |
| 10 ^( | 11 ^( |
| 199 if not hasattr(self, 'FV_SPECIAL'): | 200 if not hasattr(self, 'FV_SPECIAL'): |
| 200 # generate the regexp for handling special form values | 201 # generate the regexp for handling special form values |
| 201 classes = '|'.join(db.classes.keys()) | 202 classes = '|'.join(db.classes.keys()) |
| 202 # specials for parsePropsFromForm | 203 # specials for parsePropsFromForm |
| 203 # handle the various forms (see unit tests) | 204 # handle the various forms (see unit tests) |
| 204 self.FV_SPECIAL = re.compile(self.FV_LABELS%classes, re.VERBOSE) | 205 self.FV_SPECIAL = re.compile(self.FV_LABELS % classes, re.VERBOSE) |
| 205 self.FV_DESIGNATOR = re.compile(r'(%s)([-\d]+)'%classes) | 206 self.FV_DESIGNATOR = re.compile(r'(%s)([-\d]+)' % classes) |
| 206 | 207 |
| 207 # these indicate the default class / item | 208 # these indicate the default class / item |
| 208 default_cn = self.classname | 209 default_cn = self.classname |
| 209 default_cl = self.db.classes[default_cn] | 210 default_cl = self.db.classes[default_cn] |
| 210 default_nodeid = self.nodeid | 211 default_nodeid = self.nodeid |
| 245 cn = 'msg' | 246 cn = 'msg' |
| 246 cl = self.db.classes[cn] | 247 cl = self.db.classes[cn] |
| 247 nodeid = '-1' | 248 nodeid = '-1' |
| 248 propname = 'content' | 249 propname = 'content' |
| 249 all_links.append((default_cn, default_nodeid, 'messages', | 250 all_links.append((default_cn, default_nodeid, 'messages', |
| 250 [('msg', '-1')])) | 251 [('msg', '-1')])) |
| 251 have_note = 1 | 252 have_note = 1 |
| 252 elif d['file']: | 253 elif d['file']: |
| 253 # the special file field | 254 # the special file field |
| 254 cn = default_cn | 255 cn = default_cn |
| 255 cl = default_cl | 256 cl = default_cl |
| 284 if d['link']: | 285 if d['link']: |
| 285 value = [] | 286 value = [] |
| 286 for entry in self.extractFormList(form[key]): | 287 for entry in self.extractFormList(form[key]): |
| 287 m = self.FV_DESIGNATOR.match(entry) | 288 m = self.FV_DESIGNATOR.match(entry) |
| 288 if not m: | 289 if not m: |
| 289 raise FormError (self._('link "%(key)s" ' | 290 raise FormError(self._('link "%(key)s" ' |
| 290 'value "%(entry)s" not a designator') % locals()) | 291 'value "%(entry)s" not a designator') % locals()) |
| 291 value.append((m.group(1), m.group(2))) | 292 value.append((m.group(1), m.group(2))) |
| 292 | 293 |
| 293 # get details of linked class | 294 # get details of linked class |
| 294 lcn = m.group(1) | 295 lcn = m.group(1) |
| 302 got_props[(lcn, lnodeid)] = {} | 303 got_props[(lcn, lnodeid)] = {} |
| 303 | 304 |
| 304 # make sure the link property is valid | 305 # make sure the link property is valid |
| 305 if (not isinstance(propdef[propname], hyperdb.Multilink) and | 306 if (not isinstance(propdef[propname], hyperdb.Multilink) and |
| 306 not isinstance(propdef[propname], hyperdb.Link)): | 307 not isinstance(propdef[propname], hyperdb.Link)): |
| 307 raise FormError (self._('%(class)s %(property)s ' | 308 raise FormError(self._('%(class)s %(property)s ' |
| 308 'is not a link or multilink property') % { | 309 'is not a link or multilink property') % { |
| 309 'class':cn, 'property':propname}) | 310 'class':cn, 'property':propname}) |
| 310 | 311 |
| 311 all_links.append((cn, nodeid, propname, value)) | 312 all_links.append((cn, nodeid, propname, value)) |
| 312 continue | 313 continue |
| 313 | 314 |
| 314 # detect the special ":required" variable | 315 # detect the special ":required" variable |
| 315 if d['required']: | 316 if d['required']: |
| 316 for entry in self.extractFormList(form[key]): | 317 for entry in self.extractFormList(form[key]): |
| 317 m = self.FV_SPECIAL.match(entry) | 318 m = self.FV_SPECIAL.match(entry) |
| 318 if not m: | 319 if not m: |
| 319 raise FormError (self._('The form action claims to ' | 320 raise FormError(self._('The form action claims to ' |
| 320 'require property "%(property)s" ' | 321 'require property "%(property)s" ' |
| 321 'which doesn\'t exist') % { | 322 'which doesn\'t exist') % { |
| 322 'property':propname}) | 323 'property':propname}) |
| 323 if m.group('classname'): | 324 if m.group('classname'): |
| 324 this = (m.group('classname'), m.group('id')) | 325 this = (m.group('classname'), m.group('id')) |
| 336 mlaction = 'add' | 337 mlaction = 'add' |
| 337 | 338 |
| 338 # does the property exist? | 339 # does the property exist? |
| 339 if propname not in propdef: | 340 if propname not in propdef: |
| 340 if mlaction != 'set': | 341 if mlaction != 'set': |
| 341 raise FormError (self._('You have submitted a %(action)s ' | 342 raise FormError(self._('You have submitted a %(action)s ' |
| 342 'action for the property "%(property)s" ' | 343 'action for the property "%(property)s" ' |
| 343 'which doesn\'t exist') % { | 344 'which doesn\'t exist') % { |
| 344 'action': mlaction, 'property':propname}) | 345 'action': mlaction, 'property': propname}) |
| 345 # the form element is probably just something we don't care | 346 # the form element is probably just something we don't care |
| 346 # about - ignore it | 347 # about - ignore it |
| 347 continue | 348 continue |
| 348 proptype = propdef[propname] | 349 proptype = propdef[propname] |
| 349 | 350 |
| 361 elif isinstance(proptype, hyperdb.Multilink): | 362 elif isinstance(proptype, hyperdb.Multilink): |
| 362 value = self.extractFormList(value) | 363 value = self.extractFormList(value) |
| 363 else: | 364 else: |
| 364 # multiple values are not OK | 365 # multiple values are not OK |
| 365 if isinstance(value, type([])): | 366 if isinstance(value, type([])): |
| 366 raise FormError (self._('You have submitted more than one ' | 367 raise FormError(self._('You have submitted more than one ' |
| 367 'value for the %s property') % propname) | 368 'value for the %s property') % propname) |
| 368 # value might be a single file upload | 369 # value might be a single file upload |
| 369 if not getattr(value, 'filename', None): | 370 if not getattr(value, 'filename', None): |
| 370 value = value.value.strip() | 371 value = value.value.strip() |
| 371 | 372 |
| 386 for key, d in matches: | 387 for key, d in matches: |
| 387 if d['confirm'] and d['propname'] == propname: | 388 if d['confirm'] and d['propname'] == propname: |
| 388 confirm = form[key] | 389 confirm = form[key] |
| 389 break | 390 break |
| 390 else: | 391 else: |
| 391 raise FormError (self._('Password and confirmation text ' | 392 raise FormError(self._('Password and confirmation text ' |
| 392 'do not match')) | 393 'do not match')) |
| 393 if isinstance(confirm, type([])): | 394 if isinstance(confirm, type([])): |
| 394 raise FormError (self._('You have submitted more than one ' | 395 raise FormError(self._('You have submitted more than one ' |
| 395 'value for the %s property') % propname) | 396 'value for the %s property') % propname) |
| 396 if value != confirm.value: | 397 if value != confirm.value: |
| 397 raise FormError (self._('Password and confirmation text ' | 398 raise FormError(self._('Password and confirmation text ' |
| 398 'do not match')) | 399 'do not match')) |
| 399 try: | 400 try: |
| 400 value = password.Password(value, scheme = proptype.scheme, | 401 value = password.Password(value, scheme=proptype.scheme, |
| 401 config=self.db.config) | 402 config=self.db.config) |
| 402 except hyperdb.HyperdbValueError as msg: | 403 except hyperdb.HyperdbValueError as msg: |
| 403 raise FormError (msg) | 404 raise FormError(msg) |
| 404 elif d['file']: | 405 elif d['file']: |
| 405 # This needs to be a Multilink and is checked above | 406 # This needs to be a Multilink and is checked above |
| 406 fcn = 'file' | 407 fcn = 'file' |
| 407 fcl = self.db.classes[fcn] | 408 fcl = self.db.classes[fcn] |
| 408 fpropname = 'content' | 409 fpropname = 'content' |
| 410 all_propdef[fcn] = fcl.getprops() | 411 all_propdef[fcn] = fcl.getprops() |
| 411 fpropdef = all_propdef[fcn] | 412 fpropdef = all_propdef[fcn] |
| 412 have_file = [] | 413 have_file = [] |
| 413 for n, v in enumerate(value): | 414 for n, v in enumerate(value): |
| 414 if not hasattr(v, 'filename'): | 415 if not hasattr(v, 'filename'): |
| 415 raise FormError (self._('Not a file attachment')) | 416 raise FormError(self._('Not a file attachment')) |
| 416 # skip if the upload is empty | 417 # skip if the upload is empty |
| 417 if not v.filename: | 418 if not v.filename: |
| 418 continue | 419 continue |
| 419 fnodeid = str (-(n+1)) | 420 fnodeid = str(-(n+1)) |
| 420 have_file.append(fnodeid) | 421 have_file.append(fnodeid) |
| 421 fthis = (fcn, fnodeid) | 422 fthis = (fcn, fnodeid) |
| 422 if fthis not in all_props: | 423 if fthis not in all_props: |
| 423 all_props[fthis] = {} | 424 all_props[fthis] = {} |
| 424 fprops = all_props[fthis] | 425 fprops = all_props[fthis] |
| 425 all_links.append((cn, nodeid, 'files', [('file', fnodeid)])) | 426 all_links.append((cn, nodeid, 'files', |
| 427 [('file', fnodeid)])) | |
| 426 | 428 |
| 427 fprops['content'] = self.parse_file(fpropdef, fprops, v) | 429 fprops['content'] = self.parse_file(fpropdef, fprops, v) |
| 428 value = None | 430 value = None |
| 429 nodeid = None | 431 nodeid = None |
| 430 elif isinstance(proptype, hyperdb.Multilink): | 432 elif isinstance(proptype, hyperdb.Multilink): |
| 431 # convert input to list of ids | 433 # convert input to list of ids |
| 432 try: | 434 try: |
| 433 l = hyperdb.rawToHyperdb(self.db, cl, nodeid, | 435 l = hyperdb.rawToHyperdb(self.db, cl, nodeid, |
| 434 propname, value) | 436 propname, value) |
| 435 except hyperdb.HyperdbValueError as msg: | 437 except hyperdb.HyperdbValueError as msg: |
| 436 raise FormError (msg) | 438 raise FormError(msg) |
| 437 | 439 |
| 438 # now use that list of ids to modify the multilink | 440 # now use that list of ids to modify the multilink |
| 439 if mlaction == 'set': | 441 if mlaction == 'set': |
| 440 value = l | 442 value = l |
| 441 else: | 443 else: |
| 453 # the list | 455 # the list |
| 454 for entry in l: | 456 for entry in l: |
| 455 try: | 457 try: |
| 456 existing.remove(entry) | 458 existing.remove(entry) |
| 457 except ValueError: | 459 except ValueError: |
| 458 raise FormError (self._('property ' | 460 raise FormError(self._('property ' |
| 459 '"%(propname)s": "%(value)s" ' | 461 '"%(propname)s": "%(value)s" ' |
| 460 'not currently in list') % { | 462 'not currently in list') % { |
| 461 'propname': propname, 'value': entry}) | 463 'propname': propname, 'value': entry}) |
| 462 else: | 464 else: |
| 463 # add - easy, just don't dupe | 465 # add - easy, just don't dupe |
| 480 hasattr(value, 'filename') and | 482 hasattr(value, 'filename') and |
| 481 value.filename is not None): | 483 value.filename is not None): |
| 482 value = self.parse_file(propdef, props, value) | 484 value = self.parse_file(propdef, props, value) |
| 483 else: | 485 else: |
| 484 value = hyperdb.rawToHyperdb(self.db, cl, nodeid, | 486 value = hyperdb.rawToHyperdb(self.db, cl, nodeid, |
| 485 propname, value) | 487 propname, value) |
| 486 except hyperdb.HyperdbValueError as msg: | 488 except hyperdb.HyperdbValueError as msg: |
| 487 raise FormError (msg) | 489 raise FormError(msg) |
| 488 | 490 |
| 489 # register that we got this property | 491 # register that we got this property |
| 490 if isinstance(proptype, hyperdb.Multilink): | 492 if isinstance(proptype, hyperdb.Multilink): |
| 491 if value != []: | 493 if value != []: |
| 492 got_props[this][propname] = 1 | 494 got_props[this][propname] = 1 |
| 514 existing.sort(key=int) | 516 existing.sort(key=int) |
| 515 | 517 |
| 516 # "missing" existing values may not be None | 518 # "missing" existing values may not be None |
| 517 if not existing: | 519 if not existing: |
| 518 if isinstance(proptype, hyperdb.String): | 520 if isinstance(proptype, hyperdb.String): |
| 519 # some backends store "missing" Strings as empty strings | 521 # some backends store "missing" Strings as |
| 522 # empty strings | |
| 520 if existing == self.db.BACKEND_MISSING_STRING: | 523 if existing == self.db.BACKEND_MISSING_STRING: |
| 521 existing = None | 524 existing = None |
| 522 elif isinstance(proptype, hyperdb.Number) or isinstance(proptype, hyperdb.Integer): | 525 elif isinstance(proptype, hyperdb.Number) or \ |
| 526 isinstance(proptype, hyperdb.Integer): | |
| 523 # some backends store "missing" Numbers as 0 :( | 527 # some backends store "missing" Numbers as 0 :( |
| 524 if existing == self.db.BACKEND_MISSING_NUMBER: | 528 if existing == self.db.BACKEND_MISSING_NUMBER: |
| 525 existing = None | 529 existing = None |
| 526 elif isinstance(proptype, hyperdb.Boolean): | 530 elif isinstance(proptype, hyperdb.Boolean): |
| 527 # likewise Booleans | 531 # likewise Booleans |
| 581 ) % { | 585 ) % { |
| 582 'class': self._(thing[0]), | 586 'class': self._(thing[0]), |
| 583 'property': ', '.join(map(self.gettext, required)) | 587 'property': ', '.join(map(self.gettext, required)) |
| 584 }) | 588 }) |
| 585 if s: | 589 if s: |
| 586 raise FormError ('\n'.join(s)) | 590 raise FormError('\n'.join(s)) |
| 587 | 591 |
| 588 # When creating a FileClass node, it should have a non-empty content | 592 # When creating a FileClass node, it should have a non-empty content |
| 589 # property to be created. When editing a FileClass node, it should | 593 # property to be created. When editing a FileClass node, it should |
| 590 # either have a non-empty content property or no property at all. In | 594 # either have a non-empty content property or no property at all. In |
| 591 # the latter case, nothing will change. | 595 # the latter case, nothing will change. |
| 608 if id is not None and \ | 612 if id is not None and \ |
| 609 not id.startswith('-') and \ | 613 not id.startswith('-') and \ |
| 610 not props['content']: | 614 not props['content']: |
| 611 # This is an existing file with emtpy content | 615 # This is an existing file with emtpy content |
| 612 # value in the form. | 616 # value in the form. |
| 613 del props ['content'] | 617 del props['content'] |
| 614 else: | 618 else: |
| 615 # this is a new file without any content property. | 619 # this is a new file without any content property. |
| 616 if id is not None and id.startswith('-'): | 620 if id is not None and id.startswith('-'): |
| 617 del all_props[(cn, id)] | 621 del all_props[(cn, id)] |
| 618 # if this is a new file with content (even 0 length content) | 622 # if this is a new file with content (even 0 length content) |
| 637 | 641 |
| 638 def extractFormList(self, value): | 642 def extractFormList(self, value): |
| 639 ''' Extract a list of values from the form value. | 643 ''' Extract a list of values from the form value. |
| 640 | 644 |
| 641 It may be one of: | 645 It may be one of: |
| 642 [MiniFieldStorage('value'), MiniFieldStorage('value','value',...), ...] | 646 [MiniFieldStorage('value'), |
| 647 MiniFieldStorage('value','value',...), ...] | |
| 643 MiniFieldStorage('value,value,...') | 648 MiniFieldStorage('value,value,...') |
| 644 MiniFieldStorage('value') | 649 MiniFieldStorage('value') |
| 645 ''' | 650 ''' |
| 646 # multiple values are OK | 651 # multiple values are OK |
| 647 if isinstance(value, type([])): | 652 if isinstance(value, type([])): |
