IAP doc: tutorial (#20317)
* [ADD] doc: iap tutorial Should be easier to approach than the raw APIDoc. contains: - overview of the flow - tutorial (coalroller) with guidelines - technical description of objects/helpers
@@ -2,3 +2,4 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from .models.iap import jsonrpc, charge, InsufficientCreditError
|
||||
|
||||
@@ -82,7 +82,14 @@ def charge(env, key, account_token, credit, description=None, credit_template=No
|
||||
:param str key: service identifier
|
||||
:param str account_token: user identifier
|
||||
:param int credit: cost of the body's operation
|
||||
:param str description:
|
||||
:param description: a description of the purpose of the charge,
|
||||
the user will be able to see it in their
|
||||
dashboard
|
||||
:type description: str
|
||||
:param credit_template: a QWeb template to render and show to the
|
||||
user if their account does not have enough
|
||||
credits for the requested operation
|
||||
:type credit_template: str
|
||||
"""
|
||||
endpoint = get_endpoint(env)
|
||||
params = {
|
||||
|
||||
@@ -381,6 +381,10 @@ class BootstrapTranslator(nodes.NodeVisitor, object):
|
||||
"Unsupported alignment value \"%s\"" % node['align'],
|
||||
location=doc
|
||||
)
|
||||
elif 'align' in node.parent and node.parent['align'] == 'center':
|
||||
# figure > image
|
||||
attrs['class'] += ' center-block'
|
||||
|
||||
# todo: explicit width/height/scale?
|
||||
self.body.append(self.starttag(node, 'img', **attrs))
|
||||
def depart_image(self, node): pass
|
||||
|
||||
@@ -22,4 +22,3 @@ diff --git a/academy/security/ir.model.access.csv b/academy/security/ir.model.ac
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
|
||||
+access_academy_courses,access_academy_courses,model_academy_courses,,1,0,0,0
|
||||
\ No newline at end of file
|
||||
|
||||
@@ -9,7 +9,7 @@ diff --git a/academy/__manifest__.py b/academy/__manifest__.py
|
||||
'security/ir.model.access.csv',
|
||||
'templates.xml',
|
||||
'views.xml',
|
||||
+ 'data.xml',
|
||||
+ 'data.xml',
|
||||
],
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
@@ -79,11 +79,8 @@ diff --git a/academy/security/ir.model.access.csv b/academy/security/ir.model.ac
|
||||
+++ b/academy/security/ir.model.access.csv
|
||||
@@ -1,3 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
-access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
|
||||
access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
|
||||
-access_academy_courses,access_academy_courses,model_academy_courses,,1,0,0,0
|
||||
\ No newline at end of file
|
||||
+access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
|
||||
\ No newline at end of file
|
||||
diff --git a/academy/views.xml b/academy/views.xml
|
||||
--- a/academy/views.xml
|
||||
+++ b/academy/views.xml
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,10 +1,41 @@
|
||||
:types: api
|
||||
|
||||
|
||||
:code-column:
|
||||
|
||||
.. _webservices/iap:
|
||||
|
||||
.. using sphinx-patchqueue:
|
||||
* the "queue" directive selects a *series* file which lists the patches in
|
||||
the patch queue, in order of application (from top to bottom). The
|
||||
corresponding patch files should be in the same directory.
|
||||
* the "patch" directive steps to the next patch in the queue, applies it
|
||||
and reifies its content (depending on the extension's configuration, by
|
||||
default it shows the changed files post-diff application, slicing to
|
||||
only display sections affecte by the file)
|
||||
|
||||
.. while it's technically possible to apply and update patches by hand, it's
|
||||
finnicky work and easy to break.
|
||||
|
||||
.. the easiest way is to install quilt (http://savannah.nongnu.org/projects/quilt),
|
||||
go to the directory where you want to reify the addon, then create a
|
||||
"patches" symlink to the patches directory (the iap/ folder next to this
|
||||
file) or set QUILT_PATCHES to that folder.
|
||||
|
||||
.. at that point you have a "primed" queue with no patch applied, and you can
|
||||
move within the queue with "quilt push" and "quilt pop".
|
||||
* "quilt new" creates a new empty patch at the top of the stack
|
||||
* "quilt add" tells quilt to start tracking the file, quilt add *works per
|
||||
patch*, it must be called *every time you want to alter a file within a
|
||||
patch*: quilt is not a full VCS (since it's intended to sit on top of
|
||||
an existing source) and does not do permanent tracking of files
|
||||
* "quilt edit" is a shorthand to "quilt add" then open the file in your
|
||||
editor, I suggest you use that rather than open the edited module
|
||||
normally, it avoids forgetting to "quilt add" before doing your
|
||||
modifications (at which point your modifications are untracked,
|
||||
invisible and depending on your editor may be a PITA to revert & redo)
|
||||
* "quilt refresh" updates the current patch to include pending changes
|
||||
|
||||
.. see "man quilt" for the rest of the subcommands. FWIW I could not get
|
||||
"quilt setup" to do anything useful.
|
||||
|
||||
================
|
||||
In-App Purchases
|
||||
================
|
||||
@@ -13,11 +44,12 @@ IAP allow providers of ongoing services through Odoo apps to be compensated
|
||||
for ongoing service use rather than — and possibly instead of — a sole initial
|
||||
purchase.
|
||||
|
||||
In that context, Odoo acts mostly as a *broker* between the service user
|
||||
(client) and the service provider (Odoo App developer):
|
||||
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
|
||||
* service providers draw tokens from the user's Odoo account when service
|
||||
is requested
|
||||
|
||||
.. attention::
|
||||
|
||||
@@ -25,15 +57,256 @@ In that context, Odoo acts mostly as a *broker* between the service user
|
||||
which can be done either via direct JSON-RPC2_ or if you are using Odoo
|
||||
using the convenience helpers it provides.
|
||||
|
||||
.. image:: flow.png
|
||||
Overview
|
||||
========
|
||||
|
||||
.. figure:: images/players.png
|
||||
:align: center
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
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
|
||||
|
||||
.. note:: in the following explanations we will ignore the External Service,
|
||||
they're just a detail of the service you provide
|
||||
|
||||
.. figure:: images/normal.png
|
||||
:align: center
|
||||
|
||||
A "normal" service flow
|
||||
|
||||
If everything goes well, the normal flow is:
|
||||
|
||||
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
|
||||
|
||||
If the Client's account lacks credits for the service, however
|
||||
|
||||
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)
|
||||
* act as intermediary to an other service provider (e.g. bridge to an MMS
|
||||
gateway)
|
||||
|
||||
Register the service on Odoo
|
||||
----------------------------
|
||||
|
||||
.. queue:: iap_service/series
|
||||
|
||||
.. 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, http://iap-sandbox.odoo.com for testing, the endpoints are
|
||||
*independent* and *not synchronized*).
|
||||
|
||||
Log in then go to :menuselection:`My Account --> Your InApp Services`, click
|
||||
Create and provide the name of your service.
|
||||
|
||||
|
||||
The now created service has *two* important fields:
|
||||
|
||||
* :samp:`name` - :class:`ServiceName`: this will identify your service in the
|
||||
client's :ref:`app <iap-odoo-app>` communicates directly with IAP.
|
||||
* :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.
|
||||
|
||||
.. warning::
|
||||
The :class:`ServiceName` is unique and should usually match the name of your
|
||||
Odoo App.
|
||||
|
||||
.. danger::
|
||||
Your :class:`ServiceKey` *is a secret*, leaking your service key
|
||||
allows other application developers to draw credits bought for
|
||||
your service(s).
|
||||
|
||||
.. image:: images/service_select.png
|
||||
:align: center
|
||||
|
||||
.. image:: images/service_create.png
|
||||
:align: center
|
||||
|
||||
.. image:: images/service_packs.png
|
||||
:align: center
|
||||
|
||||
You can then create *credit packs* which clients can purchase in order to
|
||||
use your service.
|
||||
|
||||
.. _iap-odoo-app:
|
||||
|
||||
Odoo App
|
||||
--------
|
||||
|
||||
.. queue:: iap/series
|
||||
|
||||
.. 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* services you will 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'll 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
|
||||
|
||||
.. patch::
|
||||
|
||||
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.
|
||||
|
||||
.. patch::
|
||||
|
||||
.. 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.jsonrpc` helper to call a JSON-RPC2_ endpoint on an
|
||||
other Odoo instance and transparently re-raise relevant Odoo exceptions
|
||||
(:class:`~odoo.addons.iap.models.iap.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, this 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.
|
||||
|
||||
.. patch::
|
||||
|
||||
.. note::
|
||||
|
||||
``iap`` automatically handles
|
||||
:class:`~odoo.addons.iap.models.iap.InsufficientCreditError` coming from the action
|
||||
and prompts the user to add credits to their account.
|
||||
|
||||
:func:`~odoo.addons.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.jsonrpc` you *must* be
|
||||
careful to re-raise
|
||||
:class:`~odoo.addons.iap.models.iap.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
|
||||
-------
|
||||
|
||||
.. queue:: iap_service/series
|
||||
|
||||
Though that is not *required*, since ``iap`` provides both a client helper
|
||||
for JSON-RPC2_ calls (:func:`~odoo.addons.iap.jsonrpc`) and a service helper
|
||||
for transactions (:class:`~odoo.addons.iap.models.iap.charge`) we will also be
|
||||
implementing the service side as an Odoo module:
|
||||
|
||||
.. patch::
|
||||
|
||||
Since the query from the client comes as JSON-RPC2_ we will need the
|
||||
corresponding controller which can call :class:`~odoo.addons.iap.models.iap.charge` and
|
||||
perform the service within:
|
||||
|
||||
.. patch::
|
||||
|
||||
.. 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.models.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 succesfully, capture (confirm) the
|
||||
transaction
|
||||
4. 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.models.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.models.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
|
||||
|
||||
.. patch::
|
||||
|
||||
|
||||
|
||||
.. 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
|
||||
@@ -41,39 +314,6 @@ JSON-RPC2_ Transaction API
|
||||
* Exceptions are returned as JSON-RPC2_ errors, the formal exception name is
|
||||
available on ``data.name`` for programmatic manipulation.
|
||||
|
||||
.. class:: ServiceKey
|
||||
|
||||
Identifier generated for the provider's service. Each key (and service)
|
||||
matches a token of a fixed value, as generated by the service provide.
|
||||
|
||||
Multiple types of tokens correspond to multiple services e.g. SMS and MMS
|
||||
could either be the same service (with an MMS being "worth" multiple SMS)
|
||||
or could be separate services at separate price points.
|
||||
|
||||
.. danger:: your service key *is a secret*, leaking your service key
|
||||
allows other application developers to draw credits bought for
|
||||
your service(s)
|
||||
|
||||
.. class:: UserToken
|
||||
|
||||
Identifier for a user account.
|
||||
|
||||
.. class:: TransactionToken
|
||||
|
||||
Transaction identifier, returned by the authorization process and consumed
|
||||
by either capturing or cancelling the transaction
|
||||
|
||||
.. exception:: odoo.addons.iap.models.iap.NoCreditError
|
||||
|
||||
Raised during transaction authorization if the credits requested are not
|
||||
currently available on the account (either not enough credits or too many
|
||||
pending transactions/existing holds).
|
||||
|
||||
.. exception:: odoo.addons.iap.models.iap.BadAuthError
|
||||
|
||||
Raised by any operation to which a service token is required, if the
|
||||
service token is invalid.
|
||||
|
||||
Authorize
|
||||
---------
|
||||
|
||||
@@ -94,12 +334,10 @@ Authorize
|
||||
:param str description: optional, helps users identify the reason for
|
||||
charges on their accounts.
|
||||
:returns: :class:`TransactionToken` if the authorization succeeded.
|
||||
:raises: :class:`~odoo.addons.iap.models.iap.BadAuthError` if the service token is invalid
|
||||
:raises: :class:`~odoo.addons.iap.models.iap.NoCreditError` if the account does
|
||||
:raises: :class:`~odoo.exceptions.AccessError` if the service token is invalid
|
||||
:raises: :class:`~odoo.addons.iap.models.iap.InsufficientCreditError` if the account does
|
||||
:raises: ``TypeError`` if the ``credit`` value is not an integer
|
||||
|
||||
.. rst-class:: doc-aside
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
r = requests.post(ODOO + '/iap/1/authorize', json={
|
||||
@@ -132,9 +370,7 @@ Capture
|
||||
|
||||
:param TransactionToken token:
|
||||
:param ServiceKey key:
|
||||
:raises: :class:`~odoo.addons.iap.models.iap.BadAuthError`
|
||||
|
||||
.. rst-class:: doc-aside
|
||||
:raises: :class:`~odoo.exceptions.AccessError`
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -164,9 +400,7 @@ Cancel
|
||||
|
||||
:param TransactionToken token:
|
||||
:param ServiceKey key:
|
||||
:raises: :class:`~odoo.addons.iap.models.iap.BadAuthError`
|
||||
|
||||
.. rst-class:: doc-aside
|
||||
:raises: :class:`~odoo.exceptions.AccessError`
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -183,6 +417,56 @@ Cancel
|
||||
# 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 e.g. SMS and MMS
|
||||
could either be the same service (with an MMS being "worth" multiple SMS)
|
||||
or could be separate services at separate price points.
|
||||
|
||||
.. danger:: your service key *is a secret*, leaking your service key
|
||||
allows other application developers to draw credits bought for
|
||||
your service(s).
|
||||
|
||||
.. class:: UserToken
|
||||
|
||||
Identifier for a user account.
|
||||
|
||||
.. class:: TransactionToken
|
||||
|
||||
Transaction identifier, returned by the authorization process and consumed
|
||||
by either capturing or cancelling the transaction
|
||||
|
||||
.. exception:: odoo.addons.iap.models.iap.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
|
||||
|
||||
Raised by:
|
||||
|
||||
* any operation to which a service token is required, if the service token is invalid.
|
||||
* any failure in an inter-server call. (typically, in :func:`~odoo.addons.iap.jsonrpc`)
|
||||
|
||||
.. exception:: odoo.exceptions.UserError
|
||||
|
||||
Raised by any unexpeted behaviour at the discretion of the App developer (*you*).
|
||||
|
||||
Odoo Helpers
|
||||
============
|
||||
|
||||
@@ -192,7 +476,7 @@ module provides a few helpers to make IAP flow even simpler:
|
||||
Charging
|
||||
--------
|
||||
|
||||
.. class:: odoo.addons.iap.models.iap.charge(env, key, account_token, credit[, description])
|
||||
.. class:: odoo.addons.iap.models.iap.charge(env, key, account_token, credit[, description, credit_template])
|
||||
|
||||
A *context manager* for authorizing and automatically capturing or
|
||||
cancelling transactions for use in the backend/proxy.
|
||||
@@ -211,8 +495,6 @@ Charging
|
||||
:param int credit:
|
||||
:param str description:
|
||||
|
||||
.. rst-class:: doc-aside
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@route('/deathstar/superlaser', type='json')
|
||||
@@ -231,5 +513,5 @@ Charging
|
||||
('name', '=', 'Alderaan'),
|
||||
]).unlink()
|
||||
|
||||
|
||||
.. _JSON-RPC2: http://www.jsonrpc.org/specification
|
||||
.. _Odoo App: https://www.odoo.com/apps
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
Index: coalroller/coalroller/__manifest__.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller/coalroller/__manifest__.py
|
||||
@@ -0,0 +1,5 @@
|
||||
+{
|
||||
+ 'name': "Coal Roller",
|
||||
+ 'category': 'Tools',
|
||||
+ 'depends': ['iap'],
|
||||
+}
|
||||
Index: coalroller/coalroller/__init__.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller/coalroller/__init__.py
|
||||
@@ -0,0 +1 @@
|
||||
+# -*- coding: utf-8 -*-
|
||||
@@ -0,0 +1,34 @@
|
||||
Index: coalroller/coalroller/__manifest__.py
|
||||
===================================================================
|
||||
--- coalroller.orig/coalroller/__manifest__.py
|
||||
+++ coalroller/coalroller/__manifest__.py
|
||||
@@ -2,4 +2,7 @@
|
||||
'name': "Coal Roller",
|
||||
'category': 'Tools',
|
||||
'depends': ['iap'],
|
||||
+ 'data': [
|
||||
+ 'views/views.xml',
|
||||
+ ],
|
||||
}
|
||||
Index: coalroller/coalroller/views/views.xml
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller/coalroller/views/views.xml
|
||||
@@ -0,0 +1,17 @@
|
||||
+<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>
|
||||
@@ -0,0 +1,40 @@
|
||||
Index: coalroller/coalroller/__init__.py
|
||||
===================================================================
|
||||
--- coalroller.orig/coalroller/__init__.py
|
||||
+++ coalroller/coalroller/__init__.py
|
||||
@@ -1 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
+from . import models
|
||||
Index: coalroller/coalroller/models/__init__.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller/coalroller/models/__init__.py
|
||||
@@ -0,0 +1 @@
|
||||
+from . import res_partner
|
||||
Index: coalroller/coalroller/models/res_partner.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller/coalroller/models/res_partner.py
|
||||
@@ -0,0 +1,22 @@
|
||||
+# -*- coding: utf-8 -*-
|
||||
+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'
|
||||
+ @api.multi
|
||||
+ 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
|
||||
@@ -0,0 +1,3 @@
|
||||
01-init
|
||||
02-button
|
||||
03-callback
|
||||
@@ -0,0 +1,16 @@
|
||||
Index: coalroller_service/coalroller_service/__init__.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller_service/coalroller_service/__init__.py
|
||||
@@ -0,0 +1 @@
|
||||
+# -*- encoding: utf-8 -*-
|
||||
Index: coalroller_service/coalroller_service/__manifest__.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller_service/coalroller_service/__manifest__.py
|
||||
@@ -0,0 +1,5 @@
|
||||
+{
|
||||
+ 'name': "Coal Roller Service",
|
||||
+ 'category': 'Tools',
|
||||
+ 'depends': ['iap'],
|
||||
+}
|
||||
@@ -0,0 +1,48 @@
|
||||
Index: coalroller_service/coalroller_service/controllers/main.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller_service/coalroller_service/controllers/main.py
|
||||
@@ -0,0 +1,29 @@
|
||||
+import time
|
||||
+
|
||||
+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 = self.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
|
||||
Index: coalroller_service/coalroller_service/controllers/__init__.py
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller_service/coalroller_service/controllers/__init__.py
|
||||
@@ -0,0 +1,2 @@
|
||||
+# -*- encoding: utf-8 -*-
|
||||
+from . import main
|
||||
Index: coalroller_service/coalroller_service/__init__.py
|
||||
===================================================================
|
||||
--- coalroller_service.orig/coalroller_service/__init__.py
|
||||
+++ coalroller_service/coalroller_service/__init__.py
|
||||
@@ -1 +1,2 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
+from . import controllers
|
||||
@@ -0,0 +1,56 @@
|
||||
Index: coalroller_service/coalroller_service/controllers/main.py
|
||||
===================================================================
|
||||
--- coalroller_service.orig/coalroller_service/controllers/main.py
|
||||
+++ coalroller_service/coalroller_service/controllers/main.py
|
||||
@@ -10,12 +10,14 @@ class CoalBurnerController(http.Controll
|
||||
def roll(self, account_token):
|
||||
# the service key *is a secret*, it should not be committed in
|
||||
# the source
|
||||
- service_key = self.env['ir.config_parameter'].sudo().get_param('coalroller.service_key')
|
||||
+ 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):
|
||||
+ 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)
|
||||
Index: coalroller_service/coalroller_service/views/no-credit.xml
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ coalroller_service/coalroller_service/views/no-credit.xml
|
||||
@@ -0,0 +1,18 @@
|
||||
+<odoo>
|
||||
+ <template id="no_credit" name="No credit warning">
|
||||
+ <div>
|
||||
+ <div class="container-fluid">
|
||||
+ <div class="row">
|
||||
+ <div class="col-sm-7 col-md-offset-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>
|
||||
Index: coalroller_service/coalroller_service/__manifest__.py
|
||||
===================================================================
|
||||
--- coalroller_service.orig/coalroller_service/__manifest__.py
|
||||
+++ coalroller_service/coalroller_service/__manifest__.py
|
||||
@@ -2,4 +2,7 @@
|
||||
'name': "Coal Roller Service",
|
||||
'category': 'Tools',
|
||||
'depends': ['iap'],
|
||||
+ 'data': [
|
||||
+ 'views/no-credit.xml',
|
||||
+ ],
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
01-init
|
||||
02-tnx
|
||||
03-template
|
||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 41 KiB |