[IMP]project_issue: Improved in fields
[odoo/odoo.git] / doc / source / 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 Views
117 +++++
118
119 Views are the standard high-level component in OpenERP. A view type corresponds
120 to a way to display a set of data (coming from an OpenERP model).
121
122 In OpenERP Web, views are standard objects registered against a dedicated
123 object registry, so the :js:class:`~openerp.base.ViewManager` knows where to
124 find and how to call them.
125
126 Although not mandatory, it is recommended that views inherit from
127 :js:class:`openerp.base.View`, which provides a view useful services to its
128 children.
129
130 Registering a view
131 ~~~~~~~~~~~~~~~~~~
132
133 This is the first task to perform when creating a view, and the simplest by
134 far: simply call ``openerp.base.views.add(name, object_path)`` to register
135 the object of path ``object_path`` as the view for the view name ``name``.
136
137 The view name is the name you gave to your new view in the OpenERP server.
138
139 From that point onwards, OpenERP Web will be able to find your object and
140 instantiate it.
141
142 Standard view behaviors
143 ~~~~~~~~~~~~~~~~~~~~~~~
144
145 In the normal OpenERP Web flow, views have to implement a number of methods so
146 view managers can correctly communicate with them:
147
148 ``start()``
149     This method will always be called after creating the view (via its
150     constructor), but not necessarily immediately.
151
152     It is called with no arguments and should handle the heavy setup work,
153     including remote call (to load the view's setup data from the server via
154     e.g. ``fields_view_get``, for instance).
155
156     ``start`` should return a `promise object`_ which *must* be resolved when
157     the view's setup is completed. This promise is used by view managers to
158     know when they can start interacting with the view.
159
160 ``do_hide()``
161     Called by the view manager when it wants to replace this view by an other
162     one, but wants to keep this view around to re-activate it later.
163
164     Should put the view in some sort of hibernation mode, and *must* hide its
165     DOM elements.
166
167 ``do_show()``
168     Called when the view manager wants to re-display the view after having
169     hidden it. The view should refresh its data display upon receiving this
170     notification
171
172 ``do_search(domains: Array, contexts: Array, groupbys: Array)``
173     If the view is searchable, this method is called to notify it of a search
174     against it.
175
176     It should use the provided query data to perform a search and refresh its
177     internal content (and display).
178
179     All views are searchable by default, but they can be made non-searchable
180     by setting the property ``searchable`` to ``false``.
181
182     This can be done either on the view class itself (at the same level as
183     defining e.g. the ``start`` method) or at the instance level (in the
184     class's ``init``), though you should generally set it on the class.
185
186 Utility behaviors
187 -----------------
188
189 JavaScript
190 ++++++++++
191
192 * All javascript objects inheriting from
193   :js:class:`openerp.base.BasicConroller` will have all methods
194   starting with ``on_`` or ``do_`` bound to their ``this``. This means
195   they don't have to be manually bound (via ``_.bind`` or ``$.proxy``)
196   in order to be useable as bound event handlers (event handlers
197   keeping their object as ``this`` rather than taking whatever
198   ``this`` object they were called with).
199
200   Beware that this is only valid for methods starting with ``do_`` and
201   ``on_``, any other method will have to be bound manually.
202
203 .. _addons-testing:
204
205 Testing
206 -------
207
208 Python
209 ++++++
210
211 OpenERP Web uses unittest2_ for its testing needs. We selected
212 unittest2 rather than unittest_ for the following reasons:
213
214 * autodiscovery_ (similar to nose, via the ``unit2``
215   CLI utility) and `pluggable test discovery`_.
216
217 * `new and improved assertions`_ (with improvements in type-specific
218   inequality reportings) including `pluggable custom types equality
219   assertions`_
220
221 * neveral new APIs, most notably `assertRaises context manager`_,
222   `cleanup function registration`_, `test skipping`_ and `class- and
223   module-level setup and teardown`_
224
225 * finally, unittest2 is a backport of Python 3's unittest. We might as
226   well get used to it.
227
228 To run tests on addons (from the root directory of OpenERP Web) is as
229 simple as typing ``PYTHONPATH=. unit2 discover -s addons`` [#]_. To
230 test an addon which does not live in the ``addons`` directory, simply
231 replace ``addons`` by the directory in which your own addon lives.
232
233 .. note:: unittest2 is entirely compatible with nose_ (or the
234      other way around). If you want to use nose as your test
235      runner (due to its addons for instance) you can simply install it
236      and run ``nosetests addons`` instead of the ``unit2`` command,
237      the result should be exactly the same.
238
239 APIs
240 ----
241
242 Javascript
243 ++++++++++
244
245 .. js:class:: openerp.base.Widget(view, node)
246
247     :param openerp.base.Controller view: The view to which the widget belongs
248     :param Object node: the ``fields_view_get`` descriptor for the widget
249
250     .. js:attribute:: $element
251
252         The widget's root element as jQuery object
253
254 .. js:class:: openerp.base.DataSet(session, model)
255
256     :param openerp.base.Session session: the RPC session object
257     :param String model: the model managed by this dataset
258
259     The DataSet is the abstraction for a sequence of records stored in
260     database.
261
262     It provides interfaces for reading records based on search
263     criteria, and for selecting and fetching records based on
264     activated ids.
265
266     .. js:function:: fetch([offset][, limit])
267
268        :param Number offset: the index from which records should start
269                              being returned (section)
270        :param Number limit: the maximum number of records to return
271        :returns: the dataset instance it was called on
272
273        Asynchronously fetches the records selected by the DataSet's
274        domain and context, in the provided sort order if any.
275
276        Only fetches the fields selected by the DataSet.
277
278        On success, triggers :js:func:`on_fetch`
279
280     .. js:function:: on_fetch(records, event)
281
282         :param Array records: an array of
283                              :js:class:`openerp.base.DataRecord`
284                              matching the DataSet's selection
285         :param event: a data holder letting the event handler fetch
286                      meta-informations about the event.
287         :type event: OnFetchEvent
288
289         Fired after :js:func:`fetch` is done fetching the records
290         selected by the DataSet.
291
292     .. js:function:: active_ids
293
294         :returns: the dataset instance it was called on
295
296         Asynchronously fetches the active records for this DataSet.
297
298         On success, triggers :js:func:`on_active_ids`
299
300     .. js:function:: on_active_ids(records)
301
302         :param Array records: an array of
303                               :js:class:`openerp.base.DataRecord`
304                               matching the currently active ids
305
306         Fired after :js:func:`active_ids` fetched the records matching
307         the DataSet's active ids.
308
309     .. js:function:: active_id
310
311         :returns: the dataset instance in was called on
312
313         Asynchronously fetches the current active record.
314
315         On success, triggers :js:func:`on_active_id`
316
317     .. js:function:: on_active_id(record)
318
319         :param Object record: the record fetched by
320                               :js:func:`active_id`, or ``null``
321         :type record: openerp.base.DataRecord
322
323         Fired after :js:func:`active_id` fetched the record matching
324         the dataset's active id
325
326     .. js:function:: set(options)
327
328         :param Object options: the options to set on the dataset
329         :type options: DataSetOptions
330         :returns: the dataset instance it was called on
331
332         Configures the data set by setting various properties on it
333
334     .. js:function:: prev
335
336         :returns: the dataset instance it was called on
337
338         Activates the id preceding the current one in the active ids
339         sequence of the dataset.
340
341         If the current active id is at the start of the sequence,
342         wraps back to the last id of the sequence.
343
344     .. js:function:: next
345
346         :returns: the dataset instance it was called on
347
348         Activates the id following the current one in the active ids
349         sequence.
350
351         If the current active id is the last of the sequence, wraps
352         back to the beginning of the active ids sequence.
353
354     .. js:function:: select(ids)
355
356         :param Array ids: the identifiers to activate on the dataset
357         :returns: the dataset instance it was called on
358
359         Activates all the ids specified in the dataset, resets the
360         current active id to be the first id of the new sequence.
361
362         The internal order will be the same as the ids list provided.
363
364     .. js:function:: get_active_ids
365
366         :returns: the list of current active ids for the dataset
367
368     .. js:function:: activate(id)
369
370         :param Number id: the id to activate
371         :returns: the dataset instance it was called on
372
373         Activates the id provided in the dataset. If no ids are
374         selected, selects the id in the dataset.
375
376         If ids are already selected and the provided id is not in that
377         selection, raises an error.
378
379     .. js:function:: get_active_id
380
381         :returns: the dataset's current active id
382
383 Ad-hoc objects and structural types
384 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
385
386 These objects are not associated with any specific class, they're
387 generally literal objects created on the spot. Names are merely
388 convenient ways to refer to them and their properties.
389
390 .. js:class:: OnFetchEvent
391
392     .. js:attribute:: context
393
394         The context used for the :js:func:`fetch` call (domain set on
395         the :js:class:`openerp.base.DataSet` when ``fetch`` was
396         called)
397
398     .. js:attribute:: domain
399
400         The domain used for the :js:func:`fetch` call
401
402     .. js:attribute:: limit
403
404         The limit with which the original :js:func:`fetch` call was
405         performed
406
407     .. js:attribute:: offset
408
409         The offset with which the original :js:func:`fetch` call was
410         performed
411
412     .. js:attribute:: sort
413
414        The sorting criteria active on the
415        :js:class:`openerp.base.DataSet` when :js:func:`fetch` was
416        called
417
418 .. js:class:: DataSetOptions
419
420     .. js:attribute:: context
421
422     .. js:attribute:: domain
423
424     .. js:attribute:: sort
425
426 Python
427 ++++++
428
429 .. autoclass:: openerpweb.openerpweb.OpenERPSession
430     :members:
431
432 .. autoclass:: openerpweb.openerpweb.OpenERPModel
433     :members:
434
435 * Addons lifecycle (loading, execution, events, ...)
436
437   * Python-side
438   * JS-side
439
440 * Handling static files
441 * Overridding a Python controller (object?)
442 * Overridding a Javascript controller (object?)
443 * Extending templates
444   .. how do you handle deploying static files via e.g. a separate lighttpd?
445 * Python public APIs
446 * QWeb templates description?
447 * OpenERP Web modules (from OpenERP modules)
448
449 .. [#] the ``-s`` parameter tells ``unit2`` to start trying to
450        find tests in the provided directory (here we're testing
451        addons). However a side-effect of that is to set the
452        ``PYTHONPATH`` there as well, so it will fail to find (and
453        import) ``openerpweb``.
454
455        The ``-t`` parameter lets us set the ``PYTHONPATH``
456        independently, but it doesn't accept multiple values and here
457        we really want to have both ``.`` and ``addons`` on the
458        ``PYTHONPATH``.
459
460        The solution is to set the ``PYTHONPATH`` to ``.`` on start,
461        and the ``start-directory`` to ``addons``. This results in a
462        correct ``PYTHONPATH`` within ``unit2``.
463
464 .. _unittest:
465     http://docs.python.org/library/unittest.html
466
467 .. _unittest2:
468     http://www.voidspace.org.uk/python/articles/unittest2.shtml
469
470 .. _autodiscovery:
471     http://www.voidspace.org.uk/python/articles/unittest2.shtml#test-discovery
472
473 .. _pluggable test discovery:
474     http://www.voidspace.org.uk/python/articles/unittest2.shtml#load-tests
475
476 .. _new and improved assertions:
477     http://www.voidspace.org.uk/python/articles/unittest2.shtml#new-assert-methods
478
479 .. _pluggable custom types equality assertions:
480     http://www.voidspace.org.uk/python/articles/unittest2.shtml#add-new-type-specific-functions
481
482 .. _assertRaises context manager:
483     http://www.voidspace.org.uk/python/articles/unittest2.shtml#assertraises
484
485 .. _cleanup function registration:
486     http://www.voidspace.org.uk/python/articles/unittest2.shtml#cleanup-functions-with-addcleanup
487
488 .. _test skipping:
489     http://www.voidspace.org.uk/python/articles/unittest2.shtml#test-skipping
490
491 .. _class- and module-level setup and teardown:
492     http://www.voidspace.org.uk/python/articles/unittest2.shtml#class-and-module-level-fixtures
493
494 .. _Semantic Versioning:
495     http://semver.org/
496
497 .. _nose:
498     http://somethingaboutorange.com/mrl/projects/nose/1.0.0/
499
500 .. _promise object:
501     http://api.jquery.com/deferred.promise/