@@ -163,6 +163,7 @@ def do_raise(error):
163163 "migrate" ,
164164 "logging" ,
165165 "load_data" ,
166+ "add_columns" ,
166167 "add_fields" ,
167168 "copy_columns" ,
168169 "copy_fields_multilang" ,
@@ -191,6 +192,7 @@ def do_raise(error):
191192 "add_ir_model_fields" ,
192193 "get_legacy_name" ,
193194 "get_model2table" ,
195+ "get_field2column_type" ,
194196 "m2o_to_x2m" ,
195197 "float_to_integer" ,
196198 "message" ,
@@ -1912,6 +1914,44 @@ def get_model2table(model):
19121914 return model2table .get (model , model .replace ("." , "_" ))
19131915
19141916
1917+ def get_field2column_type (field_type , translatable = False ):
1918+ """This method returns SQL type given a field type.
1919+
1920+ :param: field type: binary, boolean, char, date, datetime, float, html,
1921+ integer, many2many, many2one, many2one_reference, monetary, one2many,
1922+ reference, selection, text, serialized. The list can vary depending on
1923+ Odoo version or custom added field types.
1924+ :param: (optional) translatable: From >=v16, if field is translatable then
1925+ SQL field type is changed to 'jsonb'.
1926+ :return: SQL field type: If the field type is custom or if it's one of the special
1927+ cases (see below), you need to indicate here the SQL type to use
1928+ (from the valid PostgreSQL types):
1929+ https://www.postgresql.org/docs/9.6/static/datatype.html"""
1930+ sql_type_mapping = {
1931+ "binary" : "bytea" , # If there's attachment, no SQL. Force it manually
1932+ "boolean" : "bool" ,
1933+ "char" : "varchar" , # Force it manually if there's size limit
1934+ "date" : "date" ,
1935+ "datetime" : "timestamp" ,
1936+ "float" : "numeric" , # Force manually to double precision if no digits
1937+ "html" : "text" ,
1938+ "integer" : "int4" ,
1939+ "many2many" : False , # No need to create SQL column
1940+ "many2one" : "int4" ,
1941+ "many2one_reference" : "int4" ,
1942+ "monetary" : "numeric" ,
1943+ "one2many" : False , # No need to create SQL column
1944+ "reference" : "varchar" ,
1945+ "selection" : "varchar" , # Can be sometimes integer. Force it manually
1946+ "text" : "text" ,
1947+ "serialized" : "text" ,
1948+ "json" : "jsonb" ,
1949+ }
1950+ if version_info [0 ] > 15 and field_type in ["char" , "text" , "html" ] and translatable :
1951+ return "jsonb"
1952+ return sql_type_mapping .get (field_type , False )
1953+
1954+
19151955def m2o_to_x2m (cr , model , table , field , source_field ):
19161956 """
19171957 Transform many2one relations into one2many or many2many.
@@ -2995,6 +3035,71 @@ def format_message(f):
29953035 f .active = False
29963036
29973037
3038+ def add_columns (env , field_spec ):
3039+ """This method adds a new column.
3040+
3041+ It's intended for being run in pre-migration scripts for pre-populating
3042+ columns to avoid the execution of new stored computed fields.
3043+
3044+ :param: field_spec: List of tuples with the following expected elements
3045+ for each tuple:
3046+
3047+ * model name
3048+ * field name
3049+ * field type: binary, boolean, char, date, datetime, float, html,
3050+ integer, many2many, many2one, many2one_reference, monetary, one2many,
3051+ reference, selection, text, serialized. The list can vary depending on
3052+ Odoo version or custom added field types.
3053+ * (optional) initialization value: if included in the tuple, it is set
3054+ in the column for existing records.
3055+ * (optional) SQL table name
3056+ * (optional) SQL field type: If the field type is custom or if it's one
3057+ of the special cases (see get_field2column_type), you need to indicate
3058+ here the SQL type to use (from the valid PostgreSQL types):
3059+ https://www.postgresql.org/docs/9.6/static/datatype.html
3060+ * (optional) translatable: From >=v16, if field is translatable then
3061+ SQL field type is changed to 'jsonb'.
3062+ """
3063+ cr = env .cr
3064+ for vals in field_spec :
3065+ model_name = vals [0 ]
3066+ field_name = vals [1 ]
3067+ field_type = vals [2 ]
3068+ init_value = vals [3 ] if len (vals ) > 3 else False
3069+ table_name = vals [4 ] if len (vals ) > 4 else False
3070+ sql_type = vals [5 ] if len (vals ) > 5 else False
3071+ translatable = vals [6 ] if len (vals ) > 6 else False
3072+ sql_type = sql_type or get_field2column_type (field_type , translatable )
3073+ if version_info [0 ] > 15 and translatable :
3074+ from psycopg2 .extras import Json
3075+
3076+ init_value = init_value and Json ({"en_US" : init_value })
3077+ if not table_name :
3078+ try :
3079+ table_name = env [model_name ]._table
3080+ except KeyError :
3081+ table_name = get_model2table (model_name )
3082+ if sql_type and not column_exists (cr , table_name , field_name ):
3083+ query = sql .SQL ("ALTER TABLE {} ADD COLUMN {} {}" ).format (
3084+ sql .Identifier (table_name ),
3085+ sql .Identifier (field_name ),
3086+ sql .SQL (sql_type ),
3087+ )
3088+ args = []
3089+ if init_value :
3090+ query += sql .SQL (" DEFAULT %s" )
3091+ args .append (init_value )
3092+ logged_query (cr , query , args )
3093+ if init_value :
3094+ logged_query (
3095+ cr ,
3096+ sql .SQL ("ALTER TABLE {} ALTER COLUMN {} DROP DEFAULT" ).format (
3097+ sql .Identifier (table_name ),
3098+ sql .Identifier (field_name ),
3099+ ),
3100+ )
3101+
3102+
29983103def add_fields (env , field_spec ):
29993104 """This method adds all the needed stuff for having a new field populated
30003105 in the DB (SQL column, ir.model.fields entry, ir.model.data entry...).
@@ -3006,7 +3111,7 @@ def add_fields(env, field_spec):
30063111 always add the XML-ID entry:
30073112 https://github.com/odoo/odoo/blob/9201f92a4f29a53a014b462469f27b32dca8fc5a/
30083113 odoo/addons/base/models/ir_model.py#L794-L802, but you can still call
3009- this method for consistency and for avoiding to know the internal PG
3114+ this method for consistency and for avoiding knowing the internal PG
30103115 column type.
30113116
30123117 :param: field_spec: List of tuples with the following expected elements
@@ -3020,33 +3125,18 @@ def add_fields(env, field_spec):
30203125 integer, many2many, many2one, many2one_reference, monetary, one2many,
30213126 reference, selection, text, serialized. The list can vary depending on
30223127 Odoo version or custom added field types.
3023- * SQL field type: If the field type is custom or it's one of the special
3024- cases (see below ), you need to indicate here the SQL type to use
3025- (from the valid PostgreSQL types):
3128+ * SQL field type: If the field type is custom or if it's one of the special
3129+ cases (see get_field2column_type ), you need to indicate here the SQL type
3130+ to use (from the valid PostgreSQL types):
30263131 https://www.postgresql.org/docs/9.6/static/datatype.html
3132+
3133+ Note: From >=v16, if field is translatable, SQL field type has to be
3134+ explicitly stated as 'jsonb'.
3135+
30273136 * module name: for adding the XML-ID entry.
30283137 * (optional) initialization value: if included in the tuple, it is set
30293138 in the column for existing records.
30303139 """
3031- sql_type_mapping = {
3032- "binary" : "bytea" , # If there's attachment, no SQL. Force it manually
3033- "boolean" : "bool" ,
3034- "char" : "varchar" , # Force it manually if there's size limit
3035- "date" : "date" ,
3036- "datetime" : "timestamp" ,
3037- "float" : "numeric" , # Force manually to double precision if no digits
3038- "html" : "text" ,
3039- "integer" : "int4" ,
3040- "many2many" : False , # No need to create SQL column
3041- "many2one" : "int4" ,
3042- "many2one_reference" : "int4" ,
3043- "monetary" : "numeric" ,
3044- "one2many" : False , # No need to create SQL column
3045- "reference" : "varchar" ,
3046- "selection" : "varchar" , # Can be sometimes integer. Force it manually
3047- "text" : "text" ,
3048- "serialized" : "text" ,
3049- }
30503140 for vals in field_spec :
30513141 field_name = vals [0 ]
30523142 model_name = vals [1 ]
@@ -3055,33 +3145,16 @@ def add_fields(env, field_spec):
30553145 sql_type = vals [4 ]
30563146 module = vals [5 ]
30573147 init_value = vals [6 ] if len (vals ) > 6 else False
3058- # Add SQL column
30593148 if not table_name :
30603149 try :
30613150 table_name = env [model_name ]._table
30623151 except KeyError :
30633152 table_name = get_model2table (model_name )
3064- if not column_exists (env .cr , table_name , field_name ):
3065- sql_type = sql_type or sql_type_mapping .get (field_type )
3066- if sql_type :
3067- query = sql .SQL ("ALTER TABLE {} ADD COLUMN {} {}" ).format (
3068- sql .Identifier (table_name ),
3069- sql .Identifier (field_name ),
3070- sql .SQL (sql_type ),
3071- )
3072- args = []
3073- if init_value :
3074- query += sql .SQL (" DEFAULT %s" )
3075- args .append (init_value )
3076- logged_query (env .cr , query , args )
3077- if init_value :
3078- logged_query (
3079- env .cr ,
3080- sql .SQL ("ALTER TABLE {} ALTER COLUMN {} DROP DEFAULT" ).format (
3081- sql .Identifier (table_name ),
3082- sql .Identifier (field_name ),
3083- ),
3084- )
3153+ # Add SQL column
3154+ add_columns (
3155+ env ,
3156+ [(model_name , field_name , field_type , init_value , table_name , sql_type )],
3157+ )
30853158 # Add ir.model.fields entry
30863159 env .cr .execute (
30873160 "SELECT id FROM ir_model WHERE model = %s" ,
0 commit comments