Most of the time I spend on an Odoo version upgrade is figuring out who wrote what.
By the time an Odoo deployment is two or three years old, it has typically lived through more than one consultant. Each one left modules behind. Each one extended res.partner and sale.order in their own style. None of them prefixed anything. The result is a graveyard where you can't tell what's standard Odoo, what's a third-party app, what's the last consultant's good idea, and what's their emergency hack.
Our rule is simple. Every custom artifact DimeSoft writes (modules, models, fields, methods, XML IDs, asset filenames, security groups) is prefixed ds_. No exceptions. The convention pays off most exactly when you most need it: during version upgrades, audits, and handoffs.
Any consistent prefix works. What matters is picking one and using it everywhere, every time.
Why namespacing matters more in Odoo than people expect
Odoo's module and model namespaces are flat. Two modules in your addons_path cannot have the same name. Two models in the registry cannot have the same _name. Two XML records sharing an external ID will collide loudly at install time.
That sounds like the system is protecting you. It isn't. The system catches exact collisions, not the worse problem: ambiguity.
Take a custom field x_discount_tier on res.partner, added a year ago by a previous consultant. You don't know why. You don't know which workflow reads it. You don't know whether removing it will break the website module, the accounting reports, or that one cron job. You search the codebase for x_discount_tier and get fourteen hits across nine modules. Some are your code, some are core Odoo, some are third-party apps with their own meaning of that string.
If the field had been called acme_discount_tier, you would know immediately whose decision this was, which modules belong together, and what you could safely change.
The flat namespace doesn't punish lazy naming with an install error. It punishes you a year later, when you're trying to understand a codebase no one fully remembers anymore.
The rule: ds_ everywhere
Every artifact we create starts with ds_. The full inventory:
- Module technical name.
ds_warehouse_split_picking,ds_partner_credit_terms. Module names are flat, global, and permanent. This is the highest-stakes namespacing decision. - New model names.
ds.partner.credit.review,ds.warehouse.pick.batch. When we introduce a new entity, the_namestarts withds.(Odoo uses dots in model names and underscores in everything else, and we follow that convention). - New fields on existing models. A custom field on
res.partnerisds_credit_tier, neverx_credit_tierand never barecredit_tier. Thex_prefix is reserved by Odoo Studio. Using it on hand-written code makes the distinction between Studio output and developer output impossible to recover later. (More on Studio in another article.) - Method names. Methods we add to a model are prefixed
ds_. Overrides of Odoo methods keep the original name (we have to) but callsuper()and are clearly marked as overrides in code review. - XML record IDs. Every record we add via XML has an ID like
ds_view_partner_credit_formords_action_credit_review_list. Views, actions, menus, demo data, security records: everything. - Security groups and rules.
ds_group_credit_reviewer. Record rules:ds_rule_credit_review_company. - Asset filenames. JavaScript and SCSS files inside our modules'
static/src/trees useds_filenames where the file name is likely to appear in another module's includes.
The principle: anything that has a name visible across a module boundary, prefix it.
What this looks like in practice
A small concrete example. Suppose we are building a module that adds a credit-review workflow on top of res.partner. Module name: ds_partner_credit_review.
The manifest:
# ds_partner_credit_review/__manifest__.py
{
"name": "DimeSoft Partner Credit Review",
"version": "19.0.1.0.0",
"depends": ["base", "contacts"],
"data": [
"security/ds_security.xml",
"views/ds_partner_views.xml",
"views/ds_credit_review_views.xml",
],
"license": "LGPL-3",
}
A field added to res.partner:
# ds_partner_credit_review/models/ds_res_partner.py
from odoo import fields, models
class DsResPartner(models.Model):
# Extending the stock res.partner model. We do not rename it.
_inherit = "res.partner"
# Custom field. ds_ prefix. Not x_. Not bare credit_tier.
# The prefix tells the next developer this is DimeSoft custom code,
# so it can be found, audited, and upgraded as a single unit.
ds_credit_tier = fields.Selection(
[("standard", "Standard"),
("priority", "Priority"),
("hold", "Hold")],
string="Credit Tier",
help="DimeSoft credit review classification.",
)
A view inheriting the partner form:
<record id="ds_view_partner_form_credit" model="ir.ui.view">
<field name="name">ds.res.partner.form.credit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='sales_purchases']" position="after">
<page string="Credit Review" name="ds_credit_review">
<field name="ds_credit_tier"/>
</page>
</xpath>
</field>
</record>
The record ID is ds_view_partner_form_credit. The view's name field is ds.res.partner.form.credit. The page name attribute is ds_credit_review. The page string ("Credit Review") is user-facing and has no prefix. Prefixes are for technical names, not for what end users see.
Three years from now, a search for ds_ across the addons tree returns every DimeSoft artifact, and nothing else.
Where the convention pays off
Version upgrades. When a new Odoo version changes an inherited field signature, you don't have to wonder which fields are yours. A search across your addons tree for ds_ (in PyCharm, the project-wide find; on the command line, grep -rn "ds_" .) lists every place to look. Without the prefix, you'd search for the field name itself, sort through dozens of unrelated hits in core Odoo and third-party apps, and miss the one that actually breaks in production.
Audits. A new client hands us their Odoo deployment with code from two prior consultants. The first triage step is mechanical: identify what's standard Odoo and what's custom. A consistent prefix on the custom code reduces a week of archaeology to an afternoon. Our convention is ds_. Other shops use their own. The real problem is when no one used a convention.
Handoffs. This includes handoffs back to the client, or to the next consultant if our engagement ends. Code we wrote is identifiable at a glance. The client owns it, can read it, and is not dependent on us to know which lines are ours.
Debugging. A stack trace mentioning ds_credit_review._ds_compute_credit_state tells you in one line: this is DimeSoft custom code, the model is the credit review, and the method is a computed-field calculator. That's hours of orientation saved per bug.
Field origin at a glance. Open developer mode on any Odoo form and inspect a field. The technical name is right there. ds_credit_tier tells you immediately it's DimeSoft custom code, not a stock Odoo field. Without the prefix, you'd have to dig through module manifests to find where the field came from. This sounds minor until you do it twenty times in a workday.
Conflict avoidance. If a third-party Odoo app later adds a credit_tier field to res.partner, our ds_credit_tier is unaffected. No silent shadowing. No mysterious "why did the value change" bugs.
An honest caveat
The ds_ convention is a discipline, not a magic spell. It does not make our code better. It makes our code identifiable, which is a different and smaller claim.
It has costs. Names get longer. Some patterns look awkward at first. The prefix is not the only convention that matters: file layout, method naming patterns, view inheritance discipline, and security model conventions all matter at least as much.
Pick a prefix and hold the line. ds_ works for us because DimeSoft is short and unambiguous. If your shop is called Acme Consulting, acme_ is fine. If your client is doing the work themselves, their company short-name works fine. What matters is consistency across every artifact, across every developer, across every project. The prefix you pick matters less than the discipline of using it everywhere, every time.
The one place I'd push back hardest: don't use x_ for hand-written code. It collides with Studio's auto-generated output and erases a distinction you'll want back during your next version upgrade.
If you're running Odoo and have lived through more than one consultant, you already know what an unprefixed codebase costs you. Every modification is a research project. Every version upgrade is archaeology.
DimeSoft has been writing Odoo modules under this discipline for years. If you're inheriting code from a previous consultant and not sure what you have, an audit is usually the right starting point, and we'd start it with a real conversation rather than a sales pitch. We'd look at what's actually in your codebase and what's worth cleaning up first. Reach out if that would help.
Most of the time I spend on an Odoo version upgrade is figuring out who wrote what.
By the time an Odoo deployment is two or three years old, it has typically lived through more than one consultant. Each one left modules behind. Each one extended res.partner and sale.order in their own style. None of them prefixed anything. The result is a graveyard where you can't tell what's standard Odoo, what's a third-party app, what's the last consultant's good idea, and what's their emergency hack.
Our rule is simple. Every custom artifact DimeSoft writes (modules, models, fields, methods, XML IDs, asset filenames, security groups) is prefixed ds_. No exceptions. The convention pays off most exactly when you most need it: during version upgrades, audits, and handoffs.
Any consistent prefix works. What matters is picking one and using it everywhere, every time.
Why namespacing matters more in Odoo than people expect
Odoo's module and model namespaces are flat. Two modules in your addons_path cannot have the same name. Two models in the registry cannot have the same _name. Two XML records sharing an external ID will collide loudly at install time.
That sounds like the system is protecting you. It isn't. The system catches exact collisions, not the worse problem: ambiguity.
Take a custom field x_discount_tier on res.partner, added a year ago by a previous consultant. You don't know why. You don't know which workflow reads it. You don't know whether removing it will break the website module, the accounting reports, or that one cron job. You search the codebase for x_discount_tier and get fourteen hits across nine modules. Some are your code, some are core Odoo, some are third-party apps with their own meaning of that string.
If the field had been called acme_discount_tier, you would know immediately whose decision this was, which modules belong together, and what you could safely change.
The flat namespace doesn't punish lazy naming with an install error. It punishes you a year later, when you're trying to understand a codebase no one fully remembers anymore.
The rule: ds_ everywhere
Every artifact we create starts with ds_. The full inventory:
- Module technical name.
ds_warehouse_split_picking,ds_partner_credit_terms. Module names are flat, global, and permanent. This is the highest-stakes namespacing decision. - New model names.
ds.partner.credit.review,ds.warehouse.pick.batch. When we introduce a new entity, the_namestarts withds.(Odoo uses dots in model names and underscores in everything else, and we follow that convention). - New fields on existing models. A custom field on
res.partnerisds_credit_tier, neverx_credit_tierand never barecredit_tier. Thex_prefix is reserved by Odoo Studio. Using it on hand-written code makes the distinction between Studio output and developer output impossible to recover later. (More on Studio in another article.) - Method names. Methods we add to a model are prefixed
ds_. Overrides of Odoo methods keep the original name (we have to) but callsuper()and are clearly marked as overrides in code review. - XML record IDs. Every record we add via XML has an ID like
ds_view_partner_credit_formords_action_credit_review_list. Views, actions, menus, demo data, security records: everything. - Security groups and rules.
ds_group_credit_reviewer. Record rules:ds_rule_credit_review_company. - Asset filenames. JavaScript and SCSS files inside our modules'
static/src/trees useds_filenames where the file name is likely to appear in another module's includes.
The principle: anything that has a name visible across a module boundary, prefix it.
What this looks like in practice
A small concrete example. Suppose we are building a module that adds a credit-review workflow on top of res.partner. Module name: ds_partner_credit_review.
The manifest:
# ds_partner_credit_review/__manifest__.py
{
"name": "DimeSoft Partner Credit Review",
"version": "19.0.1.0.0",
"depends": ["base", "contacts"],
"data": [
"security/ds_security.xml",
"views/ds_partner_views.xml",
"views/ds_credit_review_views.xml",
],
"license": "LGPL-3",
}
A field added to res.partner:
# ds_partner_credit_review/models/ds_res_partner.py
from odoo import fields, models
class DsResPartner(models.Model):
# Extending the stock res.partner model. We do not rename it.
_inherit = "res.partner"
# Custom field. ds_ prefix. Not x_. Not bare credit_tier.
# The prefix tells the next developer this is DimeSoft custom code,
# so it can be found, audited, and upgraded as a single unit.
ds_credit_tier = fields.Selection(
[("standard", "Standard"),
("priority", "Priority"),
("hold", "Hold")],
string="Credit Tier",
help="DimeSoft credit review classification.",
)
A view inheriting the partner form:
<record id="ds_view_partner_form_credit" model="ir.ui.view">
<field name="name">ds.res.partner.form.credit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='sales_purchases']" position="after">
<page string="Credit Review" name="ds_credit_review">
<field name="ds_credit_tier"/>
</page>
</xpath>
</field>
</record>
The record ID is ds_view_partner_form_credit. The view's name field is ds.res.partner.form.credit. The page name attribute is ds_credit_review. The page string ("Credit Review") is user-facing and has no prefix. Prefixes are for technical names, not for what end users see.
Three years from now, a search for ds_ across the addons tree returns every DimeSoft artifact, and nothing else.
Where the convention pays off
Version upgrades. When a new Odoo version changes an inherited field signature, you don't have to wonder which fields are yours. A search across your addons tree for ds_ (in PyCharm, the project-wide find; on the command line, grep -rn "ds_" .) lists every place to look. Without the prefix, you'd search for the field name itself, sort through dozens of unrelated hits in core Odoo and third-party apps, and miss the one that actually breaks in production.
Audits. A new client hands us their Odoo deployment with code from two prior consultants. The first triage step is mechanical: identify what's standard Odoo and what's custom. A consistent prefix on the custom code reduces a week of archaeology to an afternoon. Our convention is ds_. Other shops use their own. The real problem is when no one used a convention.
Handoffs. This includes handoffs back to the client, or to the next consultant if our engagement ends. Code we wrote is identifiable at a glance. The client owns it, can read it, and is not dependent on us to know which lines are ours.
Debugging. A stack trace mentioning ds_credit_review._ds_compute_credit_state tells you in one line: this is DimeSoft custom code, the model is the credit review, and the method is a computed-field calculator. That's hours of orientation saved per bug.
Field origin at a glance. Open developer mode on any Odoo form and inspect a field. The technical name is right there. ds_credit_tier tells you immediately it's DimeSoft custom code, not a stock Odoo field. Without the prefix, you'd have to dig through module manifests to find where the field came from. This sounds minor until you do it twenty times in a workday.
Conflict avoidance. If a third-party Odoo app later adds a credit_tier field to res.partner, our ds_credit_tier is unaffected. No silent shadowing. No mysterious "why did the value change" bugs.
An honest caveat
The ds_ convention is a discipline, not a magic spell. It does not make our code better. It makes our code identifiable, which is a different and smaller claim.
It has costs. Names get longer. Some patterns look awkward at first. The prefix is not the only convention that matters: file layout, method naming patterns, view inheritance discipline, and security model conventions all matter at least as much.
Pick a prefix and hold the line. ds_ works for us because DimeSoft is short and unambiguous. If your shop is called Acme Consulting, acme_ is fine. If your client is doing the work themselves, their company short-name works fine. What matters is consistency across every artifact, across every developer, across every project. The prefix you pick matters less than the discipline of using it everywhere, every time.
The one place I'd push back hardest: don't use x_ for hand-written code. It collides with Studio's auto-generated output and erases a distinction you'll want back during your next version upgrade.
If you're running Odoo and have lived through more than one consultant, you already know what an unprefixed codebase costs you. Every modification is a research project. Every version upgrade is archaeology.
DimeSoft has been writing Odoo modules under this discipline for years. If you're inheriting code from a previous consultant and not sure what you have, an audit is usually the right starting point, and we'd start it with a real conversation rather than a sales pitch. We'd look at what's actually in your codebase and what's worth cleaning up first. Reach out if that would help.