Enable Dark Mode!
how-to-create-and-manage-a-custom-field-from-a-function-in-odoo-17.jpg
By: Jumana Jabin MP

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

Technical Odoo 17

In this blog, we'll explore how to dynamically create custom fields within an Odoo model directly from a function. This approach provides flexibility for adapting to specific business needs. Instead of defining fields in a model's class, we'll see how to generate and customize fields on the fly from the user interface. Additionally, we'll cover how to define the position of these dynamically created fields in the view.

Before creating the field, it's essential to have a model where the field can be specified and defined. This model forms the basis for the subsequent field creation.

import xml.etree.ElementTree as xee
from odoo import api, fields, models, _
class EmployeeDynamicFields(models.TransientModel):
   """
      Model for creating dynamic fields and adding necessary fields
   """
   _name = 'employee.dynamic.fields'
   _description = 'Dynamic Fields'
   _inherit = 'ir.model.fields'

   form_view_id = fields.Many2one('ir.ui.view',
                                  string="Form View ID",
                                  help="View ID of the form")

   @api.model
   def get_possible_field_types(self):
       """
          Return all available field types other than '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 set_domain(self):
       """Return the fields that currently present in the form"""
       view_id = self.env.ref('hr.view_employee_form')
       view_arch = str(view_id.arch_base)
       doc = xee.fromstring(view_arch)
       field_list = []
       for tag in doc.findall('.//field'):
           field_list.append(tag.attrib['name'])
       model_id = self.env['ir.model'].sudo().search(
           [('model', '=', 'hr.employee')])
       return [('model_id', '=', model_id.id), ('state', '=', 'base'),
               ('name', 'in', field_list)]
   def _set_default(self):
       """
           This method is used to set a default filter in a domain expression
           for the 'hr.employee' model.It retrieves the ID of the
           'hr.employee' model using a search query and sets it as a default
           filter in the domain expression.
       """
       model_id = self.env['ir.model'].sudo().search(
           [('model', '=', 'hr.employee')])
       return [('id', '=', model_id.id)]
   def action_create_fields(self):
       """
          This method is used to create custom fields for the 'hr.employee'
          model and extend the employee form view.It creates a new field in
          the 'ir.model.fields' table, extends the 'hr.view_employee_form'
          view.
       """
       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.selection_field,
            'copied': self.copied,
            'is_employee_dynamic': True
            })
       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_id.name, self.position, self.name)
       if self.widget_id:
           arch_base = _('<?xml version="1.0"?>'
                         '<data>'
                         '<field name="%s" position="%s">'
                         '<field name="%s" widget="%s"/>'
                         '</field>'
                         '</data>') % (
                           self.position_field_id.name, self.position,
                           self.name,
                           self.widget.name)

       self.form_view_id = self.env['ir.ui.view'].sudo().create(
           {'name': 'employee.dynamic.fields',
            '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',
       }
   position_field_id = fields.Many2one('ir.model.fields',
                                       string='Field Name',
                                       domain=set_domain, required=True,
                                       ondelete='cascade',
                                       help="Position of the field")
   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",
                              domain=_set_default)
   ref_model_id = fields.Many2one('ir.model', string='Model',
                                  index=True)
   selection_field = fields.Char(string="Selection Options")
   rel_field_id = fields.Many2one('ir.model.fields',
                                  string='Related Field')
   field_type = fields.Selection(selection='get_possible_field_types',
                                 string='Field Type', required=True)
   ttype = fields.Selection(string="Field Type", related='field_type',
                            help="Specifies the type of the field")
   widget_id = fields.Many2one('employee.field.widgets',
                               string='Widget', help="Widget of the field")
   groups = fields.Many2many('res.groups',
                             'employee_dynamic_fields_group_rel',
                             'field_id', 'group_id')
   extra_features = fields.Boolean(string="Show Extra Properties",
                                   help="Add extra features for the field")
   @api.depends('field_type')
   @api.onchange('field_type')
   def onchange_field_type(self):
       """
           This method is triggered when the 'field_type' attribute is changed
           in a form. It provides a domain for the 'widget' attribute based on
           the selected field type.
       """
       if self.field_type:
           if self.field_type == 'binary':
               return {'domain': {'widget_id': [('name', '=', 'image')]}}
           elif self.field_type == 'many2many':
               return {'domain': {
                   'widget_id': [
                       ('name', 'in', ['many2many_tags', 'binary'])]}}
           elif self.field_type == 'selection':
               return {'domain': {
                   'widget_id': [('name', 'in', ['radio', 'priority'])]}}
           elif self.field_type == 'float':
               return {'domain': {'widget_id': [('name', '=', 'monetary')]}}
           elif self.field_type == 'many2one':
               return {'domain': {'widget_id': [('name', '=', 'selection')]}}
           else:
               return {'domain': {'widget_id': [('id', '=', False)]}}
       return {'domain': {'widget_id': [('id', '=', False)]}}

This specialized model is established through inheritance from 'ir.model.fields,' a fundamental model for fields within Odoo 17. Consequently, we gain access to the functionalities provided by the fields in 'ir.model.fields' for use in our custom model.

In our custom model, the following fields must be defined:

position_field_id: This field is a many2one relationship with 'ir.model.fields' and is used to determine the position of the custom field. The view of our custom field is structured based on the selected 'position_field_id.'

position: A selection field with two values, 'Before' and 'After.' This field allows us to specify whether the custom field should be positioned before or after the 'position_field.'

model_id: Denotes the model in which the custom field is being created.

field_type: This selection field specifies the type of the custom field. The selection values are retrieved from the 'get_possible_field_types' function, which excludes 'one2many' and 'reference' field types.

ref_model_id: If the custom model represents a relational field, this field is used to specify the relation for the custom field.

By defining these fields, we establish the essential parameters for configuring and structuring our custom fields within the Odoo model.

Next, let's explore how the view of our custom model is configured, determining how the model is visually presented and organized.

<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!--    Wizard view for creating new fields -->
   <record id='employee_dynamic_fields_view_form' model='ir.ui.view'>
       <field name="name">employee.dynamic.fields.view.form</field>
       <field name="model">employee.dynamic.fields</field>
       <field name="arch" type="xml">
           <form string="Dynamic Fields">
               <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="selection_field"
                                  placeholder="[('blue', 'Blue'),('yellow', 'Yellow')]"
                                  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}'
                                  required="field_type in ('many2one','many2many')"
                                  readonly="field_type not in ('many2one','many2many')"
                                  invisible="field_type not in ('many2one','many2many')"/>
                           <field name="widget_id" widget="selection"
                                  invisible="field_type not in ('binary', 'many2many', 'selection', 'float', 'many2one')"/>
                           <field name="required"/>
                       </group>
                       <group string="Position">
                           <field name="position_field_id"
                                  options='{"no_open": True, "no_create": True}'/>
                           <field name="position"/>
                       </group>
                   </group>
                   <group string="Extra Properties">
                       <group>
                           <field name="extra_features"/>
                       </group>
                       <group invisible="extra_features == False">
                           <field name="help"/>
                       </group>
                       <group invisible="extra_features == False">
                           <field name="readonly"/>
                           <field name="store"/>
                           <field name="index"/>
                           <field name="copied"/>
                       </group>
                   </group>
               </sheet>
               <footer>
                   <button name="action_create_fields" string="Create Fields"
                           type="object" class="oe_highlight"/>
                   <button string="Cancel" class="oe_link" special="cancel"/>
               </footer>
           </form>
       </field>
   </record>
   <record id='employee_dynamic_fields_view_tree' model='ir.ui.view'>
       <field name="name">employee.dynamic.fields.view.tree</field>
       <field name="model">employee.dynamic.fields</field>
       <field name="arch" type="xml">
           <tree create="false">
               <field name="name"/>
               <field name="field_description"/>
               <field name="ttype"/>
           </tree>
       </field>
   </record>
   <record id='action_employee_dynamic_fields' model='ir.actions.act_window'>
       <field name="name">Create Custom Fields</field>
       <field name="res_model">employee.dynamic.fields</field>
       <field name="view_mode">form</field>
       <field name="view_id" ref="employee_dynamic_fields_view_form"/>
       <field name="target">new</field>
   </record>
   <record id="action_employee_dynamic_fields_delete"
           model="ir.actions.act_window">
       <field name="name">Delete Fields</field>
       <field name="res_model">employee.dynamic.fields</field>
       <field name="view_mode">tree</field>
       <field name="view_id" ref="employee_dynamic_fields_view_tree"/>
       <field name="help" type="html">
           <p class="o_view_nocontent_smiling_face">
               Delete created custom fields
           </p>
       </field>
   </record>
   <!-- Menu Item in Employee to create fields -->
   <menuitem id="employee_dynamic_fields_menu"
           name="Create Fields"
           parent="hr.menu_hr_employee_payroll"
           action="employee_dynamic_fields.action_employee_dynamic_fields"
           groups="employee_dynamic_fields.employee_dynamic_fields_group_add_employee_custom_fields"
           sequence="10"/>
   <menuitem id="employee_dynamic_fields_menu_delete"
             name="Delete Fields"
             parent="hr.menu_hr_employee_payroll"
             action="action_employee_dynamic_fields_delete"
             groups="employee_dynamic_fields.employee_dynamic_fields_group_add_employee_custom_fields"
             sequence="12"/>
</odoo>

Now, we can see the view:

how-to-create-and-manage-a-custom-field-from-a-function-in-odoo-17-1-cybrosys

Now, let's proceed with creating a field within the 'hr.employee' model using the following steps.

how-to-create-and-manage-a-custom-field-from-a-function-in-odoo-17-2-cybrosys

Now, we'll examine the functionality of the action_create_fields button, which is responsible for generating the custom field. Let's explore the specifics of this function and how it facilitates the creation of the desired custom field.

def action_create_fields(self):
   """
      This method is used to create custom fields for the 'hr.employee'
      model and extend the employee form view.It creates a new field in
      the 'ir.model.fields' table, extends the 'hr.view_employee_form'
      view.
   """
   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.selection_field,
        'copied': self.copied,
        'is_employee_dynamic': True
        })
   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_id.name, self.position, self.name)
   if self.widget_id:
       arch_base = _('<?xml version="1.0"?>'
                     '<data>'
                     '<field name="%s" position="%s">'
                     '<field name="%s" widget="%s"/>'
                     '</field>'
                     '</data>') % (
                       self.position_field_id.name, self.position,
                       self.name,
                       self.widget.name)
   self.form_view_id = self.env['ir.ui.view'].sudo().create(
       {'name': 'employee.dynamic.fields',
        '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',
   }

Within the function definition, the field creation process takes place within the 'ir.model.fields' model, utilizing the 'create' method. To ensure the proper placement of our custom field 'Test' in the 'hr.view_employee_form,' we retrieve the view record's ID using 'inherit_id.' Subsequently, a new view is created by inheriting from 'hr.view_employee_form' through the 'create' method in 'ir.ui.view.' This approach ensures that our custom field is positioned after the 'Coach' field in the specified view.

how-to-create-and-manage-a-custom-field-from-a-function-in-odoo-17-3-cybrosys

In certain scenarios, the requirement arises to dynamically create fields within a model through the user interface. In such cases, the process of crafting a custom field within a model can be executed as follows.

In essence, this blog has demonstrated a method that not only simplifies the customization of Odoo models but also empowers users to take control of their application's structure, fostering adaptability and efficiency in the ever-evolving business landscape.

To read more about creating & managing a custom field from a function in Odoo 16, refer to our blog How to Create & Manage a Custom Field From a Function in Odoo 16


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



0
Comments



Leave a comment

 


whatsapp_icon
location

Calicut

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

location

Kochi

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

location

Bangalore

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

Send Us A Message