[IMP] web controller helpers
- move helpers on top - move some code from controllers to helpers - deprecate module dependecy processing in standalone client bzr revid: al@openerp.com-20120812214827-m1wrv7y1jvnro9ew
This commit is contained in:
parent
6a78b5dbd9
commit
aa93e7186d
|
@ -31,9 +31,138 @@ from .. import common
|
||||||
openerpweb = common.http
|
openerpweb = common.http
|
||||||
|
|
||||||
#----------------------------------------------------------
|
#----------------------------------------------------------
|
||||||
# OpenERP Web web Controllers
|
# OpenERP Web helpers
|
||||||
#----------------------------------------------------------
|
#----------------------------------------------------------
|
||||||
|
|
||||||
|
def sass2scss(src):
|
||||||
|
# Validated by diff -u of sass2scss against:
|
||||||
|
# sass-convert -F sass -T scss openerp.sass openerp.scss
|
||||||
|
block = []
|
||||||
|
sass = ('', block)
|
||||||
|
reComment = re.compile(r'//.*$')
|
||||||
|
reIndent = re.compile(r'^\s+')
|
||||||
|
reIgnore = re.compile(r'^\s*(//.*)?$')
|
||||||
|
reFixes = { re.compile(r'\(\((.*)\)\)') : r'(\1)', }
|
||||||
|
lastLevel = 0
|
||||||
|
prevBlocks = {}
|
||||||
|
for l in src.split('\n'):
|
||||||
|
l = l.rstrip()
|
||||||
|
if reIgnore.search(l): continue
|
||||||
|
l = reComment.sub('', l)
|
||||||
|
l = l.rstrip()
|
||||||
|
indent = reIndent.match(l)
|
||||||
|
level = indent.end() if indent else 0
|
||||||
|
l = l[level:]
|
||||||
|
if level>lastLevel:
|
||||||
|
prevBlocks[lastLevel] = block
|
||||||
|
newBlock = []
|
||||||
|
block[-1] = (block[-1], newBlock)
|
||||||
|
block = newBlock
|
||||||
|
elif level<lastLevel:
|
||||||
|
block = prevBlocks[level]
|
||||||
|
lastLevel = level
|
||||||
|
if not l: continue
|
||||||
|
# Fixes
|
||||||
|
for ereg, repl in reFixes.items():
|
||||||
|
l = ereg.sub(repl if type(repl)==str else repl(), l)
|
||||||
|
block.append(l)
|
||||||
|
|
||||||
|
def write(sass, level=-1):
|
||||||
|
out = ""
|
||||||
|
indent = ' '*level
|
||||||
|
if type(sass)==tuple:
|
||||||
|
if level>=0:
|
||||||
|
out += indent+sass[0]+" {\n"
|
||||||
|
for e in sass[1]:
|
||||||
|
out += write(e, level+1)
|
||||||
|
if level>=0:
|
||||||
|
out = out.rstrip(" \n")
|
||||||
|
out += ' }\n'
|
||||||
|
if level==0:
|
||||||
|
out += "\n"
|
||||||
|
else:
|
||||||
|
out += indent+sass+";\n"
|
||||||
|
return out
|
||||||
|
return write(sass)
|
||||||
|
|
||||||
|
def db_list(req):
|
||||||
|
proxy = req.session.proxy("db")
|
||||||
|
dbs = proxy.list()
|
||||||
|
h = req.httprequest.environ['HTTP_HOST'].split(':')[0]
|
||||||
|
d = h.split('.')[0]
|
||||||
|
r = req.config.dbfilter.replace('%h', h).replace('%d', d)
|
||||||
|
dbs = [i for i in dbs if re.match(r, i)]
|
||||||
|
return dbs
|
||||||
|
|
||||||
|
def module_topological_sort(modules):
|
||||||
|
""" Return a list of module names sorted so that their dependencies of the
|
||||||
|
modules are listed before the module itself
|
||||||
|
|
||||||
|
modules is a dict of {module_name: dependencies}
|
||||||
|
|
||||||
|
:param modules: modules to sort
|
||||||
|
:type modules: dict
|
||||||
|
:returns: list(str)
|
||||||
|
"""
|
||||||
|
|
||||||
|
dependencies = set(itertools.chain.from_iterable(modules.itervalues()))
|
||||||
|
# incoming edge: dependency on other module (if a depends on b, a has an
|
||||||
|
# incoming edge from b, aka there's an edge from b to a)
|
||||||
|
# outgoing edge: other module depending on this one
|
||||||
|
|
||||||
|
# [Tarjan 1976], http://en.wikipedia.org/wiki/Topological_sorting#Algorithms
|
||||||
|
#L ← Empty list that will contain the sorted nodes
|
||||||
|
L = []
|
||||||
|
#S ← Set of all nodes with no outgoing edges (modules on which no other
|
||||||
|
# module depends)
|
||||||
|
S = set(module for module in modules if module not in dependencies)
|
||||||
|
|
||||||
|
visited = set()
|
||||||
|
#function visit(node n)
|
||||||
|
def visit(n):
|
||||||
|
#if n has not been visited yet then
|
||||||
|
if n not in visited:
|
||||||
|
#mark n as visited
|
||||||
|
visited.add(n)
|
||||||
|
#change: n not web module, can not be resolved, ignore
|
||||||
|
if n not in modules: return
|
||||||
|
#for each node m with an edge from m to n do (dependencies of n)
|
||||||
|
for m in modules[n]:
|
||||||
|
#visit(m)
|
||||||
|
visit(m)
|
||||||
|
#add n to L
|
||||||
|
L.append(n)
|
||||||
|
#for each node n in S do
|
||||||
|
for n in S:
|
||||||
|
#visit(n)
|
||||||
|
visit(n)
|
||||||
|
return L
|
||||||
|
|
||||||
|
def module_installed(req):
|
||||||
|
# Candidates module the current heuristic is the /static dir
|
||||||
|
loadable = openerpweb.addons_manifest.keys()
|
||||||
|
modules = {}
|
||||||
|
# Retrieve database installed modules
|
||||||
|
Modules = req.session.model('ir.module.module')
|
||||||
|
domain = [('state','=','installed'), ('name','in', loadable)]
|
||||||
|
for module in Modules.search_read(domain, ['name', 'dependencies_id']):
|
||||||
|
modules[module['name']] = []
|
||||||
|
deps = module.get('dependencies_id')
|
||||||
|
if deps:
|
||||||
|
deps_read = req.session.model('ir.module.module.dependency').read(deps, ['name'])
|
||||||
|
dependencies = [i['name'] for i in deps_read]
|
||||||
|
modules[module['name']] = dependencies
|
||||||
|
|
||||||
|
sorted_modules = module_topological_sort(modules)
|
||||||
|
return sorted_modules
|
||||||
|
|
||||||
|
def module_boot(req):
|
||||||
|
addons = []
|
||||||
|
for i in req.config.server_wide_modules:
|
||||||
|
if i in openerpweb.addons_manifest:
|
||||||
|
addons.append(i)
|
||||||
|
return addons
|
||||||
|
|
||||||
def concat_xml(file_list):
|
def concat_xml(file_list):
|
||||||
"""Concatenate xml files
|
"""Concatenate xml files
|
||||||
|
|
||||||
|
@ -89,64 +218,9 @@ def concat_files(file_list, reader=None, intersperse=""):
|
||||||
files_concat = intersperse.join(files_content)
|
files_concat = intersperse.join(files_content)
|
||||||
return files_concat, checksum.hexdigest()
|
return files_concat, checksum.hexdigest()
|
||||||
|
|
||||||
def sass2scss(src):
|
|
||||||
# Validated by diff -u of sass2scss against:
|
|
||||||
# sass-convert -F sass -T scss openerp.sass openerp.scss
|
|
||||||
block = []
|
|
||||||
sass = ('', block)
|
|
||||||
reComment = re.compile(r'//.*$')
|
|
||||||
reIndent = re.compile(r'^\s+')
|
|
||||||
reIgnore = re.compile(r'^\s*(//.*)?$')
|
|
||||||
reFixes = { re.compile(r'\(\((.*)\)\)') : r'(\1)', }
|
|
||||||
lastLevel = 0
|
|
||||||
prevBlocks = {}
|
|
||||||
for l in src.split('\n'):
|
|
||||||
l = l.rstrip()
|
|
||||||
if reIgnore.search(l): continue
|
|
||||||
l = reComment.sub('', l)
|
|
||||||
l = l.rstrip()
|
|
||||||
indent = reIndent.match(l)
|
|
||||||
level = indent.end() if indent else 0
|
|
||||||
l = l[level:]
|
|
||||||
if level>lastLevel:
|
|
||||||
prevBlocks[lastLevel] = block
|
|
||||||
newBlock = []
|
|
||||||
block[-1] = (block[-1], newBlock)
|
|
||||||
block = newBlock
|
|
||||||
elif level<lastLevel:
|
|
||||||
block = prevBlocks[level]
|
|
||||||
lastLevel = level
|
|
||||||
if not l: continue
|
|
||||||
# Fixes
|
|
||||||
for ereg, repl in reFixes.items():
|
|
||||||
l = ereg.sub(repl if type(repl)==str else repl(), l)
|
|
||||||
block.append(l)
|
|
||||||
|
|
||||||
def write(sass, level=-1):
|
|
||||||
out = ""
|
|
||||||
indent = ' '*level
|
|
||||||
if type(sass)==tuple:
|
|
||||||
if level>=0:
|
|
||||||
out += indent+sass[0]+" {\n"
|
|
||||||
for e in sass[1]:
|
|
||||||
out += write(e, level+1)
|
|
||||||
if level>=0:
|
|
||||||
out = out.rstrip(" \n")
|
|
||||||
out += ' }\n'
|
|
||||||
if level==0:
|
|
||||||
out += "\n"
|
|
||||||
else:
|
|
||||||
out += indent+sass+";\n"
|
|
||||||
return out
|
|
||||||
return write(sass)
|
|
||||||
|
|
||||||
def server_wide_modules(req):
|
|
||||||
addons = [i for i in req.config.server_wide_modules if i in openerpweb.addons_manifest]
|
|
||||||
return addons
|
|
||||||
|
|
||||||
def manifest_glob(req, addons, key):
|
def manifest_glob(req, addons, key):
|
||||||
if addons is None:
|
if addons is None:
|
||||||
addons = server_wide_modules(req)
|
addons = module_boot(req)
|
||||||
else:
|
else:
|
||||||
addons = addons.split(',')
|
addons = addons.split(',')
|
||||||
r = []
|
r = []
|
||||||
|
@ -214,6 +288,164 @@ def make_conditional(req, response, last_modified=None, etag=None):
|
||||||
response.set_etag(etag)
|
response.set_etag(etag)
|
||||||
return response.make_conditional(req.httprequest)
|
return response.make_conditional(req.httprequest)
|
||||||
|
|
||||||
|
def login_and_redirect(req, db, login, key, redirect_url='/'):
|
||||||
|
req.session.authenticate(db, login, key, {})
|
||||||
|
redirect = werkzeug.utils.redirect(redirect_url, 303)
|
||||||
|
cookie_val = urllib2.quote(simplejson.dumps(req.session_id))
|
||||||
|
redirect.set_cookie('instance0|session_id', cookie_val)
|
||||||
|
return redirect
|
||||||
|
|
||||||
|
def eval_context_and_domain(session, context, domain=None):
|
||||||
|
e_context = session.eval_context(context)
|
||||||
|
# should we give the evaluated context as an evaluation context to the domain?
|
||||||
|
e_domain = session.eval_domain(domain or [])
|
||||||
|
|
||||||
|
return e_context, e_domain
|
||||||
|
|
||||||
|
def load_actions_from_ir_values(req, key, key2, models, meta):
|
||||||
|
context = req.session.eval_context(req.context)
|
||||||
|
Values = req.session.model('ir.values')
|
||||||
|
actions = Values.get(key, key2, models, meta, context)
|
||||||
|
|
||||||
|
return [(id, name, clean_action(req, action))
|
||||||
|
for id, name, action in actions]
|
||||||
|
|
||||||
|
def clean_action(req, action, do_not_eval=False):
|
||||||
|
action.setdefault('flags', {})
|
||||||
|
|
||||||
|
context = req.session.eval_context(req.context)
|
||||||
|
eval_ctx = req.session.evaluation_context(context)
|
||||||
|
|
||||||
|
if not do_not_eval:
|
||||||
|
# values come from the server, we can just eval them
|
||||||
|
if action.get('context') and isinstance(action.get('context'), basestring):
|
||||||
|
action['context'] = eval( action['context'], eval_ctx ) or {}
|
||||||
|
|
||||||
|
if action.get('domain') and isinstance(action.get('domain'), basestring):
|
||||||
|
action['domain'] = eval( action['domain'], eval_ctx ) or []
|
||||||
|
else:
|
||||||
|
if 'context' in action:
|
||||||
|
action['context'] = parse_context(action['context'], req.session)
|
||||||
|
if 'domain' in action:
|
||||||
|
action['domain'] = parse_domain(action['domain'], req.session)
|
||||||
|
|
||||||
|
action_type = action.setdefault('type', 'ir.actions.act_window_close')
|
||||||
|
if action_type == 'ir.actions.act_window':
|
||||||
|
return fix_view_modes(action)
|
||||||
|
return action
|
||||||
|
|
||||||
|
# I think generate_views,fix_view_modes should go into js ActionManager
|
||||||
|
def generate_views(action):
|
||||||
|
"""
|
||||||
|
While the server generates a sequence called "views" computing dependencies
|
||||||
|
between a bunch of stuff for views coming directly from the database
|
||||||
|
(the ``ir.actions.act_window model``), it's also possible for e.g. buttons
|
||||||
|
to return custom view dictionaries generated on the fly.
|
||||||
|
|
||||||
|
In that case, there is no ``views`` key available on the action.
|
||||||
|
|
||||||
|
Since the web client relies on ``action['views']``, generate it here from
|
||||||
|
``view_mode`` and ``view_id``.
|
||||||
|
|
||||||
|
Currently handles two different cases:
|
||||||
|
|
||||||
|
* no view_id, multiple view_mode
|
||||||
|
* single view_id, single view_mode
|
||||||
|
|
||||||
|
:param dict action: action descriptor dictionary to generate a views key for
|
||||||
|
"""
|
||||||
|
view_id = action.get('view_id') or False
|
||||||
|
if isinstance(view_id, (list, tuple)):
|
||||||
|
view_id = view_id[0]
|
||||||
|
|
||||||
|
# providing at least one view mode is a requirement, not an option
|
||||||
|
view_modes = action['view_mode'].split(',')
|
||||||
|
|
||||||
|
if len(view_modes) > 1:
|
||||||
|
if view_id:
|
||||||
|
raise ValueError('Non-db action dictionaries should provide '
|
||||||
|
'either multiple view modes or a single view '
|
||||||
|
'mode and an optional view id.\n\n Got view '
|
||||||
|
'modes %r and view id %r for action %r' % (
|
||||||
|
view_modes, view_id, action))
|
||||||
|
action['views'] = [(False, mode) for mode in view_modes]
|
||||||
|
return
|
||||||
|
action['views'] = [(view_id, view_modes[0])]
|
||||||
|
|
||||||
|
def fix_view_modes(action):
|
||||||
|
""" For historical reasons, OpenERP has weird dealings in relation to
|
||||||
|
view_mode and the view_type attribute (on window actions):
|
||||||
|
|
||||||
|
* one of the view modes is ``tree``, which stands for both list views
|
||||||
|
and tree views
|
||||||
|
* the choice is made by checking ``view_type``, which is either
|
||||||
|
``form`` for a list view or ``tree`` for an actual tree view
|
||||||
|
|
||||||
|
This methods simply folds the view_type into view_mode by adding a
|
||||||
|
new view mode ``list`` which is the result of the ``tree`` view_mode
|
||||||
|
in conjunction with the ``form`` view_type.
|
||||||
|
|
||||||
|
TODO: this should go into the doc, some kind of "peculiarities" section
|
||||||
|
|
||||||
|
:param dict action: an action descriptor
|
||||||
|
:returns: nothing, the action is modified in place
|
||||||
|
"""
|
||||||
|
if not action.get('views'):
|
||||||
|
generate_views(action)
|
||||||
|
|
||||||
|
id_form = None
|
||||||
|
for index, (id, mode) in enumerate(action['views']):
|
||||||
|
if mode == 'form':
|
||||||
|
id_form = id
|
||||||
|
break
|
||||||
|
|
||||||
|
if action.pop('view_type', 'form') != 'form':
|
||||||
|
return action
|
||||||
|
|
||||||
|
action['views'] = [
|
||||||
|
[id, mode if mode != 'tree' else 'list']
|
||||||
|
for id, mode in action['views']
|
||||||
|
]
|
||||||
|
|
||||||
|
return action
|
||||||
|
|
||||||
|
def parse_domain(domain, session):
|
||||||
|
""" Parses an arbitrary string containing a domain, transforms it
|
||||||
|
to either a literal domain or a :class:`common.nonliterals.Domain`
|
||||||
|
|
||||||
|
:param domain: the domain to parse, if the domain is not a string it
|
||||||
|
is assumed to be a literal domain and is returned as-is
|
||||||
|
:param session: Current OpenERP session
|
||||||
|
:type session: openerpweb.openerpweb.OpenERPSession
|
||||||
|
"""
|
||||||
|
if not isinstance(domain, basestring):
|
||||||
|
return domain
|
||||||
|
try:
|
||||||
|
return ast.literal_eval(domain)
|
||||||
|
except ValueError:
|
||||||
|
# not a literal
|
||||||
|
return common.nonliterals.Domain(session, domain)
|
||||||
|
|
||||||
|
def parse_context(context, session):
|
||||||
|
""" Parses an arbitrary string containing a context, transforms it
|
||||||
|
to either a literal context or a :class:`common.nonliterals.Context`
|
||||||
|
|
||||||
|
:param context: the context to parse, if the context is not a string it
|
||||||
|
is assumed to be a literal domain and is returned as-is
|
||||||
|
:param session: Current OpenERP session
|
||||||
|
:type session: openerpweb.openerpweb.OpenERPSession
|
||||||
|
"""
|
||||||
|
if not isinstance(context, basestring):
|
||||||
|
return context
|
||||||
|
try:
|
||||||
|
return ast.literal_eval(context)
|
||||||
|
except ValueError:
|
||||||
|
return common.nonliterals.Context(session, context)
|
||||||
|
|
||||||
|
#----------------------------------------------------------
|
||||||
|
# OpenERP Web web Controllers
|
||||||
|
#----------------------------------------------------------
|
||||||
|
|
||||||
html_template = """<!DOCTYPE html>
|
html_template = """<!DOCTYPE html>
|
||||||
<html style="height: 100%%">
|
<html style="height: 100%%">
|
||||||
<head>
|
<head>
|
||||||
|
@ -246,21 +478,14 @@ class Home(openerpweb.Controller):
|
||||||
r = html_template % {
|
r = html_template % {
|
||||||
'js': js,
|
'js': js,
|
||||||
'css': css,
|
'css': css,
|
||||||
'modules': simplejson.dumps(server_wide_modules(req)),
|
'modules': simplejson.dumps(module_boot(req)),
|
||||||
'init': 'var wc = new s.web.WebClient();wc.appendTo($(document.body));'
|
'init': 'var wc = new s.web.WebClient();wc.appendTo($(document.body));'
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@openerpweb.httprequest
|
@openerpweb.httprequest
|
||||||
def login(self, req, db, login, key):
|
def login(self, req, db, login, key):
|
||||||
return self._login(req, db, login, key)
|
return login_and_redirect(req, db, login, key)
|
||||||
|
|
||||||
def _login(self, req, db, login, key, redirect_url='/'):
|
|
||||||
req.session.authenticate(db, login, key, {})
|
|
||||||
redirect = werkzeug.utils.redirect(redirect_url, 303)
|
|
||||||
cookie_val = urllib2.quote(simplejson.dumps(req.session_id))
|
|
||||||
redirect.set_cookie('instance0|session_id', cookie_val)
|
|
||||||
return redirect
|
|
||||||
|
|
||||||
class WebClient(openerpweb.Controller):
|
class WebClient(openerpweb.Controller):
|
||||||
_cp_path = "/web/webclient"
|
_cp_path = "/web/webclient"
|
||||||
|
@ -410,12 +635,7 @@ class Database(openerpweb.Controller):
|
||||||
|
|
||||||
@openerpweb.jsonrequest
|
@openerpweb.jsonrequest
|
||||||
def get_list(self, req):
|
def get_list(self, req):
|
||||||
proxy = req.session.proxy("db")
|
dbs = db_list(req)
|
||||||
dbs = proxy.list()
|
|
||||||
h = req.httprequest.environ['HTTP_HOST'].split(':')[0]
|
|
||||||
d = h.split('.')[0]
|
|
||||||
r = req.config.dbfilter.replace('%h', h).replace('%d', d)
|
|
||||||
dbs = [i for i in dbs if re.match(r, i)]
|
|
||||||
return {"db_list": dbs}
|
return {"db_list": dbs}
|
||||||
|
|
||||||
@openerpweb.jsonrequest
|
@openerpweb.jsonrequest
|
||||||
|
@ -484,50 +704,6 @@ class Database(openerpweb.Controller):
|
||||||
return {'error': e.faultCode, 'title': 'Change Password'}
|
return {'error': e.faultCode, 'title': 'Change Password'}
|
||||||
return {'error': 'Error, password not changed !', 'title': 'Change Password'}
|
return {'error': 'Error, password not changed !', 'title': 'Change Password'}
|
||||||
|
|
||||||
def topological_sort(modules):
|
|
||||||
""" Return a list of module names sorted so that their dependencies of the
|
|
||||||
modules are listed before the module itself
|
|
||||||
|
|
||||||
modules is a dict of {module_name: dependencies}
|
|
||||||
|
|
||||||
:param modules: modules to sort
|
|
||||||
:type modules: dict
|
|
||||||
:returns: list(str)
|
|
||||||
"""
|
|
||||||
|
|
||||||
dependencies = set(itertools.chain.from_iterable(modules.itervalues()))
|
|
||||||
# incoming edge: dependency on other module (if a depends on b, a has an
|
|
||||||
# incoming edge from b, aka there's an edge from b to a)
|
|
||||||
# outgoing edge: other module depending on this one
|
|
||||||
|
|
||||||
# [Tarjan 1976], http://en.wikipedia.org/wiki/Topological_sorting#Algorithms
|
|
||||||
#L ← Empty list that will contain the sorted nodes
|
|
||||||
L = []
|
|
||||||
#S ← Set of all nodes with no outgoing edges (modules on which no other
|
|
||||||
# module depends)
|
|
||||||
S = set(module for module in modules if module not in dependencies)
|
|
||||||
|
|
||||||
visited = set()
|
|
||||||
#function visit(node n)
|
|
||||||
def visit(n):
|
|
||||||
#if n has not been visited yet then
|
|
||||||
if n not in visited:
|
|
||||||
#mark n as visited
|
|
||||||
visited.add(n)
|
|
||||||
#change: n not web module, can not be resolved, ignore
|
|
||||||
if n not in modules: return
|
|
||||||
#for each node m with an edge from m to n do (dependencies of n)
|
|
||||||
for m in modules[n]:
|
|
||||||
#visit(m)
|
|
||||||
visit(m)
|
|
||||||
#add n to L
|
|
||||||
L.append(n)
|
|
||||||
#for each node n in S do
|
|
||||||
for n in S:
|
|
||||||
#visit(n)
|
|
||||||
visit(n)
|
|
||||||
return L
|
|
||||||
|
|
||||||
class Session(openerpweb.Controller):
|
class Session(openerpweb.Controller):
|
||||||
_cp_path = "/web/session"
|
_cp_path = "/web/session"
|
||||||
|
|
||||||
|
@ -593,34 +769,9 @@ class Session(openerpweb.Controller):
|
||||||
|
|
||||||
@openerpweb.jsonrequest
|
@openerpweb.jsonrequest
|
||||||
def modules(self, req):
|
def modules(self, req):
|
||||||
# Compute available candidates module
|
loaded = module_boot(req)
|
||||||
loadable = openerpweb.addons_manifest
|
modules = module_installed(req)
|
||||||
loaded = set(req.config.server_wide_modules)
|
return [module for module in modules if module not in loaded]
|
||||||
candidates = [mod for mod in loadable if mod not in loaded]
|
|
||||||
|
|
||||||
# already installed modules have no dependencies
|
|
||||||
modules = dict.fromkeys(loaded, [])
|
|
||||||
|
|
||||||
# Compute auto_install modules that might be on the web side only
|
|
||||||
modules.update((name, openerpweb.addons_manifest[name].get('depends', []))
|
|
||||||
for name in candidates
|
|
||||||
if openerpweb.addons_manifest[name].get('auto_install'))
|
|
||||||
|
|
||||||
# Retrieve database installed modules
|
|
||||||
Modules = req.session.model('ir.module.module')
|
|
||||||
for module in Modules.search_read(
|
|
||||||
[('state','=','installed'), ('name','in', candidates)],
|
|
||||||
['name', 'dependencies_id']):
|
|
||||||
deps = module.get('dependencies_id')
|
|
||||||
if deps:
|
|
||||||
dependencies = map(
|
|
||||||
operator.itemgetter('name'),
|
|
||||||
req.session.model('ir.module.module.dependency').read(deps, ['name']))
|
|
||||||
modules[module['name']] = list(
|
|
||||||
set(modules.get(module['name'], []) + dependencies))
|
|
||||||
|
|
||||||
sorted_modules = topological_sort(modules)
|
|
||||||
return [module for module in sorted_modules if module not in loaded]
|
|
||||||
|
|
||||||
@openerpweb.jsonrequest
|
@openerpweb.jsonrequest
|
||||||
def eval_domain_and_context(self, req, contexts, domains,
|
def eval_domain_and_context(self, req, contexts, domains,
|
||||||
|
@ -724,120 +875,6 @@ class Session(openerpweb.Controller):
|
||||||
def destroy(self, req):
|
def destroy(self, req):
|
||||||
req.session._suicide = True
|
req.session._suicide = True
|
||||||
|
|
||||||
def eval_context_and_domain(session, context, domain=None):
|
|
||||||
e_context = session.eval_context(context)
|
|
||||||
# should we give the evaluated context as an evaluation context to the domain?
|
|
||||||
e_domain = session.eval_domain(domain or [])
|
|
||||||
|
|
||||||
return e_context, e_domain
|
|
||||||
|
|
||||||
def load_actions_from_ir_values(req, key, key2, models, meta):
|
|
||||||
context = req.session.eval_context(req.context)
|
|
||||||
Values = req.session.model('ir.values')
|
|
||||||
actions = Values.get(key, key2, models, meta, context)
|
|
||||||
|
|
||||||
return [(id, name, clean_action(req, action))
|
|
||||||
for id, name, action in actions]
|
|
||||||
|
|
||||||
def clean_action(req, action, do_not_eval=False):
|
|
||||||
action.setdefault('flags', {})
|
|
||||||
|
|
||||||
context = req.session.eval_context(req.context)
|
|
||||||
eval_ctx = req.session.evaluation_context(context)
|
|
||||||
|
|
||||||
if not do_not_eval:
|
|
||||||
# values come from the server, we can just eval them
|
|
||||||
if action.get('context') and isinstance(action.get('context'), basestring):
|
|
||||||
action['context'] = eval( action['context'], eval_ctx ) or {}
|
|
||||||
|
|
||||||
if action.get('domain') and isinstance(action.get('domain'), basestring):
|
|
||||||
action['domain'] = eval( action['domain'], eval_ctx ) or []
|
|
||||||
else:
|
|
||||||
if 'context' in action:
|
|
||||||
action['context'] = parse_context(action['context'], req.session)
|
|
||||||
if 'domain' in action:
|
|
||||||
action['domain'] = parse_domain(action['domain'], req.session)
|
|
||||||
|
|
||||||
action_type = action.setdefault('type', 'ir.actions.act_window_close')
|
|
||||||
if action_type == 'ir.actions.act_window':
|
|
||||||
return fix_view_modes(action)
|
|
||||||
return action
|
|
||||||
|
|
||||||
# I think generate_views,fix_view_modes should go into js ActionManager
|
|
||||||
def generate_views(action):
|
|
||||||
"""
|
|
||||||
While the server generates a sequence called "views" computing dependencies
|
|
||||||
between a bunch of stuff for views coming directly from the database
|
|
||||||
(the ``ir.actions.act_window model``), it's also possible for e.g. buttons
|
|
||||||
to return custom view dictionaries generated on the fly.
|
|
||||||
|
|
||||||
In that case, there is no ``views`` key available on the action.
|
|
||||||
|
|
||||||
Since the web client relies on ``action['views']``, generate it here from
|
|
||||||
``view_mode`` and ``view_id``.
|
|
||||||
|
|
||||||
Currently handles two different cases:
|
|
||||||
|
|
||||||
* no view_id, multiple view_mode
|
|
||||||
* single view_id, single view_mode
|
|
||||||
|
|
||||||
:param dict action: action descriptor dictionary to generate a views key for
|
|
||||||
"""
|
|
||||||
view_id = action.get('view_id') or False
|
|
||||||
if isinstance(view_id, (list, tuple)):
|
|
||||||
view_id = view_id[0]
|
|
||||||
|
|
||||||
# providing at least one view mode is a requirement, not an option
|
|
||||||
view_modes = action['view_mode'].split(',')
|
|
||||||
|
|
||||||
if len(view_modes) > 1:
|
|
||||||
if view_id:
|
|
||||||
raise ValueError('Non-db action dictionaries should provide '
|
|
||||||
'either multiple view modes or a single view '
|
|
||||||
'mode and an optional view id.\n\n Got view '
|
|
||||||
'modes %r and view id %r for action %r' % (
|
|
||||||
view_modes, view_id, action))
|
|
||||||
action['views'] = [(False, mode) for mode in view_modes]
|
|
||||||
return
|
|
||||||
action['views'] = [(view_id, view_modes[0])]
|
|
||||||
|
|
||||||
def fix_view_modes(action):
|
|
||||||
""" For historical reasons, OpenERP has weird dealings in relation to
|
|
||||||
view_mode and the view_type attribute (on window actions):
|
|
||||||
|
|
||||||
* one of the view modes is ``tree``, which stands for both list views
|
|
||||||
and tree views
|
|
||||||
* the choice is made by checking ``view_type``, which is either
|
|
||||||
``form`` for a list view or ``tree`` for an actual tree view
|
|
||||||
|
|
||||||
This methods simply folds the view_type into view_mode by adding a
|
|
||||||
new view mode ``list`` which is the result of the ``tree`` view_mode
|
|
||||||
in conjunction with the ``form`` view_type.
|
|
||||||
|
|
||||||
TODO: this should go into the doc, some kind of "peculiarities" section
|
|
||||||
|
|
||||||
:param dict action: an action descriptor
|
|
||||||
:returns: nothing, the action is modified in place
|
|
||||||
"""
|
|
||||||
if not action.get('views'):
|
|
||||||
generate_views(action)
|
|
||||||
|
|
||||||
id_form = None
|
|
||||||
for index, (id, mode) in enumerate(action['views']):
|
|
||||||
if mode == 'form':
|
|
||||||
id_form = id
|
|
||||||
break
|
|
||||||
|
|
||||||
if action.pop('view_type', 'form') != 'form':
|
|
||||||
return action
|
|
||||||
|
|
||||||
action['views'] = [
|
|
||||||
[id, mode if mode != 'tree' else 'list']
|
|
||||||
for id, mode in action['views']
|
|
||||||
]
|
|
||||||
|
|
||||||
return action
|
|
||||||
|
|
||||||
class Menu(openerpweb.Controller):
|
class Menu(openerpweb.Controller):
|
||||||
_cp_path = "/web/menu"
|
_cp_path = "/web/menu"
|
||||||
|
|
||||||
|
@ -1202,39 +1239,6 @@ class View(openerpweb.Controller):
|
||||||
def load(self, req, model, view_id, view_type, toolbar=False):
|
def load(self, req, model, view_id, view_type, toolbar=False):
|
||||||
return self.fields_view_get(req, model, view_id, view_type, toolbar=toolbar)
|
return self.fields_view_get(req, model, view_id, view_type, toolbar=toolbar)
|
||||||
|
|
||||||
def parse_domain(domain, session):
|
|
||||||
""" Parses an arbitrary string containing a domain, transforms it
|
|
||||||
to either a literal domain or a :class:`common.nonliterals.Domain`
|
|
||||||
|
|
||||||
:param domain: the domain to parse, if the domain is not a string it
|
|
||||||
is assumed to be a literal domain and is returned as-is
|
|
||||||
:param session: Current OpenERP session
|
|
||||||
:type session: openerpweb.openerpweb.OpenERPSession
|
|
||||||
"""
|
|
||||||
if not isinstance(domain, basestring):
|
|
||||||
return domain
|
|
||||||
try:
|
|
||||||
return ast.literal_eval(domain)
|
|
||||||
except ValueError:
|
|
||||||
# not a literal
|
|
||||||
return common.nonliterals.Domain(session, domain)
|
|
||||||
|
|
||||||
def parse_context(context, session):
|
|
||||||
""" Parses an arbitrary string containing a context, transforms it
|
|
||||||
to either a literal context or a :class:`common.nonliterals.Context`
|
|
||||||
|
|
||||||
:param context: the context to parse, if the context is not a string it
|
|
||||||
is assumed to be a literal domain and is returned as-is
|
|
||||||
:param session: Current OpenERP session
|
|
||||||
:type session: openerpweb.openerpweb.OpenERPSession
|
|
||||||
"""
|
|
||||||
if not isinstance(context, basestring):
|
|
||||||
return context
|
|
||||||
try:
|
|
||||||
return ast.literal_eval(context)
|
|
||||||
except ValueError:
|
|
||||||
return common.nonliterals.Context(session, context)
|
|
||||||
|
|
||||||
class ListView(View):
|
class ListView(View):
|
||||||
_cp_path = "/web/listview"
|
_cp_path = "/web/listview"
|
||||||
|
|
||||||
|
@ -1308,7 +1312,6 @@ class SearchView(View):
|
||||||
del filter['context']
|
del filter['context']
|
||||||
del filter['domain']
|
del filter['domain']
|
||||||
return filters
|
return filters
|
||||||
|
|
||||||
|
|
||||||
class Binary(openerpweb.Controller):
|
class Binary(openerpweb.Controller):
|
||||||
_cp_path = "/web/binary"
|
_cp_path = "/web/binary"
|
||||||
|
@ -1955,3 +1958,5 @@ class Import(View):
|
||||||
message, record)
|
message, record)
|
||||||
return '<script>window.top.%s(%s);</script>' % (
|
return '<script>window.top.%s(%s);</script>' % (
|
||||||
jsonp, simplejson.dumps({'error': {'message':msg}}))
|
jsonp, simplejson.dumps({'error': {'message':msg}}))
|
||||||
|
|
||||||
|
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
Loading…
Reference in New Issue