Enable Dark Mode!
how-to-create-manage-a-custom-field-from-a-function-in-odoo-18.jpg
By: Mohammed Farhan

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

Technical Odoo 18

In this blog, we’ll look at how to create custom fields dynamically within an Odoo model using a Python function. This method allows for greater adaptability to meet specific business requirements. Rather than declaring fields statically in the model class, we’ll demonstrate how to generate and configure them at runtime, including how to control their placement within the view from the user interface.

A model must first exist as the structure where the new field will be created. This model acts as the base for defining and managing the dynamic field.

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.
      """
      model_id = self.env['ir.model'].sudo().search(
          [('model', '=', 'hr.employee')])
      self.env['ir.model.fields'].sudo().create(
          {'name': self.name,
           'field_description': self.field_description,
           'model_id': 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,
           })
      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)
      self.form_view_id = self.env['ir.ui.view'].sudo().create(
          {'name': 'contact.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")
  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")

This custom model is built by inheriting from 'ir.model.fields', which is the core model managing fields in Odoo 18. As a result, it allows us to leverage the features and capabilities of 'ir.model.fields' within our own model.

In our custom model, the following fields need to be defined:

* position_field_id: A Many2one field that references “ir.model.felds” It determines where the custom field will appear in the view. The placement of the new field is based on the selected “position_field_id”.

* position: A selection field with two options — 'Before' and 'After'. It defines whether the custom field should be displayed before or after the field chosen in “position_field_id”.

* model_id: Indicates the target model where the custom field is being added.

* field_type: A selection field representing the type of field to be created. The available options are fetched from the “get_possible_field_types” method, which filters out unsupported types like “one2many” and “reference”.

* ref_model_id: Used when the custom field is relational. This field specifies the related model that the custom field should link to.

Defining these fields sets the key parameters needed to configure and organize custom fields within the Odoo model.

Next, we’ll examine how the view for our custom model is set up, which defines the layout and visual structure of the model within the interface.

<?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="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">
          <list create="false">
              <field name="name"/>
              <field name="field_description"/>
              <field name="ttype"/>
          </list>
      </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="custom_fields_creation.action_employee_dynamic_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"
            sequence="12"/>
</odoo>

Let’s now take a look at how the view is defined:

How to Create & Manage a Custom Field From a Function in Odoo 18-cybrosys

Let’s move forward by adding a new field to the “hr.employee” model, following these steps.

How to Create & Manage a Custom Field From a Function in Odoo 18-cybrosys

Now, we’ll take a closer look at the “action_create_fields” button, which handles the creation of custom fields. Let’s delve into how this function works and the role it plays in generating the desired 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.
   """
   model_id = self.env['ir.model'].sudo().search(
       [('model', '=', 'hr.employee')])
   self.env['ir.model.fields'].sudo().create(
       {'name': self.name,
        'field_description': self.field_description,
        'model_id': 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,
        })
   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)
   self.form_view_id = self.env['ir.ui.view'].sudo().create(
       {'name': 'contact.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',
   }

Inside the function, the custom field is created using the “create” method on the “ir.model.fields” model. To correctly position the custom field “Teacher” in the “hr.view_employee_form”, the function first retrieves the target view’s ID via the “inherit_id”. Then, it creates a new inherited view from “hr.view_employee_form” using the “create” method of the “ir.ui.view” model. This setup ensures that the custom field appears immediately after the “Job Position“ field in the form view.

How to Create & Manage a Custom Field From a Function in Odoo 18-cybrosys

In some situations, there is a need to dynamically generate fields within a model directly from the user interface. When such requirements arise, custom fields can be created within a model using the following approach.

To summarize, this blog has outlined a practical method for customizing Odoo models more efficiently. It enables users to take greater control over their application's structure, promoting flexibility and responsiveness in a constantly changing business environment.

To read more about How to Create & Manage a Custom Field From a Function in Odoo 17, refer to our blog How to Create & Manage a Custom Field From a Function in Odoo 17.


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