comparison roundup/cgi/client.py @ 1444:8c5a513e52c9

Form handling now performs all actions in one go. (That is, property setting including linking.) We figure out the linking dependencies and create items in an appropriate order.
author Richard Jones <richard@users.sourceforge.net>
date Tue, 18 Feb 2003 03:58:18 +0000
parents 55d16d2b39cc
children 0bbc8be96592
comparison
equal deleted inserted replaced
1443:55d16d2b39cc 1444:8c5a513e52c9
1 # $Id: client.py,v 1.91 2003-02-18 01:59:10 richard Exp $ 1 # $Id: client.py,v 1.92 2003-02-18 03:58:18 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
776 # handle the props - edit or create 776 # handle the props - edit or create
777 # XXX reinstate exception handling 777 # XXX reinstate exception handling
778 # try: 778 # try:
779 if 1: 779 if 1:
780 # create the context here 780 # create the context here
781 cn = self.classname 781 # cn = self.classname
782 nid = self._createnode(cn, props[(cn, None)]) 782 # nid = self._createnode(cn, props[(cn, None)])
783 del props[(cn, None)] 783 # del props[(cn, None)]
784 784
785 extra = self._editnodes(props, links, {(cn, None): nid}) 785 # when it hits the None element, it'll set self.nodeid
786 if extra: 786 messages = self._editnodes(props, links) #, {(cn, None): nid})
787 extra = '<br>' + extra 787
788
789 # now do the rest
790 messages = '%s %s created'%(cn, nid) + extra
791 # except (ValueError, KeyError, IndexError), message: 788 # except (ValueError, KeyError, IndexError), message:
792 # # these errors might just be indicative of user dumbness 789 # # these errors might just be indicative of user dumbness
793 # self.error_message.append(_('Error: ') + str(message)) 790 # self.error_message.append(_('Error: ') + str(message))
794 # return 791 # return
795 792
796 # commit now that all the tricky stuff is done 793 # commit now that all the tricky stuff is done
797 self.db.commit() 794 self.db.commit()
798 795
799 # redirect to the new item's page 796 # redirect to the new item's page
800 raise Redirect, '%s%s%s?@ok_message=%s'%(self.base, self.classname, 797 raise Redirect, '%s%s%s?@ok_message=%s'%(self.base, self.classname,
801 nid, urllib.quote(messages)) 798 self.nodeid, urllib.quote(messages))
802 799
803 def newItemPermission(self, props): 800 def newItemPermission(self, props):
804 ''' Determine whether the user has permission to create (edit) this 801 ''' Determine whether the user has permission to create (edit) this
805 item. 802 item.
806 803
814 return 1 811 return 1
815 if has('Edit', self.userid, self.classname): 812 if has('Edit', self.userid, self.classname):
816 return 1 813 return 1
817 return 0 814 return 0
818 815
816
817 #
818 # Utility methods for editing
819 #
820 def _editnodes(self, all_props, all_links, newids=None):
821 ''' Use the props in all_props to perform edit and creation, then
822 use the link specs in all_links to do linking.
823 '''
824 # figure dependencies and re-work links
825 deps = {}
826 links = {}
827 for cn, nodeid, propname, vlist in all_links:
828 for value in vlist:
829 deps.setdefault((cn, nodeid), []).append(value)
830 links.setdefault(value, []).append((cn, nodeid, propname))
831
832 # figure chained dependencies ordering
833 order = []
834 done = {}
835 # loop detection
836 change = 0
837 while len(all_props) != len(done):
838 for needed in all_props.keys():
839 if done.has_key(needed):
840 continue
841 tlist = deps.get(needed, [])
842 for target in tlist:
843 if not done.has_key(target):
844 break
845 else:
846 done[needed] = 1
847 order.append(needed)
848 change = 1
849 if not change:
850 raise ValueError, 'linking must not loop!'
851
852 # now, edit / create
853 m = []
854 for needed in order:
855 props = all_props[needed]
856 cn, nodeid = needed
857
858 if nodeid is not None and int(nodeid) > 0:
859 # make changes to the node
860 props = self._changenode(cn, nodeid, props)
861
862 # and some nice feedback for the user
863 if props:
864 info = ', '.join(props.keys())
865 m.append('%s %s %s edited ok'%(cn, nodeid, info))
866 else:
867 m.append('%s %s - nothing changed'%(cn, nodeid))
868 else:
869 assert props
870
871 # make a new node
872 newid = self._createnode(cn, props)
873 if nodeid is None:
874 self.nodeid = newid
875 nodeid = newid
876
877 # and some nice feedback for the user
878 m.append('%s %s created'%(cn, newid))
879
880 # fill in new ids in links
881 if links.has_key(needed):
882 for linkcn, linkid, linkprop in links[needed]:
883 props = all_props[(linkcn, linkid)]
884 cl = self.db.classes[linkcn]
885 propdef = cl.getprops()[linkprop]
886 if not props.has_key(linkprop):
887 if linkid is None or linkid.startswith('-'):
888 # linking to a new item
889 if isinstance(propdef, hyperdb.Multilink):
890 props[linkprop] = [newid]
891 else:
892 props[linkprop] = newid
893 else:
894 # linking to an existing item
895 if isinstance(propdef, hyperdb.Multilink):
896 existing = cl.get(linkid, linkprop)[:]
897 existing.append(nodeid)
898 props[linkprop] = existing
899 else:
900 props[linkprop] = newid
901
902 return '<br>'.join(m)
903
904 def _changenode(self, cn, nodeid, props):
905 ''' change the node based on the contents of the form
906 '''
907 # check for permission
908 if not self.editItemPermission(props):
909 raise PermissionError, 'You do not have permission to edit %s'%cn
910
911 # make the changes
912 cl = self.db.classes[cn]
913 return cl.set(nodeid, **props)
914
915 def _createnode(self, cn, props):
916 ''' create a node based on the contents of the form
917 '''
918 # check for permission
919 if not self.newItemPermission(props):
920 raise PermissionError, 'You do not have permission to create %s'%cn
921
922 # create the node and return its id
923 cl = self.db.classes[cn]
924 return cl.create(**props)
925
926 #
927 # More actions
928 #
819 def editCSVAction(self): 929 def editCSVAction(self):
820 ''' Performs an edit of all of a class' items in one go. 930 ''' Performs an edit of all of a class' items in one go.
821 931
822 The "rows" CGI var defines the CSV-formatted entries for the 932 The "rows" CGI var defines the CSV-formatted entries for the
823 class. New nodes are identified by the ID 'X' (or any other 933 class. New nodes are identified by the ID 'X' (or any other
1020 # XXX allow : @ + 1130 # XXX allow : @ +
1021 t = self.form[':type'].value 1131 t = self.form[':type'].value
1022 n = self.form[':number'].value 1132 n = self.form[':number'].value
1023 url = '%s%s%s'%(self.db.config.TRACKER_WEB, t, n) 1133 url = '%s%s%s'%(self.db.config.TRACKER_WEB, t, n)
1024 raise Redirect, url 1134 raise Redirect, url
1025
1026
1027 #
1028 # Utility methods for editing
1029 #
1030 def _editnodes(self, all_props, all_links, newids=None):
1031 ''' Use the props in all_props to perform edit and creation, then
1032 use the link specs in all_links to do linking.
1033 '''
1034 m = []
1035 if newids is None:
1036 newids = {}
1037 for (cn, nodeid), props in all_props.items():
1038 if int(nodeid) > 0:
1039 # make changes to the node
1040 props = self._changenode(cn, nodeid, props)
1041
1042 # and some nice feedback for the user
1043 if props:
1044 info = ', '.join(props.keys())
1045 m.append('%s %s %s edited ok'%(cn, nodeid, info))
1046 else:
1047 m.append('%s %s - nothing changed'%(cn, nodeid))
1048 elif props:
1049 # make a new node
1050 newid = self._createnode(cn, props)
1051 newids[(cn, nodeid)] = newid
1052 nodeid = newid
1053
1054 # and some nice feedback for the user
1055 m.append('%s %s created'%(cn, newid))
1056
1057 # handle linked nodes
1058 keys = self.form.keys()
1059 for cn, nodeid, propname, value in all_links:
1060 cl = self.db.classes[cn]
1061 property = cl.getprops()[propname]
1062 if nodeid is None or nodeid.startswith('-'):
1063 if not newids.has_key((cn, nodeid)):
1064 continue
1065 nodeid = newids[(cn, nodeid)]
1066
1067 # map the desired classnames to their actual created ids
1068 for link in value:
1069 if not newids.has_key(link):
1070 continue
1071 linkid = newids[link]
1072 if isinstance(property, hyperdb.Multilink):
1073 # take a dupe of the list so we're not changing the cache
1074 existing = cl.get(nodeid, propname)[:]
1075 existing.append(linkid)
1076 cl.set(nodeid, **{propname: existing})
1077 elif isinstance(property, hyperdb.Link):
1078 # make the Link set
1079 cl.set(nodeid, **{propname: linkid})
1080 else:
1081 raise ValueError, '%s %s is not a link or multilink '\
1082 'property'%(cn, propname)
1083 m.append('%s %s linked to <a href="%s%s">%s %s</a>'%(
1084 link[0], linkid, cn, nodeid, cn, nodeid))
1085
1086 return '<br>'.join(m)
1087
1088 def _changenode(self, cn, nodeid, props):
1089 ''' change the node based on the contents of the form
1090 '''
1091 # check for permission
1092 if not self.editItemPermission(props):
1093 raise PermissionError, 'You do not have permission to edit %s'%cn
1094
1095 # make the changes
1096 cl = self.db.classes[cn]
1097 return cl.set(nodeid, **props)
1098
1099 def _createnode(self, cn, props):
1100 ''' create a node based on the contents of the form
1101 '''
1102 # check for permission
1103 if not self.newItemPermission(props):
1104 raise PermissionError, 'You do not have permission to create %s'%cn
1105
1106 # create the node and return its id
1107 cl = self.db.classes[cn]
1108 return cl.create(**props)
1109 1135
1110 def parsePropsFromForm(self, num_re=re.compile('^\d+$')): 1136 def parsePropsFromForm(self, num_re=re.compile('^\d+$')):
1111 ''' Pull properties out of the form. 1137 ''' Pull properties out of the form.
1112 1138
1113 In the following, <bracketed> values are variable, ":" may be 1139 In the following, <bracketed> values are variable, ":" may be
1265 m = self.FV_DESIGNATOR.match(entry) 1291 m = self.FV_DESIGNATOR.match(entry)
1266 if not m: 1292 if not m:
1267 raise ValueError, \ 1293 raise ValueError, \
1268 'link "%s" value "%s" not a designator'%(key, entry) 1294 'link "%s" value "%s" not a designator'%(key, entry)
1269 value.append((m.group(1), m.group(2))) 1295 value.append((m.group(1), m.group(2)))
1296
1297 # make sure the link property is valid
1298 if (not isinstance(propdef, hyperdb.Multilink) and
1299 not isinstance(propdef, hyperdb.Link)):
1300 raise ValueError, '%s %s is not a link or '\
1301 'multilink property'%(cn, propname)
1302
1270 all_links.append((cn, nodeid, propname, value)) 1303 all_links.append((cn, nodeid, propname, value))
1271 continue 1304 continue
1272 1305
1273 # detect the special ":required" variable 1306 # detect the special ":required" variable
1274 if d['required']: 1307 if d['required']:
1523 if not isinstance(cl, hyperdb.FileClass): 1556 if not isinstance(cl, hyperdb.FileClass):
1524 continue 1557 continue
1525 if not props.get('content', ''): 1558 if not props.get('content', ''):
1526 del all_props[(cn, id)] 1559 del all_props[(cn, id)]
1527 1560
1528 return all_props, all_links 1561 # clean up the links, removing ones that aren't possible
1562 l = []
1563 for entry in all_links:
1564 (cn, nodeid, propname, destlist) = entry
1565 source = (cn, nodeid)
1566 if not all_props.has_key(source) or not all_props[source]:
1567 # nothing to create - don't try to link
1568 continue
1569 # nothing to create - don't try to link
1570 continue
1571 for dest in destlist[:]:
1572 if not all_props.has_key(dest) or not all_props[dest]:
1573 destlist.remove(dest)
1574 l.append(entry)
1575
1576 return all_props, l
1529 1577
1530 def fixNewlines(text): 1578 def fixNewlines(text):
1531 ''' Homogenise line endings. 1579 ''' Homogenise line endings.
1532 1580
1533 Different web clients send different line ending values, but 1581 Different web clients send different line ending values, but

Roundup Issue Tracker: http://roundup-tracker.org/