# HG changeset patch # User Paul Boddie # Date 1360436138 -3600 # Node ID 3e82a1ac3057fdefdded0a2c6c6d97067e1086fe # Parent 603adb24b5299904aadfd52d0ac3f444447e4e48 Prevented the expansion of sections in forms unrelated to the current request. Introduced the "finishing" attribute to indicate the form fields that cause the handling of a form to be considered finished (and thus causing the form data to be stored for the default action). Added form loading, storing the current form attributes in the action instance. diff -r 603adb24b529 -r 3e82a1ac3057 MoinForms.py --- a/MoinForms.py Sat Feb 09 19:50:37 2013 +0100 +++ b/MoinForms.py Sat Feb 09 19:55:38 2013 +0100 @@ -29,9 +29,16 @@ self.pagename = pagename self.request = request self.access_handler = None + self.attributes = None - def getAccessHandler(self, attributes): - return FormAccess(self.pagename, self.request, attributes) + def getAccessHandler(self): + + """ + Return an access handler for the form whose attributes have been + obtained and stored in this instance. + """ + + return FormAccess(self.pagename, self.request, self.attributes) def processForm(self): @@ -47,35 +54,54 @@ form = get_form(self.request) fields = getFields(form, remove=True) - # Modify and validate the form. - - self.modifyFields(fields) - - # Get the form definition. + # Detect any request to load data. - attributes, text = self.getFormForFragment(fields) - structure = getFormStructure(text, self.request) - self.access_handler = self.getAccessHandler(attributes) + if fields.has_key("load"): + try: + number = int(fields["load"][0]) + except ValueError: + fields = {} + else: + self.attributes, text = self.getFormForFragment(fields) + self.access_handler = self.getAccessHandler() + fields = self.loadFields(number) - # Check the permissions on the form. + self.unfinished(fields, form) + + # Otherwise, process any supplied data. + + else: + # Modify and validate the form. + + self.modifyFields(fields) + + # Get the form definition. - if not self.checkPermissions("write"): - self.request.theme.add_msg(_("You do not appear to have access to this form."), "error") - do_show(self.pagename, self.request) - return + self.attributes, text = self.getFormForFragment(fields) + self.access_handler = self.getAccessHandler() + structure = getFormStructure(text, self.request) + + # Check the permissions on the form. - # Without any form definition, the page is probably the wrong one. + if not self.checkPermissions("write"): + self.request.theme.add_msg(_("You do not appear to have access to this form."), "error") + do_show(self.pagename, self.request) + return + + # Without any form definition, the page is probably the wrong one. - if not structure: - self.request.theme.add_msg(_("This page does not provide a form."), "error") - do_show(self.pagename, self.request) - return + if not structure: + self.request.theme.add_msg(_("This page does not provide a form."), "error") + do_show(self.pagename, self.request) + return - # With a form definition, attempt to validate the fields. + # With a form definition, attempt to validate the fields. - if self.validateFields(fields, structure): - self.finished(fields, form) - else: + if self.validateFields(fields, structure): + if self.shouldFinish(fields): + self.finished(fields, form) + return + self.unfinished(fields, form) def finished(self, fields, form): @@ -94,6 +120,23 @@ self.serialiseFields(fields, form) do_show(self.pagename, self.request) + def shouldFinish(self, fields): + + """ + Subject to the attributes stored for the form in this instance, return + whether any field referenced by the "finishing" attribute is present + and thus indicate whether the form handling should finish. + """ + + finishing = self.attributes.has_key("finishing") and self.attributes["finishing"].split(",") + + if finishing: + for name in finishing: + if fields.has_key(name): + return True + + return False + def getFormForFragment(self, fields): "Return the attributes and text of the form being handled." @@ -249,10 +292,7 @@ def storeFields(self, fields): - """ - Store the given 'fields' as a Python object representation, subject to - the given 'attributes'. - """ + "Store the given 'fields' as a Python object representation." store = FormStore(self.access_handler) store.append(repr(fields)) @@ -264,18 +304,6 @@ store = FormStore(self.access_handler) return loadFields(store, number) -def loadFieldsForRequest(number, attrs, request): - - """ - Load the fields corresponding to the given record 'number', using the form - 'attrs' to control access to the form data, and using the 'request' for - access and retrieval purposes. - """ - - access_handler = FormAccess(request.page.page_name, request, attrs) - store = FormStore(access_handler) - return loadFields(store, number) - def loadFields(store, number): """ @@ -460,7 +488,7 @@ # Common formatting functions. -def getFormOutput(text, fields, path=None, fragment=None, repeating=None, index=None): +def getFormOutput(text, fields, form_fragment=None, path=None, fragment=None, repeating=None, index=None): """ Combine regions found in the given 'text' and then return them as a single @@ -472,16 +500,22 @@ The given 'fields' are used to populate fields provided in forms and to control whether sections are populated or not. + The optional 'form_fragment' is used to indicate the form to which the + fields belong. + The optional 'path' is used to adjust form fields to refer to the correct part of the form hierarchy. - The optional 'fragment' is used to indicate the form to which the fields - belong. + The optional 'fragment' is used to indicate the form being output. If this + value is different to 'form_fragment', the structure of the form should not + be influenced by the 'fields'. The optional 'repeating' and 'index' is used to refer to individual values of a designated field. """ + this_form = fragment and form_fragment == fragment or not fragment and not form_fragment + output = [] section = fields @@ -501,9 +535,14 @@ message_name = attributes.get("message") absent_message_name = attributes.get("not-message") + # Ignore sections not related to the supplied field data. + + if not this_form: + pass + # Sections are groups of fields in their own namespace. - if section_name and section.has_key(section_name): + elif section_name and section.has_key(section_name): # Iterate over the section contents ignoring the given indexes. @@ -512,7 +551,7 @@ # Get the output for the section. - output.append(getFormOutput(body, element, + output.append(getFormOutput(body, element, form_fragment, path and ("%s/%s" % (path, element_ref)) or element_ref, fragment)) # Message regions are conditional on a particular field and @@ -522,21 +561,21 @@ if attributes.get("repeating"): for index in range(0, len(section[message_name])): - output.append(getFormOutput(body, section, path, fragment, message_name, index)) + output.append(getFormOutput(body, section, form_fragment, path, fragment, message_name, index)) else: - output.append(getFormOutput(body, section, path, fragment)) + output.append(getFormOutput(body, section, form_fragment, path, fragment)) # Not-message regions are conditional on a particular field being # absent. They reference the current namespace. elif absent_message_name and not section.has_key(absent_message_name): - output.append(getFormOutput(body, section, path, fragment)) + output.append(getFormOutput(body, section, form_fragment, path, fragment)) # Inspect and include other regions. else: output.append(header) - output.append(getFormOutput(body, section, path, fragment, repeating, index)) + output.append(getFormOutput(body, section, form_fragment, path, fragment, repeating, index)) output.append(close) return "".join(output) @@ -837,7 +876,9 @@ write = write or request.write page = request.page - fields = getFields(get_form(request)) + form = get_form(request) + form_fragment = form.get("fragment", [None])[0] + fields = getFields(form) # Prepare the query string for the form action URL. @@ -861,7 +902,7 @@ # Obtain page text for the form, incorporating subregions and applicable # sections. - output = getFormOutput(text, fields, fragment=fragment) + output = getFormOutput(text, fields, form_fragment=form_fragment, fragment=fragment) write(formatText(output, request, fmt, inhibit_p=False)) write(fmt.rawHTML('')) diff -r 603adb24b529 -r 3e82a1ac3057 pages/HelpOnMoinForms --- a/pages/HelpOnMoinForms Sat Feb 09 19:50:37 2013 +0100 +++ b/pages/HelpOnMoinForms Sat Feb 09 19:55:38 2013 +0100 @@ -14,7 +14,7 @@ To embed a form within a page, a special region must be declared as in the following example: {{{{ -{{{#!form fragment=exampleform +{{{#!form fragment=exampleform finishing=finish || '''Name''' || <> || || '''Address''' || <> || @@ -34,7 +34,7 @@ The above form definition produces the following form: -{{{#!form fragment=exampleform +{{{#!form fragment=exampleform finishing=finish || '''Name''' || <> || || '''Address''' || <> || @@ -52,17 +52,17 @@ === Defining Form Regions === {{{{ -{{{#!form fragment= action= +{{{#!form fragment= action= finishing=[,...] ... }}} }}}} -Form regions must be defined using the special `#!form` notation and should provide a `fragment` identifier that uniquely identifies the form on the page, along with an `action` identifier indicating which action should be used to interpret and process the form data. If the `action` argument is missing, the default `MoinFormHandlerAction` is used. +Form regions must be defined using the special `#!form` notation and should provide a `fragment` identifier that uniquely identifies the form on the page, along with an `action` identifier indicating which action should be used to interpret and process the form data. If the `action` argument is missing, the default `MoinFormHandlerAction` is used. The `finishing` argument should list field names separated by commas that cause a validated form to be finished and thus stored (in the default action). Normal Wiki markup can be used within form regions, but care must be taken to ensure that where other regions are embedded within form regions, the number of brackets used to declare the outer regions is greater than the number used to declare the embedded regions. This technique is described on the HelpOnParsers page, but is also illustrated below. {{{{{ -{{{{#!form fragment=exampleform action=ExampleAction +{{{{#!form fragment=exampleform action=ExampleAction finishing=finish Fill out the form below: @@ -127,7 +127,7 @@ Some kinds of forms require collections of fields that may be optional or repeating and that may be logically grouped. Form sections permit fields to be declared within their own namespace or group such that they may be collectively added, removed or replicated. For example: {{{{{ -{{{{#!form fragment=exampleform2 +{{{{#!form fragment=exampleform2 finishing=finish || '''Name''' || <> || || '''Address''' || <> || @@ -144,7 +144,7 @@ This produces the following form: -{{{{#!form fragment=exampleform2 +{{{{#!form fragment=exampleform2 finishing=finish || '''Name''' || <> || || '''Address''' || <> || @@ -172,7 +172,7 @@ Message sections are introduced in forms like normal sections but using the `message` argument in the form region's declaration. For example: {{{{{ -{{{{#!form fragment=exampleform3 +{{{{#!form fragment=exampleform3 finishing=finish || '''Name''' || <> || {{{#!form message=name-error @@ -186,7 +186,7 @@ The form described above should look like this: -{{{{#!form fragment=exampleform3 +{{{{#!form fragment=exampleform3 finishing=finish || '''Name''' || <> || {{{#!form message=name-error @@ -206,7 +206,7 @@ Each field can potentially have many errors associated with it and stored in a separate error field. To be able to show all the errors, as opposed to only the first one, a `repeating` argument can be specified in the declaration of the message section. For example: {{{{{ -{{{{#!form fragment=exampleform4 +{{{{#!form fragment=exampleform4 finishing=finish || '''Name''' || <> || {{{#!form message=name-error repeating @@ -220,7 +220,7 @@ The form described above should look like this: -{{{{#!form fragment=exampleform4 +{{{{#!form fragment=exampleform4 finishing=finish || '''Name''' || <> || {{{#!form message=name-error repeating