Enable Dark Mode!
By: Arunima

How to Create & Manage a Custom Field From a Function in Odoo 15


Odoo has almost all types of fields for specific functionality. We usually create a  field inside a model along with its class definition. But in some business scenarios, we may have to add fields in a model and customize accordingly from the User interface itself. So in this blog, let us see how we create a custom field inside a model from a function and how we can define its position in the view.

For creating the field, first of all, we need a model to specify the field.


class FieldCreation(models.Model):
   _name = 'dynamic.fields'
   _description = 'Dynamic Field Creation'
   _inherit = 'ir.model.fields'
   position_field = fields.Many2one('ir.model.fields', string='Field Name')
   position = fields.Selection([('before', 'Before'),
                                ('after', 'After')], string='Position', required=True)
   model_id = fields.Many2one('ir.model', string='Model', required=True, index=True, ondelete='cascade',
                              help="The model this field belongs to")
   ref_model_id = fields.Many2one('ir.model', string='Model', index=True)
   field_selection = fields.Char(string="Selection Options")
   relation_field = fields.Many2one('ir.model.fields', string='Related Field')
   ttype = fields.Selection(string="Field Type", related='field_type')
   field_type = fields.Selection(selection='get_possible_field_types', string='Field Type', required=True)
   extra_features = fields.Boolean(string="Show Extra Properties")
   groups = fields.Many2many('res.groups', 'dynamic_field_creation_id', 'field_id', 'group_id')
def get_possible_field_types(self):
   """Return all available field types excluding 'one2many' and 'reference' fields."""
   field_list = sorted((key, key) for key in fields.MetaField.by_type)
   field_list.remove(('one2many', 'one2many'))
   field_list.remove(('reference', 'reference'))
   return field_list
def onchange_field_type(self):
   if self.field_type:
       if self.field_type == 'binary':
           return {'domain': {'widget': [('name', '=', 'image')]}}
       elif self.field_type == 'many2many':
           return {'domain': {'widget': [('name', 'in', ['many2many_tags', 'binary'])]}}
       elif self.field_type == 'selection':
           return {'domain': {'widget': [('name', 'in', ['radio', 'priority'])]}}
       elif self.field_type == 'float':
           return {'domain': {'widget': [('name', '=', 'monetary')]}}
       elif self.field_type == 'many2one':
           return {'domain': {'widget': [('name', '=', 'selection')]}}
           return {'domain': {'widget': [('id', '=', False)]}}
   return {'domain': {'widget': [('id', '=', False)]}}

This custom model is created by inheriting ‘ir.model.fields’, which is a base model for fields in Odoo. So we can make use of the fields in ‘ir.model.fields’ in our custom model.


The groups field is defined in our custom model again because we will get a 500 error, for the reason,’ Many2many fields dynamic.fields.groups and ir.model.fields.groups use the same table and columns.

In order to fix this problem, we need to define a custom field in ‘res.groups’ in the relation to our custom model.

class Groups(models.Model):
   _inherit = 'res.groups'
   dynamic_field_creation_id = fields.Many2one('dynamic.fields')

In our custom model, we need to define the fields,

position_field: To define the position of the custom field. It is a many2one field in relation to ‘ir.model.fields’. The view of our custom field can be defined based on ‘position_field’.

position: To define the position of the custom field. It is a selection field with two values, Before and After. We can define the position of the custom field a ‘Before’ or ‘After’ the ‘position_field’.

model_id: The model in which we are creating the custom field.

field_type: To specify the type of the field. It is a selection field. The selection values are returned in the function ‘get_possible_field_types’. The function will return all field types excluding ‘one2many’ and ‘reference’.

ref_model_id: If the custom model is a relational field, it is to specify the relation of the custom field.

Now let's see how the view of this custom model is defined,


<?xml version="1.0" encoding="utf-8"?>
  <record model='ir.ui.view' id='wizard_dynamic_fields_form'>
      <field name="name">dynamic.fields.form</field>
      <field name="model">dynamic.fields</field>
      <field name="arch" type="xml">
          <form string="Dynamic Field Creation">
                      <group string="Field Info">
                          <field name="name"/>
                          <field name="field_description"/>
                          <field name="state" readonly="1" groups="base.group_no_one"/>
                          <field name="model_id" options='{"no_open": True, "no_create": True}'/>
                          <field name="field_type"/>
                          <field name="field_selection" placeholder="[('blue', 'Blue'),('yellow', 'Yellow')]"
                                 attrs="{'required': [('field_type','in',['selection','reference'])],
                                                  'readonly': [('field_type','not in',['selection','reference'])],
                                                  'invisible': [('field_type','not in',['selection','reference'])]}"/>
                          <field name="ref_model_id" options='{"no_open": True, "no_create": True}' attrs="{'required': [('field_type','in',['many2one','many2many'])],
                                                              'readonly': [('field_type','not in',['many2one','many2many'])],
                                                              'invisible': [('field_type','not in',['many2one','many2many'])]}"/>
                          <field name="required"/>
                      <group string="Position">
                          <field name="position_field" options='{"no_open": True, "no_create": True}'/>
                          <field name="position"/>
                  <group string="Extra Properties">
                          <field name="extra_features"/>
                      <group attrs="{'invisible': [('extra_features', '=', False)]}">
                          <field name="help"/>
                      <group attrs="{'invisible': [('extra_features', '=', False)]}">
                          <field name="readonly"/>
                          <field name="store"/>
                          <field name="index"/>
                          <field name="copied"/>
                  <button name="create_custom_field" string="Create Field" type="object" class="oe_highlight"/>
                  <button string="Cancel" class="oe_link" special="cancel"/>
  <record model='ir.actions.act_window' id='action_dynamic_fields'>
      <field name="name">Create Custom Fields</field>
      <field name="res_model">dynamic.fields</field>
      <field name="view_mode">form</field>
      <field name="view_id" ref="wizard_dynamic_fields_form"/>
      <field name="target">new</field>
  <!-- Menu Item in Employee to create fields -->
          name="Create Fields"


Let's create a field inside the model ‘hr.employee’ like this,


Now let us see the function of the button ‘create_custom_field’ to create the custom field.

def create_custom_field(self):
   self.env['ir.model.fields'].sudo().create({'name': self.name,
                                              'field_description': self.field_description,
                                              'model_id': self.model_id.id,
                                              'ttype': self.field_type,
                                              'relation': self.ref_model_id.model,
                                              'required': self.required,
                                              'index': self.index,
                                              'store': self.store,
                                              'help': self.help,
                                              'readonly': self.readonly,
                                              'selection': self.field_selection,
                                              'copied': self.copied,
   inherit_id = self.env.ref('hr.view_employee_form')
   arch_base = _('<?xml version="1.0"?>'
                 '<field name="%s" position="%s">'
                 '<field name="%s"/>'
                 '</data>') % (self.position_field.name, self.position, self.name)
   self.env['ir.ui.view'].sudo().create({'name': 'employee.dynamic.fields.%s' % self.name,
                                         'type': 'form',
                                         'model': 'hr.employee',
                                         'mode': 'extension',
                                         'inherit_id': inherit_id.id,
                                         'arch_base': arch_base,
                                         'active': True})
   return {
       'type': 'ir.actions.client',
       'tag': 'reload',

Inside this function definition, the field creation is done inside the model ‘ir.model.fields’ using the ‘create’ method. The external id of the employee form view is ‘hr.view_employee_form’. We need to define the view of the custom field ‘Test’ after the ‘Coach’ field of this particular view. So we will get the id of the view record in ‘inherit_id.’After that, we will create a new view inheriting the view  ‘hr.view_employee_form’ using the ‘create’ method in ‘ir.ui.view’.


Sometimes it will be needed to create some fields inside a model from UI. In those cases, we can create a custom field inside a model in this way.

If you need any assistance in odoo, we are online, please chat with us.


Leave a comment




Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635



Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.



Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message