Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

View file

@ -0,0 +1,150 @@
============
Graphql Base
============
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:7cce5e4816ad8b14f69ef2998f98b1fa1ae2ff494da1ca0c1a247b4ababdd463
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
:target: https://odoo-community.org/page/development-status
:alt: Production/Stable
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Frest--framework-lightgray.png?logo=github
:target: https://github.com/OCA/rest-framework/tree/16.0/graphql_base
:alt: OCA/rest-framework
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/rest-framework-16-0/rest-framework-16-0-graphql_base
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/rest-framework&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This modules enables the creation of `GraphQL <https://graphql.org/>`__ endpoints.
In itself, it does nothing and must be used by a developer to
create the GraphQL schema and resolvers using
`graphene <https://graphene-python.org/>`__,
and expose them through a controller.
An example is available in the ``graphql_demo`` module.
**Table of contents**
.. contents::
:local:
Usage
=====
To use this module, you need to
- create your graphene schema
- create your controller to expose your GraphQL endpoint,
and optionally a GraphiQL IDE.
This module does not attempt to expose the whole Odoo object model.
This could be the purpose of another module based on this one.
We believe however that it is preferable to expose a specific well tested
endpoint for each customer, so as to reduce coupling by knowing precisely
what is exposed and needs to be tested when upgrading Odoo.
To start working with this module, we recommend the following approach:
- Learn `GraphQL basics <https://graphql.org/learn/>`__
- Learn `graphene <https://graphene-python.org/>`__, the python library
used to create GraphQL schemas and resolvers.
- Examine the ``graphql_demo`` module in this repo, copy it,
adapt the controller to suit your needs (routes, authentication methods).
- Start building your own schema and resolver.
Building your schema
~~~~~~~~~~~~~~~~~~~~
The schema can be built using native graphene types.
An ``odoo.addons.graphql_base.types.OdooObjectType``
is provided as a convenience. It is a graphene ``ObjectType`` with a
default attribute resolver which:
- converts False to None (except for Boolean types), to avoid Odoo's weird
``False`` strings being rendered as json ``"false"``;
- adds the user timezone to Datetime fields;
- raises an error if an attribute is absent to avoid field name typing errors.
Creating GraphQL controllers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The module provides an ``odoo.addons.graphql_base.GraphQLControllerMixin``
class to help you build GraphQL controllers providing GraphiQL and/or GraphQL
endpoints.
.. code-block:: python
from odoo import http
from odoo.addons.graphql_base import GraphQLControllerMixin
from ..schema import schema
class GraphQLController(http.Controller, GraphQLControllerMixin):
# The GraphiQL route, providing an IDE for developers
@http.route("/graphiql/demo", auth="user")
def graphiql(self, **kwargs):
return self._handle_graphiql_request(schema)
# The graphql route, for applications.
# Note csrf=False: you may want to apply extra security
# (such as origin restrictions) to this route.
@http.route("/graphql/demo", auth="user", csrf=False)
def graphql(self, **kwargs):
return self._handle_graphql_request(schema)
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/rest-framework/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/rest-framework/issues/new?body=module:%20graphql_base%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* ACSONE SA/NV
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
.. |maintainer-sbidoul| image:: https://github.com/sbidoul.png?size=40px
:target: https://github.com/sbidoul
:alt: sbidoul
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-sbidoul|
This module is part of the `OCA/rest-framework <https://github.com/OCA/rest-framework/tree/16.0/graphql_base>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View file

@ -0,0 +1,5 @@
# Copyright 2018 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from .controllers import GraphQLControllerMixin
from .types import OdooObjectType

View file

@ -0,0 +1,18 @@
# Copyright 2018 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
{
"name": "Graphql Base",
"summary": """
Base GraphQL/GraphiQL controller""",
"version": "16.0.1.0.1",
"license": "LGPL-3",
"author": "ACSONE SA/NV,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/rest-framework",
"depends": ["base"],
"data": ["views/graphiql.xml"],
"external_dependencies": {"python": ["graphene", "graphql_server"]},
"development_status": "Production/Stable",
"maintainers": ["sbidoul"],
"installable": True,
}

View file

@ -0,0 +1 @@
from .main import GraphQLControllerMixin

View file

@ -0,0 +1,85 @@
# Copyright 2018 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from functools import partial
from graphql_server import (
HttpQueryError,
encode_execution_results,
format_error_default,
json_encode,
load_json_body,
run_http_query,
)
from odoo import http
class GraphQLControllerMixin(object):
def _parse_body(self):
req = http.request.httprequest
# We use mimetype here since we don't need the other
# information provided by content_type
content_type = req.mimetype
if content_type == "application/graphql":
return {"query": req.data.decode("utf8")}
elif content_type == "application/json":
return load_json_body(req.data.decode("utf8"))
elif content_type in (
"application/x-www-form-urlencoded",
"multipart/form-data",
):
return http.request.params
return {}
def _process_request(self, schema, data):
try:
request = http.request.httprequest
execution_results, all_params = run_http_query(
schema,
request.method.lower(),
data,
query_data=request.args,
batch_enabled=False,
catch=False,
context_value={"env": http.request.env},
)
result, status_code = encode_execution_results(
execution_results,
is_batch=isinstance(data, list),
format_error=format_error_default,
encode=partial(json_encode, pretty=False),
)
headers = dict()
headers["Content-Type"] = "application/json"
response = http.request.make_response(result, headers=headers)
response.status_code = status_code
if any(er.errors for er in execution_results):
env = http.request.env
env.cr.rollback()
env.clear()
return response
except HttpQueryError as e:
result = json_encode({"errors": [{"message": str(e)}]})
headers = dict(e.headers or {})
headers["Content-Type"] = "application/json"
response = http.request.make_response(result, headers=headers)
response.status_code = e.status_code
env = http.request.env
env.cr.rollback()
env.clear()
return response
def _handle_graphql_request(self, schema):
data = self._parse_body()
return self._process_request(schema, data)
def _handle_graphiql_request(self, schema):
req = http.request.httprequest
if req.method == "GET" and req.accept_mimetypes.accept_html:
return http.request.render("graphql_base.graphiql", {})
# this way of passing a GraphQL query over http is not spec compliant
# (https://graphql.org/learn/serving-over-http/), but we use
# this only for our GraphiQL UI, and it works with Odoo's way
# of passing the csrf token
return self._process_request(schema, http.request.params)

View file

@ -0,0 +1,19 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * graphql_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: graphql_base
#: model_terms:ir.ui.view,arch_db:graphql_base.graphiql
msgid "Loading..."
msgstr "Učitavanje..."

View file

@ -0,0 +1,19 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * graphql_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: graphql_base
#: model_terms:ir.ui.view,arch_db:graphql_base.graphiql
msgid "Loading..."
msgstr ""

View file

@ -0,0 +1,22 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * graphql_base
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-01-15 17:34+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.17\n"
#. module: graphql_base
#: model_terms:ir.ui.view,arch_db:graphql_base.graphiql
msgid "Loading..."
msgstr "Caricamento ..."

View file

@ -0,0 +1,6 @@
This modules enables the creation of `GraphQL <https://graphql.org/>`__ endpoints.
In itself, it does nothing and must be used by a developer to
create the GraphQL schema and resolvers using
`graphene <https://graphene-python.org/>`__,
and expose them through a controller.
An example is available in the ``graphql_demo`` module.

View file

@ -0,0 +1,62 @@
To use this module, you need to
- create your graphene schema
- create your controller to expose your GraphQL endpoint,
and optionally a GraphiQL IDE.
This module does not attempt to expose the whole Odoo object model.
This could be the purpose of another module based on this one.
We believe however that it is preferable to expose a specific well tested
endpoint for each customer, so as to reduce coupling by knowing precisely
what is exposed and needs to be tested when upgrading Odoo.
To start working with this module, we recommend the following approach:
- Learn `GraphQL basics <https://graphql.org/learn/>`__
- Learn `graphene <https://graphene-python.org/>`__, the python library
used to create GraphQL schemas and resolvers.
- Examine the ``graphql_demo`` module in this repo, copy it,
adapt the controller to suit your needs (routes, authentication methods).
- Start building your own schema and resolver.
Building your schema
~~~~~~~~~~~~~~~~~~~~
The schema can be built using native graphene types.
An ``odoo.addons.graphql_base.types.OdooObjectType``
is provided as a convenience. It is a graphene ``ObjectType`` with a
default attribute resolver which:
- converts False to None (except for Boolean types), to avoid Odoo's weird
``False`` strings being rendered as json ``"false"``;
- adds the user timezone to Datetime fields;
- raises an error if an attribute is absent to avoid field name typing errors.
Creating GraphQL controllers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The module provides an ``odoo.addons.graphql_base.GraphQLControllerMixin``
class to help you build GraphQL controllers providing GraphiQL and/or GraphQL
endpoints.
.. code-block:: python
from odoo import http
from odoo.addons.graphql_base import GraphQLControllerMixin
from ..schema import schema
class GraphQLController(http.Controller, GraphQLControllerMixin):
# The GraphiQL route, providing an IDE for developers
@http.route("/graphiql/demo", auth="user")
def graphiql(self, **kwargs):
return self._handle_graphiql_request(schema)
# The graphql route, for applications.
# Note csrf=False: you may want to apply extra security
# (such as origin restrictions) to this route.
@http.route("/graphql/demo", auth="user", csrf=False)
def graphql(self, **kwargs):
return self._handle_graphql_request(schema)

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -0,0 +1,489 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Graphql Base</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="graphql-base">
<h1 class="title">Graphql Base</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:7cce5e4816ad8b14f69ef2998f98b1fa1ae2ff494da1ca0c1a247b4ababdd463
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/rest-framework/tree/16.0/graphql_base"><img alt="OCA/rest-framework" src="https://img.shields.io/badge/github-OCA%2Frest--framework-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/rest-framework-16-0/rest-framework-16-0-graphql_base"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/rest-framework&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This modules enables the creation of <a class="reference external" href="https://graphql.org/">GraphQL</a> endpoints.
In itself, it does nothing and must be used by a developer to
create the GraphQL schema and resolvers using
<a class="reference external" href="https://graphene-python.org/">graphene</a>,
and expose them through a controller.
An example is available in the <tt class="docutils literal">graphql_demo</tt> module.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a><ul>
<li><a class="reference internal" href="#building-your-schema" id="toc-entry-2">Building your schema</a></li>
<li><a class="reference internal" href="#creating-graphql-controllers" id="toc-entry-3">Creating GraphQL controllers</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-6">Authors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<p>To use this module, you need to</p>
<ul class="simple">
<li>create your graphene schema</li>
<li>create your controller to expose your GraphQL endpoint,
and optionally a GraphiQL IDE.</li>
</ul>
<p>This module does not attempt to expose the whole Odoo object model.
This could be the purpose of another module based on this one.
We believe however that it is preferable to expose a specific well tested
endpoint for each customer, so as to reduce coupling by knowing precisely
what is exposed and needs to be tested when upgrading Odoo.</p>
<p>To start working with this module, we recommend the following approach:</p>
<ul class="simple">
<li>Learn <a class="reference external" href="https://graphql.org/learn/">GraphQL basics</a></li>
<li>Learn <a class="reference external" href="https://graphene-python.org/">graphene</a>, the python library
used to create GraphQL schemas and resolvers.</li>
<li>Examine the <tt class="docutils literal">graphql_demo</tt> module in this repo, copy it,
adapt the controller to suit your needs (routes, authentication methods).</li>
<li>Start building your own schema and resolver.</li>
</ul>
<div class="section" id="building-your-schema">
<h2><a class="toc-backref" href="#toc-entry-2">Building your schema</a></h2>
<p>The schema can be built using native graphene types.
An <tt class="docutils literal">odoo.addons.graphql_base.types.OdooObjectType</tt>
is provided as a convenience. It is a graphene <tt class="docutils literal">ObjectType</tt> with a
default attribute resolver which:</p>
<ul class="simple">
<li>converts False to None (except for Boolean types), to avoid Odoos weird
<tt class="docutils literal">False</tt> strings being rendered as json <tt class="docutils literal">&quot;false&quot;</tt>;</li>
<li>adds the user timezone to Datetime fields;</li>
<li>raises an error if an attribute is absent to avoid field name typing errors.</li>
</ul>
</div>
<div class="section" id="creating-graphql-controllers">
<h2><a class="toc-backref" href="#toc-entry-3">Creating GraphQL controllers</a></h2>
<p>The module provides an <tt class="docutils literal">odoo.addons.graphql_base.GraphQLControllerMixin</tt>
class to help you build GraphQL controllers providing GraphiQL and/or GraphQL
endpoints.</p>
<pre class="code python literal-block">
<span class="kn">from</span> <span class="nn">odoo</span> <span class="kn">import</span> <span class="n">http</span><span class="w">
</span><span class="kn">from</span> <span class="nn">odoo.addons.graphql_base</span> <span class="kn">import</span> <span class="n">GraphQLControllerMixin</span><span class="w">
</span><span class="kn">from</span> <span class="nn">..schema</span> <span class="kn">import</span> <span class="n">schema</span><span class="w">
</span><span class="k">class</span> <span class="nc">GraphQLController</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">Controller</span><span class="p">,</span> <span class="n">GraphQLControllerMixin</span><span class="p">):</span><span class="w">
</span> <span class="c1"># The GraphiQL route, providing an IDE for developers</span><span class="w">
</span> <span class="nd">&#64;http</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/graphiql/demo&quot;</span><span class="p">,</span> <span class="n">auth</span><span class="o">=</span><span class="s2">&quot;user&quot;</span><span class="p">)</span><span class="w">
</span> <span class="k">def</span> <span class="nf">graphiql</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span><span class="w">
</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_handle_graphiql_request</span><span class="p">(</span><span class="n">schema</span><span class="p">)</span><span class="w">
</span> <span class="c1"># The graphql route, for applications.</span><span class="w">
</span> <span class="c1"># Note csrf=False: you may want to apply extra security</span><span class="w">
</span> <span class="c1"># (such as origin restrictions) to this route.</span><span class="w">
</span> <span class="nd">&#64;http</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/graphql/demo&quot;</span><span class="p">,</span> <span class="n">auth</span><span class="o">=</span><span class="s2">&quot;user&quot;</span><span class="p">,</span> <span class="n">csrf</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span><span class="w">
</span> <span class="k">def</span> <span class="nf">graphql</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span><span class="w">
</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_handle_graphql_request</span><span class="p">(</span><span class="n">schema</span><span class="p">)</span>
</pre>
</div>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-4">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/rest-framework/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/rest-framework/issues/new?body=module:%20graphql_base%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-5">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-6">Authors</a></h2>
<ul class="simple">
<li>ACSONE SA/NV</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/sbidoul"><img alt="sbidoul" src="https://github.com/sbidoul.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/rest-framework/tree/16.0/graphql_base">OCA/rest-framework</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,43 @@
# Copyright 2018 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import graphene
from odoo import fields
def odoo_attr_resolver(attname, default_value, root, info, **args):
"""An attr resolver that is specialized for Odoo recordsets.
It converts False to None, except for Odoo Boolean fields.
This is necessary because Odoo null values are often represented
as False, and graphene would convert a String field with value False
to "false".
It converts datetimes to the user timezone.
It also raises an error if the attribute is not present, ignoring
any default value, so as to return if the schema declares a field
that is not present in the underlying Odoo model.
"""
value = getattr(root, attname)
field = root._fields.get(attname)
if value is False:
if not isinstance(field, fields.Boolean):
return None
elif isinstance(field, fields.Datetime):
return fields.Datetime.context_timestamp(root, value)
return value
class OdooObjectType(graphene.ObjectType):
"""A graphene ObjectType with an Odoo aware default resolver."""
@classmethod
def __init_subclass_with_meta__(cls, default_resolver=None, **options):
if default_resolver is None:
default_resolver = odoo_attr_resolver
return super(OdooObjectType, cls).__init_subclass_with_meta__(
default_resolver=default_resolver, **options
)

View file

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2018 ACSONE SA/NV
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
The html code below is originally Copyright (c) Facebook, Inc.
published under Apache license.
-->
<odoo>
<template id="graphiql" name="GraphiQL">
<t t-call="web.layout">
<t t-set="head">
<style>
body {
height: 100%;
margin: 0;
width: 100%;
overflow: hidden;
}
#graphiql {
height: 100vh;
}
</style>
<!--
This GraphiQL example depends on Promise and fetch, which are available in
modern browsers, but can be "polyfilled" for older browsers.
GraphiQL itself depends on React DOM.
If you do not want to rely on a CDN, you can host these files locally or
include them directly in your favored resource bunder.
-->
<link
href="//cdn.jsdelivr.net/npm/graphiql@0.12.0/graphiql.css"
rel="stylesheet"
/>
<script src="//cdn.jsdelivr.net/npm/whatwg-fetch@2.0.3/fetch.min.js" />
<script
src="//cdn.jsdelivr.net/npm/react@16.2.0/umd/react.production.min.js"
/>
<script
src="//cdn.jsdelivr.net/npm/react-dom@16.2.0/umd/react-dom.production.min.js"
/>
<script src="//cdn.jsdelivr.net/npm/graphiql@0.12.0/graphiql.min.js" />
</t>
<t t-set="head" t-value="head" />
</t>
<body>
<div id="graphiql">Loading...</div>
<script>
/**
* This GraphiQL example illustrates how to use some of GraphiQL's props
* in order to enable reading and updating the URL parameters, making
* link sharing of queries a little bit easier.
*
* This is only one example of this kind of feature, GraphiQL exposes
* various React params to enable interesting integrations.
*/
// Parse the search string to get url parameters.
var search = window.location.search;
var parameters = {};
search.substr(1).split('&amp;').forEach(function (entry) {
var eq = entry.indexOf('=');
if (eq >= 0) {
parameters[decodeURIComponent(entry.slice(0, eq))] =
decodeURIComponent(entry.slice(eq + 1));
}
});
// if variables was provided, try to format it.
if (parameters.variables) {
try {
parameters.variables =
JSON.stringify(JSON.parse(parameters.variables), null, 2);
} catch (e) {
// Do nothing, we want to display the invalid JSON as a string, rather
// than present an error.
}
}
// When the query and variables string is edited, update the URL bar so
// that it can be easily shared
function onEditQuery(newQuery) {
parameters.query = newQuery;
updateURL();
}
function onEditVariables(newVariables) {
parameters.variables = newVariables;
updateURL();
}
function onEditOperationName(newOperationName) {
parameters.operationName = newOperationName;
updateURL();
}
function updateURL() {
var newSearch = '?' + Object.keys(parameters).filter(function (key) {
return Boolean(parameters[key]);
}).map(function (key) {
return encodeURIComponent(key) + '=' +
encodeURIComponent(parameters[key]);
}).join('&amp;');
history.replaceState(null, null, newSearch);
}
// Defines a GraphQL fetcher using the fetch API. You're not required to
// use fetch, and could instead implement graphQLFetcher however you like,
// as long as it returns a Promise or Observable.
function graphQLFetcher(graphQLParams) {
// This example expects a GraphQL server at the path /graphql.
// Change this to point wherever you host your GraphQL server.
data = new FormData();
data.append('query', graphQLParams['query']);
if (graphQLParams['variables']) {
data.append('variables', JSON.stringify(graphQLParams['variables']));
}
if (graphQLParams['operationName']) {
data.append('operationName', graphQLParams['operationName']);
}
data.append('csrf_token', odoo.csrf_token);
return fetch('', {
method: 'post',
headers: {
'Accept': 'application/json',
},
body: data,
credentials: 'include',
}).then(function (response) {
return response.text();
}).then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}
});
}
// Render &lt;GraphiQL /> into the body.
// See the README in the top level of this module to learn more about
// how you can customize GraphiQL by providing different values or
// additional child elements.
ReactDOM.render(
React.createElement(GraphiQL, {
fetcher: graphQLFetcher,
query: parameters.query,
variables: parameters.variables,
operationName: parameters.operationName,
onEditQuery: onEditQuery,
onEditVariables: onEditVariables,
onEditOperationName: onEditOperationName
}),
document.getElementById('graphiql')
);
</script>
</body>
</template>
</odoo>