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 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).
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.
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
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``.
137 The view name is the name you gave to your new view in the OpenERP server.
139 From that point onwards, OpenERP Web will be able to find your object and
142 Standard view behaviors
143 ~~~~~~~~~~~~~~~~~~~~~~~
145 In the normal OpenERP Web flow, views have to implement a number of methods so
146 view managers can correctly communicate with them:
149 This method will always be called after creating the view (via its
150 constructor), but not necessarily immediately.
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).
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.
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.
164 Should put the view in some sort of hibernation mode, and *must* hide its
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
172 ``do_search(domain: Array, context: Object, group_by: Array)``
173 If the view is searchable, this method is called to notify it of a search
176 It should use the provided query data to perform a search and refresh its
177 internal content (and display).
179 All views are searchable by default, but they can be made non-searchable
180 by setting the property ``searchable`` to ``false``.
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.
186 Frequent development tasks
187 --------------------------
189 There are a number of tasks which OpenERP Web developers do or will need to
190 perform quite regularly. To make these easier, we have written a few guides
191 to help you get started:
197 guides/sidebar-protocol
202 OpenERP Web should provide most of the tools needed to correctly translate your
203 addons via the tool of your choice (OpenERP itself uses `Launchpad's own
206 Making strings translatable
207 +++++++++++++++++++++++++++
212 QWeb automatically marks all text nodes (any text which is not in an XML
213 attribute and not part of an XML tag) as translatable, and handles the
214 replacement for you. There is nothing special to do to mark template text as
220 OpenERP Web provides two functions to translate human-readable strings in
221 javascript code. These functions should be "imported" in your module by
222 aliasing them to their bare name:
224 .. code-block:: javascript
226 var _t = openerp.web._t,
227 _tl = openerp.web._tl;
229 importing those functions under any other name is not guaranteed to work.
231 .. note:: only import them if necessary, and only the necessary one(s), no need
232 to clutter your module's namespace for nothing
234 .. js:function:: openerp.web._t(s)
236 Base translation function, eager, works much like :manpage:`gettext(3)`
241 .. js:function:: openerp.web._lt(s)
243 Lazy equivalent to :js:func:`~openerp.web._t`, this function will postpone
244 fetching the translation to its argument until the last possible moment.
246 To use in contexts evaluated before the translation database can be
247 fetched, usually your module's toplevel and the attributes of classes
248 defined in it (class attributes, not instance attributes set in the
254 Text formatting & translations
255 """"""""""""""""""""""""""""""
257 A difficulty when translating is integrating data (from the code) into the
258 translated string. In OpenERP Web addons, this should be done by wrapping the
259 text to translate in an :manpage:`sprintf(3)` call. For OpenERP Web,
260 :manpage:`sprintf(3)` is provided by `underscore.string
261 <http://epeli.github.com/underscore.string/>`_.
263 As much as possible, you should use the "named argument" form of sprintf:
265 .. code-block:: javascript
267 var translated_string = _.str.sprintf(
268 _t("[%(first_record)d to %(last_record)d] of %(records_count)d"), {
269 first_record: first + 1,
274 named arguments make the string to translate much clearer for translators, and
275 allows them to "move" sections around based on the requirements of their
276 language (not all language order text like english).
278 Named arguments are specified using the following pattern: ``%($name)$type``
282 the name of the argument, this is the key in the object/dictionary provided
283 as second parameter to ``sprintf``
285 a type/format specifier, `see the list for all possible types
286 <http://www.diveintojavascript.com/projects/javascript-sprintf>`_.
288 .. note:: positional arguments are acceptable if the translated string has
289 *a single* argument and its content is easy to guess from the text
290 around it. Named arguments should still be preferred.
292 .. warning:: you should *never* use string concatenation as it robs the
293 translator of context and make result in a completely incorrect
299 .. program:: gen_translations.sh
301 Once strings have been marked for translation, they need to be extracted into
302 :abbr:`POT (Portable Object Template)` files, from which most translation tools
303 can build a database.
305 This can be done via the provided :program:`gen_translations.sh`.
307 It can be called either as :option:`gen_translations.sh -a` or by providing
308 two parameters, a path to the addons and the complete path in which to put the
313 Extracts translations from all standard OpenERP Web addons (addons bundled
314 with OpenERP Web itself) and puts the extracted templates into the right
315 directory for `Rosetta`_ to handle them
323 * All javascript objects inheriting from
324 :js:class:`openerp.base.BasicConroller` will have all methods
325 starting with ``on_`` or ``do_`` bound to their ``this``. This means
326 they don't have to be manually bound (via ``_.bind`` or ``$.proxy``)
327 in order to be useable as bound event handlers (event handlers
328 keeping their object as ``this`` rather than taking whatever
329 ``this`` object they were called with).
331 Beware that this is only valid for methods starting with ``do_`` and
332 ``on_``, any other method will have to be bound manually.
342 OpenERP Web uses unittest2_ for its testing needs. We selected
343 unittest2 rather than unittest_ for the following reasons:
345 * autodiscovery_ (similar to nose, via the ``unit2``
346 CLI utility) and `pluggable test discovery`_.
348 * `new and improved assertions`_ (with improvements in type-specific
349 inequality reportings) including `pluggable custom types equality
352 * neveral new APIs, most notably `assertRaises context manager`_,
353 `cleanup function registration`_, `test skipping`_ and `class- and
354 module-level setup and teardown`_
356 * finally, unittest2 is a backport of Python 3's unittest. We might as
359 To run tests on addons (from the root directory of OpenERP Web) is as
360 simple as typing ``PYTHONPATH=. unit2 discover -s addons`` [#]_. To
361 test an addon which does not live in the ``addons`` directory, simply
362 replace ``addons`` by the directory in which your own addon lives.
364 .. note:: unittest2 is entirely compatible with nose_ (or the
365 other way around). If you want to use nose as your test
366 runner (due to its addons for instance) you can simply install it
367 and run ``nosetests addons`` instead of the ``unit2`` command,
368 the result should be exactly the same.
373 .. autoclass:: web.common.session.OpenERPSession
376 .. autoclass:: web.common.openerplib.main.Model
379 * Addons lifecycle (loading, execution, events, ...)
384 * Handling static files
385 * Overridding a Python controller (object?)
386 * Overridding a Javascript controller (object?)
387 * Extending templates
388 .. how do you handle deploying static files via e.g. a separate lighttpd?
390 * QWeb templates description?
391 * OpenERP Web modules (from OpenERP modules)
393 .. [#] the ``-s`` parameter tells ``unit2`` to start trying to
394 find tests in the provided directory (here we're testing
395 addons). However a side-effect of that is to set the
396 ``PYTHONPATH`` there as well, so it will fail to find (and
397 import) ``openerpweb``.
399 The ``-t`` parameter lets us set the ``PYTHONPATH``
400 independently, but it doesn't accept multiple values and here
401 we really want to have both ``.`` and ``addons`` on the
404 The solution is to set the ``PYTHONPATH`` to ``.`` on start,
405 and the ``start-directory`` to ``addons``. This results in a
406 correct ``PYTHONPATH`` within ``unit2``.
409 http://docs.python.org/library/unittest.html
412 http://www.voidspace.org.uk/python/articles/unittest2.shtml
415 http://www.voidspace.org.uk/python/articles/unittest2.shtml#test-discovery
417 .. _pluggable test discovery:
418 http://www.voidspace.org.uk/python/articles/unittest2.shtml#load-tests
420 .. _new and improved assertions:
421 http://www.voidspace.org.uk/python/articles/unittest2.shtml#new-assert-methods
423 .. _pluggable custom types equality assertions:
424 http://www.voidspace.org.uk/python/articles/unittest2.shtml#add-new-type-specific-functions
426 .. _assertRaises context manager:
427 http://www.voidspace.org.uk/python/articles/unittest2.shtml#assertraises
429 .. _cleanup function registration:
430 http://www.voidspace.org.uk/python/articles/unittest2.shtml#cleanup-functions-with-addcleanup
433 http://www.voidspace.org.uk/python/articles/unittest2.shtml#test-skipping
435 .. _class- and module-level setup and teardown:
436 http://www.voidspace.org.uk/python/articles/unittest2.shtml#class-and-module-level-fixtures
438 .. _Semantic Versioning:
442 http://somethingaboutorange.com/mrl/projects/nose/1.0.0/
445 http://api.jquery.com/deferred.promise/
448 .. _Launchpad's own translation tool:
449 https://help.launchpad.net/Translations