Mercurial > p > roundup > code
comparison roundup/cgi/client.py @ 1003:f89b8d32291b
Hack hack hack...
. Implemented security assertion idea punted to mailing list (pretty easy to
back out if someone comes up with a better idea) so editing "my details"
works again. Rationalised and cleaned up the actions in any case.
. fixed some more display issues (stuff appearing when it should and shouldn't)
. trying a nicer colouring scheme for the top level page
. handle no grouping being specified
. fixed journaltag so the logged-in user is journalled, not admin!
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Sun, 01 Sep 2002 12:18:41 +0000 |
| parents | 1798d2fa9fec |
| children | 5f12d3259f31 |
comparison
equal
deleted
inserted
replaced
| 1002:1798d2fa9fec | 1003:f89b8d32291b |
|---|---|
| 1 # $Id: client.py,v 1.2 2002-09-01 04:32:30 richard Exp $ | 1 # $Id: client.py,v 1.3 2002-09-01 12:18:40 richard Exp $ |
| 2 | 2 |
| 3 __doc__ = """ | 3 __doc__ = """ |
| 4 WWW request handler (also used in the stand-alone server). | 4 WWW request handler (also used in the stand-alone server). |
| 5 """ | 5 """ |
| 6 | 6 |
| 169 if user == 'anonymous': | 169 if user == 'anonymous': |
| 170 self.make_user_anonymous() | 170 self.make_user_anonymous() |
| 171 else: | 171 else: |
| 172 self.user = user | 172 self.user = user |
| 173 | 173 |
| 174 # reopen the database as the correct user | |
| 175 self.opendb(self.user) | |
| 176 | |
| 174 def determine_context(self, dre=re.compile(r'([^\d]+)(\d+)')): | 177 def determine_context(self, dre=re.compile(r'([^\d]+)(\d+)')): |
| 175 ''' Determine the context of this page: | 178 ''' Determine the context of this page: |
| 176 | 179 |
| 177 home (default if no url is given) | 180 home (default if no url is given) |
| 178 classname | 181 classname |
| 289 # determine_context | 292 # determine_context |
| 290 return self.template(self.template_name) | 293 return self.template(self.template_name) |
| 291 | 294 |
| 292 # these are the actions that are available | 295 # these are the actions that are available |
| 293 actions = { | 296 actions = { |
| 294 'edit': 'edititem_action', | 297 'edit': 'editItemAction', |
| 295 'new': 'newitem_action', | 298 'new': 'newItemAction', |
| 296 'login': 'login_action', | 299 'login': 'login_action', |
| 297 'logout': 'logout_action', | 300 'logout': 'logout_action', |
| 298 'register': 'register_action', | 301 'register': 'register_action', |
| 299 'search': 'search_action', | 302 'search': 'searchAction', |
| 300 } | 303 } |
| 301 def handle_action(self): | 304 def handle_action(self): |
| 302 ''' Determine whether there should be an _action called. | 305 ''' Determine whether there should be an _action called. |
| 303 | 306 |
| 304 The action is defined by the form variable :action which | 307 The action is defined by the form variable :action which |
| 305 identifies the method on this object to call. The four basic | 308 identifies the method on this object to call. The four basic |
| 306 actions are defined in the "actions" dictionary on this class: | 309 actions are defined in the "actions" dictionary on this class: |
| 307 "edit" -> self.edititem_action | 310 "edit" -> self.editItemAction |
| 308 "new" -> self.newitem_action | 311 "new" -> self.newItemAction |
| 309 "login" -> self.login_action | 312 "login" -> self.login_action |
| 310 "logout" -> self.logout_action | 313 "logout" -> self.logout_action |
| 311 "register" -> self.register_action | 314 "register" -> self.register_action |
| 312 "search" -> self.search_action | 315 "search" -> self.searchAction |
| 313 | 316 |
| 314 ''' | 317 ''' |
| 315 if not self.form.has_key(':action'): | 318 if not self.form.has_key(':action'): |
| 316 return None | 319 return None |
| 317 try: | 320 try: |
| 452 # construct the logout cookie | 455 # construct the logout cookie |
| 453 now = Cookie._getdate() | 456 now = Cookie._getdate() |
| 454 path = '/'.join((self.env['SCRIPT_NAME'], self.env['INSTANCE_NAME'], | 457 path = '/'.join((self.env['SCRIPT_NAME'], self.env['INSTANCE_NAME'], |
| 455 '')) | 458 '')) |
| 456 self.header(headers={'Set-Cookie': | 459 self.header(headers={'Set-Cookie': |
| 457 'roundup_user=deleted; Max-Age=0; expires=%s; Path=%s;'%(now, path)}) | 460 'roundup_user=deleted; Max-Age=0; expires=%s; Path=%s;'%(now, path)}) |
| 458 # 'Location': self.db.config.DEFAULT_VIEW}, response=301) | 461 |
| 459 | 462 # Let the user know what's going on |
| 460 # suboptimal, but will do for now | |
| 461 self.ok_message.append(_('You are logged out')) | 463 self.ok_message.append(_('You are logged out')) |
| 462 #raise Redirect, None | |
| 463 | 464 |
| 464 def register_action(self): | 465 def register_action(self): |
| 465 '''Attempt to create a new user based on the contents of the form | 466 '''Attempt to create a new user based on the contents of the form |
| 466 and then set the cookie. | 467 and then set the cookie. |
| 467 | 468 |
| 495 self.set_cookie(self.user, password) | 496 self.set_cookie(self.user, password) |
| 496 | 497 |
| 497 # nice message | 498 # nice message |
| 498 self.ok_message.append(_('You are now registered, welcome!')) | 499 self.ok_message.append(_('You are now registered, welcome!')) |
| 499 | 500 |
| 500 def edititem_action(self): | 501 def editItemAction(self): |
| 501 ''' Perform an edit of an item in the database. | 502 ''' Perform an edit of an item in the database. |
| 502 | 503 |
| 503 Some special form elements: | 504 Some special form elements: |
| 504 | 505 |
| 505 :link=designator:property | 506 :link=designator:property |
| 514 "files" property. Attach the file to the message created from | 515 "files" property. Attach the file to the message created from |
| 515 the __note if it's supplied. | 516 the __note if it's supplied. |
| 516 ''' | 517 ''' |
| 517 cl = self.db.classes[self.classname] | 518 cl = self.db.classes[self.classname] |
| 518 | 519 |
| 520 # parse the props from the form | |
| 521 try: | |
| 522 props = parsePropsFromForm(self.db, cl, self.form, self.nodeid) | |
| 523 except (ValueError, KeyError), message: | |
| 524 self.error_message.append(_('Error: ') + str(message)) | |
| 525 return | |
| 526 | |
| 519 # check permission | 527 # check permission |
| 520 userid = self.db.user.lookup(self.user) | 528 if not self.editItemPermission(props): |
| 521 if not self.db.security.hasPermission('Edit', userid, self.classname): | |
| 522 self.error_message.append( | 529 self.error_message.append( |
| 523 _('You do not have permission to edit %(classname)s' % | 530 _('You do not have permission to edit %(classname)s'% |
| 524 self.__dict__)) | 531 self.__dict__)) |
| 525 return | 532 return |
| 526 | 533 |
| 527 # perform the edit | 534 # perform the edit |
| 528 try: | 535 try: |
| 529 props = parsePropsFromForm(self.db, cl, self.form, self.nodeid) | |
| 530 | |
| 531 # make changes to the node | 536 # make changes to the node |
| 532 props = self._changenode(props) | 537 props = self._changenode(props) |
| 533 | |
| 534 # handle linked nodes | 538 # handle linked nodes |
| 535 self._post_editnode(self.nodeid) | 539 self._post_editnode(self.nodeid) |
| 536 | |
| 537 except (ValueError, KeyError), message: | 540 except (ValueError, KeyError), message: |
| 538 self.error_message.append(_('Error: ') + str(message)) | 541 self.error_message.append(_('Error: ') + str(message)) |
| 539 return | 542 return |
| 540 | 543 |
| 541 # commit now that all the tricky stuff is done | 544 # commit now that all the tricky stuff is done |
| 554 | 557 |
| 555 # redirect to the item's edit page | 558 # redirect to the item's edit page |
| 556 raise Redirect, '%s/%s%s?:ok_message=%s'%(self.base, self.classname, | 559 raise Redirect, '%s/%s%s?:ok_message=%s'%(self.base, self.classname, |
| 557 self.nodeid, urllib.quote(message)) | 560 self.nodeid, urllib.quote(message)) |
| 558 | 561 |
| 559 def newitem_action(self): | 562 def editItemPermission(self, props): |
| 563 ''' Determine whether the user has permission to edit this item. | |
| 564 | |
| 565 Base behaviour is to check the user can edit this class. If we're | |
| 566 editing the "user" class, users are allowed to edit their own | |
| 567 details. Unless it's the "roles" property, which requires the | |
| 568 special Permission "Web Roles". | |
| 569 ''' | |
| 570 # if this is a user node and the user is editing their own node, then | |
| 571 # we're OK | |
| 572 has = self.db.security.hasPermission | |
| 573 if self.classname == 'user': | |
| 574 # reject if someone's trying to edit "roles" and doesn't have the | |
| 575 # right permission. | |
| 576 if props.has_key('roles') and not has('Web Roles', self.userid, | |
| 577 'user'): | |
| 578 return 0 | |
| 579 # if the item being edited is the current user, we're ok | |
| 580 if self.nodeid == self.userid: | |
| 581 return 1 | |
| 582 if not self.db.security.hasPermission('Edit', self.userid, | |
| 583 self.classname): | |
| 584 return 0 | |
| 585 return 1 | |
| 586 | |
| 587 def newItemAction(self): | |
| 560 ''' Add a new item to the database. | 588 ''' Add a new item to the database. |
| 561 | 589 |
| 562 This follows the same form as the edititem_action | 590 This follows the same form as the editItemAction |
| 563 ''' | 591 ''' |
| 564 # check permission | 592 cl = self.db.classes[self.classname] |
| 565 userid = self.db.user.lookup(self.user) | 593 |
| 566 if not self.db.security.hasPermission('Edit', userid, self.classname): | 594 # parse the props from the form |
| 595 try: | |
| 596 props = parsePropsFromForm(self.db, cl, self.form, self.nodeid) | |
| 597 except (ValueError, KeyError), message: | |
| 598 self.error_message.append(_('Error: ') + str(message)) | |
| 599 return | |
| 600 | |
| 601 if not self.newItemPermission(props): | |
| 567 self.error_message.append( | 602 self.error_message.append( |
| 568 _('You do not have permission to create %s' %self.classname)) | 603 _('You do not have permission to create %s' %self.classname)) |
| 569 | 604 |
| 570 # XXX | 605 # XXX |
| 571 # cl = self.db.classes[cn] | 606 # cl = self.db.classes[cn] |
| 576 # else: | 611 # else: |
| 577 # xtra = '' | 612 # xtra = '' |
| 578 | 613 |
| 579 try: | 614 try: |
| 580 # do the create | 615 # do the create |
| 581 nid = self._createnode() | 616 nid = self._createnode(props) |
| 582 | 617 |
| 583 # handle linked nodes | 618 # handle linked nodes |
| 584 self._post_editnode(nid) | 619 self._post_editnode(nid) |
| 585 | 620 |
| 586 # commit now that all the tricky stuff is done | 621 # commit now that all the tricky stuff is done |
| 604 | 639 |
| 605 # redirect to the new item's page | 640 # redirect to the new item's page |
| 606 raise Redirect, '%s/%s%s?:ok_message=%s'%(self.base, self.classname, | 641 raise Redirect, '%s/%s%s?:ok_message=%s'%(self.base, self.classname, |
| 607 nid, urllib.quote(message)) | 642 nid, urllib.quote(message)) |
| 608 | 643 |
| 609 def genericedit_action(self): | 644 def newItemPermission(self, props): |
| 645 ''' Determine whether the user has permission to create (edit) this | |
| 646 item. | |
| 647 | |
| 648 Base behaviour is to check the user can edit this class. No | |
| 649 additional property checks are made. Additionally, new user items | |
| 650 may be created if the user has the "Web Registration" Permission. | |
| 651 ''' | |
| 652 has = self.db.security.hasPermission | |
| 653 if self.classname == 'user' and has('Web Registration', self.userid, | |
| 654 'user'): | |
| 655 return 1 | |
| 656 if not has('Edit', self.userid, self.classname): | |
| 657 return 0 | |
| 658 return 1 | |
| 659 | |
| 660 def genericEditAction(self): | |
| 610 ''' Performs an edit of all of a class' items in one go. | 661 ''' Performs an edit of all of a class' items in one go. |
| 611 | 662 |
| 612 The "rows" CGI var defines the CSV-formatted entries for the | 663 The "rows" CGI var defines the CSV-formatted entries for the |
| 613 class. New nodes are identified by the ID 'X' (or any other | 664 class. New nodes are identified by the ID 'X' (or any other |
| 614 non-existent ID) and removed lines are retired. | 665 non-existent ID) and removed lines are retired. |
| 615 ''' | 666 ''' |
| 616 userid = self.db.user.lookup(self.user) | 667 # generic edit is per-class only |
| 617 if not self.db.security.hasPermission('Edit', userid, self.classname): | 668 if not self.genericEditPermission(): |
| 618 raise Unauthorised, _("You do not have permission to access"\ | 669 self.error_message.append( |
| 619 " %(action)s.")%{'action': self.classname} | 670 _('You do not have permission to edit %s' %self.classname)) |
| 620 cl = self.db.classes[self.classname] | |
| 621 idlessprops = cl.getprops(protected=0).keys() | |
| 622 props = ['id'] + idlessprops | |
| 623 | 671 |
| 624 # get the CSV module | 672 # get the CSV module |
| 625 try: | 673 try: |
| 626 import csv | 674 import csv |
| 627 except ImportError: | 675 except ImportError: |
| 628 self.error_message.append(_( | 676 self.error_message.append(_( |
| 629 'Sorry, you need the csv module to use this function.<br>\n' | 677 'Sorry, you need the csv module to use this function.<br>\n' |
| 630 'Get it from: <a href="http://www.object-craft.com.au/projects/csv/">http://www.object-craft.com.au/projects/csv/')) | 678 'Get it from: <a href="http://www.object-craft.com.au/projects/csv/">http://www.object-craft.com.au/projects/csv/')) |
| 631 return | 679 return |
| 680 | |
| 681 cl = self.db.classes[self.classname] | |
| 682 idlessprops = cl.getprops(protected=0).keys() | |
| 683 props = ['id'] + idlessprops | |
| 632 | 684 |
| 633 # do the edit | 685 # do the edit |
| 634 rows = self.form['rows'].value.splitlines() | 686 rows = self.form['rows'].value.splitlines() |
| 635 p = csv.parser() | 687 p = csv.parser() |
| 636 found = {} | 688 found = {} |
| 678 | 730 |
| 679 # redirect to the class' edit page | 731 # redirect to the class' edit page |
| 680 raise Redirect, '%s/%s?:ok_message=%s'%(self.base, self.classname, | 732 raise Redirect, '%s/%s?:ok_message=%s'%(self.base, self.classname, |
| 681 urllib.quote(message)) | 733 urllib.quote(message)) |
| 682 | 734 |
| 683 def _changenode(self, props): | 735 def genericEditPermission(self): |
| 684 ''' change the node based on the contents of the form | 736 ''' Determine whether the user has permission to edit this class. |
| 685 ''' | 737 |
| 686 cl = self.db.classes[self.classname] | 738 Base behaviour is to check the user can edit this class. |
| 687 | 739 ''' |
| 688 # create the message | 740 if not self.db.security.hasPermission('Edit', self.userid, |
| 689 message, files = self._handle_message() | 741 self.classname): |
| 690 if message: | 742 return 0 |
| 691 props['messages'] = cl.get(self.nodeid, 'messages') + [message] | 743 return 1 |
| 692 if files: | 744 |
| 693 props['files'] = cl.get(self.nodeid, 'files') + files | 745 def searchAction(self): |
| 694 | |
| 695 # make the changes | |
| 696 return cl.set(self.nodeid, **props) | |
| 697 | |
| 698 def _createnode(self): | |
| 699 ''' create a node based on the contents of the form | |
| 700 ''' | |
| 701 cl = self.db.classes[self.classname] | |
| 702 props = parsePropsFromForm(self.db, cl, self.form) | |
| 703 | |
| 704 # check for messages and files | |
| 705 message, files = self._handle_message() | |
| 706 if message: | |
| 707 props['messages'] = [message] | |
| 708 if files: | |
| 709 props['files'] = files | |
| 710 # create the node and return it's id | |
| 711 return cl.create(**props) | |
| 712 | |
| 713 def _handle_message(self): | |
| 714 ''' generate an edit message | |
| 715 ''' | |
| 716 # handle file attachments | |
| 717 files = [] | |
| 718 if self.form.has_key('__file'): | |
| 719 file = self.form['__file'] | |
| 720 if file.filename: | |
| 721 filename = file.filename.split('\\')[-1] | |
| 722 mime_type = mimetypes.guess_type(filename)[0] | |
| 723 if not mime_type: | |
| 724 mime_type = "application/octet-stream" | |
| 725 # create the new file entry | |
| 726 files.append(self.db.file.create(type=mime_type, | |
| 727 name=filename, content=file.file.read())) | |
| 728 | |
| 729 # we don't want to do a message if none of the following is true... | |
| 730 cn = self.classname | |
| 731 cl = self.db.classes[self.classname] | |
| 732 props = cl.getprops() | |
| 733 note = None | |
| 734 # in a nutshell, don't do anything if there's no note or there's no | |
| 735 # NOSY | |
| 736 if self.form.has_key('__note'): | |
| 737 note = self.form['__note'].value.strip() | |
| 738 if not note: | |
| 739 return None, files | |
| 740 if not props.has_key('messages'): | |
| 741 return None, files | |
| 742 if not isinstance(props['messages'], hyperdb.Multilink): | |
| 743 return None, files | |
| 744 if not props['messages'].classname == 'msg': | |
| 745 return None, files | |
| 746 if not (self.form.has_key('nosy') or note): | |
| 747 return None, files | |
| 748 | |
| 749 # handle the note | |
| 750 if '\n' in note: | |
| 751 summary = re.split(r'\n\r?', note)[0] | |
| 752 else: | |
| 753 summary = note | |
| 754 m = ['%s\n'%note] | |
| 755 | |
| 756 # handle the messageid | |
| 757 # TODO: handle inreplyto | |
| 758 messageid = "<%s.%s.%s@%s>"%(time.time(), random.random(), | |
| 759 self.classname, self.instance.MAIL_DOMAIN) | |
| 760 | |
| 761 # now create the message, attaching the files | |
| 762 content = '\n'.join(m) | |
| 763 message_id = self.db.msg.create(author=self.userid, | |
| 764 recipients=[], date=date.Date('.'), summary=summary, | |
| 765 content=content, files=files, messageid=messageid) | |
| 766 | |
| 767 # update the messages property | |
| 768 return message_id, files | |
| 769 | |
| 770 def _post_editnode(self, nid): | |
| 771 '''Do the linking part of the node creation. | |
| 772 | |
| 773 If a form element has :link or :multilink appended to it, its | |
| 774 value specifies a node designator and the property on that node | |
| 775 to add _this_ node to as a link or multilink. | |
| 776 | |
| 777 This is typically used on, eg. the file upload page to indicated | |
| 778 which issue to link the file to. | |
| 779 | |
| 780 TODO: I suspect that this and newfile will go away now that | |
| 781 there's the ability to upload a file using the issue __file form | |
| 782 element! | |
| 783 ''' | |
| 784 cn = self.classname | |
| 785 cl = self.db.classes[cn] | |
| 786 # link if necessary | |
| 787 keys = self.form.keys() | |
| 788 for key in keys: | |
| 789 if key == ':multilink': | |
| 790 value = self.form[key].value | |
| 791 if type(value) != type([]): value = [value] | |
| 792 for value in value: | |
| 793 designator, property = value.split(':') | |
| 794 link, nodeid = hyperdb.splitDesignator(designator) | |
| 795 link = self.db.classes[link] | |
| 796 # take a dupe of the list so we're not changing the cache | |
| 797 value = link.get(nodeid, property)[:] | |
| 798 value.append(nid) | |
| 799 link.set(nodeid, **{property: value}) | |
| 800 elif key == ':link': | |
| 801 value = self.form[key].value | |
| 802 if type(value) != type([]): value = [value] | |
| 803 for value in value: | |
| 804 designator, property = value.split(':') | |
| 805 link, nodeid = hyperdb.splitDesignator(designator) | |
| 806 link = self.db.classes[link] | |
| 807 link.set(nodeid, **{property: nid}) | |
| 808 | |
| 809 def search_action(self): | |
| 810 ''' Mangle some of the form variables. | 746 ''' Mangle some of the form variables. |
| 811 | 747 |
| 812 Set the form ":filter" variable based on the values of the | 748 Set the form ":filter" variable based on the values of the |
| 813 filter variables - if they're set to anything other than | 749 filter variables - if they're set to anything other than |
| 814 "dontcare" then add them to :filter. | 750 "dontcare" then add them to :filter. |
| 815 ''' | 751 ''' |
| 752 # generic edit is per-class only | |
| 753 if not self.searchPermission(): | |
| 754 self.error_message.append( | |
| 755 _('You do not have permission to search %s' %self.classname)) | |
| 756 | |
| 816 # add a faked :filter form variable for each filtering prop | 757 # add a faked :filter form variable for each filtering prop |
| 817 props = self.db.classes[self.classname].getprops() | 758 props = self.db.classes[self.classname].getprops() |
| 818 for key in self.form.keys(): | 759 for key in self.form.keys(): |
| 819 if not props.has_key(key): continue | 760 if not props.has_key(key): continue |
| 820 if not self.form[key].value: continue | 761 if not self.form[key].value: continue |
| 821 self.form.value.append(cgi.MiniFieldStorage(':filter', key)) | 762 self.form.value.append(cgi.MiniFieldStorage(':filter', key)) |
| 822 | 763 |
| 823 def remove_action(self, dre=re.compile(r'([^\d]+)(\d+)')): | 764 def searchPermission(self): |
| 765 ''' Determine whether the user has permission to search this class. | |
| 766 | |
| 767 Base behaviour is to check the user can view this class. | |
| 768 ''' | |
| 769 if not self.db.security.hasPermission('View', self.userid, | |
| 770 self.classname): | |
| 771 return 0 | |
| 772 return 1 | |
| 773 | |
| 774 def XXXremove_action(self, dre=re.compile(r'([^\d]+)(\d+)')): | |
| 775 # XXX I believe this could be handled by a regular edit action that | |
| 776 # just sets the multilink... | |
| 824 # XXX handle this ! | 777 # XXX handle this ! |
| 825 target = self.index_arg(':target')[0] | 778 target = self.index_arg(':target')[0] |
| 826 m = dre.match(target) | 779 m = dre.match(target) |
| 827 if m: | 780 if m: |
| 828 classname = m.group(1) | 781 classname = m.group(1) |
| 844 return func() | 797 return func() |
| 845 else: | 798 else: |
| 846 raise NotFound, parent | 799 raise NotFound, parent |
| 847 else: | 800 else: |
| 848 raise NotFound, target | 801 raise NotFound, target |
| 802 | |
| 803 # | |
| 804 # Utility methods for editing | |
| 805 # | |
| 806 def _changenode(self, props): | |
| 807 ''' change the node based on the contents of the form | |
| 808 ''' | |
| 809 cl = self.db.classes[self.classname] | |
| 810 | |
| 811 # create the message | |
| 812 message, files = self._handle_message() | |
| 813 if message: | |
| 814 props['messages'] = cl.get(self.nodeid, 'messages') + [message] | |
| 815 if files: | |
| 816 props['files'] = cl.get(self.nodeid, 'files') + files | |
| 817 | |
| 818 # make the changes | |
| 819 return cl.set(self.nodeid, **props) | |
| 820 | |
| 821 def _createnode(self, props): | |
| 822 ''' create a node based on the contents of the form | |
| 823 ''' | |
| 824 cl = self.db.classes[self.classname] | |
| 825 | |
| 826 # check for messages and files | |
| 827 message, files = self._handle_message() | |
| 828 if message: | |
| 829 props['messages'] = [message] | |
| 830 if files: | |
| 831 props['files'] = files | |
| 832 # create the node and return it's id | |
| 833 return cl.create(**props) | |
| 834 | |
| 835 def _handle_message(self): | |
| 836 ''' generate an edit message | |
| 837 ''' | |
| 838 # handle file attachments | |
| 839 files = [] | |
| 840 if self.form.has_key('__file'): | |
| 841 file = self.form['__file'] | |
| 842 if file.filename: | |
| 843 filename = file.filename.split('\\')[-1] | |
| 844 mime_type = mimetypes.guess_type(filename)[0] | |
| 845 if not mime_type: | |
| 846 mime_type = "application/octet-stream" | |
| 847 # create the new file entry | |
| 848 files.append(self.db.file.create(type=mime_type, | |
| 849 name=filename, content=file.file.read())) | |
| 850 | |
| 851 # we don't want to do a message if none of the following is true... | |
| 852 cn = self.classname | |
| 853 cl = self.db.classes[self.classname] | |
| 854 props = cl.getprops() | |
| 855 note = None | |
| 856 # in a nutshell, don't do anything if there's no note or there's no | |
| 857 # NOSY | |
| 858 if self.form.has_key('__note'): | |
| 859 note = self.form['__note'].value.strip() | |
| 860 if not note: | |
| 861 return None, files | |
| 862 if not props.has_key('messages'): | |
| 863 return None, files | |
| 864 if not isinstance(props['messages'], hyperdb.Multilink): | |
| 865 return None, files | |
| 866 if not props['messages'].classname == 'msg': | |
| 867 return None, files | |
| 868 if not (self.form.has_key('nosy') or note): | |
| 869 return None, files | |
| 870 | |
| 871 # handle the note | |
| 872 if '\n' in note: | |
| 873 summary = re.split(r'\n\r?', note)[0] | |
| 874 else: | |
| 875 summary = note | |
| 876 m = ['%s\n'%note] | |
| 877 | |
| 878 # handle the messageid | |
| 879 # TODO: handle inreplyto | |
| 880 messageid = "<%s.%s.%s@%s>"%(time.time(), random.random(), | |
| 881 self.classname, self.instance.MAIL_DOMAIN) | |
| 882 | |
| 883 # now create the message, attaching the files | |
| 884 content = '\n'.join(m) | |
| 885 message_id = self.db.msg.create(author=self.userid, | |
| 886 recipients=[], date=date.Date('.'), summary=summary, | |
| 887 content=content, files=files, messageid=messageid) | |
| 888 | |
| 889 # update the messages property | |
| 890 return message_id, files | |
| 891 | |
| 892 def _post_editnode(self, nid): | |
| 893 '''Do the linking part of the node creation. | |
| 894 | |
| 895 If a form element has :link or :multilink appended to it, its | |
| 896 value specifies a node designator and the property on that node | |
| 897 to add _this_ node to as a link or multilink. | |
| 898 | |
| 899 This is typically used on, eg. the file upload page to indicated | |
| 900 which issue to link the file to. | |
| 901 | |
| 902 TODO: I suspect that this and newfile will go away now that | |
| 903 there's the ability to upload a file using the issue __file form | |
| 904 element! | |
| 905 ''' | |
| 906 cn = self.classname | |
| 907 cl = self.db.classes[cn] | |
| 908 # link if necessary | |
| 909 keys = self.form.keys() | |
| 910 for key in keys: | |
| 911 if key == ':multilink': | |
| 912 value = self.form[key].value | |
| 913 if type(value) != type([]): value = [value] | |
| 914 for value in value: | |
| 915 designator, property = value.split(':') | |
| 916 link, nodeid = hyperdb.splitDesignator(designator) | |
| 917 link = self.db.classes[link] | |
| 918 # take a dupe of the list so we're not changing the cache | |
| 919 value = link.get(nodeid, property)[:] | |
| 920 value.append(nid) | |
| 921 link.set(nodeid, **{property: value}) | |
| 922 elif key == ':link': | |
| 923 value = self.form[key].value | |
| 924 if type(value) != type([]): value = [value] | |
| 925 for value in value: | |
| 926 designator, property = value.split(':') | |
| 927 link, nodeid = hyperdb.splitDesignator(designator) | |
| 928 link = self.db.classes[link] | |
| 929 link.set(nodeid, **{property: nid}) | |
| 849 | 930 |
| 850 | 931 |
| 851 def parsePropsFromForm(db, cl, form, nodeid=0, num_re=re.compile('^\d+$')): | 932 def parsePropsFromForm(db, cl, form, nodeid=0, num_re=re.compile('^\d+$')): |
| 852 '''Pull properties for the given class out of the form. | 933 '''Pull properties for the given class out of the form. |
| 853 ''' | 934 ''' |
