diff options
| author | Christophe Simonis <chs@odoo.com> | 2017-10-10 19:14:30 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2017-10-10 19:14:30 +0200 |
| commit | 0be264b29eac11a5dec5e6565d23d4ea86bd905a (patch) | |
| tree | 79362f2340c89cb3cf2e8d8a467f357b7c7a8326 /doc | |
| parent | cf753a7fdf1739dcf37d2ad74f4c8efb3d4ff3b3 (diff) | |
| parent | e1104ac344cbc349a46b22339b13dfe9cbab8bcb (diff) | |
[MERGE] forward port branch 11.0 up to e1104ac344c
Diffstat (limited to 'doc')
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 Binary files differnew file mode 100644 index 00000000000..df4bf7cfbbb --- /dev/null +++ b/doc/webservices/flow.png 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 ~~~~~~~~~~~~ |
