My use of web2py requires an audit trail for (essentially) all database tables; The preferable way to do that is to keep a "foo_history" table for each table "foo". The records (both audited and original) need to have a "revision" that increments with each update, and "last modified on", "last modified by" fields that get updated with the user_id and the time on each update.
At this point in time, there is no need to delete records - but if there was, I'm not sure what the proper way would be -- possibly having another field "deleted" on the audit trail table, which would be set to "true" on the revision that was the latest version. Now, the built in Crud system gives me most of what I want (I don't see how to make it keep the history of the deleted table above, though for now that's not a problem). But there's quite a bit of application logic that needs this which cannot be implemented using the Crud. The "classic" solution is to write this as stored procedures. I would like to avoid that for portability and maintenance reasons. Another solution is to use my own "update/insert/delete" methods that update audit tables whenever a record updates. That's error prone, and I would rather avoid that, especially since I _do_ want to build on web2py infrastructure such as Crud for other things. The solution that I'm thinking of is to hook the DAL; specifically, the following functions would be sufficient for me; It seems that all database modifications go through them eventually, whether called directly or when manipulating Rows or Row objects. insert: (copy all newly added records to the audit table table - easy) update: (copy all modified records to the audit table, add a 'revision = revision + 1' to the SET code - easy) delete: (copy all deleted records to the audit table, adding a 'deleted = True' - possibly a little more complicated) For completeness, hooking "truncate" and "drop table" to do the same thing (either raise an error if that is disallowed, or transform it into a delete of each individual record so that everything goes into the audit). And I would also disable migrations if I do that, because they may change the database without updating my audit trail. Now, before I start working on a monkey patch (or full patch) to dal.py - perhaps I've missed a simpler way to do the same thing? If I haven't - I'm currently working with Postgres and SQLite; I would like to contribute the resulting patch to the web2py project, but have no time to evaluate and test on other databases/gae at the moment. Is there anything else I should be aware of that would be required for other databases? ==== Finally, a question about transaction rollback - in my app, I sometimes want to rollback everything (as if I did e.g. raise MySecurityException("Forbidden")), but actually return a non-error HTML result to the user. I am aware of db.rollback(), but the exception handling code does more, e.g., today it does: if response._custom_rollback: response._custom_rollback() else: BaseAdapter.close_all_instances('rollback') I would suggest adding an "response._transaction_verb" variable, which would default to 'COMMIT', but could be changed to 'ROLLBACK', and would determine what to do with the database (commit/rollback) in the case of a successful completion - meaning raising of an HTTP exception or simple return from controller. On other exception, I would expect rollback always (and the application code can call db.commit or BaseAdapter.close_all_instances('commit') or whatever if needed) - because committing on error is something that has to be extremely explicit. What say you?