[ADD] support for fetching views's fields straight from demo
* resolves action by external id * fetches relevant view * prints table of all fields with an @help in view order
This commit is contained in:
parent
98e8930e02
commit
f8d202061a
163
_extensions/demo_link.py
Normal file
163
_extensions/demo_link.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import Queue
|
||||||
|
import collections
|
||||||
|
import threading
|
||||||
|
import xmlrpclib
|
||||||
|
|
||||||
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
|
from docutils import nodes, utils
|
||||||
|
from docutils.parsers.rst import Directive, directives
|
||||||
|
from sphinx.domains import Domain
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
app.add_domain(OdooDemoDomain)
|
||||||
|
|
||||||
|
class Fields(Directive):
|
||||||
|
"""Fetches and lists the fields linked to a specific action.
|
||||||
|
|
||||||
|
Required argument: external ID of the action
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
view
|
||||||
|
defaults to "form"
|
||||||
|
fields
|
||||||
|
comma-separated whitelist of fields. By default, lists all
|
||||||
|
fields returned by fields_view_get
|
||||||
|
"""
|
||||||
|
required_arguments = 1
|
||||||
|
option_spec = {
|
||||||
|
'view': directives.unchanged,
|
||||||
|
'fields': directives.unchanged,
|
||||||
|
}
|
||||||
|
def __init__(self, name, arguments, options, content, lineno,
|
||||||
|
content_offset, block_text, state, state_machine):
|
||||||
|
super(Fields, self).__init__(
|
||||||
|
name, arguments, options, content, lineno,
|
||||||
|
content_offset, block_text, state, state_machine)
|
||||||
|
xid = arguments[0]
|
||||||
|
self.future_fields = self._get_fields(xid, options.get('view') or 'form')
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
fields = self.future_fields.get(timeout=30)
|
||||||
|
except Queue.Empty:
|
||||||
|
return [self.state_machine.reporter.error(
|
||||||
|
"Timed out while fetching fields related to action [%s]" % self.arguments[0]
|
||||||
|
)]
|
||||||
|
if fields is None:
|
||||||
|
return [self.state_machine.reporter.warning(
|
||||||
|
"Could not find any field related to the action [%s]" % self.arguments[0]
|
||||||
|
)]
|
||||||
|
whitelist = set(filter(None, self.options.get('fields', '').split(',')))
|
||||||
|
return [nodes.field_list('', *(
|
||||||
|
nodes.field('',
|
||||||
|
nodes.field_name(text=v['string'] or k),
|
||||||
|
nodes.field_body('',
|
||||||
|
# keep help formatting around (e.g. newlines for lists)
|
||||||
|
nodes.line_block('', *(
|
||||||
|
nodes.line(text=line)
|
||||||
|
for line in v['help'].split('\n')
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for k, v in fields.iteritems()
|
||||||
|
# if there's a whitelist, only display whitelisted fields
|
||||||
|
if not whitelist or k in whitelist
|
||||||
|
# only display if there's a help text
|
||||||
|
if v.get('help')
|
||||||
|
))]
|
||||||
|
|
||||||
|
def _get_fields(self, xid, view='form'):
|
||||||
|
q = Queue.Queue(1)
|
||||||
|
_submit(q, xid, view)
|
||||||
|
return q
|
||||||
|
|
||||||
|
class Action(Directive):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class OdooDemoDomain(Domain):
|
||||||
|
name = 'demo'
|
||||||
|
label = 'Odoo Demo'
|
||||||
|
directives = {
|
||||||
|
'fields': Fields,
|
||||||
|
'action': Action,
|
||||||
|
}
|
||||||
|
|
||||||
|
FETCH_THREADS = 4
|
||||||
|
launcher_lock = threading.Lock()
|
||||||
|
launcher = None
|
||||||
|
work_queue = Queue.Queue()
|
||||||
|
Task = collections.namedtuple('Task', 'result xid view')
|
||||||
|
def _submit(result_queue, xid, view='form'):
|
||||||
|
global launcher
|
||||||
|
# enqueue task before checking launcher, that way if the launcher
|
||||||
|
# is already started (likely) a worker can immediately get to work
|
||||||
|
work_queue.put(Task(result_queue, xid, view))
|
||||||
|
|
||||||
|
with launcher_lock:
|
||||||
|
if launcher is None:
|
||||||
|
launcher = threading.Thread(target=_launcher, name="Fetch threads launcher")
|
||||||
|
launcher.daemon = True
|
||||||
|
launcher.start()
|
||||||
|
|
||||||
|
def _launcher():
|
||||||
|
info = xmlrpclib.ServerProxy('https://demo.odoo.com/start').start()
|
||||||
|
url, db, username, password = \
|
||||||
|
info['host'], info['database'], info['user'], info['password']
|
||||||
|
|
||||||
|
uid = xmlrpclib.ServerProxy('{}/xmlrpc/2/common'.format(url))\
|
||||||
|
.authenticate(db, username, password, {})
|
||||||
|
|
||||||
|
for i in range(FETCH_THREADS):
|
||||||
|
# daemon because Launcher is daemon
|
||||||
|
threading.Thread(target=_fetch_fields, kwargs={
|
||||||
|
'db': db,
|
||||||
|
'uid': uid,
|
||||||
|
'password': password,
|
||||||
|
'url': '{}/xmlrpc/2/object'.format(url)
|
||||||
|
}, name="fields_get fetcher thread %d/%d" % (i, FETCH_THREADS)).start()
|
||||||
|
|
||||||
|
def _fetch_fields(url, db, uid, password):
|
||||||
|
server = xmlrpclib.ServerProxy(url)
|
||||||
|
while True:
|
||||||
|
task = work_queue.get()
|
||||||
|
|
||||||
|
# resolve xid
|
||||||
|
model, id_ = server.execute_kw(
|
||||||
|
db, uid, password,
|
||||||
|
'ir.model.data', 'xmlid_to_res_model_res_id', [task.xid])
|
||||||
|
if not id_: # didn't find xid
|
||||||
|
result = None
|
||||||
|
elif model != 'ir.actions.act_window': # we only handle action windows, rest is unknown
|
||||||
|
result = None
|
||||||
|
else:
|
||||||
|
action = server.execute_kw(db, uid, password, model, 'read', [id_, ['res_model', 'views']])
|
||||||
|
view_id = next((id_ for type, id_ in action['views'] if type == task.view), False)
|
||||||
|
fvg = server.execute_kw(
|
||||||
|
db, uid, password,
|
||||||
|
action['res_model'], 'fields_view_get', [], {
|
||||||
|
'view_id': view_id,
|
||||||
|
'view_type': task.view
|
||||||
|
})
|
||||||
|
result = collections.OrderedDict()
|
||||||
|
# reorder fields to be in view order, and add @help from view if any
|
||||||
|
arch = ET.fromstring(fvg['arch'])
|
||||||
|
for node in arch.iter(tag='field'):
|
||||||
|
field = node.get('name')
|
||||||
|
|
||||||
|
result[field] = fvg['fields'][field]
|
||||||
|
# bit trashy but should work well enough to update
|
||||||
|
# @string and @help
|
||||||
|
result[field].update(node.attrib)
|
||||||
|
if node.get('nolabel'):
|
||||||
|
# native @string suppressed, look for <label
|
||||||
|
# for=@name>. invisible means a field could have
|
||||||
|
# multiple <label> but that's basically impossible
|
||||||
|
# to handle so jusr get the first one
|
||||||
|
label = arch.find(".//label[@for='%s']" % field)
|
||||||
|
if label is not None:
|
||||||
|
result[field]['string'] = label.get('string')
|
||||||
|
|
||||||
|
task.result.put(result)
|
||||||
|
work_queue.task_done()
|
@ -59,11 +59,7 @@ Types`
|
|||||||
.. image:: media/image01.png
|
.. image:: media/image01.png
|
||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
**Explanation of the fields**
|
.. demo:fields:: account_asset.action_account_asset_asset_list_normal_sale
|
||||||
|
|
||||||
**[Add here a code that loads automatically all fields of the asset type
|
|
||||||
objects and their tooltips. This will give something similar to the
|
|
||||||
above section, but no need for duplicate content]**
|
|
||||||
|
|
||||||
+--------------------------------------------------------+
|
+--------------------------------------------------------+
|
||||||
| **View Asset Types in the our online demonstration** |
|
| **View Asset Types in the our online demonstration** |
|
||||||
|
Loading…
Reference in New Issue
Block a user