[ADD] skills: odoo-guidelines, odoo-review, odoo-security - #5250
Conversation
|
This PR targets the un-managed branch odoo-dev/odoo:master-js-guidelines-ged, it needs to be retargeted before it can be merged. |
27fbaf2 to
f91f605
Compare
4c97733 to
c951129
Compare
f91f605 to
fd61148
Compare
c951129 to
aee3371
Compare
62cdc41 to
97ebb86
Compare
aee3371 to
cbcfb5c
Compare
97ebb86 to
f5321c6
Compare
cbcfb5c to
91ccb5d
Compare
| - Standard directories: `models/`, `views/`, `controllers/`, `data/`, | ||
| `security/`, `static/`, and optional `wizard/`, `report/`, `tests/`, | ||
| `populate/`. | ||
| - Split models by main model, one file per main model; each inherited model in |
There was a problem hiding this comment.
What is a main model?
The rule is just one model class per file.
There was a problem hiding this comment.
I agree, there is no such a thing as a "main model".
The rule is: one model is one class. Then one class (or model) per file.
| - `populate/` holds `populate.blueprint` XML records (`populate_demo.xml`, | ||
| `benchmarks.xml`); an `__init__.py` makes it an importable package for | ||
| custom populate generators. | ||
| - File names use only `[a-z0-9_]`. Don't link external data by URL (images, |
There was a problem hiding this comment.
Filenames are python packages (follow that naming).
| - Model `_name`: dotted, prefixed by module, **singular** (`sale.order`, not | ||
| `sale.orders`). Transient/wizard: `<base_model>.<action>` (avoiding the word | ||
| "wizard" is a soft preference — core ships many `.wizard` names). SQL-view report model: `<base_model>.report.<action>`. | ||
| - Python classes use PascalCase (`class AccountInvoice(models.Model)`). |
There was a problem hiding this comment.
This file is mostly python dependent, isn't the LLM already aware of python standards like PEP8?
There was a problem hiding this comment.
Dropped the two PEP 8 lines (class and variable casing); the rest of the file is Odoo-specific.
There was a problem hiding this comment.
Kept a one-line pointer: PEP 8 applies, ruff.toml at the repo root is the reference for what is enforced.
| @@ -0,0 +1,37 @@ | |||
| # ORM idioms & correctness | |||
|
|
|||
| - Use recordset methods (`filtered`, `mapped`, `sorted`) for readability and | |||
There was a problem hiding this comment.
| - Use recordset methods (`filtered`, `mapped`, `sorted`) for readability and | |
| - Use recordset methods (`filtered`, `grouped`, `mapped`, `sorted`) for readability and |
but you probably shouldn't prefer them in all cases. Use when it makes sense.
| need correct dependencies and no side effects. | ||
| - `@api.onchange` is UI-only — never rely on it for data integrity; enforce | ||
| invariants with `@api.constrains` or SQL constraints declared as model | ||
| attributes (`_x_check = models.Constraint("CHECK (...)", "msg")`; the |
There was a problem hiding this comment.
You can also define indexes similarly. For example models.UniqueIndex(...).
| minimal. In *master*, apply guidelines to new code, or to existing code only | ||
| when a file is under major change (do a separate *move* commit first). | ||
| - **Version traps.** Odoo's ORM and view/template syntax change between major | ||
| versions (`attrs=`, `<tree>`, `name_get`, `read_group` are all gone from |
There was a problem hiding this comment.
read_group is back, but it's for RPC calls :) (you can ignore this comment)
| | --- | --- | | ||
| | `sudo(`, `with_user(`, `with_company(` | [Don't over-sudo](#dont-over-sudo) | | ||
| | `cr.execute`, `SQL(`, `.format`/`%`/f-string near SQL | [Use the ORM; parameterize SQL](#use-the-orm-parameterize-sql) | | ||
| | domain built from request/user input | [Domain injection](#domain-injection) | |
There was a problem hiding this comment.
Also from field input. Users can set whatever value.
| - A field's `groups` attribute removes it from views and `fields_get` and | ||
| raises on explicit read/write. Use it for sensitive fields instead of | ||
| relying on the UI to hide them. | ||
| - **Passwords and API tokens**: restrict them with `groups="base.group_system"` |
|
|
||
| - Never use the cursor directly when the ORM can do it: raw SQL bypasses | ||
| access control, translations, field invalidation, and `active` handling. | ||
| Prefer `search`/`_read_group`/`browse(...).read(...)`. |
There was a problem hiding this comment.
read should not be used in python code, just access the fields directly.
| - When you must write SQL, **never** interpolate with `+`/`%`/`.format`. Pass | ||
| values as **parameters** (psycopg2 formats them, including a tuple for | ||
| `IN %s`), or use the `odoo.tools.SQL` wrapper. For dynamic **identifiers** | ||
| (table/column names, which can't be parameters) use `SQL.identifier(name)`, |
There was a problem hiding this comment.
There is also odoo.models.Query and a new API where you can do stuff like:
query = model._search([...])
query.select(SQL("LENGTH(%s)", query.table.some_field)) # select length(t.some_field) from ... t where ...f5321c6 to
915cd0c
Compare
fa5cce5 to
ed30662
Compare
| - Public (non-`_`) model methods are RPC-callable; prefix internal helpers | ||
| with `_`, or decorate with `@api.private` when a public name must not be | ||
| exposed. |
There was a problem hiding this comment.
I would invert the rule: private by default, public when a name must be exposed.
| - Standard directories: `models/`, `views/`, `controllers/`, `data/`, | ||
| `security/`, `static/`, and optional `wizard/`, `report/`, `tests/`, | ||
| `populate/`. |
There was a problem hiding this comment.
It's strange this is by type and not by featyre, the opposite of the JS guideline.
| its own file (`res_partner.py`). A single-model module's file matches the | ||
| module name. |
There was a problem hiding this comment.
Single-model name: I disagree, this seems oddly specific. Let us allow to pick the best module name and best model name without having them to match.
What if a module contains 2 models and we remove one? Do we suddenly need to rename the other one?
What if a module starts with a single model but need a second one later?
This is just useless and counter-productive.
| `benchmarks.xml`); an `__init__.py` makes it an importable package for | ||
| custom populate generators. | ||
| - File names use only `[a-z0-9_]`. Don't link external data by URL (images, | ||
| libs) — copy it into the codebase. |
There was a problem hiding this comment.
No link to external data seems unrelated to module structure.
There was a problem hiding this comment.
Moved to odoo-web-guidelines as a new 0003 Assets convention file, together with the no-minified-libraries rule from the docs, since both are asset concerns.
| custom populate generators. | ||
| - File names use only `[a-z0-9_]`. Don't link external data by URL (images, | ||
| libs) — copy it into the codebase. | ||
| - Permissions: directories 755, files 644 — no executable bit on source files. |
There was a problem hiding this comment.
Wait what, does this need to be specified? I don't think I've ever checked or changed the permission of a code file in my career, the default has always been good.
There was a problem hiding this comment.
Dropped (came from runbot checks fyi).
| # Batch ORM calls | ||
|
|
||
| Make **one** ORM call for the whole collection; an ORM call inside a | ||
| `for` loop over records or vals is almost always a bug. |
There was a problem hiding this comment.
It's not a "bug", just less efficient code.
| ```python | ||
| # bad — one INSERT round-trip per record | ||
| for vals in vals_list: | ||
| self.env['sale.order.line'].create(vals) |
There was a problem hiding this comment.
Can we possibly use fake model names here?
I don't want to find this file whenever I grep for sale.order.line.
There was a problem hiding this comment.
Renamed to a schematic library.book model.
| The review is complete when every changed file has been checked against every | ||
| guideline that matches it. |
There was a problem hiding this comment.
I would say this is only the start of the review, not the end.
Checking guidelines is necessary but certainly not sufficient to consider a review done.
There was a problem hiding this comment.
Done, and the overrides-outside-the-diff concern is now its own section.
| Security audit of Odoo addon code: access control (ir.access, field groups, | ||
| sudo), injection (SQL, domain, eval, XSS), untrusted public methods/RPC, | ||
| controller auth/CSRF, file access, deserialization, returning complex | ||
| objects, getattr/setattr, timing attacks. Use when auditing an addon, or | ||
| judging whether a specific construct (a sudo, raw SQL, a route, …) is safe. |
There was a problem hiding this comment.
I would say markup should be in the description somewhere.
| # Security audit of Odoo code | ||
|
|
||
| Audit Odoo addons for the framework-specific ways access control and injection | ||
| go wrong. **Never weaken security to make a feature work.** |
There was a problem hiding this comment.
Wait what? We're asked to do that on a regular basis (we try our best to mitigate the potential issues, if anything).
There was a problem hiding this comment.
Rephrased: minimum access, narrowest scope, with a comment; a silent widening is the finding.
| - Class naming: avoid `id` selectors; prefix classes with `o_<module>` (just | ||
| `o_` for the webclient). Use the flat "grandchild" approach | ||
| (`o_element_entry`), not hyper-specific nested names. |
There was a problem hiding this comment.
This contradicts the line below, _ vs -
There was a problem hiding this comment.
Clarified: classes use underscores, SCSS identifiers hyphens.
915cd0c to
ee4d31d
Compare
ed30662 to
787d9a4
Compare
| performance, tests; its table maps file types to guideline files. | ||
| - [`odoo-web-guidelines`](../odoo-web-guidelines/SKILL.md) — everything | ||
| under `static/`: JavaScript, Owl templates, SCSS. | ||
| - If the diff touches controllers, ACLs or record rules, `sudo()`, raw SQL, |
There was a problem hiding this comment.
"ACLs or record rules" → only before saas-19.4
Instead I suggest "access rules" (ir.access records).
ee4d31d to
5503f0c
Compare
45bfdaa to
2070840
Compare
Before this commit, the house rules for addon code and the way to review or audit an addon against them only existed in the heads of reviewers and in scattered documentation that lags the code. After this commit, skills/ holds three agent skills alongside odoo-web-guidelines, following the same structure: - odoo-guidelines: the house rules for addon code outside static/, one numbered file per domain (module structure, manifest, Python/ORM, fields, controllers, XML views and data, QWeb reports, access rights, performance, tests), dispatched by a routing table keyed on the files being touched. Rules stay one-line bullets by default; the trap-shaped ones (translation interpolation, view inheritance, batch ORM calls) get the full treatment with a bad and a fixed example. Authoring conventions live in AUTHORING.md. - odoo-review: a thin review-process skill, dispatching each changed file to the matching guidelines (including odoo-web-guidelines), with the stable-vs-master policy, a version-trap warning, and an explicit completion criterion. - odoo-security: a security-audit skill routed by a sweep table (16 grep patterns, each dispatched to its pitfall section): access control in ir.access vocabulary, sudo and x2many commands, SQL and domain injection, XSS through markup()/t-out, CSRF on http routes, safe_eval, file access, RPC surface, returning complex objects. The SCSS conventions go to odoo-web-guidelines (0002), so the two guideline skills split on static/ versus everything else. Every claim was verified against the documentation and then against the master source, which overrules the docs where they disagree: ir.model.access and ir.rule are unified into ir.access, t-raw and the restricted pickle no longer exist, CSRF validation only applies to type='http' routes, standard,post_install is the default test tag, _sql_constraints is ignored in favour of models.Constraint. Examples are schematic only and never point at real files, since nothing checks such references and a stale one misleads more than none. A README.md at the root of skills/ explains what the directory holds, where to copy it depending on the agent harness, and that the skills must be installed together since they reference each other.
2070840 to
f2f3456
Compare
Stacked on odoo#285015 (targets its branch so only these commits show; rebase to master once it merges).
Adds three agent skills to
skills/, following the structure introduced there:AUTHORING.md.odoo-js-guidelines), stable-vs-master policy, version-trap warning, explicit completion criterion.ir.access), sudo, SQL/domain injection, XSS, CSRF, eval, file access, and friends.All content was verified in two passes against today's master: first against the documentation, then against the source itself. The source pass overruled the docs in several places (
ir.accessunification,t-rawand the restricted pickle no longer existing, CSRF being http-type-only, test-tag defaults) — a list of the documentation bugs found along the way is being forwarded to the docs team separately.