OD121 cursor-rollback¶
Business code rolls back the cursor, discarding the caller's work with its own.
| Category | Severity | Series | Needs the index |
|---|---|---|---|
| Correctness | Warning | all | no |
What it reports¶
A call to rollback() on a cursor the function was given: cr.rollback(),
self.env.cr.rollback(), request.env.cr.rollback() or self._cr.rollback(), a cr parameter
included. Each function is checked on its own, apart from the functions it nests, and sees the
cursors the functions enclosing it open.
It stays silent on a rollback:
- in a function that also makes a commit OD103 reports;
- on a cursor the function binds itself: one a
withstatement yields, such aswith registry.cursor() as cr:orwith closing(registry.cursor()) as cr:, or one bound with=to anything but a cursor it was given, such ascr = registry.cursor()orcr = new_cr or self.env.cr; - on the cursor of an environment built on such a name or on a
.cursor()call, withapi.Environment(cr, ...),self.env(cr=cr)orwith_env(), or of one awithstatement yields; - in a block that ends by raising or by calling
sys.exit(), since the transaction is lost anyway; - under
if <cursor>.readonly:, where a read-only cursor holds no write to lose; - in
_handle_error()of anir.httpclass, or in its_handle_exception()for a module before 16.0: the hook Odoo calls once the request has failed.
A name holds a cursor or an environment the function binds only when every path reaching the rollback binds it so, and a function nested in the one binding it sees it unless a parameter of its own hides it, both as OD103 describes.
Why it matters¶
Odoo runs each request, cron job and RPC call in one transaction, and the cursor's rollback()
ends that whole transaction. Everything done before it is gone: the records the caller created, the
messages it posted, the work of every override that ran first, none of which knows. The code then
carries on in a new transaction, so the request ends with a success and commits only what came
after the rollback, a state nobody wrote on purpose.
To abort, raise an exception and let Odoo roll back. To try a step that may fail and keep going, run it in a savepoint, which undoes that step alone:
Example¶
class SaleOrder(models.Model):
_inherit = "sale.order"
def action_x_try_invoice(self):
try:
self._create_invoices()
except UserError:
self.env.cr.rollback()
return False
return True
If the order was confirmed in the same call, the rollback undoes the confirmation too, and the user is not told.
class SaleOrder(models.Model):
_inherit = "sale.order"
def action_x_try_invoice(self):
try:
with self.env.cr.savepoint():
self._create_invoices()
except UserError:
return False
return True