Les règles à appliquer avant de coder sont éparpillées entre les configs, les hooks et les modules maison, et la documentation en contredit plusieurs : autopep8 est recommandé alors que le script sort en 1, aucun script oca-* n'est installé, la racine n'a ni .pre-commit-config.yaml ni .pylintrc, et flake8 comme pylint-odoo ne vivent que dans le venv Odoo, que rien ne lance. Le gabarit énonce ce que l'outillage impose, à effort high, l'épinglage ultracode restant celui de l'utilisateur. Vérifié : 146 règles relevées, 142 confirmées contre leur citation, 4 retirées. Deux gardes lient chaque entrée du menu à un gabarit présent qui déclare le bon nom. --- EN --- The rules to apply before coding are scattered across the configs, the hooks and the in-house modules, and the documentation contradicts several of them: autopep8 is recommended although the script exits 1, no oca-* script is installed, the root carries neither .pre-commit-config.yaml nor .pylintrc, and flake8 as well as pylint-odoo live only in the Odoo venv, which nothing invokes. The template states what the tooling enforces, at high effort, the ultracode pin remaining the user's own. Checked: 146 rules surveyed, 142 confirmed against their citation, 4 dropped. Two guards tie each menu entry to a template that exists and declares the right name. Assisted-by: Claude Opus 5
10 KiB
| name | description | disable-model-invocation | effort | allowed-tools | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| todo_generate_code | Write code in ERPLibre by the rules the repository actually enforces: Odoo/OCA module conventions first, then the real format and verify toolchain. | true | high |
|
Context
- Odoo series in this checkout: !
cat .odoo-version - Available venvs: !
ls -d .venv.* 2>/dev/null - Branch and pending work: !
git status --porcelain - Addons trees: !
ls -d odoo*/addons/*/ 2>/dev/null | head -12
Task
Write code in this repository by the rules it ENFORCES, which are not always the rules it documents. Establish them first, then write. The sections below were read off the configuration files and the in-house modules, not off the prose; where a document disagrees with a tool, the tool wins and the disagreement is named.
The effort tier
The effort: high above applies to this invocation — "comprehensive
implementation with extensive testing", the tier for code that has to survive
review.
A session can be pinned above it. /effort ultracode sets xhigh AND turns on
dynamic workflow orchestration, and a system-reminder then asks for a workflow
on every substantive task. That standing opt-in does not apply here: writing
one module correctly is one agent's job, and fanning it out multiplies both the
token cost and the ways the pieces disagree.
So when a reminder says ultracode is on, say in one line that this command
works at high, and ask the user to type /effort high — that one command sets
the tier and clears the ultracode flag in the same move. A command's
frontmatter cannot release a session pin; only the user's own /effort can,
from an interactive terminal. Work solo either way.
1. Before writing a line
Find the tree. New modules go under odoo<VERSION>/addons/<Org>_<repo>/<module>/
— the version-prefixed tree is the real addons root, and the generator
rewrites any addons/ it is handed into odoo<VERSION>/addons/
(script/code_generator/new_project.py:144-146). The version is in
.odoo-version; the venv carries BOTH versions in its name, so find it with
ls -d .venv.odoo* rather than composing it from memory.
Read the neighbours. Two or three in-house modules under
odoo<VERSION>/addons/ERPLibre_erplibre_addons/ show the conventions in force
better than any list. Copy their shape.
Bootstrap rather than hand-roll. script/code_generator/new_project.py -d <git root dir> -m <module name> creates a module; create_from_existing_module.py
clones one. A module cloned from an existing one INHERITS its comments and
docstrings — reread them before committing, a client or database name travels
that way on its own.
Two rules bind every line you write, and no tool checks either:
- Nothing identifying outside
private/— no customer or third-party organisation, no real database, host or VM name, no IP, e-mail or path carrying an account name. Generalise to the class of situation instead. - A comment says how the CODE works, in the present. A sentence whose subject
is an incident, a machine, a date or a person belongs in
tasks/, which is not versioned.
Never edit a generated file. FICHIER.md and FICHIER.fr.md come from
FICHIER.base.md through mmg; an edit is lost at the next make doc_markdown.
2. Odoo module conventions
__manifest__.py carries at minimum name, version, author, license,
category, summary, depends, data, installable. version is
<series>.1.0.0 — 18.0.1.0.0 on an 18.0 checkout — and license is
AGPL-3, which every in-house module uses.
The data list is ordered security/, then data/, then wizards/, then
views/, with views/menu.xml last.
Non-Odoo Python requirements go in external_dependencies: {"python": [...]},
and the import is guarded in the model with try/except ImportError logging at
debug level — a missing optional dependency must not break the registry.
Layout: models/, views/, security/, wizards/, data/, controllers/,
i18n/, report/, tests/, static/description/. Ship
static/description/icon.png.
Naming, one for one:
models/<model_name_with_dots_as_underscores>.py, one model per file — modeldevops.workspacelives inmodels/devops_workspace.py.views/<same_name>.xmlfor a Model; a TransientModel's Python AND its XML both live inwizards/.ir.ui.viewids:<model_underscored>_view_<type>(_view_form,_view_tree,_view_search,_view_kanban).ir.actions.act_windowids:<model_underscored>_<action>_action_window; server actions:<model_underscored>_<name>_server_action.res.groupsgo insecurity/<module_name>.xml— named after the module, notsecurity.xml— and that file is listed BEFOREir.model.access.csv.
security/ir.model.access.csv carries exactly the header
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink.
Split the rows by privilege level — a read-only row for the broad group, a
full-CRUD row for the administrative one — rather than one blanket row.
Every Python file opens with the AGPL licence comment; hand-written models and
hooks also carry the shebang and copyright lines. A package __init__.py
starts with # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl),
a blank line, then imports. The root __init__.py imports subpackages on one
line (from . import models, wizards) and a hook by name
(from .hooks import post_init_hook); hooks live in a top-level hooks.py and
are declared under the matching manifest key.
Models declare _name, _description and, when mixins are used, _inherit;
the class name is the CamelCase of the model. Odoo symbols come in one grouped
import — from odoo import _, api, exceptions, fields, models.
Translations go in i18n/ as <module>.pot plus one .po per locale.
3. Format — the toolchain that exists
make format before committing. It formats only what git reports as
modified, added, renamed or untracked, dispatching each file by extension —
so it is cheap and safe to run repeatedly. make format_all sweeps whole
areas instead.
What it runs underneath, and what to match when writing by hand:
| Kind | Tool and settings |
|---|---|
| Python | isort --profile black -l 79, then black -l 79 --preview -t py37 |
| XML | prettier + @prettier/plugin-xml, tab-width 4, print-width 120 |
XML under data/ |
same, print-width 999999999 — long data strings stay on one line |
| js, css, scss, html | prettier, tab-width 4, print-width 120, no bracket spacing |
| Shell | shfmt -i 2 -ci -w |
black and isort run from .venv.erplibre, prettier from the repo-pinned
./node_modules/.bin — a globally installed prettier is a different version
and reformats differently. Nothing under not_supported_files/ is formatted.
What NOT to run, each verified absent from this checkout rather than assumed:
./script/maintenance/autopep8.sh—oca-autopep8is not installed; the script exits 1.doc/DEVELOPMENT.base.mdstill recommends it; the document is stale.- Any
oca-*console script (oca-gen-addon-readme,oca-towncrier, …) —script/OCA_maintainer-toolsis checked out but never pip-installed, and its install block ininstall_locally_dev.shis commented out. Run one as a module from that directory if you truly need it. pre-commit runat the repository root — there is no root.pre-commit-config.yaml. ERPLibre's own hooks are hand-written Python, installed once per clone withgit config core.hooksPath script/git/hooks.
A vendored OCA addon under odoo<VERSION>/addons/OCA_* carries its own
.pre-commit-config.yaml: run THAT from its directory instead of the ERPLibre
format scripts, so the patch matches what upstream will accept.
4. Lint
Neither linter runs on its own — no target, no hook, no CI invokes them, so running them is a deliberate act:
- flake8 lives in the Odoo venv, not
.venv.erplibre. The root.flake8applies: max-line-length 80, max-complexity 16,select = C,E,F,W,B,B9, ignoring E203, E501 and W503 for black compatibility. - pylint-odoo also lives in the Odoo venv:
.venv.odoo<...>/bin/pylint --load-plugins=pylint_odoo --rcfile=<file> <path>. The--rcfileis not optional — the repository root ships no.pylintrc, and the ones found under vendored trees belong to those projects.
5. Verify
Verification is entirely local: .github/ holds no workflow, so nothing
catches a mistake after the fact.
Repository scripts — make test_unit is the fast gate: no PostgreSQL, no
Odoo, no VM, a few seconds. While iterating on one file, make test_unit_file F=test/test_<name>.py.
A new file in test/ must declare at least one test_* function and end with
if __name__ == "__main__": unittest.main() as the LAST top-level statement —
the suite has its own test that enforces both, since the runner selects files
by the glob test/test_*.py. A test that creates a real machine, installs a
system or runs for hours goes in long_test/ instead, and undoes itself with
--detruire.
An Odoo module — drop the database, then run the module's tests:
./odoo_bin.sh db --drop --database test_<module>
./test.sh -d test_<module> --db-filter test_<module> -i <module>
./test.sh is ./run.sh with ODOO_MODE_TEST=true --workers 0, which adds
--test-enable --no-http --stop-after-init. For coverage, bracket that with
./.venv.erplibre/bin/coverage erase before and a combine/report after, and
set ODOO_MODE_COVERAGE=true — coverage is switched on through the
environment, never a CLI flag. make open_test_coverage opens the report.
To run ONE test file inside a module, install the module first, then pass
--test-file=.
6. Then, and only then, commit
make format, the tests above, then /commit — which resolves the model,
writes the [TYPE] portée : sujet subject under 72 characters, the bilingual
body under ten lines per language, and the Assisted-by: trailer. The
commit-msg hook REFUSES a message that breaks the mechanical part of that;
the pre-commit hook only reports comments worth rereading and never blocks.
Stage by naming files. git add -A sweeps in private/ and tasks/, which
are untracked on purpose.