paul@19 | 1 | ##master-page:HelpTemplate |
paul@19 | 2 | ##master-date:Unknown-Date |
paul@19 | 3 | #format wiki |
paul@19 | 4 | #language en |
paul@19 | 5 | |
paul@19 | 6 | == MoinForms == |
paul@19 | 7 | |
paul@19 | 8 | The !MoinForms support for !MoinMoin provides a way of describing Web forms that can be embedded in Wiki pages and handled by special Wiki actions. |
paul@19 | 9 | |
paul@19 | 10 | <<TableOfContents(3)>> |
paul@19 | 11 | |
paul@19 | 12 | == Creating Forms == |
paul@19 | 13 | |
paul@19 | 14 | To embed a form within a page, a special region must be declared as in the following example: |
paul@19 | 15 | |
paul@19 | 16 | {{{{ |
paul@29 | 17 | {{{#!form fragment=exampleform finishing=finish |
paul@19 | 18 | |
paul@19 | 19 | || '''Name''' || <<FormField(name,ExampleFormDict)>> || |
paul@19 | 20 | || '''Address''' || <<FormField(address,ExampleFormDict)>> || |
paul@19 | 21 | || '''Country''' || <<FormField(country,ExampleFormDict)>> || |
paul@19 | 22 | || || <<FormField(finish,ExampleFormDict,label=Finish)>> || |
paul@19 | 23 | |
paul@19 | 24 | }}} |
paul@19 | 25 | }}}} |
paul@19 | 26 | |
paul@19 | 27 | This example demonstrates... |
paul@19 | 28 | |
paul@19 | 29 | * A region providing a form |
paul@19 | 30 | * Within which form fields can be inserted using the `FormField` macro |
paul@19 | 31 | * Using normal Wiki syntax, meaning that normal formatting facilities can be used to present forms (with a table being used in this case) |
paul@19 | 32 | |
paul@19 | 33 | Here, four form fields are used, but their definitions are not provided in the form region itself. Instead, each of the fields references a !WikiDict providing such definition details, and this is described [[#Defining_Fields|below]]. |
paul@19 | 34 | |
paul@19 | 35 | The above form definition produces the following form: |
paul@19 | 36 | |
paul@29 | 37 | {{{#!form fragment=exampleform finishing=finish |
paul@19 | 38 | |
paul@19 | 39 | || '''Name''' || <<FormField(name,ExampleFormDict)>> || |
paul@19 | 40 | || '''Address''' || <<FormField(address,ExampleFormDict)>> || |
paul@19 | 41 | || '''Country''' || <<FormField(country,ExampleFormDict)>> || |
paul@19 | 42 | || || <<FormField(finish,ExampleFormDict,label=Finish)>> || |
paul@19 | 43 | |
paul@19 | 44 | }}} |
paul@19 | 45 | |
paul@21 | 46 | To define labels that contain commas or brackets, quote them as follows: |
paul@21 | 47 | |
paul@21 | 48 | {{{ |
paul@21 | 49 | <<FormField(finish,ExampleFormDict,"label=Finish, once again, with the form. (Yes, I am sure.)")>> |
paul@21 | 50 | }}} |
paul@21 | 51 | |
paul@19 | 52 | === Defining Form Regions === |
paul@19 | 53 | |
paul@19 | 54 | {{{{ |
paul@29 | 55 | {{{#!form fragment=<fragment> action=<action> finishing=<field>[,<field>...] |
paul@19 | 56 | ... |
paul@19 | 57 | }}} |
paul@19 | 58 | }}}} |
paul@19 | 59 | |
paul@29 | 60 | 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). |
paul@19 | 61 | |
paul@19 | 62 | 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. |
paul@19 | 63 | |
paul@19 | 64 | {{{{{ |
paul@29 | 65 | {{{{#!form fragment=exampleform action=ExampleAction finishing=finish |
paul@19 | 66 | |
paul@19 | 67 | Fill out the form below: |
paul@19 | 68 | |
paul@19 | 69 | {{{#!wiki important |
paul@19 | 70 | Be sure to fill out '''everything'''! |
paul@19 | 71 | }}} |
paul@19 | 72 | |
paul@19 | 73 | Name: <<FormField(name,ExampleFormDict)>> |
paul@19 | 74 | Address: <<FormField(address,ExampleFormDict)>> |
paul@19 | 75 | Done? <<FormField(finish,ExampleFormDict,label=Finish)>> |
paul@19 | 76 | }}}} |
paul@19 | 77 | }}}}} |
paul@19 | 78 | |
paul@19 | 79 | Note how the form declaration uses `{{{{` and `}}}}` while the [[HelpOnAdmonitions|adminition]] uses `{{{` and `}}}`. |
paul@19 | 80 | |
paul@19 | 81 | === Using the Form Field Macro === |
paul@19 | 82 | |
paul@19 | 83 | {{{ |
paul@19 | 84 | <<FormField(<name>,<WikiDict>,<option>...)>> |
paul@19 | 85 | }}} |
paul@19 | 86 | |
paul@19 | 87 | The `FormField` macro causes a form field to appear in the page having a particular name and employing a particular form control (text field, text area, pull-down menu, button) depending on the definition of the field. The !WikiDict argument must be the name of a page providing a collection of form definitions as described [[#Defining_Fields|below]]. |
paul@19 | 88 | |
paul@19 | 89 | Besides the name and !WikiDict, some other options can be employed to change the nature of the displayed control: |
paul@19 | 90 | |
paul@19 | 91 | || '''Option''' || '''Effect''' || |
paul@19 | 92 | || `label` || provides a label for `submit` fields (buttons) || |
paul@19 | 93 | || `section` || indicates the kind of section to be added to a form by a selector field (having the `_add` name) || |
paul@19 | 94 | |
paul@19 | 95 | The label specified for a `submit` field will be localised according to the user's language settings. If no label is specified, an attempt will be made to localise the field name, but it is recommended that a label always be specified and any required translations be defined in user-specified translation pages (or other appropriate resources) as described on the HelpOnLanguages page. |
paul@19 | 96 | |
paul@19 | 97 | ==== Selector Fields ==== |
paul@19 | 98 | |
paul@19 | 99 | Most fields will rely on a separate definition recorded on the specified !WikiDict page, but a special class of fields known as ''selector'' fields that manipulate the form structure can be included by specifying one of the special names given below: |
paul@19 | 100 | |
paul@19 | 101 | || '''Name''' || '''Purpose''' || |
paul@19 | 102 | || `_add` || adds a section to the form (in conjunction with the `section` option) || |
paul@19 | 103 | || `_remove` || removes a section from the form || |
paul@19 | 104 | |
paul@19 | 105 | The !WikiDict information need not then be specified, but where a [[#Form_Sections|form section]] is to be added, the `section` option may need to be defined to indicate which kind of section is to be added to the form. |
paul@19 | 106 | |
paul@19 | 107 | Selector fields also employ labels which behave just as they do for `submit` fields. |
paul@19 | 108 | |
paul@19 | 109 | === Defining Fields === |
paul@19 | 110 | |
paul@19 | 111 | Each form definition is provided as an entry in a !WikiDict, where each entry in the dictionary corresponds to a particular field name, and where each entry describes the details of any field of that name referencing that dictionary. A !WikiDict describing form fields has the following definition list syntax: |
paul@19 | 112 | |
paul@19 | 113 | {{{ |
paul@19 | 114 | <field name>:: <type> <option>... |
paul@19 | 115 | }}} |
paul@19 | 116 | |
paul@19 | 117 | In the above example, fields reference the ExampleFormDict page, and as a result the `name` field appears as a simple text field, the `address` field as a textarea, the `country` field as a pull-down menu, and the `finish` field as a submit button. How this is achieved can be seen on the ExampleFormDict page, but a summary of the different field types and options is provided here: |
paul@19 | 118 | |
paul@19 | 119 | || '''Type''' || '''Appearance''' || '''Options''' || |
paul@19 | 120 | || `text` || text field || `size`, `required` || |
paul@19 | 121 | || `textarea` || text area || `cols`, `rows`, `required` || |
paul@19 | 122 | || `select` || pull-down menu || `maxselected`, `source`, `required` || |
paul@19 | 123 | || `submit` || submit button || || |
paul@19 | 124 | |
paul@19 | 125 | === Form Sections === |
paul@19 | 126 | |
paul@19 | 127 | 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: |
paul@19 | 128 | |
paul@19 | 129 | {{{{{ |
paul@29 | 130 | {{{{#!form fragment=exampleform2 finishing=finish |
paul@19 | 131 | |
paul@19 | 132 | || '''Name''' || <<FormField(name,ExampleFormDict)>> || |
paul@19 | 133 | || '''Address''' || <<FormField(address,ExampleFormDict)>> || |
paul@19 | 134 | || '''Country''' || <<FormField(country,ExampleFormDict)>> || |
paul@19 | 135 | {{{#!form section=activity |
paul@19 | 136 | || '''Activity/hobby''' || <<FormField(activity,ExampleFormDict)>> || |
paul@19 | 137 | || '''Hours per week''' || <<FormField(duration,ExampleFormDict)>> || |
paul@19 | 138 | || || <<FormField(_remove,label=Remove this activity)>> || |
paul@19 | 139 | }}}|| || <<FormField(_add,section=activity,label=Add activity)>> || |
paul@19 | 140 | || || <<FormField(finish,ExampleFormDict,label=Finish)>> || |
paul@19 | 141 | |
paul@19 | 142 | }}}} |
paul@19 | 143 | }}}}} |
paul@19 | 144 | |
paul@19 | 145 | This produces the following form: |
paul@19 | 146 | |
paul@29 | 147 | {{{{#!form fragment=exampleform2 finishing=finish |
paul@19 | 148 | |
paul@19 | 149 | || '''Name''' || <<FormField(name,ExampleFormDict)>> || |
paul@19 | 150 | || '''Address''' || <<FormField(address,ExampleFormDict)>> || |
paul@19 | 151 | || '''Country''' || <<FormField(country,ExampleFormDict)>> || |
paul@19 | 152 | {{{#!form section=activity |
paul@19 | 153 | || '''Activity/hobby''' || <<FormField(activity,ExampleFormDict)>> || |
paul@19 | 154 | || '''Hours per week''' || <<FormField(duration,ExampleFormDict)>> || |
paul@19 | 155 | || || <<FormField(_remove,label=Remove this activity)>> || |
paul@19 | 156 | }}}|| || <<FormField(_add,section=activity,label=Add activity)>> || |
paul@19 | 157 | || || <<FormField(finish,ExampleFormDict,label=Finish)>> || |
paul@19 | 158 | |
paul@19 | 159 | }}}} |
paul@19 | 160 | |
paul@19 | 161 | == Handling Form Data == |
paul@19 | 162 | |
paul@19 | 163 | !MoinForms supplies a default form handler called `MoinFormHandlerAction` that supports the manipulation of forms by selector fields and provides a framework for applications to implement their own validation logic. The basic validation logic provided by the default handler is limited to detecting and reporting the following: |
paul@19 | 164 | |
paul@19 | 165 | * Whether required fields have been specified or not |
paul@19 | 166 | * Whether the correct number of choices have been specified for a field |
paul@19 | 167 | |
paul@19 | 168 | Where validation is unsuccessful for a field, it is possible to signal such a condition using a variant of the form section intended to communicate messages to the user, with message information inserted into the page using a special `FormMessage` macro as described below. |
paul@19 | 169 | |
paul@19 | 170 | === Form Message Sections === |
paul@19 | 171 | |
paul@19 | 172 | Message sections are introduced in forms like normal sections but using the `message` argument in the form region's declaration. For example: |
paul@19 | 173 | |
paul@19 | 174 | {{{{{ |
paul@29 | 175 | {{{{#!form fragment=exampleform3 finishing=finish |
paul@19 | 176 | |
paul@19 | 177 | || '''Name''' || <<FormField(name,ExampleFormDict)>> || |
paul@19 | 178 | {{{#!form message=name-error |
paul@19 | 179 | ||<-2> /!\ <<FormMessage(name-error)>> || |
paul@19 | 180 | }}}|| '''Address''' || <<FormField(address,ExampleFormDict)>> || |
paul@19 | 181 | || '''Country''' || <<FormField(country,ExampleFormDict)>> || |
paul@19 | 182 | || || <<FormField(finish,ExampleFormDict,label=Finish)>> || |
paul@19 | 183 | |
paul@19 | 184 | }}}} |
paul@19 | 185 | }}}}} |
paul@19 | 186 | |
paul@19 | 187 | The form described above should look like this: |
paul@19 | 188 | |
paul@29 | 189 | {{{{#!form fragment=exampleform3 finishing=finish |
paul@19 | 190 | |
paul@19 | 191 | || '''Name''' || <<FormField(name,ExampleFormDict)>> || |
paul@19 | 192 | {{{#!form message=name-error |
paul@19 | 193 | ||<-2> /!\ <<FormMessage(name-error)>> || |
paul@19 | 194 | }}}|| '''Address''' || <<FormField(address,ExampleFormDict)>> || |
paul@19 | 195 | || '''Country''' || <<FormField(country,ExampleFormDict)>> || |
paul@19 | 196 | || || <<FormField(finish,ExampleFormDict,label=Finish)>> || |
paul@19 | 197 | |
paul@19 | 198 | }}}} |
paul@19 | 199 | |
paul@19 | 200 | Here, a message section has been introduced below the `name` field for a field called `name-error`. When validation produces errors, it will typically store them in fields whose names are a combination of the name of the erroneous field and the suffix `-error`. So in the above example, when `name` has an error associated with it, the error will be displayed in a new table row provided by the message section and inserted into the row using the `FormMessage` macro. |
paul@19 | 201 | |
paul@19 | 202 | The errors can only be seen if the above form is submitted without a name, so you can try this out to reassure yourself that it does work as described. |
paul@19 | 203 | |
paul@19 | 204 | ==== Repeating Message Sections ==== |
paul@19 | 205 | |
paul@19 | 206 | 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: |
paul@19 | 207 | |
paul@19 | 208 | {{{{{ |
paul@29 | 209 | {{{{#!form fragment=exampleform4 finishing=finish |
paul@19 | 210 | |
paul@19 | 211 | || '''Name''' || <<FormField(name,ExampleFormDict)>> || |
paul@19 | 212 | {{{#!form message=name-error repeating |
paul@19 | 213 | ||<-2> /!\ <<FormMessage(name-error)>> || |
paul@19 | 214 | }}}|| '''Address''' || <<FormField(address,ExampleFormDict)>> || |
paul@19 | 215 | || '''Country''' || <<FormField(country,ExampleFormDict)>> || |
paul@19 | 216 | || || <<FormField(finish,ExampleFormDict,label=Finish)>> || |
paul@19 | 217 | |
paul@19 | 218 | }}}} |
paul@19 | 219 | }}}}} |
paul@19 | 220 | |
paul@19 | 221 | The form described above should look like this: |
paul@19 | 222 | |
paul@29 | 223 | {{{{#!form fragment=exampleform4 finishing=finish |
paul@19 | 224 | |
paul@19 | 225 | || '''Name''' || <<FormField(name,ExampleFormDict)>> || |
paul@19 | 226 | {{{#!form message=name-error repeating |
paul@19 | 227 | ||<-2> /!\ <<FormMessage(name-error)>> || |
paul@19 | 228 | }}}|| '''Address''' || <<FormField(address,ExampleFormDict)>> || |
paul@19 | 229 | || '''Country''' || <<FormField(country,ExampleFormDict)>> || |
paul@19 | 230 | || || <<FormField(finish,ExampleFormDict,label=Finish)>> || |
paul@19 | 231 | |
paul@19 | 232 | }}}} |
paul@19 | 233 | |
paul@19 | 234 | Since the default handler doesn't tend to report multiple errors, this is not particularly instructive, but [[#Extending_the_Default_Form_Handler|specialised handlers]] could associate more errors with fields and this would then be exploited above. |
paul@19 | 235 | |
paul@19 | 236 | === Using the Form Message Macro === |
paul@19 | 237 | |
paul@19 | 238 | {{{ |
paul@19 | 239 | <<FormMessage(<field name>)>> |
paul@19 | 240 | }}} |
paul@19 | 241 | |
paul@19 | 242 | The `FormMessage` macro causes a message associated with a field to appear in the page. Although the macro is most likely to be used with error fields, it can insert the value of any field into the page. |
paul@19 | 243 | |
paul@23 | 244 | === Storing Submitted Form Data === |
paul@23 | 245 | |
paul@23 | 246 | The default form handler will store submitted form data in a `forms` subdirectory of the page on which a particular form appears, with each submitted form being encoded as a dictionary represented as a value encoded using Python syntax. |
paul@23 | 247 | |
paul@23 | 248 | === Restricting Access to Forms === |
paul@23 | 249 | |
paul@23 | 250 | By default, the usage of forms and the storage of form data is restricted according to the permissions granted for a given user for the page on which each form appears. This is summarised in the following table: |
paul@23 | 251 | |
paul@23 | 252 | || '''Page Permission''' || '''Access to Form and Form Data''' || |
paul@24 | 253 | || `admin` || May read form data || |
paul@23 | 254 | || `delete` || May delete form data (since the entire page may also be deleted) || |
paul@23 | 255 | || `read` || ''Permission grants no additional access'' || |
paul@23 | 256 | || `write` || May submit forms and store form data || |
paul@23 | 257 | |
paul@23 | 258 | Thus, on any page for which a user only has read access, any form will by default be visible but not usable for submitting data. |
paul@23 | 259 | |
paul@26 | 260 | Since granting write access to a user will also permit them to change the form definition, as discussed below, it is possible to override these restrictions specifically for each form. This is done by specifying an `access` keyword which defines a different set of permissions that applies to a user when using the form. For example: |
paul@23 | 261 | |
paul@23 | 262 | {{{{ |
paul@24 | 263 | {{{#!form fragment=exampleform5 access='All:write' |
paul@23 | 264 | ... |
paul@23 | 265 | }}} |
paul@23 | 266 | }}}} |
paul@23 | 267 | |
paul@24 | 268 | Here, unprivileged users - those who may have been forbidden from changing the page and thus changing the form definition - may submit the form and store their submissions. The above table also summarises the permissions that may be specified along with their effects. |
paul@23 | 269 | |
paul@24 | 270 | The `access` keyword supports the conventional [[HelpOnAccessControlLists|ACL]] syntax, and where spaces are present in the specified value, quotes should be placed around the value itself and not the `access` keyword and equals sign as well. |
paul@23 | 271 | |
paul@26 | 272 | {{{#!wiki important |
paul@26 | 273 | Note that in practice, any user with write access to a page can change the `access` criteria and grant themselves admin access to a form. Therefore, any use of forms where users are not generally to be trusted with the submitted data or the integrity of the form definition should be protected by a page ACL that denies write access to all but privileged users. The general users of the form can then be granted write access to it specifically. |
paul@26 | 274 | }}} |
paul@26 | 275 | |
paul@19 | 276 | === Extending the Default Form Handler === |
paul@19 | 277 | |
paul@19 | 278 | Specific applications will probably need to provide more sophisticated validation and handling of forms than the default action. This is most easily done by writing an action with the following general form: |
paul@19 | 279 | |
paul@19 | 280 | {{{#!python |
paul@19 | 281 | from MoinForms import MoinFormHandlerAction |
paul@19 | 282 | |
paul@19 | 283 | class ExampleFormAction(MoinFormHandlerAction): |
paul@19 | 284 | |
paul@19 | 285 | "An example form handler." |
paul@19 | 286 | |
paul@30 | 287 | def validateFields(self, fields, structure): |
paul@19 | 288 | |
paul@19 | 289 | """ |
paul@30 | 290 | Determine whether the submission is valid, first using the 'fields' and |
paul@30 | 291 | 'structure' in a basic validation of the form structure, then using |
paul@30 | 292 | rules specific to this action. |
paul@19 | 293 | """ |
paul@19 | 294 | |
paul@30 | 295 | valid = MoinFormHandlerAction.validateFields(self, fields, structure) |
paul@30 | 296 | |
paul@30 | 297 | # This defines the errors for the activity-error message field and |
paul@30 | 298 | # overrides any underlying validity decision. |
paul@30 | 299 | |
paul@30 | 300 | if not fields.get("activity"): |
paul@30 | 301 | fields["activity-error"] = [_("Need at least one activity.")] |
paul@30 | 302 | return False |
paul@30 | 303 | |
paul@30 | 304 | return valid |
paul@30 | 305 | |
paul@30 | 306 | def finished(self, fields, form): |
paul@30 | 307 | |
paul@30 | 308 | "Handle the apparently finished 'fields' and 'form'." |
paul@30 | 309 | |
paul@19 | 310 | _ = self.request.getText |
paul@19 | 311 | |
paul@19 | 312 | # Upon successful validation, a special field is used to store |
paul@19 | 313 | # messages and the underlying action method is called. |
paul@19 | 314 | |
paul@30 | 315 | fields["form-complete"] = [_("The form was correct.")] |
paul@30 | 316 | MoinFormHandlerAction.finished(self, fields, form) |
paul@19 | 317 | |
paul@19 | 318 | def execute(pagename, request): |
paul@19 | 319 | ExampleFormAction(pagename, request).processForm() |
paul@19 | 320 | }}} |
paul@19 | 321 | |
paul@30 | 322 | By overriding the `validateFields` method, it is possible to take advantage of the field-level validation and form manipulation supported by the default form handler whilst adding logic and preventing forms from being accepted until such additional logic considers them to be correct. |
paul@19 | 323 | |
paul@30 | 324 | By overriding the `finished` method, additional messages can be added to the form as well as operations that might modify the default behaviour and prevent the underlying `finished` method from being called. It is not necessary to override this method in an application-specific action, but usually some form of feedback is desirable to indicate that the form was submitted successfully. |
paul@19 | 325 | |
paul@19 | 326 | Like normal form fields, the fields populated with error messages also use collections of values instead of single values. To show all values, use repeating message sections as described above. |