diff options
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/_themes/odoodoc/layout.html | 6 | ||||
| -rw-r--r-- | doc/_themes/odoodoc/static/app.js | 5 | ||||
| -rw-r--r-- | doc/_themes/odoodoc/static/style.css | 431 | ||||
| -rw-r--r-- | doc/_themes/odoodoc/static/style.less | 158 | ||||
| -rw-r--r-- | doc/api_integration.rst | 750 | ||||
| -rw-r--r-- | doc/conf.py | 2 | ||||
| -rw-r--r-- | doc/howtos/backend.rst | 10 | ||||
| -rw-r--r-- | doc/howtos/themes.rst | 4 | ||||
| -rw-r--r-- | doc/howtos/web.rst | 6 | ||||
| -rw-r--r-- | doc/howtos/website.rst | 4 | ||||
| -rw-r--r-- | doc/index.rst | 4 | ||||
| -rw-r--r-- | doc/reference.rst | 1 | ||||
| -rw-r--r-- | doc/reference/cmdline.rst | 6 | ||||
| -rw-r--r-- | doc/reference/security.rst | 2 | ||||
| -rw-r--r-- | doc/reference/translations.rst | 96 | ||||
| -rw-r--r-- | doc/reference/translations/po-export.png | bin | 0 -> 6069 bytes | |||
| -rw-r--r-- | doc/reference/views.rst | 6 | ||||
| -rw-r--r-- | doc/reference/workflows.rst | 2 | ||||
| -rw-r--r-- | doc/tutorials.rst | 2 |
19 files changed, 1359 insertions, 136 deletions
diff --git a/doc/_themes/odoodoc/layout.html b/doc/_themes/odoodoc/layout.html index 5235137eddd..b01eaf05216 100644 --- a/doc/_themes/odoodoc/layout.html +++ b/doc/_themes/odoodoc/layout.html @@ -14,6 +14,12 @@ <!doctype html> {%- endblock -%} +{%- block htmltitle -%} + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + {{ super() }} +{%- endblock -%} + {%- block extrahead -%} {%- if canonical -%} <link rel="canonical" href="{{ canonical }}"/> diff --git a/doc/_themes/odoodoc/static/app.js b/doc/_themes/odoodoc/static/app.js index 47c33f41d23..a0d7376530a 100644 --- a/doc/_themes/odoodoc/static/app.js +++ b/doc/_themes/odoodoc/static/app.js @@ -77,7 +77,7 @@ $(function () { var sheet = findSheet(/style\.css$/); if (!sheet) { return; } // build PL switcher UI and hook toggle event - $(buildSwitcher(Object.keys(languages))) + var $switcher = $(buildSwitcher(Object.keys(languages))) .prependTo('div.documentwrapper') .on('click', 'li', function (e) { $(e.target).addClass('active') @@ -93,7 +93,8 @@ $(function () { ].join(''); sheet.deleteRule(lastIndex); sheet.insertRule(sel + '{' + content + '}', lastIndex); - }); + }); + $switcher.affix(); })(); } diff --git a/doc/_themes/odoodoc/static/style.css b/doc/_themes/odoodoc/static/style.css index 7c0b8e82959..12ffb135f6f 100644 --- a/doc/_themes/odoodoc/static/style.css +++ b/doc/_themes/odoodoc/static/style.css @@ -1,4 +1,4 @@ -@import url(http://fonts.googleapis.com/css?family=Lato); +@import url(https://fonts.googleapis.com/css?family=Lato); /*! normalize.css v3.0.1 | MIT License | git.io/normalize */ html { font-family: sans-serif; @@ -6808,11 +6808,405 @@ td.field-body > ul { float: left; clear: left; } + .stripe .section > .force-right { + padding-left: 1em; + padding-right: 1em; + } .stripe .section > .force-right, .stripe .section > [class*=highlight] { float: none; clear: none; + margin-left: 50%; + width: 50%; + color: #eeeeee; + } + .stripe .section > .force-right a, + .stripe .section > [class*=highlight] a { + color: #d9a8cc; + } + .stripe .section > .force-right code, + .stripe .section > [class*=highlight] code, + .stripe .section > .force-right .literal, + .stripe .section > [class*=highlight] .literal { + color: #f9f2f4; + background-color: #555555; + } + .stripe .section > .force-right:not(.highlight-json) .highlight, + .stripe .section > [class*=highlight]:not(.highlight-json) .highlight { + border-bottom-color: #777777; + } + .stripe .section > .force-right.admonition, + .stripe .section > [class*=highlight].admonition { margin-left: 51%; + width: 49%; + border-top-color: #777777; + border-bottom-color: #777777; + border-right-color: #777777; + } + .stripe .section > .force-right pre, + .stripe .section > [class*=highlight] pre { + padding: 0; + margin: 10px; + background: none; + border: none; + } + .stripe .section > .force-right .highlight, + .stripe .section > [class*=highlight] .highlight { + border-color: #555555; + border-style: solid; + border-width: 1px 0; + color: #cccccc; + background: none; + /* Line Numbers */ + /* Comment */ + /* Error */ + /* Generic */ + /* Keyword */ + /* Literal */ + /* Name */ + /* Operator */ + /* Other */ + /* Punctuation */ + /* Comment.Multiline */ + /* Comment.Preproc */ + /* Comment.Single */ + /* Comment.Special */ + /* Generic.Deleted */ + /* Generic.Emph */ + /* Generic.Error */ + /* Generic.Heading */ + /* Generic.Inserted */ + /* Generic.Output */ + /* Generic.Prompt */ + /* Generic.Strong */ + /* Generic.Subheading */ + /* Generic.Traceback */ + /* Keyword.Constant */ + /* Keyword.Declaration */ + /* Keyword.Namespace */ + /* Keyword.Pseudo */ + /* Keyword.Reserved */ + /* Keyword.Type */ + /* Literal.Date */ + /* Literal.Number */ + /* Literal.String */ + /* Name.Attribute */ + /* Name.Builtin */ + /* Name.Class */ + /* Name.Constant */ + /* Name.Decorator */ + /* Name.Entity */ + /* Name.Exception */ + /* Name.Function */ + /* Name.Label */ + /* Name.Namespace */ + /* Name.Other */ + /* Name.Property */ + /* Name.Tag */ + /* Name.Variable */ + /* Operator.Word */ + /* Text.Whitespace */ + /* Literal.Number.Float */ + /* Literal.Number.Hex */ + /* Literal.Number.Integer */ + /* Literal.Number.Oct */ + /* Literal.String.Backtick */ + /* Literal.String.Char */ + /* Literal.String.Doc */ + /* Literal.String.Double */ + /* Literal.String.Escape */ + /* Literal.String.Heredoc */ + /* Literal.String.Interpol */ + /* Literal.String.Other */ + /* Literal.String.Regex */ + /* Literal.String.Single */ + /* Literal.String.Symbol */ + /* Name.Builtin.Pseudo */ + /* Name.Variable.Class */ + /* Name.Variable.Global */ + /* Name.Variable.Instance */ + /* Literal.Number.Integer.Long */ + } + .stripe .section > .force-right .highlight .lineno, + .stripe .section > [class*=highlight] .highlight .lineno { + color: #586e75; + } + .stripe .section > .force-right .highlight .c, + .stripe .section > [class*=highlight] .highlight .c { + color: #586e75; + } + .stripe .section > .force-right .highlight .err, + .stripe .section > [class*=highlight] .highlight .err { + color: #cccccc; + } + .stripe .section > .force-right .highlight .g, + .stripe .section > [class*=highlight] .highlight .g { + color: #cccccc; + } + .stripe .section > .force-right .highlight .k, + .stripe .section > [class*=highlight] .highlight .k { + color: #859900; + } + .stripe .section > .force-right .highlight .l, + .stripe .section > [class*=highlight] .highlight .l { + color: #cccccc; + } + .stripe .section > .force-right .highlight .n, + .stripe .section > [class*=highlight] .highlight .n { + color: #cccccc; + } + .stripe .section > .force-right .highlight .o, + .stripe .section > [class*=highlight] .highlight .o { + color: #859900; + } + .stripe .section > .force-right .highlight .x, + .stripe .section > [class*=highlight] .highlight .x { + color: #cb4b16; + } + .stripe .section > .force-right .highlight .p, + .stripe .section > [class*=highlight] .highlight .p { + color: #cccccc; + } + .stripe .section > .force-right .highlight .cm, + .stripe .section > [class*=highlight] .highlight .cm { + color: #586e75; + } + .stripe .section > .force-right .highlight .cp, + .stripe .section > [class*=highlight] .highlight .cp { + color: #859900; + } + .stripe .section > .force-right .highlight .c1, + .stripe .section > [class*=highlight] .highlight .c1 { + color: #586e75; + } + .stripe .section > .force-right .highlight .cs, + .stripe .section > [class*=highlight] .highlight .cs { + color: #859900; + } + .stripe .section > .force-right .highlight .gd, + .stripe .section > [class*=highlight] .highlight .gd { + color: #2aa198; + } + .stripe .section > .force-right .highlight .ge, + .stripe .section > [class*=highlight] .highlight .ge { + color: #cccccc; + font-style: italic; + } + .stripe .section > .force-right .highlight .gr, + .stripe .section > [class*=highlight] .highlight .gr { + color: #dc322f; + } + .stripe .section > .force-right .highlight .gh, + .stripe .section > [class*=highlight] .highlight .gh { + color: #cb4b16; + } + .stripe .section > .force-right .highlight .gi, + .stripe .section > [class*=highlight] .highlight .gi { + color: #859900; + } + .stripe .section > .force-right .highlight .go, + .stripe .section > [class*=highlight] .highlight .go { + color: #cccccc; + } + .stripe .section > .force-right .highlight .gp, + .stripe .section > [class*=highlight] .highlight .gp { + color: #cccccc; + } + .stripe .section > .force-right .highlight .gs, + .stripe .section > [class*=highlight] .highlight .gs { + color: #cccccc; + font-weight: bold; + } + .stripe .section > .force-right .highlight .gu, + .stripe .section > [class*=highlight] .highlight .gu { + color: #cb4b16; + } + .stripe .section > .force-right .highlight .gt, + .stripe .section > [class*=highlight] .highlight .gt { + color: #cccccc; + } + .stripe .section > .force-right .highlight .kc, + .stripe .section > [class*=highlight] .highlight .kc { + color: #cb4b16; + } + .stripe .section > .force-right .highlight .kd, + .stripe .section > [class*=highlight] .highlight .kd { + color: #268bd2; + } + .stripe .section > .force-right .highlight .kn, + .stripe .section > [class*=highlight] .highlight .kn { + color: #859900; + } + .stripe .section > .force-right .highlight .kp, + .stripe .section > [class*=highlight] .highlight .kp { + color: #859900; + } + .stripe .section > .force-right .highlight .kr, + .stripe .section > [class*=highlight] .highlight .kr { + color: #268bd2; + } + .stripe .section > .force-right .highlight .kt, + .stripe .section > [class*=highlight] .highlight .kt { + color: #dc322f; + } + .stripe .section > .force-right .highlight .ld, + .stripe .section > [class*=highlight] .highlight .ld { + color: #cccccc; + } + .stripe .section > .force-right .highlight .m, + .stripe .section > [class*=highlight] .highlight .m { + color: #2aa198; + } + .stripe .section > .force-right .highlight .s, + .stripe .section > [class*=highlight] .highlight .s { + color: #2aa198; + } + .stripe .section > .force-right .highlight .na, + .stripe .section > [class*=highlight] .highlight .na { + color: #cccccc; + } + .stripe .section > .force-right .highlight .nb, + .stripe .section > [class*=highlight] .highlight .nb { + color: #b58900; + } + .stripe .section > .force-right .highlight .nc, + .stripe .section > [class*=highlight] .highlight .nc { + color: #268bd2; + } + .stripe .section > .force-right .highlight .no, + .stripe .section > [class*=highlight] .highlight .no { + color: #cb4b16; + } + .stripe .section > .force-right .highlight .nd, + .stripe .section > [class*=highlight] .highlight .nd { + color: #268bd2; + } + .stripe .section > .force-right .highlight .ni, + .stripe .section > [class*=highlight] .highlight .ni { + color: #cb4b16; + } + .stripe .section > .force-right .highlight .ne, + .stripe .section > [class*=highlight] .highlight .ne { + color: #cb4b16; + } + .stripe .section > .force-right .highlight .nf, + .stripe .section > [class*=highlight] .highlight .nf { + color: #268bd2; + } + .stripe .section > .force-right .highlight .nl, + .stripe .section > [class*=highlight] .highlight .nl { + color: #cccccc; + } + .stripe .section > .force-right .highlight .nn, + .stripe .section > [class*=highlight] .highlight .nn { + color: #cccccc; + } + .stripe .section > .force-right .highlight .nx, + .stripe .section > [class*=highlight] .highlight .nx { + color: #cccccc; + } + .stripe .section > .force-right .highlight .py, + .stripe .section > [class*=highlight] .highlight .py { + color: #cccccc; + } + .stripe .section > .force-right .highlight .nt, + .stripe .section > [class*=highlight] .highlight .nt { + color: #268bd2; + } + .stripe .section > .force-right .highlight .nv, + .stripe .section > [class*=highlight] .highlight .nv { + color: #268bd2; + } + .stripe .section > .force-right .highlight .ow, + .stripe .section > [class*=highlight] .highlight .ow { + color: #859900; + } + .stripe .section > .force-right .highlight .w, + .stripe .section > [class*=highlight] .highlight .w { + color: #cccccc; + } + .stripe .section > .force-right .highlight .mf, + .stripe .section > [class*=highlight] .highlight .mf { + color: #2aa198; + } + .stripe .section > .force-right .highlight .mh, + .stripe .section > [class*=highlight] .highlight .mh { + color: #2aa198; + } + .stripe .section > .force-right .highlight .mi, + .stripe .section > [class*=highlight] .highlight .mi { + color: #2aa198; + } + .stripe .section > .force-right .highlight .mo, + .stripe .section > [class*=highlight] .highlight .mo { + color: #2aa198; + } + .stripe .section > .force-right .highlight .sb, + .stripe .section > [class*=highlight] .highlight .sb { + color: #586e75; + } + .stripe .section > .force-right .highlight .sc, + .stripe .section > [class*=highlight] .highlight .sc { + color: #2aa198; + } + .stripe .section > .force-right .highlight .sd, + .stripe .section > [class*=highlight] .highlight .sd { + color: #cccccc; + } + .stripe .section > .force-right .highlight .s2, + .stripe .section > [class*=highlight] .highlight .s2 { + color: #2aa198; + } + .stripe .section > .force-right .highlight .se, + .stripe .section > [class*=highlight] .highlight .se { + color: #cb4b16; + } + .stripe .section > .force-right .highlight .sh, + .stripe .section > [class*=highlight] .highlight .sh { + color: #cccccc; + } + .stripe .section > .force-right .highlight .si, + .stripe .section > [class*=highlight] .highlight .si { + color: #2aa198; + } + .stripe .section > .force-right .highlight .sx, + .stripe .section > [class*=highlight] .highlight .sx { + color: #2aa198; + } + .stripe .section > .force-right .highlight .sr, + .stripe .section > [class*=highlight] .highlight .sr { + color: #dc322f; + } + .stripe .section > .force-right .highlight .s1, + .stripe .section > [class*=highlight] .highlight .s1 { + color: #2aa198; + } + .stripe .section > .force-right .highlight .ss, + .stripe .section > [class*=highlight] .highlight .ss { + color: #2aa198; + } + .stripe .section > .force-right .highlight .bp, + .stripe .section > [class*=highlight] .highlight .bp { + color: #268bd2; + } + .stripe .section > .force-right .highlight .vc, + .stripe .section > [class*=highlight] .highlight .vc { + color: #268bd2; + } + .stripe .section > .force-right .highlight .vg, + .stripe .section > [class*=highlight] .highlight .vg { + color: #268bd2; + } + .stripe .section > .force-right .highlight .vi, + .stripe .section > [class*=highlight] .highlight .vi { + color: #268bd2; + } + .stripe .section > .force-right .highlight .il, + .stripe .section > [class*=highlight] .highlight .il { + color: #2aa198; + } + .stripe .body > .section > .section { + border-top: 1px solid #eeeeee; } .stripe .section > h1, .stripe .section > h2, @@ -6820,7 +7214,7 @@ td.field-body > ul { .stripe .section > h4, .stripe .section > h5, .stripe .section > h6 { - background-color: rgba(255, 255, 255, 0.7); + max-width: 50%; } .stripe .section > h1, .stripe .section > h2, @@ -6834,27 +7228,40 @@ td.field-body > ul { float: none; clear: both; } - .stripe .bodywrapper { - position: relative; + .stripe .documentwrapper { + overflow: auto; } - .stripe .bodywrapper:before { + .stripe .documentwrapper:before { position: absolute; top: 0; bottom: 0; left: 50%; content: ""; - width: 0; - border-left: 1px solid #777777; + width: 50%; + background: #333333; } .stripe .highlight pre { white-space: pre-wrap; } + .stripe .btn-clipboard { + background-color: transparent; + color: #eeeeee; + border-color: #777777; + border-top: none; + border-top-right-radius: 0; + } + .stripe .btn-clipboard.active { + background-color: #555555; + -webkit-transition: background-color 0.5s linear; + -o-transition: background-color 0.5s linear; + transition: background-color 0.5s linear; + } } .stripe .switcher { color: white; width: auto !important; float: none !important; - position: fixed; + position: absolute; display: -webkit-flex; display: flex; -webkit-justify-content: flex-end; @@ -6865,6 +7272,10 @@ td.field-body > ul { margin: 0; z-index: 5; } +.stripe .switcher.affix { + position: fixed; + top: 0; +} .stripe .switcher li { background-color: #0f131a; padding: 0.4em 1em; @@ -6874,10 +7285,10 @@ td.field-body > ul { } .stripe .switcher li:first-child { border-left-width: 1px; - border-radius: 5px 0 0 5px; + border-radius: 0 0 0 5px; } .stripe .switcher li:last-child { - border-radius: 0 5px 5px 0; + border-radius: 0 0 5px 0; } .stripe .switcher li:hover { background-color: #222; diff --git a/doc/_themes/odoodoc/static/style.less b/doc/_themes/odoodoc/static/style.less index ea53cd95e85..d3703999414 100644 --- a/doc/_themes/odoodoc/static/style.less +++ b/doc/_themes/odoodoc/static/style.less @@ -1,5 +1,5 @@ @import "bootstrap/less/bootstrap"; -@import url(http://fonts.googleapis.com/css?family=Lato); +@import url(https://fonts.googleapis.com/css?family=Lato); @icon-font-path: 'fonts/'; @brand-primary: #a24689; @@ -472,7 +472,6 @@ div.section > h2 { */ .highlight pre { padding: 4px; - font-size: 75%; // don't break lines within words word-break: normal; @@ -590,6 +589,11 @@ td.field-body { } } } + +@background-right: @gray-dark; +@color-right: @gray-lighter; +@color-right-code: lighten(#000, 80%); +@separator-right: @gray-light; // STRIPE-STYLE PAGES .stripe { .section { @@ -606,16 +610,129 @@ td.field-body { clear: left; } // column 2 + .section > .force-right { + padding-left: 1em; + padding-right: 1em; + } .section > .force-right, .section > [class*=highlight] { float: none; clear: none; - margin-left: 51%; + margin-left: 50%; + width: 50%; + color: @color-right; + a { + color: lighten(@link-color, 30%); + } + code, .literal { + color: @code-bg; + background-color: @gray; + } + &:not(.highlight-json) .highlight { + border-bottom-color: @separator-right; + } + &.admonition { + margin-left: 51%; + width: 49%; + border-top-color: @separator-right; + border-bottom-color: @separator-right; + border-right-color: @separator-right; + } + pre { + // padding leads to overflow scrollbar in safari for no reason I understand, + // margin does not have that issue + padding: 0; + margin: 10px; + + background: none; + border: none; + } + .highlight { + border-color: @gray; + border-style: solid; + border-width: 1px 0; + // solarized-ish from https://gist.github.com/qguv/7936275 + color: @color-right-code; + background: none; + .lineno { color: #586e75 } /* Line Numbers */ + .c { color: #586e75 } /* Comment */ + .err { color: @color-right-code } /* Error */ + .g { color: @color-right-code } /* Generic */ + .k { color: #859900 } /* Keyword */ + .l { color: @color-right-code } /* Literal */ + .n { color: @color-right-code } /* Name */ + .o { color: #859900 } /* Operator */ + .x { color: #cb4b16 } /* Other */ + .p { color: @color-right-code } /* Punctuation */ + .cm { color: #586e75 } /* Comment.Multiline */ + .cp { color: #859900 } /* Comment.Preproc */ + .c1 { color: #586e75 } /* Comment.Single */ + .cs { color: #859900 } /* Comment.Special */ + .gd { color: #2aa198 } /* Generic.Deleted */ + .ge { color: @color-right-code; font-style: italic } /* Generic.Emph */ + .gr { color: #dc322f } /* Generic.Error */ + .gh { color: #cb4b16 } /* Generic.Heading */ + .gi { color: #859900 } /* Generic.Inserted */ + .go { color: @color-right-code } /* Generic.Output */ + .gp { color: @color-right-code } /* Generic.Prompt */ + .gs { color: @color-right-code; font-weight: bold } /* Generic.Strong */ + .gu { color: #cb4b16 } /* Generic.Subheading */ + .gt { color: @color-right-code } /* Generic.Traceback */ + .kc { color: #cb4b16 } /* Keyword.Constant */ + .kd { color: #268bd2 } /* Keyword.Declaration */ + .kn { color: #859900 } /* Keyword.Namespace */ + .kp { color: #859900 } /* Keyword.Pseudo */ + .kr { color: #268bd2 } /* Keyword.Reserved */ + .kt { color: #dc322f } /* Keyword.Type */ + .ld { color: @color-right-code } /* Literal.Date */ + .m { color: #2aa198 } /* Literal.Number */ + .s { color: #2aa198 } /* Literal.String */ + .na { color: @color-right-code } /* Name.Attribute */ + .nb { color: #B58900 } /* Name.Builtin */ + .nc { color: #268bd2 } /* Name.Class */ + .no { color: #cb4b16 } /* Name.Constant */ + .nd { color: #268bd2 } /* Name.Decorator */ + .ni { color: #cb4b16 } /* Name.Entity */ + .ne { color: #cb4b16 } /* Name.Exception */ + .nf { color: #268bd2 } /* Name.Function */ + .nl { color: @color-right-code } /* Name.Label */ + .nn { color: @color-right-code } /* Name.Namespace */ + .nx { color: @color-right-code } /* Name.Other */ + .py { color: @color-right-code } /* Name.Property */ + .nt { color: #268bd2 } /* Name.Tag */ + .nv { color: #268bd2 } /* Name.Variable */ + .ow { color: #859900 } /* Operator.Word */ + .w { color: @color-right-code } /* Text.Whitespace */ + .mf { color: #2aa198 } /* Literal.Number.Float */ + .mh { color: #2aa198 } /* Literal.Number.Hex */ + .mi { color: #2aa198 } /* Literal.Number.Integer */ + .mo { color: #2aa198 } /* Literal.Number.Oct */ + .sb { color: #586e75 } /* Literal.String.Backtick */ + .sc { color: #2aa198 } /* Literal.String.Char */ + .sd { color: @color-right-code } /* Literal.String.Doc */ + .s2 { color: #2aa198 } /* Literal.String.Double */ + .se { color: #cb4b16 } /* Literal.String.Escape */ + .sh { color: @color-right-code } /* Literal.String.Heredoc */ + .si { color: #2aa198 } /* Literal.String.Interpol */ + .sx { color: #2aa198 } /* Literal.String.Other */ + .sr { color: #dc322f } /* Literal.String.Regex */ + .s1 { color: #2aa198 } /* Literal.String.Single */ + .ss { color: #2aa198 } /* Literal.String.Symbol */ + .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ + .vc { color: #268bd2 } /* Name.Variable.Class */ + .vg { color: #268bd2 } /* Name.Variable.Global */ + .vi { color: #268bd2 } /* Name.Variable.Instance */ + .il { color: #2aa198 } /* Literal.Number.Integer.Long */ + } + } + // separator above H2 + .body > .section > .section { + border-top: 1px solid @color-right; } - // fullwidth elements .section > h1, .section > h2, .section > h3, .section > h4, .section > h5, .section > h6 { - background-color: fadeout(@body-bg, 30%); + max-width: 50%; + } .section > h1, .section > h2, .section > h3, .section > h4, .section > h5, .section > h6, .section > .section { @@ -625,8 +742,8 @@ td.field-body { clear: both; } - .bodywrapper { - position: relative; + .documentwrapper { + overflow: auto; // middle separator &:before { position: absolute; @@ -634,8 +751,8 @@ td.field-body { bottom: 0; left: 50%; content: ""; - width: 0; - border-left: 1px solid @gray-light; + width: 50%; + background: @background-right; } } @@ -643,6 +760,19 @@ td.field-body { // but allow additional line-breaks between "words" to avoid overflow white-space: pre-wrap; } + + .btn-clipboard { + background-color: transparent; + color: @color-right; + border-color: @separator-right; + border-top: none; + border-top-right-radius: 0; + + &.active { + background-color: @gray; + .transition(background-color .5s linear); + } + } } .switcher { @@ -650,7 +780,7 @@ td.field-body { width: auto !important; float: none !important; - position: fixed; + position: absolute; display: -webkit-flex; display: flex; -webkit-justify-content: flex-end; @@ -661,6 +791,10 @@ td.field-body { padding: 0; margin: 0; z-index: 5; + &.affix { + position: fixed; + top: 0; + } li { background-color: #0f131a; @@ -670,10 +804,10 @@ td.field-body { cursor: pointer; &:first-child { border-left-width: 1px; - border-radius: 5px 0 0 5px; + border-radius: 0 0 0 5px; } &:last-child { - border-radius: 0 5px 5px 0; + border-radius: 0 0 5px 0; } &:hover { background-color: #222; diff --git a/doc/api_integration.rst b/doc/api_integration.rst index 4a9835fa743..6ee6871ff9f 100644 --- a/doc/api_integration.rst +++ b/doc/api_integration.rst @@ -19,8 +19,8 @@ easily available over XML-RPC_ and accessible from a variety of languages. into every call. Session would allow db to be stored as well These issues are especially visible in Java, somewhat less so in PHP -Connection and authentication -============================= +Connection +========== .. kinda gross because it duplicates existing bits @@ -61,11 +61,10 @@ Connection and authentication .. code-block:: java final XmlRpcClient client = new XmlRpcClient(); - final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl(); start_config.setServerURL(new URL("https://demo.odoo.com/start")); final Map<String, String> info = (Map<String, String>)client.execute( - start_config, "start", Collections.emptyList()); + start_config, "start", emptyList()); final String url = info.get("host"), db = info.get("database"), @@ -77,7 +76,7 @@ Connection and authentication int uid = (int)client.execute( common_config, "authenticate", Arrays.asList( - db, username, password, Collections.emptyMap())); + db, username, password, emptyMap())); final XmlRpcClient models = new XmlRpcClient() {{ setConfig(new XmlRpcClientConfigImpl() {{ @@ -154,7 +153,7 @@ database: final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl(); start_config.setServerURL(new URL("https://demo.odoo.com/start")); final Map<String, String> info = (Map<String, String>)client.execute( - start_config, "start", Collections.emptyList()); + start_config, "start", emptyList()); final String url = info.get("host"), db = info.get("database"), @@ -183,6 +182,9 @@ database: These examples use the `Apache XML-RPC library <https://ws.apache.org/xmlrpc/>`_ + The examples do not include imports as these imports couldn't be + pasted in the code. + Logging in ---------- @@ -217,8 +219,9 @@ the login. .. code-block:: java final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl(); - common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url))); - client.execute(common_config, "version", Collections.emptyList()); + common_config.setServerURL( + new URL(String.format("%s/xmlrpc/2/common", url))); + client.execute(common_config, "version", emptyList()); .. code-block:: json @@ -246,8 +249,8 @@ the login. .. code-block:: java int uid = (int)client.execute( - common_config, "authenticate", Arrays.asList( - db, username, password, Collections.emptyMap())); + common_config, "authenticate", asList( + db, username, password, emptyMap())); Calling methods =============== @@ -302,10 +305,10 @@ rather than true/error): setServerURL(new URL(String.format("%s/xmlrpc/2/object", url))); }}); }}; - models.execute("execute_kw", Arrays.asList( + models.execute("execute_kw", asList( db, uid, password, "res.partner", "check_access_rights", - Arrays.asList("read"), + asList("read"), new HashMap() {{ put("raise_exception", false); }} )); @@ -341,20 +344,19 @@ companies for instance: .. code-block:: php - $domain = array(array('is_company', '=', true), - array('customer', '=', true)); $models->execute_kw($db, $uid, $password, - 'res.partner', 'search', array($domain)); + 'res.partner', 'search', array( + array(array('is_company', '=', true), + array('customer', '=', true)))); .. code-block:: java - final List domain = Arrays.asList( - Arrays.asList("is_company", "=", true), - Arrays.asList("customer", "=", true)); - Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList( + asList((Object[])models.execute("execute_kw", asList( db, uid, password, "res.partner", "search", - Arrays.asList(domain) + asList(asList( + asList("is_company", "=", true), + asList("customer", "=", true))) ))); .. code-block:: json @@ -388,15 +390,18 @@ available to only retrieve a subset of all matched records. $models->execute_kw($db, $uid, $password, 'res.partner', 'search', - array($domain), + array(array(array('is_company', '=', true), + array('customer', '=', true))), array('offset'=>10, 'limit'=>5)); .. code-block:: java - Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList( + asList((Object[])models.execute("execute_kw", asList( db, uid, password, "res.partner", "search", - Arrays.asList(domain), + asList(asList( + asList("is_company", "=", true), + asList("customer", "=", true))), new HashMap() {{ put("offset", 10); put("limit", 5); }} ))); @@ -431,14 +436,17 @@ only the number of records matching the query. It takes the same $models->execute_kw($db, $uid, $password, 'res.partner', 'search_count', - array($domain)); + array(array(array('is_company', '=', true), + array('customer', '=', true)))); .. code-block:: java - (Integer)models.execute("execute_kw", Arrays.asList( + (Integer)models.execute("execute_kw", asList( db, uid, password, "res.partner", "search_count", - Arrays.asList(domain) + asList(asList( + asList("is_company", "=", true), + asList("customer", "=", true))) )); .. code-block:: json @@ -488,7 +496,8 @@ which tends to be a huge amount. $ids = $models->execute_kw($db, $uid, $password, 'res.partner', 'search', - array($domain), + array(array(array('is_company', '=', true), + array('customer', '=', true))), array('limit'=>1)); $records = $models->execute_kw($db, $uid, $password, 'res.partner', 'read', array($ids)); @@ -497,17 +506,19 @@ which tends to be a huge amount. .. code-block:: java - final List ids = Arrays.asList((Object[])models.execute( - "execute_kw", Arrays.asList( + final List ids = asList((Object[])models.execute( + "execute_kw", asList( db, uid, password, "res.partner", "search", - Arrays.asList(domain), + asList(asList( + asList("is_company", "=", true), + asList("customer", "=", true))), new HashMap() {{ put("limit", 1); }}))); final Map record = (Map)((Object[])models.execute( - "execute_kw", Arrays.asList( + "execute_kw", asList( db, uid, password, "res.partner", "read", - Arrays.asList(ids) + asList(ids) ) ))[0]; // count the number of fields fetched by default @@ -542,12 +553,12 @@ Conversedly, picking only three fields deemed interesting. .. code-block:: java - Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList( + asList((Object[])models.execute("execute_kw", asList( db, uid, password, "res.partner", "read", - Arrays.asList(ids), + asList(ids), new HashMap() {{ - put("fields", Arrays.asList("name", "country_id", "comment")); + put("fields", asList("name", "country_id", "comment")); }} ))); @@ -574,53 +585,32 @@ updating a record): .. code-block:: python - fields = models.execute_kw(db, uid, password, 'res.partner', 'fields_get', []) - # filter keys of field attributes for display - {field: { - k: v for k, v in attributes.iteritems() - if k in ['string', 'help', 'type'] - } - for field, attributes in fields.iteritems()} + models.execute_kw( + db, uid, password, 'res.partner', 'fields_get', + [], {'attributes': ['string', 'help', 'type']}) .. code-block:: ruby - fields = models.execute_kw(db, uid, password, 'res.partner', 'fields_get', []) - # filter keys of field attributes for display - fields.each {|k, v| - fields[k] = v.keep_if {|kk, vv| %w(string help type).include? kk} - } + models.execute_kw( + db, uid, password, 'res.partner', 'fields_get', + [], {attributes: %w(string help type)}) .. code-block:: php - $fields_full = $models->execute_kw($db, $uid, $password, - 'res.partner', 'fields_get', array()); - // filter keys of field attributes for display - $allowed = array_flip(array('string', 'help', 'type')); - $fields = array(); - foreach($fields_full as $field => $attributes) { - $fields[$field] = array_intersect_key($attributes, $allowed); - } + $models->execute_kw($db, $uid, $password, + 'res.partner', 'fields_get', + array(), array('attributes' => array('string', 'help', 'type'))); .. code-block:: java - final Map<String, Map<String, Object>> fields = - (Map<String, Map<String, Object>>)models.execute("execute_kw", Arrays.asList( - db, uid, password, - "res.partner", "fields_get", - Collections.emptyList())); - // filter keys of field attributes for display - final List<String> allowed = Arrays.asList("string", "help", "type"); - new HashMap<String, Map<String, Object>>() {{ - for(Entry<String, Map<String, Object>> item: fields.entrySet()) { - put(item.getKey(), new HashMap<String, Object>() {{ - for(Entry<String, Object> it: item.getValue().entrySet()) { - if (allowed.contains(it.getKey())) { - put(it.getKey(), it.getValue()); - } - } - }}); - } - }}; + (Map<String, Map<String, Object>>)models.execute("execute_kw", asList( + db, uid, password, + "res.partner", "fields_get", + emptyList(), + new HashMap() {{ + put("attributes", asList("string", "help", "type")); + }} + )); .. code-block:: json @@ -668,10 +658,11 @@ Because that is a very common task, Odoo provides a :meth:`~openerp.models.Model.search_read` shortcut which as its name notes is equivalent to a :meth:`~openerp.models.Model.search` followed by a :meth:`~openerp.models.Model.read`, but avoids having to perform two requests -and keep ids around. Its arguments are similar to -:meth:`~openerp.models.Model.search`'s, but it can also take a list of -``fields`` (like :meth:`~openerp.models.Model.read`, if that list is not -provided it'll fetch all fields of matched records): +and keep ids around. + +Its arguments are similar to :meth:`~openerp.models.Model.search`'s, but it +can also take a list of ``fields`` (like :meth:`~openerp.models.Model.read`, +if that list is not provided it'll fetch all fields of matched records): .. rst-class:: switchable @@ -693,17 +684,20 @@ provided it'll fetch all fields of matched records): $models->execute_kw($db, $uid, $password, 'res.partner', 'search_read', - array($domain), + array(array(array('is_company', '=', true), + array('customer', '=', true))), array('fields'=>array('name', 'country_id', 'comment'), 'limit'=>5)); .. code-block:: java - Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList( + asList((Object[])models.execute("execute_kw", asList( db, uid, password, "res.partner", "search_read", - Arrays.asList(domain), + asList(asList( + asList("is_company", "=", true), + asList("customer", "=", true))), new HashMap() {{ - put("fields", Arrays.asList("name", "country_id", "comment")); + put("fields", asList("name", "country_id", "comment")); put("limit", 5); }} ))); @@ -747,6 +741,13 @@ provided it'll fetch all fields of matched records): Create records -------------- +Records of a model are created using :meth:`~openerp.models.Model.create`. The +method will create a single record and return its database identifier. + +:meth:`~openerp.models.Model.create` takes a mapping of fields to values, used +to initialize the record. For any field which has a default value and is not +set through the mapping argument, the default value will be used. + .. rst-class:: switchable .. code-block:: python @@ -769,19 +770,40 @@ Create records .. code-block:: java - final Integer id = (Integer)models.execute("execute_kw", Arrays.asList( + final Integer id = (Integer)models.execute("execute_kw", asList( db, uid, password, "res.partner", "create", - Arrays.asList(new HashMap() {{ put("name", "New Partner"); }}) + asList(new HashMap() {{ put("name", "New Partner"); }}) )); .. code-block:: json 78 +.. warning:: + + while most value types are what would be expected (integer for + :class:`~openerp.fields.Integer`, string for :class:`~openerp.fields.Char` + or :class:`~openerp.fields.Text`), + + * :class:`~openerp.fields.Date`, :class:`~openerp.fields.Datetime` and + :class:`~openerp.fields.Binary` fields use string values + * :class:`~openerp.fields.One2many` and :class:`~openerp.fields.Many2many` + use a special command protocol detailed in :meth:`the documentation to + the write method <openerp.models.Model.write>`. + Update records -------------- +Reccords can be updated using :meth:`~openerp.models.Model.write`, it takes +a list of records to update and a mapping of updated fields to values similar +to :meth:`~openerp.models.Model.create`. + +Multiple records can be updated simultanously, but they will all get the same +values for the fields being set. It is not currently possible to perform +"computed" updates (where the value being set depends on an existing value of +a record). + .. rst-class:: switchable .. code-block:: python @@ -810,19 +832,19 @@ Update records .. code-block:: java - models.execute("execute_kw", Arrays.asList( + models.execute("execute_kw", asList( db, uid, password, "res.partner", "write", - Arrays.asList( - Arrays.asList(id), + asList( + asList(id), new HashMap() {{ put("name", "Newer Partner"); }} ) )); // get record name after having changed it - Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList( + asList((Object[])models.execute("execute_kw", asList( db, uid, password, "res.partner", "name_get", - Arrays.asList(Arrays.asList(id)) + asList(asList(id)) ))); .. code-block:: json @@ -832,6 +854,9 @@ Update records Delete records -------------- +Records can be deleted in bulk by providing the ids of all records to remove +to :meth:`~openerp.models.Model.unlink`. + .. rst-class:: switchable .. code-block:: python @@ -860,20 +885,561 @@ Delete records .. code-block:: java - models.execute("execute_kw", Arrays.asList( + models.execute("execute_kw", asList( db, uid, password, "res.partner", "unlink", - Arrays.asList(Arrays.asList(id)))); + asList(asList(id)))); // check if the deleted record is still in the database - Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList( + asList((Object[])models.execute("execute_kw", asList( db, uid, password, "res.partner", "search", - Arrays.asList(Arrays.asList(Arrays.asList("id", "=", 78))) + asList(asList(asList("id", "=", 78))) ))); .. code-block:: json [] +Inspection and introspection +---------------------------- + +.. todo:: ``get_external_id`` is kinda crap and may not return an id: it just + gets a random existing xid but won't generate one if there is no + xid currently associated with the record. And operating with xids + isn't exactly fun in RPC. + +While we previously used :meth:`~openerp.models.Model.fields_get` to query a +model's and have been using an arbitrary model from the start, Odoo stores +most model metadata inside a few meta-models which allow both querying the +system and altering models and fields (with some limitations) on the fly over +XML-RPC. + +.. _reference/webservice/inspection/models: + +``ir.model`` +'''''''''''' + +Provides informations about Odoo models themselves via its various fields + +``name`` + a human-readable description of the model +``model`` + the name of each model in the system +``state`` + whether the model was generated in Python code (``base``) or by creating + an ``ir.model`` record (``manual``) +``field_id`` + list of the model's fields through a :class:`~openerp.fields.One2many` to + :ref:`reference/webservice/inspection/fields` +``view_ids`` + :class:`~openerp.fields.One2many` to the :ref:`reference/views` defined + for the model +``access_ids`` + :class:`~openerp.fields.One2many` relation to the + :ref:`reference/security/acl` set on the model + +``ir.model`` can be used to + +* query the system for installed models (as a precondition to operations + on the model or to explore the system's content) +* get information about a specific model (generally by listing the fields + associated with it) +* create new models dynamically over RPC + +.. warning:: + + * "custom" model names must start with ``x_`` + * the ``state`` must be provided and ``manual``, otherwise the model will + not be loaded + * it is not possible to add new *methods* to a custom model, only fields + +.. rst-class:: force-right + + a custom model will initially contain only the "built-in" fields available + on all models: + +.. rst-class:: switchable + + .. code-block:: python + + models.execute_kw(db, uid, password, 'ir.model', 'create', [{ + 'name': "Custom Model", + 'model': "x_custom_model", + 'state': 'manual', + }]) + models.execute_kw( + db, uid, password, 'x_custom_model', 'fields_get', + [], {'attributes': ['string', 'help', 'type']}) + + .. code-block:: php + + $models->execute_kw( + $db, $uid, $password, + 'ir.model', 'create', array(array( + 'name' => "Custom Model", + 'model' => 'x_custom_model', + 'state' => 'manual' + )) + ); + $models->execute_kw( + $db, $uid, $password, + 'x_custom_model', 'fields_get', + array(), + array('attributes' => array('string', 'help', 'type')) + ); + + .. code-block:: ruby + + models.execute_kw( + db, uid, password, + 'ir.model', 'create', [{ + name: "Custom Model", + model: 'x_custom_model', + state: 'manual' + }]) + fields = models.execute_kw( + db, uid, password, 'x_custom_model', 'fields_get', + [], {attributes: %w(string help type)}) + + .. code-block:: java + + models.execute( + "execute_kw", asList( + db, uid, password, + "ir.model", "create", + asList(new HashMap<String, Object>() {{ + put("name", "Custom Model"); + put("model", "x_custom_model"); + put("state", "manual"); + }}) + )); + final Object fields = models.execute( + "execute_kw", asList( + db, uid, password, + "x_custom_model", "fields_get", + emptyList(), + new HashMap<String, Object> () {{ + put("attributes", asList( + "string", + "help", + "type")); + }} + )); + +.. code-block:: json + + { + "create_uid": { + "type": "many2one", + "string": "Created by" + }, + "create_date": { + "type": "datetime", + "string": "Created on" + }, + "__last_update": { + "type": "datetime", + "string": "Last Modified on" + }, + "write_uid": { + "type": "many2one", + "string": "Last Updated by" + }, + "write_date": { + "type": "datetime", + "string": "Last Updated on" + }, + "display_name": { + "type": "char", + "string": "Display Name" + }, + "id": { + "type": "integer", + "string": "Id" + } + } + +.. _reference/webservice/inspection/fields: + +``ir.model.fields`` +''''''''''''''''''' + +Provides informations about the fields of Odoo models and allows adding +custom fields without using Python code + +``model_id`` + :class:`~openerp.fields.Many2one` to + :ref:`reference/webservice/inspection/models` to which the field belongs +``name`` + the field's technical name (used in ``read`` or ``write``) +``field_description`` + the field's user-readable label (e.g. ``string`` in ``fields_get``) +``ttype`` + the :ref:`type <reference/orm/fields>` of field to create +``state`` + whether the field was created via Python code (``base``) or via + ``ir.model.fields`` (``manual``) +``required``, ``readonly``, ``translate`` + enables the corresponding flag on the field +``groups`` + :ref:`field-level access control <reference/security/fields>`, a + :class:`~openerp.fields.Many2many` to ``res.groups`` +``selection``, ``size``, ``on_delete``, ``relation``, ``relation_field``, ``domain`` + type-specific properties and customizations, see :ref:`the fields + documentation <reference/orm/fields>` for details + +Like custom models, only new fields created with ``state="manual"`` are +activated as actual fields on the model. + +.. warning:: computed fields can not be added via ``ir.model.fields``, some + field meta-information (defaults, onchange) can not be set either + +.. todo:: maybe new-API fields could store constant ``default`` in a new + column, maybe JSON-encoded? + +.. rst-class:: switchable + + .. code-block:: python + + id = models.execute_kw(db, uid, password, 'ir.model', 'create', [{ + 'name': "Custom Model", + 'model': "x_custom", + 'state': 'manual', + }]) + models.execute_kw( + db, uid, password, + 'ir.model.fields', 'create', [{ + 'model_id': id, + 'name': 'x_name', + 'ttype': 'char', + 'state': 'manual', + 'required': True, + }]) + record_id = models.execute_kw( + db, uid, password, + 'x_custom', 'create', [{ + 'x_name': "test record", + }]) + models.execute_kw(db, uid, password, 'x_custom', 'read', [[record_id]]) + + .. code-block:: php + + $id = $models->execute_kw( + $db, $uid, $password, + 'ir.model', 'create', array(array( + 'name' => "Custom Model", + 'model' => 'x_custom', + 'state' => 'manual' + )) + ); + $models->execute_kw( + $db, $uid, $password, + 'ir.model.fields', 'create', array(array( + 'model_id' => $id, + 'name' => 'x_name', + 'ttype' => 'char', + 'state' => 'manual', + 'required' => true + )) + ); + $record_id = $models->execute_kw( + $db, $uid, $password, + 'x_custom', 'create', array(array( + 'x_name' => "test record" + )) + ); + $models->execute_kw( + $db, $uid, $password, + 'x_custom', 'read', + array(array($record_id))); + + .. code-block:: ruby + + id = models.execute_kw( + db, uid, password, + 'ir.model', 'create', [{ + name: "Custom Model", + model: "x_custom", + state: 'manual' + }]) + models.execute_kw( + db, uid, password, + 'ir.model.fields', 'create', [{ + model_id: id, + name: "x_name", + ttype: "char", + state: "manual", + required: true + }]) + record_id = models.execute_kw( + db, uid, password, + 'x_custom', 'create', [{ + x_name: "test record" + }]) + models.execute_kw( + db, uid, password, + 'x_custom', 'read', [[record_id]]) + + .. code-block:: java + + final Integer id = (Integer)models.execute( + "execute_kw", asList( + db, uid, password, + "ir.model", "create", + asList(new HashMap<String, Object>() {{ + put("name", "Custom Model"); + put("model", "x_custom"); + put("state", "manual"); + }}) + )); + models.execute( + "execute_kw", asList( + db, uid, password, + "ir.model.fields", "create", + asList(new HashMap<String, Object>() {{ + put("model_id", id); + put("name", "x_name"); + put("ttype", "char"); + put("state", "manual"); + put("required", true); + }}) + )); + final Integer record_id = (Integer)models.execute( + "execute_kw", asList( + db, uid, password, + "x_custom", "create", + asList(new HashMap<String, Object>() {{ + put("x_name", "test record"); + }}) + )); + + client.execute( + "execute_kw", asList( + db, uid, password, + "x_custom", "read", + asList(asList(record_id)) + )); + +.. code-block:: json + + [ + { + "create_uid": [1, "Administrator"], + "x_name": "test record", + "__last_update": "2014-11-12 16:32:13", + "write_uid": [1, "Administrator"], + "write_date": "2014-11-12 16:32:13", + "create_date": "2014-11-12 16:32:13", + "id": 1, + "display_name": "test record" + } + ] + +Workflow manipulations +---------------------- + +:ref:`reference/workflows` can be moved along by sending them *signals*. +Instead of using the top-level ``execute_kw``, signals are sent using +``exec_workflow``. + +Signals are sent to a specific record, and possibly trigger a transition on +the workflow instance associated with the record. + +.. warning:: requires that the ``account`` module be installed + :class: force-right + +.. rst-class:: switchable + + .. code-block:: python + + client = models.execute_kw( + db, uid, password, + 'res.partner', 'search_read', + [[('customer', '=', True)]], + {'limit': 1, 'fields': [ + 'property_account_receivable', + 'property_payment_term', + 'property_account_position'] + })[0] + invoice_id = models.execute_kw( + db, uid, password, + 'account.invoice', 'create', [{ + 'partner_id': client['id'], + 'account_id': client['property_account_receivable'][0], + 'invoice_line': [(0, False, {'name': "AAA"})] + }]) + + models.exec_workflow( + db, uid, password, 'account.invoice', 'invoice_open', invoice_id) + + .. code-block:: php + + $client = $models->execute_kw( + $db, $uid, $password, + 'res.partner', 'search_read', + array(array(array('customer', '=', true))), + array( + 'limit' => 1, + 'fields' => array( + 'property_account_receivable', + 'property_payment_term', + 'property_account_position' + )))[0]; + $invoice_id = $models->execute_kw( + $db, $uid, $password, + 'account.invoice', 'create', array(array( + 'partner_id' => $client['id'], + 'account_id' => $client['property_account_receivable'][0], + 'invoice_line' => array(array(0, false, array('name' => "AAA"))) + ))); + + $models->exec_workflow( + $db, $uid, $password, + 'account.invoice', 'invoice_open', + $invoice_id); + + .. code-block:: ruby + + client = models.execute_kw( + db, uid, password, + 'res.partner', 'search_read', + [[['customer', '=', true]]], + {limit: 1, fields: %w(property_account_receivable property_payment_term property_account_position)} + )[0] + invoice_id = models.execute_kw( + db, uid, password, + 'account.invoice', 'create', [{ + partner_id: client['id'], + account_id: client['property_account_receivable'][0], + invoice_line: [[0, false, {name: "AAA"}]] + }]) + + models.exec_workflow( + db, uid, password, + 'account.invoice', 'invoice_open', invoice_id) + + .. code-block:: java + + final Map<String, Object> c = (Map<String, Object>) + ((Object[])models.execute("execute_kw", asList( + db, uid, password, + "res.partner", "search_read", + asList( + asList( + asList("customer", "=", true))), + new HashMap<String, Object>() {{ + put("limit", 1); + put("fields", asList( + "property_account_receivable", + "property_payment_term", + "property_account_position" + )); + }} + )))[0]; + final Integer invoice_id = (Integer)models.execute( + "execute_kw", asList( + db, uid, password, + "account.invoice", "create", + asList(new HashMap<String, Object>() {{ + put("partner_id", c.get("id")); + put("account_id", ((Object[])c.get("property_account_receivable"))[0]); + put("invoice_line", asList( + asList(0, false, new HashMap<String, Object>() {{ + put("name", "AAA"); + }}) + )); + }}) + )); + + models.execute( + "exec_workflow", asList( + db, uid, password, + "account.invoice", "invoice_open", invoice_id)); + +Report printing +--------------- + +Available reports can be listed by searching the ``ir.actions.report.xml`` +model, fields of interest being + +``model`` + the model on which the report applies, can be used to look for available + reports on a specific model +``name`` + human-readable report name +``report_name`` + the technical name of the report, used to print it + +Reports can be printed over RPC with the following information: + +* the name of the report (``report_name``) +* the ids of the records to include in the report + +.. rst-class:: switchable + + .. code-block:: python + + invoice_ids = models.execute_kw( + db, uid, password, 'account.invoice', 'search', + [[('type', '=', 'out_invoice'), ('state', '=', 'open')]]) + report = xmlrpclib.ServerProxy('{}/xmlrpc/2/report'.format(url)) + result = report.render_report( + db, uid, password, 'account.report_invoice', invoice_ids) + report_data = result['result'].decode('base64') + + .. code-block:: php + + $invoice_ids = $models->execute_kw( + $db, $uid, $password, + 'account.invoice', 'search', + array(array(array('type', '=', 'out_invoice'), + array('state', '=', 'open')))); + $report = ripcord::client("$url/xmlrpc/2/report"); + $result = $report->render_report( + $db, $uid, $password, + 'account.report_invoice', $invoice_ids); + $report_data = base64_decode($result['result']); + + .. code-block:: ruby + + require 'base64' + invoice_ids = models.execute_kw( + db, uid, password, + 'account.invoice', 'search', + [[['type', '=', 'out_invoice'], ['state', '=', 'open']]]) + report = XMLRPC::Client.new2("#{url}/xmlrpc/2/report").proxy + result = report.render_report( + db, uid, password, + 'account.report_invoice', invoice_ids) + report_data = Base64.decode64(result['result']) + + .. code-block:: java + + final Object[] invoice_ids = (Object[])models.execute( + "execute_kw", asList( + db, uid, password, + "account.invoice", "search", + asList(asList( + asList("type", "=", "out_invoice"), + asList("state", "=", "open"))) + )); + final XmlRpcClientConfigImpl report_config = new XmlRpcClientConfigImpl(); + report_config.setServerURL( + new URL(String.format("%s/xmlrpc/2/report", url))); + final Map<String, Object> result = (Map<String, Object>)client.execute( + report_config, "render_report", asList( + db, uid, password, + "account.report_invoice", + invoice_ids)); + final byte[] report_data = DatatypeConverter.parseBase64Binary( + (String)result.get("result")); + +.. note:: + :class: force-right + + the report is sent as PDF binary data encoded in base64_, it must be + decoded and may need to be saved to disk before use + .. _PostgreSQL: http://www.postgresql.org .. _XML-RPC: http://en.wikipedia.org/wiki/XML-RPC +.. _base64: http://en.wikipedia.org/wiki/Base64 diff --git a/doc/conf.py b/doc/conf.py index 086e3ec4d95..b4751fb5db9 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -46,7 +46,7 @@ master_doc = 'index' # General information about the project. project = u'odoo' -copyright = u'OpenERP S.A.' +copyright = u'Odoo S.A.' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/doc/howtos/backend.rst b/doc/howtos/backend.rst index c9715893614..b72540cf2d3 100644 --- a/doc/howtos/backend.rst +++ b/doc/howtos/backend.rst @@ -1,8 +1,8 @@ .. queue:: backend/series -======= -Backend -======= +================= +Building a Module +================= Start/Stop the Odoo server ========================== @@ -1725,12 +1725,12 @@ server with the library ``xmlrpclib``:: print "Logged in as %s (uid:%d)" % (USER,uid) call = functools.partial( - xmlcprlib.ServerProxy(ROOT + 'object').execute, + xmlrpclib.ServerProxy(ROOT + 'object').execute, DB, uid, PASS) # 2. Read the sessions sessions = call('openacademy.session','search_read', [], ['name','seats']) - for session in sessions : + for session in sessions: print "Session %s (%s seats)" % (session['name'], session['seats']) # 3.create a new session session_id = call('openacademy.session', 'create', { diff --git a/doc/howtos/themes.rst b/doc/howtos/themes.rst index 9044c47587e..35cb43989aa 100644 --- a/doc/howtos/themes.rst +++ b/doc/howtos/themes.rst @@ -1,7 +1,7 @@ .. highlight:: xml =============== -Creating themes +Building Themes =============== Basic set up @@ -56,7 +56,7 @@ Refresh the page and feel the hit. Editing content on a page ''''''''''''''''''''''''' -You can now add you content! You should always use the Bootstrap structure as +You can now add your content! You should always use the Bootstrap structure as below:: <template id="website.hello" name="Homepage" page="True"> diff --git a/doc/howtos/web.rst b/doc/howtos/web.rst index 98cec4a5d3f..384297dc6a9 100644 --- a/doc/howtos/web.rst +++ b/doc/howtos/web.rst @@ -1,6 +1,6 @@ -========== -Web Client -========== +============================= +Building Interface Extensions +============================= .. highlight:: javascript diff --git a/doc/howtos/website.rst b/doc/howtos/website.rst index 62c28f53de9..2187956abaf 100644 --- a/doc/howtos/website.rst +++ b/doc/howtos/website.rst @@ -1,7 +1,7 @@ .. queue:: website/series ================== -Building a website +Building a Website ================== .. warning:: @@ -457,7 +457,7 @@ though they may have to be looked for. * to extend a model in-place, it's :attr:`inherited <openerp.models.Model._inherit>` without giving it a new :attr:`~openerp.models.Model._name` - * ``product.template`` already uses the discussions system, so we don't + * ``product.template`` already uses the discussions system, so we can remove it from our extension model * we're creating our courses as *published* by default so they can be seen without having to log in diff --git a/doc/index.rst b/doc/index.rst index 56f58b6aa0d..45394cdfd58 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,9 +1,7 @@ ============================ -odoo developer documentation +Odoo Technical Documentation ============================ -Welcome to the Odoo developer documentation. - .. todo:: what's the documentation's license? .. rst-class:: index-tree diff --git a/doc/reference.rst b/doc/reference.rst index 8a09e86cbf7..0d17f5df7c7 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -18,6 +18,7 @@ Reference reference/qweb reference/javascript + reference/translations reference/reports reference/workflows reference/cdn diff --git a/doc/reference/cmdline.rst b/doc/reference/cmdline.rst index 1ec7d96ae9d..e0713410981 100644 --- a/doc/reference/cmdline.rst +++ b/doc/reference/cmdline.rst @@ -54,6 +54,12 @@ Running the server runs tests after installing modules +.. option:: --debug + + when an unexpected error is raised (not a warning or an access error), + automatically starts :mod:`python:pdb` before logging and returning the + error + .. _reference/cmdline/scaffold: Scaffolding diff --git a/doc/reference/security.rst b/doc/reference/security.rst index 72742f0639c..05ca9079fb7 100644 --- a/doc/reference/security.rst +++ b/doc/reference/security.rst @@ -71,6 +71,8 @@ This means the first *group rule* restricts access, but any further although access rules do +.. _reference/security/fields: + Field Access ============ diff --git a/doc/reference/translations.rst b/doc/reference/translations.rst new file mode 100644 index 00000000000..45616189c0d --- /dev/null +++ b/doc/reference/translations.rst @@ -0,0 +1,96 @@ +.. _reference/translations: + +=================== +Translating Modules +=================== + +Exporting translatable term +=========================== + +A number of terms in your modules are "implicitly translatable" as a result, +even if you haven't done any specific work towards translation you can export +your module's translatable terms and may find content to work with. + +.. todo:: needs technical features + +Translations export is done via the administration interface by logging into +the backend interface and opening :menuselection:`Settings --> Translations +--> Import / Export --> Export Translations` + +* leave the language to the default (new language/empty template) +* select the `PO File`_ format +* select your module +* click :guilabel:`Export` and download the file + +.. image:: translations/po-export.* + :align: center + :width: 75% + +This gives you a file called :file:`{yourmodule}.pot` which should be moved to +the :file:`{yourmodule}/i18n/` directory. The file is a *PO Template* which +simply lists translatable strings and from which actual translations (PO files) +can be created. PO files can be created using msginit_, with a dedicated +translation tool like POEdit_ or by simply copying the template to a new file +called :file:`{language}.po`. Translation files should be put in +:file:`{yourmodule}/i18n/`, next to :file:`{yourmodule}.pot`, and will be +automatically loaded by Odoo when the corresponding language is installed (via +:menuselection:`Settings --> Translations --> Load a Translation`) + +.. note:: translations for all loaded languages are also installed or updated + when installing or updating a module + +Implicit exports +================ + +Odoo automatically exports translatable strings from "data"-type content: + +* in non-QWeb views, all text nodes are exported as well as the content of + the ``string``, ``help``, ``sum``, ``confirm`` and ``placeholder`` + attributes +* QWeb templates (both server-side and client-side), all text nodes are + exported except inside ``t-translation="off"`` blocks, the content of the + ``title``, ``alt``, ``label`` and ``placeholder`` attributes are also + exported +* for :class:`~openerp.fields.Field`, unless their model is marked with + ``_translate = False``: + + * their ``string`` and ``help`` attributes are exported + * if ``selection`` is present and a list (or tuple), it's exported + * if their ``translate`` attribute is set to ``True``, all of their existing + values (across all records) are exported +* help/error messages of :attr:`~openerp.models.Model._constraints` and + :attr:`~openerp.models.Model._sql_constraints` are exported + +Explicit exports +================ + +When it comes to more "imperative" situations in Python code or Javascript +code, Odoo is not able to automatically export translatable terms and they +must be marked explicitly for export. This is done by wrapping a literal +string in a function call. + +In Python, the wrapping function is :func:`openerp._`:: + + title = _("Bank Accounts") + +In JavaScript, the wrapping function is generally :js:func:`openerp.web._t`: + +.. code-block:: javascript + + title = _t("Bank Accounts") + +.. warning:: + + Only literal strings can be marked for exports, not expressions and not + variables. For situations where strings are formatted, this means the + format string must be marked, not the formatted string:: + + # bad, the extract may work but it will not correctly translate the text + _("Scheduled meeting with %s" % invitee.name) + + # good + _("Scheduled meeting with %s") % invitee.name + +.. _PO File: http://en.wikipedia.org/wiki/Gettext#Translating +.. _msginit: http://www.gnu.org/software/gettext/manual/gettext.html#Creating +.. _POEdit: http://poedit.net/ diff --git a/doc/reference/translations/po-export.png b/doc/reference/translations/po-export.png Binary files differnew file mode 100644 index 00000000000..82f8d5ad011 --- /dev/null +++ b/doc/reference/translations/po-export.png diff --git a/doc/reference/views.rst b/doc/reference/views.rst index 9eaab61b324..3f5b2ea2043 100644 --- a/doc/reference/views.rst +++ b/doc/reference/views.rst @@ -85,8 +85,8 @@ There are three types of inheritance specs: * any other element, the first element with the same name and identical attributes (ignoring ``position``) is matched -The inheritance spec can an optional ``position`` attribute specifhing how -the matched node should be altered: +The inheritance spec may have an optional ``position`` attribute specifying +how the matched node should be altered: ``inside`` (default) the content of the inheritance spec is appended to the matched node @@ -98,7 +98,7 @@ the matched node should be altered: ``before`` the content of the inheritance spec is added to the matched node's parent, before the matched node -``attribute`` +``attributes`` the content of the inheritance spec should be ``attribute`` elements with a ``name`` attribute and an optional body: diff --git a/doc/reference/workflows.rst b/doc/reference/workflows.rst index 210213e5f55..a6890f05ab6 100644 --- a/doc/reference/workflows.rst +++ b/doc/reference/workflows.rst @@ -284,6 +284,8 @@ defined (in addition to the Odoo ``safe_eval`` environment): - all the model column names, and - all the browse record's attributes. +.. _reference/workflows/signals: + Signals ''''''' diff --git a/doc/tutorials.rst b/doc/tutorials.rst index a9df0517bc7..6ad06861ef1 100644 --- a/doc/tutorials.rst +++ b/doc/tutorials.rst @@ -7,5 +7,5 @@ Tutorials howtos/website howtos/backend - howtos/web howtos/themes + howtos/web |
