1- # $Id: client.py,v 1.88 2003-02-17 01:04:31 richard Exp $
1+ # $Id: client.py,v 1.89 2003-02-17 06:44:00 richard Exp $
22
33__doc__ = """
44WWW request handler (also used in the stand-alone server).
@@ -93,16 +93,27 @@ class Client:
9393 FV_OK_MESSAGE = re .compile (r'[@:]ok_message' )
9494 FV_ERROR_MESSAGE = re .compile (r'[@:]error_message' )
9595
96- # specials for parsePropsFromForm
97- FV_REQUIRED = re .compile (r'[@:]required' )
98- FV_ADD = re .compile (r'([@:])add\1' )
99- FV_REMOVE = re .compile (r'([@:])remove\1' )
100- FV_CONFIRM = re .compile (r'.+[@:]confirm' )
101- FV_LINK = re .compile (r'([@:])link\1(.+)' )
102-
103- # deprecated
104- FV_NOTE = re .compile (r'[@:]note' )
105- FV_FILE = re .compile (r'[@:]file' )
96+ # edit form variable handling (see unit tests)
97+ FV_LABELS = r'''
98+ ^(
99+ (?P<note>[@:]note)|
100+ (?P<file>[@:]file)|
101+ (
102+ ((?P<classname>%s)(?P<id>[-\d]+))? # optional leading designator
103+ ((?P<required>[@:]required$)| # :required
104+ (
105+ (
106+ (?P<add>[@:]add[@:])| # :add:<prop>
107+ (?P<remove>[@:]remove[@:])| # :remove:<prop>
108+ (?P<confirm>[@:]confirm[@:])| # :confirm:<prop>
109+ (?P<link>[@:]link[@:])| # :link:<prop>
110+ ([@:]) # just a separator
111+ )?
112+ (?P<propname>[^@:]+) # <prop>
113+ )
114+ )
115+ )
116+ )$'''
106117
107118 # Note: index page stuff doesn't appear here:
108119 # columns, sort, sortdir, filter, group, groupdir, search_text,
@@ -1097,39 +1108,44 @@ def _createnode(self, cn, props):
10971108 return cl .create (** props )
10981109
10991110 def parsePropsFromForm (self , num_re = re .compile ('^\d+$' )):
1100- ''' Pull properties for the given class out of the form.
1111+ ''' Pull properties out of the form.
11011112
11021113 In the following, <bracketed> values are variable, ":" may be
11031114 one of ":" or "@", and other text "required" is fixed.
11041115
1105- Properties are specified as form variables
1116+ Properties are specified as form variables:
1117+
1118+ <propname>
1119+ - property on the current context item
1120+
11061121 <designator>:<propname>
1122+ - property on the indicated item
1123+
1124+ <classname>-<N>:<propname>
1125+ - property on the Nth new item of classname
11071126
1108- where the propery belongs to the context class or item if the
1109- designator is not specified. The designator may specify a
1110- negative item id value (ie. "issue-1") and a new item of the
1111- specified class will be created for each negative id found.
1127+ Once we have determined the "propname", we check to see if it
1128+ is one of the special form values:
11121129
1113- If a "<designator> :required" parameter is supplied,
1114- then the named property values must be supplied or a
1115- ValueError will be raised.
1130+ :required
1131+ The named property values must be supplied or a ValueError
1132+ will be raised.
11161133
1117- Other special form values:
1118- [classname|designator]:remove:<propname>=id(s)
1134+ :remove:<propname>=id(s)
11191135 The ids will be removed from the multilink property.
1120- [classname|designator]:add:<propname>=id(s)
1136+
1137+ :add:<propname>=id(s)
11211138 The ids will be added to the multilink property.
11221139
1123- [classname|designator] :link:<propname>=<classname >
1140+ :link:<propname>=<designator >
11241141 Used to add a link to new items created during edit.
11251142 These are collected up and returned in all_links. This will
11261143 result in an additional linking operation (either Link set or
11271144 Multilink append) after the edit/create is done using
1128- all_props in _editnodes. The <propname> on
1129- [classname|designator] will be set/appended the id of the
1130- newly created item of class <classname>.
1131-
1132- Note: the colon may be either ":" or "@".
1145+ all_props in _editnodes. The <propname> on the current item
1146+ will be set/appended the id of the newly created item of
1147+ class <designator> (where <designator> must be
1148+ <classname>-<N>).
11331149
11341150 Any of the form variables may be prefixed with a classname or
11351151 designator.
@@ -1149,19 +1165,21 @@ def parsePropsFromForm(self, num_re=re.compile('^\d+$')):
11491165 Two special form values are supported for backwards
11501166 compatibility:
11511167 :note - create a message (with content, author and date), link
1152- to the context item
1168+ to the context item. This is ALWAYS desginated "msg-1".
11531169 :file - create a file, attach to the current item and any
1154- message created by :note
1170+ message created by :note. This is ALWAYS designated
1171+ "file-1".
11551172 '''
11561173 # some very useful variables
11571174 db = self .db
11581175 form = self .form
11591176
1160- if not hasattr (self , 'FV_ITEMSPEC' ):
1161- # generate the regexp for detecting
1162- # <classname|designator>[@:+]property
1177+ if not hasattr (self , 'FV_SPECIAL' ):
1178+ # generate the regexp for handling special form values
11631179 classes = '|' .join (db .classes .keys ())
1164- self .FV_ITEMSPEC = re .compile (r'(%s)([-\d]+)[@:](.+)$' % classes )
1180+ # specials for parsePropsFromForm
1181+ # handle the various forms (see unit tests)
1182+ self .FV_SPECIAL = re .compile (self .FV_LABELS % classes , re .VERBOSE )
11651183 self .FV_DESIGNATOR = re .compile (r'(%s)([-\d]+)' % classes )
11661184
11671185 # these indicate the default class / item
@@ -1181,54 +1199,52 @@ def parsePropsFromForm(self, num_re=re.compile('^\d+$')):
11811199 keys = form .keys ()
11821200 timezone = db .getUserTimezone ()
11831201
1202+ # sentinels for the :note and :file props
1203+ have_note = have_file = 0
1204+
1205+ # extract the usable form labels from the form
1206+ matches = []
11841207 for key in keys :
1185- # see if this value modifies a different item to the default
1186- m = self .FV_ITEMSPEC .match (key )
1208+ m = self .FV_SPECIAL .match (key )
11871209 if m :
1210+ matches .append ((key , m .groupdict ()))
1211+
1212+ # now handle the matches
1213+ for key , d in matches :
1214+ if d ['classname' ]:
11881215 # we got a designator
1189- cn = m . group ( 1 )
1216+ cn = d [ 'classname' ]
11901217 cl = self .db .classes [cn ]
1191- nodeid = m . group ( 2 )
1192- propname = m . group ( 3 )
1193- elif key == ': note' :
1194- # backwards compatibility: the special note field
1218+ nodeid = d [ 'id' ]
1219+ propname = d [ 'propname' ]
1220+ elif d [ ' note'] :
1221+ # the special note field
11951222 cn = 'msg'
11961223 cl = self .db .classes [cn ]
11971224 nodeid = '-1'
11981225 propname = 'content'
11991226 all_links .append ((default_cn , default_nodeid , 'messages' ,
12001227 [('msg' , '-1' )]))
1201- elif key == ':file' :
1202- # backwards compatibility: the special file field
1228+ have_note = 1
1229+ elif d ['file' ]:
1230+ # the special file field
12031231 cn = 'file'
12041232 cl = self .db .classes [cn ]
12051233 nodeid = '-1'
12061234 propname = 'content'
12071235 all_links .append ((default_cn , default_nodeid , 'files' ,
12081236 [('file' , '-1' )]))
1209- if self .form .has_key (':note' ):
1210- all_links .append (('msg' , '-1' , 'files' , [('file' , '-1' )]))
1237+ have_file = 1
12111238 else :
12121239 # default
12131240 cn = default_cn
12141241 cl = default_cl
12151242 nodeid = default_nodeid
1216- propname = key
1243+ propname = d [ 'propname' ]
12171244
12181245 # the thing this value relates to is...
12191246 this = (cn , nodeid )
12201247
1221- # is this a link command?
1222- if self .FV_LINK .match (propname ):
1223- value = []
1224- for entry in extractFormList (form [key ]):
1225- m = self .FV_DESIGNATOR .match (entry )
1226- if not m :
1227- raise ValueError , \
1228- 'link "%s" value "%s" not a designator' % (key , entry )
1229- value .append ((m .groups (1 ), m .groups (2 )))
1230- all_links .append ((cn , nodeid , propname [6 :], value ))
1231-
12321248 # get more info about the class, and the current set of
12331249 # form props for it
12341250 if not all_propdef .has_key (cn ):
@@ -1238,8 +1254,20 @@ def parsePropsFromForm(self, num_re=re.compile('^\d+$')):
12381254 all_props [this ] = {}
12391255 props = all_props [this ]
12401256
1257+ # is this a link command?
1258+ if d ['link' ]:
1259+ value = []
1260+ for entry in extractFormList (form [key ]):
1261+ m = self .FV_DESIGNATOR .match (entry )
1262+ if not m :
1263+ raise ValueError , \
1264+ 'link "%s" value "%s" not a designator' % (key , entry )
1265+ value .append ((m .group (1 ), m .group (2 )))
1266+ all_links .append ((cn , nodeid , propname , value ))
1267+ continue
1268+
12411269 # detect the special ":required" variable
1242- if self . FV_REQUIRED . match ( key ) :
1270+ if d [ 'required' ] :
12431271 all_required [this ] = extractFormList (form [key ])
12441272 continue
12451273
@@ -1250,11 +1278,9 @@ def parsePropsFromForm(self, num_re=re.compile('^\d+$')):
12501278
12511279 # see if we're performing a special multilink action
12521280 mlaction = 'set'
1253- if self .FV_REMOVE .match (propname ):
1254- propname = propname [8 :]
1281+ if d ['remove' ]:
12551282 mlaction = 'remove'
1256- elif self .FV_ADD .match (propname ):
1257- propname = propname [5 :]
1283+ elif d ['add' ]:
12581284 mlaction = 'add'
12591285
12601286 # does the property exist?
@@ -1263,6 +1289,8 @@ def parsePropsFromForm(self, num_re=re.compile('^\d+$')):
12631289 raise ValueError , 'You have submitted a %s action for' \
12641290 ' the property "%s" which doesn\' t exist' % (mlaction ,
12651291 propname )
1292+ # the form element is probably just something we don't care
1293+ # about - ignore it
12661294 continue
12671295 proptype = propdef [propname ]
12681296
@@ -1285,7 +1313,7 @@ def parsePropsFromForm(self, num_re=re.compile('^\d+$')):
12851313
12861314 # now that we have the props field, we need a teensy little
12871315 # extra bit of help for the old :note field...
1288- if key == ': note' and value :
1316+ if d [ ' note'] and value :
12891317 props ['author' ] = self .db .getuid ()
12901318 props ['date' ] = date .Date ()
12911319
@@ -1294,8 +1322,8 @@ def parsePropsFromForm(self, num_re=re.compile('^\d+$')):
12941322 if not value :
12951323 # ignore empty password values
12961324 continue
1297- for key in keys :
1298- if self . FV_CONFIRM . match ( key ) :
1325+ for key , d in matches :
1326+ if d [ 'confirm' ] and d [ 'propname' ] == propname :
12991327 confirm = form [key ]
13001328 break
13011329 else :
@@ -1466,6 +1494,10 @@ def parsePropsFromForm(self, num_re=re.compile('^\d+$')):
14661494 if propname in required and value is not None :
14671495 required .remove (propname )
14681496
1497+ # check to see if we need to specially link a file to the note
1498+ if have_note and have_file :
1499+ all_links .append (('msg' , '-1' , 'files' , [('file' , '-1' )]))
1500+
14691501 # see if all the required properties have been supplied
14701502 s = []
14711503 for thing , required in all_required .items ():
0 commit comments