1.1 --- a/MoinForms.py Thu Nov 29 00:53:51 2012 +0100
1.2 +++ b/MoinForms.py Sat Dec 01 20:38:35 2012 +0100
1.3 @@ -7,12 +7,14 @@
1.4 """
1.5
1.6 from MoinMoin import wikiutil
1.7 -from StringIO import StringIO
1.8 from MoinSupport import *
1.9 import re
1.10
1.11 __version__ = "0.1"
1.12
1.13 +form_field_regexp_str = r"<<FormField\((.*?)\)>>"
1.14 +form_field_regexp = re.compile(form_field_regexp_str, re.DOTALL)
1.15 +
1.16 # Common formatting functions.
1.17
1.18 def formatForm(text, request, fmt, attrs=None, write=None):
1.19 @@ -30,7 +32,13 @@
1.20
1.21 fields = getFields(get_form(request))
1.22
1.23 - querystr = attrs and attrs.has_key("action") and ("action=%s" % attrs["action"]) or None
1.24 + queryparams = []
1.25 +
1.26 + for argname in ["fragment", "action"]:
1.27 + if attrs and attrs.has_key(argname):
1.28 + queryparams.append("%s=%s" % (argname, attrs[argname]))
1.29 +
1.30 + querystr = "&".join(queryparams)
1.31
1.32 write(fmt.rawHTML('<form method="post" action="%s">' %
1.33 escattr(page.url(request, querystr))
1.34 @@ -60,8 +68,6 @@
1.35 for region in getRegions(text, True):
1.36 format, attributes, body, header, close = getFragmentFromRegion(region)
1.37
1.38 - # NOTE: Need to adjust FormField macros to use hierarchical names.
1.39 -
1.40 # Include bare regions as they are.
1.41
1.42 if format is None:
1.43 @@ -72,9 +78,15 @@
1.44 elif format == "form":
1.45 section_name = attributes.get("section")
1.46 if section_name and section.has_key(section_name):
1.47 - output.append(header)
1.48 - output.append(getFormOutput(body, section[section_name]))
1.49 - output.append(close)
1.50 +
1.51 + # Iterate over the section contents ignoring the given indexes.
1.52 +
1.53 + for index, element in enumerate(getSectionElements(section[section_name])):
1.54 +
1.55 + # Adjust FormField macros to use hierarchical names.
1.56 +
1.57 + adjusted_body = adjustFormFields(body, section_name, index)
1.58 + output.append(getFormOutput(adjusted_body, element))
1.59
1.60 # Inspect and include other regions.
1.61
1.62 @@ -85,40 +97,157 @@
1.63
1.64 return "".join(output)
1.65
1.66 -def getFields(d):
1.67 +def adjustFormFields(body, section_name, index):
1.68 +
1.69 + """
1.70 + Return a version of the 'body' with the names in FormField macros updated to
1.71 + incorporate the given 'section_name' and 'index'.
1.72 + """
1.73 +
1.74 + result = []
1.75 + in_macro = False
1.76 +
1.77 + for match in form_field_regexp.split(body):
1.78 + if not in_macro:
1.79 + result.append(match)
1.80 + else:
1.81 + result.append("<<FormField(%s)>>" % ",".join(
1.82 + adjustMacroArguments(parseMacroArguments(match), section_name, index)
1.83 + ))
1.84 +
1.85 + in_macro = not in_macro
1.86 +
1.87 + return "".join(result)
1.88 +
1.89 +def adjustMacroArguments(args, section_name, index):
1.90
1.91 """
1.92 - Return the form fields hierarchy for the given dictionary 'd'.
1.93 + Adjust the given 'args' so that the path incorporates the given
1.94 + 'section_name' and 'index', returning a new list containing the revised
1.95 + path and remaining arguments.
1.96 + """
1.97 +
1.98 + result = []
1.99 + path = None
1.100 +
1.101 + for arg in args:
1.102 + if arg.startswith("path="):
1.103 + path = arg[5:]
1.104 + else:
1.105 + result.append(arg)
1.106 +
1.107 + qualified = "%s%s$%s" % (path and ("%s/" % path) or "", section_name, index)
1.108 + result.append("path=%s" % qualified)
1.109 +
1.110 + return result
1.111 +
1.112 +def parseMacroArguments(args):
1.113 +
1.114 + """
1.115 + Interpret the arguments.
1.116 + NOTE: The argument parsing should really be more powerful in order to
1.117 + NOTE: support labels.
1.118 + """
1.119 +
1.120 + try:
1.121 + parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or []
1.122 + except AttributeError:
1.123 + parsed_args = args.split(",")
1.124 +
1.125 + return [arg for arg in parsed_args if arg]
1.126 +
1.127 +def getFields(d, remove=False):
1.128 +
1.129 + """
1.130 + Return the form fields hierarchy for the given dictionary 'd'. If the
1.131 + optional 'remove' parameter is set to a true value, remove the entries for
1.132 + the fields from 'd'.
1.133 """
1.134
1.135 fields = {}
1.136
1.137 for key, value in d.items():
1.138
1.139 + # Detect modifying fields.
1.140 +
1.141 + if key.find("=") != -1:
1.142 + fields[key] = value
1.143 + if remove:
1.144 + del d[key]
1.145 + continue
1.146 +
1.147 # Reproduce the original hierarchy of the fields.
1.148
1.149 section = fields
1.150 - parts = key.split("/")
1.151 + parts = getPathDetails(key)
1.152
1.153 - for part in parts[:-1]:
1.154 - try:
1.155 - name, index = part.split("$", 1)
1.156 - index = int(index)
1.157 - except ValueError:
1.158 - name, index = part, None
1.159 + for name, index in parts[:-1]:
1.160 +
1.161 + # Add an entry for instances of the section.
1.162
1.163 if not section.has_key(name):
1.164 section[name] = {}
1.165
1.166 + # Add an entry for the specific instance of the section.
1.167 +
1.168 if not section[name].has_key(index):
1.169 section[name][index] = {}
1.170
1.171 section = section[name][index]
1.172
1.173 - section[parts[-1]] = value
1.174 + section[parts[-1][0]] = value
1.175 +
1.176 + if remove:
1.177 + del d[key]
1.178
1.179 return fields
1.180
1.181 +def getPathDetails(path):
1.182 + parts = []
1.183 +
1.184 + for part in path.split("/"):
1.185 + try:
1.186 + name, index = part.split("$", 1)
1.187 + index = int(index)
1.188 + except ValueError:
1.189 + name, index = part, None
1.190 +
1.191 + parts.append((name, index))
1.192 +
1.193 + return parts
1.194 +
1.195 +def getSectionForPath(path, fields):
1.196 +
1.197 + """
1.198 + Obtain the section indicated by the given 'path' from the 'fields',
1.199 + returning a tuple of the form (parent section, (name, index)), where the
1.200 + parent section contains the referenced section, where name is the name of
1.201 + the referenced section, and where index, if not None, is the index of a
1.202 + specific section instance within the named section.
1.203 + """
1.204 +
1.205 + parts = getPathDetails(path)
1.206 + section = fields
1.207 +
1.208 + for name, index in parts[:-1]:
1.209 + section = fields[name][index]
1.210 +
1.211 + return section, parts[-1]
1.212 +
1.213 +def getSectionElements(section_elements):
1.214 +
1.215 + "Return the given 'section_elements' as an ordered collection."
1.216 +
1.217 + keys = map(int, section_elements.keys())
1.218 + keys.sort()
1.219 +
1.220 + elements = []
1.221 +
1.222 + for key in keys:
1.223 + elements.append(section_elements[key])
1.224 +
1.225 + return elements
1.226 +
1.227 def formatFormForOutputType(text, request, mimetype, attrs=None, write=None):
1.228
1.229 """
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/actions/MoinFormHandler.py Sat Dec 01 20:38:35 2012 +0100
2.3 @@ -0,0 +1,113 @@
2.4 +# -*- coding: iso-8859-1 -*-
2.5 +"""
2.6 + MoinMoin - MoinFormHandler Action
2.7 +
2.8 + @copyright: 2012 by Paul Boddie <paul@boddie.org.uk>
2.9 + @license: GNU GPL (v2 or later), see COPYING.txt for details.
2.10 +"""
2.11 +
2.12 +from MoinMoin.action import do_show
2.13 +from MoinMoin import config
2.14 +from MoinForms import *
2.15 +
2.16 +Dependencies = ['pages']
2.17 +
2.18 +class MoinFormHandlerAction:
2.19 +
2.20 + "A handler action that can be specialised for individual forms."
2.21 +
2.22 + def __init__(self, pagename):
2.23 + self.pagename = pagename
2.24 +
2.25 + def processForm(self, request):
2.26 +
2.27 + """
2.28 + Interpret the given 'request' details and modify them according to the
2.29 + structure of the interpreted information.
2.30 + """
2.31 +
2.32 + # Get the form fields and obtain the hierarchical field structure.
2.33 +
2.34 + form = get_form(request)
2.35 + fields = getFields(form, remove=True)
2.36 +
2.37 + # Process modification operations.
2.38 +
2.39 + self.modifyFields(fields)
2.40 + self.serialiseFields(fields, form)
2.41 +
2.42 + do_show(self.pagename, request)
2.43 +
2.44 + def serialiseFields(self, fields, form, path=None):
2.45 +
2.46 + """
2.47 + Serialise the given 'fields' to the given 'form', using the given 'path'
2.48 + to name the entries.
2.49 + """
2.50 +
2.51 + for key, value in fields.items():
2.52 +
2.53 + # Serialise sections.
2.54 +
2.55 + if isinstance(value, dict):
2.56 + for index, element in enumerate(getSectionElements(value)):
2.57 + element_ref = "%s$%s" % (key, index)
2.58 +
2.59 + self.serialiseFields(element, form,
2.60 + path and ("%s/%s" % (path, element_ref)) or element_ref
2.61 + )
2.62 +
2.63 + # Serialise fields.
2.64 +
2.65 + else:
2.66 + form[path and ("%s/%s" % (path, key)) or key] = value
2.67 +
2.68 + def modifyFields(self, fields):
2.69 +
2.70 + "Modify the given 'fields', removing and adding items."
2.71 +
2.72 + # First, remove fields.
2.73 +
2.74 + for key in fields.keys():
2.75 + if key.startswith("_remove="):
2.76 + self.removeField(key[8:], fields)
2.77 +
2.78 + # Then, add fields.
2.79 +
2.80 + for key in fields.keys():
2.81 + if key.startswith("_add="):
2.82 + self.addField(key[5:], fields)
2.83 +
2.84 + def removeField(self, path, fields):
2.85 +
2.86 + """
2.87 + Remove the section element indicated by the given 'path' from the
2.88 + 'fields'.
2.89 + """
2.90 +
2.91 + section, (name, index) = getSectionForPath(path, fields)
2.92 + del section[name][index]
2.93 +
2.94 + def addField(self, path, fields):
2.95 +
2.96 + """
2.97 + Add a section element indicated by the given 'path' to the 'fields'.
2.98 + """
2.99 +
2.100 + section, (name, index) = getSectionForPath(path, fields)
2.101 + placeholder = {"_new" : ""}
2.102 +
2.103 + if section.has_key(name):
2.104 + indexes = section[name].keys()
2.105 + max_index = max(map(int, indexes))
2.106 + section[name][max_index + 1] = placeholder
2.107 + else:
2.108 + max_index = -1
2.109 + section[name] = {0 : placeholder}
2.110 +
2.111 +# Action function.
2.112 +
2.113 +def execute(pagename, request):
2.114 + MoinFormHandlerAction(pagename).processForm(request)
2.115 +
2.116 +# vim: tabstop=4 expandtab shiftwidth=4
3.1 --- a/macros/FormField.py Thu Nov 29 00:53:51 2012 +0100
3.2 +++ b/macros/FormField.py Sat Dec 01 20:38:35 2012 +0100
3.3 @@ -8,6 +8,7 @@
3.4
3.5 from MoinMoin import wikiutil
3.6 from MoinSupport import *
3.7 +from MoinForms import parseMacroArguments
3.8
3.9 Dependencies = ['pages']
3.10
3.11 @@ -26,6 +27,7 @@
3.12 The following optional named arguments are also supported:
3.13
3.14 label=TEXT The label employed by button-like fields
3.15 + path=PATH The location of the field in the form section hierarchy
3.16
3.17 The nature of each field is described by a WikiDict entry for the given
3.18 field name.
3.19 @@ -37,32 +39,33 @@
3.20 _ = request.getText
3.21
3.22 # Interpret the arguments.
3.23 - # NOTE: The argument parsing should really be more powerful in order to
3.24 - # NOTE: support labels.
3.25
3.26 - try:
3.27 - parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or []
3.28 - except AttributeError:
3.29 - parsed_args = args.split(",")
3.30 -
3.31 - parsed_args = [arg for arg in parsed_args if arg]
3.32 + parsed_args = parseMacroArguments(args)
3.33
3.34 # Get special arguments.
3.35
3.36 name = None
3.37 + path = None
3.38 dictpage = None
3.39 label = None
3.40 + section = None
3.41
3.42 for arg in parsed_args:
3.43 if arg.startswith("name="):
3.44 name = arg[5:]
3.45
3.46 + elif arg.startswith("path="):
3.47 + path = arg[5:]
3.48 +
3.49 elif arg.startswith("dict="):
3.50 dictpage = arg[5:]
3.51
3.52 elif arg.startswith("label="):
3.53 label = arg[6:]
3.54
3.55 + elif arg.startswith("section="):
3.56 + section = arg[8:]
3.57 +
3.58 elif name is None:
3.59 name = arg
3.60
3.61 @@ -72,41 +75,55 @@
3.62 if not name:
3.63 return showError(_("No field name specified."), request)
3.64
3.65 - if not dictpage:
3.66 - return showError(_("No WikiDict specified."), request)
3.67 + # Detect special modification fields.
3.68 +
3.69 + if name in ("_add", "_remove"):
3.70 + field_args = {"type" : "submit"}
3.71 +
3.72 + # The field name is a combination of the name, path and section.
3.73 +
3.74 + ref = "%s=%s%s%s" % (name, path or "", path and section and "/" or "", section or "")
3.75
3.76 # Get the WikiDict and the field's definition.
3.77
3.78 - wikidict = getWikiDict(dictpage, request)
3.79 -
3.80 - if not wikidict:
3.81 - return showError(_("WikiDict %s cannot be loaded.") % dictpage, request)
3.82 + elif dictpage:
3.83 + wikidict = getWikiDict(dictpage, request)
3.84
3.85 - try:
3.86 - field_definition = wikidict[name]
3.87 - except KeyError:
3.88 - return showError(_("No entry for %s in %s.") % (name, dictpage), request)
3.89 -
3.90 - field_args = {}
3.91 -
3.92 - for field_arg in field_definition.split():
3.93 -
3.94 - # Record the key-value details.
3.95 + if not wikidict:
3.96 + return showError(_("WikiDict %s cannot be loaded for %s.") % (dictpage, name), request)
3.97
3.98 try:
3.99 - argname, argvalue = field_arg.split("=", 1)
3.100 - field_args[argname] = argvalue
3.101 + field_definition = wikidict[name]
3.102 + except KeyError:
3.103 + return showError(_("No entry for %s in %s.") % (name, dictpage), request)
3.104 +
3.105 + field_args = {}
3.106 +
3.107 + for field_arg in field_definition.split():
3.108 +
3.109 + # Record the key-value details.
3.110
3.111 - # Single keywords are interpreted as type descriptions.
3.112 + try:
3.113 + argname, argvalue = field_arg.split("=", 1)
3.114 + field_args[argname] = argvalue
3.115 +
3.116 + # Single keywords are interpreted as type descriptions.
3.117
3.118 - except ValueError:
3.119 - if not field_args.has_key("type"):
3.120 - field_args["type"] = field_arg
3.121 + except ValueError:
3.122 + if not field_args.has_key("type"):
3.123 + field_args["type"] = field_arg
3.124 +
3.125 + # The field name is a combination of the path and the name.
3.126 +
3.127 + ref = "%s%s" % (path and ("%s/" % path) or "", name)
3.128 +
3.129 + else:
3.130 + return showError(_("No WikiDict specified for %s.") % name, request)
3.131
3.132 # Obtain any request parameters corresponding to the field.
3.133
3.134 form = get_form(request)
3.135 - value = form.get(name, [""])[0]
3.136 + value = form.get(ref, [""])[0]
3.137
3.138 # Render the field.
3.139
3.140 @@ -114,23 +131,42 @@
3.141
3.142 if type == "text":
3.143 return fmt.rawHTML('<input name="%s" type="%s" size="%s" value="%s" />' % (
3.144 - escattr(name), escattr(type), escattr(field_args.get("size", "10")), escattr(value)
3.145 + escattr(ref), escattr(type), escattr(field_args.get("size", "10")), escattr(value)
3.146 ))
3.147
3.148 elif type == "textarea":
3.149 return fmt.rawHTML('<textarea name="%s" cols="%s" rows="%s">%s</textarea>' % (
3.150 - escattr(name), escattr(field_args.get("cols", "60")), escattr(field_args.get("rows", "5")), escape(value)
3.151 + escattr(ref), escattr(field_args.get("cols", "60")), escattr(field_args.get("rows", "5")), escape(value)
3.152 ))
3.153
3.154 elif type == "submit":
3.155 return fmt.rawHTML('<input name="%s" type="submit" value="%s" />' % (
3.156 - escattr(name), escattr(_(label or name))
3.157 + escattr(ref), escattr(_(label or name))
3.158 ))
3.159
3.160 - # NOTE: Support select fields at the very least.
3.161 + elif type == "select":
3.162 +
3.163 + if not field_args.has_key("source"):
3.164 + return showError(_("No source dictionary given for %s.") % name, request)
3.165 +
3.166 + sourcedict = getWikiDict(field_args["source"], request)
3.167 +
3.168 + if not sourcedict:
3.169 + return showError(_("WikiDict %s cannot be loaded for %s.") % (sourcedict, name), request)
3.170 +
3.171 + output = []
3.172 + output.append(fmt.rawHTML('<select name="%s">' % escattr(ref)))
3.173 +
3.174 + for option, label in sourcedict.items():
3.175 + output.append(fmt.rawHTML('<option value="%s" %s>%s</option>' % (
3.176 + escattr(option), value == option and "selected" or "", escape(label))
3.177 + ))
3.178 +
3.179 + output.append(fmt.rawHTML('</select>'))
3.180 + return u''.join(output)
3.181
3.182 else:
3.183 - return ''
3.184 + return u''
3.185
3.186 def showError(text, request):
3.187 fmt = request.formatter
4.1 --- a/parsers/form.py Thu Nov 29 00:53:51 2012 +0100
4.2 +++ b/parsers/form.py Sat Dec 01 20:38:35 2012 +0100
4.3 @@ -33,9 +33,7 @@
4.4
4.5 self.raw = raw
4.6 self.request = request
4.7 - attrs = parseAttributes(kw.get("format_args", ""), False)
4.8 -
4.9 - self.fragment = attrs.get("fragment")
4.10 + self.attrs = parseAttributes(kw.get("format_args", ""), False)
4.11
4.12 def format(self, fmt, write=None):
4.13
4.14 @@ -45,7 +43,7 @@
4.15 using the request.
4.16 """
4.17
4.18 - formatForm(self.raw, self.request, fmt, write=write)
4.19 + formatForm(self.raw, self.request, fmt, self.attrs, write=write)
4.20
4.21 # Extra API methods.
4.22
4.23 @@ -57,7 +55,7 @@
4.24 request.
4.25 """
4.26
4.27 - formatFormForOutputType(self.raw, self.request, mimetype, write=write)
4.28 + formatFormForOutputType(self.raw, self.request, mimetype, self.attrs, write=write)
4.29
4.30 # Class methods.
4.31