--- /dev/null
--- /dev/null
++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
--- /dev/null
+ # -*- 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),
+ }
--- /dev/null
--- /dev/null
++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/
--- /dev/null
--- /dev/null
++Getting Started with OpenERP Web
++================================
++
++Installing
++----------
++
++.. per-distro packaging
++
++Launching
++---------
--- /dev/null
--- /dev/null
++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
++
--- /dev/null
--- /dev/null
++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?
--- /dev/null
--- /dev/null
++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
+++++++++++++++++
--- /dev/null
--- /dev/null
++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
--- /dev/null
--- /dev/null
++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?
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:
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>
+++ /dev/null
--#!/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: