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.
field_creation.py
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')
@api.model
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
@api.depends('field_type')
@api.onchange('field_type')
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')]}}
else:
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.
Note:
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,
field_creation.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<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">
<sheet>
<group>
<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>
<group string="Position">
<field name="position_field" options='{"no_open": True, "no_create": True}'/>
<field name="position"/>
</group>
</group>
<group string="Extra Properties">
<group>
<field name="extra_features"/>
</group>
<group attrs="{'invisible': [('extra_features', '=', False)]}">
<field name="help"/>
</group>
<group attrs="{'invisible': [('extra_features', '=', False)]}">
<field name="readonly"/>
<field name="store"/>
<field name="index"/>
<field name="copied"/>
</group>
</group>
</sheet>
<footer>
<button name="create_custom_field" string="Create Field" type="object" class="oe_highlight"/>
<button string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<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>
</record>
<!-- Menu Item in Employee to create fields -->
<menuitem
id="menu_create_fields"
name="Create Fields"
parent="hr.menu_hr_employee_payroll"
action="button_near_create.action_dynamic_fields"
sequence="10"/>
</odoo>
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"?>'
'<data>'
'<field name="%s" position="%s">'
'<field name="%s"/>'
'</field>'
'</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.