Tech Reads
ERP & Enterprise Systems11 min read

Migrating from Odoo 16 to 17: Nine Areas to Test Before You Upgrade

Odoo's upgrade guide makes version 17 sound like a routine update with some Python 3.12 housekeeping and a refreshed frontend. For an instance with custom modules, it rarely is. Below are nine areas to test in your own codebase before you commit to a date. One disclosure: we implement Business Central, not Odoo, so treat this as a checklist to verify against Odoo's own upgrade documentation, not as a record of projects we ran.

Why this migration is harder than the number gap suggests

The jump from Odoo 14 to 16 was painful because of the web client rewrite. The jump from 16 to 17 is painful for a different reason: the changes are distributed. Nothing single change is catastrophic, but there are dozens of small breakages in custom modules, reports, and third-party integrations that each require attention. The official migration scripts handle database schema changes. They do not handle your QWeb report templates, your custom Python controllers, or your external API calls that relied on specific response shapes.

The standard migration scripts handle the standard modules. Custom code is where the manual attention goes, and the breakdown below covers the categories where the most time tends to be lost.

1. The OWL component framework replacement is not backward compatible

Odoo 17 completes the OWL (Odoo Web Library) migration that started in version 16. Any custom JavaScript widgets or views you built using the legacy widget system will not render. They may not throw clear errors. They can simply fail to appear, or appear blank.

Picture a custom dashboard with 14 widgets, built by a previous contractor on the legacy Widget class. Every one has to be rewritten in OWL, and the contractor is no longer around to ask.

Before migration, audit every custom JS file for require(['web.Widget']) or AbstractField imports. These are all dead code in v17.

2. The res.currency.round() method signature changed

A change like this is expensive to find, because the error only surfaces during invoice validation for specific currency combinations. The currency.round() method in 16 was called as currency.round(amount). In 17 the rounding logic was internalized into the amount field computation and direct calls to the standalone method behave differently in edge cases involving zero-decimal currencies.

If you have any custom code that performs currency arithmetic directly (AP matching, custom pricing logic, intercompany reconciliation), run it through a full test matrix with zero-decimal currencies (JPY, KWD, BHD) before signing off on the migration.

3. QWeb report templates: t-att vs t-attf deprecated behavior

Version 17 enforces stricter QWeb rendering. Specifically, the t-att and t-attf directives now raise warnings (and in some cases errors) when used in ways that were silently accepted in v16. Custom invoice templates, delivery note formats, and contract PDFs built with older QWeb patterns frequently break.

The symptom is usually invisible: the report renders but a field is blank or the layout shifts. The most common culprit is t-attf-class used with complex string interpolation that evaluates to empty. In v16 this silently became an empty class attribute. In v17 under some rendering paths it causes a template abort.

Print every report type you use in a staging environment after migration. Do not just confirm the PDF generates — confirm each data field populates correctly across at least three representative records.

4. The account.move state machine has a new draft→posted guard

In Odoo 16, custom code that called move.action_post() programmatically worked as long as the move was in draft state. In Odoo 17, there is an additional validation layer that checks journal sequence configuration before allowing the state transition. Journals without properly configured sequences will block posting with a validation error that reads as a permission issue rather than a configuration issue.

Our take

Affected modules: Any custom automation rules, cron jobs, or third-party integrations that auto-post journal entries will need testing. This particularly affects EDI integrations where external systems push invoices and expect automatic confirmation.

5. ir.sequence is now used more aggressively in stock moves

Stock move references in v17 use sequence-generated codes more consistently. The practical issue: if you have any automation that reads or parses stock.move.name or stock.picking.name as a structured identifier, the format may have changed. Take a custom integration to a 3PL system that parses picking names and expects a specific prefix-number format. If the format shifts, the integration breaks silently, and picks go to the 3PL without being marked as sent.

6. Python 3.12 breaks some dependencies that were working fine

Odoo 17 targets Python 3.12. Several common Python packages that were used in Odoo 16 custom modules either have breaking changes or are no longer maintained for 3.12. The ones to check first:

  • →openpyxl < 3.1 has deprecation warnings that become errors in some Python 3.12 builds under specific conditions.
  • →Any code using distutils directly (removed in Python 3.12) will fail on import. Check your third-party modules for it too.
  • →Custom modules using imp (also removed in 3.12) need to be updated to use importlib.

Run your full module set through a 3.12 linter before migration. Odoo's own upgrade script will not catch Python version compatibility issues in your custom code.

7. The hr.leave model has significant structural changes

If you have any HR module customizations touching leave management, plan extra time. The hr.leave and hr.leave.allocation models both had field changes and the approval flow was restructured. Custom workflows that interacted with leave states (approved/refused/canceled) need to be re-validated.

Take a custom leave request portal that employees reach through an external link, built against the v16 API. After migration, the approval links in confirmation emails can break and the state field names in custom templates can be wrong. It is a half-day fix, but you do not want it found by an employee trying to approve a leave request on their first day back from vacation.

8. External API authentication changes for OAuth clients

Odoo 17 changed how OAuth authentication tokens are validated for external API clients. If you have any integrations using Odoo's built-in OAuth provider (not user/password auth), token refresh behavior changed. Long-running integrations that cached tokens for 24+ hours started failing after migration without any code change.

Any external system that connects to Odoo via OAuth rather than API key should be tested with an explicit token expiry simulation before go-live. Force token expiry in staging, confirm the integration refreshes correctly, then proceed.

9. The website module's portal view inheritance changed

For anyone using Odoo's portal module (the customer-facing web portal for orders, invoices and support), the view inheritance structure changed in v17. Custom portal pages built by inheriting base portal views need to update their inherit_id references. The original XPath targets moved.

This sounds minor. It is minor. But customer-facing portals tend to be high-visibility, and having your portal throw a rendering error on the day after go-live is the kind of thing that generates executive escalations. Test the portal independently, with an actual customer account, before the migration is declared complete.

A realistic migration timeline

For a mid-size Odoo deployment (50–150 users, 8–15 custom modules), a realistic timeline looks like this:

  • →Week 1: Module compatibility audit. Identify all custom JS widgets, QWeb templates, Python 3.12 incompatibilities, OAuth integrations.
  • →Weeks 2–4: Fix identified issues in a v17 branch. Run migration scripts against a staging copy of the production database.
  • →Week 5: User acceptance testing across every report, every portal, every external integration. Real end users running real workflows, not a developer test.
  • →Week 6: Buffer. Always needed. Never skipped.

A plan that squeezes this into four weeks tends to take longer than a plan that allowed six from the start. Treat six weeks as the minimum for anything beyond a vanilla installation.

Share