[MERGE] accounting, usability review of form views
[odoo/odoo.git] / doc / addons.rst
1 Developing OpenERP Web Addons
2 =============================
3
4 An OpenERP Web addon is simply a Python package with an openerp
5 descriptor (a ``__openerp__.py`` file) which follows a few structural
6 and namespacing rules.
7
8 Structure
9 ---------
10
11 .. literalinclude:: addon-structure.txt
12
13 ``__openerp__.py``
14   The addon's descriptor, contains the following information:
15
16   ``name: str``
17     The addon name, in plain, readable english
18   ``version: str``
19     The addon version, following `Semantic Versioning`_ rules
20   ``depends: [str]``
21     A list of addons this addon needs to work correctly. ``base`` is
22     an implied dependency if the list is empty.
23   ``css: [str]``
24     An ordered list of CSS files this addon provides and needs. The
25     file paths are relative to the addon's root. Because the Web
26     Client *may* perform concatenations and other various
27     optimizations on CSS files, the order is important.
28   ``js: [str]``
29     An ordered list of Javascript files this addon provides and needs
30     (including dependencies files). As with CSS files, the order is
31     important as the Web Client *may* perform contatenations and
32     minimizations of files.
33   ``active: bool``
34     Whether this addon should be enabled by default any time it is
35     found, or whether it will be enabled through other means (on a
36     by-need or by-installation basis for instance).
37
38 ``controllers/``
39   All of the Python controllers and JSON-RPC endpoints.
40
41 ``static/``
42   The static files directory, may be served via a separate web server.
43
44 ``static/lib/``
45   Third-party libraries used by the addon.
46
47 ``static/src/{css,js,img,xml}``
48   Location for (respectively) the addon's static CSS files, its JS
49   files, its various image resources as well as the template files
50
51 ``static/test``
52   Javascript tests files
53
54 ``test/``
55   The directories in which all tests for the addon are located.
56
57 Some of these are guidelines (and not enforced by code), but it's
58 suggested that these be followed. Code which does not fit into these
59 categories can go wherever deemed suitable.
60
61 Namespacing
62 -----------
63
64 Python
65 ++++++
66
67 Because addons are also Python packages, they're inherently namespaced
68 and nothing special needs to be done on that front.
69
70 JavaScript
71 ++++++++++
72
73 The JavaScript side of an addon has to live in the namespace
74 ``openerp.$addon_name``. For instance, everything created by the addon
75 ``base`` lives in ``openerp.base``.
76
77 The root namespace of the addon is a function which takes a single
78 parameter ``openerp``, which is an OpenERP client instance. Objects
79 (as well as functions, registry instances, etc...) should be added on
80 the correct namespace on that object.
81
82 The root function will be called by the OpenERP Web client when
83 initializing the addon.
84
85 .. code-block:: javascript
86
87     // root namespace of the openerp.example addon
88     /** @namespace */
89     openerp.example = function (openerp) {
90         // basic initialization code (e.g. templates loading)
91         openerp.example.SomeClass = openerp.base.Class.extend(
92             /** @lends openerp.example.SomeClass# */{
93             /**
94              * Description for SomeClass's constructor here
95              *
96              * @constructs
97              */
98             init: function () {
99                 // SomeClass initialization code
100             }
101             // rest of SomeClass
102         });
103
104         // access an object in an other addon namespace to replace it
105         openerp.base.SearchView = openerp.base.SearchView.extend({
106             init: function () {
107                 this._super.apply(this, arguments);
108                 console.log('Search view initialized');
109             }
110         });
111     }
112
113 Creating new standard roles
114 ---------------------------
115
116 Widget
117 ++++++
118
119 This is the base class for all visual components. It provides a number of
120 services for the management of a DOM subtree:
121
122 * Rendering with QWeb
123
124 * Parenting-child relations
125
126 * Life-cycle management (including facilitating children destruction when a
127   parent object is removed)
128
129 * DOM insertion, via jQuery-powered insertion methods. Insertion targets can
130   be anything the corresponding jQuery method accepts (generally selectors,
131   DOM nodes and jQuery objects):
132
133   :js:func:`~openerp.base.Widget.appendTo`
134     Renders the widget and inserts it as the last child of the target, uses
135     `.appendTo()`_
136
137   :js:func:`~openerp.base.Widget.prependTo`
138     Renders the widget and inserts it as the first child of the target, uses
139     `.prependTo()`_
140
141   :js:func:`~openerp.base.Widget.insertAfter`
142     Renders the widget and inserts it as the preceding sibling of the target,
143     uses `.insertAfter()`_
144
145   :js:func:`~openerp.base.Widget.insertBefore`
146     Renders the widget and inserts it as the following sibling of the target,
147     uses `.insertBefore()`_
148
149 :js:class:`~openerp.base.Widget` inherits from
150 :js:class:`~openerp.base.SessionAware`, so subclasses can easily access the
151 RPC layers.
152
153 Subclassing Widget
154 ~~~~~~~~~~~~~~~~~~
155
156 :js:class:`~openerp.base.Widget` is subclassed in the standard manner (via the
157 :js:func:`~openerp.base.Class.extend` method), and provides a number of
158 abstract properties and concrete methods (which you may or may not want to
159 override). Creating a subclass looks like this:
160
161 .. code-block:: javascript
162
163     var MyWidget = openerp.base.Widget.extend({
164         // QWeb template to use when rendering the object
165         template: "MyQWebTemplate",
166
167         init: function(parent) {
168             this._super(parent);
169             // insert code to execute before rendering, for object
170             // initialization
171         },
172         start: function() {
173             this._super();
174             // post-rendering initialization code, at this point
175             // ``this.$element`` has been initialized
176             this.$element.find(".my_button").click(/* an example of event binding * /);
177
178             // if ``start`` is asynchronous, return a promise object so callers
179             // know when the object is done initializing
180             return this.rpc(/* … */)
181         }
182     });
183
184 The new class can then be used in the following manner:
185
186 .. code-block:: javascript
187
188     // Create the instance
189     var my_widget = new MyWidget(this);
190     // Render and insert into DOM
191     my_widget.appendTo(".some-div");
192
193 After these two lines have executed (and any promise returned by ``appendTo``
194 has been resolved if needed), the widget is ready to be used.
195
196 .. note:: the insertion methods will start the widget themselves, and will
197           return the result of :js:func:`~openerp.base.Widget.start()`.
198
199           If for some reason you do not want to call these methods, you will
200           have to first call :js:func:`~openerp.base.Widget.render()` on the
201           widget, then insert it into your DOM and start it.
202
203 If the widget is not needed anymore (because it's transient), simply terminate
204 it:
205
206 .. code-block:: javascript
207
208     my_widget.stop();
209
210 will unbind all DOM events, remove the widget's content from the DOM and
211 destroy all widget data.
212
213 Views
214 +++++
215
216 Views are the standard high-level component in OpenERP. A view type corresponds
217 to a way to display a set of data (coming from an OpenERP model).
218
219 In OpenERP Web, views are standard objects registered against a dedicated
220 object registry, so the :js:class:`~openerp.base.ViewManager` knows where to
221 find and how to call them.
222
223 Although not mandatory, it is recommended that views inherit from
224 :js:class:`openerp.base.View`, which provides a view useful services to its
225 children.
226
227 Registering a view
228 ~~~~~~~~~~~~~~~~~~
229
230 This is the first task to perform when creating a view, and the simplest by
231 far: simply call ``openerp.base.views.add(name, object_path)`` to register
232 the object of path ``object_path`` as the view for the view name ``name``.
233
234 The view name is the name you gave to your new view in the OpenERP server.
235
236 From that point onwards, OpenERP Web will be able to find your object and
237 instantiate it.
238
239 Standard view behaviors
240 ~~~~~~~~~~~~~~~~~~~~~~~
241
242 In the normal OpenERP Web flow, views have to implement a number of methods so
243 view managers can correctly communicate with them:
244
245 ``start()``
246     This method will always be called after creating the view (via its
247     constructor), but not necessarily immediately.
248
249     It is called with no arguments and should handle the heavy setup work,
250     including remote call (to load the view's setup data from the server via
251     e.g. ``fields_view_get``, for instance).
252
253     ``start`` should return a `promise object`_ which *must* be resolved when
254     the view's setup is completed. This promise is used by view managers to
255     know when they can start interacting with the view.
256
257 ``do_hide()``
258     Called by the view manager when it wants to replace this view by an other
259     one, but wants to keep this view around to re-activate it later.
260
261     Should put the view in some sort of hibernation mode, and *must* hide its
262     DOM elements.
263
264 ``do_show()``
265     Called when the view manager wants to re-display the view after having
266     hidden it. The view should refresh its data display upon receiving this
267     notification
268
269 ``do_search(domain: Array, context: Object, group_by: Array)``
270     If the view is searchable, this method is called to notify it of a search
271     against it.
272
273     It should use the provided query data to perform a search and refresh its
274     internal content (and display).
275
276     All views are searchable by default, but they can be made non-searchable
277     by setting the property ``searchable`` to ``false``.
278
279     This can be done either on the view class itself (at the same level as
280     defining e.g. the ``start`` method) or at the instance level (in the
281     class's ``init``), though you should generally set it on the class.
282
283 Frequent development tasks
284 --------------------------
285
286 There are a number of tasks which OpenERP Web developers do or will need to
287 perform quite regularly. To make these easier, we have written a few guides
288 to help you get started:
289
290 .. toctree::
291     :maxdepth: 1
292
293     guides/client-action
294     guides/sidebar-protocol
295
296 Translations
297 ------------
298
299 OpenERP Web should provide most of the tools needed to correctly translate your
300 addons via the tool of your choice (OpenERP itself uses `Launchpad's own
301 translation tool`_.
302
303 Making strings translatable
304 +++++++++++++++++++++++++++
305
306 QWeb
307 ~~~~
308
309 QWeb automatically marks all text nodes (any text which is not in an XML
310 attribute and not part of an XML tag) as translatable, and handles the
311 replacement for you. There is nothing special to do to mark template text as
312 translatable
313
314 JavaScript
315 ~~~~~~~~~~
316
317 OpenERP Web provides two functions to translate human-readable strings in
318 javascript code. These functions should be "imported" in your module by
319 aliasing them to their bare name:
320
321 .. code-block:: javascript
322
323     var _t = openerp.web._t,
324        _tl = openerp.web._tl;
325
326 importing those functions under any other name is not guaranteed to work.
327
328 .. note:: only import them if necessary, and only the necessary one(s), no need
329           to clutter your module's namespace for nothing
330
331 .. js:function:: openerp.web._t(s)
332
333     Base translation function, eager, works much like :manpage:`gettext(3)`
334
335     :type s: String
336     :rtype: String
337
338 .. js:function:: openerp.web._lt(s)
339
340     Lazy equivalent to :js:func:`~openerp.web._t`, this function will postpone
341     fetching the translation to its argument until the last possible moment.
342
343     To use in contexts evaluated before the translation database can be
344     fetched, usually your module's toplevel and the attributes of classes
345     defined in it (class attributes, not instance attributes set in the
346     constructor).
347
348     :type s: String
349     :rtype: LazyString
350
351 Text formatting & translations
352 """"""""""""""""""""""""""""""
353
354 A difficulty when translating is integrating data (from the code) into the
355 translated string. In OpenERP Web addons, this should be done by wrapping the
356 text to translate in an :manpage:`sprintf(3)` call. For OpenERP Web,
357 :manpage:`sprintf(3)` is provided by `underscore.string
358 <http://epeli.github.com/underscore.string/>`_.
359
360 As much as possible, you should use the "named argument" form of sprintf:
361
362 .. code-block:: javascript
363
364     var translated_string = _.str.sprintf(
365         _t("[%(first_record)d to %(last_record)d] of %(records_count)d"), {
366             first_record: first + 1,
367             last_record: last,
368             records_count: total
369         }));
370
371 named arguments make the string to translate much clearer for translators, and
372 allows them to "move" sections around based on the requirements of their
373 language (not all language order text like english).
374
375 Named arguments are specified using the following pattern: ``%($name)$type``
376 where
377
378 ``$name``
379   the name of the argument, this is the key in the object/dictionary provided
380   as second parameter to ``sprintf``
381 ``$type``
382   a type/format specifier, `see the list for all possible types
383   <http://www.diveintojavascript.com/projects/javascript-sprintf>`_.
384
385 .. note:: positional arguments are acceptable if the translated string has
386           *a single* argument and its content is easy to guess from the text
387           around it. Named arguments should still be preferred.
388
389 .. warning:: you should *never* use string concatenation as it robs the
390              translator of context and make result in a completely incorrect
391              translation
392
393 Extracting strings
394 ~~~~~~~~~~~~~~~~~~
395
396 .. program:: gen_translations.sh
397
398 Once strings have been marked for translation, they need to be extracted into
399 :abbr:`POT (Portable Object Template)` files, from which most translation tools
400 can build a database.
401
402 This can be done via the provided :program:`gen_translations.sh`.
403
404 It can be called either as :option:`gen_translations.sh -a` or by providing
405 two parameters, a path to the addons and the complete path in which to put the
406 extracted POT file.
407
408 .. option:: -a
409
410     Extracts translations from all standard OpenERP Web addons (addons bundled
411     with OpenERP Web itself) and puts the extracted templates into the right
412     directory for `Rosetta`_ to handle them
413
414 Utility behaviors
415 -----------------
416
417 JavaScript
418 ++++++++++
419
420 * All javascript objects inheriting from
421   :js:class:`openerp.base.BasicConroller` will have all methods
422   starting with ``on_`` or ``do_`` bound to their ``this``. This means
423   they don't have to be manually bound (via ``_.bind`` or ``$.proxy``)
424   in order to be useable as bound event handlers (event handlers
425   keeping their object as ``this`` rather than taking whatever
426   ``this`` object they were called with).
427
428   Beware that this is only valid for methods starting with ``do_`` and
429   ``on_``, any other method will have to be bound manually.
430
431 .. _addons-testing:
432
433 Testing
434 -------
435
436 Python
437 ++++++
438
439 OpenERP Web uses unittest2_ for its testing needs. We selected
440 unittest2 rather than unittest_ for the following reasons:
441
442 * autodiscovery_ (similar to nose, via the ``unit2``
443   CLI utility) and `pluggable test discovery`_.
444
445 * `new and improved assertions`_ (with improvements in type-specific
446   inequality reportings) including `pluggable custom types equality
447   assertions`_
448
449 * neveral new APIs, most notably `assertRaises context manager`_,
450   `cleanup function registration`_, `test skipping`_ and `class- and
451   module-level setup and teardown`_
452
453 * finally, unittest2 is a backport of Python 3's unittest. We might as
454   well get used to it.
455
456 To run tests on addons (from the root directory of OpenERP Web) is as
457 simple as typing ``PYTHONPATH=. unit2 discover -s addons`` [#]_. To
458 test an addon which does not live in the ``addons`` directory, simply
459 replace ``addons`` by the directory in which your own addon lives.
460
461 .. note:: unittest2 is entirely compatible with nose_ (or the
462      other way around). If you want to use nose as your test
463      runner (due to its addons for instance) you can simply install it
464      and run ``nosetests addons`` instead of the ``unit2`` command,
465      the result should be exactly the same.
466
467 Python
468 ++++++
469
470 .. autoclass:: web.common.session.OpenERPSession
471     :members:
472
473 .. autoclass:: web.common.openerplib.main.Model
474     :members:
475
476 * Addons lifecycle (loading, execution, events, ...)
477
478   * Python-side
479   * JS-side
480
481 * Handling static files
482 * Overridding a Python controller (object?)
483 * Overridding a Javascript controller (object?)
484 * Extending templates
485   .. how do you handle deploying static files via e.g. a separate lighttpd?
486 * Python public APIs
487 * QWeb templates description?
488 * OpenERP Web modules (from OpenERP modules)
489
490 .. [#] the ``-s`` parameter tells ``unit2`` to start trying to
491        find tests in the provided directory (here we're testing
492        addons). However a side-effect of that is to set the
493        ``PYTHONPATH`` there as well, so it will fail to find (and
494        import) ``openerpweb``.
495
496        The ``-t`` parameter lets us set the ``PYTHONPATH``
497        independently, but it doesn't accept multiple values and here
498        we really want to have both ``.`` and ``addons`` on the
499        ``PYTHONPATH``.
500
501        The solution is to set the ``PYTHONPATH`` to ``.`` on start,
502        and the ``start-directory`` to ``addons``. This results in a
503        correct ``PYTHONPATH`` within ``unit2``.
504
505 .. _unittest:
506     http://docs.python.org/library/unittest.html
507
508 .. _unittest2:
509     http://www.voidspace.org.uk/python/articles/unittest2.shtml
510
511 .. _autodiscovery:
512     http://www.voidspace.org.uk/python/articles/unittest2.shtml#test-discovery
513
514 .. _pluggable test discovery:
515     http://www.voidspace.org.uk/python/articles/unittest2.shtml#load-tests
516
517 .. _new and improved assertions:
518     http://www.voidspace.org.uk/python/articles/unittest2.shtml#new-assert-methods
519
520 .. _pluggable custom types equality assertions:
521     http://www.voidspace.org.uk/python/articles/unittest2.shtml#add-new-type-specific-functions
522
523 .. _assertRaises context manager:
524     http://www.voidspace.org.uk/python/articles/unittest2.shtml#assertraises
525
526 .. _cleanup function registration:
527     http://www.voidspace.org.uk/python/articles/unittest2.shtml#cleanup-functions-with-addcleanup
528
529 .. _test skipping:
530     http://www.voidspace.org.uk/python/articles/unittest2.shtml#test-skipping
531
532 .. _class- and module-level setup and teardown:
533     http://www.voidspace.org.uk/python/articles/unittest2.shtml#class-and-module-level-fixtures
534
535 .. _Semantic Versioning:
536     http://semver.org/
537
538 .. _nose:
539     http://somethingaboutorange.com/mrl/projects/nose/1.0.0/
540
541 .. _promise object:
542     http://api.jquery.com/deferred.promise/
543
544 .. _.appendTo():
545     http://api.jquery.com/appendTo/
546
547 .. _.prependTo():
548     http://api.jquery.com/prependTo/
549
550 .. _.insertAfter():
551     http://api.jquery.com/insertAfter/
552
553 .. _.insertBefore():
554     http://api.jquery.com/insertBefore/
555
556 .. _Rosetta:
557 .. _Launchpad's own translation tool:
558     https://help.launchpad.net/Translations