# HG changeset patch # User Paul Boddie # Date 1358702646 -3600 # Node ID c9ca36e232e084da02456a263326275f02f2ac2f # Parent 08235af0d835e58d8759816178b0274016cae1c4 Added support for storing form data and for form-level ACLs which may be used to override page-level ACLs and permit the storage of data for unprivileged users. Added initial functionality for checking and loading stored form data. diff -r 08235af0d835 -r c9ca36e232e0 MoinForms.py --- a/MoinForms.py Sat Jan 19 22:50:29 2013 +0100 +++ b/MoinForms.py Sun Jan 20 18:24:06 2013 +0100 @@ -6,8 +6,11 @@ @license: GNU GPL (v2 or later), see COPYING.txt for details. """ +from compiler import parse +from compiler.ast import Const, Dict, Discard, List, Module, Stmt from MoinMoin.action import do_show from MoinMoin.Page import Page +from MoinMoin.security import parseACL from MoinMoin import wikiutil from MoinSupport import * import re @@ -47,7 +50,14 @@ # Get the form definition. - structure = self.getFormStructure(fields) + self.attributes, structure = self.getFormStructure(fields) + + # Check the permissions on the form. + + 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. @@ -67,6 +77,7 @@ "Handle the finished 'fields' and 'form'." + self.storeFields(fields) self.unfinished(fields, form) def unfinished(self, fields, form): @@ -80,12 +91,33 @@ def getFormStructure(self, fields): - "Return the structure of the form being handled." + "Return the attributes and structure of the form being handled." fragment = fields.get("fragment", [None])[0] text = Page(self.request, self.pagename).get_raw_body() - text = getFormForFragment(text, fragment) - return getFormStructure(text, self.request) + attributes, text = getFormForFragment(text, fragment) + return attributes, getFormStructure(text, self.request) + + def checkPermissions(self, action): + + """ + Check the permissions of the user against any restrictions specified in + the form's 'attributes'. + """ + + user = self.request.user + + # Use the page permissions if no access definition is given. + + if not self.attributes.has_key("access"): + return user and getattr(user.may, action)(self.pagename) + + # Otherwise use the access definition. + + else: + access = self.attributes["access"] + acl = parseACL(self.request, access) + return user and acl.may(self.request, user.name, action) def validateFields(self, fields, structure): @@ -221,6 +253,70 @@ max_index = -1 section[name] = {0 : placeholder} + # Storage of form submissions. + + def storeFields(self, fields): + + """ + Store the given 'fields' as a Python object representation, subject to + the given 'attributes'. + """ + + store = FormStore(self) + store.append(repr(fields)) + + def loadFields(self, number): + + "Load the fields associated with the given submission 'number'." + + try: + module = parse(store[number]) + if checkStoredFormData(module): + return eval(module) + + # NOTE: Should indicate any errors in retrieving form data. + + except: + pass + + return {} + +def checkStoredFormData(node): + + """ + Check the syntax 'node' and its descendants for suitability as parts of + a field definition. + """ + + for child in node.getChildNodes(): + if isinstance(child, Const): + pass + elif not isinstance(child, (Dict, Discard, List, Module, Stmt)) or not checkStoredFormData(child): + return False + + return True + +class FormStore(ItemStore): + + "A form-specific storage mechanism." + + def __init__(self, handler): + + "Initialise the store with the form 'handler'." + + self.handler = handler + page = Page(handler.request, handler.pagename) + ItemStore.__init__(self, page, "forms", "form-locks") + + def can_write(self): + + """ + Permit writing of form data using the form attributes or page + permissions. + """ + + return self.handler.checkPermissions("write") + # Form and field information. def getFormStructure(text, request, path=None, structure=None): @@ -265,14 +361,16 @@ """ Return the form region from the given 'text' for the specified 'fragment'. - If no fragment is specified, the first form region is returned. + If no fragment is specified, the first form region is returned. The form + region is described using a tuple containing the attributes for the form + and the body text of the form. """ for format, attributes, body in getFragments(text): - if not fragment or attributes.get("fragment") == fragment: - return body + if format == "form" and (not fragment or attributes.get("fragment") == fragment): + return attributes, body - return None + return {}, None def getFieldArguments(field_definition): diff -r 08235af0d835 -r c9ca36e232e0 pages/HelpOnMoinForms --- a/pages/HelpOnMoinForms Sat Jan 19 22:50:29 2013 +0100 +++ b/pages/HelpOnMoinForms Sun Jan 20 18:24:06 2013 +0100 @@ -241,6 +241,38 @@ 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. +=== Storing Submitted Form Data === + +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. + +=== Restricting Access to Forms === + +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: + +|| '''Page Permission''' || '''Access to Form and Form Data''' || +|| `delete` || May delete form data (since the entire page may also be deleted) || +|| `read` || ''Permission grants no additional access'' || +|| `write` || May submit forms and store form data || + +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. + +However, it is possible to change these restrictions by specifying an `access` keyword which defines the permissions that an unprivileged user has when using the form. For example: + +{{{{ +{{{#!form fragment=exampleform5 access=All:write +... +}}} +}}}} + +Here, unprivileged users - those who may not change the page and thus change the form definition - may submit the form and store their submissions. The following table summarises the different `access` options: + +|| '''Access Option''' || '''Access to Form and Form Data''' || +|| `delete` || May delete form data || +|| `read` || May read form data || +|| `write` || May submit forms and store form data || + +The `access` keyword supports conventional [[HelpOnAccessControlLists|ACL]] syntax. + === Extending the Default Form Handler === 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: