10
votes

Is there a way to add custom field attribute in Odoo? For example every field has attribute help where you can enter message explaining the field for the user. So I want to add custom attribute, so that would change the way field acts for all types of fields.

I want to add into Field class, so all fields would get that attribute. But it seems no matter what I do, Odoo does not see that such attribute was added.

If I simply add new custom attribute like:

some_field = fields.Char(custom_att="hello")

Then it is simply ignored. And I need it to be picked up by method fields_get, which can return wanted attribute value (info what it does:

def fields_get(self, cr, user, allfields=None, context=None, write_access=True, attributes=None):
    """ fields_get([fields][, attributes])

    Return the definition of each field.

    The returned value is a dictionary (indiced by field name) of
    dictionaries. The _inherits'd fields are included. The string, help,
    and selection (if present) attributes are translated.

    :param allfields: list of fields to document, all if empty or not provided
    :param attributes: list of description attributes to return for each field, all if empty or not provided
    """

So calling it, does not return my custom attribute (it does return the ones originally defined by Odoo though).

I also tried updating _slots (with monkey patch or just testing by changing source code) attribute in Field class, but it seems it is not enough. Because my attribute is still ignored.

from openerp import fields

original_slots = fields.Field._slots

_slots = original_slots
_slots['custom_att'] = None

fields.Field._slots = _slots

Does anyone know how to properly add new custom attribute for field?

2

2 Answers

3
votes

Assuming v9

The result of fields_get is a summary of fields defined on a model, the code shows that it will only add the attribute if the description was filled. It will fetch the description of the current field by calling field.get_description

So in order to ensure that your attribute gets inserted into this self.description_attrs you will need to add an attribute or method that starts with _description_customatt (customatt part from your example) and will return the required data.

I've not run any tests for this but you can look at the code for the fields and their attributes what they actually return. For instance the help attribute description (src)

def _description_help(self, env):
  if self.help and env.lang:
    model_name = self.base_field.model_name
    field_help = env['ir.translation'].get_field_help(model_name)
    return field_help.get(self.name) or self.help
  return self.help
0
votes

This is only something you can do if you run OpenERP/ODOO on your own server (in other words, not the cloud version whose code you cannot access).

You will need to modify the <base>/osv/fields.py file and add your changes to the field_to_dict function towards the bottom of the file (the base _column class already saves extra keyword arguments for you -- at least in version 7.0):

def field_to_dict(model, cr, user, field, context=None): 
    res = {'type': field._type}
    ...
    ...
    for arg in ('string', 'readonly', ...) :

Somewhere in that long list of attributes you need to insert the name of the one you are interested in.

Alternatively, you could update _column.__init__ to save the names of the extra arguments, and field_to_dict to include them (untested):

diff -r a30d30db3cd9 osv/fields.py
--- a/osv/fields.py Thu Jun 09 17:18:29 2016 -0700
+++ b/osv/fields.py Mon Jun 13 18:11:26 2016 -0700
@@ -116,23 +116,24 @@ class _column(object):
         self._context = context
         self.write = False
         self.read = False
         self.view_load = 0
         self.select = select
         self.manual = manual
         self.selectable = True
         self.group_operator = args.get('group_operator', False)
         self.groups = False  # CSV list of ext IDs of groups that can access this field
         self.deprecated = False # Optional deprecation warning
-        for a in args:
-            if args[a]:
-                setattr(self, a, args[a])
+        self._user_args = ()
+        for name, value in args:
+            setattr(self, name, value or False)
+            self._user_args += name

     def restart(self):
         pass

     def set(self, cr, obj, id, name, value, user=None, context=None):
         cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))

     def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
         raise Exception(_('undefined get method !'))

@@ -1559,20 +1560,22 @@ def field_to_dict(model, cr, user, field
         res['o2m_order'] = field._order or False
     if isinstance(field, many2many):
         (table, col1, col2) = field._sql_names(model)
         res['m2m_join_columns'] = [col1, col2]
         res['m2m_join_table'] = table
     for arg in ('string', 'readonly', 'states', 'size', 'group_operator', 'required',
             'change_default', 'translate', 'help', 'select', 'selectable', 'groups',
             'deprecated', 'digits', 'invisible', 'filters'):
         if getattr(field, arg, None):
             res[arg] = getattr(field, arg)
+    for arg in field._user_args:
+        res[arg] = getattr(field, arg)

     if hasattr(field, 'selection'):
         if isinstance(field.selection, (tuple, list)):
             res['selection'] = field.selection
         else:
             # call the 'dynamic selection' function
             res['selection'] = field.selection(model, cr, user, context)
     if res['type'] in ('one2many', 'many2many', 'many2one'):
         res['relation'] = field._obj
         res['domain'] = field._domain(model) if callable(field._domain) else field._domain