Version 12 (modified by MattS, 10 years ago) ( diff )


Record Approval

The S3 Record Approval Framework hides records which have not yet been reviewed and approved by an authorized user.

Status: under construction

Unapproved Records

Whether a record is approved or not is encoded as approved_by field in the record. The field is either None (unapproved record) or holds the user ID of the approving user (approved record).

For tables which require approval, this field is mandatory (field type "integer" or "reference auth_user"). The default value for this field is None, i.e. all new records are unapproved by default. This field should not be editable, i.e. writable=False.


The intended workflow for approval is:

  • user creates a new record => record is unapproved by default (i.e. approved_by=None)
  • unapproved records are treated as invalid and excluded from any kind of workflow (i.e. invisible) except for review/approval
  • user with approval role reviews the record and either approves or rejects it
  • approve sets the approved_by field of the record to the approving user's ID
  • reject deletes the record and all its dependencies
  • approved records are now available for all workflows, except for review/approval

Developer notes:

  • it should not be possible to import pre-approved records, otherwise this would allow unauthorized users to circumvent the approval framework
  • unapproved records should be disregarded in Sync, because otherwise this introduces the same hole as above: the user could approve the record in a remote instance where he is Admin and synchronize it back. Apart from that, circulation of unapproved records turns the purpose of approval somewhat upside down.


1) The Record Approval Framework can be turned on/off globally by a deployment setting:

settings.auth.record_approval = True

Default is False.

2) Whether record approval is required for a table can be configured per table like:

s3db.configure(tablename, requires_approval=True)

Default is False.

For a deployment-specific approval configuration, you can override all table-specific settings with:

settings.auth.record_approval_required_for = [tablename, tablename]

Record approval will then only be applied to those tables in the list. If you set this to None, it will fall back to the table-specific settings.

3) In addition to activating record approval for a table, you will also need to give user roles permission to review/approve/reject records in this table.

These permissions are encoded as auth.permission.REVIEW (permission to access unapproved records) and auth.permission.APPROVE (permission to approve or reject unapproved records).

            acl = current.auth.permission

Note: that users can have review permission (=permission to see unapproved records) without need to also have approve/reject permission.

Note: REVIEW permission alone doesn't give any access to unapproved records: it merely extends the READ/UPDATE permissions so they also apply for unapproved records. That means, if the user besides REVIEW has only READ permission in this table, then they can read unapproved records but not update them. To do that, they would need REVIEW+UPDATE permission.

The permission to review/approve/reject records of this role is realm-limited (see S3AAA/OrgAuth for more details).

Note: users can always only see approved records, unless they have review permissions and use the "review" method. For custom layouts, it is possible to implement custom views which include both groups of records by using the S3Resource API directly.

Methods to Approve or Reject Records

S3Resource implements two low-level methods for record approval:

resource.approve() approve all records in a resource (and all its components), and

resource.reject() reject (=delete) all unapproved records in a resource.

Note: that S3Resource instances always exclude unapproved records, so these methods will always fail unless you define the resource with the unapproved=True parameter:

resource = current.s3db.resource("my_table", 1, unapproved=True)

NOTE: resource.reject() is much more radical about record deletion than delete() - it will try to bypass any RESTRICTs and rigorously delete any dependency of the rejected records so they can not leave any undesired legacy behind. However, that means that reject() must always be properly authorized and strictly limited to unapproved records in tables which require approval.

S3CRUD implements the review method to review/approve/reject records in a resource:

/controller/function/review see all unapproved records in a resource, or:

/controller/function/XY/review review/approve/reject a particular record.

Note: For security reasons, both approve and reject via CRUD are only possible as POST requests which include the respective form keys from the review of a record.


For both, resource.approve() and resource.reject(), there are hooks which will be called for each record that gets approved/rejected:

s3db.configure(tablename, onapprove=function)
s3db.configure(tablename, onreject=function)

The hook function must take (table, row) as parameters, and doesn't return anything.


Imported data is assumed to be approved by the default approver.

If you wish to import unapproved data then you need to set the attribute @approved=false

Note: See TracWiki for help on using the wiki.