15 - Advanced Fields: Reference, Selection, Json
Welcome back! In our last lesson, we mastered the bread and butter of Odoo data: Basic and Relational fields. You now know how to link a Sales Order to a Customer using a Many2one field.
But what happens when your data needs to be much more flexible? What if you need a field that can link to a Sales Order, or an Invoice, or a Helpdesk Ticket depending on the situation? Or what if you need to store complex, unpredictable data from a third-party API?
For these tricky scenarios, Odoo 19 provides advanced field types. Let's explore the Reference, Selection (Advanced), and JSON fields.
1. The Reference Field: The Ultimate Shapeshifter
A Many2one field is great, but it has one strict limitation: it can only point to one specific model.
Imagine you are building a "Global Task" app. You want to attach a task to anything in Odoo. Sometimes the task is for a res.partner (Customer), sometimes it's for a sale.order (Sales Order), and sometimes it's for a fleet.vehicle (Car). You can't create 50 different Many2one fields just to handle this!
Enter the Reference Field (fields.Reference).
A Reference field allows the user to select the Model first, and then select the Record from that model. Under the hood in your PostgreSQL database, Odoo stores this as a simple string combining the model name and the ID, like this: sale.order,42.
How to Write a Reference Field
To use a Reference field, you must give it a list of models the user is allowed to choose from.
from odoo import models, fields
class GlobalTask(models.Model):
_name = 'global.task'
_description = 'Global Task'
name = fields.Char(string='Task Name', required=True)
# We define the models the user can link this task to
reference_record = fields.Reference(
selection=[
('res.partner', 'Customer'),
('sale.order', 'Sales Order'),
('fleet.vehicle', 'Company Vehicle')
],
string='Attached To'
)
2. Advanced Selection Fields: Dynamic Lists
We briefly looked at Selection fields in the last lesson. Normally, you hardcode a fixed list of options, like [('draft', 'Draft'), ('done', 'Done')].
But what if you want the options to change dynamically based on logic, or you want other developers to be able to add their own options later?
Dynamic Selections using a Method
Instead of passing a fixed list, you can pass the name of a function that generates the list!
class CustomReport(models.Model):
_name = 'custom.report'
_description = 'Custom Report'
# Notice we pass a string matching the function name below
report_type = fields.Selection(
selection='_get_dynamic_report_types',
string='Report Type'
)
def _get_dynamic_report_types(self):
# You could run complex logic here, check user permissions,
# or fetch data from somewhere else before returning the list!
options = [
('sales', 'Sales Report'),
('inventory', 'Inventory Report')
]
if self.env.user.has_group('base.group_system'):
options.append(('admin', 'Admin Audit Report'))
return options
Extending Selections (selection_add)
If you are inheriting an existing model (like a Sales Order) and want to add a new option to an existing selection field, Odoo 19 makes it incredibly easy. You just use selection_add!
class SaleOrder(models.Model):
_inherit = 'sale.order'
# We are adding a 'quality_check' state to the existing standard field!
state = fields.Selection(
selection_add=[('quality_check', 'In Quality Check')]
)
3. The Json Field: Total Data Freedom
Sometimes, you need to store data, but you don't know exactly what that data will look like. Maybe you are pulling weather data from an external API, or saving a user's custom layout preferences for a website builder.
Creating 20 separate Char and Integer fields for unpredictable data is a nightmare.
Odoo solves this with the JSON Field (fields.Json). It utilizes PostgreSQL's powerful JSONB column type under the hood. It allows you to store entire Python dictionaries or lists directly in a single database column.
class WebIntegration(models.Model):
_name = 'web.integration'
_description = 'API Integration Data'
name = fields.Char(string='Integration Name')
# This field can hold virtually anything: a dict, a list, strings, etc.
api_response_data = fields.Json(string='Raw API Data')
def fetch_data(self):
for record in self:
# Storing a complex dictionary directly into the field
record.api_response_data = {
'status': 'success',
'user_count': 150,
'active_modules': ['sales', 'accounting', 'website'],
'settings': {
'theme': 'dark',
'notifications': True
}
}
💡 Developer Pro-Tips for Odoo 19
Reference Field Performance: Because Reference fields can point to any table, Odoo cannot create standard database foreign keys for them. This means searching or grouping by a Reference field can be slower than a standard Many2one field. Use them only when you truly need that flexibility!
Getting the Record from a Reference: If you access self.reference_record in Python, Odoo is smart enough to return the actual recordset (e.g., sale.order(42,)), so you can immediately do things like self.reference_record.name!
JSON Field Default Values: If you use a fields.Json, it is usually a good idea to set a default value of an empty dictionary or list to avoid NoneType errors in your code. You can do this by adding default=dict or default=list to the field definition.
Homework: Try adding a fields.Reference to one of your custom models that allows you to link the record to either a res.users (User) or a res.partner (Contact).
There are no comments for now.