Mercurial > p > roundup > code
comparison roundup/hyperdb.py @ 1905:dc43e339e607
Centralised conversion of user-input data to hyperdb values
(bug [SF#802405], bug [SF#817217], rfe [SF#816994])
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Tue, 11 Nov 2003 00:35:14 +0000 |
| parents | 969a14faf707 |
| children | 3bdd34547fa7 |
comparison
equal
deleted
inserted
replaced
| 1904:9445caec3ff5 | 1905:dc43e339e607 |
|---|---|
| 13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | 13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| 14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" | 14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" |
| 15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, | 15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, |
| 16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | 16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
| 17 # | 17 # |
| 18 # $Id: hyperdb.py,v 1.90 2003-10-24 22:52:48 richard Exp $ | 18 # $Id: hyperdb.py,v 1.91 2003-11-11 00:35:13 richard Exp $ |
| 19 | 19 |
| 20 """ | 20 """ |
| 21 Hyperdatabase implementation, especially field types. | 21 Hyperdatabase implementation, especially field types. |
| 22 """ | 22 """ |
| 23 | 23 |
| 569 | 569 |
| 570 def index(self, nodeid): | 570 def index(self, nodeid): |
| 571 '''Add (or refresh) the node to search indexes | 571 '''Add (or refresh) the node to search indexes |
| 572 ''' | 572 ''' |
| 573 raise NotImplementedError | 573 raise NotImplementedError |
| 574 | |
| 575 class HyperdbValueError(ValueError): | |
| 576 ''' Error converting a raw value into a Hyperdb value ''' | |
| 577 pass | |
| 578 | |
| 579 def convertLinkValue(db, propname, prop, value, idre=re.compile('\d+')): | |
| 580 ''' Convert the link value (may be id or key value) to an id value. ''' | |
| 581 linkcl = db.classes[prop.classname] | |
| 582 if not idre.match(value): | |
| 583 if linkcl.getkey(): | |
| 584 try: | |
| 585 value = linkcl.lookup(value) | |
| 586 except KeyError, message: | |
| 587 raise HyperdbValueError, 'property %s: %r is not a %s.'%( | |
| 588 propname, value, prop.classname) | |
| 589 else: | |
| 590 raise HyperdbValueError, 'you may only enter ID values '\ | |
| 591 'for property %s'%propname | |
| 592 return value | |
| 593 | |
| 594 def fixNewlines(text): | |
| 595 ''' Homogenise line endings. | |
| 596 | |
| 597 Different web clients send different line ending values, but | |
| 598 other systems (eg. email) don't necessarily handle those line | |
| 599 endings. Our solution is to convert all line endings to LF. | |
| 600 ''' | |
| 601 text = text.replace('\r\n', '\n') | |
| 602 return text.replace('\r', '\n') | |
| 603 | |
| 604 def rawToHyperdb(db, klass, itemid, propname, value, | |
| 605 pwre=re.compile(r'{(\w+)}(.+)')): | |
| 606 ''' Convert the raw (user-input) value to a hyperdb-storable value. The | |
| 607 value is for the "propname" property on itemid (may be None for a | |
| 608 new item) of "klass" in "db". | |
| 609 | |
| 610 The value is usually a string, but in the case of multilink inputs | |
| 611 it may be either a list of strings or a string with comma-separated | |
| 612 values. | |
| 613 ''' | |
| 614 properties = klass.getprops() | |
| 615 | |
| 616 # ensure it's a valid property name | |
| 617 propname = propname.strip() | |
| 618 try: | |
| 619 proptype = properties[propname] | |
| 620 except KeyError: | |
| 621 raise HyperdbValueError, '%r is not a property of %s'%(propname, | |
| 622 klass.classname) | |
| 623 | |
| 624 # if we got a string, strip it now | |
| 625 if isinstance(value, type('')): | |
| 626 value = value.strip() | |
| 627 | |
| 628 # convert the input value to a real property value | |
| 629 if isinstance(proptype, String): | |
| 630 # fix the CRLF/CR -> LF stuff | |
| 631 value = fixNewlines(value) | |
| 632 if isinstance(proptype, Password): | |
| 633 m = pwre.match(value) | |
| 634 if m: | |
| 635 # password is being given to us encrypted | |
| 636 p = password.Password() | |
| 637 p.scheme = m.group(1) | |
| 638 if p.scheme not in 'SHA crypt plaintext'.split(): | |
| 639 raise HyperdbValueError, 'property %s: unknown encryption '\ | |
| 640 'scheme %r'%(propname, p.scheme) | |
| 641 p.password = m.group(2) | |
| 642 value = p | |
| 643 else: | |
| 644 try: | |
| 645 value = password.Password(value) | |
| 646 except password.PasswordValueError, message: | |
| 647 raise HyperdbValueError, 'property %s: %s'%(propname, message) | |
| 648 elif isinstance(proptype, Date): | |
| 649 try: | |
| 650 tz = db.getUserTimezone() | |
| 651 value = date.Date(value).local(tz) | |
| 652 except ValueError, message: | |
| 653 raise HyperdbValueError, 'property %s: %r is an invalid '\ | |
| 654 'date (%s)'%(propname, value, message) | |
| 655 elif isinstance(proptype, Interval): | |
| 656 try: | |
| 657 value = date.Interval(value) | |
| 658 except ValueError, message: | |
| 659 raise HyperdbValueError, 'property %s: %r is an invalid '\ | |
| 660 'date interval (%s)'%(propname, value, message) | |
| 661 elif isinstance(proptype, Link): | |
| 662 if value == '-1' or not value: | |
| 663 value = None | |
| 664 else: | |
| 665 value = convertLinkValue(db, propname, proptype, value) | |
| 666 | |
| 667 elif isinstance(proptype, Multilink): | |
| 668 # get the current item value if it's not a new item | |
| 669 if itemid and not itemid.startswith('-'): | |
| 670 curvalue = klass.get(itemid, propname) | |
| 671 else: | |
| 672 curvalue = [] | |
| 673 | |
| 674 # if the value is a comma-separated string then split it now | |
| 675 if isinstance(value, type('')): | |
| 676 value = value.split(',') | |
| 677 | |
| 678 # handle each add/remove in turn | |
| 679 # keep an extra list for all items that are | |
| 680 # definitely in the new list (in case of e.g. | |
| 681 # <propname>=A,+B, which should replace the old | |
| 682 # list with A,B) | |
| 683 set = 1 | |
| 684 newvalue = [] | |
| 685 for item in value: | |
| 686 item = item.strip() | |
| 687 | |
| 688 # skip blanks | |
| 689 if not item: continue | |
| 690 | |
| 691 # handle +/- | |
| 692 remove = 0 | |
| 693 if item.startswith('-'): | |
| 694 remove = 1 | |
| 695 item = item[1:] | |
| 696 set = 0 | |
| 697 elif item.startswith('+'): | |
| 698 item = item[1:] | |
| 699 set = 0 | |
| 700 | |
| 701 # look up the value | |
| 702 itemid = convertLinkValue(db, propname, proptype, item) | |
| 703 | |
| 704 # perform the add/remove | |
| 705 if remove: | |
| 706 try: | |
| 707 curvalue.remove(itemid) | |
| 708 except ValueError: | |
| 709 raise HyperdbValueError, 'property %s: %r is not ' \ | |
| 710 'currently an element'%(propname, item) | |
| 711 else: | |
| 712 newvalue.append(itemid) | |
| 713 if itemid not in curvalue: | |
| 714 curvalue.append(itemid) | |
| 715 | |
| 716 # that's it, set the new Multilink property value, | |
| 717 # or overwrite it completely | |
| 718 if set: | |
| 719 value = newvalue | |
| 720 else: | |
| 721 value = curvalue | |
| 722 | |
| 723 # TODO: one day, we'll switch to numeric ids and this will be | |
| 724 # unnecessary :( | |
| 725 value = [int(x) for x in value] | |
| 726 value.sort() | |
| 727 value = [str(x) for x in value] | |
| 728 elif isinstance(proptype, Boolean): | |
| 729 value = value.strip() | |
| 730 value = value.lower() in ('yes', 'true', 'on', '1') | |
| 731 elif isinstance(proptype, Number): | |
| 732 value = value.strip() | |
| 733 try: | |
| 734 value = float(value) | |
| 735 except ValueError: | |
| 736 raise HyperdbValueError, 'property %s: %r is not a number'%( | |
| 737 propname, value) | |
| 738 return value | |
| 574 | 739 |
| 575 class FileClass: | 740 class FileClass: |
| 576 ''' A class that requires the "content" property and stores it on | 741 ''' A class that requires the "content" property and stores it on |
| 577 disk. | 742 disk. |
| 578 ''' | 743 ''' |
