Mercurial > p > roundup > code
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 |
