Skip to content

Rules

Every rule has a code of the form OD and three digits, and a name. --select, --ignore, the configuration file and suppression comments take the code, or for the first three a prefix of it such as OD3.

The hundreds digit says what the rule reads. A code is never renumbered and never reused for another rule: once released, OD203 means the same check in every later version, so baselines, suppressions and links keep working. A retired rule leaves a gap.

A rule marked as needing the index checks one module against the others: the models, fields, methods, XML ids and views of every module it depends on. It is off unless base is among the indexed modules, and a notice says so. The other rules read one file, or one module, at a time.

The series of a rule is the range of Odoo versions it applies to. Each module is checked against the series its manifest version names, and a module whose series is unknown gets only the rules that apply to every series.

Load failures

Rule pages say a failure stops the install: that holds for a file of data. From saas~11.5 a failure while loading a file of demo stops neither the install nor the update: Odoo logs a warning and installs the module without its demo data. That covers the load alone: an update validates a view of that file again after it, so a view that no longer validates stops the update. Before saas~11.5 any failure in a demo file stops them like a data file.

OD0: parsing

A file that does not parse. Nothing else is checked in it.

Code Name Reports
OD000 syntax-error The file does not parse.

OD1: Python, one file at a time

Python code checked on its own: security, deprecated APIs and ORM mistakes visible in one file.

Code Name Reports
OD101 sql-injection cr.execute() receives a query built by string formatting.
OD102 translation-interpolated _() receives an already formatted string, so the source text cannot be translated.
OD103 cursor-commit Business code commits the cursor, breaking the request's transaction.
OD104 default-evaluated-at-import A field default is called once at import instead of on each record.
OD105 missing-super An ORM override never calls super().
OD106 name-get-deprecated name_get() is ignored; override_compute_display_name().
OD107 api-multi-one-removed @api.multi and @api.one no longer exist.
OD108 group-operator-renamed Field attribute group_operator is renamed aggregator.
OD109 private-env-shortcut self._cr, self._uid and self._context are deprecated; use self.env.
OD110 sql-constraints-unsupported _sql_constraints is ignored; declare models.Constraint attributes.
OD111 public-route-sudo A public or auth='none' route escalates with sudo().
OD112 csrf-disabled An HTTP route disables CSRF protection.
OD113 fields-view-get-removed fields_view_get() is removed; override _get_view() or get_views().
OD114 depends-on-id @api.depends names the field id, which Odoo refuses.
OD115 dotted-onchange-constrains @api.onchange or @api.constrains names a dotted path, which Odoo ignores.
OD116 field-shadows-model-member A field is named like a method or an attribute every model has.
OD117 create-not-batched create() is decorated with @api.model, so Odoo calls it once per record.
OD118 create-reads-dict create() under @api.model reads its argument as one dict, but receives a list of them.
OD119 override-drops-result A create(), write() or copy() override calls super() but returns nothing.
OD120 ormcache-unknown-name An ormcache key names something that is not a parameter of the method.
OD121 cursor-rollback Business code rolls back the cursor, discarding the caller's work with its own.
OD122 sql-write-without-invalidation An SQL write runs without invalidating the cache, so the ORM keeps stale values.
OD123 patch-at-import Code run at import patches Odoo, for every database the server runs.
OD124 module-level-cache A module-level dict, list or set filled at runtime is shared by every database.
OD125 python-api-deprecated The module uses a Python API its series deprecates or ignores.
OD126 python-api-removed The module uses a Python API its series removed.

OD2: models and fields

Models, fields and methods checked against every module in the dependency graph. A model class has Model, TransientModel, AbstractModel, their aliases osv, osv_memory and osv_abstract or, from 20.0, CachedModel among its bases, and any other base is a plain Python base class, which the rules whose page says so skip, since only Python knows its fields and methods. As each page says, they also skip a model when a class of it, or of a model it inherits or, for a rule reading fields, delegates to, has an _inherit element that is not a literal, such as a variable, since the scan cannot tell which model it names.

Code Name Reports
OD201 unknown-model The model is not defined by any indexed module.
OD202 model-not-in-depends The model comes from a module missing from depends.
OD203 unresolved-field-path A field path does not resolve on its model.
OD204 missing-method compute, inverse, search, selection or group_expand names a method the model does not have.
OD205 field-type-changed An inherited field is redefined with another type.
OD206 unknown-env-model env[...] names a model no indexed module defines.
OD207 related-type-mismatch A related field has another type than the field it relates to.
OD208 invalid-one2many-inverse A One2many's inverse field is not a Many2one.
OD209 model-redefined A class redefines a model that a module in depends already defines.
OD210 unknown-domain-field A literal domain or group_by names a field its model does not have.
OD211 orphan-super super() calls a method no parent class defines.
OD212 monetary-without-currency A Monetary field's currency field is not a field of its model.
OD213 required-many2one-set-null A required Many2one declares ondelete='set null'.
OD214 many2many-table-clash Two Many2many fields of a model to one comodel get the same table.
OD215 many2many-table-too-long A Many2many's relation table name is longer than PostgreSQL allows.
OD216 selection-add-without-ondelete selection_add leaves a value of a required Selection with ondelete 'set null'.
OD217 parent-store-without-path A model sets _parent_store without an indexed parent_path field.
OD218 delegation-field-not-required A field that delegates to a parent model is not required with ondelete cascade or restrict.
OD219 tracking-without-mail-thread A field sets tracking on a model that does not inherit mail.thread, which Odoo ignores.
OD220 duplicate-field-label Two fields of a model have the same label.
OD221 self-dependency-not-recursive A computed field depends on itself through other records without recursive=True.
OD222 check-company-without-company A field sets check_company to a comodel without company_id.
OD223 compute-leaves-field-unassigned A compute method leaves a non-stored readonly field unassigned on some records.
OD224 sql-unknown-column A literal SQL query names a column its model's table does not have.
OD225 sql-view-missing-column The SQL view of a model selects no column for one of its stored fields.
OD226 sql-view-not-created A model sets _auto = False, and nothing creates its SQL view.
OD227 sql-definition-unknown-column An SQL constraint or index names a column its model's table does not have.
OD228 delegate-without-ondelete A delegate=True Many2one has no ondelete, so Odoo fails when it sets up the registry.
OD229 override-signature-mismatch An override refuses a call that the method it overrides takes.
OD230 orphan-hook-method A compute, inverse, search or onchange method of a model extension is named nowhere, so Odoo never runs it.

OD3: XML ids, views and data

XML ids, views and data records, within a file and across the dependency graph.

Code Name Reports
OD301 unresolved-xmlid The XML id is not defined by its module.
OD302 xmlid-not-in-depends The XML id comes from a module missing from depends.
OD303 duplicate-xmlid The same XML id is defined twice in one file, with another model or overwriting a field.
OD304 unknown-view-field A view references a field its model does not have.
OD305 attrs-states-removed attrs and states are removed; use invisible, readonly and required expressions.
OD306 tree-renamed-list The tree view type is renamed list.
OD307 t-raw-unsafe t-raw is deprecated and renders unescaped HTML; use t-out with Markup.
OD308 xmlid-used-before-definition An XML id is referenced before the file that defines it is loaded.
OD309 unknown-record-field A data record sets a field its model does not have.
OD310 unmatched-view-locator An inherited view locates an element its parent views do not have.
OD311 unknown-qweb-template A QWeb view or a report names a template no module in scope defines.
OD312 unknown-button-method A button calls a method its model does not have.
OD313 private-button-method A button calls a private method, which Odoo refuses.
OD314 unknown-function-method A <function> of a data file calls a method its model does not have.
OD315 invalid-modifier A view modifier is not an expression Odoo reads.
OD316 unknown-modifier-name A view modifier names a field its model does not have.
OD317 modifier-field-not-in-view A view modifier reads a field the view does not show.
OD318 view-mode-without-view A window action lists a view type its model has no view for.
OD319 python-field-syntax-error The code of a server action or a cron, or the file name of a report, is not Python that Odoo compiles.
OD320 unknown-server-code-method The code of a server action or a cron calls a method its model does not have.
OD321 wrong-model-reference A data record gives a relational field a record of another model.
OD322 unknown-selection-value A data record gives a Selection field a value it does not list.
OD323 duplicate-xmlid-across-files A data file defines an XML id another file of the module already defines, with another model, or overwriting the fields both name.
OD324 missing-required-field A data record is created without a required field that has no default.
OD325 t-esc-removed The server no longer renders t-esc in a QWeb view; use t-out.
OD326 kanban-box-renamed-card The kanban-box template of a kanban view is renamed card.
OD327 chatter-div-replaced A form's div with class oe_chatter no longer shows the chatter; use <chatter/>.
OD328 bundle-template-removed A data file inherits or references an asset bundle, which is no longer a template.

OD4: access

Access rights: models no one can reach, and rights granted too widely.

Code Name Reports
OD401 model-without-access A new model has no ir.model.access rule, or from saas~19.4 no ir.access rule with a group.
OD402 public-write-access An access row gives public or portal users write, create or unlink, through their group or no group.

OD5: manifest and module graph

The manifest and the dependency graph: listed files, unlisted files and dependencies.

Code Name Reports
OD501 manifest-data-missing A file listed in the manifest does not exist.
OD502 data-file-not-loaded A data file is listed under no manifest key Odoo reads, so Odoo never loads it.
OD503 unknown-dependency A module in depends is not on the addons path.
OD504 circular-dependency A module in depends depends back on this module.
OD505 qweb-key-ignored The manifest lists templates under qweb, which Odoo ignores.
OD506 assets-qweb-unloaded The manifest adds files to web.assets_qweb, which Odoo no longer loads.

OD6: JavaScript and Owl

Scripts under static/src that a bundle loads, checked against the scripts of every indexed module.

Code Name Reports
OD601 unresolved-js-import A JS import names a module or an export no indexed module provides.
OD602 orphan-patch-super A patch calls a super method the class it patches does not have.
OD603 unknown-rpc-model An ORM call from JS names a model no indexed module defines.
OD604 unknown-rpc-method orm.call() names a method its model does not have.
OD605 unknown-template A component, a t-inherit or a t-call names a template no indexed bundle defines.
OD606 unmatched-template-locator A template locates an element its parent templates do not have.
OD607 private-rpc-method orm.call() names a private method, which Odoo refuses to call over RPC.

OD7: data flows

Values a user controls, followed through one function to the calls that must not receive them.

The sources are the parameters of a controller method behind @route; the parameters of a public model method (no leading _, no @api.private), which any user with access to the model can call over RPC; and the request: request.params, request.get_json_data(), request.get_http_params(), and the args, form, values, files, cookies, headers, data and JSON of request.httprequest.

A value stays tainted through assignments, formatting, concatenation, f-strings, string methods, items, attributes, collections, comprehensions and module functions such as json.loads(). Records do not carry it: what a recordset method such as browse() or search() returns is not tainted. Both branches of an if are followed, a branch that returns or raises adds nothing after it, and a loop body is read twice.

A value passed to a method of self, of env["x"] or of a local bound to one is followed into every definition of that method the module's dependencies hold, overrides included, up to four calls deep; so is a value such a method returns. The finding is at the sink when the module holds it, otherwise at the call that leaves the module. A method that runs under sudo() only because its caller called it so is not known to write as superuser.

The flow ends at a sanitizer: int(), float(), bool(), len(), SQL.identifier(), escape(), html_escape(), html_sanitize() and plaintext2html(); at assert x in (...) or if x not in allowed: raise; and at an intersection with values the code chose, such as set(vals) & allowed.

Code Name Reports
OD701 sql-injection-flow Data a user controls reaches the text of an SQL query.
OD702 code-injection-flow Data a user controls reaches safe_eval(), eval() or exec().
OD703 markup-injection-flow Data a user controls is wrapped in Markup() without escaping.
OD704 sudo-mass-assignment A mapping a user controls is written as superuser, so the user picks the fields.