Upgrade utils¶
Upgrade utils is a library that contains helper functions to facilitate the writing of upgrade scripts. This library, used by Odoo for the upgrade scripts of standard modules, provides reliability and helps speed up the upgrade process:
The helper functions help make sure the data is consistent in the database.
It takes care of indirect references of the updated records.
Allows calling functions and avoid writing code, saving time and reducing development risks.
Helpers allow to focus on what is important for the upgrade and not think of details.
Installation¶
Clone the Upgrade utils repository locally and start
odoo with the src directory prepended to the --upgrade-path option.
$ ./odoo-bin --upgrade-path=/path/to/upgrade-util/src,/path/to/other/upgrade/script/directory [...]
On platforms where you do not manage Odoo yourself, you can install this library via pip:
$ python3 -m pip install git+https://github.com/odoo/upgrade-util@master
On Odoo.sh it is recommended to add it to the requirements.txt of
the custom repository. For this, add the following line inside the file:
odoo_upgrade @ git+https://github.com/odoo/upgrade-util@master
Using upgrade utils¶
Once installed, the following packages are available for the upgrade scripts:
odoo.upgrade.util: the helper itself.odoo.upgrade.testing: base TestCase classes.
To use it in upgrade scripts, simply import it:
from odoo.upgrade import util
def migrate(cr, version):
# Rest of the script
Now, the helper functions are available to be called through util.
Util functions¶
Upgrade utils provides many useful functions to ease the upgrade process. Here, we describe some of the most useful ones. Refer to the util folder for the comprehensive declaration of helper functions.
Note
The cr parameter in util functions always refers to the database cursor. Pass the one
received as a parameter in migrate(). Not all functions need this parameter.
Modules¶
Utility functions for module-level operations.
In most cases module operations (rename, merge, remove, …) should be performed in a
base script. The reason is that once the base module is upgraded, all the information
regarding modules should be already set in the DB for the upgrade process to function
correctly. The command line option --pre-upgrade-scripts (available from Odoo 16) allows
to run upgrade scripts before loading base. This is the recommended way to perform
module operations after a major upgrade.
- modules_installed(cr, *modules)[source]¶
Return whether all given modules are installed.
Note
In the context of upgrades a module is considered as installed if it is marked for upgrade, or for installation; even if they are not yet fully installed.
- module_installed(cr, module)[source]¶
Return whether a module is installed.
See
modules_installed().
- uninstall_module(cr, module)[source]¶
Uninstall and remove all records owned by a module.
- Paramètres
module (str) – name of the module to uninstall
- uninstall_theme(cr, theme, base_theme=None)[source]¶
Uninstall a theme module and remove it from websites.
- Paramètres
Avertissement
This function can only be used in
post-scripts ofwebsitemodule as it relies on the ORM.See
remove_theme()anduninstall_module().
- remove_module(cr, module)[source]¶
Completely remove a module.
This operation is equivalent to uninstall and removal of all references to the module - no trace of it is left in the database.
- Paramètres
module (str) – name of the module to remove
Avertissement
Since this function removes all data associated to the module. Ensure to reassign records before calling this function.
- remove_theme(cr, theme, base_theme=None)[source]¶
Uninstall a theme module.
Avertissement
This function can only be used in
post-scripts.See
remove_module()anduninstall_theme().
- merge_module(cr, old, into, update_dependers=True)[source]¶
Merge a module into another.
This function moves all references and records from the source module to the destination module.
Avertissement
This functions does not remove any record, but it removes xml_ids from the source module with a conflicting name in the destination module.
- force_install_module(cr, module, if_installed=None, reason='it has been explicitly asked for')[source]¶
Force the ORM to install a module.
- force_upgrade_of_fresh_module(cr, module, init=True)[source]¶
Force the execution of upgrade scripts for a module that is being installed.
Standard Odoo doesn’t run upgrade scripts when a module is being installed. This makes sense because, technically speaking, the module is not being upgraded. Still, it happens that a (new) module needs to perform some operations for it to be correctly installed, like grabbing data from another module. This is common when a module is functionally split into several ones.
- Paramètres
Being in init mode has the side effect of not respecting noupdate flags, in XML file nor in
ir_model_data.
- move_model(cr, model, from_module, to_module, move_data=False, keep=())[source]¶
Move a model from one module to another.
- Paramètres
This function can be called for moving overrides of a model to another module. As it cannot distinguish between source model or inherited model, it raises an exception if the destination module isn’t installed.
Models¶
Utility functions for modifying models.
Model operations are best done in pre- script of the involved modules.
- remove_model(cr, model, drop_table=True, ignore_m2m=())[source]¶
Remove a model and its references from the database.
Some required indirect references to the model are replaced by the unknown model - an empty model that serves as placeholder for gone models.
- delete_model(cr, model, drop_table=True, ignore_m2m=())[source]¶
Remove a model and its references from the database.
Some required indirect references to the model are replaced by the unknown model - an empty model that serves as placeholder for gone models.
- rename_model(cr, old, new, rename_table=True, ignored_m2ms='ALL_BEFORE_18_1')[source]¶
Rename a model.
Updates all references to the model name in the DB.
If table rename is requested, from saas~18.1+, m2m table are updated too, unless ignored. In older versions, m2m tables are skipped unless an empty list is passed.
- Paramètres
old (str) – current model name
new (str) – new model name
rename_table (bool) – whether to also rename the table of the model
ignored_m2ms – m2m tables to skip. Defaults to
"ALL_BEFORE_18_1", which skips all in Odoo 18 or below, none in saa~18.1+. For all versions, if the value is not the default, skip only the specified m2m tables.
- merge_model(cr, source, target, drop_table=True, fields_mapping=None, ignore_m2m=())[source]¶
Merge a model into another one.
This function moves all the references from
sourcemodel intotargetmodel and removessourcemodel and its references. By default, only the fields with the same name in both models are moved from source to target, optionally a mapping with differently named fields can be provided.Avertissement
This function does not move the records from
sourcemodel totargetmodel.- Paramètres
source (str) – name of the model to be merged
target (str) – name of the destination model to merge into
drop_table (bool) – whether to drop the table of the source model
fields_mapping (dict or None) – mapping of field names from source to target model, when
Noneonly fields with same name are movedignore_m2m (list(str) or str) – list of m2m tables ignored to remove from source model.
- remove_inherit_from_model(cr, model, inherit, keep=(), skip_inherit=(), with_inherit_parents=True)[source]¶
Remove
inheritfrommodel.This function removes all fields inherited via
inheritfrom amodeland all its descendant models. All fields from the inherit, including those from its parent models are also removed, unlesswith_inherit_parentsmode is unset. In such case only fields frominheritare removed, excluding fields in its parents. Fields listed inkeepare never removed. If some descendants frommodelare listed inskip_inheritthey will keep the fields frominherit.- Paramètres
model (str) – name of the model from which to remove the inherit
inherit (str) – name of the inherited model (or mixin) to remove
skip_inherit (tuple(str)) – list of descendant models of
modelto not processwith_inherit_parents (boolean) – if unset, remove fields coming from
inheritonly, keeping all fields from its parents
Fields¶
Utility functions for modifying model fields.
Field operations are best done in pre- script of the involved modules. In some cases a
preliminary operation could be done in pre and finished in post. A common example is to
remove a field in pre- keeping its column, later used in post when the column is finally
dropped.
- remove_field(cr, model, fieldname, cascade=False, drop_column=True, skip_inherit=(), keep_as_attachments=False)[source]¶
Remove a field and its references from the database.
This function also removes the field from inheriting models, unless exceptions are specified in
skip_inherit. When the field is stored we can choose to not drop the column.- Paramètres
model (str) – model name of the field to remove
fieldname (str) – name of the field to remove
cascade (bool) – whether the field column(s) are removed in
CASCADEmodedrop_column (bool) – whether the field’s column is dropped
skip_inherit (list(str) or str) – list of inheriting models to skip the removal of the field, use
"*"to skip allkeep_as_attachments (bool) – for binary fields, whether the data should be kept as attachments
- move_field_to_module(cr, model, fieldname, old_module, new_module, skip_inherit=())[source]¶
Move a field from one module to another.
This functions updates all references to a specific field, switching from the source module to the destination module. It avoid data removal after the registry is fully loaded. The field in inheriting models are also moved unless skipped.
- Paramètres
model (str) – name of the owner model of the field to move
fieldname (str) – name of the field to move
old_module (str) – source module name from which the field is moved
new_module (str) – target module name into which the field is moved
skip_inherit (list(str) or str) – list of inheriting models for which the field is not to be moved, use
"*"to skip all
- rename_field(cr, model, old, new, update_references=True, domain_adapter=None, skip_inherit=())[source]¶
Rename a field and its references from
oldtonewon the givenmodel.The field is updated in all inheriting models, except for models specified in
skip_inherit.This functions also updates references, indirect or direct ones, including filters, server actions, related fields, emails, dashboards, domains, and many more. See
update_field_usage()For the update of domains a special adapter function can be used. The default adapter just replaces
oldbynewin each domain leaf. Refer toadapt_domains()for information about domain adapters.- Paramètres
model (str) – model name of the field to rename
old (str) – current name of the field to rename
new (str) – new name of the field to rename
update_references (bool) – whether to update all references
domain_adapter (function) – adapter to use for domains, see
adapt_domains()skip_inherit (list(str) or str) – models to skip when renaming the field in inheriting models, use
"*"to skip all
- invert_boolean_field(cr, model, old, new, skip_inherit=())[source]¶
Rename a boolean field and invert its value.
- convert_m2o_field_to_m2m(cr, model, field, new_name=None, m2m_table=None, col1=None, col2=None)[source]¶
Convert a Many2one field to a Many2many.
It creates the relation table and fill it.
- Paramètres
model (str) – model name of the field to convert
field (str) – current name of the field to convert
new_name (str) – new name of the field to convert. If not provide, it’s
fieldwith a trailing « s ».m2m_table (str) – name of the relation table. Automatically generated if not provided.
col1 (str) – name of the column referencing
model.col2 (str) – name of the column referencing the target of
field.
- m2o2m2m(cr, model, field, new_name=None, m2m_table=None, col1=None, col2=None)[source]¶
Convert a Many2one field to a Many2many.
It creates the relation table and fill it.
- Paramètres
model (str) – model name of the field to convert
field (str) – current name of the field to convert
new_name (str) – new name of the field to convert. If not provide, it’s
fieldwith a trailing « s ».m2m_table (str) – name of the relation table. Automatically generated if not provided.
col1 (str) – name of the column referencing
model.col2 (str) – name of the column referencing the target of
field.
- change_field_selection_values(cr, model, field, mapping, skip_inherit=())[source]¶
Replace references of values of a selection field.
This function replaces all references to selection values according to a mapping. Domains are also updated.
- Paramètres
model (str) – model name of the selection field to update
field (str) – name of the selection field to update
mapping (dict) – selection values to update, key values are replaced by their corresponding values in the mapping
skip_inherit (list(str) or str) – list of inheriting models to skip in the update of the selection values, use
"*"to skip all
- update_field_usage(cr, model, old, new, domain_adapter=None, skip_inherit=())[source]¶
Replace all references to the field
oldbynewin different places.- Search in:
ir_filters
ir_exports_line
ir_act_server
mail_alias
ir_ui_view_custom (dashboard)
domains (using
domain_adapter)related fields
This function can be used to replace the usage of a field by another. Domains are updated using the
domain_adapter. By default the domain adapter just replacesoldbynewin domain leaves. Seeadapt_domains()for more information about domain adapters.- Paramètres
model (str) – model name of the field
old (str) – source name of the field to replace
new (str) – target name of the field to set
domain_adapter (function) – adapter to use for domains, see
adapt_domains()skip_inherit (list(str) or str) – models to skip when renaming the field in inheriting models, use
"*"to skip all
Records¶
Utility functions for record-level operations.
- remove_view(cr, xml_id=None, view_id=None, silent=False, key=None)[source]¶
Remove a view and all its descendants.
This function recursively deletes the given view and its inherited views, as long as they are part of a module. It will fail as soon as a custom view exists anywhere in the hierarchy. It also removes multi-website COWed views.
- Paramètres
xml_id (str) – optional, the xml_id of the view to remove
view_id (int) – optional, the ID of the view to remove
silent (bool) – whether to show in the logs disabled custom views
key (str or None) – key used to detect multi-website COWed views, if
Nonethen set toxml_idif provided, otherwise set to the xml_id referencing the view with IDview_idif any
Avertissement
Either
xml_idorview_idmust be set. Specifying both will raise an error.
- edit_view(cr, xmlid=None, view_id=None, skip_if_not_noupdate=True, active='auto')[source]¶
Context manager to edit a view’s arch.
This function returns a context manager that may yield a parsed arch of a view as an etree Element. Any changes done in the returned object will be written back to the database upon exit of the context manager, updating also the translated versions of the arch. Since the function may not yield, use
skippable_cm()to avoid errors.with util.skippable_cm(), util.edit_view(cr, "xml.id") as arch: arch.attrib["string"] = "My Form"
To select the target view to edit use either
xmlidorview_id, not both.When the view is identified by
view_id, the arch is always yielded if the view exists, with disregard to anynoupdateflag it may have associated. Whenxmlidis set, if the viewnoupdateflag isFalsethen the arch will not be yielded unlessskip_if_not_noupdateis set toFalse. IfnoupdateisTrue, the view will be yielded for edit.If the
activeargument isTrueorFalse, theactiveflag of the view will be set accordingly.Note
If
activeis « auto » (default value), the view will be activated if selected viaxmlidand left untouched if selected viaview_id.- Paramètres
xmlid (str) – optional, xml_id of the view edit
view_id (int) – optional, ID of the view to edit
skip_if_not_noupdate (bool) – whether to force the edit of views requested via
xmlidparameter even if they are flagged asnoupdate=True, ignored ifview_idis setactive (bool or None or "auto") – active flag value to set. Unchanged when
None.
- Renvoie
a context manager that yields the parsed arch, upon exit the context manager writes back the changes.
- remove_record(cr, name)[source]¶
Remove a record and its references corresponding to the given xml_id.
- Paramètres
name (str) – record xml_id, under the format
module.name
- is_changed(cr, xmlid, interval='1 minute')[source]¶
Return whether a record was changed.
This function checks if a record was changed before the current upgrade start time. See
upgrade-util/src/base/0.0.0/pre-00-upgrade-start.pyThis utility will return a false positive on xmlids of records that match the following conditions:
Have been updated in an upgrade preceding the current one
Have not been updated in the current upgrade
If the
xmliddoesn’t exist in the DB this function returnsNone.
- if_unchanged(cr, xmlid, callback, interval='1 minute', **kwargs)[source]¶
Run
callbackif a record is unchanged.This function will run a
callbackwhen the referred record is unchanged. Thexmlidand any extra parameter, but notinterval, will be passed to thecallback. In case the record was changed it will be marked asnoupdate=True. See alsois_changed()andforce_noupdate().This function is useful to take an action only when a record hasn’t been updated, a common example is to force an update from XML even if the record was
noupdate=Trueutil.if_unchanged(cr, "mymodule.myrecord", util.update_record_from_xml)
- Paramètres
- rename_xmlid(cr, old, new, noupdate=None, on_collision='fail')[source]¶
Rename an external identifier (
xml_id) of a record.A rename cannot happen when the target name already exists on the database. In such cases there are two options to control how this function behaves:
fail: raise an exception and prevent renamingmerge: rename the external identifier, remove the old one, and replace references. Seereplace_record_references_batch()
Note
This function does not remove records, it only updates xml_ids.
- Paramètres
old (str) – current xml_id of the record, under the format
module.namenew (str) – new xml_id of the record, under the format
module.namenoupdate (bool or None) – value to set on the
noupdateflag of the xml_id, ignored ifNoneon_collision (str) – how to proceed if the xml_id already exists, the options are
mergeorfail(default)
- Renvoie
the ID of the record referenced by the new xml_id,
Nonewhen the record doesn’t exist- Type renvoyé
- ensure_xmlid_match_record(cr, xmlid, model, values)[source]¶
Ensure an xml_id references a record with specific values.
This function ensures the record pointed by an xml_id matches the values for the fields specified in the
valuesparameter. When the xmlid exist but it points to a record that doesn’t match the values, the xmlid is updated to point to a record that matches the values if one is found. If the xmlid doesn’t exist, it is created with the found record. When no matching record is found, nothing is done. In all cases the referenced record, after a possible update, of the xml_id is returned.- Paramètres
- Renvoie
the ID of the matched record,
Noneif no record found- Type renvoyé
Astuce
This function is useful when migrating in-database records into a custom module, to create the xml_ids before the module is updated and avoid duplication.
- update_record_from_xml(cr, xmlid, reset_write_metadata=True, force_create=AUTO, from_module=None, reset_translations=(), ensure_references=False, fields=None)[source]¶
Update a record based on its definition in the Data Files.
This function ignores the
noupdateflag on the record. It searches in all XML files from the manifest of the module in the xmlid, or thefrom_moduleparameter if set, for a matching definition. When found, it forces the ORM to update the record as in the specs in the data file.Optionally this function can reset the translations of some fields.
- Paramètres
xmlid (str) – record xml_id, under the format
module.namereset_write_metadata (bool) – whether to update the
write_dateof the recordforce_create (bool) – whether the record is created if it does not exist.
Trueby default, unlessfieldsis not None.from_module (str) – name of the module from which to update the record, necessary only when the specs are in a different module than the one in the xml_id
reset_translations (set(str)) – field names whose translations get reset
ensure_references (bool) – whether referred records via
refXML attributes should also be updated.fields (set(str) or None) – optional list of fields to include in the XML declaration. If set, all other fields will be ignored. When set, record won’t be created if missing.
Avertissement
This functions uses the ORM, therefore it can only be used after all models referenced in the data specs of the record are already loaded. In practice this means that this function should be used in
post-orend-scripts.Note
The standard behavior of ORM is to create the record if it doesn’t exist, including its xml_id. That will happen on this function as well, unless
force_createis set toFalse.
- delete_unused(cr, *xmlids, **kwargs)[source]¶
Remove unused records.
This function will remove records pointed by
xmlidsonly if they are not referenced from any table. For hierarchical records (like product categories), it verifies if the children marked as cascade removal are also not referenced. In which case the record and its children are all removed.Note
The records that cannot be removed are set as
noupdate=True.- Paramètres
- Renvoie
list of ids of removed records, if any
- Type renvoyé
- replace_record_references_batch(cr, id_mapping, model_src, model_dst=None, replace_xmlid=True, ignores=(), parent_field='parent_id')[source]¶
Replace all references to records.
This functions replaces all references, direct or indirect to records of
model_srcby the corresponding records in the mapping. If the target model of the mapping is not the same as the source one, thenmodel_dstparameter must be set.- Paramètres
id_mapping (dict(int, int)) – mapping of IDs to replace, key value is replaced by the mapped value
model_src (str) – name of the source model of the records to replace
model_dst (str) – name of the target model of the records to replace, if
Nonethe target is assumed the same as the sourcereplace_xmlid (bool) – whether to replace the references in xml_ids pointing to the source records
ignores (list(str)) – list of table names to skip when updating the referenced values
- Paream str parent_field
when the target and source model are the same, and the model table has
parent_pathcolumn, this field will be used to update it.
- replace_in_all_jsonb_values(cr, table, column, old, new, extra_filter=None)[source]¶
Replace values in JSONB columns.
This function replaces
oldbynewin JSONB values. It is useful for replacing values in all translations of translated fields.- Paramètres
table (str) – table name where the values are to be replaced
column (str) – column name where the values are to be replaced
old (str) – original value to replace, can be a simple term (str) or a Postgres regular expression wrapped by
PGRegexpnew (str) – new value to set, can be a simple term or a expression using
<number>notation to refer to captured groups ifoldis a regexp expressionextra_filter (str) – extra
WHEREcompatible clause to filter the values to update, must use thetalias for the table, it can also include{parallel_filter}to execute the query in parallel, seeexplode_execute()
ORM¶
Utility functions to perform operations via the ORM.
The functions on this module allow to use the ORM safely during upgrades. They enhance or patch the ORM such that it can handle high volumes of data in a performant way. In some cases totally different alternatives to the ORM’s own functions are provided. The functions on this module work along the ORM of all supported versions.
- env(cr)[source]¶
Create a new environment.
Avertissement
This function does not empty the cache maintained on the cursor for superuser with an empty environment. A call to
invalidate_cachemay be necessary every time data is modified directly in the database.- Renvoie
the new environment
- Type renvoyé
- recompute_fields(cr, model, fields, ids=None, logger=<Logger odoo.upgrade.util.orm (WARNING)>, chunk_size=256, strategy='auto', query=None)[source]¶
Recompute field values.
This function will recompute fields of a model restricted to a set of records - or all. The re-computation is not done on all records at the same time. It is split in batches (chunks). This avoids performance issues, and, in the worse cases, failures due to
MemoryError. After each chunk is processed the data is sent back to the database according to one of the following strategies:flush: use the
flushmethod of the ORMcommit:
committhe cursor - also flushauto: pick the best alternative between the two above given the number of records to compute and the presence of tracked fields.
The commit strategy is less prone to cause a
MemoryErrorfor a huge volume of data.- Paramètres
model (str) – name of the model to recompute
fields (list(str)) – list of the name of the fields to recompute
ids (list(int) or None) – list of the IDs of the records to recompute, when
Nonerecompute all records, unlessqueryis also set (see below)logger (
logging.Logger) – logger used to report the progresschunk_size (int) – number of records per chunk - used to split the processing
strategy (str) – strategy used to process the re-computation
query (str) – query to get the IDs of records to recompute, it is an error to set both
idsandquery. Note that the processing will always happen in ascending order. If that is unwanted, you must useidsinstead.
- class iter_browse(model, *args, **kw)[source]¶
Iterate over recordsets.
The
callableobject returned by this class can be used as an iterator that loads records by chunks (into arecordset). After each chunk is exhausted their data is sent back to the database - flushed or committed - and a new chunk is loaded.This class allows to run code like:
for record in env['my.model'].browse(ids): record.field1 = val env['my.model'].browse(ids)._compute_field2() env['my.model'].create(values)
in a performant way while also avoiding
MemoryError, even whenidsorvalueshave millions of entries. The alternative using this class would be:Example
MyModel = util.env(cr)['my.model'] for record in util.iter_browse(MyModel, ids): record.field1 = val util.iter_browse(MyModel, ids)._compute_field2() util.iter_browse(MyModel, ids).create(values)
- Paramètres
model (
odoo.model.Model) – the model to iteratechunk_size (int) – number of records to load in each iteration chunk,
200by defaultlogger (
logging.Logger) – logger used to report the progress, by default_loggerstrategy (str) – whether to
flushorcommiton each chunk, default isflush
- Renvoie
the object returned by this class can be used to iterate, or call any model method, safely on millions of records.
See also
env()
- adapt_domains(cr, model, old, new, adapter=None, skip_inherit=(), force_adapt=False)[source]¶
Replace
oldbynewin domains usingmodeland inheriting models.adapteris a callback function to adapt leaves. Adapter functions must take three arguments and return a domain that substitutes the original leaf. The arguments are:leaf: a domain leaf which is atupleof the form(left, op, right)in_or: a boolean, whenTrueit means the leaf is part of an OR ("|") domain, otherwise it is part of an AND ("&") domainnegated: a boolean, whenTrueit means that the leaf is negated ("!")
Example
def adapter(leaf, in_or, negated): left, op, right = leaf ok, ko = (1, 2) if not negated else (2, 1) if op == "=" return [(left, "=", ok)] elif op == "!=": return [(left, "=", ko)] return [leaf]
adapteris called only on leaves that use theoldfield ofmodelas last part of theleftpart of leaves, unlessforce_adaptisTrue. In the latter case the adapter is called if the field appears anywhere in the path, useful for relational fields only.The domains returned by an adapter do not need to have the
oldfield replaced bynewin theleftpart of the input leaf. The replace will be done anyway to the whole domain returned by the adapter. The usual purpose of theadapteris to modify the operator and therightpart of the input leaf. Whenadapteris not set only the replacement takes place.Example
When replacing
"field1"by"field2", the following happens:("foo.bar.baz.field1", "=", 1)gets adapted only if the record pointed to byfoo.bar.bazis of the requestedmodel.("foo.field1.baz", "=", 1)is not adapted even iffoopoints tomodel, unlessforce_adaptisTrue, becausefield1is not the last part ofleftin this leaf.
Note
This function will replace domains in all standard domain fields. Including filters, dashboards, and standard fields known to represent a domain.
- Paramètres
model (str) – name of the model for which to adapt the domains
old (str) – name of the field to be adapted
new (str) – name of the field that should replace
oldadapter (function) – adapter for leaves
skip_inherit (list(str)) – list of inheriting model names to don’t adapt (skip)
force_adapt (bool) – when
True, run theadapteron all leaves havingnewinleftpart of the leaf (path), useful when deleting a field (in which casenewis ignored).
SQL¶
Utility functions for interacting with PostgreSQL.
- class PGRegexp[source]¶
Wrapper for semantic meaning of parameters: this string is a Postgres regular expression.
- class SQLStr[source]¶
Wrapper for semantic meaning of parameters: this string is a valid SQL snippet.
See
format_query()
- parallel_execute(cr, queries, logger=<Logger odoo.upgrade.util.pg (WARNING)>)[source]¶
Execute queries in parallel.
Example
util.parallel_execute(cr, [util.format_query(cr, "REINDEX TABLE {}", t) for t in tables])
Astuce
If looking to speedup a single query, see
explode_execute().- Paramètres
- Renvoie
the sum of
cr.rowcountfor each query run- Type renvoyé
Avertissement
Due to the nature of
cr.rowcount, the return value of this function may represent an underestimate of the real number of affected records. For instance, when some records are deleted/updated as a result of anondeleteclause, they won’t be taken into account.As a side effect, the cursor will be committed.
Note
If a concurrency issue occurs, the failing queries will be retried sequentially.
- format_query(cr, query, *args, **kwargs)[source]¶
Safely format a query.
The
strarguments to this function are assumed to be SQL identifiers. They are wrapped in double quotes before being expanded usingstr.format(). Any other psycopg2.sql.Composable are also allowed. This includesColumnList, see alsoget_columns()Example
>>> util.format_query(cr, "SELECT {0} FROM {table}", "id", table="res_users") SELECT "id" FROM "res_users"
- Paramètres
query (str) – query to format, can use brackets
{}as instr.format()
- explode_execute(cr, query, table, alias=None, bucket_size=10000, logger=<Logger odoo.upgrade.util.pg (WARNING)>)[source]¶
Execute a query in parallel.
The query is split by buckets of ids, then processed in parallel by workers. If the query does not include the special
{parallel_filter}value, it is added to the lastWHEREclause, possibly also adding it if none found. When the query already has the filter nothing is done. The filter always expands to the splitting strategy. The split is done into buckets where no more thanbucket_sizeIDs are updated on each individual query.Example
util.explode_execute( cr, ''' UPDATE res_users u SET active = False WHERE (u.login LIKE 'dummy' OR u.login = 'bob') AND {parallel_filter} ''', table="res_users" alias="u", )
- Paramètres
query (str) – the query to execute.
table (str) – name of the main table of the query, used to split the processing
alias (str) – alias used for the main table in the query
bucket_size (int) – size of the buckets of ids to split the processing
logger (
logging.Logger) – logger used to report the progress
- Renvoie
the sum of
cr.rowcountfor each query run- Type renvoyé
Avertissement
It’s up to the caller to ensure the queries do not update the same records in different buckets. It is advised to never use this function for
DELETEqueries on tables with self references due to the potentialON DELETEeffects. For more details seeparallel_execute().
- create_column(cr, table, column, definition, **kwargs)[source]¶
Create a column.
This function will create the column only if it doesn’t exist. It will log an error if the existing column has different type. If
fk_tableis set, it will ensure the foreign key is setup, updating if necessary, with the righton_delete_actionif any is set.- Paramètres
table (str) – table of the new column
column (str) – name of the new column
definition (str) – column type of the new column
default (bool) – default value to set on the new column
fk_table (bool) – if the new column if a foreign key, name of the foreign table
on_delete_action (str) –
ON DELETEclause, defaultNO ACTION, only valid if the column is a foreign key.
- Renvoie
whether the column was created
- Type renvoyé
- alter_column_type(cr, table, column, type, using=None, where=None, logger=<Logger odoo.upgrade.util.pg (WARNING)>)[source]¶
Alter the type of a column.
Do it efficiently using a temporary column and parallel UPDATE queries.
- Paramètres
table (str) – name of the affected table.
column (str) – name of the column to alter type.
type (str) – new type of the column.
using (str) – SQL expression that define how to convert the value to the new type. The
{0}placeholder will be replaced by the column name.where (str) – used to restrict the values converted via
using.logger (
logging.Logger) – logger used to report the progress.
- remove_constraint(cr, table, name, cascade=False, warn=True)[source]¶
Remove a table constraint.
This function removes the constraint
namefromtable. It also removes records fromir_model_constraintand its xml_ids. It logs not found constraints.Note
If there is no constraint
name, this function will attempt to remove{table}_{name}, the latter is the default name the ORM uses for constraints created from_sql_constraints.- Paramètres
- Renvoie
whether the constraint was removed
- Type renvoyé
- class ColumnList(list_=(), quoted=())[source]¶
Encapsulate a list of elements that represent column names.
The resulting object can be rendered as string with leading/trailing comma or an alias.
- Paramètres
Example
>>> columns = ColumnList(["id", "field_Yx"], ["id", '"field_Yx"'])
>>> list(columns) ['id', '"field_Yx"']
>>> columns.using(alias="t").as_string(cr._cnx) '"t"."id", "t"."field_Yx"'
>>> columns.using(leading_comma=True).as_string(cr._cnx) ', "id", "field_Yx"'
>>> util.format_query(cr, "SELECT {} t.name FROM table t", columns.using(alias="t", trailing_comma=True)) 'SELECT "t"."id", "t"."field_Yx", t.name FROM table t'
>>> columns = ColumnList.from_unquoted(cr, ["foo", "BAR"])
>>> list(columns) ['"foo"', '"BAR"']
>>> list(columns.iter_unquoted()) ['foo', 'BAR']
Note
This class is better used via
get_columns()- classmethod from_unquoted(cr, list_)[source]¶
Build a ColumnList from a list of column names that may need quoting.
- get_columns(cr, table, ignore=('id',))[source]¶
Return a list of columns in a table.
- Paramètres
- Renvoie
a list of column names present in the table
- Type renvoyé
- get_common_columns(cr, table1, table2, ignore=('id',))[source]¶
Return a list of columns present in both tables.
- Paramètres
- Renvoie
a list of column names present in both tables
- Type renvoyé
- rename_table(cr, old_table, new_table, remove_constraints=True)[source]¶
Rename a table.
This function renames the table
old_tableintonew_table, as well as its primary key (and sequence), indexes, and foreign keys.
- create_m2m(cr, m2m, fk1, fk2, col1=None, col2=None)[source]¶
Ensure a m2m table exists or is created.
This function creates the table associated to a m2m field. If the table already exists,
fixup_m2m()is run on it. The table name can be generated automatically, applying the same logic of the ORM. In order to do so, use the value « auto » for them2mparameter.- Paramètres
- Renvoie
the name of the table just created/fixed-up
- Type renvoyé
- bulk_update_table(cr, table, columns, mapping, key_col='id')[source]¶
Update table based on mapping.
Each
mappingentry defines the new values for the specifiedcolumnsfor the row(s) whosekey_colvalue matches the key.Example
# single column update util.bulk_update_table(cr, "res_users", "active", {42: False, 27: True}) # multi-column update util.bulk_update_table( cr, "res_users", ["active", "password"], { "admin": [True, "1234"], "demo": [True, "5678"], }, key_col="login", )
- Paramètres
table (str) – table to update.
columns (str | list(str)) – columns spec for the update. It could be a single column name or a list of column names. The
mappingmust match the spec.mapping (dict) – values to set, which must match the spec in
columns, following the same orderkey_col (str) – column used as key to get the values from
mappingduring the update.
Avertissement
The values in the mapping will be casted to the type of the target column. This function is designed to update scalar values, avoid setting arrays or json data via the mapping.
Misc¶
Miscellaneous standalone functions.
- version_gte(version)[source]¶
Return whether currently running Odoo version is greater or equal to
version.This function is useful for conditional execution in an upgrade script that applies to multiple versions, e.g.
0.0.0scripts.
- version_between(a, b)[source]¶
Return whether currently running Odoo version is in the range
[a,b].See also
version_gte()Note
The bounds are inclusive.
- expand_braces(s)[source]¶
Expand braces in the input.
Example
>>> util.expand_braces("a_{this,that}_b") ['a_this_b', 'a_that_b']
- Paramètres
s (str) – string to expand, must contain precisely one pair of braces.
- Renvoie
expanded input
- import_script(path, name=None)[source]¶
Import an upgrade script.
This function allows to import functions from other upgrade scripts into the current one.
Example
In
mymodule/15.0.1.0/pre-migrate.pydef my_util(cr): # do stuff
In
myothermodule/16.0.1.0/post-migrate.pyfrom odoo.upgrade import util script = util.import_script("mymodule/15.0.1.0/pre-migrate.py") def migrate(cr, version): script.my_util(cr) # reuse the function
This function returns a Python
module.>>> util.import_script("base/0.0.0/end-moved0.py", name="my-moved0") <module 'my-moved0' from '/home/odoo/src/upgrade-util/src/base/0.0.0/end-moved0.py'>
- Paramètres
- Renvoie
a module created from the imported upgrade script
- skippable_cm()[source]¶
Return a context manager to allow another context manager to not yield.
See
edit_view()for an example usage.
- chunks(iterable, size, fmt=None)[source]¶
Split
iterableinto chunks ofsizeand wrap each chunk usingfmtfunction.This function is useful for splitting huge input data into smaller chunks that can be processed independently.
Example
>>> list(chunks(range(10), 4, fmt=tuple)) [(0, 1, 2, 3), (4, 5, 6, 7), (8, 9)] >>> ' '.join(chunks('abcdefghijklm', 3)) 'abc def ghi jkl m'
- Paramètres
iterable (iterable) – iterable object to split
size (int) – chunk size
fmt (function) – function to apply to each chunk, when
Noneis passedfmtbecomes"".joinifiterableis a string, otherwiseiter
- Renvoie
a generator that iterates over the result of
fmtapplied to each chunk