While CSV files provide a simple and compact format to represent data, XML files are more powerful and give more control over the loading process. Their filenames are not required to match the model to be loaded. This is because the XML format is much richer and that information is provided by the XML elements inside the file.
We already used XML data files in the previous chapters. The user interface components, such as views and menu items, are in fact records stored in system models. The XML files in the modules are the means used to load these records into the server.
To showcase this, we will add a second data file to the todo_user
module, data/todo_data.xml
, with the following content:
<?xml version="1.0"?> <odoo> <!-- Data to load --> <record model="todo.task" id="todo_task_c"> <field name="name">Reinstall Odoo</field> <field name="user_id" ref="base.user_root" /> <field name="date_deadline">2015-01-30</field> <field name="is_done" eval="False" /> </record> </odoo>
This XML is equivalent to the CSV data file we just saw in the previous section.
XML data files have a <odoo>
top element, inside of which we can have several <record>
elements that correspond to the CSV data rows.
The <odoo>
top element in data files was introduced in version 9.0 and replaces the former <openerp>
tag. The <data>
section inside the top element is still supported, but it's now optional. In fact, now <odoo>
and <data>
are equivalent, so we could use either one as top elements for our XML data files.
A <record>
element has two mandatory attributes, namely model
and id
(the external identifier for the record), and contains a <field>
tag for each field to write on.
Note that the slash notation in field names is not available here; we can't use
<field name="user_id/id">
. Instead, the ref
special attribute is used to reference external identifiers. We'll discuss the values of the relational to-many fields in a moment.
When data loading is repeated, records loaded from the previous run are rewritten. It is important to keep in mind that this means that upgrading a module will overwrite any manual changes that might have been made inside the database. Notably, if views were modified with customizations, then these changes will be lost with the next module upgrade. The correct procedure is to instead create inherited views for the changes we need, as discussed in Chapter 3 , Inheritance - Extending Existing Applications.
This re-import behavior is default, but it can be changed, so that when an module is upgraded, some data file records are left untouched. This is done by the noupdate="1"
attribute of the <odoo>
or <data>
element. These records will be created when the addon module is installed, but in subsequent module upgrades nothing will be done to them.
This allows you to ensure that manually made customizations are kept safe from module upgrades. It is often used with record access rules, allowing them to be adapted to implementation-specific needs.
It is possible to have more than one <data>
section in the same XML file. We can take advantage of this to separate data to import only one, with noupdate="1"
, and data to be re-imported on each upgrade, with noupdate="0"
.
The noupdate
flag is stored in the External Identifier information for each record. It's possible to manually edit it directly using the External Identifier form available in the Technical menu, using the Non Updatable checkbox.
The noupdate
attribute can be tricky when developing modules, because changes made to the data later will be ignored. A solution is to, instead of upgrading the module with the -u
option, reinstall it using the -i
option. Reinstalling from the command line using the -i
option ignores the noupdate
flags on data records.
Each <record>
element has two basic attributes, id
and model
, and contains <field>
elements that assign values to each column. As mentioned before, the id
attribute corresponds to the record's external identifier and the model
attribute to the target model where the record will be written. The <field>
elements have a few different ways to assign values. Let's look at them in detail.
The <record>
element defines a data record and contains <field>
elements to set values on each field.
The name
attribute of the field
element identifies the field to be written.
The value to write is the element content: the text between the field's opening and closing tag. For dates and datetimes, strings with "YYYY-mm-dd"
and "YYYY-mm-dd HH:MM:SS"
will be converted properly. But for Boolean fields any non-empty value will be converted as True
, and the "0"
and "False"
values are converted to False
.
A more elaborate alternative to define a field value is the eval
attribute. It evaluates a Python expression and assigns the resulting value to the field.
The expression is evaluated in a context that, besides Python built-ins, also has some additional identifiers available. Let's have a look at them.
To handle dates, the following modules are available: time
, datetime
, timedelta
, and relativedelta
. They allow you to calculate date values, something that is frequently used in demonstration and test data, so that the dates used are close to the module installation date. For example, to set a value to yesterday, we will use this:
<field name="date_deadline" eval="(datetime.now() + timedelta(-1)).strftime('%Y-%m-%d')" />
Also available in the evaluation context is the ref()
function, which is used to translate an external identifier into the corresponding database ID. This can be used to set values for relational fields. As an example, we have used it before to set the value for user_id
:
<field name="user_id" eval="ref('base.group_user')" />
We have just seen how to set a value on a many-to-one relation field, such as user_id
, using the eval
attribute with a ref()
function. But there is a simpler way.
The <field>
element also has a ref
attribute to set the value for a many-to-one field, using an external identifier. With this, we can set the value for user_id
using just this:
<field name="user_id" ref="base.user_demo" />
For one-to-many and many-to-many fields, a list of related IDs is expected, so a different syntax is needed; Odoo provides a special syntax to write on this type of fields.
The following example, taken from the official Fleet app, replaces the list of related records of a tag_ids
field:
<field name="tag_ids" eval="[(6,0, [ref('vehicle_tag_leasing'), ref('fleet.vehicle_tag_compact'), ref('fleet.vehicle_tag_senior')] )]" />
To write on a to-many field, we use a list of triples. Each triple is a write command that does different things according to the code used:
(0,_ ,{'field': value})
creates a new record and links it to this one(1,id,{'field': value})
updates the values on an already linked record(2,id,_)
unlinks and deletes a related record(3,id,_)
unlinks but does not delete a related record(4,id,_)
links an already existing record(5,_,_)
unlinks but does not delete all the linked records(6,_,[ids])
replaces the list of linked records with the provided listThe underscore symbol used in the preceding list represents irrelevant values, usually filled with 0
or False
.
If we go back to
Chapter 2
, Building Your First Odoo Application, we will find elements other than <record>
, such as <act_window>
and <menuitem>
, in the XML files.
These are convenient shortcuts for frequently used models that can also be loaded using regular <record>
elements. They load data into base models supporting the user interface and will be explored in more detail later, specifically in
Chapter 6
, Views - Designing the User Interface.
For reference, the following shortcut elements are available with the corresponding models they load data into:
<act_window>
is the window action model, ir.actions.act_window
<menuitem>
is the menu items model, ir.ui.menu
<report>
is the report action model, ir.actions.report.xml
<template>
is for QWeb templates stored in the model ir.ui.view
<url>
is the URL action model, ir.actions.act_url
Until now, we have seen how to add or update data using XML files. But XML files also allow you to perform other types of actions that are sometimes needed to set up data. In particular, they can delete data, execute arbitrary model methods, and trigger workflow events.
To delete a data record, we use the <delete>
element, providing it with either an ID or a search domain to find the target record. For example, using a search domain to find the record to delete looks like this:
<delete model="ir.rule" search=" [('id','=',ref('todo_app.todo_task_user_rule'))]" />
Since in this case we know the specific ID to delete, we could have used it directly for the same effect:
<delete model="ir.rule" id="todo_app.todo_task_user_rule" />
An XML file can also execute methods during its load process through the <function>
element. This can be used to set up demo and test data. For example, the CRM app uses it to set up demonstration data:
<function model="crm.lead" name="action_set_lost" eval="[ref('crm_case_7'), ref('crm_case_9') , ref('crm_case_11'), ref('crm_case_12')] , {'install_mode': True}" />
This calls the action_set_lost
method of the crm.lead
model, passing two arguments through the eval
attribute. The first is the list of IDs to work on, and the next is the context to use.
Another way XML data files can perform actions is by triggering Odoo workflows through the <workflow>
element. Workflows can, for example, change the state of a sales order or convert it into an invoice. The sale
app no longer uses workflows, but this example can still be found in the demo data:
<workflow model="sale.order" ref="sale_order_4" action="order_confirm" />
The model
attribute is self-explanatory by now, and ref
identifies the workflow instance we are acting upon. The action
is the workflow signal sent to this workflow instance.