[REM] old doc
bzr revid: xmo@openerp.com-20121002134604-w6xbkg7sqitd96db
This commit is contained in:
parent
c28dfb19e1
commit
c8dead4f60
449
doc/addons.rst
449
doc/addons.rst
|
@ -1,449 +0,0 @@
|
||||||
Developing OpenERP Web Addons
|
|
||||||
=============================
|
|
||||||
|
|
||||||
An OpenERP Web addon is simply a Python package with an openerp
|
|
||||||
descriptor (a ``__openerp__.py`` file) which follows a few structural
|
|
||||||
and namespacing rules.
|
|
||||||
|
|
||||||
Structure
|
|
||||||
---------
|
|
||||||
|
|
||||||
.. literalinclude:: addon-structure.txt
|
|
||||||
|
|
||||||
``__openerp__.py``
|
|
||||||
The addon's descriptor, contains the following information:
|
|
||||||
|
|
||||||
``name: str``
|
|
||||||
The addon name, in plain, readable english
|
|
||||||
``version: str``
|
|
||||||
The addon version, following `Semantic Versioning`_ rules
|
|
||||||
``depends: [str]``
|
|
||||||
A list of addons this addon needs to work correctly. ``base`` is
|
|
||||||
an implied dependency if the list is empty.
|
|
||||||
``css: [str]``
|
|
||||||
An ordered list of CSS files this addon provides and needs. The
|
|
||||||
file paths are relative to the addon's root. Because the Web
|
|
||||||
Client *may* perform concatenations and other various
|
|
||||||
optimizations on CSS files, the order is important.
|
|
||||||
``js: [str]``
|
|
||||||
An ordered list of Javascript files this addon provides and needs
|
|
||||||
(including dependencies files). As with CSS files, the order is
|
|
||||||
important as the Web Client *may* perform contatenations and
|
|
||||||
minimizations of files.
|
|
||||||
``active: bool``
|
|
||||||
Whether this addon should be enabled by default any time it is
|
|
||||||
found, or whether it will be enabled through other means (on a
|
|
||||||
by-need or by-installation basis for instance).
|
|
||||||
|
|
||||||
``controllers/``
|
|
||||||
All of the Python controllers and JSON-RPC endpoints.
|
|
||||||
|
|
||||||
``static/``
|
|
||||||
The static files directory, may be served via a separate web server.
|
|
||||||
|
|
||||||
``static/lib/``
|
|
||||||
Third-party libraries used by the addon.
|
|
||||||
|
|
||||||
``static/src/{css,js,img,xml}``
|
|
||||||
Location for (respectively) the addon's static CSS files, its JS
|
|
||||||
files, its various image resources as well as the template files
|
|
||||||
|
|
||||||
``static/test``
|
|
||||||
Javascript tests files
|
|
||||||
|
|
||||||
``test/``
|
|
||||||
The directories in which all tests for the addon are located.
|
|
||||||
|
|
||||||
Some of these are guidelines (and not enforced by code), but it's
|
|
||||||
suggested that these be followed. Code which does not fit into these
|
|
||||||
categories can go wherever deemed suitable.
|
|
||||||
|
|
||||||
Namespacing
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Python
|
|
||||||
++++++
|
|
||||||
|
|
||||||
Because addons are also Python packages, they're inherently namespaced
|
|
||||||
and nothing special needs to be done on that front.
|
|
||||||
|
|
||||||
JavaScript
|
|
||||||
++++++++++
|
|
||||||
|
|
||||||
The JavaScript side of an addon has to live in the namespace
|
|
||||||
``openerp.$addon_name``. For instance, everything created by the addon
|
|
||||||
``base`` lives in ``openerp.base``.
|
|
||||||
|
|
||||||
The root namespace of the addon is a function which takes a single
|
|
||||||
parameter ``openerp``, which is an OpenERP client instance. Objects
|
|
||||||
(as well as functions, registry instances, etc...) should be added on
|
|
||||||
the correct namespace on that object.
|
|
||||||
|
|
||||||
The root function will be called by the OpenERP Web client when
|
|
||||||
initializing the addon.
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
// root namespace of the openerp.example addon
|
|
||||||
/** @namespace */
|
|
||||||
openerp.example = function (openerp) {
|
|
||||||
// basic initialization code (e.g. templates loading)
|
|
||||||
openerp.example.SomeClass = openerp.base.Class.extend(
|
|
||||||
/** @lends openerp.example.SomeClass# */{
|
|
||||||
/**
|
|
||||||
* Description for SomeClass's constructor here
|
|
||||||
*
|
|
||||||
* @constructs
|
|
||||||
*/
|
|
||||||
init: function () {
|
|
||||||
// SomeClass initialization code
|
|
||||||
}
|
|
||||||
// rest of SomeClass
|
|
||||||
});
|
|
||||||
|
|
||||||
// access an object in an other addon namespace to replace it
|
|
||||||
openerp.base.SearchView = openerp.base.SearchView.extend({
|
|
||||||
init: function () {
|
|
||||||
this._super.apply(this, arguments);
|
|
||||||
console.log('Search view initialized');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Creating new standard roles
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
Views
|
|
||||||
+++++
|
|
||||||
|
|
||||||
Views are the standard high-level component in OpenERP. A view type corresponds
|
|
||||||
to a way to display a set of data (coming from an OpenERP model).
|
|
||||||
|
|
||||||
In OpenERP Web, views are standard objects registered against a dedicated
|
|
||||||
object registry, so the :js:class:`~openerp.base.ViewManager` knows where to
|
|
||||||
find and how to call them.
|
|
||||||
|
|
||||||
Although not mandatory, it is recommended that views inherit from
|
|
||||||
:js:class:`openerp.base.View`, which provides a view useful services to its
|
|
||||||
children.
|
|
||||||
|
|
||||||
Registering a view
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This is the first task to perform when creating a view, and the simplest by
|
|
||||||
far: simply call ``openerp.base.views.add(name, object_path)`` to register
|
|
||||||
the object of path ``object_path`` as the view for the view name ``name``.
|
|
||||||
|
|
||||||
The view name is the name you gave to your new view in the OpenERP server.
|
|
||||||
|
|
||||||
From that point onwards, OpenERP Web will be able to find your object and
|
|
||||||
instantiate it.
|
|
||||||
|
|
||||||
Standard view behaviors
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
In the normal OpenERP Web flow, views have to implement a number of methods so
|
|
||||||
view managers can correctly communicate with them:
|
|
||||||
|
|
||||||
``start()``
|
|
||||||
This method will always be called after creating the view (via its
|
|
||||||
constructor), but not necessarily immediately.
|
|
||||||
|
|
||||||
It is called with no arguments and should handle the heavy setup work,
|
|
||||||
including remote call (to load the view's setup data from the server via
|
|
||||||
e.g. ``fields_view_get``, for instance).
|
|
||||||
|
|
||||||
``start`` should return a `promise object`_ which *must* be resolved when
|
|
||||||
the view's setup is completed. This promise is used by view managers to
|
|
||||||
know when they can start interacting with the view.
|
|
||||||
|
|
||||||
``do_hide()``
|
|
||||||
Called by the view manager when it wants to replace this view by an other
|
|
||||||
one, but wants to keep this view around to re-activate it later.
|
|
||||||
|
|
||||||
Should put the view in some sort of hibernation mode, and *must* hide its
|
|
||||||
DOM elements.
|
|
||||||
|
|
||||||
``do_show()``
|
|
||||||
Called when the view manager wants to re-display the view after having
|
|
||||||
hidden it. The view should refresh its data display upon receiving this
|
|
||||||
notification
|
|
||||||
|
|
||||||
``do_search(domain: Array, context: Object, group_by: Array)``
|
|
||||||
If the view is searchable, this method is called to notify it of a search
|
|
||||||
against it.
|
|
||||||
|
|
||||||
It should use the provided query data to perform a search and refresh its
|
|
||||||
internal content (and display).
|
|
||||||
|
|
||||||
All views are searchable by default, but they can be made non-searchable
|
|
||||||
by setting the property ``searchable`` to ``false``.
|
|
||||||
|
|
||||||
This can be done either on the view class itself (at the same level as
|
|
||||||
defining e.g. the ``start`` method) or at the instance level (in the
|
|
||||||
class's ``init``), though you should generally set it on the class.
|
|
||||||
|
|
||||||
Frequent development tasks
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
There are a number of tasks which OpenERP Web developers do or will need to
|
|
||||||
perform quite regularly. To make these easier, we have written a few guides
|
|
||||||
to help you get started:
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
guides/client-action
|
|
||||||
guides/sidebar-protocol
|
|
||||||
|
|
||||||
Translations
|
|
||||||
------------
|
|
||||||
|
|
||||||
OpenERP Web should provide most of the tools needed to correctly translate your
|
|
||||||
addons via the tool of your choice (OpenERP itself uses `Launchpad's own
|
|
||||||
translation tool`_.
|
|
||||||
|
|
||||||
Making strings translatable
|
|
||||||
+++++++++++++++++++++++++++
|
|
||||||
|
|
||||||
QWeb
|
|
||||||
~~~~
|
|
||||||
|
|
||||||
QWeb automatically marks all text nodes (any text which is not in an XML
|
|
||||||
attribute and not part of an XML tag) as translatable, and handles the
|
|
||||||
replacement for you. There is nothing special to do to mark template text as
|
|
||||||
translatable
|
|
||||||
|
|
||||||
JavaScript
|
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
OpenERP Web provides two functions to translate human-readable strings in
|
|
||||||
javascript code. These functions should be "imported" in your module by
|
|
||||||
aliasing them to their bare name:
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
var _t = openerp.web._t,
|
|
||||||
_tl = openerp.web._tl;
|
|
||||||
|
|
||||||
importing those functions under any other name is not guaranteed to work.
|
|
||||||
|
|
||||||
.. note:: only import them if necessary, and only the necessary one(s), no need
|
|
||||||
to clutter your module's namespace for nothing
|
|
||||||
|
|
||||||
.. js:function:: openerp.web._t(s)
|
|
||||||
|
|
||||||
Base translation function, eager, works much like :manpage:`gettext(3)`
|
|
||||||
|
|
||||||
:type s: String
|
|
||||||
:rtype: String
|
|
||||||
|
|
||||||
.. js:function:: openerp.web._lt(s)
|
|
||||||
|
|
||||||
Lazy equivalent to :js:func:`~openerp.web._t`, this function will postpone
|
|
||||||
fetching the translation to its argument until the last possible moment.
|
|
||||||
|
|
||||||
To use in contexts evaluated before the translation database can be
|
|
||||||
fetched, usually your module's toplevel and the attributes of classes
|
|
||||||
defined in it (class attributes, not instance attributes set in the
|
|
||||||
constructor).
|
|
||||||
|
|
||||||
:type s: String
|
|
||||||
:rtype: LazyString
|
|
||||||
|
|
||||||
Text formatting & translations
|
|
||||||
""""""""""""""""""""""""""""""
|
|
||||||
|
|
||||||
A difficulty when translating is integrating data (from the code) into the
|
|
||||||
translated string. In OpenERP Web addons, this should be done by wrapping the
|
|
||||||
text to translate in an :manpage:`sprintf(3)` call. For OpenERP Web,
|
|
||||||
:manpage:`sprintf(3)` is provided by `underscore.string
|
|
||||||
<http://epeli.github.com/underscore.string/>`_.
|
|
||||||
|
|
||||||
As much as possible, you should use the "named argument" form of sprintf:
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
var translated_string = _.str.sprintf(
|
|
||||||
_t("[%(first_record)d to %(last_record)d] of %(records_count)d"), {
|
|
||||||
first_record: first + 1,
|
|
||||||
last_record: last,
|
|
||||||
records_count: total
|
|
||||||
}));
|
|
||||||
|
|
||||||
named arguments make the string to translate much clearer for translators, and
|
|
||||||
allows them to "move" sections around based on the requirements of their
|
|
||||||
language (not all language order text like english).
|
|
||||||
|
|
||||||
Named arguments are specified using the following pattern: ``%($name)$type``
|
|
||||||
where
|
|
||||||
|
|
||||||
``$name``
|
|
||||||
the name of the argument, this is the key in the object/dictionary provided
|
|
||||||
as second parameter to ``sprintf``
|
|
||||||
``$type``
|
|
||||||
a type/format specifier, `see the list for all possible types
|
|
||||||
<http://www.diveintojavascript.com/projects/javascript-sprintf>`_.
|
|
||||||
|
|
||||||
.. note:: positional arguments are acceptable if the translated string has
|
|
||||||
*a single* argument and its content is easy to guess from the text
|
|
||||||
around it. Named arguments should still be preferred.
|
|
||||||
|
|
||||||
.. warning:: you should *never* use string concatenation as it robs the
|
|
||||||
translator of context and make result in a completely incorrect
|
|
||||||
translation
|
|
||||||
|
|
||||||
Extracting strings
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. program:: gen_translations.sh
|
|
||||||
|
|
||||||
Once strings have been marked for translation, they need to be extracted into
|
|
||||||
:abbr:`POT (Portable Object Template)` files, from which most translation tools
|
|
||||||
can build a database.
|
|
||||||
|
|
||||||
This can be done via the provided :program:`gen_translations.sh`.
|
|
||||||
|
|
||||||
It can be called either as :option:`gen_translations.sh -a` or by providing
|
|
||||||
two parameters, a path to the addons and the complete path in which to put the
|
|
||||||
extracted POT file.
|
|
||||||
|
|
||||||
.. option:: -a
|
|
||||||
|
|
||||||
Extracts translations from all standard OpenERP Web addons (addons bundled
|
|
||||||
with OpenERP Web itself) and puts the extracted templates into the right
|
|
||||||
directory for `Rosetta`_ to handle them
|
|
||||||
|
|
||||||
Utility behaviors
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
JavaScript
|
|
||||||
++++++++++
|
|
||||||
|
|
||||||
* All javascript objects inheriting from
|
|
||||||
:js:class:`openerp.base.BasicConroller` will have all methods
|
|
||||||
starting with ``on_`` or ``do_`` bound to their ``this``. This means
|
|
||||||
they don't have to be manually bound (via ``_.bind`` or ``$.proxy``)
|
|
||||||
in order to be useable as bound event handlers (event handlers
|
|
||||||
keeping their object as ``this`` rather than taking whatever
|
|
||||||
``this`` object they were called with).
|
|
||||||
|
|
||||||
Beware that this is only valid for methods starting with ``do_`` and
|
|
||||||
``on_``, any other method will have to be bound manually.
|
|
||||||
|
|
||||||
.. _addons-testing:
|
|
||||||
|
|
||||||
Testing
|
|
||||||
-------
|
|
||||||
|
|
||||||
Python
|
|
||||||
++++++
|
|
||||||
|
|
||||||
OpenERP Web uses unittest2_ for its testing needs. We selected
|
|
||||||
unittest2 rather than unittest_ for the following reasons:
|
|
||||||
|
|
||||||
* autodiscovery_ (similar to nose, via the ``unit2``
|
|
||||||
CLI utility) and `pluggable test discovery`_.
|
|
||||||
|
|
||||||
* `new and improved assertions`_ (with improvements in type-specific
|
|
||||||
inequality reportings) including `pluggable custom types equality
|
|
||||||
assertions`_
|
|
||||||
|
|
||||||
* neveral new APIs, most notably `assertRaises context manager`_,
|
|
||||||
`cleanup function registration`_, `test skipping`_ and `class- and
|
|
||||||
module-level setup and teardown`_
|
|
||||||
|
|
||||||
* finally, unittest2 is a backport of Python 3's unittest. We might as
|
|
||||||
well get used to it.
|
|
||||||
|
|
||||||
To run tests on addons (from the root directory of OpenERP Web) is as
|
|
||||||
simple as typing ``PYTHONPATH=. unit2 discover -s addons`` [#]_. To
|
|
||||||
test an addon which does not live in the ``addons`` directory, simply
|
|
||||||
replace ``addons`` by the directory in which your own addon lives.
|
|
||||||
|
|
||||||
.. note:: unittest2 is entirely compatible with nose_ (or the
|
|
||||||
other way around). If you want to use nose as your test
|
|
||||||
runner (due to its addons for instance) you can simply install it
|
|
||||||
and run ``nosetests addons`` instead of the ``unit2`` command,
|
|
||||||
the result should be exactly the same.
|
|
||||||
|
|
||||||
Python
|
|
||||||
++++++
|
|
||||||
|
|
||||||
.. autoclass:: web.common.session.OpenERPSession
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. autoclass:: web.common.openerplib.main.Model
|
|
||||||
:members:
|
|
||||||
|
|
||||||
* Addons lifecycle (loading, execution, events, ...)
|
|
||||||
|
|
||||||
* Python-side
|
|
||||||
* JS-side
|
|
||||||
|
|
||||||
* Handling static files
|
|
||||||
* Overridding a Python controller (object?)
|
|
||||||
* Overridding a Javascript controller (object?)
|
|
||||||
* Extending templates
|
|
||||||
.. how do you handle deploying static files via e.g. a separate lighttpd?
|
|
||||||
* Python public APIs
|
|
||||||
* QWeb templates description?
|
|
||||||
* OpenERP Web modules (from OpenERP modules)
|
|
||||||
|
|
||||||
.. [#] the ``-s`` parameter tells ``unit2`` to start trying to
|
|
||||||
find tests in the provided directory (here we're testing
|
|
||||||
addons). However a side-effect of that is to set the
|
|
||||||
``PYTHONPATH`` there as well, so it will fail to find (and
|
|
||||||
import) ``openerpweb``.
|
|
||||||
|
|
||||||
The ``-t`` parameter lets us set the ``PYTHONPATH``
|
|
||||||
independently, but it doesn't accept multiple values and here
|
|
||||||
we really want to have both ``.`` and ``addons`` on the
|
|
||||||
``PYTHONPATH``.
|
|
||||||
|
|
||||||
The solution is to set the ``PYTHONPATH`` to ``.`` on start,
|
|
||||||
and the ``start-directory`` to ``addons``. This results in a
|
|
||||||
correct ``PYTHONPATH`` within ``unit2``.
|
|
||||||
|
|
||||||
.. _unittest:
|
|
||||||
http://docs.python.org/library/unittest.html
|
|
||||||
|
|
||||||
.. _unittest2:
|
|
||||||
http://www.voidspace.org.uk/python/articles/unittest2.shtml
|
|
||||||
|
|
||||||
.. _autodiscovery:
|
|
||||||
http://www.voidspace.org.uk/python/articles/unittest2.shtml#test-discovery
|
|
||||||
|
|
||||||
.. _pluggable test discovery:
|
|
||||||
http://www.voidspace.org.uk/python/articles/unittest2.shtml#load-tests
|
|
||||||
|
|
||||||
.. _new and improved assertions:
|
|
||||||
http://www.voidspace.org.uk/python/articles/unittest2.shtml#new-assert-methods
|
|
||||||
|
|
||||||
.. _pluggable custom types equality assertions:
|
|
||||||
http://www.voidspace.org.uk/python/articles/unittest2.shtml#add-new-type-specific-functions
|
|
||||||
|
|
||||||
.. _assertRaises context manager:
|
|
||||||
http://www.voidspace.org.uk/python/articles/unittest2.shtml#assertraises
|
|
||||||
|
|
||||||
.. _cleanup function registration:
|
|
||||||
http://www.voidspace.org.uk/python/articles/unittest2.shtml#cleanup-functions-with-addcleanup
|
|
||||||
|
|
||||||
.. _test skipping:
|
|
||||||
http://www.voidspace.org.uk/python/articles/unittest2.shtml#test-skipping
|
|
||||||
|
|
||||||
.. _class- and module-level setup and teardown:
|
|
||||||
http://www.voidspace.org.uk/python/articles/unittest2.shtml#class-and-module-level-fixtures
|
|
||||||
|
|
||||||
.. _Semantic Versioning:
|
|
||||||
http://semver.org/
|
|
||||||
|
|
||||||
.. _nose:
|
|
||||||
http://somethingaboutorange.com/mrl/projects/nose/1.0.0/
|
|
||||||
|
|
||||||
.. _promise object:
|
|
||||||
http://api.jquery.com/deferred.promise/
|
|
||||||
|
|
||||||
.. _Rosetta:
|
|
||||||
.. _Launchpad's own translation tool:
|
|
||||||
https://help.launchpad.net/Translations
|
|
|
@ -1,424 +0,0 @@
|
||||||
OpenERP Web Core and standard addons
|
|
||||||
====================================
|
|
||||||
|
|
||||||
* General organization and core ideas (design philosophies)
|
|
||||||
* Internal documentation, autodoc, Python and JS domains
|
|
||||||
* QWeb code documentation/description
|
|
||||||
* Documentation of the OpenERP APIs and choices taken based on that?
|
|
||||||
* Style guide and coding conventions (PEP8? More)
|
|
||||||
* Test frameworks in JS?
|
|
||||||
|
|
||||||
Standard Views
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Search View
|
|
||||||
+++++++++++
|
|
||||||
|
|
||||||
The OpenERP search view really is a sub-view, used in support of views
|
|
||||||
acting on collections of records (list view or graph view, for
|
|
||||||
instance).
|
|
||||||
|
|
||||||
Its main goal is to collect information from its widgets (themselves
|
|
||||||
collecting information from the users) and make those available to the
|
|
||||||
rest of the client.
|
|
||||||
|
|
||||||
The search view's root is :js:class:`~openerp.base.SearchView`. This
|
|
||||||
object should never need to be created or managed directly, its
|
|
||||||
lifecycle should be driven by the
|
|
||||||
:js:class:`~openerp.base.ViewManager`.
|
|
||||||
|
|
||||||
.. TODO: insert SearchView constructor here
|
|
||||||
|
|
||||||
The search view defines a number of internal and external protocols to
|
|
||||||
communicate with the objects around and within it. Most of these
|
|
||||||
protocols are informal, and types available for inheritance are more
|
|
||||||
mixins than mandatory.
|
|
||||||
|
|
||||||
Events
|
|
||||||
""""""
|
|
||||||
|
|
||||||
``on_loaded``
|
|
||||||
|
|
||||||
.. TODO: method openerp.base.SearchView.on_loaded
|
|
||||||
|
|
||||||
Fires when the search view receives its view data (the result of
|
|
||||||
``fields_view_get``). Hooking up before the event allows for
|
|
||||||
altering view data before it can be used.
|
|
||||||
|
|
||||||
By the time ``on_loaded`` is done, the search view is guaranteed to
|
|
||||||
be fully set up and ready to use.
|
|
||||||
|
|
||||||
``on_search``
|
|
||||||
|
|
||||||
.. TODO: method openerp.base.SearchView.on_search
|
|
||||||
|
|
||||||
Event triggered after a user asked for a search. The search view
|
|
||||||
fires this event after collecting all input data (contexts, domains
|
|
||||||
and group_by contexts). Note that the search view does *not* merge
|
|
||||||
those (or otherwise evaluate them), they are returned as provided by
|
|
||||||
the various inputs within the view.
|
|
||||||
|
|
||||||
``on_clear``
|
|
||||||
|
|
||||||
.. TODO: method openerp.base.SearchView.on_clear
|
|
||||||
|
|
||||||
Triggered after a user asked for a form clearing.
|
|
||||||
|
|
||||||
Input management
|
|
||||||
""""""""""""""""
|
|
||||||
|
|
||||||
An important concept in the search view is that of input. It is both
|
|
||||||
an informal protocol and an abstract type that can be inherited from.
|
|
||||||
|
|
||||||
Inputs are widgets which can contain user data (a char widget for
|
|
||||||
instance, or a selection box). They are capable of action and of
|
|
||||||
reaction:
|
|
||||||
|
|
||||||
.. _views-search-registration:
|
|
||||||
|
|
||||||
``registration``
|
|
||||||
|
|
||||||
This is an input action. Inputs have to register themselves to the
|
|
||||||
main view (which they receive as a constructor argument). This is
|
|
||||||
performed by pushing themselves on the
|
|
||||||
:js:attr:`openerp.base.SearchView.inputs` array.
|
|
||||||
|
|
||||||
``get_context``
|
|
||||||
|
|
||||||
An input reaction. When it needs to collect contexts, the view calls
|
|
||||||
``get_context()`` on all its inputs.
|
|
||||||
|
|
||||||
Inputs can react in the following manners:
|
|
||||||
|
|
||||||
* Return a context (an object), this is the "normal" response if the
|
|
||||||
input holds a value.
|
|
||||||
|
|
||||||
* Return a value that evaluates as false (generally ``null``). This
|
|
||||||
value indicates the input does not contain any value and will not
|
|
||||||
affect the results of the search.
|
|
||||||
|
|
||||||
* Raise :js:class:`openerp.base.search.Invalid` to indicate that it
|
|
||||||
holds a value but this value can not be used in the search
|
|
||||||
(because it is incorrectly formatted or nonsensical). Raising
|
|
||||||
:js:class:`~openerp.base.search.Invalid` is guaranteed to cancel
|
|
||||||
the search process.
|
|
||||||
|
|
||||||
:js:class:`~openerp.base.search.Invalid` takes three mandatory
|
|
||||||
arguments: an identifier (a name for instance), the invalid value,
|
|
||||||
and a validation message indicating the issue.
|
|
||||||
|
|
||||||
``get_domain``
|
|
||||||
|
|
||||||
The second input reaction, the possible behaviors of inputs are the
|
|
||||||
same as for ``get_context``.
|
|
||||||
|
|
||||||
The :js:class:`openerp.base.search.Input` type implements registration
|
|
||||||
on its own, but its implementations of ``get_context`` and
|
|
||||||
``get_domain`` simply raise errors and *must* be overridden.
|
|
||||||
|
|
||||||
One last action is for filters, as an activation order has to be kept
|
|
||||||
on them for some controls (to establish the correct grouping sequence,
|
|
||||||
for instance).
|
|
||||||
|
|
||||||
To that end, filters can call
|
|
||||||
:js:func:`openerp.base.Search.do_toggle_filter`, providing themselves
|
|
||||||
as first argument.
|
|
||||||
|
|
||||||
Filters calling :js:func:`~openerp.base.Search.do_toggle_filter` also
|
|
||||||
need to implement a method called
|
|
||||||
:js:func:`~openerp.base.search.Filter.is_enabled`, which the search
|
|
||||||
view will use to know the current status of the filter.
|
|
||||||
|
|
||||||
The search view automatically triggers a search after calls to
|
|
||||||
:js:func:`~openerp.base.Search.do_toggle_filter`.
|
|
||||||
|
|
||||||
Life cycle
|
|
||||||
""""""""""
|
|
||||||
|
|
||||||
The search view has a pretty simple and linear life cycle, in three main steps:
|
|
||||||
|
|
||||||
:js:class:`~openerp.base.SearchView.init`
|
|
||||||
|
|
||||||
Nothing interesting happens here
|
|
||||||
|
|
||||||
:js:func:`~openerp.base.SearchView.start`
|
|
||||||
|
|
||||||
Called by the main view's creator, this is the main initialization
|
|
||||||
step for the list view.
|
|
||||||
|
|
||||||
It begins with a remote call to fetch the view's descriptors
|
|
||||||
(``fields_view_get``).
|
|
||||||
|
|
||||||
Once the remote call is complete, the ``on_loaded`` even happens,
|
|
||||||
holding three main operations:
|
|
||||||
|
|
||||||
:js:func:`~openerp.base.SearchView.make_widgets`
|
|
||||||
|
|
||||||
Builds and returns the top-level widgets of the search
|
|
||||||
view. Because it returns an array of widget lines (a 2-dimensional
|
|
||||||
matrix of widgets) it should be called recursively by container
|
|
||||||
widgets (:js:class:`openerp.base.search.Group` for instance).
|
|
||||||
|
|
||||||
:js:func:`~openerp.base.search.Widget.render`
|
|
||||||
|
|
||||||
Called by the search view on all top-level widgets. Container
|
|
||||||
widgets should recursively call this method on their own children
|
|
||||||
widgets.
|
|
||||||
|
|
||||||
Widgets are provided with a mapping of ``{name: value}`` holding
|
|
||||||
default values for the search view. They can freely pick their
|
|
||||||
initial values from there, but must pass the mapping to their
|
|
||||||
children widgets if they have any.
|
|
||||||
|
|
||||||
:js:func:`~openerp.base.search.Widget.start`
|
|
||||||
|
|
||||||
The last operation of the search view startup is to initialize all
|
|
||||||
its widgets in order. This is again done recursively (the search
|
|
||||||
view starts its children, which have to start their own children).
|
|
||||||
|
|
||||||
:js:func:`~openerp.base.SearchView.stop`
|
|
||||||
|
|
||||||
Used before discarding a search view, allows the search view to
|
|
||||||
disable its events and pass the message to its own widgets,
|
|
||||||
gracefully shutting down the whole view.
|
|
||||||
|
|
||||||
Widgets
|
|
||||||
"""""""
|
|
||||||
|
|
||||||
In a search view, the widget is simply a unit of display.
|
|
||||||
|
|
||||||
All widgets must be able to react to three events, which will be
|
|
||||||
called in this order:
|
|
||||||
|
|
||||||
:js:func:`~openerp.base.search.Widget.render`
|
|
||||||
|
|
||||||
Called with a map of default values. The widget must return a
|
|
||||||
``String``, which is its HTML representation. That string can be
|
|
||||||
empty (if the widget should not be represented).
|
|
||||||
|
|
||||||
Widgets are responsible for asking their children for rendering, and
|
|
||||||
for passing along the default values.
|
|
||||||
|
|
||||||
:js:func:`~openerp.base.search.Widget.start`
|
|
||||||
|
|
||||||
Called without arguments. At this point, the widget has been fully
|
|
||||||
rendered and can set its events up, if any.
|
|
||||||
|
|
||||||
The widget is responsible for starting its children, if it has any.
|
|
||||||
|
|
||||||
:js:func:`~openerp.base.search.Widget.stop`
|
|
||||||
|
|
||||||
Gives the widget the opportunity to unbind its events, remove itself
|
|
||||||
from the DOM and perform any other cleanup task it may have.
|
|
||||||
|
|
||||||
Even if the widget does not do anything itself, it is responsible
|
|
||||||
for shutting down its children.
|
|
||||||
|
|
||||||
An abstract type is available and can be inherited from, to simplify
|
|
||||||
the implementation of those tasks:
|
|
||||||
|
|
||||||
.. TODO: insert Widget here
|
|
||||||
|
|
||||||
.. remember to document all methods
|
|
||||||
|
|
||||||
Inputs
|
|
||||||
""""""
|
|
||||||
|
|
||||||
The search namespace (``openerp.base.search``) provides two more
|
|
||||||
abstract types, used to implement input widgets:
|
|
||||||
|
|
||||||
* :js:class:`openerp.base.search.Input` is the most basic input type,
|
|
||||||
it only implements :ref:`input registration
|
|
||||||
<views-search-registration>`.
|
|
||||||
|
|
||||||
If inherited from, descendant classes should not call its
|
|
||||||
implementations of :js:func:`~openerp.base.search.Input.get_context`
|
|
||||||
and :js:func:`~openerp.base.search.Input.get_domain`.
|
|
||||||
|
|
||||||
* :js:class:`openerp.base.search.Field` is used to implement more
|
|
||||||
"field" widgets (which allow the user to input potentially complex
|
|
||||||
values).
|
|
||||||
|
|
||||||
It provides various services for its subclasses:
|
|
||||||
|
|
||||||
* Sets up the field attributes, using attributes from the field and
|
|
||||||
the view node.
|
|
||||||
|
|
||||||
* It fills the widget with :js:class:`~openerp.base.search.Filter`
|
|
||||||
if the field has any child filter.
|
|
||||||
|
|
||||||
* It automatically generates an identifier based on the field type
|
|
||||||
and the field name, using
|
|
||||||
:js:func:`~openerp.base.search.Widget.make_id`.
|
|
||||||
|
|
||||||
* It sets up a basic (overridable)
|
|
||||||
:js:attr:`~openerp.base.search.Field.template` attribute, combined
|
|
||||||
with the previous tasks, this makes subclasses of
|
|
||||||
:js:class:`~openerp.base.search.Field` render themselves "for
|
|
||||||
free".
|
|
||||||
|
|
||||||
* It provides basic implementations of ``get_context`` and
|
|
||||||
``get_domain``, both hinging on the subclasses implementing
|
|
||||||
``get_value()`` (which should return a correct, converted
|
|
||||||
Javascript value):
|
|
||||||
|
|
||||||
:js:func:`~openerp.base.search.Field.get_context`
|
|
||||||
|
|
||||||
Checks if the field has a non-``null`` and non-empty
|
|
||||||
(``String``) value, and that the field has a ``context`` attr.
|
|
||||||
|
|
||||||
If both conditions are fullfilled, returns the context.
|
|
||||||
|
|
||||||
:js:func:`~openerp.base.search.Field.get_domain`
|
|
||||||
|
|
||||||
Only requires that the field has a non-``null`` and non-empty
|
|
||||||
value.
|
|
||||||
|
|
||||||
If the field has a ``filter_domain``, returns it
|
|
||||||
immediately. Otherwise, builds a context using the field's
|
|
||||||
name, the field :js:attr:`~openerp.base.search.Field.operator`
|
|
||||||
and the field value, and returns it.
|
|
||||||
|
|
||||||
.. TODO: insert Input, Field, Filter, and just about every Field subclass
|
|
||||||
|
|
||||||
List View
|
|
||||||
+++++++++
|
|
||||||
|
|
||||||
OpenERP Web's list views don't actually exist as such in OpenERP itself: a
|
|
||||||
list view is an OpenERP tree view in the ``view_mode`` form.
|
|
||||||
|
|
||||||
The overall purpose of a list view is to display collections of objects in two
|
|
||||||
main forms: per-object, where each object is a row in and of itself, and
|
|
||||||
grouped, where multiple objects are represented with a single row providing
|
|
||||||
an aggregated view of all grouped objects.
|
|
||||||
|
|
||||||
These two forms can be mixed within a single list view, if needed.
|
|
||||||
|
|
||||||
The root of a list view is :js:class:`openerp.base.ListView`, which may need
|
|
||||||
to be overridden (partially or fully) to control list behavior in non-view
|
|
||||||
cases (when using a list view as sub-component of a form widget for instance).
|
|
||||||
|
|
||||||
Creation and Initialization
|
|
||||||
"""""""""""""""""""""""""""
|
|
||||||
|
|
||||||
As with most OpenERP Web views, the list view's
|
|
||||||
:js:func:`~openerp.base.ListView.init` takes quite a number of arguments.
|
|
||||||
|
|
||||||
While most of them are the standard view constructor arguments
|
|
||||||
(``view_manager``, ``session``, ``element_id``, ``dataset`` and an
|
|
||||||
optional ``view_id``), the list view adds a number of options for basic
|
|
||||||
customization (without having to override methods or templates):
|
|
||||||
|
|
||||||
``selectable`` (default: ``true``)
|
|
||||||
Indicates that the list view should allow records to be selected
|
|
||||||
individually. Displays selection check boxes to the left of all record rows,
|
|
||||||
and allows for the triggering of the
|
|
||||||
:ref:`selection event <listview-events-selection>`.
|
|
||||||
``deletable`` (default: ``true``)
|
|
||||||
Indicates that the list view should allow records to be removed
|
|
||||||
individually. Displays a deletion button to the right of all record rows,
|
|
||||||
and allows for the triggering of the
|
|
||||||
:ref:`deletion event <listview-events-deletion>`.
|
|
||||||
``header`` (default: ``true``)
|
|
||||||
Indicates that list columns should bear a header sporting their name (for
|
|
||||||
non-action columns).
|
|
||||||
``addable`` (default: ``"New"``)
|
|
||||||
Indicates that a record addition/creation button should be displayed in
|
|
||||||
the list's header, along with its label. Also allows for the triggering of
|
|
||||||
the :ref:`record addition event <listview-events-addition>`.
|
|
||||||
``sortable`` (default: ``true``)
|
|
||||||
Indicates that the list view can be sorted per-column (by clicking on its
|
|
||||||
column headers).
|
|
||||||
|
|
||||||
.. TODO: event?
|
|
||||||
``reorderable`` (default: ``true``)
|
|
||||||
Indicates that the list view records can be reordered (and re-sequenced)
|
|
||||||
by drag and drop.
|
|
||||||
|
|
||||||
.. TODO: event?
|
|
||||||
|
|
||||||
Events
|
|
||||||
""""""
|
|
||||||
.. _listview-events-addition:
|
|
||||||
|
|
||||||
Addition
|
|
||||||
''''''''
|
|
||||||
The addition event is used to add a record to an existing list view. The
|
|
||||||
default behavior is to switch to the form view, on a new record.
|
|
||||||
|
|
||||||
Addition behavior can be overridden by replacing the
|
|
||||||
:js:func:`~openerp.base.ListView.do_add_record` method.
|
|
||||||
|
|
||||||
.. _listview-events-selection:
|
|
||||||
|
|
||||||
Selection
|
|
||||||
'''''''''
|
|
||||||
The selection event is triggered when a given record is selected in the list
|
|
||||||
view.
|
|
||||||
|
|
||||||
It can be overridden by replacing the
|
|
||||||
:js:func:`~openerp.base.ListView.do_select` method.
|
|
||||||
|
|
||||||
The default behavior is simply to hide or display the list-wise deletion button
|
|
||||||
depending on whether there are selected records or not.
|
|
||||||
|
|
||||||
.. _listview-events-deletion:
|
|
||||||
|
|
||||||
Deletion
|
|
||||||
''''''''
|
|
||||||
The deletion event is triggered when the user tries to remove 1..n records from
|
|
||||||
the list view, either individually or globally (via the header button).
|
|
||||||
|
|
||||||
Deletion can be overridden by replacing the
|
|
||||||
:js:func:`~openerp.base.ListView.do_delete` method. By default, this method
|
|
||||||
calls :js:func:`~openerp.base.DataSet.unlink` in order to remove the records
|
|
||||||
entirely.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
the list-wise deletion button (next to the record addition button)
|
|
||||||
simply proxies to :js:func:`~openerp.base.ListView.do_delete` after
|
|
||||||
obtaining all selected record ids, but it is possible to override it
|
|
||||||
alone by replacing
|
|
||||||
:js:func:`~openerp.base.ListView.do_delete_selected`.
|
|
||||||
|
|
||||||
Internal API Doc
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Python
|
|
||||||
++++++
|
|
||||||
|
|
||||||
These classes should be moved to other sections of the doc as needed,
|
|
||||||
probably.
|
|
||||||
|
|
||||||
.. automodule:: web.common.http
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
|
|
||||||
See also: :class:`~web.common.session.OpenERPSession`,
|
|
||||||
:class:`~web.common.openerplib.main.OpenERPModel`
|
|
||||||
|
|
||||||
.. automodule:: web.controllers.main
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
|
|
||||||
Testing
|
|
||||||
-------
|
|
||||||
|
|
||||||
Python
|
|
||||||
++++++
|
|
||||||
|
|
||||||
Testing for the OpenERP Web core is similar to :ref:`testing addons
|
|
||||||
<addons-testing>`: the tests live in ``openerpweb.tests``, unittest2_
|
|
||||||
is the testing framework and tests can be run via either unittest2
|
|
||||||
(``unit2 discover``) or via nose_ (``nosetests``).
|
|
||||||
|
|
||||||
Tests for the OpenERP Web core can also be run using ``setup.py
|
|
||||||
test``.
|
|
||||||
|
|
||||||
|
|
||||||
.. _unittest2:
|
|
||||||
http://www.voidspace.org.uk/python/articles/unittest2.shtml
|
|
||||||
|
|
||||||
.. _nose:
|
|
||||||
http://somethingaboutorange.com/mrl/projects/nose/1.0.0/
|
|
|
@ -1,10 +0,0 @@
|
||||||
Getting Started with OpenERP Web
|
|
||||||
================================
|
|
||||||
|
|
||||||
Installing
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. per-distro packaging
|
|
||||||
|
|
||||||
Launching
|
|
||||||
---------
|
|
|
@ -22,20 +22,6 @@ Contents:
|
||||||
list-view
|
list-view
|
||||||
form-notes
|
form-notes
|
||||||
|
|
||||||
Older stuff
|
|
||||||
-----------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
getting-started
|
|
||||||
production
|
|
||||||
widgets
|
|
||||||
addons
|
|
||||||
development
|
|
||||||
project
|
|
||||||
old-version
|
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
Main differences with the 6.0 client
|
|
||||||
====================================
|
|
||||||
|
|
||||||
.. No more populate.sh, use virtualenvs
|
|
||||||
|
|
||||||
.. Logic is mainly in Javascript (had to make a choice between JS and
|
|
||||||
.. Python logic)
|
|
||||||
|
|
||||||
.. Templating language changes
|
|
||||||
|
|
||||||
.. How to port addons and modules?
|
|
|
@ -1,47 +0,0 @@
|
||||||
Deploying OpenERP Web
|
|
||||||
=====================
|
|
||||||
|
|
||||||
.. After release one, add upgrade instructions if any
|
|
||||||
|
|
||||||
.. How about running the web client on alternative Python
|
|
||||||
.. implementations e.g. pypy or Jython? Since the only lib with C
|
|
||||||
.. accelerators we're using right now is SimpleJSON and it has a pure
|
|
||||||
.. Python base component, we should be able to test and deploy on
|
|
||||||
.. non-cpython no?
|
|
||||||
|
|
||||||
In-depth configuration
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
SSL, basic proxy (link to relevant section), links to sections and
|
|
||||||
example files for various servers and proxies, WSGI
|
|
||||||
integration/explanation (if any), ...
|
|
||||||
|
|
||||||
Deployment Options
|
|
||||||
------------------
|
|
||||||
|
|
||||||
Serving via WSGI
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Apache mod_wsgi
|
|
||||||
+++++++++++++++
|
|
||||||
|
|
||||||
NGinx mod_wsgi
|
|
||||||
++++++++++++++
|
|
||||||
|
|
||||||
uWSGI
|
|
||||||
+++++
|
|
||||||
|
|
||||||
Gunicorn
|
|
||||||
++++++++
|
|
||||||
|
|
||||||
FastCGI, SCGI, or AJP
|
|
||||||
+++++++++++++++++++++
|
|
||||||
|
|
||||||
Behind a proxy
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Apache mod_proxy
|
|
||||||
++++++++++++++++
|
|
||||||
|
|
||||||
NGinx HttpProxy
|
|
||||||
+++++++++++++++
|
|
451
doc/project.rst
451
doc/project.rst
|
@ -1,451 +0,0 @@
|
||||||
The OpenERP Web open-source project
|
|
||||||
===================================
|
|
||||||
|
|
||||||
Getting involved
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Translations
|
|
||||||
++++++++++++
|
|
||||||
|
|
||||||
Bug reporting
|
|
||||||
+++++++++++++
|
|
||||||
|
|
||||||
Source code repository
|
|
||||||
++++++++++++++++++++++
|
|
||||||
|
|
||||||
Merge proposals
|
|
||||||
+++++++++++++++
|
|
||||||
|
|
||||||
Coding issues and coding conventions
|
|
||||||
++++++++++++++++++++++++++++++++++++
|
|
||||||
|
|
||||||
Javascript coding
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
These are a number of guidelines for javascript code. More than coding
|
|
||||||
conventions, these are warnings against potentially harmful or sub-par
|
|
||||||
constructs.
|
|
||||||
|
|
||||||
Ideally, you should be able to configure your editor or IDE to warn you against
|
|
||||||
these kinds of issues.
|
|
||||||
|
|
||||||
Use ``var`` for *all* declarations
|
|
||||||
**********************************
|
|
||||||
|
|
||||||
In javascript (as opposed to Python), assigning to a variable which does not
|
|
||||||
already exist and is not explicitly declared (via ``var``) will implicitly
|
|
||||||
create a global variable. This is bad for a number of reasons:
|
|
||||||
|
|
||||||
* It leaks information outside function scopes
|
|
||||||
* It keeps memory of previous run, with potentially buggy behaviors
|
|
||||||
* It may conflict with other functions with the same issue
|
|
||||||
* It makes code harder to statically check (via e.g. IDE inspectors)
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
It is perfectly possible to use ``var`` in ``for`` loops:
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
for (var i = 0; i < some_array.length; ++i) {
|
|
||||||
// code here
|
|
||||||
}
|
|
||||||
|
|
||||||
this is not an issue
|
|
||||||
|
|
||||||
All local *and global* variables should be declared via ``var``.
|
|
||||||
|
|
||||||
.. note:: generally speaking, you should not need globals in OpenERP Web: you
|
|
||||||
can just declare a variable local to your top-level function. This
|
|
||||||
way, if your widget/addon is instantiated several times on the same
|
|
||||||
page (because it's used in embedded mode) each instance will have its
|
|
||||||
own internal but global-to-its-objects data.
|
|
||||||
|
|
||||||
Do not leave trailing commas in object literals
|
|
||||||
***********************************************
|
|
||||||
|
|
||||||
While it is legal to leave trailing commas in Python dictionaries, e.g.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
foo = {
|
|
||||||
'a': 1,
|
|
||||||
'b': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
and it's valid in ECMAScript 5 and most browsers support it in Javascript, you
|
|
||||||
should *never* use trailing commas in Javascript object literals:
|
|
||||||
|
|
||||||
* Internet Explorer does *not* support trailing commas (at least until and
|
|
||||||
including Internet Explorer 8), and trailing comma will cause hard-to-debug
|
|
||||||
errors in it
|
|
||||||
|
|
||||||
* JSON does not accept trailing comma (it is a syntax error), and using them
|
|
||||||
in object literals puts you at risks of using them in literal JSON strings
|
|
||||||
as well (though there are few reasons to write JSON by hand)
|
|
||||||
|
|
||||||
*Never* use ``for … in`` to iterate on arrays
|
|
||||||
*********************************************
|
|
||||||
|
|
||||||
:ref:`Iterating over an object with for…in is a bit tricky already
|
|
||||||
<for-in-iteration>`, it is far more complex than in Python (where it Just
|
|
||||||
Works™) due to the interaction of various Javascript features, but to iterate
|
|
||||||
on arrays it becomes downright deadly and errorneous: ``for…in`` really
|
|
||||||
iterates over an *object*'s *properties*.
|
|
||||||
|
|
||||||
With an array, this has the following consequences:
|
|
||||||
|
|
||||||
* It does not necessarily iterate in numerical order, nor does it iterate in
|
|
||||||
any kind of set order. The order is implementation-dependent and may vary
|
|
||||||
from one run to the next depending on a number of reasons and implementation
|
|
||||||
details.
|
|
||||||
* If properties are added to an array, to ``Array.prototype`` or to
|
|
||||||
``Object.prototype`` (the latter two should not happen in well-behaved
|
|
||||||
javascript code, but you never know...) those properties *will* be iterated
|
|
||||||
over by ``for…in``. While ``Object.hasOwnProperty`` will guard against
|
|
||||||
iterating prototype properties, they will not guard against properties set
|
|
||||||
on the array instance itself (as memoizers for instance).
|
|
||||||
|
|
||||||
Note that this includes setting negative keys on arrays.
|
|
||||||
|
|
||||||
For this reason, ``for…in`` should **never** be used on array objects. Instead,
|
|
||||||
you should use either a normal ``for`` or (even better, unless you have
|
|
||||||
profiled the code and found a hotspot) one of Underscore's array iteration
|
|
||||||
methods (`_.each`_, `_.map`_, `_.filter`_, etc...).
|
|
||||||
|
|
||||||
Underscore is guaranteed to be bundled and available in OpenERP Web scopes.
|
|
||||||
|
|
||||||
.. _for-in-iteration:
|
|
||||||
|
|
||||||
Use ``hasOwnProperty`` when iterating on an object with ``for … in``
|
|
||||||
********************************************************************
|
|
||||||
|
|
||||||
``for…in`` is Javascript's built-in facility for iterating over and object's
|
|
||||||
properties.
|
|
||||||
|
|
||||||
`It is also fairly tricky to use`_: it iterates over *all* non-builtin
|
|
||||||
properties of your objects [#]_, which includes methods of an object's class.
|
|
||||||
|
|
||||||
As a result, when iterating over an object with ``for…in`` the first line of
|
|
||||||
the body *should* generally be a call to `Object.hasOwnProperty`_. This call
|
|
||||||
will check whether the property was set directly on the object or comes from
|
|
||||||
the object's class:
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
for(var key in ob) {
|
|
||||||
if (!ob.hasOwnProperty(key)) {
|
|
||||||
// comes from ob's class
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// do stuff with key
|
|
||||||
}
|
|
||||||
|
|
||||||
Since properties can be added directly to e.g. ``Object.prototype`` (even
|
|
||||||
though it's usually considered bad style), you should not assume you ever know
|
|
||||||
which properties ``for…in`` is going to iterate over.
|
|
||||||
|
|
||||||
An alternative is to use Underscore's iteration methods, which generally work
|
|
||||||
over objects as well as arrays:
|
|
||||||
|
|
||||||
Instead of
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
for (var key in ob) {
|
|
||||||
if (!ob.hasOwnProperty(key)) { continue; }
|
|
||||||
var value = ob[key];
|
|
||||||
// Do stuff with key and value
|
|
||||||
}
|
|
||||||
|
|
||||||
you could write:
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
_.each(ob, function (value, key) {
|
|
||||||
// do stuff with key and value
|
|
||||||
});
|
|
||||||
|
|
||||||
and not worry about the details of the iteration: underscore should do the
|
|
||||||
right thing for you on its own [#]_.
|
|
||||||
|
|
||||||
Writing documentation
|
|
||||||
+++++++++++++++++++++
|
|
||||||
|
|
||||||
The OpenERP Web project documentation uses Sphinx_ for the literate
|
|
||||||
documentation (this document for instance), the development guides
|
|
||||||
(for Python and Javascript alike) and the Python API documentation
|
|
||||||
(via autodoc_).
|
|
||||||
|
|
||||||
For the Javascript API, documentation should be written using the
|
|
||||||
`JsDoc Toolkit`_.
|
|
||||||
|
|
||||||
Guides and main documentation
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The meat and most important part of all documentation. Should be
|
|
||||||
written in plain English, using reStructuredText_ and taking advantage
|
|
||||||
of `Sphinx's extensions`_, especially `cross-references`_.
|
|
||||||
|
|
||||||
Python API Documentation
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
All public objects in Python code should have a docstring written in
|
|
||||||
RST, using Sphinx's `Python domain`_ [#]_:
|
|
||||||
|
|
||||||
* Functions and methods documentation should be in their own
|
|
||||||
docstring, using Sphinx's `info fields`_
|
|
||||||
|
|
||||||
For parameters types, built-in and stdlib types should be using the
|
|
||||||
combined syntax:
|
|
||||||
|
|
||||||
.. code-block:: restructuredtext
|
|
||||||
|
|
||||||
:param dict foo: what the purpose of foo is
|
|
||||||
|
|
||||||
unless a more extensive explanation needs to be given (e.g. the
|
|
||||||
specification that the input should be a list of 3-tuple needs to
|
|
||||||
use ``:type:`` even though all types involved are built-ins). Any
|
|
||||||
other type should be specified in full using the ``:type:`` field
|
|
||||||
|
|
||||||
.. code-block:: restructuredtext
|
|
||||||
|
|
||||||
:param foo: what the purpose of foo is
|
|
||||||
:type foo: some.addon.Class
|
|
||||||
|
|
||||||
Mentions of other methods (including within the same class), modules
|
|
||||||
or types in descriptions (of anything, including parameters) should
|
|
||||||
be cross-referenced.
|
|
||||||
|
|
||||||
* Classes should likewise be documented using their own docstring, and
|
|
||||||
should include the documentation of their construction (``__init__``
|
|
||||||
and ``__new__``), using the `info fields`_ as well.
|
|
||||||
|
|
||||||
* Attributes (class and instance) should be documented in their
|
|
||||||
class's docstring via the ``.. attribute::`` directive, following
|
|
||||||
the class's own documentation.
|
|
||||||
|
|
||||||
* The relation between modules and module-level attributes is similar:
|
|
||||||
modules should be documented in their own docstring, public module
|
|
||||||
attributes should be documented in the module's docstring using the
|
|
||||||
``.. data::`` directive.
|
|
||||||
|
|
||||||
Javascript API documentation
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Javascript API documentation uses JsDoc_, a javascript documentation
|
|
||||||
toolkit with a syntax similar to (and inspired by) JavaDoc's.
|
|
||||||
|
|
||||||
Due to limitations of JsDoc, the coding patterns in OpenERP Web and
|
|
||||||
the Sphinx integration, there are a few peculiarities to be aware of
|
|
||||||
when writing javascript API documentation:
|
|
||||||
|
|
||||||
* Namespaces and classes *must* be explicitly marked up even if they
|
|
||||||
are not documented, or JsDoc will not understand what they are and
|
|
||||||
will not generate documentation for their content.
|
|
||||||
|
|
||||||
As a result, the bare minimum for a namespace is:
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
/** @namespace */
|
|
||||||
foo.bar.baz = {};
|
|
||||||
|
|
||||||
while for a class it is:
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
/** @class */
|
|
||||||
foo.bar.baz.Qux = [...]
|
|
||||||
|
|
||||||
* Because the OpenERP Web project uses `John Resig's Class
|
|
||||||
implementation`_ instead of direct prototypal inheritance [#]_,
|
|
||||||
JsDoc fails to infer class scopes (and constructors or super
|
|
||||||
classes, for that matter) and has to be told explicitly.
|
|
||||||
|
|
||||||
See :ref:`js-class-doc` for the complete rundown.
|
|
||||||
|
|
||||||
* Much like the JavaDoc, JsDoc does not include a full markup
|
|
||||||
language. Instead, comments are simply marked up in HTML.
|
|
||||||
|
|
||||||
This has a number of inconvenients:
|
|
||||||
|
|
||||||
* Complex documentation comments become nigh-unreadable to read in
|
|
||||||
text editors (as opposed to IDEs, which may handle rendering
|
|
||||||
documentation comments on the fly)
|
|
||||||
|
|
||||||
* Though cross-references are supported by JsDoc (via ``@link`` and
|
|
||||||
``@see``), they only work within the JsDoc
|
|
||||||
|
|
||||||
* More general impossibility to integrate correctly with Sphinx, and
|
|
||||||
e.g. reference JavaScript objects from a tutorial, or have all the
|
|
||||||
documentation live at the same place.
|
|
||||||
|
|
||||||
As a result, JsDoc comments should be marked up using RST, not
|
|
||||||
HTML. They may use Sphinx's cross-references as well.
|
|
||||||
|
|
||||||
.. _js-class-doc:
|
|
||||||
|
|
||||||
Documenting a Class
|
|
||||||
*******************
|
|
||||||
|
|
||||||
The first task when documenting a class using JsDoc is to *mark* that
|
|
||||||
class, so JsDoc knows it can be used to instantiate objects (and, more
|
|
||||||
importantly as far as it's concerned, should be documented with
|
|
||||||
methods and attributes and stuff).
|
|
||||||
|
|
||||||
This is generally done through the ``@class`` tag, but this tag has a
|
|
||||||
significant limitation: it "believes" the constructor and the class
|
|
||||||
are one and the same [#]_. This will work for constructor-less
|
|
||||||
classes, but because OpenERP Web uses Resig's class the constructor is
|
|
||||||
not the class itself but its ``init()`` method.
|
|
||||||
|
|
||||||
Because this pattern is common in modern javascript code bases, JsDoc
|
|
||||||
supports it: it is possible to mark an arbitrary instance method as
|
|
||||||
the *class specification* by using the ``@constructs`` tag.
|
|
||||||
|
|
||||||
.. warning:: ``@constructs`` is a class specification in and of
|
|
||||||
itself, it *completely replaces* the class documentation.
|
|
||||||
|
|
||||||
Using both a class documentation (even without ``@class`` itself)
|
|
||||||
and a constructor documentation is an *error* in JsDoc and will
|
|
||||||
result in incorrect behavior and broken documentation.
|
|
||||||
|
|
||||||
The second issue is that Resig's class uses an object literal to
|
|
||||||
specify instance methods, and because JsDoc does not know anything
|
|
||||||
about Resig's class, it does not know about the role of the object
|
|
||||||
literal.
|
|
||||||
|
|
||||||
As with constructors, though, JsDoc provides a pluggable way to tell
|
|
||||||
it about methods: the ``@lends`` tag. It specifies that the object
|
|
||||||
literal "lends" its properties to the class being built.
|
|
||||||
|
|
||||||
``@lends`` must be specified right before the opening brace of the
|
|
||||||
object literal (between the opening paren of the ``#extend`` call and
|
|
||||||
the brace), and takes the full qualified name of the class being
|
|
||||||
created as a parameter, followed by the character ``#`` or by
|
|
||||||
``.prototype``. This latter part tells JsDoc these are instance
|
|
||||||
methods, not class (static) methods..
|
|
||||||
|
|
||||||
Finally, specifying a class's superclass is done through the
|
|
||||||
``@extends`` tag, which takes a fully qualified class name as a
|
|
||||||
parameter.
|
|
||||||
|
|
||||||
Here are a class without a constructor, and a class with one, so that
|
|
||||||
everything is clear (these are straight from the OpenERP Web source,
|
|
||||||
with the descriptions and irrelevant atttributes stripped):
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <Insert description here, not below>
|
|
||||||
*
|
|
||||||
* @class
|
|
||||||
* @extends openerp.base.search.Field
|
|
||||||
*/
|
|
||||||
openerp.base.search.CharField = openerp.base.search.Field.extend(
|
|
||||||
/** @lends openerp.base.search.CharField# */ {
|
|
||||||
// methods here
|
|
||||||
});
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
openerp.base.search.Widget = openerp.base.Controller.extend(
|
|
||||||
/** @lends openerp.base.search.Widget# */{
|
|
||||||
/**
|
|
||||||
* <Insert description here, not below>
|
|
||||||
*
|
|
||||||
* @constructs
|
|
||||||
* @extends openerp.base.Controller
|
|
||||||
*
|
|
||||||
* @param view the ancestor view of this widget
|
|
||||||
*/
|
|
||||||
init: function (view) {
|
|
||||||
// construction of the instance
|
|
||||||
},
|
|
||||||
// bunch of other methods
|
|
||||||
});
|
|
||||||
|
|
||||||
OpenERP Web over time
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Release process
|
|
||||||
+++++++++++++++
|
|
||||||
|
|
||||||
OpenSUSE packaging: http://blog.lowkster.com/2011/04/packaging-python-packages-in-opensuse.html
|
|
||||||
|
|
||||||
Roadmap
|
|
||||||
+++++++
|
|
||||||
|
|
||||||
Release notes
|
|
||||||
+++++++++++++
|
|
||||||
|
|
||||||
.. [#] More precisely, it iterates over all *enumerable* properties. It just
|
|
||||||
happens that built-in properties (such as ``String.indexOf`` or
|
|
||||||
``Object.toString``) are set to non-enumerable.
|
|
||||||
|
|
||||||
The enumerability of a property can be checked using
|
|
||||||
`Object.propertyIsEnumeable`_.
|
|
||||||
|
|
||||||
Before ECMAScript 5, it was not possible for user-defined properties
|
|
||||||
to be non-enumerable in a portable manner. ECMAScript 5 introduced
|
|
||||||
`Object.defineProperty`_ which lets user code create non-enumerable
|
|
||||||
properties (and more, read-only properties for instance, or implicit
|
|
||||||
getters and setters). However, support for these is not fully complete
|
|
||||||
at this point, and they are not being used in OpenERP Web code anyway.
|
|
||||||
|
|
||||||
.. [#] While using underscore is generally the preferred method (simpler,
|
|
||||||
more reliable and easier to write than a *correct* ``for…in``
|
|
||||||
iteration), it is also probably slower (due to the overhead of
|
|
||||||
calling a bunch of functions).
|
|
||||||
|
|
||||||
As a result, if you profile some code and find out that an underscore
|
|
||||||
method adds unacceptable overhead in a tight loop, you may want to
|
|
||||||
replace it with a ``for…in`` (or a regular ``for`` statement for
|
|
||||||
arrays).
|
|
||||||
|
|
||||||
.. [#] Because Python is the default domain, the ``py:`` markup prefix
|
|
||||||
is optional and should be left out.
|
|
||||||
|
|
||||||
.. [#] Resig's Class still uses prototypes under the hood, it doesn't
|
|
||||||
reimplement its own object system although it does add several
|
|
||||||
helpers such as the ``_super()`` instance method.
|
|
||||||
|
|
||||||
.. [#] Which is the case in normal Javascript semantics. Likewise, the
|
|
||||||
``.prototype`` / ``#`` pattern we will see later on is due to
|
|
||||||
JsDoc defaulting to the only behavior it can rely on: "normal"
|
|
||||||
Javascript prototype-based type creation.
|
|
||||||
|
|
||||||
.. _reStructuredText:
|
|
||||||
http://docutils.sourceforge.net/rst.html
|
|
||||||
.. _Sphinx:
|
|
||||||
http://sphinx.pocoo.org/index.html
|
|
||||||
.. _Sphinx's extensions:
|
|
||||||
http://sphinx.pocoo.org/markup/index.html
|
|
||||||
.. _Python domain:
|
|
||||||
http://sphinx.pocoo.org/domains.html#the-python-domain
|
|
||||||
.. _info fields:
|
|
||||||
http://sphinx.pocoo.org/domains.html#info-field-lists
|
|
||||||
.. _autodoc:
|
|
||||||
http://sphinx.pocoo.org/ext/autodoc.html
|
|
||||||
?highlight=autodoc#sphinx.ext.autodoc
|
|
||||||
.. _cross-references:
|
|
||||||
http://sphinx.pocoo.org/markup/inline.html#xref-syntax
|
|
||||||
.. _JsDoc:
|
|
||||||
.. _JsDoc Toolkit:
|
|
||||||
http://code.google.com/p/jsdoc-toolkit/
|
|
||||||
.. _John Resig's Class implementation:
|
|
||||||
http://ejohn.org/blog/simple-javascript-inheritance/
|
|
||||||
.. _\_.each:
|
|
||||||
http://documentcloud.github.com/underscore/#each
|
|
||||||
.. _\_.map:
|
|
||||||
http://documentcloud.github.com/underscore/#map
|
|
||||||
.. _\_.filter:
|
|
||||||
http://documentcloud.github.com/underscore/#select
|
|
||||||
.. _It is also fairly tricky to use:
|
|
||||||
https://developer.mozilla.org/en/JavaScript/Reference/Statements/for...in#Description
|
|
||||||
.. _Object.propertyIsEnumeable:
|
|
||||||
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/propertyIsEnumerable
|
|
||||||
.. _Object.defineProperty:
|
|
||||||
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty
|
|
||||||
.. _Object.hasOwnProperty:
|
|
||||||
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
|
|
|
@ -1,12 +0,0 @@
|
||||||
OpenERP Web as a widgets provider
|
|
||||||
=================================
|
|
||||||
|
|
||||||
* Using a readonly view as a widget
|
|
||||||
|
|
||||||
* Site example
|
|
||||||
* iGoogle example
|
|
||||||
* social site example e.g. Facebook app?
|
|
||||||
|
|
||||||
* Write-access widgets (e.g. contact form)
|
|
||||||
* Multiple widgets on the same page
|
|
||||||
* JSON-RPC2 API description for third-parties?
|
|
Loading…
Reference in New Issue