summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2017-10-10 19:14:30 +0200
committerChristophe Simonis <chs@odoo.com>2017-10-10 19:14:30 +0200
commit0be264b29eac11a5dec5e6565d23d4ea86bd905a (patch)
tree79362f2340c89cb3cf2e8d8a467f357b7c7a8326 /doc
parentcf753a7fdf1739dcf37d2ad74f4c8efb3d4ff3b3 (diff)
parente1104ac344cbc349a46b22339b13dfe9cbab8bcb (diff)
[MERGE] forward port branch 11.0 up to e1104ac344c
Diffstat (limited to 'doc')
-rw-r--r--doc/_extensions/autojsdoc/__main__.py6
-rw-r--r--doc/_extensions/autojsdoc/parser/jsdoc.py2
-rw-r--r--doc/_extensions/autojsdoc/parser/parser.py25
-rw-r--r--doc/_extensions/autojsdoc/parser/utils.py6
-rw-r--r--doc/_extensions/odoo_ext/static/layout.less4
-rw-r--r--doc/_extensions/odoo_ext/static/style.css24
-rw-r--r--doc/_extensions/odoo_ext/translator.py9
-rw-r--r--doc/_extensions/pyjsdoc/LICENSE174
-rw-r--r--doc/_extensions/pyjsdoc/__init__.py408
-rw-r--r--doc/_extensions/pyjsparser/parser.py2
-rw-r--r--doc/_extensions/pyjsparser/pyjsparserdata.py2
-rw-r--r--doc/cla/corporate/eficent.md3
-rw-r--r--doc/cla/corporate/equitania.md21
-rw-r--r--doc/cla/corporate/merchise.md2
-rw-r--r--doc/cla/corporate/quartile.md14
-rw-r--r--doc/cla/corporate/sewisoft.md16
-rw-r--r--doc/cla/corporate/syleam.md5
-rw-r--r--doc/cla/corporate/tvtmarine-automation.md4
-rw-r--r--doc/cla/corporate/vauxoo.md1
-rw-r--r--doc/cla/corporate/xoe-corp.md15
-rw-r--r--doc/cla/individual/bazemoreb.md11
-rw-r--r--doc/cla/individual/blaggacao.md12
-rw-r--r--doc/cla/individual/danger89.md11
-rw-r--r--doc/cla/individual/hansmi.md11
-rw-r--r--doc/cla/individual/stephanrozendaal.md11
-rwxr-xr-xdoc/cla/stats.py19
-rw-r--r--doc/howtos/backend/exercise-access-rules2
-rw-r--r--doc/howtos/backend/exercise-demo4
-rw-r--r--doc/howtos/backend/exercise-domain-advanced2
-rw-r--r--doc/howtos/backend/exercise-formview2
-rw-r--r--doc/howtos/backend/exercise-gantt2
-rw-r--r--doc/howtos/backend/exercise-graph2
-rw-r--r--doc/howtos/backend/exercise-kanban2
-rw-r--r--doc/howtos/backend/exercise-session2
-rw-r--r--doc/howtos/backend/exercise-wizard-action2
-rw-r--r--doc/howtos/backend/exercise-wizard-launch7
-rw-r--r--doc/howtos/backend/exercise-wizard-multi2
-rw-r--r--doc/howtos/themes.rst2
-rw-r--r--doc/howtos/website/chatter-backend2
-rw-r--r--doc/howtos/website/course-o2m4
-rw-r--r--doc/howtos/website/course-product9
-rw-r--r--doc/howtos/website/course-views2
-rw-r--r--doc/howtos/website/demo-data4
-rw-r--r--doc/howtos/website/teachers-formview2
-rw-r--r--doc/howtos/website/template2
-rw-r--r--doc/howtos/website/website-support2
-rw-r--r--doc/index.rst2
-rw-r--r--doc/reference.rst1
-rw-r--r--doc/reference/views.rst41
-rw-r--r--doc/requirements.txt4
-rw-r--r--doc/setup/deploy.rst33
-rw-r--r--doc/setup/install.rst4
-rw-r--r--doc/webservices.rst13
-rw-r--r--doc/webservices/flow.pngbin0 -> 14726 bytes
-rw-r--r--doc/webservices/iap.rst235
-rw-r--r--doc/webservices/odoo.rst (renamed from doc/api_integration.rst)6
-rw-r--r--doc/webservices/upgrade.rst (renamed from doc/reference/upgrade_api.rst)6
57 files changed, 1077 insertions, 144 deletions
diff --git a/doc/_extensions/autojsdoc/__main__.py b/doc/_extensions/autojsdoc/__main__.py
index 20ab65c2c37..2ac5f3a0cb3 100644
--- a/doc/_extensions/autojsdoc/__main__.py
+++ b/doc/_extensions/autojsdoc/__main__.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+from __future__ import print_function
import cgitb
import fnmatch
import io
@@ -6,13 +7,12 @@ import logging
import click
-import pyjsdoc
import pyjsparser
import sys
from .parser.parser import ModuleMatcher
from .parser.visitor import Visitor, SKIP
-from . import jsdoc
+from .parser import jsdoc
class Printer(Visitor):
def __init__(self, level=0):
@@ -20,7 +20,7 @@ class Printer(Visitor):
self.level = level
def _print(self, text):
- print ' ' * self.level, text
+ print(' ' * self.level, text)
def enter_generic(self, node):
self._print(node['type'])
diff --git a/doc/_extensions/autojsdoc/parser/jsdoc.py b/doc/_extensions/autojsdoc/parser/jsdoc.py
index 00406df42c1..a34a2aa9b83 100644
--- a/doc/_extensions/autojsdoc/parser/jsdoc.py
+++ b/doc/_extensions/autojsdoc/parser/jsdoc.py
@@ -174,7 +174,7 @@ class NSDoc(CommentDoc):
for p in self.get_as_list('property')
)
]
- return self.members.items() or self['_members'] or []
+ return list(self.members.items()) or self['_members'] or []
def has_property(self, name):
return self.get_property(name) is not None
diff --git a/doc/_extensions/autojsdoc/parser/parser.py b/doc/_extensions/autojsdoc/parser/parser.py
index d91b9f22596..358d20c8e9b 100644
--- a/doc/_extensions/autojsdoc/parser/parser.py
+++ b/doc/_extensions/autojsdoc/parser/parser.py
@@ -2,7 +2,6 @@
import collections
import pyjsdoc
-from attr import attrs, Factory, attr, astuple
from . import jsdoc
from . import utils
@@ -97,21 +96,25 @@ def m2r(me, scope):
utils._value(me['property'], strict=True)
)
-@attrs(slots=True)
+NOTHING = object()
class Declaration(object):
- id = attr(default=None)
- comments = attr(default=Factory(list))
-@attrs(slots=True)
+ __slots__ = ['id', 'comments']
+ def __init__(self, id=None, comments=NOTHING):
+ self.id = id
+ self.comments = [] if comments is NOTHING else comments
+
class ModuleContent(object):
- dependencies = attr(default=Factory(set))
- post = attr(default=Factory(list))
+ __slots__ = ['dependencies', 'post']
+ def __init__(self, dependencies=NOTHING, post=NOTHING):
+ self.dependencies = set() if dependencies is NOTHING else dependencies
+ self.post = [] if post is NOTHING else post
def __iter__(self):
yield self.dependencies
yield self.post
-@attrs # needs dict as it's replacing a ModuleProxy
class Nothing(object):
- name = attr()
+ def __init__(self, name):
+ self.name = name
def __bool__(self):
return False
__nonzero__ = __bool__
@@ -389,7 +392,7 @@ class ValueExtractor(Visitor):
return SKIP
def enter_FunctionExpression(self, node):
- name, comments = astuple(self.declaration)
+ name, comments = (self.declaration.id, self.declaration.comments)
self.result = jsdoc.parse_comments(comments, jsdoc.FunctionDoc)
self.result.parsed['name'] = node['id'] and node['id']['name']
self._update_result_meta()
@@ -437,7 +440,7 @@ class ValueExtractor(Visitor):
},
}): # creates a new class, but may not actually return it
obj = node['callee']['object']
- _, comments = astuple(self.declaration)
+ comments = self.declaration.comments
self.result = cls = jsdoc.parse_comments(comments, jsdoc.ClassDoc)
cls.parsed['extends'] = self.parent.refify(obj)
self._update_result_meta()
diff --git a/doc/_extensions/autojsdoc/parser/utils.py b/doc/_extensions/autojsdoc/parser/utils.py
index b8b1a97e7aa..213327f6c0e 100644
--- a/doc/_extensions/autojsdoc/parser/utils.py
+++ b/doc/_extensions/autojsdoc/parser/utils.py
@@ -104,7 +104,7 @@ def resolve(obj, store):
if obj is None:
return None
- if isinstance(obj, (unicode, bool, int, float)):
+ if isinstance(obj, (type(u''), bool, int, float)):
return obj
if isinstance(obj, ModuleProxy):
@@ -139,11 +139,11 @@ class Namespace(Resolvable):
r = super(Namespace, self).resolve(store)
self.attrs = {
k: resolve(v, store)
- for k, v in self.attrs.iteritems()
+ for k, v in self.attrs.items()
}
return r
def __getitem__(self, key):
- assert isinstance(key, unicode), "%r is not a namespace key" % key
+ assert isinstance(key, type(u'')), "%r is not a namespace key" % key
try:
return self.attrs[key]
except KeyError:
diff --git a/doc/_extensions/odoo_ext/static/layout.less b/doc/_extensions/odoo_ext/static/layout.less
index c350defa2a1..4c8a5c64386 100644
--- a/doc/_extensions/odoo_ext/static/layout.less
+++ b/doc/_extensions/odoo_ext/static/layout.less
@@ -103,7 +103,7 @@ main.has_code_col{
> *{
max-width: 100%;
}
- section {
+ > section {
position: relative;
display:block;
float: left;
@@ -116,7 +116,7 @@ main.has_code_col{
&:before {
.code-col();
}
- section {
+ > section {
> * {
width: 54.633333%;
max-width: 600px;
diff --git a/doc/_extensions/odoo_ext/static/style.css b/doc/_extensions/odoo_ext/static/style.css
index ead78f9101d..1442d4bdd67 100644
--- a/doc/_extensions/odoo_ext/static/style.css
+++ b/doc/_extensions/odoo_ext/static/style.css
@@ -9708,7 +9708,7 @@ main.has_code_col article.doc-body {
main.has_code_col article.doc-body > * {
max-width: 100%;
}
-main.has_code_col article.doc-body section {
+main.has_code_col article.doc-body > section {
position: relative;
display: block;
float: left;
@@ -9730,36 +9730,36 @@ main.has_code_col article.doc-body section {
top: 0;
right: 0;
}
- main.has_code_col article.doc-body section > * {
+ main.has_code_col article.doc-body > section > * {
width: 54.633333%;
max-width: 600px;
float: left;
clear: left;
}
- main.has_code_col article.doc-body section > h1,
- main.has_code_col article.doc-body section > h2,
- main.has_code_col article.doc-body section > h3,
- main.has_code_col article.doc-body section > h4,
- main.has_code_col article.doc-body section > h5,
- main.has_code_col article.doc-body section > h6 {
+ main.has_code_col article.doc-body > section > h1,
+ main.has_code_col article.doc-body > section > h2,
+ main.has_code_col article.doc-body > section > h3,
+ main.has_code_col article.doc-body > section > h4,
+ main.has_code_col article.doc-body > section > h5,
+ main.has_code_col article.doc-body > section > h6 {
width: 100%;
float: none;
clear: none;
}
- main.has_code_col article.doc-body section .doc-aside {
+ main.has_code_col article.doc-body > section .doc-aside {
width: 41%;
float: none;
clear: none;
margin-right: 15px;
margin-left: 57%;
}
- main.has_code_col article.doc-body section .doc-aside .content-switcher {
+ main.has_code_col article.doc-body > section .doc-aside .content-switcher {
margin-top: 0;
}
- main.has_code_col article.doc-body section .doc-aside .content-switcher > ul {
+ main.has_code_col article.doc-body > section .doc-aside .content-switcher > ul {
margin-bottom: 0;
}
- main.has_code_col article.doc-body section .doc-aside .content-switcher > ul > li {
+ main.has_code_col article.doc-body > section .doc-aside .content-switcher > ul > li {
color: #dcddde;
}
}
diff --git a/doc/_extensions/odoo_ext/translator.py b/doc/_extensions/odoo_ext/translator.py
index 443458f33c7..1c6d1c86d2e 100644
--- a/doc/_extensions/odoo_ext/translator.py
+++ b/doc/_extensions/odoo_ext/translator.py
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
+from __future__ import print_function
+
import os.path
import posixpath
import re
@@ -114,7 +116,7 @@ class BootstrapTranslator(nodes.NodeVisitor, object):
return u''.join(self.body)
def unknown_visit(self, node):
- print "unknown node", node.__class__.__name__
+ print("unknown node", node.__class__.__name__)
self.body.append(u'[UNKNOWN NODE {}]'.format(node.__class__.__name__))
raise nodes.SkipNode
@@ -142,6 +144,11 @@ class BootstrapTranslator(nodes.NodeVisitor, object):
if not self.section_level:
self.body.append(u'</section>')
+ def visit_topic(self, node):
+ self.body.append(self.starttag(node, 'nav'))
+ def depart_topic(self, node):
+ self.body.append(u'</nav>')
+
def is_compact_paragraph(self, node):
parent = node.parent
if isinstance(parent, (nodes.document, nodes.compound,
diff --git a/doc/_extensions/pyjsdoc/LICENSE b/doc/_extensions/pyjsdoc/LICENSE
new file mode 100644
index 00000000000..dd5b3a58aa1
--- /dev/null
+++ b/doc/_extensions/pyjsdoc/LICENSE
@@ -0,0 +1,174 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
diff --git a/doc/_extensions/pyjsdoc/__init__.py b/doc/_extensions/pyjsdoc/__init__.py
new file mode 100644
index 00000000000..8368f73083e
--- /dev/null
+++ b/doc/_extensions/pyjsdoc/__init__.py
@@ -0,0 +1,408 @@
+from __future__ import division
+import re, json
+
+##### Parsing utilities #####
+
+def split_delimited(delimiters, split_by, text):
+ """
+ Generator that walks the ``text`` and splits it into an array on
+ ``split_by``, being careful not to break inside a delimiter pair.
+ ``delimiters`` should be an even-length string with each pair of matching
+ delimiters listed together, open first.
+
+
+ >>> list(split_delimited('{}[]', ',', ''))
+ ['']
+ >>> list(split_delimited('', ',', 'foo,bar'))
+ ['foo', 'bar']
+ >>> list(split_delimited('[]', ',', 'foo,[bar, baz]'))
+ ['foo', '[bar, baz]']
+ >>> list(split_delimited('{}', ' ', '{Type Name} name Desc'))
+ ['{Type Name}', 'name', 'Desc']
+ >>> list(split_delimited('[]{}', ',', '[{foo,[bar, baz]}]'))
+ ['[{foo,[bar, baz]}]']
+
+ Two adjacent delimiters result in a zero-length string between them:
+
+ >>> list(split_delimited('{}', ' ', '{Type Name} Desc'))
+ ['{Type Name}', '', 'Desc']
+
+ ``split_by`` may be a predicate function instead of a string, in which
+ case it should return true on a character to split.
+
+ >>> list(split_delimited('', lambda c: c in '[]{}, ', '[{foo,[bar, baz]}]'))
+ ['', '', 'foo', '', 'bar', '', 'baz', '', '', '']
+
+ """
+ delims = [0] * (len(delimiters) // 2)
+ actions = {}
+ for i in range(0, len(delimiters), 2):
+ actions[delimiters[i]] = (i // 2, 1)
+ actions[delimiters[i + 1]] = (i // 2, -1)
+
+ if isinstance(split_by, str):
+ def split_fn(c):
+ return c == split_by
+ else:
+ split_fn = split_by
+ last = 0
+
+ for i in range(len(text)):
+ c = text[i]
+ if split_fn(c) and not any(delims):
+ yield text[last:i]
+ last = i + 1
+ try:
+ which, dir = actions[c]
+ delims[which] = delims[which] + dir
+ except KeyError:
+ pass # Normal character
+ yield text[last:]
+
+
+def strip_stars(doc_comment):
+ r"""
+ Strip leading stars from a doc comment.
+
+ >>> strip_stars('/** This is a comment. */')
+ 'This is a comment.'
+ >>> strip_stars('/**\n * This is a\n * multiline comment. */')
+ 'This is a\n multiline comment.'
+ >>> strip_stars('/** \n\t * This is a\n\t * multiline comment. \n*/')
+ 'This is a\n multiline comment.'
+
+ """
+ return re.sub('\n\s*?\*\s*?', '\n', doc_comment[3:-2]).strip()
+
+
+def split_tag(section):
+ """
+ Split the JSDoc tag text (everything following the @) at the first
+ whitespace. Returns a tuple of (tagname, body).
+ """
+ splitval = re.split('\s+', section, 1)
+ tag, body = len(splitval) > 1 and splitval or (splitval[0], '')
+ return tag.strip(), body.strip()
+
+
+FUNCTION_REGEXPS = [
+ 'function (\w+)',
+ '(\w+):\sfunction',
+ '\.(\w+)\s*=\s*function',
+]
+
+
+def guess_function_name(next_line, regexps=FUNCTION_REGEXPS):
+ """
+ Attempt to determine the function name from the first code line
+ following the comment. The patterns recognized are described by
+ `regexps`, which defaults to FUNCTION_REGEXPS. If a match is successful,
+ returns the function name. Otherwise, returns None.
+ """
+ for regexp in regexps:
+ match = re.search(regexp, next_line)
+ if match:
+ return match.group(1)
+ return None
+
+
+def guess_parameters(next_line):
+ """
+ Attempt to guess parameters based on the presence of a parenthesized
+ group of identifiers. If successful, returns a list of parameter names;
+ otherwise, returns None.
+ """
+ match = re.search('\(([\w\s,]+)\)', next_line)
+ if match:
+ return [arg.strip() for arg in match.group(1).split(',')]
+ else:
+ return None
+
+
+def parse_comment(doc_comment, next_line):
+ r"""
+ Split the raw comment text into a dictionary of tags. The main comment
+ body is included as 'doc'.
+
+ >>> comment = get_doc_comments(read_file('examples/module.js'))[4][0]
+ >>> parse_comment(strip_stars(comment), '')['doc']
+ 'This is the documentation for the fourth function.\n\n Since the function being documented is itself generated from another\n function, its name needs to be specified explicitly. using the @function tag'
+ >>> parse_comment(strip_stars(comment), '')['function']
+ 'not_auto_discovered'
+
+ If there are multiple tags with the same name, they're included as a list:
+
+ >>> parse_comment(strip_stars(comment), '')['param']
+ ['{String} arg1 The first argument.', '{Int} arg2 The second argument.']
+
+ """
+ sections = re.split('\n\s*@', doc_comment)
+ tags = {
+ 'doc': sections[0].strip(),
+ 'guessed_function': guess_function_name(next_line),
+ 'guessed_params': guess_parameters(next_line)
+ }
+ for section in sections[1:]:
+ tag, body = split_tag(section)
+ if tag in tags:
+ existing = tags[tag]
+ try:
+ existing.append(body)
+ except AttributeError:
+ tags[tag] = [existing, body]
+ else:
+ tags[tag] = body
+ return tags
+
+
+#### Classes #####
+
+class CommentDoc(object):
+ """
+ Base class for all classes that represent a parsed comment of some sort.
+ """
+
+ def __init__(self, parsed_comment):
+ self.parsed = parsed_comment
+
+ def __str__(self):
+ return "Docs for " + self.name
+
+ def __repr__(self):
+ return str(self)
+
+ def __contains__(self, tag_name):
+ return tag_name in self.parsed
+
+ def __getitem__(self, tag_name):
+ return self.get(tag_name)
+
+ def get(self, tag_name, default=''):
+ """
+ Return the value of a particular tag, or None if that tag doesn't
+ exist. Use 'doc' for the comment body itself.
+ """
+ return self.parsed.get(tag_name, default)
+
+ def get_as_list(self, tag_name):
+ """
+ Return the value of a tag, making sure that it's a list. Absent
+ tags are returned as an empty-list; single tags are returned as a
+ one-element list.
+
+ The returned list is a copy, and modifications do not affect the
+ original object.
+ """
+ val = self.get(tag_name, [])
+ if isinstance(val, list):
+ return val[:]
+ else:
+ return [val]
+
+ @property
+ def doc(self):
+ """
+ Return the comment body.
+ """
+ return self.get('doc')
+
+ @property
+ def url(self):
+ """
+ Return a URL for the comment, within the page.
+ """
+ return '#' + self.name
+
+ @property
+ def see(self):
+ """
+ Return a list of all @see tags on the comment.
+ """
+ return self.get_as_list('see')
+
+ def to_json(self):
+ """
+ Return a JSON representation of the CommentDoc. Keys are as per
+ to_dict.
+ """
+ return json.dumps(self.to_dict())
+
+ def to_dict(self):
+ """
+ Return a dictionary representation of the CommentDoc. The keys of
+ this correspond to the tags in the comment, with the comment body in
+ `doc`.
+ """
+ return self.parsed.copy()
+
+class ParamDoc(object):
+ """
+ Represents a parameter, option, or parameter-like object, basically
+ anything that has a name, a type, and a description, separated by spaces.
+ This is also used for return types and exceptions, which use an empty
+ string for the name.
+
+ >>> param = ParamDoc('{Array<DOM>} elems The elements to act upon')
+ >>> param.name
+ 'elems'
+ >>> param.doc
+ 'The elements to act upon'
+ >>> param.type
+ 'Array<DOM>'
+
+ You can also omit the type: if the first element is not surrounded by
+ curly braces, it's assumed to be the name instead:
+
+ >>> param2 = ParamDoc('param1 The first param')
+ >>> param2.type
+ ''
+ >>> param2.name
+ 'param1'
+ >>> param2.doc
+ 'The first param'
+
+ """
+
+ def __init__(self, text):
+ parsed = list(split_delimited('{}', ' ', text))
+ if parsed[0].startswith('{') and parsed[0].endswith('}'):
+ self.type = parsed[0][1:-1]
+ self.name = parsed[1]
+ self.doc = ' '.join(parsed[2:])
+ else:
+ self.type = ''
+ self.name = parsed[0]
+ self.doc = ' '.join(parsed[1:])
+
+ def to_dict(self):
+ """
+ Convert this to a dict. Keys (all strings) are:
+
+ - **name**: Parameter name
+ - **type**: Parameter type
+ - **doc**: Parameter description
+ """
+ return {
+ 'name': self.name,
+ 'type': self.type,
+ 'doc': self.doc
+ }
+
+ def to_html(self, css_class=''):
+ """
+ Returns the parameter as a dt/dd pair.
+ """
+ if self.name and self.type:
+ header_text = '%s (%s)' % (self.name, self.type)
+ elif self.type:
+ header_text = self.type
+ else:
+ header_text = self.name
+ return '<dt>%s</dt><dd>%s</dd>' % (header_text, self.doc)
+
+
+##### DEPENDENCIES #####
+
+class CyclicDependency(Exception):
+ """
+ Exception raised if there is a cyclic dependency.
+ """
+
+ def __init__(self, remaining_dependencies):
+ self.values = remaining_dependencies
+
+ def __str__(self):
+ return ('The following dependencies result in a cycle: '
+ + ', '.join(self.values))
+
+
+class MissingDependency(Exception):
+ """
+ Exception raised if a file references a dependency that doesn't exist.
+ """
+
+ def __init__(self, file, dependency):
+ self.file = file
+ self.dependency = dependency
+
+ def __str__(self):
+ return "Couldn't find dependency %s when processing %s" % \
+ (self.dependency, self.file)
+
+
+def build_dependency_graph(start_nodes, js_doc):
+ """
+ Build a graph where nodes are filenames and edges are reverse dependencies
+ (so an edge from jquery.js to jquery.dimensions.js indicates that jquery.js
+ must be included before jquery.dimensions.js). The graph is represented
+ as a dictionary from filename to (in-degree, edges) pair, for ease of
+ topological sorting. Also returns a list of nodes of degree zero.
+ """
+ queue = []
+ dependencies = {}
+ start_sort = []
+
+ def add_vertex(file):
+ in_degree = len(js_doc[file].module.dependencies)
+ dependencies[file] = [in_degree, []]
+ queue.append(file)
+ if in_degree == 0:
+ start_sort.append(file)
+
+ def add_edge(from_file, to_file):
+ dependencies[from_file][1].append(to_file)
+
+ def is_in_graph(file):
+ return file in dependencies
+
+ for file in start_nodes:
+ add_vertex(file)
+ for file in queue:
+ for dependency in js_doc[file].module.dependencies:
+ if dependency not in js_doc:
+ raise MissingDependency(file, dependency)
+ if not is_in_graph(dependency):
+ add_vertex(dependency)
+ add_edge(dependency, file)
+ return dependencies, start_sort
+
+
+def topological_sort(dependencies, start_nodes):
+ """
+ Perform a topological sort on the dependency graph `dependencies`, starting
+ from list `start_nodes`.
+ """
+ retval = []
+
+ def edges(node):
+ return dependencies[node][1]
+
+ def in_degree(node):
+ return dependencies[node][0]
+
+ def remove_incoming(node):
+ dependencies[node][0] = in_degree(node) - 1
+
+ while start_nodes:
+ node = start_nodes.pop()
+ retval.append(node)
+ for child in edges(node):
+ remove_incoming(child)
+ if not in_degree(child):
+ start_nodes.append(child)
+ leftover_nodes = [node for node in dependencies.keys()
+ if in_degree(node) > 0]
+ if leftover_nodes:
+ raise CyclicDependency(leftover_nodes)
+ else:
+ return retval
+
+
+def find_dependencies(start_nodes, js_doc):
+ """
+ Sort the dependency graph, taking in a list of starting module names and a
+ CodeBaseDoc (or equivalent dictionary). Returns an ordered list of
+ transitive dependencies such that no module appears before its
+ dependencies.
+ """
+ return topological_sort(*build_dependency_graph(start_nodes, js_doc))
diff --git a/doc/_extensions/pyjsparser/parser.py b/doc/_extensions/pyjsparser/parser.py
index 78ebe14e6ec..28001886a8e 100644
--- a/doc/_extensions/pyjsparser/parser.py
+++ b/doc/_extensions/pyjsparser/parser.py
@@ -174,7 +174,7 @@ class PyJsParser:
break
else:
break
- return filter(None, comments)
+ return [c for c in comments if c]
def scanHexEscape(self, prefix):
code = 0
diff --git a/doc/_extensions/pyjsparser/pyjsparserdata.py b/doc/_extensions/pyjsparser/pyjsparserdata.py
index 1d6ab2ee2ca..6ca6dbc47cc 100644
--- a/doc/_extensions/pyjsparser/pyjsparserdata.py
+++ b/doc/_extensions/pyjsparser/pyjsparserdata.py
@@ -300,4 +300,4 @@ if __name__=='__main__':
assert isLineTerminator(0x0A)
assert isIdentifierStart('$')
assert isIdentifierStart(100)
- assert isWhiteSpace(' ') \ No newline at end of file
+ assert isWhiteSpace(' ')
diff --git a/doc/cla/corporate/eficent.md b/doc/cla/corporate/eficent.md
index 3723fbf0740..284e2b7b691 100644
--- a/doc/cla/corporate/eficent.md
+++ b/doc/cla/corporate/eficent.md
@@ -14,6 +14,7 @@ List of contributors:
Aarón Henríquez ahenriquez@eficent.com https://github.com/aheficent
Lois Rilo lois.rilo@eficent.com https://github.com/lreficent
-Gisela Mora gisela.mora@eficent.com https://github.com/gmeficent
+Roser Garcia roser.garcia@eficent.com https://github.com/rgarnau
Miquel Raich miquel.raich@eficent.com https://github.com/mreficent
Jordi Ballester jordi.ballester@eficent.com https://github.com/jbeficent
+Hector Villarreal hector.villarreal@eficent.com https://github.com/hveficent
diff --git a/doc/cla/corporate/equitania.md b/doc/cla/corporate/equitania.md
new file mode 100644
index 00000000000..8dedd6fd05c
--- /dev/null
+++ b/doc/cla/corporate/equitania.md
@@ -0,0 +1,21 @@
+Germany, 2017-09-11
+
+Equitania Software GmbH agrees to the terms of the
+Odoo Corporate Contributor License Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Martin Schmid m.schmid@equitania.com https://github.com/eqms
+
+List of contributors:
+
+Julia Eberle j.eberle@equitania.de https://github.com/eqje
+Paul Exler p.exler@equitania.de https://github.com/eqpe
+Oliver Hercht o.hercht@equitania.de https://github.com/eqoh
+Johann Müller j.mueller@equitania.de https://github.com/eqjm
+Robin Prijatel r.prijatel@equitania.de https://github.com/eqrp
+Michal Sodomka m.sodomka@equitania.de https://github.com/eqmiso
+
diff --git a/doc/cla/corporate/merchise.md b/doc/cla/corporate/merchise.md
index 6cc3e022152..d8d5e75f830 100644
--- a/doc/cla/corporate/merchise.md
+++ b/doc/cla/corporate/merchise.md
@@ -18,3 +18,5 @@ Henrik Pestano henrik@merchise.org https://github.com/hpestano
Oraldo Jacinto Simón oraldo@merchise.org
Sergio Valdés sergiov@merchise.org
Medardo Rodríguez med@merchise.org https://github.com/med-merchise
+Abel Firvida firvida@merchise.org
+Abel Firvida abel@merchise.org
diff --git a/doc/cla/corporate/quartile.md b/doc/cla/corporate/quartile.md
new file mode 100644
index 00000000000..b43892e1227
--- /dev/null
+++ b/doc/cla/corporate/quartile.md
@@ -0,0 +1,14 @@
+Hong Kong, 2017-09-18
+
+Quartile Limited agrees to the terms of the Odoo Corporate Contributor License Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this declaration.
+
+Signed,
+
+Yoshi Tashiro tashiro@quartile.co https://github.com/yostashiro
+
+List of contributors:
+
+Yoshi Tashiro tashiro@quartile.co https://github.com/yostashiro
+Tim Lai tl@quartile.co https://github.com/TimLai125
diff --git a/doc/cla/corporate/sewisoft.md b/doc/cla/corporate/sewisoft.md
new file mode 100644
index 00000000000..6b76b2feca1
--- /dev/null
+++ b/doc/cla/corporate/sewisoft.md
@@ -0,0 +1,16 @@
+Germany, 2017-07-25
+
+sewisoft UG agrees to the terms of the Odoo Corporate
+Contributor License Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Günter Selbert guenter.selbert@sewisoft.de https://github.com/Guenzn
+
+List of contributors:
+
+Günter Selbert guenter.selbert@sewisoft.de https://github.com/Guenzn
+Stefan Wild stefan.wild@sewisoft.de https://github.com/wildi1
diff --git a/doc/cla/corporate/syleam.md b/doc/cla/corporate/syleam.md
index 75ac0025efe..7c2e09760d0 100644
--- a/doc/cla/corporate/syleam.md
+++ b/doc/cla/corporate/syleam.md
@@ -14,4 +14,7 @@ List of contributors:
Sébastien Lange sebastien.lange@syleam.fr https://github.com/alnslang
Sylvain Garancher sylvain.garancher@syleam.fr https://github.com/sylvain-garancher
-Alexandre Moreau alexandre.moreau@syleam.fr https://github.com/a-moreau
+Alexandre Moreau alexandre.moreau@syleam.fr https://github.com/a-moreau (Up to 2017-08-31)
+Christopher Tribbeck chris.tribbeck@syleam.fr https://github.com/ctribbeck
+Jérémy Cormier jeremy.cormier@syleam.fr https://github.com/Corbiezorq
+Samuel Piquet samuel.piquet@syleam.fr https://github.com/sa3m
diff --git a/doc/cla/corporate/tvtmarine-automation.md b/doc/cla/corporate/tvtmarine-automation.md
index 8b5b59d501b..de0906f0331 100644
--- a/doc/cla/corporate/tvtmarine-automation.md
+++ b/doc/cla/corporate/tvtmarine-automation.md
@@ -14,4 +14,6 @@ List of contributors:
David Tran david.tran@tvtmarine.com https://github.com/davidtranhp
Long Do dhlong.1209@gmail.com https://github.com/hoanglong87
-Hao Hoang hao.hoang@ma.tvtmarine.com https://github.com/hoanghao2001 \ No newline at end of file
+Hao Hoang hao.hoang@ma.tvtmarine.com https://github.com/hoanghao2001
+Tommy Tran tu.tran@tvtmarine.com https://github.com/tutran81
+Emily Ha emily.ha@tvtmarine.com https://github.com/emilyha \ No newline at end of file
diff --git a/doc/cla/corporate/vauxoo.md b/doc/cla/corporate/vauxoo.md
index 096c835ef55..017ffe08bba 100644
--- a/doc/cla/corporate/vauxoo.md
+++ b/doc/cla/corporate/vauxoo.md
@@ -38,3 +38,4 @@ Gabriela Mogollon gmogollon@vauxoo.com https://github.com/GavyMG
Jesus Zapata jesus@vauxoo.com https://github.com/JesusZapata
Germana Oliveira germana@vauxoo.com https://github.com/goliveirab
Mariano Fernandez mariano@vauxoo.com https://github.com/Batuto
+Edgar Rivero edgar@vauxoo.com https://github.com/egrivero
diff --git a/doc/cla/corporate/xoe-corp.md b/doc/cla/corporate/xoe-corp.md
new file mode 100644
index 00000000000..ef449e25d72
--- /dev/null
+++ b/doc/cla/corporate/xoe-corp.md
@@ -0,0 +1,15 @@
+Colombia, 2016-04-20
+
+Xoe Corp SAS. agrees to the terms of the Odoo Corporate Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+David Arnold dar@xoe.solutions https://github.com/blaggacao
+
+List of contributors:
+
+David Arnold dar@devco.co https://github.com/blaggacao
diff --git a/doc/cla/individual/bazemoreb.md b/doc/cla/individual/bazemoreb.md
new file mode 100644
index 00000000000..c800a883cee
--- /dev/null
+++ b/doc/cla/individual/bazemoreb.md
@@ -0,0 +1,11 @@
+USA, 2017-10-01
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Brandon Bazemore brandonb@audian.com https://github.com/bazemoreb
diff --git a/doc/cla/individual/blaggacao.md b/doc/cla/individual/blaggacao.md
new file mode 100644
index 00000000000..30b2fee69a0
--- /dev/null
+++ b/doc/cla/individual/blaggacao.md
@@ -0,0 +1,12 @@
+Colombia, 2017-09-01
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+David Arnold dar@xoe.solutions https://github.com/blaggacao
+
diff --git a/doc/cla/individual/danger89.md b/doc/cla/individual/danger89.md
new file mode 100644
index 00000000000..11c9e89616c
--- /dev/null
+++ b/doc/cla/individual/danger89.md
@@ -0,0 +1,11 @@
+The Netherlands, 2017-10-02
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Melroy van den Berg webmaster1989@gmail.com https://github.com/danger89
diff --git a/doc/cla/individual/hansmi.md b/doc/cla/individual/hansmi.md
new file mode 100644
index 00000000000..ff479503083
--- /dev/null
+++ b/doc/cla/individual/hansmi.md
@@ -0,0 +1,11 @@
+Switzerland, 2017-09-15
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Michael Hanselmann public@hansmi.ch https://github.com/hansmi
diff --git a/doc/cla/individual/stephanrozendaal.md b/doc/cla/individual/stephanrozendaal.md
new file mode 100644
index 00000000000..f84dc55b3fe
--- /dev/null
+++ b/doc/cla/individual/stephanrozendaal.md
@@ -0,0 +1,11 @@
+The Netherlands, 2017-10-07
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Stephan Rozendaal stephan.rozendaal@gmail.com https://github.com/StephanRozendaal
diff --git a/doc/cla/stats.py b/doc/cla/stats.py
index f37e01297e8..c05f8198ada 100755
--- a/doc/cla/stats.py
+++ b/doc/cla/stats.py
@@ -2,6 +2,7 @@
#
# Runme From the repo toplevel directory
#
+from __future__ import print_function
import subprocess
import glob
import re
@@ -37,18 +38,18 @@ def blamestat(ext='py'):
else:
ko += lines
kol.append(i)
- print '-'*60
- print 'Stats for ',ext
- print '-'*60
- print "\nCLA SIGNED %s/%s (%.0f%%)" % (ok, ok+ko, ok*100.0/(ok+ko))
+ print('-'*60)
+ print('Stats for ', ext)
+ print('-'*60)
+ print("\nCLA SIGNED %s/%s (%.0f%%)" % (ok, ok+ko, ok*100.0/(ok+ko)))
for i in okl:
- print i
+ print(i)
- print "\nCLA MISSING %s/%s (%.0f%%)\n" % (ko, ok+ko, ko*100.0/(ok+ko))
+ print("\nCLA MISSING %s/%s (%.0f%%)\n" % (ko, ok+ko, ko*100.0/(ok+ko)))
for i in kol:
- print i
- print
- print
+ print(i)
+ print()
+ print()
blamestat('md')
blamestat('rst')
diff --git a/doc/howtos/backend/exercise-access-rules b/doc/howtos/backend/exercise-access-rules
index 1d78adb984e..d6290dfdbe1 100644
--- a/doc/howtos/backend/exercise-access-rules
+++ b/doc/howtos/backend/exercise-access-rules
@@ -23,5 +23,5 @@ Index: addons/openacademy/security/security.xml
+ ('responsible_id','=',user.id)]
+ </field>
+ </record>
-
+
</odoo>
diff --git a/doc/howtos/backend/exercise-demo b/doc/howtos/backend/exercise-demo
index 69b202db15d..50a9564424a 100644
--- a/doc/howtos/backend/exercise-demo
+++ b/doc/howtos/backend/exercise-demo
@@ -7,7 +7,7 @@ Index: addons/openacademy/demo.xml
+++ addons/openacademy/demo.xml 2014-08-26 17:25:52.679783480 +0200
@@ -1,25 +1,19 @@
<odoo>
-
+
- <!-- -->
- <!-- <record id="object0" model="openacademy.openacademy"> -->
- <!-- <field name="name">Object 0</field> -->
@@ -44,5 +44,5 @@ Index: addons/openacademy/demo.xml
+ <field name="name">Course 2</field>
+ <field name="description">Course 2's description</field>
+ </record>
-
+
</odoo>
diff --git a/doc/howtos/backend/exercise-domain-advanced b/doc/howtos/backend/exercise-domain-advanced
index db7b89f5d81..55d5e1da708 100644
--- a/doc/howtos/backend/exercise-domain-advanced
+++ b/doc/howtos/backend/exercise-domain-advanced
@@ -39,5 +39,5 @@ Index: addons/openacademy/views/partner.xml
+ <record model="res.partner.category" id="teacher2">
+ <field name="name">Teacher / Level 2</field>
+ </record>
-
+
</odoo>
diff --git a/doc/howtos/backend/exercise-formview b/doc/howtos/backend/exercise-formview
index c42d451b1ea..9f80bc2f768 100644
--- a/doc/howtos/backend/exercise-formview
+++ b/doc/howtos/backend/exercise-formview
@@ -8,7 +8,7 @@ Index: addons/openacademy/views/openacademy.xml
@@ -1,6 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
-
+
+ <record model="ir.ui.view" id="course_form_view">
+ <field name="name">course.form</field>
+ <field name="model">openacademy.course</field>
diff --git a/doc/howtos/backend/exercise-gantt b/doc/howtos/backend/exercise-gantt
index da37764a010..63c3981f09e 100644
--- a/doc/howtos/backend/exercise-gantt
+++ b/doc/howtos/backend/exercise-gantt
@@ -34,7 +34,7 @@ diff --git a/openacademy/models.py b/openacademy/models.py
diff --git a/openacademy/views/openacademy.xml b/openacademy/views/openacademy.xml
--- a/openacademy/views/openacademy.xml
+++ b/openacademy/views/openacademy.xml
-@@ -145,11 +145,23 @@
+@@ -143,11 +143,23 @@
</field>
</record>
diff --git a/doc/howtos/backend/exercise-graph b/doc/howtos/backend/exercise-graph
index 07fdd467ef1..ca38438971d 100644
--- a/doc/howtos/backend/exercise-graph
+++ b/doc/howtos/backend/exercise-graph
@@ -30,7 +30,7 @@ diff --git a/openacademy/models.py b/openacademy/models.py
diff --git a/openacademy/views/openacademy.xml b/openacademy/views/openacademy.xml
--- a/openacademy/views/openacademy.xml
+++ b/openacademy/views/openacademy.xml
-@@ -157,11 +157,22 @@
+@@ -155,11 +155,22 @@
</field>
</record>
diff --git a/doc/howtos/backend/exercise-kanban b/doc/howtos/backend/exercise-kanban
index 14899f46a3d..a3387aa6e6c 100644
--- a/doc/howtos/backend/exercise-kanban
+++ b/doc/howtos/backend/exercise-kanban
@@ -17,7 +17,7 @@ Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml 2014-08-28 14:21:58.627015606 +0200
+++ addons/openacademy/views/openacademy.xml 2014-08-28 14:21:58.623015606 +0200
-@@ -168,11 +168,57 @@
+@@ -166,11 +166,57 @@
</field>
</record>
diff --git a/doc/howtos/backend/exercise-session b/doc/howtos/backend/exercise-session
index a189a3d3a64..9555847336b 100644
--- a/doc/howtos/backend/exercise-session
+++ b/doc/howtos/backend/exercise-session
@@ -54,5 +54,5 @@ Index: addons/openacademy/views/openacademy.xml
+ <menuitem id="session_menu" name="Sessions"
+ parent="openacademy_menu"
+ action="session_list_action"/>
-
+
</odoo>
diff --git a/doc/howtos/backend/exercise-wizard-action b/doc/howtos/backend/exercise-wizard-action
index 23d6a0f2667..6cf27a3d41d 100644
--- a/doc/howtos/backend/exercise-wizard-action
+++ b/doc/howtos/backend/exercise-wizard-action
@@ -2,7 +2,7 @@ Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml 2014-08-28 14:34:19.255004611 +0200
+++ addons/openacademy/views/openacademy.xml 2014-08-28 14:34:19.251004612 +0200
-@@ -234,6 +234,12 @@
+@@ -232,6 +232,12 @@
<field name="session_id"/>
<field name="attendee_ids"/>
</group>
diff --git a/doc/howtos/backend/exercise-wizard-launch b/doc/howtos/backend/exercise-wizard-launch
index 7b5d65c8074..7f76c45cbaf 100644
--- a/doc/howtos/backend/exercise-wizard-launch
+++ b/doc/howtos/backend/exercise-wizard-launch
@@ -17,11 +17,10 @@ Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml 2014-08-28 14:34:07.879004780 +0200
+++ addons/openacademy/views/openacademy.xml 2014-08-28 14:34:07.871004780 +0200
-@@ -224,5 +224,26 @@
- <menuitem id="session_menu" name="Sessions"
+@@ -223,4 +223,25 @@
parent="openacademy_menu"
action="session_list_action"/>
-+
+
+ <record model="ir.ui.view" id="wizard_form_view">
+ <field name="name">wizard.form</field>
+ <field name="model">openacademy.wizard</field>
@@ -42,5 +41,5 @@ Index: addons/openacademy/views/openacademy.xml
+ view_mode="form"
+ target="new"
+ key2="client_action_multi"/>
-
++
</odoo>
diff --git a/doc/howtos/backend/exercise-wizard-multi b/doc/howtos/backend/exercise-wizard-multi
index f8ece675090..4386dd90d90 100644
--- a/doc/howtos/backend/exercise-wizard-multi
+++ b/doc/howtos/backend/exercise-wizard-multi
@@ -2,7 +2,7 @@ Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml 2014-08-28 14:34:28.583004473 +0200
+++ addons/openacademy/views/openacademy.xml 2014-08-28 14:34:28.579004473 +0200
-@@ -231,7 +231,7 @@
+@@ -229,7 +229,7 @@
<field name="arch" type="xml">
<form string="Add Attendees">
<group>
diff --git a/doc/howtos/themes.rst b/doc/howtos/themes.rst
index b4d579f7871..18dba100f3b 100644
--- a/doc/howtos/themes.rst
+++ b/doc/howtos/themes.rst
@@ -253,8 +253,6 @@ These values will be used to identify your new theme in Odoo’s backend.
The ``data`` property will contain the xml files list. Right now it’s empty, but we will add any new files created.
-``application: True`` is mandatory.
-
``category`` defines your module category (always “Theme”) and, after a slash, the subcategory. You can use one subcategory from the Odoo Apps categories list. (https://www.odoo.com/apps/themes)
diff --git a/doc/howtos/website/chatter-backend b/doc/howtos/website/chatter-backend
index 8598be05bae..1521a78a272 100644
--- a/doc/howtos/website/chatter-backend
+++ b/doc/howtos/website/chatter-backend
@@ -16,7 +16,7 @@ diff --git a/academy/models.py b/academy/models.py
diff --git a/academy/views.xml b/academy/views.xml
--- a/academy/views.xml
+++ b/academy/views.xml
-@@ -60,6 +60,10 @@
+@@ -61,6 +61,10 @@
<label for="teacher_id"/>
<field name="teacher_id"/>
</sheet>
diff --git a/doc/howtos/website/course-o2m b/doc/howtos/website/course-o2m
index fca83fd50fd..f68c0f90b90 100644
--- a/doc/howtos/website/course-o2m
+++ b/doc/howtos/website/course-o2m
@@ -3,7 +3,7 @@
diff -r c3619e60d819 -r 957395d27f63 academy/models.py
--- a/academy/models.py Wed Aug 13 14:29:45 2014 +0200
+++ b/academy/models.py Wed Aug 13 15:06:30 2014 +0200
-@@ -7,6 +7,8 @@ class Teachers(models.Model):
+@@ -8,6 +8,8 @@ class Teachers(models.Model):
name = fields.Char()
biography = fields.Html()
@@ -15,7 +15,7 @@ diff -r c3619e60d819 -r 957395d27f63 academy/models.py
diff -r c3619e60d819 -r 957395d27f63 academy/views.xml
--- a/academy/views.xml Wed Aug 13 14:29:45 2014 +0200
+++ b/academy/views.xml Wed Aug 13 15:06:30 2014 +0200
-@@ -11,8 +11,15 @@
+@@ -12,8 +12,15 @@
<form>
<sheet>
<label for="name"/> <field name="name"/>
diff --git a/doc/howtos/website/course-product b/doc/howtos/website/course-product
index 0508da60851..53aa555be52 100644
--- a/doc/howtos/website/course-product
+++ b/doc/howtos/website/course-product
@@ -27,11 +27,10 @@ new file mode 100644
diff --git a/academy/demo.xml b/academy/demo.xml
--- a/academy/demo.xml
+++ b/academy/demo.xml
-@@ -9,5 +9,30 @@
- <record id="vaughn" model="academy.teachers">
+@@ -10,4 +10,29 @@
<field name="name">Lester Vaughn</field>
</record>
-+
+
+ <record id="course0" model="product.template">
+ <field name="name">Course 0</field>
+ <field name="teacher_id" ref="padilla"/>
@@ -56,7 +55,7 @@ diff --git a/academy/demo.xml b/academy/demo.xml
+ <field name="list_price" type="float">0</field>
+ <field name="type">service</field>
+ </record>
-
++
</odoo>
diff --git a/academy/models.py b/academy/models.py
--- a/academy/models.py
@@ -88,7 +87,7 @@ diff --git a/academy/security/ir.model.access.csv b/academy/security/ir.model.ac
diff --git a/academy/views.xml b/academy/views.xml
--- a/academy/views.xml
+++ b/academy/views.xml
-@@ -25,55 +25,9 @@
+@@ -26,55 +26,9 @@
</field>
</record>
diff --git a/doc/howtos/website/course-views b/doc/howtos/website/course-views
index c1373b8df74..a624ef9dc8b 100644
--- a/doc/howtos/website/course-views
+++ b/doc/howtos/website/course-views
@@ -3,7 +3,7 @@
diff -r c687289c105f -r 1012f1736bb6 academy/views.xml
--- a/academy/views.xml Thu Aug 14 12:58:50 2014 +0200
+++ b/academy/views.xml Thu Aug 14 13:00:19 2014 +0200
-@@ -18,9 +18,51 @@
+@@ -19,9 +19,51 @@
</field>
</record>
diff --git a/doc/howtos/website/demo-data b/doc/howtos/website/demo-data
index 0e34b57463b..bf50fd31632 100644
--- a/doc/howtos/website/demo-data
+++ b/doc/howtos/website/demo-data
@@ -7,7 +7,7 @@ diff --git a/academy/demo.xml b/academy/demo.xml
+++ b/academy/demo.xml
@@ -1,25 +1,13 @@
<odoo>
-
+
- <!-- -->
- <!-- <record id="object0" model="academy.academy"> -->
- <!-- <field name="name">Object 0</field> -->
@@ -38,5 +38,5 @@ diff --git a/academy/demo.xml b/academy/demo.xml
+ <record id="vaughn" model="academy.teachers">
+ <field name="name">Lester Vaughn</field>
+ </record>
-
+
</odoo>
diff --git a/doc/howtos/website/teachers-formview b/doc/howtos/website/teachers-formview
index 28dd3df9bd9..407938fde83 100644
--- a/doc/howtos/website/teachers-formview
+++ b/doc/howtos/website/teachers-formview
@@ -3,7 +3,7 @@
diff -r 0201053e995d -r c3705f93fcea academy/views.xml
--- a/academy/views.xml Wed Aug 13 11:04:49 2014 +0200
+++ b/academy/views.xml Wed Aug 13 11:04:55 2014 +0200
-@@ -4,6 +4,20 @@
+@@ -5,6 +5,20 @@
<field name="res_model">academy.teachers</field>
</record>
diff --git a/doc/howtos/website/template b/doc/howtos/website/template
index 705eb4c4165..a7e68254c65 100644
--- a/doc/howtos/website/template
+++ b/doc/howtos/website/template
@@ -21,7 +21,7 @@ diff --git a/academy/templates.xml b/academy/templates.xml
+++ b/academy/templates.xml
@@ -1,14 +1,11 @@
<odoo>
-
+
- <!-- <template id="listing"> -->
- <!-- <ul> -->
- <!-- <li t-foreach="objects" t-as="object"> -->
diff --git a/doc/howtos/website/website-support b/doc/howtos/website/website-support
index 3fadb435430..8df46dc7a5a 100644
--- a/doc/howtos/website/website-support
+++ b/doc/howtos/website/website-support
@@ -31,7 +31,7 @@ diff --git a/academy/templates.xml b/academy/templates.xml
+++ b/academy/templates.xml
@@ -1,9 +1,15 @@
<odoo>
-
+
<template id="index">
- <title>Academy</title>
- <t t-foreach="teachers" t-as="teacher">
diff --git a/doc/index.rst b/doc/index.rst
index 21231b7ce3f..8035da789f9 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -14,7 +14,7 @@ Index
:maxdepth: 2
tutorials
- api_integration
+ webservices
setup
reference
diff --git a/doc/reference.rst b/doc/reference.rst
index d83ef3bb476..1b41f4cdbd2 100644
--- a/doc/reference.rst
+++ b/doc/reference.rst
@@ -21,5 +21,4 @@ Reference
reference/reports
reference/mixins
reference/guidelines
- reference/upgrade_api
reference/mobile
diff --git a/doc/reference/views.rst b/doc/reference/views.rst
index 2f41bb61a7f..c2463245998 100644
--- a/doc/reference/views.rst
+++ b/doc/reference/views.rst
@@ -881,8 +881,6 @@ Possible children of the view element are:
The kanban view uses mostly-standard :ref:`javascript qweb
<reference/qweb/javascript>` and provides the following context variables:
- ``instance``
- the current :ref:`reference/javascript/client` instance
``widget``
the current :js:class:`KanbanRecord`, can be used to fetch some
meta-information. These methods are also available directly in the
@@ -894,8 +892,6 @@ Possible children of the view element are:
a :meth:`~odoo.models.Model.read` (except for date and datetime fields
that are `formatted according to user's locale
<https://github.com/odoo/odoo/blob/a678bd4e/addons/web_kanban/static/src/js/kanban_record.js#L102>`_)
- ``formats``
- the :js:class:`web.formats` module to manipulate and convert values
``read_only_mode``
self-explanatory
@@ -929,40 +925,7 @@ Possible children of the view element are:
* kanban-specific CSS
* kanban structures/widgets (vignette, details, ...)
-Javascript API
---------------
-
-.. js:class:: KanbanRecord
-
- :js:class:`Widget` handling the rendering of a single record to a
- card. Available within its own rendering as ``widget`` in the template
- context.
-
- .. js:function:: kanban_color(raw_value)
-
- Converts a color segmentation value to a kanban color class
- :samp:`oe_kanban_color_{color_index}`. The built-in CSS provides classes
- up to a ``color_index`` of 9.
-
- .. js:function:: kanban_getcolor(raw_value)
-
- Converts a color segmentation value to a color index (between 0 and 9 by
- default). Color segmentation values can be either numbers or strings.
-
- .. js:function:: kanban_image(model, field, id[, cache][, options])
-
- Generates the URL to the specified field as an image access.
-
- :param String model: model hosting the image
- :param String field: name of the field holding the image data
- :param id: identifier of the record contaning the image to display
- :param Number cache: caching duration (in seconds) of the browser
- default should be overridden. ``0`` disables
- caching entirely
- :returns: an image URL
-
- .. warning::
- ``kanban_text_ellipsis`` has been removed in Odoo 9. CSS ``text-overflow`` should be used instead.
+If you need to extend the Kanban view, see :js:class::`the JS API <KanbanRecord>`.
.. _reference/views/calendar:
@@ -1029,8 +992,6 @@ calendar view are:
The kanban view uses mostly-standard :ref:`javascript qweb
<reference/qweb/javascript>` and provides the following context variables:
- ``instance``
- the current :ref:`reference/javascript/client` instance
``widget``
the current :js:class:`KanbanRecord`, can be used to fetch some
meta-information. These methods are also available directly in the
diff --git a/doc/requirements.txt b/doc/requirements.txt
index 6c2a5df88c2..160e375ba55 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -1,4 +1,2 @@
sphinx>=1.2
-mercurial==3.2.2
-sphinx-patchqueue==0.4.0
-PyJSDoc==0.9.1
+sphinx-patchqueue>=1.0
diff --git a/doc/setup/deploy.rst b/doc/setup/deploy.rst
index 9879117b607..ed1825298e2 100644
--- a/doc/setup/deploy.rst
+++ b/doc/setup/deploy.rst
@@ -71,7 +71,8 @@ in ``/etc/odoo.conf`` set:
Once it is correctly working and only matching a single database per hostname, it
is strongly recommended to block access to the database manager screens,
and to use the ``--no-database-list`` startup paramater to prevent listing
- your databases. See also security_.
+ your databases, and to block access to the database management screens.
+ See also security_.
PostgreSQL
@@ -213,10 +214,6 @@ Instead you must have a proxy redirecting requests whose URL starts with
``/longpolling/`` to the longpolling port. Other request should be proxied to
the :option:`normal HTTP port <odoo-bin --http-port>`
-.. warning:: The livechat worker requires the ``psycogreen`` Python module,
- which is not always included with all installation packages.
- It can be manually installed with ``pip install psycogreen``.
-
Configuration sample
--------------------
@@ -427,10 +424,14 @@ security-related topics:
- Use appropriate database filters ( :option:`--db-filter <odoo-bin --db-filter>`)
to restrict the visibility of your databases according to the hostname.
See :ref:`db_filter`.
-
-- Once your ``db_filter`` is configured and only matches a single database per hostname,
- you should set ``list_db`` configuration option to ``False``, to prevent listing databases
- entirely (this is also exposed as the :option:`--no-database-list <odoo-bin --no-database-list>`
+ You may also use :option:`-d <odoo-bin -d>` to provide your own (comma-separated)
+ list of available databases to filter from, instead of letting the system fetch
+ them all from the database backend.
+
+- Once your ``db_name`` and ``db_filter`` are configured and only match a single database
+ per hostname, you should set ``list_db`` configuration option to ``False``, to prevent
+ listing databases entirely, and to block access to the database management screens
+ (this is also exposed as the :option:`--no-database-list <odoo-bin --no-database-list>`
command-line option)
- Make sure the PostgreSQL user (:option:`--db_user <odoo-bin --db_user>`) is *not* a super-user,
@@ -473,10 +474,16 @@ Database Manager Security
This setting is used on all database management screens (to create, delete,
dump or restore databases).
-If the management screens must not be accessible, or must only be accessible
-from a selected set of machines, use the proxy server's features to block
-access to all routes starting with ``/web/database`` except (maybe)
-``/web/database/selector`` which displays the database-selection screen.
+If the management screens must not be accessible at all, you should set ``list_db``
+configuration option to ``False``, to block access to all the database selection and
+management screens. But be sure to setup an appropriate ``db_name`` parameter
+(and optionally, ``db_filter`` too) so that the system can determine the target database
+for each request, otherwise users will be blocked as they won't be allowed to choose the
+database themselves.
+
+If the management screens must only be accessible from a selected set of machines,
+use the proxy server's features to block access to all routes starting with ``/web/database``
+except (maybe) ``/web/database/selector`` which displays the database-selection screen.
If the database-management screen should be left accessible, the
``admin_passwd`` setting must be changed from its ``admin`` default: this
diff --git a/doc/setup/install.rst b/doc/setup/install.rst
index 8e25e5e3ded..c476e4adabe 100644
--- a/doc/setup/install.rst
+++ b/doc/setup/install.rst
@@ -411,7 +411,7 @@ Source installation requires manually installing dependencies:
.. code-block:: console
- $ pip install -r requirements.txt
+ $ pip3 install -r requirements.txt
- on OS X, you will need to install the Command Line Tools
(``xcode-select --install``) then download and install a package manager
@@ -420,7 +420,7 @@ Source installation requires manually installing dependencies:
.. code-block:: console
- $ pip install -r requirements.txt
+ $ pip3 install -r requirements.txt
- on Windows you need to install some of the dependencies manually, tweak the
requirements.txt file, then run pip to install the remaning ones.
diff --git a/doc/webservices.rst b/doc/webservices.rst
new file mode 100644
index 00000000000..3dad86448ba
--- /dev/null
+++ b/doc/webservices.rst
@@ -0,0 +1,13 @@
+:banner: banners/web_service_api.jpg
+:types: api
+
+============
+Web Services
+============
+
+.. toctree::
+ :titlesonly:
+
+ webservices/odoo
+ webservices/iap
+ webservices/upgrade
diff --git a/doc/webservices/flow.png b/doc/webservices/flow.png
new file mode 100644
index 00000000000..df4bf7cfbbb
--- /dev/null
+++ b/doc/webservices/flow.png
Binary files differ
diff --git a/doc/webservices/iap.rst b/doc/webservices/iap.rst
new file mode 100644
index 00000000000..18af76900fb
--- /dev/null
+++ b/doc/webservices/iap.rst
@@ -0,0 +1,235 @@
+:types: api
+
+
+:code-column:
+
+.. _webservices/iap:
+
+================
+In-App Purchases
+================
+
+IAP allow providers of ongoing services through Odoo apps to be compensated
+for ongoing service use rather than — and possibly instead of — a sole initial
+purchase.
+
+In that context, Odoo acts mostly as a *broker* between the service user
+(client) and the service provider (Odoo App developer):
+
+* users purchase service tokens from Odoo
+* service providers draw tokens from the user's Odoo account
+
+.. attention::
+
+ This document is intended for *service providers* and presents the latter,
+ which can be done either via direct JSON-RPC2_ or if you are using Odoo
+ using the convenience helpers it provides.
+
+.. image:: flow.png
+ :align: center
+
+.. contents::
+ :local:
+
+JSON-RPC2_ Transaction API
+==========================
+
+* The IAP transaction API does not require using Odoo when implementing your
+ server gateway, calls are standard JSON-RPC2_.
+* Calls use different *endpoints* but the same *method* on all endpoints
+ (``call``).
+* Exceptions are returned as JSON-RPC2_ errors, the formal exception name is
+ available on ``data.name`` for programmatic manipulation.
+
+.. class:: ServiceKey
+
+ Identifier generated for the provider's service. Each key (and service)
+ matches a token of a fixed value, as generated by the service provide.
+
+ Multiple types of tokens correspond to multiple services e.g. SMS and MMS
+ could either be the same service (with an MMS being "worth" multiple SMS)
+ or could be separate services at separate price points.
+
+ .. danger:: your service key *is a secret*, leaking your service key
+ allows other application developers to draw credits bought for
+ your service(s)
+
+.. class:: UserToken
+
+ Identifier for a user account.
+
+.. class:: TransactionToken
+
+ Transaction identifier, returned by the authorization process and consumed
+ by either capturing or cancelling the transaction
+
+.. exception:: odoo.addons.iap.models.iap.NoCreditError
+
+ Raised during transaction authorization if the credits requested are not
+ currently available on the account (either not enough credits or too many
+ pending transactions/existing holds).
+
+.. exception:: odoo.addons.iap.models.iap.BadAuthError
+
+ Raised by any operation to which a service token is required, if the
+ service token is invalid.
+
+Authorize
+---------
+
+.. function:: /iap/1/authorize
+
+ Verifies that the user's account has at least as ``credit`` available
+ *and creates a hold (pending transaction) on that amount*.
+
+ Any amount currently on hold by a pending transaction is considered
+ unavailable to further authorize calls.
+
+ Returns a :class:`TransactionToken` identifying the pending transaction
+ which can be used to capture (confirm) or cancel said transaction.
+
+ :param ServiceKey key:
+ :param UserToken account_token:
+ :param int credit:
+ :param str description: optional, helps users identify the reason for
+ charges on their accounts.
+ :returns: :class:`TransactionToken` if the authorization succeeded.
+ :raises: :class:`~odoo.addons.iap.models.iap.BadAuthError` if the service token is invalid
+ :raises: :class:`~odoo.addons.iap.models.iap.NoCreditError` if the account does
+ :raises: ``TypeError`` if the ``credit`` value is not an integer
+
+.. rst-class:: doc-aside
+
+.. code-block:: python
+
+ r = requests.post(ODOO + '/iap/1/authorize', json={
+ 'jsonrpc': '2.0',
+ 'id': None,
+ 'method': 'call',
+ 'params': {
+ 'account_token': user_account,
+ 'key': SERVICE_KEY,
+ 'credit': 25,
+ 'description': "Why this is being charged",
+ }
+ }).json()
+ if 'error' in r:
+ # handle authorize error
+ tx = r['result']
+
+ # provide your service here
+
+Capture
+-------
+
+.. function:: /iap/1/capture
+
+ Confirms the specified transaction, transferring the reserved credits from
+ the user's account to the service provider's.
+
+ Capture calls are idempotent: performing capture calls on an already
+ captured transaction has no further effect.
+
+ :param TransactionToken token:
+ :param ServiceKey key:
+ :raises: :class:`~odoo.addons.iap.models.iap.BadAuthError`
+
+.. rst-class:: doc-aside
+
+.. code-block:: python
+
+ r2 = requests.post(ODOO + '/iap/1/capture', json={
+ 'jsonrpc': '2.0',
+ 'id': None,
+ 'method': 'call',
+ 'params': {
+ 'token': tx,
+ 'key': SERVICE_KEY,
+ }
+ }).json()
+ if 'error' in r:
+ # handle capture error
+ # otherwise transaction is captured
+
+Cancel
+------
+
+.. function:: /iap/1/cancel
+
+ Cancels the specified transaction, releasing the hold on the user's
+ credits.
+
+ Cancel calls are idempotent: performing capture calls on an already
+ cancelled transaction has no further effect.
+
+ :param TransactionToken token:
+ :param ServiceKey key:
+ :raises: :class:`~odoo.addons.iap.models.iap.BadAuthError`
+
+.. rst-class:: doc-aside
+
+.. code-block:: python
+
+ r2 = requests.post(ODOO + '/iap/1/cancel', json={
+ 'jsonrpc': '2.0',
+ 'id': None,
+ 'method': 'call',
+ 'params': {
+ 'token': tx,
+ 'key': SERVICE_KEY,
+ }
+ }).json()
+ if 'error' in r:
+ # handle cancel error
+ # otherwise transaction is cancelled
+
+Odoo Helpers
+============
+
+For convenience, if you are implementing your service using Odoo the ``iap``
+module provides a few helpers to make IAP flow even simpler:
+
+Charging
+--------
+
+.. class:: odoo.addons.iap.models.iap.charge(env, key, account_token, credit[, description])
+
+ A *context manager* for authorizing and automatically capturing or
+ cancelling transactions for use in the backend/proxy.
+
+ Works much like e.g. a cursor context manager:
+
+ * immediately authorizes a transaction with the specified parameters
+ * executes the ``with`` body
+ * if the body executes in full without error, captures the transaction
+ * otherwise cancels it
+
+ :param odoo.api.Environment env: used to retrieve the ``iap.endpoint``
+ configuration key
+ :param ServiceKey key:
+ :param UserToken token:
+ :param int credit:
+ :param str description:
+
+.. rst-class:: doc-aside
+
+.. code-block:: python
+
+ @route('/deathstar/superlaser', type='json')
+ def superlaser(self, user_account,
+ coordinates, target,
+ factor=1.0):
+ """
+ :param factor: superlaser power factor,
+ 0.0 is none, 1.0 is full power
+ """
+ credits = int(MAXIMUM_POWER * factor)
+ with charge(request.env, SERVICE_KEY, user_account, credits):
+ # TODO: allow other targets
+ self.env['systems.planets'].search([
+ ('grid', '=', 'M-10'),
+ ('name', '=', 'Alderaan'),
+ ]).unlink()
+
+
+.. _JSON-RPC2: http://www.jsonrpc.org/specification
diff --git a/doc/api_integration.rst b/doc/webservices/odoo.rst
index 612e91ed854..f68862626a7 100644
--- a/doc/api_integration.rst
+++ b/doc/webservices/odoo.rst
@@ -4,9 +4,9 @@
:code-column:
-===============
-Web Service API
-===============
+===========================
+Odoo Remote Procedure Calls
+===========================
Odoo is usually extended internally via modules, but many of its features and
all of its data are also available from the outside for external analysis or
diff --git a/doc/reference/upgrade_api.rst b/doc/webservices/upgrade.rst
index 04edf919e83..fa168935b23 100644
--- a/doc/reference/upgrade_api.rst
+++ b/doc/webservices/upgrade.rst
@@ -6,9 +6,9 @@
.. _reference/upgrade-api:
-===========
-Upgrade API
-===========
+================
+Database Upgrade
+================
Introduction
~~~~~~~~~~~~