[MERGE] from trunk
authorXavier Morel <xmo@openerp.com>
Wed, 7 Nov 2012 11:25:16 +0000 (12:25 +0100)
committerXavier Morel <xmo@openerp.com>
Wed, 7 Nov 2012 11:25:16 +0000 (12:25 +0100)
bzr revid: xmo@openerp.com-20121107112516-64hqps4jjgmrs3a4

12 files changed:
1  2 
addons/web/doc/addons.rst
addons/web/doc/conf.py
addons/web/doc/development.rst
addons/web/doc/getting-started.rst
addons/web/doc/guides/sidebar-protocol.rst
addons/web/doc/old-version.rst
addons/web/doc/production.rst
addons/web/doc/project.rst
addons/web/doc/widgets.rst
addons/web/static/src/js/search.js
addons/web/static/src/xml/base.xml
setup.py

index 0000000,0000000..da1f1f2
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,558 @@@
++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
++---------------------------
++
++Widget
++++++++
++
++This is the base class for all visual components. It provides a number of
++services for the management of a DOM subtree:
++
++* Rendering with QWeb
++
++* Parenting-child relations
++
++* Life-cycle management (including facilitating children destruction when a
++  parent object is removed)
++
++* DOM insertion, via jQuery-powered insertion methods. Insertion targets can
++  be anything the corresponding jQuery method accepts (generally selectors,
++  DOM nodes and jQuery objects):
++
++  :js:func:`~openerp.base.Widget.appendTo`
++    Renders the widget and inserts it as the last child of the target, uses
++    `.appendTo()`_
++
++  :js:func:`~openerp.base.Widget.prependTo`
++    Renders the widget and inserts it as the first child of the target, uses
++    `.prependTo()`_
++
++  :js:func:`~openerp.base.Widget.insertAfter`
++    Renders the widget and inserts it as the preceding sibling of the target,
++    uses `.insertAfter()`_
++
++  :js:func:`~openerp.base.Widget.insertBefore`
++    Renders the widget and inserts it as the following sibling of the target,
++    uses `.insertBefore()`_
++
++:js:class:`~openerp.base.Widget` inherits from
++:js:class:`~openerp.base.SessionAware`, so subclasses can easily access the
++RPC layers.
++
++Subclassing Widget
++~~~~~~~~~~~~~~~~~~
++
++:js:class:`~openerp.base.Widget` is subclassed in the standard manner (via the
++:js:func:`~openerp.base.Class.extend` method), and provides a number of
++abstract properties and concrete methods (which you may or may not want to
++override). Creating a subclass looks like this:
++
++.. code-block:: javascript
++
++    var MyWidget = openerp.base.Widget.extend({
++        // QWeb template to use when rendering the object
++        template: "MyQWebTemplate",
++
++        init: function(parent) {
++            this._super(parent);
++            // insert code to execute before rendering, for object
++            // initialization
++        },
++        start: function() {
++            this._super();
++            // post-rendering initialization code, at this point
++            // ``this.$element`` has been initialized
++            this.$element.find(".my_button").click(/* an example of event binding * /);
++
++            // if ``start`` is asynchronous, return a promise object so callers
++            // know when the object is done initializing
++            return this.rpc(/* … */)
++        }
++    });
++
++The new class can then be used in the following manner:
++
++.. code-block:: javascript
++
++    // Create the instance
++    var my_widget = new MyWidget(this);
++    // Render and insert into DOM
++    my_widget.appendTo(".some-div");
++
++After these two lines have executed (and any promise returned by ``appendTo``
++has been resolved if needed), the widget is ready to be used.
++
++.. note:: the insertion methods will start the widget themselves, and will
++          return the result of :js:func:`~openerp.base.Widget.start()`.
++
++          If for some reason you do not want to call these methods, you will
++          have to first call :js:func:`~openerp.base.Widget.render()` on the
++          widget, then insert it into your DOM and start it.
++
++If the widget is not needed anymore (because it's transient), simply terminate
++it:
++
++.. code-block:: javascript
++
++    my_widget.stop();
++
++will unbind all DOM events, remove the widget's content from the DOM and
++destroy all widget data.
++
++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/
++
++.. _.appendTo():
++    http://api.jquery.com/appendTo/
++
++.. _.prependTo():
++    http://api.jquery.com/prependTo/
++
++.. _.insertAfter():
++    http://api.jquery.com/insertAfter/
++
++.. _.insertBefore():
++    http://api.jquery.com/insertBefore/
++
++.. _Rosetta:
++.. _Launchpad's own translation tool:
++    https://help.launchpad.net/Translations
index 0000000,ac5b890..83fc969
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,257 +1,257 @@@
+ # -*- coding: utf-8 -*-
+ #
+ # OpenERP Technical Documentation configuration file, created by
+ # sphinx-quickstart on Fri Feb 17 16:14:06 2012.
+ #
+ # This file is execfile()d with the current directory set to its containing dir.
+ #
+ # Note that not all possible configuration values are present in this
+ # autogenerated file.
+ #
+ # All configuration values have a default; values that are commented out
+ # serve to show the default.
+ import sys, os
+ # If extensions (or modules to document with autodoc) are in another directory,
+ # add these directories to sys.path here. If the directory is relative to the
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
 -#sys.path.insert(0, os.path.abspath('.'))
+ sys.path.append(os.path.abspath('_themes'))
 -sys.path.append(os.path.abspath('..'))
 -sys.path.append(os.path.abspath('../openerp'))
++sys.path.insert(0, os.path.abspath('../addons'))
++sys.path.insert(0, os.path.abspath('..'))
+ # -- General configuration -----------------------------------------------------
+ # If your documentation needs a minimal Sphinx version, state it here.
+ #needs_sphinx = '1.0'
+ # Add any Sphinx extension module names here, as strings. They can be extensions
+ # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.viewcode']
+ # Add any paths that contain templates here, relative to this directory.
+ templates_path = ['_templates']
+ # The suffix of source filenames.
+ source_suffix = '.rst'
+ # The encoding of source files.
+ #source_encoding = 'utf-8-sig'
+ # The master toctree document.
+ master_doc = 'index'
+ # General information about the project.
 -project = u'OpenERP Server Developers Documentation'
++project = u'OpenERP Web Developers Documentation'
+ copyright = u'2012, OpenERP s.a.'
+ # The version info for the project you're documenting, acts as replacement for
+ # |version| and |release|, also used in various other places throughout the
+ # built documents.
+ #
+ # The short X.Y version.
+ version = '7.0'
+ # The full version, including alpha/beta/rc tags.
 -release = '7.0b'
++release = '7.0'
+ # The language for content autogenerated by Sphinx. Refer to documentation
+ # for a list of supported languages.
+ #language = None
+ # There are two options for replacing |today|: either, you set today to some
+ # non-false value, then it is used:
+ #today = ''
+ # Else, today_fmt is used as the format for a strftime call.
+ #today_fmt = '%B %d, %Y'
+ # List of patterns, relative to source directory, that match files and
+ # directories to ignore when looking for source files.
+ exclude_patterns = ['_build']
+ # The reST default role (used for this markup: `text`) to use for all documents.
+ #default_role = None
+ # If true, '()' will be appended to :func: etc. cross-reference text.
+ #add_function_parentheses = True
+ # If true, the current module name will be prepended to all description
+ # unit titles (such as .. function::).
+ #add_module_names = True
+ # If true, sectionauthor and moduleauthor directives will be shown in the
+ # output. They are ignored by default.
+ #show_authors = False
+ # The name of the Pygments (syntax highlighting) style to use.
+ pygments_style = 'sphinx'
+ # A list of ignored prefixes for module index sorting.
+ #modindex_common_prefix = []
+ # -- Options for HTML output ---------------------------------------------------
+ # The theme to use for HTML and HTML Help pages.  See the documentation for
+ # a list of builtin themes.
+ html_theme = 'flask'
+ # Theme options are theme-specific and customize the look and feel of a theme
+ # further.  For a list of options available for each theme, see the
+ # documentation.
+ #html_theme_options = {}
+ # Add any paths that contain custom themes here, relative to this directory.
+ html_theme_path = ['_themes']
+ # The name for this set of Sphinx documents.  If None, it defaults to
+ # "<project> v<release> documentation".
+ #html_title = None
+ # A shorter title for the navigation bar.  Default is the same as html_title.
+ #html_short_title = None
+ # The name of an image file (relative to this directory) to place at the top
+ # of the sidebar.
+ #html_logo = None
+ # The name of an image file (within the static path) to use as favicon of the
+ # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+ # pixels large.
+ #html_favicon = None
+ # Add any paths that contain custom static files (such as style sheets) here,
+ # relative to this directory. They are copied after the builtin static files,
+ # so a file named "default.css" will overwrite the builtin "default.css".
+ html_static_path = ['_static']
+ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+ # using the given strftime format.
+ #html_last_updated_fmt = '%b %d, %Y'
+ # If true, SmartyPants will be used to convert quotes and dashes to
+ # typographically correct entities.
+ #html_use_smartypants = True
+ # Custom sidebar templates, maps document names to template names.
+ html_sidebars = {
+     'index':    ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
+     '**':       ['sidebarlogo.html', 'localtoc.html', 'relations.html',
+                  'sourcelink.html', 'searchbox.html']
+ }
+ # Additional templates that should be rendered to pages, maps page names to
+ # template names.
+ #html_additional_pages = {}
+ # If false, no module index is generated.
+ #html_domain_indices = True
+ # If false, no index is generated.
+ #html_use_index = True
+ # If true, the index is split into individual pages for each letter.
+ #html_split_index = False
+ # If true, links to the reST sources are added to the pages.
+ #html_show_sourcelink = True
+ # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+ #html_show_sphinx = True
+ # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+ #html_show_copyright = True
+ # If true, an OpenSearch description file will be output, and all pages will
+ # contain a <link> tag referring to it.  The value of this option must be the
+ # base URL from which the finished HTML is served.
+ #html_use_opensearch = ''
+ # This is the file name suffix for HTML files (e.g. ".xhtml").
+ #html_file_suffix = None
+ # Output file base name for HTML help builder.
 -htmlhelp_basename = 'openerp-server-doc'
++htmlhelp_basename = 'openerp-web-doc'
+ # -- Options for LaTeX output --------------------------------------------------
+ latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #'papersize': 'letterpaper',
+ # The font size ('10pt', '11pt' or '12pt').
+ #'pointsize': '10pt',
+ # Additional stuff for the LaTeX preamble.
+ #'preamble': '',
+ }
+ # Grouping the document tree into LaTeX files. List of tuples
+ # (source start file, target name, title, author, documentclass [howto/manual]).
+ latex_documents = [
 -  ('index', 'openerp-server-doc.tex', u'OpenERP Server Developers Documentation',
++  ('index', 'openerp-web-doc.tex', u'OpenERP Web Developers Documentation',
+    u'OpenERP s.a.', 'manual'),
+ ]
+ # The name of an image file (relative to this directory) to place at the top of
+ # the title page.
+ #latex_logo = None
+ # For "manual" documents, if this is true, then toplevel headings are parts,
+ # not chapters.
+ #latex_use_parts = False
+ # If true, show page references after internal links.
+ #latex_show_pagerefs = False
+ # If true, show URL addresses after external links.
+ #latex_show_urls = False
+ # Documents to append as an appendix to all manuals.
+ #latex_appendices = []
+ # If false, no module index is generated.
+ #latex_domain_indices = True
+ # -- Options for manual page output --------------------------------------------
+ # One entry per manual page. List of tuples
+ # (source start file, name, description, authors, manual section).
+ man_pages = [
 -    ('index', 'openerp-server-doc', u'OpenERP Server Developers Documentation',
++    ('index', 'openerp-web-doc', u'OpenERP Web Developers Documentation',
+      [u'OpenERP s.a.'], 1)
+ ]
+ # If true, show URL addresses after external links.
+ #man_show_urls = False
+ # -- Options for Texinfo output ------------------------------------------------
+ # Grouping the document tree into Texinfo files. List of tuples
+ # (source start file, target name, title, author,
+ #  dir menu entry, description, category)
+ texinfo_documents = [
 -  ('index', 'OpenERPServerDocumentation', u'OpenERP Server Developers Documentation',
 -   u'OpenERP s.a.', 'OpenERPServerDocumentation', 'Developers documentation for the openobject-server project.',
++  ('index', 'OpenERPWebDocumentation', u'OpenERP Web Developers Documentation',
++   u'OpenERP s.a.', 'OpenERPWebDocumentation', 'Developers documentation for the openerp-web project.',
+    'Miscellaneous'),
+ ]
+ # Documents to append as an appendix to all manuals.
+ #texinfo_appendices = []
+ # If false, no module index is generated.
+ #texinfo_domain_indices = True
+ # How to display URL addresses: 'footnote', 'no', or 'inline'.
+ #texinfo_show_urls = 'footnote'
++todo_include_todos = True
+ # Example configuration for intersphinx: refer to the Python standard library.
+ intersphinx_mapping = {
+     'python': ('http://docs.python.org/', None),
 -    'openerpweb': ('http://doc.openerp.com/trunk/developers/web', None),
++    'openerpserver': ('http://doc.openerp.com/trunk/developers/server', None),
+     'openerpdev': ('http://doc.openerp.com/trunk/developers', None),
+ }
index 0000000,0000000..7431a3e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,424 @@@
++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/
index 0000000,0000000..cd7f7ee
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,10 @@@
++Getting Started with OpenERP Web
++================================
++
++Installing
++----------
++
++.. per-distro packaging
++
++Launching
++---------
index 0000000,0000000..83626c3
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,78 @@@
++Adding a sidebar to a view
++==========================
++
++Initialization
++--------------
++
++Each view has the responsibility to create its sidebar (or not) if and only if
++the ``sidebar`` flag is set in its options.
++
++In that case, it should use the ``sidebar_id`` value (from its options) to
++initialize the sidebar at the right position in the DOM:
++
++.. code-block:: javascript
++
++    if (this.options.sidebar && this.options.sidebar_id) {
++        this.sidebar = new openerp.web.Sidebar(this, this.options.sidebar_id);
++        this.sidebar.start();
++    }
++
++Because the sidebar is an old-style widget, it must be started after being
++initialized.
++
++Sidebar communication protocol
++------------------------------
++
++In order to behave correctly, a sidebar needs informations from its parent
++view.
++
++This information is extracted via a very basic protocol consisting of a
++property and two methods:
++
++.. js:attribute:: dataset
++
++    the view's dataset, used to fetch the currently active model and provide it
++    to remote action handlers as part of the basic context
++
++.. js:function:: get_selected_ids()
++
++    Used to query the parent view for the set of currently available record
++    identifiers. Used to setup the basic context's ``active_id`` and
++    ``active_ids`` keys.
++
++    .. warning::
++
++        :js:func:`get_selected_ids` must return at least one id
++
++    :returns: an array of at least one id
++    :rtype: Array<Number>
++
++.. js:function:: sidebar_context()
++
++    Queries the view for additional context data to provide to the sidebar.
++
++    :js:class:`~openerp.base.View` provides a default NOOP implementation,
++    which simply resolves to an empty object.
++
++    :returns: a promise yielding an object on success, this object is mergeed
++              into the sidebar's own context
++    :rtype: $.Deferred<Object>
++
++Programmatic folding and unfolding
++----------------------------------
++
++The sidebar object starts folded. It provides three methods to handle its
++folding status:
++
++.. js:function:: do_toggle
++
++    Toggles the status of the sidebar
++
++.. js:function:: do_fold
++
++    Forces the sidebar closed if it's currently open
++
++.. js:function:: do_unfold
++
++    Forces the sidebar open if it's currently closed
++
index 0000000,0000000..d745806
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,11 @@@
++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?
index 0000000,0000000..2524471
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,47 @@@
++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
+++++++++++++++++
index 0000000,0000000..aa465aa
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,451 @@@
++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
index 0000000,0000000..20e51db
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,12 @@@
++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?
@@@ -615,7 -624,17 +624,17 @@@ instance.web.SearchView = instance.web.
              return null;
          }
      },
-     on_loaded: function(data) {
+     add_common_inputs: function() {
+         // add Filters to this.inputs, need view.controls filled
+         (new instance.web.search.Filters(this));
+         // add custom filters to this.inputs
 -        (new instance.web.search.CustomFilters(this));
++        this.custom_filters = new instance.web.search.CustomFilters(this);
+         // add Advanced to this.inputs
+         (new instance.web.search.Advanced(this));
+     },
+     search_view_loaded: function(data) {
          var self = this;
          this.fields_view = data.fields_view;
          if (data.fields_view.type !== 'search' ||
          // build drawer
          var drawer_started = $.when.apply(
              null, _(this.select_for_drawer()).invoke(
-                 'appendTo', this.$element.find('.oe_searchview_drawer')));
+                 'appendTo', this.$('.oe_searchview_drawer')));
+         
          // load defaults
          var defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
-                 'facet_for_defaults', this.defaults))
-             .pipe(this.proxy('setup_default_query'));
+             'facet_for_defaults', this.defaults)).then(function () {
+                 self.query.reset(_(arguments).compact(), {preventSearch: true});
+             });
 -        
 +
          return $.when(drawer_started, defaults_fetched)
-             .then(function () { self.ready.resolve(); })
+             .then(function () { 
+                 self.trigger("search_view_loaded", data);
+                 self.ready.resolve();
+             });
      },
 +    setup_default_query: function () {
 +        // Hacky implementation of CustomFilters#facet_for_defaults ensure
 +        // CustomFilters will be ready (and CustomFilters#filters will be
 +        // correctly filled) by the time this method executes.
 +        var custom_filters = this.custom_filters.filters;
 +        if (!_(custom_filters).isEmpty()) {
 +            // Check for any is_default custom filter
 +            var personal_filter = _(custom_filters).find(function (filter) {
 +                return filter.user_id && filter.is_default;
 +            });
 +            if (personal_filter) {
 +                this.query.reset([this.custom_filters.facet_for(personal_filter)],
 +                                 {preventSearch: true});
 +                return;
 +            }
 +
 +            var global_filter = _(custom_filters).find(function (filter) {
 +                return !filter.user_id && filter.is_default;
 +            });
 +            if (global_filter) {
 +                this.query.reset([this.custom_filters.facet_for(global_filter)],
 +                                 {preventSearch: true});
 +                return;
 +            }
 +        }
 +        // No custom filter, or no is_default custom filter, apply view defaults
 +        this.query.reset(_(arguments).compact(), {preventSearch: true});
 +    },
      /**
-      * Handle event when the user make a selection in the filters management select box.
-      */
-     on_filters_management: function(e) {
-         var self = this;
-         var select = this.$element.find(".oe_search-view-filters-management");
-         var val = select.val();
-         switch(val) {
-         case 'advanced_filter':
-             this.extended_search.on_activate();
-             break;
-         case 'add_to_dashboard':
-             this.on_add_to_dashboard();
-             break;
-         case '':
-             this.do_clear();
-         }
-         if (val.slice(0, 4) == "get:") {
-             val = val.slice(4);
-             val = parseInt(val, 10);
-             var filter = this.managed_filters[val];
-             this.do_clear(false).then(_.bind(function() {
-                 select.val('get:' + val);
-                 var groupbys = [];
-                 var group_by = filter.context.group_by;
-                 if (group_by) {
-                     groupbys = _.map(
-                         group_by instanceof Array ? group_by : group_by.split(','),
-                         function (el) { return { group_by: el }; });
-                 }
-                 this.filter_data = {
-                     domains: [filter.domain],
-                     contexts: [filter.context],
-                     groupbys: groupbys
-                 };
-                 this.do_search();
-             }, this));
-         } else {
-             select.val('');
-         }
-     },
-     on_add_to_dashboard: function() {
-         this.$element.find(".oe_search-view-filters-management")[0].selectedIndex = 0;
-         var self = this,
-             menu = instance.webclient.menu,
-             $dialog = $(QWeb.render("SearchView.add_to_dashboard", {
-                 dashboards : menu.data.data.children,
-                 selected_menu_id : menu.$element.find('a.active').data('menu')
-             }));
-         $dialog.find('input').val(this.fields_view.name);
-         instance.web.dialog($dialog, {
-             modal: true,
-             title: _t("Add to Dashboard"),
-             buttons: [
-                 {text: _t("Cancel"), click: function() {
-                     $(this).dialog("close");
-                 }},
-                 {text: _t("OK"), click: function() {
-                     $(this).dialog("close");
-                     var menu_id = $(this).find("select").val(),
-                         title = $(this).find("input").val(),
-                         data = self.build_search_data(),
-                         context = new instance.web.CompoundContext(),
-                         domain = new instance.web.CompoundDomain();
-                     _.each(data.contexts, function(x) {
-                         context.add(x);
-                     });
-                     _.each(data.domains, function(x) {
-                            domain.add(x);
-                     });
-                     self.rpc('/web/searchview/add_to_dashboard', {
-                         menu_id: menu_id,
-                         action_id: self.getParent().action.id,
-                         context_to_save: context,
-                         domain: domain,
-                         view_mode: self.getParent().active_view,
-                         name: title
-                     }, function(r) {
-                         if (r === false) {
-                             self.do_warn("Could not add filter to dashboard");
-                         } else {
-                             self.do_notify("Filter added to dashboard", '');
-                         }
-                     });
-                 }}
-             ]
-         });
-     },
-     /**
       * Extract search data from the view's facets.
       *
       * Result is an object with 4 (own) properties:
@@@ -1571,80 -1485,37 +1517,90 @@@ instance.web.search.CustomFilters = ins
          var self = this;
          this.model = new instance.web.Model('ir.filters');
          this.filters = {};
 +        this.$filters = {};
-         this.$element.on('submit', 'form', this.proxy('save_current'));
-         this.$element.on('click', 'h4', function () {
-             self.$element.toggleClass('oe_opened');
+         this.view.query
+             .on('remove', function (facet) {
+                 if (!facet.get('is_custom_filter')) {
+                     return;
+                 }
+                 self.clear_selection();
+             })
+             .on('reset', this.proxy('clear_selection'));
+         this.$el.on('submit', 'form', this.proxy('save_current'));
+         this.$el.on('click', 'h4', function () {
+             self.$el.toggleClass('oe_opened');
          });
          // FIXME: local eval of domain and context to get rid of special endpoint
          return this.rpc('/web/searchview/get_filters', {
              model: this.view.model
 -        }).pipe(this.proxy('set_filters'));
 +        }).pipe(this.proxy('set_filters'))
 +            .then(function () {
 +                self.is_ready.resolve(null);
 +            }, function () {
 +                self.is_ready.reject();
 +            });
 +    },
 +    /**
 +     * Special implementation delaying defaults until CustomFilters is loaded
 +     */
 +    facet_for_defaults: function () {
 +        return this.is_ready;
 +    },
 +    /**
 +     * Generates a mapping key (in the filters and $filter mappings) for the
 +     * filter descriptor object provided (as returned by ``get_filters``).
 +     *
 +     * The mapping key is guaranteed to be unique for a given (user_id, name)
 +     * pair.
 +     *
 +     * @param {Object} filter
 +     * @param {String} filter.name
 +     * @param {Number|Pair<Number, String>} [filter.user_id]
 +     * @return {String} mapping key corresponding to the filter
 +     */
 +    key_for: function (filter) {
 +        var user_id = filter.user_id;
 +        var uid = (user_id instanceof Array) ? user_id[0] : user_id;
 +        return _.str.sprintf('(%s)%s', uid, filter.name);
 +    },
 +    /**
 +     * Generates a :js:class:`~instance.web.search.Facet` descriptor from a
 +     * filter descriptor
 +     *
 +     * @param {Object} filter
 +     * @param {String} filter.name
 +     * @param {Object} [filter.context]
 +     * @param {Array} [filter.domain]
 +     * @return {Object}
 +     */
 +    facet_for: function (filter) {
 +        return {
-             category: _("Custom Filter"),
++            category: _t("Custom Filter"),
 +            icon: 'M',
 +            field: {
 +                get_context: function () { return filter.context; },
 +                get_groupby: function () { return [filter.context]; },
 +                get_domain: function () { return filter.domain; }
 +            },
-             values: [
-                 {label: filter.name, value: null}
-             ]
++            is_custom_filter: true,
++            values: [{label: filter.name, value: null}]
 +        };
      },
+     clear_selection: function () {
+         this.$('li.oe_selected').removeClass('oe_selected');
+     },
      append_filter: function (filter) {
          var self = this;
 -        var key = _.str.sprintf('(%s)%s', filter.user_id, filter.name);
 +        var key = this.key_for(filter);
  
          var $filter;
 -        if (key in this.filters) {
 -            $filter = this.filters[key];
 +        if (key in this.$filters) {
 +            $filter = this.$filters[key];
          } else {
              var id = filter.id;
 -            $filter = this.filters[key] = $('<li></li>')
 +            this.filters[key] = filter;
 +            $filter = this.$filters[key] = $('<li></li>')
-                 .appendTo(this.$element.find('.oe_searchview_custom_list'))
+                 .appendTo(this.$('.oe_searchview_custom_list'))
                  .addClass(filter.user_id ? 'oe_searchview_custom_private'
                                           : 'oe_searchview_custom_public')
                  .text(filter.name);
          }
  
          $filter.unbind('click').click(function () {
 -            self.view.query.reset([{
 -                category: _t("Custom Filter"),
 -                icon: 'M',
 -                field: {
 -                    get_context: function () { return filter.context; },
 -                    get_groupby: function () { return [filter.context]; },
 -                    get_domain: function () { return filter.domain; }
 -                },
 -                is_custom_filter: true,
 -                values: [{label: filter.name, value: null}]
 -            }]);
 +            self.view.query.reset([self.facet_for(filter)]);
+             $filter.addClass('oe_selected');
          });
      },
      set_filters: function (filters) {
      },
      save_current: function () {
          var self = this;
-         var $name = this.$element.find('input:first');
-         var private_filter = !this.$element.find(
-                 'input#oe_searchview_custom_public').prop('checked');
-         var set_as_default = this.$element.find(
-                 'input#oe_searchview_custom_default').prop('checked');
+         var $name = this.$('input:first');
 -        var private_filter = !this.$('input:last').prop('checked');
++        var private_filter = !this.$('#oe_searchview_custom_public').prop('checked');
++        var set_as_default = this.$('#oe_searchview_custom_default').prop('checked');
  
          var search = this.view.build_search_data();
          this.rpc('/web/session/eval_domain_and_context', {
              }
              var filter = {
                  name: $name.val(),
-                 user_id: private_filter ? instance.connection.uid : false,
+                 user_id: private_filter ? instance.session.uid : false,
                  model_id: self.view.model,
                  context: results.context,
 -                domain: results.domain
 +                domain: results.domain,
 +                is_default: set_as_default
              };
              // FIXME: current context?
              return self.model.call('create_or_replace', [filter]).then(function (id) {
          </t>
      </t>
  </t>
- <div t-name="SearchView.Filters" class="oe_searchview_filters">
+ <div t-name="SearchView.Filters" class="oe_searchview_filters oe_searchview_section">
  
  </div>
- <div t-name="SearchView.CustomFilters" class="oe_searchview_custom">
-     <ul class="oe_searchview_custom_list"/>
-     <h4 class="oe_searchview_custom_title">
-         <label for="oe_searchview_custom_input">Save search</label></h4>
-     <form>
-         <input id="oe_searchview_custom_input"/>
-         <button>Save</button><br/>
-         <label for="oe_searchview_custom_public">Share with all users:</label>
-         <input id="oe_searchview_custom_public" type="checkbox"/>
-         <label for="oe_searchview_custom_default">Use by default:</label>
-         <input id="oe_searchview_custom_default" type="checkbox"/>
-     </form>
+ <div t-name="SearchView.CustomFilters" class="oe_searchview_custom oe_searchview_section">
+     <div>
+         <h3><span class="oe_i">M</span> Custom Filters</h3>
+         <ul class="oe_searchview_custom_list"/>
+         <div class="oe_searchview_custom">
 -          <h4>Save current filter</h4>
 -          <form>
 -            <p><input id="oe_searchview_custom_input" placeholder="Filter name"/></p>
 -            <p><input id="oe_searchview_custom_public" type="checkbox"/>
 -              <label for="oe_searchview_custom_public">Share with all users</label></p>
 -            <button>Save</button>
 -          </form>
++            <h4>Save current filter</h4>
++            <form>
++                <p><input id="oe_searchview_custom_input" placeholder="Filter name"/></p>
++                <p>
++                    <input id="oe_searchview_custom_public" type="checkbox"/>
++                    <label for="oe_searchview_custom_public">Share with all users</label>
++                    <input id="oe_searchview_custom_default" type="checkbox"/>
++                    <label for="oe_searchview_custom_default">Use by default</label>
++                </p>
++                <button>Save</button>
++            </form>
+         </div>
+     </div>
 -    <div>
 -    </div>
  </div>
  <div t-name="SearchView.advanced" class="oe_searchview_advanced">
      <h4>Advanced Search</h4>
      <form>
diff --cc setup.py
index 6e1adad,45f19ea..0000000
deleted file mode 100755,100755
+++ /dev/null
@@@ -1,126 -1,139 +1,0 @@@
--#!/usr/bin/env python
--# -*- coding: utf-8 -*-
--##############################################################################
--#
--#    OpenERP, Open Source Management Solution
--#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
--#
--#    This program is free software: you can redistribute it and/or modify
--#    it under the terms of the GNU Affero General Public License as
--#    published by the Free Software Foundation, either version 3 of the
--#    License, or (at your option) any later version.
--#
--#    This program is distributed in the hope that it will be useful,
--#    but WITHOUT ANY WARRANTY; without even the implied warranty of
--#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--#    GNU Affero General Public License for more details.
--#
--#    You should have received a copy of the GNU Affero General Public License
--#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
--#
--##############################################################################
--
--import glob, os, re, setuptools, sys
--from os.path import join, isfile
--
--# List all data files
--def data():
--    files = []
--    for root, dirnames, filenames in os.walk('openerp'):
--        for filename in filenames:
--            if not re.match(r'.*(\.pyc|\.pyo|\~)$',filename):
--                files.append(os.path.join(root, filename))
--    d = {}
--    for v in files:
--        k=os.path.dirname(v)
--        if k in d:
--            d[k].append(v)
--        else:
--            d[k]=[v]
--    r = d.items()
--    if os.name == 'nt':
--        r.append(("Microsoft.VC90.CRT", glob.glob('C:\Microsoft.VC90.CRT\*.*')))
--
--    import babel
--    r.append(("localedata",
--              glob.glob(os.path.join(os.path.dirname(babel.__file__), "localedata" , '*'))))
--
--    return r
--
--def gen_manifest():
--    file_list="\n".join(data())
--    open('MANIFEST','w').write(file_list)
--
--if os.name == 'nt':
--    sys.path.append("C:\Microsoft.VC90.CRT")
--
--def py2exe_options():
--    if os.name == 'nt':
--        import py2exe
--        return {
--            "console" : [ { "script": "openerp-server", "icon_resources": [(1, join("install","openerp-icon.ico"))], }],
--            'options' : {
--                "py2exe": {
--                    "skip_archive": 1,
--                    "optimize": 2,
--                    "dist_dir": 'dist',
--                    "packages": [ "DAV", "HTMLParser", "PIL", "asynchat", "asyncore", "commands", "dateutil", "decimal", "email", "encodings", "imaplib", "lxml", "lxml._elementpath", "lxml.builder", "lxml.etree", "lxml.objectify", "mako", "openerp", "poplib", "pychart", "pydot", "pyparsing", "reportlab", "select", "simplejson", "smtplib", "uuid", "vatnumber", "vobject", "xml", "xml.dom", "yaml", ],
--                    "excludes" : ["Tkconstants","Tkinter","tcl"],
--                }
--            }
--        }
--    else:
--        return {}
--
--execfile(join(os.path.dirname(__file__), 'openerp', 'release.py'))
--
- setuptools.setup(
-       name             = 'openerp',
-       version          = version,
-       description      = description,
-       long_description = long_desc,
-       url              = url,
-       author           = author,
-       author_email     = author_email,
-       classifiers      = filter(None, classifiers.split("\n")),
-       license          = license,
-       scripts          = ['openerp-server'],
-       data_files       = data(),
-       packages         = setuptools.find_packages(),
-       #include_package_data = True,
-       install_requires = [
-         # TODO the pychart package we include in openerp corresponds to PyChart 1.37.
-         # It seems there is a single difference, which is a spurious print in generate_docs.py.
-         # It is probably safe to move to PyChart 1.39 (the latest one).
-         # (Let setup.py choose the latest one, and we should check we can remove pychart from
-         # our tree.) http://download.gna.org/pychart/
-         # TODO  'pychart',
-           'babel',
-           'feedparser',
-           'gdata',
-           'lxml',
-           'mako',
-           'psycopg2',
-           'pydot',
-           'python-dateutil < 2',
-           'python-ldap',
-           'python-openid',
-           'pytz',
-           'pywebdav',
-           'pyyaml',
-           'reportlab',
-           'simplejson',
-           'vatnumber',
-           'vobject',
-           'werkzeug',
-           'xlwt',
-           'zsi',
-       ],
-       extras_require = {
-           'SSL' : ['pyopenssl'],
-       },
-       **py2exe_options()
- )
- # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
 -# Notes for OpenERP developer on windows:
 -#
 -# To setup a windows developer evironement install python2.7 then pip and use
 -# "pip install <depencey>" for every dependency listed below.
 -#
 -# Dependecies that requires DLLs are not installable with pip install, for
 -# them we added comments with links where you can find the installers.
 -#
 -# OpenERP on windows also require the pywin32, the binary can be found at
 -# http://pywin32.sf.net
 -#
 -# Both python2.7 32bits and 64bits are known to work.
 -
 -setuptools.setup(
 -      name             = 'openerp',
 -      version          = version,
 -      description      = description,
 -      long_description = long_desc,
 -      url              = url,
 -      author           = author,
 -      author_email     = author_email,
 -      classifiers      = filter(None, classifiers.split("\n")),
 -      license          = license,
 -      scripts          = ['openerp-server'],
 -      data_files       = data(),
 -      packages         = setuptools.find_packages(),
 -      dependency_links = ['http://download.gna.org/pychart/'],
 -      #include_package_data = True,
 -      install_requires = [
 -          'pychart', # not on pypi, use: pip install http://download.gna.org/pychart/PyChart-1.39.tar.gz
 -          'babel',
 -          'docutils',
 -          'feedparser',
 -          'gdata',
 -          'lxml < 3', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
 -          'mako',
 -          'PIL', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/
 -          'psutil', # windows binary code.google.com/p/psutil/downloads/list
 -          'psycopg2',
 -          'pydot',
 -          'python-dateutil < 2',
 -          'python-ldap', # optional
 -          'python-openid',
 -          'pytz',
 -          'pywebdav',
 -          'pyyaml',
 -          'reportlab', # windows binary pypi.python.org/pypi/reportlab
 -          'simplejson',
 -          'vatnumber',
 -          'vobject',
 -          'werkzeug',
 -          'xlwt',
 -          'zsi', # optional
 -      ],
 -      extras_require = {
 -          'SSL' : ['pyopenssl'],
 -      },
 -      tests_require = ['unittest2'],
 -      **py2exe_options()
 -)
 -
 -
 -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: