How to Store Odoo Many2many Fields in Settings

December 24, 2025 by
How to Store Odoo Many2many Fields in Settings
Widuri Sugiyani
| No comments yet


Introduction


In Odoo 17, res.config.settings is commonly used to store global or company-related configurations. The many2many fields in odoo Many2many fields in Odoo represent bidirectional many-to-many relationships between two models. They're commonly used when records from both models can be linked to multiple records from the other model. However, storing Many2many fields in settings is not straightforward because ir.config_parameter only supports string values. The model behind system settings, res.config.settings, is a Transient Model. This means:

  • Records are temporary 
  • Values are not stored automatically 
  • ir.config_parameter only supports string values

Because of this: 

  • Boolean fields can use config_parameter directly 
  • Many2many fields cannot and must be handled manually


To store Many2many settings in Odoo 17, we need to:

  1. Define the Many2many fields in res.config.settings
  2. Save their record IDs into ir.config_parameter
  3. Restore those IDs when the Settings view is opened

This is done using:

  • set_values() → save data
  • get_values() → load data


Defining the Settings Fields


Below is a generalized settings model. In this example, the configuration controls CIF/CIP breakdown behavior, but the pattern applies to any Many2many setting.

from odoo import fields, models, api

class SgeedeResConfig(models.TransientModel):
    _inherit = 'res.config.settings'

    cif_breakdown = fields.Boolean(string="CIF/CIP Breakdown", config_parameter="base.cif_breakdown")
    company_choose = fields.Many2many('res.company',string="Company")
    customer_choose = fields.Many2many('res.partner',string="Customer")
    incoterm = fields.Many2many('account.incoterms', string="Incoterms")
    shipment_term = fields.Many2many('purchase.shipment.term', string="Shipment Term")

    
    @api.model
    def get_values(self):
        res = super().get_values()
        ICP = self.env['ir.config_parameter'].sudo()

        res.update({
            'company_choose': [(6, 0, eval(ICP.get_param('base.cif_company_ids', '[]')))],
            'customer_choose': [(6, 0, eval(ICP.get_param('base.cif_customer_ids', '[]')))],
            'incoterm': [(6, 0, eval(ICP.get_param('base.cif_incoterm_ids', '[]')))],
            'shipment_term': [(6, 0, eval(ICP.get_param('base.cif_shipment_term_ids', '[]')))],
        })
        return res

    


After defining the fields and create views, the configuration will looks like this:



Saving Many2many Values (set_values)


When the user clicks Save in Settings, set_values() is executed.

def set_values(self):
        super().set_values()
        ICP = self.env['ir.config_parameter'].sudo()

        ICP.set_param('base.cif_company_ids', self.company_choose.ids)
        ICP.set_param('base.cif_customer_ids', self.customer_choose.ids)
        ICP.set_param('base.cif_incoterm_ids', self.incoterm.ids)
        ICP.set_param('base.cif_shipment_term_ids', self.shipment_term.ids)


What happens:

    • .ids returns a Python list like [1, 5, 7]
    • str() converts it to string "[1, 5, 7]"
    • This string is stored in ir.config_parameter


Loading Many2many Values (get_values)


To display previously saved values, we override get_values().

import ast

@api.model def get_values(self): res = super().get_values() ICP = self.env['ir.config_parameter'].sudo() def _get_ids(key): return [(6, 0, ast.literal_eval(ICP.get_param(key, '[]')))] res.update({ 'company_choose': _get_ids('base.cif_company_ids'), 'customer_choose': _get_ids('base.cif_customer_ids'), 'incoterm': _get_ids('base.cif_incoterm_ids'), 'shipment_term': _get_ids('base.cif_shipment_term_ids'), }) return res


Why ast.literal_eval ?

In Odoo, ir.config_parameter stores all values as strings, even when the original data represents complex structures such as lists or dictionaries. To work with these values in Python, they must be converted back into their original data types. ast.literal_eval() provides a safe way to perform this conversion by parsing only valid Python literals—such as lists, dictionaries, tuples, numbers, and strings—without executing arbitrary code. This makes it a secure and recommended alternative to eval(), especially in production environments where configuration values may be modified through the user interface or database.


Use Configuration in Business Logic


Retrieve saved settings elsewhere in your module:

import ast

def _get_cif_config(self): ICP = self.env['ir.config_parameter'].sudo() def _get_ids(key): return set(ast.literal_eval(ICP.get_param(key, '[]'))) return { 'enabled': ICP.get_param('base.cif_breakdown') == 'True', 'company_ids': _get_ids('base.cif_company_ids'), 'customer_ids': _get_ids('base.cif_customer_ids'), 'incoterm_ids': _get_ids('base.cif_incoterm_ids'), 'shipment_term_ids': _get_ids('base.cif_shipment_term_ids'), } def _compute_is_qualify(self): QUALIFYING_BUSINESS_UNITS = {'EM', 'IS', 'LS', 'ST', 'RCC', 'Website', 'Chemicals'} for rec in self: cfg = rec._get_cif_config() rec.is_qualify_to_appear = ( cfg['enabled'] and rec.company_id.id in cfg['company_ids'] and rec.partner_id.id in cfg['customer_ids'] and rec.incoterm.id in cfg['incoterm_ids'] and rec.shipment_term_id.id in cfg['shipment_term_ids'] and self._has_qualifying_business_unit(rec.order_line, QUALIFYING_BUSINESS_UNITS) )


Conclusion


Storing Many2many fields in Odoo 17 settings requires manual handling but follows a consistent pattern. By overriding get_values() and set_values() methods and using safe data conversion, you can create flexible configuration screens that persist Many2many selections across sessions. This approach ensures your settings are both user-friendly and secure, protecting your Odoo instance from potential injection attacks while providing powerful configuration capabilities.

How to Store Odoo Many2many Fields in Settings
Widuri Sugiyani December 24, 2025
Share this post
Archive
Sign in to leave a comment