1.1 --- a/MoinForms.py Sun Dec 02 15:43:39 2012 +0100
1.2 +++ b/MoinForms.py Mon Dec 03 00:51:41 2012 +0100
1.3 @@ -7,6 +7,7 @@
1.4 """
1.5
1.6 from MoinMoin.action import do_show
1.7 +from MoinMoin.Page import Page
1.8 from MoinMoin import wikiutil
1.9 from MoinSupport import *
1.10 import re
1.11 @@ -38,15 +39,90 @@
1.12 form = get_form(self.request)
1.13 fields = getFields(form, remove=True)
1.14
1.15 - # Modify, serialise and show the form.
1.16 + # Modify and validate the form.
1.17
1.18 self.modifyFields(fields)
1.19 - self.validateFields(fields)
1.20 +
1.21 + if self.validateFields(fields):
1.22 + self.finished(fields, form)
1.23 + else:
1.24 + self.unfinished(fields, form)
1.25 +
1.26 + def finished(self, fields, form):
1.27 +
1.28 + "Handle the finished 'fields' and 'form'."
1.29 +
1.30 + self.unfinished(fields, form)
1.31 +
1.32 + def unfinished(self, fields, form):
1.33 +
1.34 + "Handle the unfinished 'fields' and 'form'."
1.35 +
1.36 + # Serialise and show the form.
1.37 +
1.38 self.serialiseFields(fields, form)
1.39 do_show(self.pagename, self.request)
1.40
1.41 def validateFields(self, fields):
1.42 - pass
1.43 +
1.44 + """
1.45 + Validate the given 'fields', introducing error fields where the
1.46 + individual fields do not conform to their descriptions.
1.47 + """
1.48 +
1.49 + form = get_form(self.request)
1.50 + text = Page(self.request, self.pagename).get_raw_body()
1.51 + text = getFormForFragment(text, form.get("fragment", [None])[0])
1.52 + structure = getFormStructure(text, self.request)
1.53 +
1.54 + return self.validateFieldsUsingStructure(fields, structure)
1.55 +
1.56 + def validateFieldsUsingStructure(self, fields, structure):
1.57 +
1.58 + "Validate the given 'fields' using the given 'structure'."
1.59 +
1.60 + _ = self.request.getText
1.61 + valid = True
1.62 +
1.63 + for key, definition in structure.items():
1.64 + value = fields.get(key)
1.65 +
1.66 + # Enter form sections and validate them.
1.67 +
1.68 + if isinstance(definition, dict):
1.69 + if value:
1.70 + for element in getSectionElements(value):
1.71 + valid = valid and self.validateFieldsUsingStructure(element, structure[key])
1.72 +
1.73 + # Validate individual fields.
1.74 +
1.75 + elif structure.has_key(key):
1.76 + path, dictpage, label, section, field_args, allowed_values = definition
1.77 + errors = []
1.78 +
1.79 + # Test for obligatory values.
1.80 +
1.81 + if not value:
1.82 + if field_args.get("required"):
1.83 + errors.append(_("This field must be filled out."))
1.84 + else:
1.85 + # Test for unacceptable values.
1.86 +
1.87 + if allowed_values and set(value).difference(allowed_values):
1.88 + errors.append(_("At least one of the choices is not acceptable."))
1.89 +
1.90 + # Test the number of values.
1.91 +
1.92 + if field_args.get("type") == "select":
1.93 + if field_args.has_key("maxselected"):
1.94 + if len(value) > int(field_args["maxselected"]):
1.95 + errors.append(_("Incorrect number of choices given: need %s.") % field_args["maxselected"])
1.96 +
1.97 + if errors:
1.98 + fields["%s-error" % key] = errors
1.99 + valid = False
1.100 +
1.101 + return valid
1.102
1.103 def serialiseFields(self, fields, form, path=None):
1.104
1.105 @@ -117,6 +193,52 @@
1.106
1.107 # Form and field information.
1.108
1.109 +def getFormStructure(text, request, path=None, structure=None):
1.110 +
1.111 + """
1.112 + For the given form 'text' and using the 'request', return details of the
1.113 + form for the section at the given 'path' (or the entire form if 'path' is
1.114 + omitted), populating the given 'structure' (or populating a new structure if
1.115 + 'structure' is omitted).
1.116 + """
1.117 +
1.118 + if structure is None:
1.119 + structure = {}
1.120 +
1.121 + for format, attributes, body in getFragments(text, True):
1.122 +
1.123 + # Get field details at the current level.
1.124 +
1.125 + if format is None:
1.126 + structure.update(getFormFields(body, path, request))
1.127 +
1.128 + # Where a section is found, get details from within the section.
1.129 +
1.130 + elif format == "form" and attributes.has_key("section"):
1.131 + section_name = attributes["section"]
1.132 + section = structure[section_name] = {}
1.133 + getFormStructure(body, request, path and ("%s/%s" % (path, section_name)) or section_name, section)
1.134 +
1.135 + # Get field details from other kinds of region.
1.136 +
1.137 + elif format != "form":
1.138 + getFormStructure(body, request, path, structure)
1.139 +
1.140 + return structure
1.141 +
1.142 +def getFormForFragment(text, fragment=None):
1.143 +
1.144 + """
1.145 + Return the form region from the given 'text' for the specified 'fragment'.
1.146 + If no fragment is specified, the first form region is returned.
1.147 + """
1.148 +
1.149 + for format, attributes, body in getFragments(text):
1.150 + if not fragment or attributes.get("fragment") == fragment:
1.151 + return body
1.152 +
1.153 + return None
1.154 +
1.155 def getFieldArguments(field_definition):
1.156
1.157 "Return the parsed arguments from the given 'field_definition' string."
1.158 @@ -124,6 +246,9 @@
1.159 field_args = {}
1.160
1.161 for field_arg in field_definition.split():
1.162 + if field_arg == "required":
1.163 + field_args[field_arg] = True
1.164 + continue
1.165
1.166 # Record the key-value details.
1.167
1.168 @@ -198,11 +323,7 @@
1.169 # Adjust any FormField macros to use hierarchical names.
1.170
1.171 if format is None:
1.172 - if path:
1.173 - adjusted_body = adjustFormFields(body, path)
1.174 - output.append(adjusted_body)
1.175 - else:
1.176 - output.append(body)
1.177 + output.append(path and adjustFormFields(body, path) or body)
1.178
1.179 # Include form sections only if fields exist for those sections.
1.180
1.181 @@ -229,6 +350,59 @@
1.182
1.183 return "".join(output)
1.184
1.185 +def getFormFields(body, path, request):
1.186 +
1.187 + "Return a dictionary of fields from the given 'body' at the given 'path'."
1.188 +
1.189 + fields = {}
1.190 + cache = {}
1.191 + type = None
1.192 +
1.193 + for i, match in enumerate(form_field_regexp.split(body)):
1.194 + state = i % 3
1.195 +
1.196 + if state == 1:
1.197 + type = match
1.198 + elif state == 2 and type == "Field":
1.199 + args = {}
1.200 +
1.201 + # Obtain the macro arguments, adjusted to consider the path.
1.202 +
1.203 + name, path, dictpage, label, section = \
1.204 + getMacroArguments(adjustMacroArguments(parseMacroArguments(match), path))
1.205 +
1.206 + # Obtain field information from the cache, if possible.
1.207 +
1.208 + cache_key = (name, dictpage)
1.209 +
1.210 + if cache.has_key(cache_key):
1.211 + field_args, allowed_values = cache[cache_key]
1.212 +
1.213 + # Otherwise, obtain field information from any WikiDict.
1.214 +
1.215 + else:
1.216 + field_args = {}
1.217 + allowed_values = None
1.218 +
1.219 + if dictpage:
1.220 + wikidict = getWikiDict(dictpage, request)
1.221 + if wikidict:
1.222 + field_definition = wikidict.get(name)
1.223 + if field_definition:
1.224 + field_args = getFieldArguments(field_definition)
1.225 + if field_args.has_key("source"):
1.226 + sourcedict = getWikiDict(field_args["source"], request)
1.227 + if sourcedict:
1.228 + allowed_values = sourcedict.keys()
1.229 +
1.230 + cache[cache_key] = field_args, allowed_values
1.231 +
1.232 + # Store the field information.
1.233 +
1.234 + fields[name] = path, dictpage, label, section, field_args, allowed_values
1.235 +
1.236 + return fields
1.237 +
1.238 def adjustFormFields(body, path):
1.239
1.240 """
1.241 @@ -269,6 +443,9 @@
1.242 arguments.
1.243 """
1.244
1.245 + if not path:
1.246 + return args
1.247 +
1.248 result = []
1.249 old_path = None
1.250
1.251 @@ -298,6 +475,40 @@
1.252
1.253 return [arg for arg in parsed_args if arg]
1.254
1.255 +def getMacroArguments(parsed_args):
1.256 +
1.257 + "Return the macro arguments decoded from 'parsed_args'."
1.258 +
1.259 + name = None
1.260 + path = None
1.261 + dictpage = None
1.262 + label = None
1.263 + section = None
1.264 +
1.265 + for arg in parsed_args:
1.266 + if arg.startswith("name="):
1.267 + name = arg[5:]
1.268 +
1.269 + elif arg.startswith("path="):
1.270 + path = arg[5:]
1.271 +
1.272 + elif arg.startswith("dict="):
1.273 + dictpage = arg[5:]
1.274 +
1.275 + elif arg.startswith("label="):
1.276 + label = arg[6:]
1.277 +
1.278 + elif arg.startswith("section="):
1.279 + section = arg[8:]
1.280 +
1.281 + elif name is None:
1.282 + name = arg
1.283 +
1.284 + elif dictpage is None:
1.285 + dictpage = arg
1.286 +
1.287 + return name, path, dictpage, label, section
1.288 +
1.289 def getFields(d, remove=False):
1.290
1.291 """
2.1 --- a/macros/FormField.py Sun Dec 02 15:43:39 2012 +0100
2.2 +++ b/macros/FormField.py Mon Dec 03 00:51:41 2012 +0100
2.3 @@ -8,7 +8,7 @@
2.4
2.5 from MoinMoin import wikiutil
2.6 from MoinSupport import *
2.7 -from MoinForms import getFieldArguments, parseMacroArguments
2.8 +from MoinForms import getFieldArguments, parseMacroArguments, getMacroArguments
2.9
2.10 Dependencies = ['pages']
2.11
2.12 @@ -44,33 +44,7 @@
2.13
2.14 # Get special arguments.
2.15
2.16 - name = None
2.17 - path = None
2.18 - dictpage = None
2.19 - label = None
2.20 - section = None
2.21 -
2.22 - for arg in parsed_args:
2.23 - if arg.startswith("name="):
2.24 - name = arg[5:]
2.25 -
2.26 - elif arg.startswith("path="):
2.27 - path = arg[5:]
2.28 -
2.29 - elif arg.startswith("dict="):
2.30 - dictpage = arg[5:]
2.31 -
2.32 - elif arg.startswith("label="):
2.33 - label = arg[6:]
2.34 -
2.35 - elif arg.startswith("section="):
2.36 - section = arg[8:]
2.37 -
2.38 - elif name is None:
2.39 - name = arg
2.40 -
2.41 - elif dictpage is None:
2.42 - dictpage = arg
2.43 + name, path, dictpage, label, section = getMacroArguments(parsed_args)
2.44
2.45 if not name:
2.46 return showError(_("No field name specified."), request)
2.47 @@ -141,14 +115,14 @@
2.48 if not sourcedict:
2.49 return showError(_("WikiDict %s cannot be loaded for %s.") % (sourcedict, name), request)
2.50
2.51 - size = field_args.get("size", [None])[0]
2.52 - size = size and int(size) or size
2.53 + maxselected = field_args.get("maxselected", [None])[0]
2.54 + maxselected = maxselected and int(maxselected) or maxselected
2.55
2.56 output = []
2.57 - output.append(fmt.rawHTML('<select name="%s" %s>' % (escattr(ref), size != 1 and "multiple" or "")))
2.58 + output.append(fmt.rawHTML('<select name="%s" %s>' % (escattr(ref), maxselected != 1 and "multiple" or "")))
2.59
2.60 for option, label in sourcedict.items():
2.61 - is_selected = size == 1 and option == value or size != 1 and option in values
2.62 + is_selected = maxselected == 1 and option == value or maxselected != 1 and option in values
2.63
2.64 output.append(fmt.rawHTML('<option value="%s" %s>%s</option>' % (
2.65 escattr(option), is_selected and "selected" or "", escape(label))