# HG changeset patch # User Paul Boddie # Date 1354492301 -3600 # Node ID 11d6e15c8b0c5ef3bbd8571d0da4bc77db3c4413 # Parent 678a687f520be7f71ba0306c9f313579786a0141 Introduced support for basic validation, adding support for required fields to form definitions, and renaming "size" to "maxselected" in "select" field definitions in order to indicate the maximum number of allowed values for such fields. Moved macro argument processing into the library. diff -r 678a687f520b -r 11d6e15c8b0c MoinForms.py --- a/MoinForms.py Sun Dec 02 15:43:39 2012 +0100 +++ b/MoinForms.py Mon Dec 03 00:51:41 2012 +0100 @@ -7,6 +7,7 @@ """ from MoinMoin.action import do_show +from MoinMoin.Page import Page from MoinMoin import wikiutil from MoinSupport import * import re @@ -38,15 +39,90 @@ form = get_form(self.request) fields = getFields(form, remove=True) - # Modify, serialise and show the form. + # Modify and validate the form. self.modifyFields(fields) - self.validateFields(fields) + + if self.validateFields(fields): + self.finished(fields, form) + else: + self.unfinished(fields, form) + + def finished(self, fields, form): + + "Handle the finished 'fields' and 'form'." + + self.unfinished(fields, form) + + def unfinished(self, fields, form): + + "Handle the unfinished 'fields' and 'form'." + + # Serialise and show the form. + self.serialiseFields(fields, form) do_show(self.pagename, self.request) def validateFields(self, fields): - pass + + """ + Validate the given 'fields', introducing error fields where the + individual fields do not conform to their descriptions. + """ + + form = get_form(self.request) + text = Page(self.request, self.pagename).get_raw_body() + text = getFormForFragment(text, form.get("fragment", [None])[0]) + structure = getFormStructure(text, self.request) + + return self.validateFieldsUsingStructure(fields, structure) + + def validateFieldsUsingStructure(self, fields, structure): + + "Validate the given 'fields' using the given 'structure'." + + _ = self.request.getText + valid = True + + for key, definition in structure.items(): + value = fields.get(key) + + # Enter form sections and validate them. + + if isinstance(definition, dict): + if value: + for element in getSectionElements(value): + valid = valid and self.validateFieldsUsingStructure(element, structure[key]) + + # Validate individual fields. + + elif structure.has_key(key): + path, dictpage, label, section, field_args, allowed_values = definition + errors = [] + + # Test for obligatory values. + + if not value: + if field_args.get("required"): + errors.append(_("This field must be filled out.")) + else: + # Test for unacceptable values. + + if allowed_values and set(value).difference(allowed_values): + errors.append(_("At least one of the choices is not acceptable.")) + + # Test the number of values. + + if field_args.get("type") == "select": + if field_args.has_key("maxselected"): + if len(value) > int(field_args["maxselected"]): + errors.append(_("Incorrect number of choices given: need %s.") % field_args["maxselected"]) + + if errors: + fields["%s-error" % key] = errors + valid = False + + return valid def serialiseFields(self, fields, form, path=None): @@ -117,6 +193,52 @@ # Form and field information. +def getFormStructure(text, request, path=None, structure=None): + + """ + For the given form 'text' and using the 'request', return details of the + form for the section at the given 'path' (or the entire form if 'path' is + omitted), populating the given 'structure' (or populating a new structure if + 'structure' is omitted). + """ + + if structure is None: + structure = {} + + for format, attributes, body in getFragments(text, True): + + # Get field details at the current level. + + if format is None: + structure.update(getFormFields(body, path, request)) + + # Where a section is found, get details from within the section. + + elif format == "form" and attributes.has_key("section"): + section_name = attributes["section"] + section = structure[section_name] = {} + getFormStructure(body, request, path and ("%s/%s" % (path, section_name)) or section_name, section) + + # Get field details from other kinds of region. + + elif format != "form": + getFormStructure(body, request, path, structure) + + return structure + +def getFormForFragment(text, fragment=None): + + """ + Return the form region from the given 'text' for the specified 'fragment'. + If no fragment is specified, the first form region is returned. + """ + + for format, attributes, body in getFragments(text): + if not fragment or attributes.get("fragment") == fragment: + return body + + return None + def getFieldArguments(field_definition): "Return the parsed arguments from the given 'field_definition' string." @@ -124,6 +246,9 @@ field_args = {} for field_arg in field_definition.split(): + if field_arg == "required": + field_args[field_arg] = True + continue # Record the key-value details. @@ -198,11 +323,7 @@ # Adjust any FormField macros to use hierarchical names. if format is None: - if path: - adjusted_body = adjustFormFields(body, path) - output.append(adjusted_body) - else: - output.append(body) + output.append(path and adjustFormFields(body, path) or body) # Include form sections only if fields exist for those sections. @@ -229,6 +350,59 @@ return "".join(output) +def getFormFields(body, path, request): + + "Return a dictionary of fields from the given 'body' at the given 'path'." + + fields = {} + cache = {} + type = None + + for i, match in enumerate(form_field_regexp.split(body)): + state = i % 3 + + if state == 1: + type = match + elif state == 2 and type == "Field": + args = {} + + # Obtain the macro arguments, adjusted to consider the path. + + name, path, dictpage, label, section = \ + getMacroArguments(adjustMacroArguments(parseMacroArguments(match), path)) + + # Obtain field information from the cache, if possible. + + cache_key = (name, dictpage) + + if cache.has_key(cache_key): + field_args, allowed_values = cache[cache_key] + + # Otherwise, obtain field information from any WikiDict. + + else: + field_args = {} + allowed_values = None + + if dictpage: + wikidict = getWikiDict(dictpage, request) + if wikidict: + field_definition = wikidict.get(name) + if field_definition: + field_args = getFieldArguments(field_definition) + if field_args.has_key("source"): + sourcedict = getWikiDict(field_args["source"], request) + if sourcedict: + allowed_values = sourcedict.keys() + + cache[cache_key] = field_args, allowed_values + + # Store the field information. + + fields[name] = path, dictpage, label, section, field_args, allowed_values + + return fields + def adjustFormFields(body, path): """ @@ -269,6 +443,9 @@ arguments. """ + if not path: + return args + result = [] old_path = None @@ -298,6 +475,40 @@ return [arg for arg in parsed_args if arg] +def getMacroArguments(parsed_args): + + "Return the macro arguments decoded from 'parsed_args'." + + name = None + path = None + dictpage = None + label = None + section = None + + for arg in parsed_args: + if arg.startswith("name="): + name = arg[5:] + + elif arg.startswith("path="): + path = arg[5:] + + elif arg.startswith("dict="): + dictpage = arg[5:] + + elif arg.startswith("label="): + label = arg[6:] + + elif arg.startswith("section="): + section = arg[8:] + + elif name is None: + name = arg + + elif dictpage is None: + dictpage = arg + + return name, path, dictpage, label, section + def getFields(d, remove=False): """ diff -r 678a687f520b -r 11d6e15c8b0c macros/FormField.py --- a/macros/FormField.py Sun Dec 02 15:43:39 2012 +0100 +++ b/macros/FormField.py Mon Dec 03 00:51:41 2012 +0100 @@ -8,7 +8,7 @@ from MoinMoin import wikiutil from MoinSupport import * -from MoinForms import getFieldArguments, parseMacroArguments +from MoinForms import getFieldArguments, parseMacroArguments, getMacroArguments Dependencies = ['pages'] @@ -44,33 +44,7 @@ # Get special arguments. - name = None - path = None - dictpage = None - label = None - section = None - - for arg in parsed_args: - if arg.startswith("name="): - name = arg[5:] - - elif arg.startswith("path="): - path = arg[5:] - - elif arg.startswith("dict="): - dictpage = arg[5:] - - elif arg.startswith("label="): - label = arg[6:] - - elif arg.startswith("section="): - section = arg[8:] - - elif name is None: - name = arg - - elif dictpage is None: - dictpage = arg + name, path, dictpage, label, section = getMacroArguments(parsed_args) if not name: return showError(_("No field name specified."), request) @@ -141,14 +115,14 @@ if not sourcedict: return showError(_("WikiDict %s cannot be loaded for %s.") % (sourcedict, name), request) - size = field_args.get("size", [None])[0] - size = size and int(size) or size + maxselected = field_args.get("maxselected", [None])[0] + maxselected = maxselected and int(maxselected) or maxselected output = [] - output.append(fmt.rawHTML('' % (escattr(ref), maxselected != 1 and "multiple" or ""))) for option, label in sourcedict.items(): - is_selected = size == 1 and option == value or size != 1 and option in values + is_selected = maxselected == 1 and option == value or maxselected != 1 and option in values output.append(fmt.rawHTML('' % ( escattr(option), is_selected and "selected" or "", escape(label))