You are on page 1of 30

2016

EXPERIENCE

Developing New Views


In odoo 10

Xavier Morel

Developing new views

Technically always possible by forking the server.

But now: new view types in independent isolated modules

A bit odd: never planned, never on any roadmap.

Just various changes coming together into ability to create views.


Warning
Contains JS/web client stuff later on
Will not cover web client development in general
Ask web client team or fp for training, documentation, changelogs
Creating a View

Anyway, creating a new view (type)


Why?

generic visualisation
non-technical customisation(-ish)
integration

Why a view?
Why a view instead of a client action?

Generic visualisation over multiple models & records


Non-technical customisation via view arch
Web client integration
Web client knows the point and can interact more with view
Examples: pivot view, geo view (camptocamp), etc...
Server: view type
Required

class View(models.Model):
_inherit = 'ir.ui.view'
type = fields.Selection(
selection_add=[('myview', "MyView")]
)

class ActWindowView(models.Model):
_inherit = 'ir.actions.act_window.view'
view_mode = fields.Selection(
selection_add=[('myview', "MyView")]
)

First one is the only one actually required.

Second one not strictly necessary, most likely good idea.

Used when specifying views very explicitly via view_ids.


Server: view arch

<record id="view_myview" model="ir.ui.view">


<field name="name">My View</field>
<field name="model">res.users</field>
<field name="arch" type="xml">
<myview>???</myview>
</field>
</record>

Congratulation your server-side work is done and you can create your view.

Content is whatever you want.

Which means its not that useful, so youre not actually done
Server: <field>

groups
attributes/attrs/modifiers
fields_get filtering

Server: you probably your fields to be called <field>

Fair amount of post-processing on fields

Groups (remove from view if on model, mark as invisible on view)


Attributes (readonly, invisible and required) and attrs are transferred to
modifiers
Fields_get is merged into fields_view_get then filtered based on whats in the
view

Also onchange, but thats pretty hard to implement ATM


Server: validation
Optional (but really useful)

from openerp.tools import view_validation

@view_validation.validate('myview')
def validate_thing(arch):
""" Validates a thing
"""
if arch.find(".//field[@name='charlie']"):
return True
return False

Server validation

Can define validator functions working on lxml etree


Validator function can do pretty much anything
Can use elementpath, xpath of traversal to assert specific items are present in
the document
Warning: definition static, loaded even if module not installed. Should not be
an issue except multiple views w/ same type.
Server: lxml validators
Optional (but really useful)

def validate_thing(arch):
with misc.file_open(os.path.join('my_module', 'views',
'myview.rng')) as f:
validator = etree.RelaxNG(etree.parse(f))

if validator.validate(arch):
return True

for error in validator.error_log:


logger.error(error)
return False

LXML validators

Anything includes LXMLs various validators, such as RelaxNG (used by Odoo for
XML import validation)

Parse & load relaxng file


Validate etree
Print out validation error
Could just return `validate` result, but error messages on validation failure can
be useful
Lxml also supports Schematron, DTD and XML Schemas
Server: base methods
Optional (avoid unless necessary)

class Base(models.AbstractModel):
_inherit = 'base'

myfield = fields.Boolean("My Field")

@api.multi
def myread(self):
""" ...
"""

A base model has been introduced, which you can extend


Every other model inherits from base
Odoo inheritance now properly dynamic, properties added to an inherited
model will be reflected on all inheritors even if created before extension
Avoid unless really necessary
Especially fields as they can have odd side-effects (storage, required
fields on db upgrade)
Methods pollute ns a bit but less problematic
Warning: base is an abstract model, bad things happen otherwise
Client: declare widget

odoo.define('x_test.main', function (require) {


var core = require('web.core');
var View = require('web.View');
var _lt = core._lt;

var MyView= View.extend({


icon: 'fa-cogs',
display_name: _lt("My View"),
});
core.view_registry.add('myview', MyView);

});

Client: declare widget

Subtype of the abstract web.View object


New type must be added to view registry
Doesnt do anything at this point
Technically display_name and icon not required
But default values are empty string and question mark
Icon and display_name used for view switcher button (icon and tooltip
respectively)
Client: initial

Our empty view

Action name
View switcher if multiple views of the same category (multi v mono record)
Search view
Client: Search View

do_search: function (domain, context, groupby) {


this.set({
'domain': domain,
'context': context,
'groupby':groupby
});
return this._fetch();
}
searchable: false

Hook search view, or disable it

If searchable view, view manager will invoke do_search (can be async)


Passed domain, context and groupby compiled from action and search view
Groupby are extracted from contexts for you
Default view is searchable with no-op do_search
Non-searchable views dont get a search view shown at all (e.g. form view)
Client: other options

multi_record: false
mobile_friendly: true
require_fields: true

Other View widget options

multi_record: views classified in mono v multi records, view switcher only


shows one category at a time
warning: no way to categorically switch
do_switch_view(view_type)
mobile_friendly: on mobile devices, the first mobile-friendly view is shown
instead of the first view
require_fields
Needs full fields_get
Either to complement existing filtered fields (example: pivot view)
or because it doesnt use <field>
Client: URL & history

this.do_push_state({});
do_show: function() {
this.do_push_state({});
return this._super();
}
reload_content: function () {
// ...
this.do_push_state({
min: this.current_min,
limit: this._limit
});
}

URL and history handling

do_push_state (method of View) will add a new state item to the stack
ViewManager automatically adds current view *type*
Which is why pushing an empty state is useful (adds new state
transitioning to current view type)
Usually called on do_show to update current view_type
Can pass map of additional values to store in the URL (and history) e.g.
pagination info in list view (when updating list)
current record id in form view (when loading record)
Can call at any moment to further update values
Client: Control Panel

Hooking into the Control panel area


Provides 3 DOM slots
Associated with view (swapped in/out as view is switched)
Empty by default
Names not requirements but strong suggestions
matches standard views
may be relevant wrt themes
In broad sense: corp redesign (actual sidebar), mobile design (buttons
need prominence)
Client: Control Panel
buttons

render_buttons: function($node) {
var $buttons = $('<div/>');
$buttons.append(
QWeb.render("MyView.buttons", {'widget': this})
).on('click', '.my_button_create', this.on_create)
.on('click', '.my_button_import', this.on_import);
$node.append($buttons);
}

Buttons zone

Intended for action buttons


Add content to DOM and hook events on them
May want to keep references to buttons
enable/disable them
show/hide them
Client: Control Panel
sidebar

var Sidebar = require('web.Sidebar');

render_sidebar: function($node) {
var sidebar = new Sidebar(this, {});
sidebar.appendTo($node);
}

Sidebar section

Intended as an actions/meta menu


Helper object provided
Optional, can hand-roll sidebar contents
Client: Control Panel
pager

var Pager = require('web.Pager');

render_pager: function($node) {
var pager = new Pager(this, size, low, page, options);
pager.on('pager_changed', this, function (new_state) {
// ...
});
pager.appendTo($node);
}

Pager area

Configurable helper object


optional
Client: View Widget

fields_view
model
dataset
do_execute_action
do_switch_view
do_show

View Widget services

Fields_view is content of fields_view_get


Passed to init method (3rd arg)
Available as `this.fields_view`
Model
Setup with the actions model
For RPC
Cursor-ish, mostly setting up all records and current record for form
do_execute_action
Asks ancestor action manager to execute an action based on
descriptor
Mostly useful for buttons
do_switch_view
Takes view type
Tries to switch to that view type
do_show
Called when view is shown (~switched to)
Client: View is a Widget

About it
Aside from these, view ~ regular widget
regular widgets lifecycle
Regular widgets tools (willStart/start/$el)
Regular web client API (QWeb, RPC)
Regular JS object (vdom)
Caveats

Not planned => various holes or annoyances

Missing pieces of the puzzle can annoy


Post-processing

access rights
this.is_action_enabled('edit')

Some post-processing code works on a whitelist of view types.

is_action_enabled
Basic access rights information
Can current user create/write/delete object of the model
does not work as server-side information only provided to whitelist of
view types
Drop-in structures

Validation mixins
Client helpers/handlers

No drop-in validation or helpers/handlers for common (cross-view) Odoo structures,


mainly
Fields
Buttons
Studio

No API for Studio integration yet, no Studio for non-standard view types
API

mono/multi record switch


API stability

do_switch_view takes an explicit type requiring specific pairings


Web team wants API changes (possibly radical) following Studio
One More Thing
Unrelated bonus
format_date(date, 'EEE, MMM d', locale)

format_date
en_US Sat, Oct 8

Not related to rest


Feature I discovered recently
I18n -> use babel
Format dates -> format_date
Short date in year (month abbreviation, weekday abbreviation, day of
month)
Reference locale -> look good
Unrelated bonus
format_date(date, 'EEE, MMM d', locale)

format_date
en_US Sat, Oct 8
es_ES sb., oct. 8
fr_FR sam., oct. 8
hi_IN , 8
pt_BR sb, out 8
zh_CN , 10 8

Problem: terms translated but format hardcoded


Different cultures have different ordering & decorations
Result looks odd as best, nonsensical at worst
Unrelated bonus
format_skeleton('MMMEEEdd', date, locale)

format_date format_skeleton
en_US Sat, Oct 8 Sat, Oct 8
es_ES sb., oct. 8 sb., 8 oct.
fr_FR sam., oct. 8 sam. 8 oct.
hi_IN , 8 , 8
pt_BR sb, out 8 sb, 8 de out
zh_CN , 10 8 108

Format_skeleton takes pattern of terms to include


Locale-dependent mapping to formatted pattern
Babel 2.3 (April 8th, 2016)

You might also like