1 Developing OpenERP Web Addons
2 =============================
4 An OpenERP Web addon is simply a Python package with an openerp
5 descriptor (a ``__openerp__.py`` file) which follows a few structural
11 .. literalinclude:: addon-structure.txt
14 The addon's descriptor, contains the following information:
17 The addon name, in plain, readable english
19 The addon version, following `Semantic Versioning`_ rules
21 A list of addons this addon needs to work correctly. ``base`` is
22 an implied dependency if the list is empty.
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.
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.
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).
39 All of the Python controllers and JSON-RPC endpoints.
42 The static files directory, may be served via a separate web server.
45 Third-party libraries used by the addon.
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
52 Javascript tests files
55 The directories in which all tests for the addon are located.
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.
67 Because addons are also Python packages, they're inherently namespaced
68 and nothing special needs to be done on that front.
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``.
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.
82 The root function will be called by the OpenERP Web client when
83 initializing the addon.
85 .. code-block:: javascript
87 // root namespace of the openerp.example addon
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# */{
94 * Description for SomeClass's constructor here
99 // SomeClass initialization code
104 // access an object in an other addon namespace to replace it
105 openerp.base.SearchView = openerp.base.SearchView.extend({
107 this._super.apply(this, arguments);
108 console.log('Search view initialized');
113 Creating new standard roles
114 ---------------------------
119 This is the base class for all visual components. It provides a number of
120 services for the management of a DOM subtree:
122 * Rendering with QWeb
124 * Parenting-child relations
126 * Life-cycle management (including facilitating children destruction when a
127 parent object is removed)
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):
133 :js:func:`~openerp.base.Widget.appendTo`
134 Renders the widget and inserts it as the last child of the target, uses
137 :js:func:`~openerp.base.Widget.prependTo`
138 Renders the widget and inserts it as the first child of the target, uses
141 :js:func:`~openerp.base.Widget.insertAfter`
142 Renders the widget and inserts it as the preceding sibling of the target,
143 uses `.insertAfter()`_
145 :js:func:`~openerp.base.Widget.insertBefore`
146 Renders the widget and inserts it as the following sibling of the target,
147 uses `.insertBefore()`_
149 :js:class:`~openerp.base.Widget` inherits from
150 :js:class:`~openerp.base.SessionAware`, so subclasses can easily access the
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:
161 .. code-block:: javascript
163 var MyWidget = openerp.base.Widget.extend({
164 // QWeb template to use when rendering the object
165 template: "MyQWebTemplate",
167 init: function(parent) {
169 // insert code to execute before rendering, for object
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 * /);
178 // if ``start`` is asynchronous, return a promise object so callers
179 // know when the object is done initializing
180 return this.rpc(/* … */)
184 The new class can then be used in the following manner:
186 .. code-block:: javascript
188 // Create the instance
189 var my_widget = new MyWidget(this);
190 // Render and insert into DOM
191 my_widget.appendTo(".some-div");
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.
196 .. note:: the insertion methods will start the widget themselves, and will
197 return the result of :js:func:`~openerp.base.Widget.start()`.
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.
203 If the widget is not needed anymore (because it's transient), simply terminate
206 .. code-block:: javascript
210 will unbind all DOM events, remove the widget's content from the DOM and
211 destroy all widget data.
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).
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.
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
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``.
234 The view name is the name you gave to your new view in the OpenERP server.
236 From that point onwards, OpenERP Web will be able to find your object and
239 Standard view behaviors
240 ~~~~~~~~~~~~~~~~~~~~~~~
242 In the normal OpenERP Web flow, views have to implement a number of methods so
243 view managers can correctly communicate with them:
246 This method will always be called after creating the view (via its
247 constructor), but not necessarily immediately.
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).
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.
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.
261 Should put the view in some sort of hibernation mode, and *must* hide its
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
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
273 It should use the provided query data to perform a search and refresh its
274 internal content (and display).
276 All views are searchable by default, but they can be made non-searchable
277 by setting the property ``searchable`` to ``false``.
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.
283 Frequent development tasks
284 --------------------------
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:
294 guides/sidebar-protocol
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
303 Making strings translatable
304 +++++++++++++++++++++++++++
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
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:
321 .. code-block:: javascript
323 var _t = openerp.web._t,
324 _tl = openerp.web._tl;
326 importing those functions under any other name is not guaranteed to work.
328 .. note:: only import them if necessary, and only the necessary one(s), no need
329 to clutter your module's namespace for nothing
331 .. js:function:: openerp.web._t(s)
333 Base translation function, eager, works much like :manpage:`gettext(3)`
338 .. js:function:: openerp.web._lt(s)
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.
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
351 Text formatting & translations
352 """"""""""""""""""""""""""""""
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/>`_.
360 As much as possible, you should use the "named argument" form of sprintf:
362 .. code-block:: javascript
364 var translated_string = _.str.sprintf(
365 _t("[%(first_record)d to %(last_record)d] of %(records_count)d"), {
366 first_record: first + 1,
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).
375 Named arguments are specified using the following pattern: ``%($name)$type``
379 the name of the argument, this is the key in the object/dictionary provided
380 as second parameter to ``sprintf``
382 a type/format specifier, `see the list for all possible types
383 <http://www.diveintojavascript.com/projects/javascript-sprintf>`_.
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.
389 .. warning:: you should *never* use string concatenation as it robs the
390 translator of context and make result in a completely incorrect
396 .. program:: gen_translations.sh
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.
402 This can be done via the provided :program:`gen_translations.sh`.
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
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
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).
428 Beware that this is only valid for methods starting with ``do_`` and
429 ``on_``, any other method will have to be bound manually.
439 OpenERP Web uses unittest2_ for its testing needs. We selected
440 unittest2 rather than unittest_ for the following reasons:
442 * autodiscovery_ (similar to nose, via the ``unit2``
443 CLI utility) and `pluggable test discovery`_.
445 * `new and improved assertions`_ (with improvements in type-specific
446 inequality reportings) including `pluggable custom types equality
449 * neveral new APIs, most notably `assertRaises context manager`_,
450 `cleanup function registration`_, `test skipping`_ and `class- and
451 module-level setup and teardown`_
453 * finally, unittest2 is a backport of Python 3's unittest. We might as
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.
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.
470 .. autoclass:: web.common.session.OpenERPSession
473 .. autoclass:: web.common.openerplib.main.Model
476 * Addons lifecycle (loading, execution, events, ...)
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?
487 * QWeb templates description?
488 * OpenERP Web modules (from OpenERP modules)
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``.
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
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``.
506 http://docs.python.org/library/unittest.html
509 http://www.voidspace.org.uk/python/articles/unittest2.shtml
512 http://www.voidspace.org.uk/python/articles/unittest2.shtml#test-discovery
514 .. _pluggable test discovery:
515 http://www.voidspace.org.uk/python/articles/unittest2.shtml#load-tests
517 .. _new and improved assertions:
518 http://www.voidspace.org.uk/python/articles/unittest2.shtml#new-assert-methods
520 .. _pluggable custom types equality assertions:
521 http://www.voidspace.org.uk/python/articles/unittest2.shtml#add-new-type-specific-functions
523 .. _assertRaises context manager:
524 http://www.voidspace.org.uk/python/articles/unittest2.shtml#assertraises
526 .. _cleanup function registration:
527 http://www.voidspace.org.uk/python/articles/unittest2.shtml#cleanup-functions-with-addcleanup
530 http://www.voidspace.org.uk/python/articles/unittest2.shtml#test-skipping
532 .. _class- and module-level setup and teardown:
533 http://www.voidspace.org.uk/python/articles/unittest2.shtml#class-and-module-level-fixtures
535 .. _Semantic Versioning:
539 http://somethingaboutorange.com/mrl/projects/nose/1.0.0/
542 http://api.jquery.com/deferred.promise/
545 http://api.jquery.com/appendTo/
548 http://api.jquery.com/prependTo/
551 http://api.jquery.com/insertAfter/
554 http://api.jquery.com/insertBefore/
557 .. _Launchpad's own translation tool:
558 https://help.launchpad.net/Translations