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 = 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');
119 * All javascript objects inheriting from
120 :js:class:`openerp.base.BasicConroller` will have all methods
121 starting with ``on_`` or ``do_`` bound to their ``this``. This means
122 they don't have to be manually bound (via ``_.bind`` or ``$.proxy``)
123 in order to be useable as bound event handlers (event handlers
124 keeping their object as ``this`` rather than taking whatever
125 ``this`` object they were called with).
127 Beware that this is only valid for methods starting with ``do_`` and
128 ``on_``, any other method will have to be bound manually.
138 OpenERP Web uses unittest2_ for its testing needs. We selected
139 unittest2 rather than unittest_ for the following reasons:
141 * autodiscovery_ (similar to nose, via the ``unit2``
142 CLI utility) and `pluggable test discovery`_.
144 * `new and improved assertions`_ (with improvements in type-specific
145 inequality reportings) including `pluggable custom types equality
148 * neveral new APIs, most notably `assertRaises context manager`_,
149 `cleanup function registration`_, `test skipping`_ and `class- and
150 module-level setup and teardown`_
152 * finally, unittest2 is a backport of Python 3's unittest. We might as
155 To run tests on addons (from the root directory of OpenERP Web) is as
156 simple as typing ``PYTHONPATH=. unit2 discover -s addons`` [#]_. To
157 test an addon which does not live in the ``addons`` directory, simply
158 replace ``addons`` by the directory in which your own addon lives.
160 .. note:: unittest2 is entirely compatible with nose_ (or the
161 other way around). If you want to use nose as your test
162 runner (due to its addons for instance) you can simply install it
163 and run ``nosetests addons`` instead of the ``unit2`` command,
164 the result should be exactly the same.
172 .. js:class:: openerp.base.Widget(view, node)
174 :param openerp.base.Controller view: The view to which the widget belongs
175 :param Object node: the ``fields_view_get`` descriptor for the widget
177 .. js:attribute:: $element
179 The widget's root element as jQuery object
181 .. js:class:: openerp.base.DataSet(session, model)
183 :param openerp.base.Session session: the RPC session object
184 :param String model: the model managed by this dataset
186 The DataSet is the abstraction for a sequence of records stored in
189 It provides interfaces for reading records based on search
190 criteria, and for selecting and fetching records based on
193 .. js:function:: fetch([offset][, limit])
195 :param Number offset: the index from which records should start
196 being returned (section)
197 :param Number limit: the maximum number of records to return
198 :returns: the dataset instance it was called on
200 Asynchronously fetches the records selected by the DataSet's
201 domain and context, in the provided sort order if any.
203 Only fetches the fields selected by the DataSet.
205 On success, triggers :js:func:`on_fetch`
207 .. js:function:: on_fetch(records, event)
209 :param Array records: an array of
210 :js:class:`openerp.base.DataRecord`
211 matching the DataSet's selection
212 :param event: a data holder letting the event handler fetch
213 meta-informations about the event.
214 :type event: OnFetchEvent
216 Fired after :js:func:`fetch` is done fetching the records
217 selected by the DataSet.
219 .. js:function:: active_ids
221 :returns: the dataset instance it was called on
223 Asynchronously fetches the active records for this DataSet.
225 On success, triggers :js:func:`on_active_ids`
227 .. js:function:: on_active_ids(records)
229 :param Array records: an array of
230 :js:class:`openerp.base.DataRecord`
231 matching the currently active ids
233 Fired after :js:func:`active_ids` fetched the records matching
234 the DataSet's active ids.
236 .. js:function:: active_id
238 :returns: the dataset instance in was called on
240 Asynchronously fetches the current active record.
242 On success, triggers :js:func:`on_active_id`
244 .. js:function:: on_active_id(record)
246 :param Object record: the record fetched by
247 :js:func:`active_id`, or ``null``
248 :type record: openerp.base.DataRecord
250 Fired after :js:func:`active_id` fetched the record matching
251 the dataset's active id
253 .. js:function:: set(options)
255 :param Object options: the options to set on the dataset
256 :type options: DataSetOptions
257 :returns: the dataset instance it was called on
259 Configures the data set by setting various properties on it
261 .. js:function:: prev
263 :returns: the dataset instance it was called on
265 Activates the id preceding the current one in the active ids
266 sequence of the dataset.
268 If the current active id is at the start of the sequence,
269 wraps back to the last id of the sequence.
271 .. js:function:: next
273 :returns: the dataset instance it was called on
275 Activates the id following the current one in the active ids
278 If the current active id is the last of the sequence, wraps
279 back to the beginning of the active ids sequence.
281 .. js:function:: select(ids)
283 :param Array ids: the identifiers to activate on the dataset
284 :returns: the dataset instance it was called on
286 Activates all the ids specified in the dataset, resets the
287 current active id to be the first id of the new sequence.
289 The internal order will be the same as the ids list provided.
291 .. js:function:: get_active_ids
293 :returns: the list of current active ids for the dataset
295 .. js:function:: activate(id)
297 :param Number id: the id to activate
298 :returns: the dataset instance it was called on
300 Activates the id provided in the dataset. If no ids are
301 selected, selects the id in the dataset.
303 If ids are already selected and the provided id is not in that
304 selection, raises an error.
306 .. js:function:: get_active_id
308 :returns: the dataset's current active id
310 Ad-hoc objects and structural types
311 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
313 These objects are not associated with any specific class, they're
314 generally literal objects created on the spot. Names are merely
315 convenient ways to refer to them and their properties.
317 .. js:class:: OnFetchEvent
319 .. js:attribute:: context
321 The context used for the :js:func:`fetch` call (domain set on
322 the :js:class:`openerp.base.DataSet` when ``fetch`` was
325 .. js:attribute:: domain
327 The domain used for the :js:func:`fetch` call
329 .. js:attribute:: limit
331 The limit with which the original :js:func:`fetch` call was
334 .. js:attribute:: offset
336 The offset with which the original :js:func:`fetch` call was
339 .. js:attribute:: sort
341 The sorting criteria active on the
342 :js:class:`openerp.base.DataSet` when :js:func:`fetch` was
345 .. js:class:: DataSetOptions
347 .. js:attribute:: context
349 .. js:attribute:: domain
351 .. js:attribute:: sort
356 .. autoclass:: openerpweb.openerpweb.OpenERPSession
359 .. autoclass:: openerpweb.openerpweb.OpenERPModel
362 * Addons lifecycle (loading, execution, events, ...)
367 * Handling static files
368 * Overridding a Python controller (object?)
369 * Overridding a Javascript controller (object?)
370 * Extending templates
371 .. how do you handle deploying static files via e.g. a separate lighttpd?
373 * QWeb templates description?
374 * OpenERP Web modules (from OpenERP modules)
376 .. [#] the ``-s`` parameter tells ``unit2`` to start trying to
377 find tests in the provided directory (here we're testing
378 addons). However a side-effect of that is to set the
379 ``PYTHONPATH`` there as well, so it will fail to find (and
380 import) ``openerpweb``.
382 The ``-t`` parameter lets us set the ``PYTHONPATH``
383 independently, but it doesn't accept multiple values and here
384 we really want to have both ``.`` and ``addons`` on the
387 The solution is to set the ``PYTHONPATH`` to ``.`` on start,
388 and the ``start-directory`` to ``addons``. This results in a
389 correct ``PYTHONPATH`` within ``unit2``.
392 http://docs.python.org/library/unittest.html
395 http://www.voidspace.org.uk/python/articles/unittest2.shtml
398 http://www.voidspace.org.uk/python/articles/unittest2.shtml#test-discovery
400 .. _pluggable test discovery:
401 http://www.voidspace.org.uk/python/articles/unittest2.shtml#load-tests
403 .. _new and improved assertions:
404 http://www.voidspace.org.uk/python/articles/unittest2.shtml#new-assert-methods
406 .. _pluggable custom types equality assertions:
407 http://www.voidspace.org.uk/python/articles/unittest2.shtml#add-new-type-specific-functions
409 .. _assertRaises context manager:
410 http://www.voidspace.org.uk/python/articles/unittest2.shtml#assertraises
412 .. _cleanup function registration:
413 http://www.voidspace.org.uk/python/articles/unittest2.shtml#cleanup-functions-with-addcleanup
416 http://www.voidspace.org.uk/python/articles/unittest2.shtml#test-skipping
418 .. _class- and module-level setup and teardown:
419 http://www.voidspace.org.uk/python/articles/unittest2.shtml#class-and-module-level-fixtures
421 .. _Semantic Versioning:
425 http://somethingaboutorange.com/mrl/projects/nose/1.0.0/