Professional Documents
Culture Documents
__manifest__.py:
{
'name': 'To-Do Application',
'description': 'Manage your personal To-Do tasks.',
'author': 'Daniel Reis',
'depends': ['base'],
'application': True,
}
Modify __init__.py
from . import todo_model
Point to todo_model.py
<sheet>
<!-- Content goes here: -->
<group>
<field name="name"/>
<field name="is_done"/>
<field name="active" readonly="1"/>
</group>
</sheet>
</form>
</field>
</record>
</odoo>
The most important attribute is arch, and it contains the view definition, highlighted in the
XML code above. The <form> tag defines the view type, and in this case, contains three
fields. We also added an attribute to the active field to make it read only.
The basic attributes of a button comprise the following:
string with the text to display on the button
type of action it performs
name is the identifier for that action
class is an optional attribute to apply CSS styles, like in regular HTML
The action buttons do not work yet since we still need to add their business logic.
Add this new file to the data key in the manifest file
__manifest__.py.
{
'name': 'To-Do Application',
'description': 'Manage your personal To-Do tasks.',
'author': 'Daniel Reis',
'depends': ['base'],
'data': [
'views/todo_menu.xml',
'views/todo_view.xml',],
'application': True,
}
We can use < group > to organize forms. Placing <group> elements inside a <group>
element creates a two column layout inside the outer group. Group elements are advised
to have a name attribute so that it is easier for other modules to extend them.
views/todo_view.xml
<?xml version="1.0"?>
<odoo>
<record id="view_form_todo_task" model="ir.ui.view">
<field name="name">To-do Task Form</field>
<field name="model">todo.task</field>
<field name="arch" type="xml">
<form string="To-do Task">
<header>
<!-- Buttons go here-->
<button name="do_toggle_done" type="object"
string="Toggle Done" class="oe_highlight" />
<button name="do_clear_done" type="object"
string="Clear All Done" />
</header>
<sheet>
<group name="group_top">
<group name="group_left">
<field name="name"/>
</group>
<group name="group_right">
<field name="is_done"/>
<field name="active" readonly="1"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
</odoo>
Activating developer mode, we can see the form code selecting the icon “Open Developer
Tool” at top right menu (the icon is a bug), and then click on “Edit Form View”.
We can put css boostrap code for nicer. e.g. the lines for done tasks (is_done==True) are
shown grayed out. Check http://getbootstrap.com/css/#helper-classes-colors
In developer mode, Open Developer Tool->Edit TreeView:
Add Search View
At the top-right corner of the list, Odoo displays a search box. The fields it searches in and
the available filters are defined by a <search> view. We add a <record> to the view xml file,
todo_view.xml in this case
Add to todo_view.xml
<record id="view_filter_todo_task" model="ir.ui.view">
<field name="name">To-do Task Filter</field>
<field name="model">todo.task</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<filter string="Not Done"
domain="[('is_done','=',False)]"/>
<filter string="Done"
domain="[('is_done','!=',False)]"/>
</search>
</field>
</record>
The <field> elements define fields that are also searched when typing in the search box. The
<filter> elements add predefined filter conditions, which can be toggled with a user click,
defined using a specific syntax.
In developer mode, Open Developer Tool->Edit SearchView:
To define a filter as default, we need to modify the ./view/todo_view.xml (in blue font the
added):
Modify ./view/todo_view.xml
…
<record id="view_filter_todo_task" model="ir.ui.view">
<field name="name">To-do Task Filter</field>
<field name="model">todo.task</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<filter name="filter_not_done" string="Not Done" domain="[('is_done', '=' ,False)]"/>
<filter string="Done" domain="[('is_done', '!=' ,False)]"/>
<filter string="Not Active" domain="[('active', '=' ,False)]"/>
</search>
</field>
</record>
…
Each filter may or may not have a name. To put a filter as default we must put a name, e.g.
filter_not_done. The default filter is placed in an action record using the context field and
building a dictionary with a key formed by search_default_ followed by the name of the
filter (filter_not_done), and assign it to True.
Now, when we enter to Todos Menu, we will see in the search box, the filter Not Done.
In developer mode, Open Developer Tool->Edit Action
Add business logic
We should edit the todo_model.py Python file to add to the class the methods called by the
buttons.
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class TodoTask(models.Model):
_name = 'todo.task'
_description = 'To-do Task'
name = fields.Char('Description', required=True)
is_done = fields.Boolean('Done?')
active = fields.Boolean('Active?', default=True)
@api.multi
def do_toggle_done(self):
for task in self:
task.is_done = not task.is_done
return True
@api.model
def do_clear_done(self):
dones = self.search([('is_done', '=', True)])
dones.write({'active': False})
return True
Add Security
Adding access control security
We can see the ACL in Settings | Technical | Security | Access Controls List
We will add full access to the employee group on the model. Employee is the basic access
group nearly everyone belongs to
security/ir.model.access.csv
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_todo_task_group_user,todo.task.user,model_todo_task,base.group_user,1,1,1,1
id is the record external identifier (also known as XML ID). It should be unique in our
module.
name is a description title. It is only informative and it is best if it has kept unique.
Official modules usually use a dot-separated string with the model name and the
group. Following this convention, we used todo.task.user.
model_id is the external identifier for the model we are giving access to. Models
have XML IDs automatically generated by the ORM: for todo.task, the identifier
is model_todo_task.
group_id identifies the security group to give permissions to. The base module
provides the most important ones. The Employee group is such a case and has the
identifier base.group_user.
perm fields flag the access to grant read, write, create, or unlink (delete) access.
Add the reference to this new file in the __manifest__.py descriptor's data attribute. It
should look like this:
'data': [
'security/ir.model.access.csv',
'views/todo_view.xml',
'views/todo_menu.xml',
],
The eval attribute evaluates a Python expression and assigns the resulting value to the field.
The ref() function is used to translate an external identifier into the corresponding database
ID.
Extending Existing Applications. Inheritance
One of Odoo's most powerful feature is the ability to add features without directly
modifying the underlying objects.
This is achieved through inheritance mechanisms, functioning as modification layers on top
of existing objects. These modifications can happen at all levels: models, views, and
business logic. Instead of directly modifying an existing module, we create a new module to
add the intended modifications.
__manifest__.py:
{ 'name': 'Multiuser To-Do',
'description': 'Extend the To-Do app to multiuser.',
'author': 'Daniel Reis',
'depends': ['todo_app',], }
summary and category keys can be important when publishing modules to the Odoo online
app store.
For the inheritance mechanism to work properly, we added the explicit dependency on
todo_app module. When the todo_app module is updated, all modules depending on it,
such as todo_user module, will also be updated.
class TodoTask(models.Model):
_inherit = 'todo.task'
user_id = fields.Many2one('res.users', 'Responsible')
date_deadline = fields.Date('Deadline')
The class name TodoTask is local to this Python file and, in general, is irrelevant for other
modules. The _inherit class attribute is the key here: it tells Odoo that this class is inheriting
and thus modifying the todo.task model.
Modify __init__.py
./__init__.py
from . import models
Point to /model subdirectory
./models/__init__.py
from . import todo_task
Point to /model/todo_task.py
You would usually have a from line for each Python file in the directory
b) Modifying existing fields
To modify an existing field, we need to add a field with the same name and setting values
only for the attributes to be changed. For example, to add a help tooltip to the name field,
we would add this line to todo_ task.py, described previously:
/model/todo_task.py
…
name = fields.Char(help="What needs to be done?")
…
This modifies the field with the specified attributes, leaving unmodified all the other
attributes not explicitly used here.
To have the overriding method keep the already existing logic, we use Python's super()
construct to call the parent's version of the method.
/model/todo_task.py
…
@api.multi
def do_toggle_done(self):
for task in self:
if task.user_id != self.env.user:
raise ValidationError('Only the responsible can do this!')
return super(TodoTask, self).do_toggle_done()
Modifying the views
Forms, lists, and search views are defined using the arch XML structures. To extend views,
we need a way to modify this XML. This means locating XML elements and then introducing
modifications at those points.
There are two ways for locate XML elements. One is for Xpath expression. You can find more
information on XPath at
https://docs.python.org/2/library/xml.etree.elementtree.html#xpath-support
Other method is using Odoo shortcut notation.
Extending the form view
In the following list, we can see the form view using the XLM location odoo notation extend:
/model/todo_task.py
<?xml version="1.0"?>
<odoo>
<record id="view_form_todo_task_inherited" model="ir.ui.view">
<field name="name">Todo Task form - User extension</field>
<field name="model">todo.task</field>
<field name="inherit_id" ref="todo_app.view_form_todo_task"/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="user_id" />
</field>
<field name="is_done" position="before">
<field name="date_deadline" />
</field>
<field name="active" position="attributes">
<attribute name="invisible">1</attribute>
</field>
</field>
</record>
</odoo>
The inherit_id field identifies the view to be extended by referring to its external identifier
using the special ref attribute.
To locate the position to modify, the Odoo notation is writes this:
<field name="is_done" position="before">
<field name="date_deadline" />
</field>
Just be aware that if the field appears more than once in the same view, you should always
use the XPath syntax. This is because Odoo will stop at the first occurrence of the field and
it may apply your changes to the wrong field.
Often, we want to add new fields next to the existing ones, so the <field> tag will be used
as the locator frequently. However, any other tag can be used: <sheet>, <group>, <div>, and
so on. The name attribute is usually the best choice for matching elements, but sometimes,
we may need to use something else: the CSS class element, for example. Odoo will find the
first element that has at least all the attributes specified.
The position attribute used with the locator element is optional and can have the following
values:
after: adds the content to the parent element, after the matched node.
before: adds the content, before the matched node.
inside: (default value) appended the content inside matched node.
replace: replaces the matched node. If used with empty content, it deletes an
element.
Setting the invisible attribute to hide an element is a good alternative to using the replace
locator to remove nodes.
Note: Inherited views can also be inherited, but since this creates more intricate
dependencies, it should be avoided
We must add this new file to the data key in the manifest file
./__manifest__.py
{ 'name': 'Multiuser To-Do',
'description': 'Extend the To-Do app to multiuser.',
'author': 'Daniel Reis',
'depends': ['todo_app', ],
'data': ['views/todo_task.xml',],
}
Extending the tree and search views
Tree and search view extensions are also defined using the arch XML structure, and they
can be extended in the same way as form views.
/model/todo_task.py
…
<record id="view_tree_todo_task_inherited" model="ir.ui.view">
<field name="name">Todo Task tree - User extension</field>
<field name="model">todo.task</field>
<field name="inherit_id" ref="todo_app.view_tree_todo_task"/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="user_id" />
</field>
</field>
</record>
Context
On the client side it can carry information from one view to next, such as the ID of the record
active on the previous view, after following a link or a button, or to provide default values
to be used in the next view.
On the server side, some recordset field values can depend on the locale settings provided
by the context. Context can also provide signals for server-side code.
Keys
lang: affects the value of translatable fields
tz time zone information
uid current user ID.
active_test: when set to False changes the behavior of ORM's search() method so
that it does not filter out inactive records.
When opening a form from a link or a button in a previous view, an active_id key is added
to the context, with the ID of record we were positioned at, in the origin form. In the
particular case of list views, we have an active_ids context key containing a list of the record
IDs selected in the previous list.
On the client side, the context can be used to set default values or activate default filters
on the target view, using keys with the default_ or default_search_ prefixes
Example: set the current user as a default value of the user_id field
{'default_user_id': uid}
{'default_search_filter_my_tasks': 1}
Domain
The domain is used to filter data records. They use a specific syntax that the Odoo ORM
parses to produce the SQL WHERE expressions that will query the database.
A domain expression is a list of conditions. Each condition is a ('field_name', 'operator',
value') tuple, field name is the field being filtered. The value is evaluated as a Python
expression. e.g, this is a valid domain expression, with only one condition:
[('is_done','=',False)].
A domain expression is a list of items, and can contain several condition tuples. By default
these condition will implicitly be combined using the AND logical operator. This means that
it will only return records meeting all these conditions. Explicit logic operators can also be
used: the ampersand symbol, '&', for AND operations (the default), and the pipe symbol,'|',
for OR operations.
The exclamation point, '!', represents the NOT operator, is also available and operates on
the next item.
Examples:
- Field “is_done ” is false:
['!', ('is_done','=',True)]
- Filters all the records where the current user is in the follower list, is the
responsible user, or does not have a responsible user set:
Field
Attributes Description
name identifies the field database name
string Label text, to be used if we want to override the label text provided
by the model definition.
help Tooltip text shown when you hover the pointer over the field, and
allows to override the help text provided by the model definition
placeholder Suggestion text to display inside the field.
widget Allows us to override the default widget used for the field
options JSON data structure with additional options for the widget and
depends on what each widget supports.
class The CSS classes to use for the field HTML rendering
nolabel="True" Prevents the automatic field label from being presented. It only
makes sense for the fields inside a <group> element and is often used
along with a <label for="..."> element
invisible="True" Makes the field not visible, but it's data is fetched from the server and
is available on the form.
readonly="True" Makes the field non-editable on the form.
required="True" Makes the field mandatory on the form.
password="True" Used for text fields. It is displayed as a password field, masking the
characters typed in.
filename Used for binary fields, and it is the name of the model field to be used
to store the name of the uploaded file.
mode Used for one-to-many fields. It specifies the view type to use to
display the records. By default, it is tree, but can also be form, kanban,
or graph.