Package: odoo-repl
odoo-replimproves Odoo's interactive shell. It's a development tool, useful for debugging and documentation. It lets you do many things that you might otherwise usepsqlandgrepfor.It's compatible with Odoo 8 and upwards.InstallingThe module can be installed with pip:pip install odoo-repl.RunningThe most basic way to enable it is to runimportodoo_repl;odoo_repl.enable()when in an ordinaryodoo-bin shellenvironment or similar. That will make all the features available.pdbYou can also use it to enhancepdb. Instead of setting a breakpoint withimport pdb; pdb.set_trace(), writeimport odoo_repl; odoo_repl.set_trace().BuildoutIf you use theOdoo buildout recipeyou can instead launch it using theodoo-replwrapper script, which invokespython_odooand does the basic setup for you. The script is automatically installed when you install the module usingpip.Runodoo-replin the buildout directory to launch it, or pass it the path to a buildout directory.Arguments after--will be forwarded to Odoo. So to pick a specific database, runodoo-repl . -- -d <database name>. (The.is necessary to use the current directory.)Runodoo-repl --helpto see a full list of options.Overviewodoo-replis useful for a few different things:Documentation. View summaries of models, fields, methods, and more, including where they were defined and how they are overridden.Experimentation. Debug your code by calling methods directly, without the web interface getting in the way.Exploring records. Get overviews of all a record's fields, run quick and dirty searches, and refer to records with convenient syntax.It adds its own methods to many objects. Those generally end with an underscore, e.g..source_()rather than.source(), to avoid conflicts with existing names.DocumentationAll models are made available as ordinary names. That means that you can typeres.currencyinstead ofenv['res.currency'], and that you can press tab to complete model names.You can type the name of something to get a summary of it. This works for models, fields, and methods.Models>>>res.currencyres.currencyCurrencybase:sd  active:                 booleans   currency_subunit_label: char (Currency Subunit)s   currency_unit_label:    char (Currency Unit)c date:                   dates c decimal_places:         integerRs   name:                   char (Currency)sd  position:               selection (Symbol Position)c rate:                   float (Current Rate)s   rate_ids:               one2many: res.currency.rate (Rates)sd  rounding:               float (Rounding Factor)Rs   symbol:                 charbase: [...]/odoo/addons/base/models/res_currency.py:23This is an overview all the fields that are defined on the model.Each field is marked withr,s,d, andc, which stand forrequired,stored,defaultandcomputed, respectively. You can use this to quickly tell which fields you should expect to find in the database, which ones may not have a value, and so on.A field that's marked asRis required and doesn't have a default, so you should usually pass it to.create().The module and file where the model was defined are listed at the bottom.odoo-repltries to provide that information whenever possible.FieldsThat model overview doesn't tell you everything. You can get more detailed information about a field:>>>res.currency.roundingfloat rounding on res.currency (store, related_sudo)Rounding FactorDefault value: 0.01base: [...]/odoo/addons/base/models/res_currency.py:34You can find information about the default value, whether/how it's computed, and where it was defined.MethodsThe same thing works for methods:>>>res.currency.round@api.multires.currency.round(self, amount)Return ``amount`` rounded  according to ``self``'s rounding rules.:param float amount: the amount to round:return: rounded floatbase: [...]/odoo/addons/base/models/res_currency.py:130You can see the signature, decorators, docstrings, and everywhere it was defined. This is especially useful to track how different modules override the same method.Source codeIt's often helpful to look at the actual source code instead of these summaries. For that, all of these objects have a.source_()method. Run that to get the source code printed to your screen:>>>res.currency.rounding.source_()base: [...]/odoo/addons/base/models/res_currency.py:34rounding = fields.Float(string='Rounding Factor', digits=(12, 6), default=0.01)You can also call the.edit_()method to launch a text editor at the right file and line. The editor is taken from your$EDITORenvironment variable, ornanoby default.Working with recordsIf you know a record's ID you can refer to it concisely, e.g.res.currency[1]. This representation is used throughoutodoo-repl, so often you can just copy/paste it. You can also create a recordset this way, e.g.res.currency[1, 2, 3].SummariesA record is printed with the value of all its fields:res.currency[1] (ref.base.EUR)EURactive:                 Truecurrency_subunit_label: 'Cents'currency_unit_label:    'Euros'date:                   2010-01-01decimal_places:         2name:                   'EUR'position:               'after'rate:                   1.0rate_ids:               res.currency.rate[129] (ref.base.rateEUR)rounding:               0.01symbol:                 '€'Written on 2020-02-11 13:00:34base: [...]/odoo/addons/base/data/res_currency_data.xml:1166If possible, you're also told when and by whom it was created and modified.If the record has a XML ID you're told what it is (base.EURin this case) as well as the files where it was defined.MethodsYou can call.open_()to open the record in your browser..source_()and.edit_()work on records that were defined in XML files.ShorthandInstead of writingenv.ref('base.EUR'), you can writeref.base.EUR. This has tab completion.To get theres.usersrecord foradmin, you can writeu.admin, oru['admin']. That shorthand works with any username. It's useful for trying things as different users, e.g..sudo(u.demo).SearchingModels have a method called_that works as a more flexible version ofsearch. It supports keyword arguments and it's less fussy about record IDs and delimiters.Let's say that you want to find theres.countryrecord for Belgium. Normally you'd have to writeenv['res.country'].search([("name", "=", "Belgium")]), which is pretty verbose. The shortest way to write it withodoo-replisres.country._(name="Belgium").You can use another operator by putting its name after a double underscore (__). To find all countries thataren'tBelgium, useres.country._(name__ne="Belgium").The same trick can be used for dotted paths across relations:res.users._(partner_id__name="Mitchell Admin").Alternatively you can write it as a flat list of arguments without any brackets, i.e.res.users._("partner_id.name", "=", "Mitchell Admin").When using records in a.search()domain you have to explicitly take the ID of the record instead of passing the record.._()doesn't need that, sores.partner._(country_id=ref.base.be)will just work.Getting a random recordThe.shuf_()method (short for "shuffle") gives you a random record. If you want to look at a random user record, just runres.users.shuf_().It takes an optional argument for the number of records to return.res.users.shuf_(10)will return a recordset with ten random users.Operating on all recordsIterating over a model will iterate over all its records. So if you want to run a piece of code on all users, just start withfor user in res.users:. This is equivalent tofor user in env['res.users'].search([]):, just shorter.The.mapped()and.filtered()methods on models also operate on all records, saving you from typing.search([]).More model informationBesides the summaries, there are methods to get more information about a model.Listing menusThe.menus_()method prints ways the model can be reached in the web interface, with information about the views that are used. For example:>>>project.task.menus_()Project/All Tasks (ref.project.action_view_task)calendar: ir.ui.view[591] (ref.project.view_task_calendar)form: ir.ui.view[588] (ref.project.view_task_form2)graph: ir.ui.view[593] (ref.project.view_project_task_graph)kanban: ir.ui.view[589] (ref.project.view_task_kanban)pivot: ir.ui.view[592] (ref.project.view_project_task_pivot)timeline: ir.ui.view[1094] (ref.project_timeline.project_task_timeline)tree: ir.ui.view[590] (ref.project.view_task_tree2)activity: ???res.users → Assigned Tasks (ref.project.act_res_users_2_project_task_opened)calendar: ir.ui.view[591] (ref.project.view_task_calendar)form: ir.ui.view[588] (ref.project.view_task_form2)graph: ir.ui.view[593] (ref.project.view_project_task_graph)tree: ir.ui.view[590] (ref.project.view_task_tree2)On this instance, you can reachproject.taskby navigating to the "All Tasks" submenu in "Project", and you can choose from a number of different view types. They all have theirir.ui.viewrecord listed, except for theactivitytype, for which it couldn't be determined.You can also reach tasks from the "Assigned Tasks" menu onres.usersrecords.Security information.rules_()prints all their.model.accessandir.rulerecords that apply to a model:>>>account.invoice.rules_()ir.model.access[355] (ref.account.access_account_invoice_uinvoice)account.invoiceBilling (ref.account.group_account_invoice)read, write, create, unlink[...]ir.model.access[543] (ref.purchase.access_account_invoice_purchase_manager)account_invoice purchase managerManager (ref.purchase.group_purchase_manager)read,      ,       ,[...]ir.rule[68] (ref.account.invoice_comp_rule)Invoice multi-companyEveryoneread, write, create, unlink['|',('company_id', '=', False),('company_id', 'child_of', [user.company_id.id])]account: [...]/addons/account/security/account_security.xml:126You still need a good grasp of howir.model.accessandir.rulework to interpret this information correctly.Rendering views.view_()prints the XML used to render a model's view. By default, it prints the form view, but you can pass an argument to render a different view.>>>account.invoice.view_('tree')<tree decoration-info="state == 'draft'" decoration-muted="state == 'cancel'" decoration-bf="not partner_id" string="Vendor Bill" js_class="account_bills_tree"><field name="partner_id" invisible="1"/>[...]<field name="company_currency_id" invisible="1"/><field name="state"/><field name="type" invisible="context.get('type',True)"/></tree>Listing methods.methods_()lists all the methods that are defined for the model, grouped by implementing module.Methods that are available on all models are only shown if they're overridden.>>>res.partner.methods_()project_compute_task_count(self)partner_multi_relation_tabs_add_tab_pages(self, view)_compute_tabs_visibility(self)_get_tabs(self)add_field(self, tab)browse(self, arg=None, prefetch=None)fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False)[...]SQL information.sql_()shows a very basic summary of a model's table in the database.>>>res.currency.sql_()res_currencyaccuracy:    int4active:      boolbase:        boolcompany_id:  int4create_date: timestampcreate_uid:  int4date:        dateid:          int4name:        varcharposition:    varcharrounding:    numericsymbol:      varcharwrite_date:  timestampwrite_uid:   int4Integrationsodoo-replcan integrate with a few external programs.gitObjects with a.source_()method also have a.gitsource_()method that generates URLs for git repositories. This is useful for sharing with other people.If possible it will create a URL with a commit hash in it, so it will stay the same even if the repository is updated.>>>res.users._browse.gitsource_()base_suspend_security: https://github.com/OCA/server-backend/blob/92ebf2d/base_suspend_security/models/res_users.py#L11BaseModel: https://github.com/oca/ocb/blob/f228ae0ce5e/odoo/models.py#L4675Although only Github links are shown here it also works with other hosts that are similar enough to Github, like private Gitlab instances.grepObjects with a.source_()method have a.grep_()method for runninggrepon their source code. This is especially useful when they are defined across many different modules.You can also use thegrep_()function to search through all installed modules and Odoo's code (while skipping modules that aren't installed).ripgrep(rg) is used instead ofgrepif it's installed. This is recommended because it has more readable output when searching multiple files and handles directory searches better.Keyword arguments are converted to flags. To get one line of context you'd pass-C 1togrep. You can get the same by passingC=1to.grep_().>>>ir.attachment.grep_("ValidationError",C=1)[...]/odoo/addons/base/models/ir_attachment.py14-from odoo import api, fields, models, tools, SUPERUSER_ID, exceptions, _15:from odoo.exceptions import AccessError, ValidationError16-from odoo.tools import config, human_size, ustr, html_escape--334-            if not any([has_group(g) for g in self.get_serving_groups()]):335:                raise ValidationError("Sorry, you are not allowed to write on this document")336---564-            except Exception:565:                raise exceptions.ValidationError(_("ERROR: Invalid PDF file!"))566-            max_page = input_pdf.getNumPages()--606-        if 'pdf' not in self.mimetype:607:            raise exceptions.ValidationError(_("ERROR: the file must be a PDF"))608-        if indices:--611-            except ValueError:612:                raise exceptions.ValidationError(_("ERROR: Invalid list of pages to split. Example: 1,5-9,10"))613-            return self._split_pdf_groups(pdf_groups=[[min(x), max(x)] for x in pages], remainder=remainder)fzffzfis a tool for fuzzy incremental searching. If you have it installed you can use it to search through records very easily.You can use the.fzf_()method on a model to search through display names:>>>ir.ui.menu.fzf_()> Settings/Technical/Parameters/System Parameters1/71> systeir.ui.menu[25] (ref.base.ir_config_menu)Settings/Technical/Parameters/System Parametersaction:        ir.actions.act_window[10] (ref.base.ir_config_list_action)active:        True[...]Or you can use it on a field to search through the values of that field instead:>>>ir.ui.view.arch.fzf_()..arding_state') in ('done', 'just_done') else '') + (' o_onboarding_steps_just_done' if sta....1"/>                                     <field name="sub_model_object_field" domain="[('m...., [])]}" class="btn btn-primary float-right" name="channel_join_and_get_info">Join</button..> ..eld name="exclude_contact"/>                             <field name="exclude_journal_item..50/346> exclude_joir.ui.view[96] (ref.base.base_partner_merge_automatic_wizard_form)base.partner.merge.automatic.wizard.formactive:               True[...]It's hard to get it across in text, so it may be best to just try it.Odoo addonsOdoo addons/modules can be inspected interactively, as attributes of theaddonsobject:>>>addons.base_suspend_securitybase_suspend_security 12.0.1.0.1 by Therp BV, brain-tec AG, Odoo Community Association (OCA)http://localhost:8012/web?debug=1#model=ir.module.module&id=382[...]/server-backend/base_suspend_securityInstalledSuspend securitySuspend security checks for a callDepends: baseDependents: base_user_role_historyDefines: base, ir.model.access, ir.rule, res.users[...]This module was written to  allow you to call code with some `uid` while being sure no security checks (`ir.model.access` and `ir.rule`) are done. In this way, it's the same as `sudo()`, but the crucial difference is that the code still runs with the original user id. This can be important for inherited code that calls workflow functions, subscribes the current user to some object, etc.[...]In addition to the contents of theREADME, you see its dependencies, the modules that depend on it, the models it defines, a link to it in the web interface, and its location in the filesystem.These objects have a few useful methods and attributes. The.manifestattribute gives you the addon's manifest:>>>addons.base_suspend_security.manifest{'application': False,'author': 'Therp BV, brain-tec AG, Odoo Community Association (OCA)','auto_install': False,'category': 'Hidden/Dependency',[...]>>>addons.base_suspend_security.manifest.version'12.0.1.0.1'.recordgives you their.module.modulerecord of the addon..open_()opens the module in the web interface in your browser..grep_()and.gitsource_()are supported..definitions_()prints a listing of every model, field, method and record defined in the addon.