Version 9 (modified by 12 years ago) ( diff ) | ,
---|
Record Approval
Table of Contents
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.
Workflow
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 users 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.
Configuration
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 acl.update_acl(ROLE_XY, t="some_table", uacl=acl.READ|acl.CREATE|acl.REVIEW|acl.APPROVE, oacl=acl.READ|acl.UPDATE|acl.REVIEW|acl.APPROVE)
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()
...to approve all records in a resource (and all its components), and
resource.reject()
...to 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 rigirously 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
...to see all unapproved records in a resource, or:
/controller/function/XY/review
...to 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.
Callbacks
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 table (table, row)
as parameter, and doesn't return anything.
Imports
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