-
Notifications
You must be signed in to change notification settings - Fork 7.6k
/
backend.rst
1375 lines (1006 loc) · 48.1 KB
/
backend.rst
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
:orphan:
.. _howto/base:
.. _howto/module:
=================
Building a Module
=================
.. danger::
This tutorial is outdated. We recommend reading :doc:`getting_started` instead.
.. warning::
This tutorial requires :doc:`having installed Odoo <../../administration/install>`
Start/Stop the Odoo server
==========================
Odoo uses a client/server architecture in which clients are web browsers
accessing the Odoo server via RPC.
Business logic and extension is generally performed on the server side,
although supporting client features (e.g. new data representation such as
interactive maps) can be added to the client.
In order to start the server, simply invoke the command :ref:`odoo-bin
<reference/cmdline>` in the shell, adding the full path to the file if
necessary:
.. code:: bash
odoo-bin
The server is stopped by hitting ``Ctrl-C`` twice from the terminal, or by
killing the corresponding OS process.
Build an Odoo module
====================
Both server and client extensions are packaged as *modules* which are
optionally loaded in a *database*.
Odoo modules can either add brand new business logic to an Odoo system, or
alter and extend existing business logic: a module can be created to add your
country's accounting rules to Odoo's generic accounting support, while the
next module adds support for real-time visualisation of a bus fleet.
Everything in Odoo thus starts and ends with modules.
Composition of a module
-----------------------
An Odoo module can contain a number of elements:
Business objects
Declared as Python classes, these resources are automatically persisted
by Odoo based on their configuration
:doc:`Object views <../reference/user_interface/view_architectures>`
Definition of business objects UI display
:ref:`Data files <reference/data>`
XML or CSV files declaring the model metadata :
* :doc:`views <../reference/user_interface/view_architectures>` or :ref:`reports
<reference/reports>`,
* configuration data (modules parametrization, :ref:`security rules <reference/security>`),
* demonstration data
* and more
:ref:`Web controllers <reference/controllers>`
Handle requests from web browsers
Static web data
Images, CSS or javascript files used by the web interface or website
Module structure
----------------
Each module is a directory within a *module directory*. Module directories
are specified by using the :option:`--addons-path <odoo-bin --addons-path>`
option.
.. tip::
:class: aphorism
most command-line options can also be set using :ref:`a configuration file
<reference/cmdline/config>`
An Odoo module is declared by its :ref:`manifest <reference/module/manifest>`.
A module is also a
`Python package <http://docs.python.org/2/tutorial/modules.html#packages>`_
with a ``__init__.py`` file, containing import instructions for various Python
files in the module.
For instance, if the module has a single ``mymodule.py`` file ``__init__.py``
might contain::
from . import mymodule
Odoo provides a mechanism to help set up a new module, :ref:`odoo-bin
<reference/cmdline/server>` has a subcommand :ref:`scaffold
<reference/cmdline/scaffold>` to create an empty module:
.. code-block:: console
$ odoo-bin scaffold <module name> <where to put it>
The command creates a subdirectory for your module, and automatically creates a
bunch of standard files for a module. Most of them simply contain commented code
or XML. The usage of most of those files will be explained along this tutorial.
.. exercise:: Module creation
Use the command line above to create an empty module Open Academy, and install it in Odoo.
Object-Relational Mapping
-------------------------
A key component of Odoo is the :abbr:`ORM (Object-Relational Mapping)` layer.
This layer avoids having to write most :abbr:`SQL (Structured Query Language)`
by hand and provides extensibility and security services\ [#rawsql]_.
Business objects are declared as Python classes extending
:class:`~odoo.models.Model` which integrates them into the automated
persistence system.
Models can be configured by setting a number of attributes at their
definition. The most important attribute is
:attr:`~odoo.models.Model._name` which is required and defines the name for
the model in the Odoo system. Here is a minimally complete definition of a
model::
from odoo import models
class MinimalModel(models.Model):
_name = 'test.model'
Model fields
------------
Fields are used to define what the model can store and where. Fields are
defined as attributes on the model class::
from odoo import models, fields
class LessMinimalModel(models.Model):
_name = 'test.model2'
name = fields.Char()
Common Attributes
~~~~~~~~~~~~~~~~~
Much like the model itself, its fields can be configured, by passing
configuration attributes as parameters::
name = fields.Char(required=True)
Some attributes are available on all fields, here are the most common ones:
:attr:`~odoo.fields.Field.string` (``unicode``, default: field's name)
The label of the field in UI (visible by users).
:attr:`~odoo.fields.Field.required` (``bool``, default: ``False``)
If ``True``, the field can not be empty, it must either have a default
value or always be given a value when creating a record.
:attr:`~odoo.fields.Field.help` (``unicode``, default: ``''``)
Long-form, provides a help tooltip to users in the UI.
:attr:`~odoo.fields.Field.index` (``bool``, default: ``False``)
Requests that Odoo create a `database index`_ on the column.
Simple fields
~~~~~~~~~~~~~
There are two broad categories of fields: "simple" fields which are atomic
values stored directly in the model's table and "relational" fields linking
records (of the same model or of different models).
Example of simple fields are :class:`~odoo.fields.Boolean`,
:class:`~odoo.fields.Date`, :class:`~odoo.fields.Char`.
Reserved fields
~~~~~~~~~~~~~~~
Odoo creates a few fields in all models\ [#autofields]_. These fields are
managed by the system and shouldn't be written to. They can be read if
useful or necessary:
:attr:`~odoo.fields.Model.id` (:class:`~odoo.fields.Id`)
The unique identifier for a record in its model.
:attr:`~odoo.fields.Model.create_date` (:class:`~odoo.fields.Datetime`)
Creation date of the record.
:attr:`~odoo.fields.Model.create_uid` (:class:`~odoo.fields.Many2one`)
User who created the record.
:attr:`~odoo.fields.Model.write_date` (:class:`~odoo.fields.Datetime`)
Last modification date of the record.
:attr:`~odoo.fields.Model.write_uid` (:class:`~odoo.fields.Many2one`)
user who last modified the record.
Special fields
~~~~~~~~~~~~~~
By default, Odoo also requires a ``name`` field on all models for various
display and search behaviors. The field used for these purposes can be
overridden by setting :attr:`~odoo.models.Model._rec_name`.
.. exercise:: Define a model
Define a new data model *Course* in the *openacademy* module. A course has a title and a
description. Courses must have a title.
Data files
----------
Odoo is a highly data driven system. Although behavior is customized using
Python_ code part of a module's value is in the data it sets up when loaded.
.. tip:: some modules exist solely to add data into Odoo
:class: aphorism
Module data is declared via :ref:`data files <reference/data>`, XML files with
``<record>`` elements. Each ``<record>`` element creates or updates a database
record.
.. code-block:: xml
<odoo>
<record model="{model name}" id="{record identifier}">
<field name="{a field name}">{a value}</field>
</record>
</odoo>
* ``model`` is the name of the Odoo model for the record.
* ``id`` is an :term:`external identifier`, it allows referring to the record
(without having to know its in-database identifier).
* ``<field>`` elements have a ``name`` which is the name of the field in the
model (e.g. ``description``). Their body is the field's value.
Data files have to be declared in the manifest file to be loaded, they can
be declared in the ``'data'`` list (always loaded) or in the ``'demo'`` list
(only loaded in demonstration mode).
.. exercise:: Define demonstration data
Create demonstration data filling the *Courses* model with a few demonstration courses.
.. tip::
The content of the data files is only loaded when a module is installed or updated.
After making some changes, do not forget to use :ref:`odoo-bin -u openacademy
<reference/cmdline>` to save the changes to your database.
.. _howtos/module/actions:
Actions and Menus
-----------------
Actions and menus are regular records in database, usually declared through
data files. Actions can be triggered in three ways:
#. by clicking on menu items (linked to specific actions)
#. by clicking on buttons in views (if these are connected to actions)
#. as contextual actions on object
Because menus are somewhat complex to declare there is a ``<menuitem>``
shortcut to declare an ``ir.ui.menu`` and connect it to the corresponding
action more easily.
.. code-block:: xml
<record model="ir.actions.act_window" id="action_list_ideas">
<field name="name">Ideas</field>
<field name="res_model">idea.idea</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_ideas" parent="menu_root" name="Ideas" sequence="10"
action="action_list_ideas"/>
.. danger::
:class: aphorism
The action must be declared before its corresponding menu in the XML file.
Data files are executed sequentially, the action's ``id`` must be present in the database before
the menu can be created.
.. exercise:: Define new menu entries
Define new menu entries to access courses under the OpenAcademy menu entry. A user should be able
to:
- display a list of all the courses
- create/modify courses
Basic views
===========
Views define the way the records of a model are displayed. Each type of view
represents a mode of visualization (a list of records, a graph of their
aggregation, …). Views can either be requested generically via their type
(e.g. *a list of partners*) or specifically via their id. For generic
requests, the view with the correct type and the lowest priority will be
used (so the lowest-priority view of each type is the default view for that
type).
:ref:`View inheritance <reference/view_records/inheritance>` allows altering views
declared elsewhere (adding or removing content).
Generic view declaration
------------------------
A view is declared as a record of the model ``ir.ui.view``. The view type
is implied by the root element of the ``arch`` field:
.. code-block:: xml
<record model="ir.ui.view" id="view_id">
<field name="name">view.name</field>
<field name="model">object_name</field>
<field name="priority" eval="16"/>
<field name="arch" type="xml">
<!-- view content: <form>, <tree>, <graph>, ... -->
</field>
</record>
.. danger:: The view's content is XML.
:class: aphorism
The ``arch`` field must thus be declared as ``type="xml"`` to be parsed correctly.
Tree views
----------
Tree views, also called list views, display records in a tabular form.
Their root element is ``<tree>``. The simplest form of the tree view simply
lists all the fields to display in the table (each field as a column):
.. code-block:: xml
<tree string="Idea list">
<field name="name"/>
<field name="inventor_id"/>
</tree>
.. _howtos/module/views/form:
Form views
----------
Forms are used to create and edit single records.
Their root element is ``<form>``. They are composed of high-level structure
elements (groups, notebooks) and interactive elements (buttons and fields):
.. code-block:: xml
<form string="Idea form">
<group colspan="4">
<group colspan="2" col="2">
<separator string="General stuff" colspan="2"/>
<field name="name"/>
<field name="inventor_id"/>
</group>
<group colspan="2" col="2">
<separator string="Dates" colspan="2"/>
<field name="active"/>
<field name="invent_date" readonly="1"/>
</group>
<notebook colspan="4">
<page string="Description">
<field name="description" nolabel="1"/>
</page>
</notebook>
<field name="state"/>
</group>
</form>
.. exercise:: Customise form view using XML
Create your own form view for the Course object. Data displayed should be: the name and the
description of the course.
.. exercise:: Notebooks
In the Course form view, put the description field under a tab, such that it will be easier to
add other tabs later, containing additional information.
Form views can also use plain HTML for more flexible layouts:
.. code-block:: xml
<form string="Idea Form">
<header>
<button string="Confirm" type="object" name="action_confirm"
invisible="state != 'draft'" class="oe_highlight" />
<button string="Mark as done" type="object" name="action_done"
invisible="state != 'confirmed'" class="oe_highlight"/>
<button string="Reset to draft" type="object" name="action_draft"
invisible="state not in ['confirmed', 'done']" />
<field name="state" widget="statusbar"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only" string="Idea Name" />
<h1><field name="name" /></h1>
</div>
<separator string="General" colspan="2" />
<group colspan="2" col="2">
<field name="description" placeholder="Idea description..." />
</group>
</sheet>
</form>
Search views
------------
Search views customize the search field associated with the list view (and
other aggregated views). Their root element is ``<search>`` and they're
composed of fields defining which fields can be searched on:
.. code-block:: xml
<search>
<field name="name"/>
<field name="inventor_id"/>
</search>
If no search view exists for the model, Odoo generates one which only allows
searching on the ``name`` field.
.. exercise:: Search courses
Allow searching for courses based on their title or their description.
Relations between models
========================
A record from a model may be related to a record from another model. For
instance, a sale order record is related to a client record that contains the
client data; it is also related to its sale order line records.
.. exercise:: Create a session model
For the module Open Academy, we consider a model for *sessions*: a session
is an occurrence of a course taught at a given time for a given audience.
Create a model for *sessions*. A session has a name, a start date, a
duration and a number of seats. Add an action and a menu item to display
them. Make the new model visible via a menu item.
Relational fields
-----------------
Relational fields link records, either of the same model (hierarchies) or
between different models.
Relational field types are:
:class:`Many2one(other_model, ondelete='set null') <odoo.fields.Many2one>`
A simple link to an other object::
print(foo.other_id.name)
.. seealso:: `foreign keys <http://www.postgresql.org/docs/12/static/tutorial-fk.html>`_
:class:`One2many(other_model, related_field) <odoo.fields.One2many>`
A virtual relationship, inverse of a :class:`~odoo.fields.Many2one`.
A :class:`~odoo.fields.One2many` behaves as a container of records,
accessing it results in a (possibly empty) set of records::
for other in foo.other_ids:
print(other.name)
.. danger::
Because a :class:`~odoo.fields.One2many` is a virtual relationship,
there *must* be a :class:`~odoo.fields.Many2one` field in the
:samp:`{other_model}`, and its name *must* be :samp:`{related_field}`
:class:`Many2many(other_model) <odoo.fields.Many2many>`
Bidirectional multiple relationship, any record on one side can be related
to any number of records on the other side. Behaves as a container of
records, accessing it also results in a possibly empty set of records::
for other in foo.other_ids:
print(other.name)
.. exercise:: Many2one relations
Using a many2one, modify the *Course* and *Session* models to reflect their
relation with other models:
- A course has a *responsible* user; the value of that field is a record of
the built-in model ``res.users``.
- A session has an *instructor*; the value of that field is a record of the
built-in model ``res.partner``.
- A session is related to a *course*; the value of that field is a record
of the model ``openacademy.course`` and is required.
- Adapt the views.
.. exercise:: Inverse one2many relations
Using the inverse relational field one2many, modify the models to reflect
the relation between courses and sessions.
.. exercise:: Multiple many2many relations
Using the relational field many2many, modify the *Session* model to relate
every session to a set of *attendees*. Attendees will be represented by
partner records, so we will relate to the built-in model ``res.partner``.
Adapt the views accordingly.
Inheritance
===========
Model inheritance
-----------------
Odoo provides two *inheritance* mechanisms to extend an existing model in a
modular way.
The first inheritance mechanism allows a module to modify the behavior of a
model defined in another module:
- add fields to a model,
- override the definition of fields on a model,
- add constraints to a model,
- add methods to a model,
- override existing methods on a model.
The second inheritance mechanism (delegation) allows to link every record of a
model to a record in a parent model, and provides transparent access to the
fields of the parent record.
.. image:: ../reference/backend/orm/inheritance_methods.png
:align: center
.. seealso::
* :attr:`~odoo.models.Model._inherit`
* :attr:`~odoo.models.Model._inherits`
View inheritance
----------------
Instead of modifying existing views in place (by overwriting them), Odoo
provides view inheritance where children "extension" views are applied on top of
root views, and can add or remove content from their parent.
An extension view references its parent using the ``inherit_id`` field, and
instead of a single view its ``arch`` field is composed of any number of
``xpath`` elements selecting and altering the content of their parent view:
.. code-block:: xml
<!-- improved idea categories list -->
<record id="idea_category_list2" model="ir.ui.view">
<field name="name">id.category.list2</field>
<field name="model">idea.category</field>
<field name="inherit_id" ref="id_category_list"/>
<field name="arch" type="xml">
<!-- find field description and add the field
idea_ids after it -->
<xpath expr="//field[@name='description']" position="after">
<field name="idea_ids" string="Number of ideas"/>
</xpath>
</field>
</record>
``expr``
An XPath_ expression selecting a single element in the parent view.
Raises an error if it matches no element or more than one
``position``
Operation to apply to the matched element:
``inside``
appends ``xpath``'s body at the end of the matched element
``replace``
replaces the matched element with the ``xpath``'s body, replacing any ``$0`` node occurrence
in the new body with the original element
``before``
inserts the ``xpath``'s body as a sibling before the matched element
``after``
inserts the ``xpaths``'s body as a sibling after the matched element
``attributes``
alters the attributes of the matched element using special
``attribute`` elements in the ``xpath``'s body
.. tip::
When matching a single element, the ``position`` attribute can be set directly
on the element to be found. Both inheritances below will give the same result.
.. code-block:: xml
<xpath expr="//field[@name='description']" position="after">
<field name="idea_ids" />
</xpath>
<field name="description" position="after">
<field name="idea_ids" />
</field>
.. exercise:: Alter existing content
* Using model inheritance, modify the existing *Partner* model to add an
``instructor`` boolean field, and a many2many field that corresponds to
the session-partner relation
* Using view inheritance, display this fields in the partner form view
Domains
~~~~~~~
In Odoo, :ref:`reference/orm/domains` are values that encode conditions on
records. A domain is a list of criteria used to select a subset of a model's
records. Each criteria is a triple with a field name, an operator and a value.
For instance, when used on the *Product* model the following domain selects
all *services* with a unit price over *1000*::
[('product_type', '=', 'service'), ('unit_price', '>', 1000)]
By default criteria are combined with an implicit AND. The logical operators
``&`` (AND), ``|`` (OR) and ``!`` (NOT) can be used to explicitly combine
criteria. They are used in prefix position (the operator is inserted before
its arguments rather than between). For instance to select products "which are
services *OR* have a unit price which is *NOT* between 1000 and 2000"::
['|',
('product_type', '=', 'service'),
'!', '&',
('unit_price', '>=', 1000),
('unit_price', '<', 2000)]
A ``domain`` parameter can be added to relational fields to limit valid
records for the relation when trying to select records in the client interface.
.. exercise:: Domains on relational fields
When selecting the instructor for a *Session*, only instructors (partners
with ``instructor`` set to ``True``) should be visible.
.. exercise:: More complex domains
Create new partner categories *Teacher / Level 1* and *Teacher / Level 2*.
The instructor for a session can be either an instructor or a teacher
(of any level).
Computed fields and default values
==================================
So far fields have been stored directly in and retrieved directly from the
database. Fields can also be *computed*. In that case, the field's value is not
retrieved from the database but computed on-the-fly by calling a method of the
model.
To create a computed field, create a field and set its attribute
:attr:`~odoo.fields.Field.compute` to the name of a method. The computation
method should simply set the value of the field to compute on every record in
``self``.
.. danger:: ``self`` is a collection
:class: aphorism
The object ``self`` is a *recordset*, i.e., an ordered collection of records. It supports the
standard Python operations on collections, like ``len(self)`` and ``iter(self)``, plus extra set
operations like ``recs1 + recs2``.
Iterating over ``self`` gives the records one by one, where each record is itself a collection of
size 1. You can access/assign fields on single records by using the dot notation, like
``record.name``.
.. code-block:: python
import random
from odoo import models, fields, api
class ComputedModel(models.Model):
_name = 'test.computed'
name = fields.Char(compute='_compute_name')
def _compute_name(self):
for record in self:
record.name = str(random.randint(1, 1e6))
Dependencies
------------
The value of a computed field usually depends on the values of other fields on
the computed record. The ORM expects the developer to specify those dependencies
on the compute method with the decorator :func:`~odoo.api.depends`.
The given dependencies are used by the ORM to trigger the recomputation of the
field whenever some of its dependencies have been modified::
from odoo import models, fields, api
class ComputedModel(models.Model):
_name = 'test.computed'
name = fields.Char(compute='_compute_name')
value = fields.Integer()
@api.depends('value')
def _compute_name(self):
for record in self:
record.name = "Record with value %s" % record.value
.. exercise:: Computed fields
* Add the percentage of taken seats to the *Session* model
* Display that field in the tree and form views
* Display the field as a progress bar
Default values
--------------
Any field can be given a default value. In the field definition, add the option
``default=X`` where ``X`` is either a Python literal value (boolean, integer,
float, string), or a function taking a recordset and returning a value::
name = fields.Char(default="Unknown")
user_id = fields.Many2one('res.users', default=lambda self: self.env.user)
.. note::
The object ``self.env`` gives access to request parameters and other useful things:
- ``self.env.cr`` or ``self._cr`` is the database *cursor* object; it is
used for querying the database
- ``self.env.uid`` or ``self._uid`` is the current user's database id
- ``self.env.user`` is the current user's record
- ``self.env.context`` or ``self._context`` is the context dictionary
- ``self.env.ref(xml_id)`` returns the record corresponding to an XML id
- ``self.env[model_name]`` returns an instance of the given model
.. exercise:: Active objects – Default values
* Define the start_date default value as today (see
:class:`~odoo.fields.Date`).
* Add a field ``active`` in the class Session, and set sessions as active by
default.
Onchange
========
The "onchange" mechanism provides a way for the client interface to update a
form whenever the user has filled in a value in a field, without saving anything
to the database.
For instance, suppose a model has three fields ``amount``, ``unit_price`` and
``price``, and you want to update the price on the form when any of the other
fields is modified. To achieve this, define a method where ``self`` represents
the record in the form view, and decorate it with :func:`~odoo.api.onchange`
to specify on which field it has to be triggered. Any change you make on
``self`` will be reflected on the form.
.. code-block:: xml
<!-- content of form view -->
<field name="amount"/>
<field name="unit_price"/>
<field name="price" readonly="1"/>
.. code-block:: python
# onchange handler
@api.onchange('amount', 'unit_price')
def _onchange_price(self):
# set auto-changing field
self.price = self.amount * self.unit_price
# Can optionally return a warning and domains
return {
'warning': {
'title': "Something bad happened",
'message': "It was very bad indeed",
}
}
For computed fields, valued ``onchange`` behavior is built-in as can be seen by
playing with the *Session* form: change the number of seats or participants, and
the ``taken_seats`` progressbar is automatically updated.
.. exercise:: Warning
Add an explicit onchange to warn about invalid values, like a negative
number of seats, or more participants than seats.
Model constraints
=================
Odoo provides two ways to set up automatically verified invariants:
:func:`Python constraints <odoo.api.constrains>` and
:attr:`SQL constraints <odoo.models.Model._sql_constraints>`.
A Python constraint is defined as a method decorated with
:func:`~odoo.api.constrains`, and invoked on a recordset. The decorator
specifies which fields are involved in the constraint, so that the constraint is
automatically evaluated when one of them is modified. The method is expected to
raise an exception if its invariant is not satisfied::
from odoo.exceptions import ValidationError
@api.constrains('age')
def _check_something(self):
for record in self:
if record.age > 20:
raise ValidationError("Your record is too old: %s" % record.age)
# all records passed the test, don't return anything
.. exercise:: Add Python constraints
Add a constraint that checks that the instructor is not present in the
attendees of his/her own session.
SQL constraints are defined through the model attribute
:attr:`~odoo.models.Model._sql_constraints`. The latter is assigned to a list
of triples of strings ``(name, sql_definition, message)``, where ``name`` is a
valid SQL constraint name, ``sql_definition`` is a table_constraint_ expression,
and ``message`` is the error message.
.. exercise:: Add SQL constraints
With the help of `PostgreSQL's documentation`_ , add the following
constraints:
#. CHECK that the course description and the course title are different
#. Make the Course's name UNIQUE
.. exercise:: Exercise 6 - Add a duplicate option
Since we added a constraint for the Course name uniqueness, it is not
possible to use the "duplicate" function anymore (:menuselection:`Form -->
Duplicate`).
Re-implement your own "copy" method which allows to duplicate the Course
object, changing the original name into "Copy of [original name]".
Advanced Views
==============
Tree views
----------
Tree views can take supplementary attributes to further customize their
behavior:
``decoration-{$name}``
allow changing the style of a row's text based on the corresponding
record's attributes.
Values are Python expressions. For each record, the expression is evaluated
with the record's attributes as context values and if ``true``, the
corresponding style is applied to the row. Here are some of the other values
available in the context:
* ``uid``: the id of the current user,
* ``today``: the current local date as a string of the form ``YYYY-MM-DD``,
* ``now``: same as ``today`` with the addition of the current time.
This value is formatted as ``YYYY-MM-DD hh:mm:ss``.
``{$name}`` can be ``bf`` (``font-weight: bold``), ``it``
(``font-style: italic``), or any `bootstrap contextual color
<https://getbootstrap.com/docs/3.3/components/#available-variations>`_ (``danger``,
``info``, ``muted``, ``primary``, ``success`` or ``warning``).
.. code-block:: xml
<tree string="Idea Categories" decoration-info="state=='draft'"
decoration-danger="state=='trashed'">
<field name="name"/>
<field name="state"/>
</tree>
``editable``
Either ``"top"`` or ``"bottom"``. Makes the tree view editable in-place
(rather than having to go through the form view), the value is the
position where new rows appear.
.. exercise:: List coloring
Modify the Session tree view in such a way that sessions lasting less than
5 days are colored blue, and the ones lasting more than 15 days are
colored red.
Calendars
---------
Displays records as calendar events. Their root element is ``<calendar>`` and
their most common attributes are:
``color``
The name of the field used for *color segmentation*. Colors are
automatically distributed to events, but events in the same color segment
(records which have the same value for their ``@color`` field) will be
given the same color.
``date_start``
record's field holding the start date/time for the event
``date_stop`` (optional)
record's field holding the end date/time for the event
``string``
record's field to define the label for each calendar event
.. code-block:: xml
<calendar string="Ideas" date_start="invent_date" color="inventor_id">
<field name="name"/>
</calendar>
.. exercise:: Calendar view
Add a Calendar view to the *Session* model enabling the user to view the
events associated to the Open Academy.
Search views
------------
Search view ``<field>`` elements can have a ``@filter_domain`` that overrides
the domain generated for searching on the given field. In the given domain,
``self`` represents the value entered by the user. In the example below, it is
used to search on both fields ``name`` and ``description``.
Search views can also contain ``<filter>`` elements, which act as toggles for
predefined searches. Filters must have one of the following attributes:
``domain``
add the given domain to the current search
``context``
add some context to the current search; use the key ``group_by`` to group
results on the given field name
.. code-block:: xml
<search string="Ideas">
<field name="name"/>
<field name="description" string="Name and description"
filter_domain="['|', ('name', 'ilike', self), ('description', 'ilike', self)]"/>
<field name="inventor_id"/>
<field name="country_id" widget="selection"/>
<filter name="my_ideas" string="My Ideas"
domain="[('inventor_id', '=', uid)]"/>
<group string="Group By">
<filter name="group_by_inventor" string="Inventor"
context="{'group_by': 'inventor_id'}"/>
</group>
</search>
To use a non-default search view in an action, it should be linked using the
``search_view_id`` field of the action record.
The action can also set default values for search fields through its
``context`` field: context keys of the form
:samp:`search_default_{field_name}` will initialize *field_name* with the
provided value. Search filters must have an optional ``@name`` to have a
default and behave as booleans (they can only be enabled by default).
.. exercise:: Search views
#. Add a button to filter the courses for which the current user is the
responsible in the course search view. Make it selected by default.
#. Add a button to group courses by responsible user.
Gantt
-----
.. warning::
The gantt view requires the web_gantt module which is present in the :ref:`enterprise edition
<install/editions>` version.
Horizontal bar charts typically used to show project planning and advancement,
their root element is ``<gantt>``.
.. code-block:: xml
<gantt string="Ideas"
date_start="invent_date"
date_stop="date_finished"
progress="progress"
default_group_by="inventor_id" />
.. exercise:: Gantt charts
Add a Gantt Chart enabling the user to view the sessions scheduling linked
to the Open Academy module. The sessions should be grouped by instructor.
Graph views
-----------
Graph views allow aggregated overview and analysis of models, their root
element is ``<graph>``.
.. note::
Pivot views (element ``<pivot>``) a multidimensional table, allows the selection of filers and