[MERGE] Forward-port of branch 13.0 to 14.0
@@ -0,0 +1,890 @@
|
||||
|
||||
.. _webservices/iap:
|
||||
|
||||
===============
|
||||
In-App Purchase
|
||||
===============
|
||||
|
||||
In-App Purchase (IAP) allows providers of ongoing services through Odoo apps to
|
||||
be compensated for ongoing service use rather than — and possibly instead of
|
||||
— a sole initial purchase.
|
||||
|
||||
In that context, Odoo acts mostly as a *broker* between a client and an Odoo
|
||||
App Developer:
|
||||
|
||||
* Users purchase service tokens from Odoo.
|
||||
* Service providers draw tokens from the user's Odoo account when service
|
||||
is requested.
|
||||
|
||||
.. attention::
|
||||
|
||||
This document is intended for *service providers* and presents the latter,
|
||||
which can be done either via direct JSON-RPC2_ or if you are using Odoo
|
||||
using the convenience helpers it provides.
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
.. figure:: images/players.png
|
||||
:align: center
|
||||
|
||||
The Players
|
||||
|
||||
* The Service Provider is (probably) you the reader, you will be providing
|
||||
value to the client in the form of a service paid per-use.
|
||||
* The Client installed your Odoo App, and from there will request services.
|
||||
* Odoo brokers crediting, the Client adds credit to their account, and you
|
||||
can draw credits from there to provide services.
|
||||
* The External Service is an optional player: *you* can either provide a
|
||||
service directly, or you can delegate the actual service acting as a
|
||||
bridge/translator between an Odoo system and the actual service.
|
||||
|
||||
|
||||
.. figure:: images/credits.jpg
|
||||
:align: center
|
||||
|
||||
The Credits
|
||||
|
||||
.. note:: The credits went from integer to float value starting **October 2018**.
|
||||
Integer values are still supported.
|
||||
|
||||
Every service provided through the IAP platform can be used by the
|
||||
clients with tokens or *credits*. The credits are an float unit and
|
||||
their monetary value depends on the service and is decided by the
|
||||
provider. This could be:
|
||||
|
||||
* for an sms service: 1 credit = 1 sms;
|
||||
* for an ad service: 1 credit = 1 ad; or
|
||||
* for a postage service: 1 credit = 1 post stamp.
|
||||
|
||||
A credit can also simply be associated with a fixed amount of money
|
||||
to palliate the variations of price (e.g. the prices of sms and stamps
|
||||
may vary following the countries).
|
||||
|
||||
The value of the credits is fixed with the help of prepaid credit packs
|
||||
that the clients can buy on https://iap.odoo.com (see :ref:`Packs <iap-packages>`).
|
||||
|
||||
.. note:: In the following explanations we will ignore the External Service,
|
||||
they are just a detail of the service you provide.
|
||||
|
||||
.. figure:: images/normal.png
|
||||
:align: center
|
||||
|
||||
'Normal' service flow
|
||||
|
||||
If everything goes well, the normal flow is the following:
|
||||
|
||||
1. The Client requests a service of some sort.
|
||||
2. The Service Provider asks Odoo if there are enough credits for the
|
||||
service in the Client's account, and creates a transaction over that
|
||||
amount.
|
||||
3. The Service Provider provides the service (either on their own or
|
||||
calling to External Services).
|
||||
4. The Service Provider goes back to Odoo to capture (if the service could
|
||||
be provided) or cancel (if the service could not be provided) the
|
||||
transaction created at step 2.
|
||||
5. Finally, the Service Provider notifies the Client that the service has
|
||||
been rendered, possibly (depending on the service) displaying or
|
||||
storing its results in the client's system.
|
||||
|
||||
.. figure:: images/no-credit.png
|
||||
:align: center
|
||||
|
||||
Insufficient credits
|
||||
|
||||
However, if the Client's account lacks credits for the service, the flow will be as follows:
|
||||
|
||||
1. The Client requests a service as previously.
|
||||
2. The Service Provider asks Odoo if there are enough credits on the
|
||||
Client's account and gets a negative reply.
|
||||
3. This is signaled back to the Client.
|
||||
4. Who is redirected to their Odoo account to credit it and re-try.
|
||||
|
||||
|
||||
Building your service
|
||||
=====================
|
||||
|
||||
For this example, the service we will provide is ~~mining dogecoins~~ burning
|
||||
10 seconds of CPU for a credit. For your own services, you could, for example:
|
||||
|
||||
* provide an online service yourself (e.g. convert quotations to faxes for
|
||||
business in Japan);
|
||||
* provide an *offline* service yourself (e.g. provide accountancy service); or
|
||||
* act as intermediary to an other service provider (e.g. bridge to an MMS
|
||||
gateway).
|
||||
|
||||
.. _register-service:
|
||||
|
||||
Register the service on Odoo
|
||||
----------------------------
|
||||
|
||||
.. todo:: complete this part with screenshots
|
||||
|
||||
The first step is to register your service on the IAP endpoint (production
|
||||
and/or test) before you can actually query user accounts. To create a service,
|
||||
go to your *Portal Account* on the IAP endpoint (https://iap.odoo.com for
|
||||
production, https://iap-sandbox.odoo.com for testing, the endpoints are
|
||||
*independent* and *not synchronized*). Alternatively, you can go to your portal
|
||||
on Odoo (https://iap.odoo.com/my/home) and select *In-App Services*.
|
||||
|
||||
.. note::
|
||||
|
||||
On production, there is a manual validation step before the service
|
||||
can be used to manage real transactions. This step is automatically passed when
|
||||
on sandbox to ease the tests.
|
||||
|
||||
Log in then go to :menuselection:`My Account --> Your In-App Services`, click
|
||||
Create and provide the information of your service.
|
||||
|
||||
|
||||
The service has *seven* important fields:
|
||||
|
||||
* :samp:`name` - :class:`ServiceName`: This is the string you will need to provide inside
|
||||
the client's :ref:`app <iap-odoo-app>` when requesting a transaction from Odoo. (e.g.
|
||||
:class:`self.env['iap.account].get(name)`). As good practice, this should match the
|
||||
technical name of your app.
|
||||
|
||||
* :samp:`label` - :class:`Label`: The name displayed on the shopping portal for the
|
||||
client.
|
||||
|
||||
|
||||
.. warning::
|
||||
Both the :class:`ServiceName` and :class:`Label` are unique. As good practice, the
|
||||
:class:`ServiceName` should usually match the name of your Odoo Client App.
|
||||
|
||||
* :samp:`icon` - :class:`Icon`: A generic icon that will serve as default for your
|
||||
:ref:`packs <iap-packages>`.
|
||||
|
||||
* :samp:`key` - :class:`ServiceKey`: The developer key that identifies you in
|
||||
IAP (see :ref:`your service <iap-service>`) and allows to draw credits from
|
||||
the client's account. It will be shown only once upon creation of the service
|
||||
and can be regenerated at will.
|
||||
|
||||
.. danger::
|
||||
Your :class:`ServiceKey` *is a secret*, leaking your service key
|
||||
allows other application developers to draw credits bought for
|
||||
your service(s).
|
||||
|
||||
* :samp:`trial credits` - :class:`Float`: This corresponds to the credits you are ready to offer
|
||||
upon first use to your app users. Note that such service will only be available to clients that
|
||||
have an active enterprise contract.
|
||||
|
||||
* :samp:`privacy policy` - :class:`PrivacyPolicy`: This is an url to the privacy
|
||||
policy of your service. This should explicitly mention the **information you collect**,
|
||||
how you **use it, its relevance** to make your service work and inform the
|
||||
client on how they can **access, update or delete their personal information**.
|
||||
|
||||
.. image:: images/menu.png
|
||||
:align: center
|
||||
|
||||
.. image:: images/service_list.png
|
||||
:align: center
|
||||
|
||||
.. image:: images/creating_service.png
|
||||
:align: center
|
||||
|
||||
.. image:: images/service_created.png
|
||||
:align: center
|
||||
|
||||
You can then create *credit packs* which clients can purchase in order to
|
||||
use your service.
|
||||
|
||||
.. _iap-packages:
|
||||
|
||||
Packs
|
||||
-----
|
||||
|
||||
A credit pack is essentially a product with five characteristics:
|
||||
|
||||
* Name: name of the pack,
|
||||
* Icon: specific icon for the pack (if not provided, it will fallback on the service icon),
|
||||
* Description: details on the pack that will appear on the shop page as
|
||||
well as the invoice,
|
||||
* Amount: amount of credits the client is entitled to when buying the pack,
|
||||
* Price: price in EUR (for the time being, USD support is planned).
|
||||
|
||||
.. note::
|
||||
|
||||
Odoo takes a 25% commission on all pack sales. Adjust your selling price accordingly.
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Depending on the strategy, the price per credit may vary from one
|
||||
pack to another.
|
||||
|
||||
|
||||
.. image:: images/package.png
|
||||
:align: center
|
||||
|
||||
.. _iap-odoo-app:
|
||||
|
||||
Odoo App
|
||||
--------
|
||||
|
||||
.. todo:: does this actually require apps?
|
||||
|
||||
The second step is to develop an `Odoo App`_ which clients can install in their
|
||||
Odoo instance and through which they can *request* the services you provide.
|
||||
Our app will just add a button to the Partners form which lets a user request
|
||||
burning some CPU time on the server.
|
||||
|
||||
First, we will create an *odoo module* depending on ``iap``. IAP is a standard
|
||||
V11 module and the dependency ensures a local account is properly set up and
|
||||
we will have access to some necessary views and useful helpers.
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1-5
|
||||
:caption: `coalroller/__manifest__.py`
|
||||
|
||||
{
|
||||
'name': "Coal Roller",
|
||||
'category': 'Tools',
|
||||
'depends': ['iap'],
|
||||
}
|
||||
|
||||
Second, the "local" side of the integration. Here we will only be adding an
|
||||
action button to the partners view, but you can of course provide significant
|
||||
local value via your application and additional parts via a remote service.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 5-7
|
||||
:caption: `coalroller/__manifest__.py`
|
||||
|
||||
{
|
||||
'name': "Coal Roller",
|
||||
'category': 'Tools',
|
||||
'depends': ['iap'],
|
||||
'data': [
|
||||
'views/res_partner_views.xml',
|
||||
],
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
:emphasize-lines: 1-17
|
||||
:caption: `coalroller/views/res_partner_views.xml`
|
||||
|
||||
<odoo>
|
||||
<record model="ir.ui.view" id="partner_form_coalroll">
|
||||
<field name="name">partner.form.coalroll</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']">
|
||||
<button type="object" name="action_partner_coalroll"
|
||||
class="oe_stat_button" icon="fa-gears">
|
||||
<div class="o_form_field o_stat_info">
|
||||
<span class="o_stat_text">Roll Coal</span>
|
||||
</div>
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
.. image:: images/button.png
|
||||
:align: center
|
||||
|
||||
We can now implement the action method/callback. This will *call our own
|
||||
server*.
|
||||
|
||||
There are no requirements when it comes to the server or the communication
|
||||
protocol between the app and our server, but ``iap`` provides a
|
||||
:func:`~odoo.addons.iap.tools.iap_tools.iap_jsonrpc` helper to call a JSON-RPC2_ endpoint on an
|
||||
other Odoo instance and transparently re-raise relevant Odoo exceptions
|
||||
(:class:`~odoo.addons.iap.tools.iap_tools.InsufficientCreditError`,
|
||||
:class:`odoo.exceptions.AccessError` and :class:`odoo.exceptions.UserError`).
|
||||
|
||||
In that call, we will need to provide:
|
||||
|
||||
* any relevant client parameter (none here),
|
||||
* the :class:`token <UserToken>` of the current client that is provided by
|
||||
the ``iap.account`` model's ``account_token`` field. You can retrieve the
|
||||
account for your service by calling :samp:`env['iap.account'].get({service_name})`
|
||||
where :class:`service_name <ServiceName>` is the name of the service registered
|
||||
on IAP endpoint.
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1-21
|
||||
:caption: `coalroller/models/res_partner.py`
|
||||
|
||||
from odoo import api, models
|
||||
from odoo.addons.iap import jsonrpc, InsufficientCreditError
|
||||
|
||||
# whichever URL you deploy the service at, here we will run the remote
|
||||
# service in a local Odoo bound to the port 8070
|
||||
DEFAULT_ENDPOINT = 'http://localhost:8070'
|
||||
class Partner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
def action_partner_coalroll(self):
|
||||
# fetch the user's token for our service
|
||||
user_token = self.env['iap.account'].get('coalroller')
|
||||
params = {
|
||||
# we don't have any parameter to provide
|
||||
'account_token': user_token.account_token
|
||||
}
|
||||
# ir.config_parameter allows locally overriding the endpoint
|
||||
# for testing & al
|
||||
endpoint = self.env['ir.config_parameter'].sudo().get_param('coalroller.endpoint', DEFAULT_ENDPOINT)
|
||||
jsonrpc(endpoint + '/roll', params=params)
|
||||
return True
|
||||
|
||||
.. note::
|
||||
|
||||
``iap`` automatically handles
|
||||
:class:`~odoo.addons.iap.tools.iap_tools.InsufficientCreditError` coming from the action
|
||||
and prompts the user to add credits to their account.
|
||||
|
||||
:func:`~odoo.addons.iap.tools.iap_tools.iap_jsonrpc` takes care of re-raising
|
||||
:class:`~odoo.addons.iap.models.iap.InsufficientCreditError` for you.
|
||||
|
||||
.. danger::
|
||||
|
||||
If you are not using :func:`~odoo.addons.iap.tools.iap_tools.iap_jsonrpc` you *must* be
|
||||
careful to re-raise
|
||||
:class:`~odoo.addons.iap.tools.iap_tools.InsufficientCreditError` in your handler
|
||||
otherwise the user will not be prompted to credit their account, and the
|
||||
next call will fail the same way.
|
||||
|
||||
.. _iap-service:
|
||||
|
||||
Service
|
||||
-------
|
||||
|
||||
Though that is not *required*, since ``iap`` provides both a client helper
|
||||
for JSON-RPC2_ calls (:func:`~odoo.addons.iap.tools.iap_tools.iap_jsonrpc`) and a service helper
|
||||
for transactions (:class:`~odoo.addons.iap.tools.iap_tools.iap_charge`) we will also be
|
||||
implementing the service side as an Odoo module:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1-5
|
||||
:caption: `coalroller_service/__manifest__.py`
|
||||
|
||||
{
|
||||
'name': "Coal Roller Service",
|
||||
'category': 'Tools',
|
||||
'depends': ['iap'],
|
||||
}
|
||||
|
||||
Since the query from the client comes as JSON-RPC2_ we will need the
|
||||
corresponding controller which can call :class:`~odoo.addons.iap.tools.iap_tools.iap_charge` and
|
||||
perform the service within:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1-27
|
||||
:caption: `coalroller_service/controllers/main.py`
|
||||
|
||||
from passlib import pwd, hash
|
||||
|
||||
from odoo import http
|
||||
from odoo.addons.iap import charge
|
||||
|
||||
class CoalBurnerController(http.Controller):
|
||||
@http.route('/roll', type='json', auth='none', csrf='false')
|
||||
def roll(self, account_token):
|
||||
# the service key *is a secret*, it should not be committed in
|
||||
# the source
|
||||
service_key = http.request.env['ir.config_parameter'].sudo().get_param('coalroller.service_key')
|
||||
|
||||
# we charge 1 credit for 10 seconds of CPU
|
||||
cost = 1
|
||||
# TODO: allow the user to specify how many (tens of seconds) of CPU they want to use
|
||||
with charge(http.request.env, service_key, account_token, cost):
|
||||
|
||||
# 10 seconds of CPU per credit
|
||||
end = time.time() (10 * cost)
|
||||
while time.time() < end:
|
||||
# we will use CPU doing useful things: generating and
|
||||
# hashing passphrases
|
||||
p = pwd.genphrase()
|
||||
h = hash.pbkdf2_sha512.hash(p)
|
||||
# here we don't have anything useful to the client, an error
|
||||
# will be raised & transmitted in case of issue, if no error
|
||||
# is raised we did the job
|
||||
|
||||
.. todo:: for the actual IAP will the "portal" page be on odoo.com or iap.odoo.com?
|
||||
|
||||
.. todo:: "My Account" > "Your InApp Services"?
|
||||
|
||||
|
||||
The :class:`~odoo.addons.iap.tools.iap_tools.iap_charge` helper will:
|
||||
|
||||
1. authorize (create) a transaction with the specified number of credits,
|
||||
if the account does not have enough credits it will raise the relevant
|
||||
error
|
||||
2. execute the body of the ``with`` statement
|
||||
3. if the body of the ``with`` executes successfully, update the price
|
||||
of the transaction if needed
|
||||
4. capture (confirm) the transaction
|
||||
5. otherwise, if an error is raised from the body of the ``with``, cancel the
|
||||
transaction (and release the hold on the credits)
|
||||
|
||||
.. danger::
|
||||
|
||||
By default, :class:`~odoo.addons.iap.tools.iap_tools.iap_charge` contacts the *production*
|
||||
IAP endpoint, https://iap.odoo.com. While developing and testing your
|
||||
service you may want to point it towards the *development* IAP endpoint
|
||||
https://iap-sandbox.odoo.com.
|
||||
|
||||
To do so, set the ``iap.endpoint`` config parameter in your service
|
||||
Odoo: in debug/developer mode, :menuselection:`Setting --> Technical -->
|
||||
Parameters --> System Parameters`, just define an entry for the key
|
||||
``iap.endpoint`` if none already exists).
|
||||
|
||||
The :class:`~odoo.addons.iap.tools.iap_tools.iap_charge` helper has two additional optional
|
||||
parameters we can use to make things clearer to the end-user.
|
||||
|
||||
``description``
|
||||
is a message which will be associated with the transaction and will be
|
||||
displayed in the user's dashboard, it is useful to remind the user why
|
||||
the charge exists.
|
||||
``credit_template``
|
||||
is the name of a :ref:`reference/qweb` template which will be rendered
|
||||
and shown to the user if their account has less credit available than the
|
||||
service provider is requesting, its purpose is to tell your users why
|
||||
they should be interested in your IAP offers.
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 5-7
|
||||
:caption: `coalroller_service/__manifest__.py`
|
||||
|
||||
{
|
||||
'name': "Coal Roller Service",
|
||||
'category': 'Tools',
|
||||
'depends': ['iap'],
|
||||
'data': [
|
||||
'views/no-credit.xml',
|
||||
],
|
||||
}
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 10-12
|
||||
:caption: `coalroller_service/controllers/main.py`
|
||||
|
||||
@http.route('/roll', type='json', auth='none', csrf='false')
|
||||
def roll(self, account_token):
|
||||
# the service key *is a secret*, it should not be committed in
|
||||
# the source
|
||||
service_key = http.request.env['ir.config_parameter'].sudo().get_param('coalroller.service_key')
|
||||
|
||||
# we charge 1 credit for 10 seconds of CPU
|
||||
cost = 1
|
||||
# TODO: allow the user to specify how many (tens of seconds) of CPU they want to use
|
||||
with charge(http.request.env, service_key, account_token, cost,
|
||||
description="We're just obeying orders",
|
||||
credit_template='coalroller_service.no_credit'):
|
||||
|
||||
# 10 seconds of CPU per credit
|
||||
end = time.time() (10 * cost)
|
||||
while time.time() < end:
|
||||
# we will use CPU doing useful things: generating and
|
||||
# hashing passphrases
|
||||
p = pwd.genphrase()
|
||||
h = hash.pbkdf2_sha512.hash(p)
|
||||
|
||||
.. code-block:: xml
|
||||
:emphasize-lines: 1-18
|
||||
:caption: `coalroller_service/views/no-credit.xml`
|
||||
|
||||
<odoo>
|
||||
<template id="no_credit" name="No credit warning">
|
||||
<div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-7 offset-lg-1 mt32 mb32">
|
||||
<h2>Consume electricity doing nothing useful!</h2>
|
||||
<ul>
|
||||
<li>Heat our state of the art data center for no reason</li>
|
||||
<li>Use multiple watts for only 0.1€</li>
|
||||
<li>Roll coal without going outside</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
.. TODO:: how do you test your service?
|
||||
|
||||
JSON-RPC2_ Transaction API
|
||||
==========================
|
||||
|
||||
.. image:: images/flow.png
|
||||
:align: center
|
||||
|
||||
* The IAP transaction API does not require using Odoo when implementing your
|
||||
server gateway, calls are standard JSON-RPC2_.
|
||||
* Calls use different *endpoints* but the same *method* on all endpoints
|
||||
(``call``).
|
||||
* Exceptions are returned as JSON-RPC2_ errors, the formal exception name is
|
||||
available on ``data.name`` for programmatic manipulation.
|
||||
|
||||
.. seealso:: `iap.odoo.com documentation`_ for additional information.
|
||||
|
||||
Authorize
|
||||
---------
|
||||
|
||||
.. function:: /iap/1/authorize
|
||||
|
||||
Verifies that the user's account has at least as ``credit`` available
|
||||
*and creates a hold (pending transaction) on that amount*.
|
||||
|
||||
Any amount currently on hold by a pending transaction is considered
|
||||
unavailable to further authorize calls.
|
||||
|
||||
Returns a :class:`TransactionToken` identifying the pending transaction
|
||||
which can be used to capture (confirm) or cancel said transaction (`iap.odoo.com documentation`_).
|
||||
|
||||
:param ServiceKey key:
|
||||
:param UserToken account_token:
|
||||
:param float credit:
|
||||
:param str description: optional, helps users identify the reason for
|
||||
charges on their account
|
||||
:param str dbuuid: optional, allows the user to benefit from trial
|
||||
credits if his database is eligible (see :ref:`Service registration <register-service>`)
|
||||
:returns: :class:`TransactionToken` if the authorization succeeded
|
||||
:raises: :class:`~odoo.exceptions.AccessError` if the service token is invalid
|
||||
:raises: :class:`~odoo.addons.iap.models.iap.InsufficientCreditError` if the account does not have enough credits
|
||||
:raises: ``TypeError`` if the ``credit`` value is not an integer or a float
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
r = requests.post(ODOO + '/iap/1/authorize', json={
|
||||
'jsonrpc': '2.0',
|
||||
'id': None,
|
||||
'method': 'call',
|
||||
'params': {
|
||||
'account_token': user_account,
|
||||
'key': SERVICE_KEY,
|
||||
'credit': 25,
|
||||
'description': "Why this is being charged",
|
||||
}
|
||||
}).json()
|
||||
if 'error' in r:
|
||||
# handle authorize error
|
||||
tx = r['result']
|
||||
|
||||
# provide your service here
|
||||
|
||||
Capture
|
||||
-------
|
||||
|
||||
.. function:: /iap/1/capture
|
||||
|
||||
Confirms the specified transaction, transferring the reserved credits from
|
||||
the user's account to the service provider's.
|
||||
|
||||
Capture calls are idempotent: performing capture calls on an already
|
||||
captured transaction has no further effect.
|
||||
|
||||
:param TransactionToken token:
|
||||
:param ServiceKey key:
|
||||
:param float credit_to_capture: optional parameter to capture a smaller amount of credits than authorized
|
||||
:raises: :class:`~odoo.exceptions.AccessError`
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 8
|
||||
|
||||
r2 = requests.post(ODOO + '/iap/1/capture', json={
|
||||
'jsonrpc': '2.0',
|
||||
'id': None,
|
||||
'method': 'call',
|
||||
'params': {
|
||||
'token': tx,
|
||||
'key': SERVICE_KEY,
|
||||
'credit_to_capture': credit or False,
|
||||
}
|
||||
}).json()
|
||||
if 'error' in r:
|
||||
# handle capture error
|
||||
# otherwise transaction is captured
|
||||
|
||||
Cancel
|
||||
------
|
||||
|
||||
.. function:: /iap/1/cancel
|
||||
|
||||
Cancels the specified transaction, releasing the hold on the user's
|
||||
credits.
|
||||
|
||||
Cancel calls are idempotent: performing capture calls on an already
|
||||
cancelled transaction has no further effect.
|
||||
|
||||
:param TransactionToken token:
|
||||
:param ServiceKey key:
|
||||
:raises: :class:`~odoo.exceptions.AccessError`
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
r2 = requests.post(ODOO + '/iap/1/cancel', json={
|
||||
'jsonrpc': '2.0',
|
||||
'id': None,
|
||||
'method': 'call',
|
||||
'params': {
|
||||
'token': tx,
|
||||
'key': SERVICE_KEY,
|
||||
}
|
||||
}).json()
|
||||
if 'error' in r:
|
||||
# handle cancel error
|
||||
# otherwise transaction is cancelled
|
||||
|
||||
Types
|
||||
-----
|
||||
|
||||
Exceptions aside, these are *abstract types* used for clarity, you should not
|
||||
care how they are implemented.
|
||||
|
||||
.. class:: ServiceName
|
||||
|
||||
String identifying your service on https://iap.odoo.com (production) as well
|
||||
as the account related to your service in the client's database.
|
||||
|
||||
.. class:: ServiceKey
|
||||
|
||||
Identifier generated for the provider's service. Each key (and service)
|
||||
matches a token of a fixed value, as generated by the service provide.
|
||||
|
||||
Multiple types of tokens correspond to multiple services. As an exampe, SMS and MMS
|
||||
could either be the same service (with an MMS being 'worth' multiple SMS)
|
||||
or could be separate services at separate price points.
|
||||
|
||||
.. danger:: Your service key *is a secret*, leaking your service key
|
||||
allows other application developers to draw credits bought for
|
||||
your service(s).
|
||||
|
||||
.. class:: UserToken
|
||||
|
||||
Identifier for a user account.
|
||||
|
||||
.. class:: TransactionToken
|
||||
|
||||
Transaction identifier, returned by the authorization process and consumed
|
||||
by either capturing or cancelling the transaction.
|
||||
|
||||
.. exception:: odoo.addons.iap.tools.iap_tools.InsufficientCreditError
|
||||
|
||||
Raised during transaction authorization if the credits requested are not
|
||||
currently available on the account (either not enough credits or too many
|
||||
pending transactions/existing holds).
|
||||
|
||||
.. exception:: odoo.exceptions.AccessError
|
||||
:noindex:
|
||||
|
||||
Raised by:
|
||||
|
||||
* any operation to which a service token is required, if the service token is invalid; or
|
||||
* any failure in an inter-server call. (typically, in :func:`~odoo.addons.iap.tools.iap_tools.iap_jsonrpc`).
|
||||
|
||||
.. exception:: odoo.exceptions.UserError
|
||||
:noindex:
|
||||
|
||||
Raised by any unexpected behaviour at the discretion of the App developer (*you*).
|
||||
|
||||
|
||||
Test the API
|
||||
------------
|
||||
|
||||
In order to test the developed app, we propose a sandbox platform that allows you to:
|
||||
|
||||
1. Test the whole flow from the client's point of view - Actual services and transactions
|
||||
that can be consulted. (again this requires to change the endpoint, see the danger note
|
||||
in :ref:`Service <iap-service>`).
|
||||
2. Test the API.
|
||||
|
||||
The latter consists in specific tokens that will work on **IAP-Sandbox only**.
|
||||
|
||||
* Token ``000000``: Represents a non-existing account. Returns
|
||||
an :class:`~odoo.addons.iap.tools.iap_tools.InsufficientCreditError` on authorize attempt.
|
||||
* Token ``000111``: Represents an account without sufficient credits to perform any service.
|
||||
Returns an :class:`~odoo.addons.iap.tools.iap_tools.InsufficientCreditError` on authorize attempt.
|
||||
* Token ``111111``: Represents an account with enough credits to perform any service.
|
||||
An authorize attempt will return a dummy transaction token that is processed by the capture
|
||||
and cancel routes.
|
||||
|
||||
.. note::
|
||||
|
||||
* Those tokens are only active on the IAP-Sanbox server.
|
||||
* The service key is completely ignored with this flow, If you want to run a robust test
|
||||
of your service, you should ignore these tokens.
|
||||
|
||||
Odoo Helpers
|
||||
============
|
||||
|
||||
For convenience, if you are implementing your service using Odoo the ``iap``
|
||||
module provides a few helpers to make IAP flow even simpler.
|
||||
|
||||
.. _iap-charging:
|
||||
|
||||
Charging
|
||||
--------
|
||||
|
||||
.. class:: odoo.addons.iap.tools.iap_tools.iap_charge(env, key, account_token, credit[, dbuuid, description, credit_template])
|
||||
|
||||
A *context manager* for authorizing and automatically capturing or
|
||||
cancelling transactions for use in the backend/proxy.
|
||||
|
||||
Works much like e.g. a cursor context manager:
|
||||
|
||||
* immediately authorizes a transaction with the specified parameters;
|
||||
* executes the ``with`` body;
|
||||
* if the body executes in full without error, captures the transaction;
|
||||
* otherwise cancels it.
|
||||
|
||||
:param odoo.api.Environment env: used to retrieve the ``iap.endpoint``
|
||||
configuration key
|
||||
:param ServiceKey key:
|
||||
:param UserToken token:
|
||||
:param float credit:
|
||||
:param str description:
|
||||
:param Qweb template credit_template:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 11,13,14,15
|
||||
|
||||
@route('/deathstar/superlaser', type='json')
|
||||
def superlaser(self, user_account,
|
||||
coordinates, target,
|
||||
factor=1.0):
|
||||
"""
|
||||
:param factor: superlaser power factor,
|
||||
0.0 is none, 1.0 is full power
|
||||
"""
|
||||
credits = int(MAXIMUM_POWER * factor)
|
||||
description = "We will demonstrate the power of this station on your home planet of Alderaan."
|
||||
with iap_charge(request.env, SERVICE_KEY, user_account, credits, description) as transaction:
|
||||
# TODO: allow other targets
|
||||
transaction.credit = max(credits, 2)
|
||||
# Sales ongoing one the energy price,
|
||||
# a maximum of 2 credits will be charged/captured.
|
||||
self.env['systems.planets'].search([
|
||||
('grid', '=', 'M-10'),
|
||||
('name', '=', 'Alderaan'),
|
||||
]).unlink()
|
||||
|
||||
|
||||
Authorize
|
||||
---------
|
||||
|
||||
.. class:: odoo.addons.iap.tools.iap_tools.iap_authorize(env, key, account_token, credit[, dbuuid, description, credit_template])
|
||||
|
||||
Will authorize everything.
|
||||
|
||||
:param odoo.api.Environment env: used to retrieve the ``iap.endpoint``
|
||||
configuration key
|
||||
:param ServiceKey key:
|
||||
:param UserToken token:
|
||||
:param float credit:
|
||||
:param str description:
|
||||
:param Qweb template credit_template:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 12
|
||||
|
||||
@route('/deathstar/superlaser', type='json')
|
||||
def superlaser(self, user_account,
|
||||
coordinates, target,
|
||||
factor=1.0):
|
||||
"""
|
||||
:param factor: superlaser power factor,
|
||||
0.0 is none, 1.0 is full power
|
||||
"""
|
||||
credits = int(MAXIMUM_POWER * factor)
|
||||
description = "We will demonstrate the power of this station on your home planet of Alderaan."
|
||||
#actual IAP stuff
|
||||
transaction_token = authorize(request.env, SERVICE_KEY, user_account, credits, description=description)
|
||||
try:
|
||||
# Beware the power of this laser
|
||||
self.put_galactical_princess_in_sorrow()
|
||||
except Exception as e:
|
||||
# Nevermind ...
|
||||
r = cancel(env,transaction_token, key)
|
||||
raise e
|
||||
else:
|
||||
# We shall rule over the galaxy!
|
||||
capture(env,transaction_token, key, min(credits, 2))
|
||||
|
||||
Cancel
|
||||
------
|
||||
|
||||
.. class:: odoo.addons.iap.tools.iap_tools.iap_cancel(env, transaction_token, key)
|
||||
|
||||
Will cancel an authorized transaction.
|
||||
|
||||
:param odoo.api.Environment env: used to retrieve the ``iap.endpoint``
|
||||
configuration key
|
||||
:param str transaction_token:
|
||||
:param ServiceKey key:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 16,17,18,19
|
||||
|
||||
@route('/deathstar/superlaser', type='json')
|
||||
def superlaser(self, user_account,
|
||||
coordinates, target,
|
||||
factor=1.0):
|
||||
"""
|
||||
:param factor: superlaser power factor,
|
||||
0.0 is none, 1.0 is full power
|
||||
"""
|
||||
credits = int(MAXIMUM_POWER * factor)
|
||||
description = "We will demonstrate the power of this station on your home planet of Alderaan."
|
||||
#actual IAP stuff
|
||||
transaction_token = authorize(request.env, SERVICE_KEY, user_account, credits, description=description)
|
||||
try:
|
||||
# Beware the power of this laser
|
||||
self.put_galactical_princess_in_sorrow()
|
||||
except Exception as e:
|
||||
# Nevermind ...
|
||||
r = cancel(env,transaction_token, key)
|
||||
raise e
|
||||
else:
|
||||
# We shall rule over the galaxy!
|
||||
capture(env,transaction_token, key, min(credits, 2))
|
||||
|
||||
Capture
|
||||
-------
|
||||
|
||||
.. class:: odoo.addons.iap.tools.iap_tools.iap_capture(env, transaction_token, key, credit)
|
||||
|
||||
Will capture the amount ``credit`` on the given transaction.
|
||||
|
||||
:param odoo.api.Environment env: used to retrieve the ``iap.endpoint``
|
||||
configuration key
|
||||
:param str transaction_token:
|
||||
:param ServiceKey key:
|
||||
:param credit:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 20,21,22
|
||||
|
||||
@route('/deathstar/superlaser', type='json')
|
||||
def superlaser(self, user_account,
|
||||
coordinates, target,
|
||||
factor=1.0):
|
||||
"""
|
||||
:param factor: superlaser power factor,
|
||||
0.0 is none, 1.0 is full power
|
||||
"""
|
||||
credits = int(MAXIMUM_POWER * factor)
|
||||
description = "We will demonstrate the power of this station on your home planet of Alderaan."
|
||||
#actual IAP stuff
|
||||
transaction_token = authorize(request.env, SERVICE_KEY, user_account, credits, description=description)
|
||||
try:
|
||||
# Beware the power of this laser
|
||||
self.put_galactical_princess_in_sorrow()
|
||||
except Exception as e:
|
||||
# Nevermind ...
|
||||
r = cancel(env,transaction_token, key)
|
||||
raise e
|
||||
else:
|
||||
# We shall rule over the galaxy!
|
||||
capture(env,transaction_token, key, min(credits, 2))
|
||||
|
||||
|
||||
.. _JSON-RPC2: https://www.jsonrpc.org/specification
|
||||
.. _Odoo App: https://www.odoo.com/apps
|
||||
.. _iap.odoo.com documentation: https://iap.odoo.com/iap/1/documentation
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,655 @@
|
||||
:code-column:
|
||||
|
||||
.. _reference/upgrade-api:
|
||||
|
||||
================
|
||||
Database Upgrade
|
||||
================
|
||||
|
||||
Introduction
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This document describes the API used to upgrade an Odoo database to a
|
||||
higher version.
|
||||
|
||||
It allows a database to be upgraded without ressorting to the html form at
|
||||
https://upgrade.odoo.com
|
||||
Although the database will follow the same process described on that form.
|
||||
|
||||
|
||||
The required steps are:
|
||||
|
||||
* :ref:`creating a request <upgrade-api-create-method>`
|
||||
* :ref:`uploading a database dump <upgrade-api-upload-method>`
|
||||
* :ref:`running the upgrade process <upgrade-api-process-method>`
|
||||
* :ref:`obtaining the status of the database request <upgrade-api-status-method>`
|
||||
* :ref:`downloading the upgraded database dump <upgrade-api-download-method>`
|
||||
|
||||
The methods
|
||||
~~~~~~~~~~~
|
||||
|
||||
.. _upgrade-api-create-method:
|
||||
|
||||
Creating a database upgrade request
|
||||
===================================
|
||||
|
||||
This action creates a database request with the following information:
|
||||
|
||||
* your contract reference
|
||||
* your email address
|
||||
* the target version (the Odoo version you want to upgrade to)
|
||||
* the purpose of your request (test or production)
|
||||
* the database dump name (required but purely informative)
|
||||
* optionally the server timezone (for Odoo source version < 6.1)
|
||||
|
||||
The ``create`` method
|
||||
---------------------
|
||||
|
||||
.. py:function:: https://upgrade.odoo.com/database/v1/create
|
||||
|
||||
Creates a database upgrade request
|
||||
|
||||
:param str contract: (required) your enterprise contract reference
|
||||
:param str email: (required) your email address
|
||||
:param str target: (required) the Odoo version you want to upgrade to. Valid choices: 11.0, 12.0, 13.0
|
||||
:param str aim: (required) the purpose of your upgrade database request. Valid choices: test, production.
|
||||
:param str filename: (required) a purely informative name for you database dump file
|
||||
:param str timezone: (optional) the timezone used by your server. Only for Odoo source version < 6.1
|
||||
:return: request result
|
||||
:rtype: JSON dictionary
|
||||
|
||||
The *create* method returns a JSON dictionary containing the following keys:
|
||||
|
||||
.. _upgrade-api-json-failure:
|
||||
|
||||
``failures``
|
||||
''''''''''''
|
||||
|
||||
The list of errors.
|
||||
|
||||
A list of dictionaries, each dictionary giving information about one particular
|
||||
error. Each dictionary can contain various keys depending of the type of error
|
||||
but you will always get the ``reason`` and the ``message`` keys:
|
||||
|
||||
* ``reason``: the error type
|
||||
* ``message``: a human friendly message
|
||||
|
||||
Some possible keys:
|
||||
|
||||
* ``code``: a faulty value
|
||||
* ``value``: a faulty value
|
||||
* ``expected``: a list of valid values
|
||||
|
||||
See a sample output aside.
|
||||
|
||||
.. rst-class:: setup doc-aside
|
||||
|
||||
.. switcher::
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"failures": [
|
||||
{
|
||||
"expected": [
|
||||
"11.0",
|
||||
"12.0",
|
||||
"13.0",
|
||||
],
|
||||
"message": "Invalid value \"5.0\"",
|
||||
"reason": "TARGET:INVALID",
|
||||
"value": "5.0"
|
||||
},
|
||||
{
|
||||
"code": "M123456-abcxyz",
|
||||
"message": "Can not find contract M123456-abcxyz",
|
||||
"reason": "CONTRACT:NOT_FOUND"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
``request``
|
||||
'''''''''''
|
||||
|
||||
If the *create* method is successful, the value associated to the *request* key
|
||||
will be a dictionary containing various information about the created request:
|
||||
|
||||
The most important keys are:
|
||||
|
||||
* ``id``: the request id
|
||||
* ``key``: your private key for this request
|
||||
|
||||
These 2 values will be requested by the other methods (upload, process and status)
|
||||
|
||||
The other keys will be explained in the section describing the :ref:`status method <upgrade-api-status-method>`.
|
||||
|
||||
|
||||
Sample script
|
||||
'''''''''''''
|
||||
|
||||
Here are 2 examples of database upgrade request creation using:
|
||||
|
||||
* one in the python programming language using the requests library
|
||||
* one in the bash programming language using `curl <https://curl.haxx.se>`_ (tool
|
||||
for transferring data using http) and `jq <https://stedolan.github.io/jq>`_ (JSON processor):
|
||||
|
||||
.. rst-class:: setup doc-aside
|
||||
|
||||
.. switcher::
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import requests
|
||||
|
||||
CREATE_URL = "https://upgrade.odoo.com/database/v1/create"
|
||||
CONTRACT = "M123456-abcdef"
|
||||
AIM = "test"
|
||||
TARGET = "12.0"
|
||||
EMAIL = "john.doe@example.com"
|
||||
FILENAME = "db_name.dump"
|
||||
|
||||
fields = dict([
|
||||
('aim', AIM),
|
||||
('email', EMAIL),
|
||||
('filename', DB_SOURCE),
|
||||
('contract', CONTRACT),
|
||||
('target', TARGET),
|
||||
])
|
||||
|
||||
r = requests.get(CREATE_URL, data=fields)
|
||||
print(r.text)
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
CONTRACT=M123456-abcdef
|
||||
AIM=test
|
||||
TARGET=12.0
|
||||
EMAIL=john.doe@example.com
|
||||
FILENAME=db_name.dump
|
||||
CREATE_URL="https://upgrade.odoo.com/database/v1/create"
|
||||
URL_PARAMS="contract=${CONTRACT}&aim=${AIM}&target=${TARGET}&email=${EMAIL}&filename=${FILENAME}"
|
||||
curl -sS "${CREATE_URL}?${URL_PARAMS}" > create_result.json
|
||||
|
||||
# check for failures
|
||||
failures=$(cat create_result.json | jq -r '.failures[]')
|
||||
if [ "$failures" != "" ]; then
|
||||
echo $failures | jq -r '.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
.. _upgrade-api-upload-method:
|
||||
|
||||
Uploading your database dump
|
||||
============================
|
||||
|
||||
There are 2 methods to upload your database dump:
|
||||
|
||||
* the ``upload`` method using the HTTPS protocol
|
||||
* the ``request_sftp_access`` method using the SFTP protocol
|
||||
|
||||
The ``upload`` method
|
||||
---------------------
|
||||
|
||||
It's the most simple and most straightforward way of uploading your database dump.
|
||||
It uses the HTTPS protocol.
|
||||
|
||||
.. py:function:: https://upgrade.odoo.com/database/v1/upload
|
||||
|
||||
Uploads a database dump
|
||||
|
||||
:param str key: (required) your private key
|
||||
:param str request: (required) your request id
|
||||
:return: request result
|
||||
:rtype: JSON dictionary
|
||||
|
||||
The request id and the private key are obtained using the :ref:`create method
|
||||
<upgrade-api-create-method>`
|
||||
|
||||
The result is a JSON dictionary containing the list of ``failures``, which
|
||||
should be empty if everything went fine.
|
||||
|
||||
.. rst-class:: setup doc-aside
|
||||
|
||||
.. switcher::
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import requests
|
||||
|
||||
UPLOAD_URL = "https://upgrade.odoo.com/database/v1/upload"
|
||||
DUMPFILE = "/tmp/dump.sql"
|
||||
|
||||
fields = dict([
|
||||
('request', '10534'),
|
||||
('key', 'Aw7pItGVKFuZ_FOR3U8VFQ=='),
|
||||
])
|
||||
headers = {"Content-Type": "application/octet-stream"}
|
||||
|
||||
with open(DUMPFILE, 'rb') as f:
|
||||
requests.post(UPLOAD_URL, data=f, params=fields, headers=headers)
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
UPLOAD_URL="https://upgrade.odoo.com/database/v1/upload"
|
||||
DUMPFILE="openchs.70.cdump"
|
||||
KEY="Aw7pItGVKFuZ_FOR3U8VFQ=="
|
||||
REQUEST_ID="10534"
|
||||
URL_PARAMS="key=${KEY}&request=${REQUEST_ID}"
|
||||
HEADER="Content-Type: application/octet-stream"
|
||||
curl -H $HEADER --data-binary "@${DUMPFILE}" "${UPLOAD_URL}?${URL_PARAMS}"
|
||||
|
||||
.. _upgrade-api-request-sftp-access-method:
|
||||
|
||||
The ``request_sftp_access`` method
|
||||
----------------------------------
|
||||
|
||||
This method is recommended for big database dumps.
|
||||
It uses the SFTP protocol and supports resuming.
|
||||
|
||||
It will create a temporary SFTP server where you can connect to and allow you
|
||||
to upload your database dump using an SFTP client.
|
||||
|
||||
.. py:function:: https://upgrade.odoo.com/database/v1/request_sftp_access
|
||||
|
||||
Creates an SFTP server
|
||||
|
||||
:param str key: (required) your private key
|
||||
:param str request: (required) your request id
|
||||
:param str ssh_keys: (required) the path to a file listing the ssh public keys you'd like to use
|
||||
:return: request result
|
||||
:rtype: JSON dictionary
|
||||
|
||||
The request id and the private key are obtained using the :ref:`create method
|
||||
<upgrade-api-create-method>`
|
||||
|
||||
The file listing your ssh public keys should be roughly similar to a standard ``authorized_keys`` file.
|
||||
This file should only contains public keys, blank lines or comments (lines starting with the ``#`` character)
|
||||
|
||||
Your database upgrade request should be in the ``draft`` state.
|
||||
|
||||
The ``request_sftp_access`` method returns a JSON dictionary containing the following keys:
|
||||
|
||||
|
||||
.. rst-class:: setup doc-aside
|
||||
|
||||
.. switcher::
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import requests
|
||||
|
||||
UPLOAD_URL = "https://upgrade.odoo.com/database/v1/request_sftp_access"
|
||||
SSH_KEY = "$HOME/.ssh/id_rsa.pub"
|
||||
SSH_KEY_CONTENT = open(SSH_KEY,'r').read()
|
||||
|
||||
fields = dict([
|
||||
('request', '10534'),
|
||||
('key', 'Aw7pItGVKFuZ_FOR3U8VFQ=='),
|
||||
('ssh_keys', SSH_KEY_CONTENT)
|
||||
])
|
||||
|
||||
r = requests.post(UPLOAD_URL, params=fields)
|
||||
print(r.text)
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
REQUEST_SFTP_ACCESS_URL="https://upgrade.odoo.com/database/v1/request_sftp_access"
|
||||
SSH_KEYS=/path/to/your/authorized_keys
|
||||
KEY="Aw7pItGVKFuZ_FOR3U8VFQ=="
|
||||
REQUEST_ID="10534"
|
||||
URL_PARAMS="key=${KEY}&request=${REQUEST_ID}"
|
||||
|
||||
curl -sS "${REQUEST_SFTP_ACCESS_URL}?${URL_PARAMS}" -F ssh_keys=@${SSH_KEYS} > request_sftp_result.json
|
||||
|
||||
# check for failures
|
||||
failures=$(cat request_sftp_result.json | jq -r '.failures[]')
|
||||
if [ "$failures" != "" ]; then
|
||||
echo $failures | jq -r '.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
``failures``
|
||||
''''''''''''
|
||||
|
||||
The list of errors. See :ref:`failures <upgrade-api-json-failure>` for an
|
||||
explanation about the JSON dictionary returned in case of failure.
|
||||
|
||||
``request``
|
||||
'''''''''''
|
||||
|
||||
If the call is successful, the value associated to the *request* key
|
||||
will be a dictionary containing your SFTP connection parameters:
|
||||
|
||||
* ``hostname``: the host address to connect to
|
||||
* ``sftp_port``: the port to connect to
|
||||
* ``sftp_user``: the SFTP user to use for connecting
|
||||
* ``shared_file``: the filename you need to use (identical to the ``filename`` value you have used when creating the request in the :ref:`create method <upgrade-api-create-method>`.)
|
||||
* ``request_id``: the related upgrade request id (only informative, ,not required for the connection)
|
||||
* ``sample_command``: a sample command using the 'sftp' client
|
||||
|
||||
You should normally be able to connect using the sample command as is.
|
||||
|
||||
You will only have access to the ``shared_file``. No other files will be
|
||||
accessible and you will not be able to create new files in your shared
|
||||
environment on the SFTP server.
|
||||
|
||||
Using the 'sftp' client
|
||||
+++++++++++++++++++++++
|
||||
|
||||
Once you have successfully connected using your SFTP client, you can upload
|
||||
your database dump. Here is a sample session using the 'sftp' client:
|
||||
|
||||
::
|
||||
|
||||
$ sftp -P 2200 user_10534@upgrade.odoo.com
|
||||
Connected to upgrade.odoo.com.
|
||||
sftp> put /path/to/openchs.70.cdump openchs.70.cdump
|
||||
Uploading /path/to/openchs.70.cdump to /openchs.70.cdump
|
||||
sftp> ls -l openchs.70.cdump
|
||||
-rw-rw-rw- 0 0 0 849920 Aug 30 15:58 openchs.70.cdump
|
||||
|
||||
If your connection is interrupted, you can continue your file transfer using
|
||||
the ``-a`` command line switch:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
sftp> put -a /path/to/openchs.70.cdump openchs.70.cdump
|
||||
Resuming upload of /path/to/openchs.70.cdump to /openchs.70.cdump
|
||||
|
||||
If you don't want to manually type the command and need to automate your
|
||||
database upgrade using a script, you can use a batch file or pipe your commands to 'sftp':
|
||||
|
||||
::
|
||||
|
||||
echo "put /path/to/openchs.70.cdump openchs.70.cdump" | sftp -b - -P 2200 user_10534@upgrade.odoo.com
|
||||
|
||||
The ``-b`` parameter takes a filename. If the filename is ``-``, it reads the commands from standard input.
|
||||
|
||||
|
||||
.. _upgrade-api-process-method:
|
||||
|
||||
Asking to process your request
|
||||
==============================
|
||||
|
||||
This action ask the Upgrade Platform to process your database dump.
|
||||
|
||||
The ``process`` method
|
||||
----------------------
|
||||
|
||||
.. py:function:: https://upgrade.odoo.com/database/v1/process
|
||||
|
||||
Process a database dump
|
||||
|
||||
:param str key: (required) your private key
|
||||
:param str request: (required) your request id
|
||||
:return: request result
|
||||
:rtype: JSON dictionary
|
||||
|
||||
The request id and the private key are obtained using the :ref:`create method
|
||||
<upgrade-api-create-method>`
|
||||
|
||||
The result is a JSON dictionary containing the list of ``failures``, which
|
||||
should be empty if everything went fine.
|
||||
|
||||
.. rst-class:: setup doc-aside
|
||||
|
||||
.. switcher::
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import requests
|
||||
|
||||
PROCESS_URL = "https://upgrade.odoo.com/database/v1/process"
|
||||
|
||||
fields = dict([
|
||||
('request', '10534'),
|
||||
('key', 'Aw7pItGVKFuZ_FOR3U8VFQ=='),
|
||||
])
|
||||
|
||||
r = requests.get(PROCESS_URL, data=fields)
|
||||
print(r.text)
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
PROCESS_URL="https://upgrade.odoo.com/database/v1/process"
|
||||
KEY="Aw7pItGVKFuZ_FOR3U8VFQ=="
|
||||
REQUEST_ID="10534"
|
||||
URL_PARAMS="key=${KEY}&request=${REQUEST_ID}"
|
||||
curl -sS "${PROCESS_URL}?${URL_PARAMS}"
|
||||
|
||||
.. _upgrade-api-skip-tests:
|
||||
|
||||
|
||||
Asking to skip the tests
|
||||
=========================
|
||||
|
||||
This action asks the Upgrade Platform to skip the tests for your request.
|
||||
If you don't want Odoo to test and validate the migration, you can bypass the testing stage and directly get the migrated dump.
|
||||
|
||||
The ``skip_test`` method
|
||||
------------------------
|
||||
|
||||
.. py:function:: https://upgrade.odoo.com/database/v1/skip_test
|
||||
|
||||
Skip the tests, deliver the upgraded dump, and set the state to 'delivered'
|
||||
|
||||
:param str key: (required) your private key
|
||||
:param str request: (required) your request id
|
||||
:return: request result
|
||||
:rtype: JSON dictionary
|
||||
|
||||
The request id and the private key are obtained using the :ref:`create method
|
||||
<upgrade-api-create-method>`
|
||||
|
||||
The result is a JSON dictionary containing the list of ``failures``, which
|
||||
should be empty if everything went fine.
|
||||
|
||||
.. rst-class:: setup doc-aside
|
||||
|
||||
.. switcher::
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import requests
|
||||
|
||||
PROCESS_URL = "https://upgrade.odoo.com/database/v1/skip_test"
|
||||
|
||||
fields = dict([
|
||||
('request', '10534'),
|
||||
('key', 'Aw7pItGVKFuZ_FOR3U8VFQ=='),
|
||||
])
|
||||
|
||||
r = requests.get(PROCESS_URL, data=fields)
|
||||
print(r.text)
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
PROCESS_URL="https://upgrade.odoo.com/database/v1/skip_test"
|
||||
KEY="Aw7pItGVKFuZ_FOR3U8VFQ=="
|
||||
REQUEST_ID="10534"
|
||||
URL_PARAMS="key=${KEY}&request=${REQUEST_ID}"
|
||||
curl -sS "${PROCESS_URL}?${URL_PARAMS}"
|
||||
|
||||
.. _upgrade-api-status-method:
|
||||
|
||||
Obtaining your request status
|
||||
=============================
|
||||
|
||||
This action ask the status of your database upgrade request.
|
||||
|
||||
The ``status`` method
|
||||
---------------------
|
||||
|
||||
.. py:function:: https://upgrade.odoo.com/database/v1/status
|
||||
|
||||
Ask the status of a database upgrade request
|
||||
|
||||
:param str key: (required) your private key
|
||||
:param str request: (required) your request id
|
||||
:return: request result
|
||||
:rtype: JSON dictionary
|
||||
|
||||
The request id and the private key are obtained using the :ref:`create method
|
||||
<upgrade-api-create-method>`
|
||||
|
||||
The result is a JSON dictionary containing various information about your
|
||||
database upgrade request.
|
||||
|
||||
.. rst-class:: setup doc-aside
|
||||
|
||||
.. switcher::
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import requests
|
||||
|
||||
PROCESS_URL = "https://upgrade.odoo.com/database/v1/status"
|
||||
|
||||
fields = dict([
|
||||
('request', '10534'),
|
||||
('key', 'Aw7pItGVKFuZ_FOR3U8VFQ=='),
|
||||
])
|
||||
|
||||
r = requests.get(PROCESS_URL, data=fields)
|
||||
print(r.text)
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
STATUS_URL="https://upgrade.odoo.com/database/v1/status"
|
||||
KEY="Aw7pItGVKFuZ_FOR3U8VFQ=="
|
||||
REQUEST_ID="10534"
|
||||
URL_PARAMS="key=${KEY}&request=${REQUEST_ID}"
|
||||
curl -sS "${STATUS_URL}?${URL_PARAMS}"
|
||||
|
||||
Sample output
|
||||
-------------
|
||||
|
||||
The ``request`` key contains various useful information about your request:
|
||||
|
||||
``id``
|
||||
the request id
|
||||
``key``
|
||||
your private key
|
||||
``email``
|
||||
the email address you supplied when creating the request
|
||||
``target``
|
||||
the target Odoo version you supplied when creating the request
|
||||
``aim``
|
||||
the purpose (test, production) of your database upgrade request you supplied when creating the request
|
||||
``filename``
|
||||
the filename you supplied when creating the request
|
||||
``timezone``
|
||||
the timezone you supplied when creating the request
|
||||
``state``
|
||||
the state of your request
|
||||
``issue_stage``
|
||||
the stage of the issue we have create on Odoo main server
|
||||
``issue``
|
||||
the id of the issue we have create on Odoo main server
|
||||
``status_url``
|
||||
the URL to access your database upgrade request html page
|
||||
``notes_url``
|
||||
the URL to get the notes about your database upgrade
|
||||
``original_sql_url``
|
||||
the URL used to get your uploaded (not upgraded) database as an SQL stream
|
||||
``original_dump_url``
|
||||
the URL used to get your uploaded (not upgraded) database as an archive file
|
||||
``upgraded_sql_url``
|
||||
the URL used to get your upgraded database as an SQL stream
|
||||
``upgraded_dump_url``
|
||||
the URL used to get your upgraded database as an archive file
|
||||
``modules_url``
|
||||
the URL used to get your custom modules
|
||||
``filesize``
|
||||
the size of your uploaded database file
|
||||
``database_uuid``
|
||||
the Unique ID of your database
|
||||
``created_at``
|
||||
the date when you created the request
|
||||
``estimated_time``
|
||||
an estimation of the time it takes to upgrade your database
|
||||
``processed_at``
|
||||
time when your database upgrade was started
|
||||
``elapsed``
|
||||
the time it takes to upgrade your database
|
||||
``filestore``
|
||||
your attachments were converted to the filestore
|
||||
``customer_message``
|
||||
an important message related to your request
|
||||
``database_version``
|
||||
the guessed Odoo version of your uploaded (not upgraded) database
|
||||
``postgresql``
|
||||
the guessed Postgresql version of your uploaded (not upgraded) database
|
||||
``compressions``
|
||||
the compression methods used by your uploaded database
|
||||
|
||||
.. rst-class:: setup doc-aside
|
||||
|
||||
.. switcher::
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"failures": [],
|
||||
"request": {
|
||||
"id": 10534,
|
||||
"key": "Aw7pItGVKFuZ_FOR3U8VFQ==",
|
||||
"email": "john.doe@example.com",
|
||||
"target": "12.0",
|
||||
"aim": "test",
|
||||
"filename": "db_name.dump",
|
||||
"timezone": null,
|
||||
"state": "draft",
|
||||
"issue_stage": "new",
|
||||
"issue": 648398,
|
||||
"status_url": "https://upgrade.odoo.com/database/eu1/10534/Aw7pItGVKFuZ_FOR3U8VFQ==/status",
|
||||
"notes_url": "https://upgrade.odoo.com/database/eu1/10534/Aw7pItGVKFuZ_FOR3U8VFQ==/upgraded/notes",
|
||||
"original_sql_url": "https://upgrade.odoo.com/database/eu1/10534/Aw7pItGVKFuZ_FOR3U8VFQ==/original/sql",
|
||||
"original_dump_url": "https://upgrade.odoo.com/database/eu1/10534/Aw7pItGVKFuZ_FOR3U8VFQ==/original/archive",
|
||||
"upgraded_sql_url": "https://upgrade.odoo.com/database/eu1/10534/Aw7pItGVKFuZ_FOR3U8VFQ==/upgraded/sql",
|
||||
"upgraded_dump_url": "https://upgrade.odoo.com/database/eu1/10534/Aw7pItGVKFuZ_FOR3U8VFQ==/upgraded/archive",
|
||||
"modules_url": "https://upgrade.odoo.com/database/eu1/10534/Aw7pItGVKFuZ_FOR3U8VFQ==/modules/archive",
|
||||
"filesize": "912.99 Kb",
|
||||
"database_uuid": null,
|
||||
"created_at": "2018-09-09 07:13:49",
|
||||
"estimated_time": null,
|
||||
"processed_at": null,
|
||||
"elapsed": "00:00",
|
||||
"filestore": false,
|
||||
"customer_message": null,
|
||||
"database_version": null,
|
||||
"postgresql": "9.4",
|
||||
"compressions": [
|
||||
"pgdmp_custom",
|
||||
"sql"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
.. _upgrade-api-download-method:
|
||||
|
||||
Downloading your database dump
|
||||
==============================
|
||||
|
||||
Beside downloading your migrated database using the URL provided by the
|
||||
:ref:`status method <upgrade-api-status-method>`, you can also use the SFTP
|
||||
protocol as described in the :ref:`request_sftp_access method
|
||||
<upgrade-api-request-sftp-access-method>`
|
||||
|
||||
The difference is that you'll only be able to download the migrated database. No
|
||||
uploading will be possible.
|
||||
|
||||
Your database upgrade request should be in the ``done`` state.
|
||||
|
||||
Once you have successfully connected using your SFTP client, you can download
|
||||
your database dump. Here is a sample session using the 'sftp' client:
|
||||
|
||||
::
|
||||
|
||||
$ sftp -P 2200 user_10534@upgrade.odoo.com
|
||||
Connected to upgrade.odoo.com.
|
||||
sftp> get upgraded_openchs.70.cdump /path/to/upgraded_openchs.70.cdump
|
||||
Downloading /upgraded_openchs.70.cdump to /path/to/upgraded_openchs.70.cdump
|
||||
|
||||