# -*- coding: utf-8 -*-
+import ast
import base64
import csv
import glob
import os
import re
import simplejson
-import textwrap
-import xmlrpclib
import time
+import xmlrpclib
import zlib
-import webrelease
from xml.etree import ElementTree
from cStringIO import StringIO
-import web.common.dispatch as openerpweb
-import web.common.ast
-import web.common.nonliterals
-openerpweb.ast = web.common.ast
-openerpweb.nonliterals = web.common.nonliterals
-
-from babel.messages.pofile import read_po
-
-# Should move to openerpweb.Xml2Json
-class Xml2Json:
- # xml2json-direct
- # Simple and straightforward XML-to-JSON converter in Python
- # New BSD Licensed
- #
- # URL: http://code.google.com/p/xml2json-direct/
- @staticmethod
- def convert_to_json(s):
- return simplejson.dumps(
- Xml2Json.convert_to_structure(s), sort_keys=True, indent=4)
-
- @staticmethod
- def convert_to_structure(s):
- root = ElementTree.fromstring(s)
- return Xml2Json.convert_element(root)
-
- @staticmethod
- def convert_element(el, skip_whitespaces=True):
- res = {}
- if el.tag[0] == "{":
- ns, name = el.tag.rsplit("}", 1)
- res["tag"] = name
- res["namespace"] = ns[1:]
- else:
- res["tag"] = el.tag
- res["attrs"] = {}
- for k, v in el.items():
- res["attrs"][k] = v
- kids = []
- if el.text and (not skip_whitespaces or el.text.strip() != ''):
- kids.append(el.text)
- for kid in el:
- kids.append(Xml2Json.convert_element(kid))
- if kid.tail and (not skip_whitespaces or kid.tail.strip() != ''):
- kids.append(kid.tail)
- res["children"] = kids
- return res
+import babel.messages.pofile
+
+import web.common
+openerpweb = web.common.http
#----------------------------------------------------------
# OpenERP Web web Controllers
#----------------------------------------------------------
-def manifest_glob(addons_path, addons, key):
- files = []
- for addon in addons:
- globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
- for pattern in globlist:
- for path in glob.glob(os.path.join(addons_path, addon, pattern)):
- files.append(path[len(addons_path):])
- return files
-def concat_files(addons_path, file_list):
- """ Concatenate file content
+def concat_xml(file_list):
+ """Concatenate xml files
return (concat,timestamp)
concat: concatenation of file content
timestamp: max(os.path.getmtime of file_list)
"""
+ root = None
+ files_timestamp = 0
+ for fname in file_list:
+ ftime = os.path.getmtime(fname)
+ if ftime > files_timestamp:
+ files_timestamp = ftime
+
+ xml = ElementTree.parse(fname).getroot()
+
+ if root is None:
+ root = ElementTree.Element(xml.tag)
+ #elif root.tag != xml.tag:
+ # raise ValueError("Root tags missmatch: %r != %r" % (root.tag, xml.tag))
+
+ for child in xml.getchildren():
+ root.append(child)
+ return ElementTree.tostring(root, 'utf-8'), files_timestamp
+
+
+def concat_files(file_list, reader=None):
+ """ Concatenate file content
+ return (concat,timestamp)
+ concat: concatenation of file content, read by `reader`
+ timestamp: max(os.path.getmtime of file_list)
+ """
+ if reader is None:
+ def reader(f):
+ with open(f) as fp:
+ return fp.read()
+
files_content = []
files_timestamp = 0
- for i in file_list:
- fname = os.path.join(addons_path, i[1:])
+ for fname in file_list:
ftime = os.path.getmtime(fname)
if ftime > files_timestamp:
files_timestamp = ftime
- files_content.append(open(fname).read())
+
+ files_content.append(reader(fname))
files_concat = "".join(files_content)
return files_concat,files_timestamp
-home_template = textwrap.dedent("""<!DOCTYPE html>
+html_template = """<!DOCTYPE html>
<html style="height: 100%%">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>OpenERP</title>
<link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/>
%(css)s
- %(javascript)s
+ %(js)s
<script type="text/javascript">
$(function() {
- var c = new openerp.init();
- var wc = new c.web.WebClient("oe");
- wc.start();
+ var s = new openerp.init(%(modules)s);
+ %(init)s
});
</script>
</head>
<body id="oe" class="openerp"></body>
</html>
-""")
+"""
+
class WebClient(openerpweb.Controller):
_cp_path = "/web/webclient"
+ def server_wide_modules(self, req):
+ addons = [i for i in req.config.server_wide_modules if i in openerpweb.addons_manifest]
+ return addons
+
+ def manifest_glob(self, req, addons, key):
+ if addons is None:
+ addons = self.server_wide_modules(req)
+ else:
+ addons = addons.split(',')
+ r = []
+ for addon in addons:
+ manifest = openerpweb.addons_manifest.get(addon, None)
+ if not manifest:
+ continue
+ # ensure does not ends with /
+ addons_path = os.path.join(manifest['addons_path'], '')[:-1]
+ globlist = manifest.get(key, [])
+ for pattern in globlist:
+ for path in glob.glob(os.path.normpath(os.path.join(addons_path, addon, pattern))):
+ r.append( (path, path[len(addons_path):]))
+ return r
+
+ def manifest_list(self, req, mods, extension):
+ if not req.debug:
+ path = '/web/webclient/' + extension
+ if mods is not None:
+ path += '?mods=' + mods
+ return [path]
+ return ['%s?debug=%s' % (wp, os.path.getmtime(fp)) for fp, wp in self.manifest_glob(req, mods, extension)]
+
+ @openerpweb.jsonrequest
+ def csslist(self, req, mods=None):
+ return self.manifest_list(req, mods, 'css')
+
@openerpweb.jsonrequest
- def csslist(self, req, mods='web'):
- return manifest_glob(req.config.addons_path, mods.split(','), 'css')
+ def jslist(self, req, mods=None):
+ return self.manifest_list(req, mods, 'js')
@openerpweb.jsonrequest
- def jslist(self, req, mods='web'):
- return manifest_glob(req.config.addons_path, mods.split(','), 'js')
+ def qweblist(self, req, mods=None):
+ return self.manifest_list(req, mods, 'qweb')
@openerpweb.httprequest
- def css(self, req, mods='web'):
- files = manifest_glob(req.config.addons_path, mods.split(','), 'css')
- content,timestamp = concat_files(req.config.addons_path, files)
- # TODO request set the Date of last modif and Etag
+ def css(self, req, mods=None):
+
+ files = list(self.manifest_glob(req, mods, 'css'))
+ file_map = dict(files)
+
+ rx_import = re.compile(r"""@import\s+('|")(?!'|"|/|https?://)""", re.U)
+ rx_url = re.compile(r"""url\s*\(\s*('|"|)(?!'|"|/|https?://)""", re.U)
+
+
+ def reader(f):
+ """read the a css file and absolutify all relative uris"""
+ with open(f) as fp:
+ data = fp.read()
+
+ web_path = file_map[f]
+ web_dir = os.path.dirname(web_path)
+
+ data = re.sub(
+ rx_import,
+ r"""@import \1%s/""" % (web_dir,),
+ data,
+ )
+
+ data = re.sub(
+ rx_url,
+ r"""url(\1%s/""" % (web_dir,),
+ data,
+ )
+ return data
+
+ content,timestamp = concat_files((f[0] for f in files), reader)
+ # TODO use timestamp to set Last mofified date and E-tag
return req.make_response(content, [('Content-Type', 'text/css')])
@openerpweb.httprequest
- def js(self, req, mods='web'):
- files = manifest_glob(req.config.addons_path, mods.split(','), 'js')
- content,timestamp = concat_files(req.config.addons_path, files)
- # TODO request set the Date of last modif and Etag
+ def js(self, req, mods=None):
+ files = [f[0] for f in self.manifest_glob(req, mods, 'js')]
+ content,timestamp = concat_files(files)
+ # TODO use timestamp to set Last mofified date and E-tag
return req.make_response(content, [('Content-Type', 'application/javascript')])
@openerpweb.httprequest
+ def qweb(self, req, mods=None):
+ files = [f[0] for f in self.manifest_glob(req, mods, 'qweb')]
+ content,timestamp = concat_xml(files)
+ # TODO use timestamp to set Last mofified date and E-tag
+ return req.make_response(content, [('Content-Type', 'text/xml')])
+
+
+ @openerpweb.httprequest
def home(self, req, s_action=None, **kw):
- # script tags
- jslist = ['/web/webclient/js']
- if req.debug:
- jslist = [i + '?debug=' + str(time.time()) for i in manifest_glob(req.config.addons_path, ['web'], 'js')]
- js = "\n ".join(['<script type="text/javascript" src="%s"></script>'%i for i in jslist])
-
- # css tags
- csslist = ['/web/webclient/css']
- if req.debug:
- csslist = [i + '?debug=' + str(time.time()) for i in manifest_glob(req.config.addons_path, ['web'], 'css')]
- css = "\n ".join(['<link rel="stylesheet" href="%s">'%i for i in csslist])
- r = home_template % {
- 'javascript': js,
- 'css': css
+ js = "\n ".join('<script type="text/javascript" src="%s"></script>'%i for i in self.manifest_list(req, None, 'js'))
+ css = "\n ".join('<link rel="stylesheet" href="%s">'%i for i in self.manifest_list(req, None, 'css'))
+
+ r = html_template % {
+ 'js': js,
+ 'css': css,
+ 'modules': simplejson.dumps(self.server_wide_modules(req)),
+ 'init': 'new s.web.WebClient("oe").start();',
}
return r
transl = {"messages":[]}
transs[addon_name] = transl
for l in langs:
- f_name = os.path.join(req.config.addons_path, addon_name, "po", l + ".po")
+ addons_path = openerpweb.addons_manifest[addon_name]['addons_path']
+ f_name = os.path.join(addons_path, addon_name, "po", l + ".po")
if not os.path.exists(f_name):
continue
try:
with open(f_name) as t_file:
- po = read_po(t_file)
+ po = babel.messages.pofile.read_po(t_file)
except:
continue
for x in po:
@openerpweb.jsonrequest
def version_info(self, req):
return {
- "version": webrelease.version
+ "version": web.common.release.version
}
class Database(openerpweb.Controller):
params['db_lang'],
params['create_admin_pwd']
)
-
+
try:
return req.session.proxy("db").create(*create_attrs)
except xmlrpclib.Fault, e:
- if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
- return {'error': e.faultCode, 'title': 'Create Database'}
- return {'error': 'Could not create database !', 'title': 'Create Database'}
+ if e.faultCode and isinstance(e.faultCode, str)\
+ and e.faultCode.split(':')[0] == 'AccessDenied':
+ return {'error': e.faultCode, 'title': 'Database creation error'}
+ return {
+ 'error': "Could not create database '%s': %s" % (
+ params['db_name'], e.faultString),
+ 'title': 'Database creation error'
+ }
@openerpweb.jsonrequest
def drop(self, req, fields):
@openerpweb.httprequest
def restore(self, req, db_file, restore_pwd, new_db):
try:
- data = base64.b64encode(db_file.file.read())
+ data = base64.b64encode(db_file.read())
req.session.proxy("db").restore(restore_pwd, new_db, data)
return ''
except xmlrpclib.Fault, e:
@openerpweb.jsonrequest
def login(self, req, db, login, password):
req.session.login(db, login, password)
- ctx = req.session.get_context()
+ ctx = req.session.get_context() if req.session._uid else {}
return {
"session_id": req.session_id,
"uid": req.session._uid,
- "context": ctx
+ "context": ctx,
+ "db": req.session._db,
+ "login": req.session._login
}
+
+ @openerpweb.jsonrequest
+ def get_session_info(self, req):
+ req.session.assert_valid(force=True)
+ return {
+ "uid": req.session._uid,
+ "context": req.session.get_context() if req.session._uid else False,
+ "db": req.session._db,
+ "login": req.session._login
+ }
+
@openerpweb.jsonrequest
def change_password (self,req,fields):
old_password, new_password,confirm_password = operator.itemgetter('old_pwd', 'new_password','confirm_pwd')(
except:
return {'error': 'Original password incorrect, your password was not changed.', 'title': 'Change Password'}
return {'error': 'Error, password not changed !', 'title': 'Change Password'}
+
@openerpweb.jsonrequest
def sc_list(self, req):
return req.session.model('ir.ui.view_sc').get_sc(
@openerpweb.jsonrequest
def modules(self, req):
- # TODO query server for installed web modules
- mods = []
- for name, manifest in openerpweb.addons_manifest.items():
- if name != 'web' and manifest.get('active', True):
- mods.append(name)
- return mods
+ # Compute available candidates module
+ loadable = openerpweb.addons_manifest.iterkeys()
+ loaded = req.config.server_wide_modules
+ candidates = [mod for mod in loadable if mod not in loaded]
+
+ # Compute active true modules that might be on the web side only
+ active = set(name for name in candidates
+ if openerpweb.addons_manifest[name].get('active'))
+
+ # Retrieve database installed modules
+ Modules = req.session.model('ir.module.module')
+ installed = set(module['name'] for module in Modules.search_read(
+ [('state','=','installed'), ('name','in', candidates)], ['name']))
+
+ # Merge both
+ return list(active | installed)
@openerpweb.jsonrequest
def eval_domain_and_context(self, req, contexts, domains,
no group by should be performed)
"""
context, domain = eval_context_and_domain(req.session,
- openerpweb.nonliterals.CompoundContext(*(contexts or [])),
- openerpweb.nonliterals.CompoundDomain(*(domains or [])))
+ web.common.nonliterals.CompoundContext(*(contexts or [])),
+ web.common.nonliterals.CompoundDomain(*(domains or [])))
group_by_sequence = []
for candidate in (group_by_seq or []):
return [(id, name, clean_action(req, action))
for id, name, action in actions]
-def clean_action(req, action):
+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)
- # values come from the server, we can just eval them
- if isinstance(action.get('context'), basestring):
- action['context'] = eval( action['context'], eval_ctx ) or {}
+ if not do_not_eval:
+ # values come from the server, we can just eval them
+ if isinstance(action.get('context'), basestring):
+ action['context'] = eval( action['context'], eval_ctx ) or {}
- if isinstance(action.get('domain'), basestring):
- action['domain'] = eval( action['domain'], eval_ctx ) or []
+ if 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)
+
+ if 'type' not in action:
+ action['type'] = 'ir.actions.act_window_close'
if action['type'] == 'ir.actions.act_window':
return fix_view_modes(action)
:param dict action: an action descriptor
:returns: nothing, the action is modified in place
"""
- if 'views' not in action:
+ if not action.get('views'):
generate_views(action)
- if action.pop('view_type') != 'form':
+ id_form = None
+ for index, (id, mode) in enumerate(action['views']):
+ if mode == 'form':
+ id_form = id
+ break
+ if id_form is not None:
+ action['views'].insert(index + 1, (id_form, 'page'))
+
+ if action.pop('view_type', 'form') != 'form':
return action
action['views'] = [
return Model.unlink(ids, req.session.eval_context(req.context))
def call_common(self, req, model, method, args, domain_id=None, context_id=None):
- domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else []
- context = args[context_id] if context_id and len(args) - 1 >= context_id else {}
+ has_domain = domain_id is not None and domain_id < len(args)
+ has_context = context_id is not None and context_id < len(args)
+
+ domain = args[domain_id] if has_domain else []
+ context = args[context_id] if has_context else {}
c, d = eval_context_and_domain(req.session, context, domain)
- if domain_id and len(args) - 1 >= domain_id:
+ if has_domain:
args[domain_id] = d
- if context_id and len(args) - 1 >= context_id:
+ if has_context:
args[context_id] = c
for i in xrange(len(args)):
context = req.session.eval_context(req.context)
fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
# todo fme?: check that we should pass the evaluated context here
- self.process_view(req.session, fvg, context, transform)
+ self.process_view(req.session, fvg, context, transform, (view_type == 'kanban'))
if toolbar and transform:
self.process_toolbar(req, fvg['toolbar'])
return fvg
- def process_view(self, session, fvg, context, transform):
+ def process_view(self, session, fvg, context, transform, preserve_whitespaces=False):
# depending on how it feels, xmlrpclib.ServerProxy can translate
# XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
# enjoy unicode strings which can not be trivially converted to
xml = self.transform_view(arch, session, evaluation_context)
else:
xml = ElementTree.fromstring(arch)
- fvg['arch'] = Xml2Json.convert_element(xml)
+ fvg['arch'] = web.common.xml2json.Xml2Json.convert_element(xml, preserve_whitespaces)
for field in fvg['fields'].itervalues():
if field.get('views'):
for view in field["views"].itervalues():
self.process_view(session, view, None, transform)
if field.get('domain'):
- field["domain"] = self.parse_domain(field["domain"], session)
+ field["domain"] = parse_domain(field["domain"], session)
if field.get('context'):
- field["context"] = self.parse_context(field["context"], session)
+ field["context"] = parse_context(field["context"], session)
def process_toolbar(self, req, toolbar):
"""
for actions in toolbar.itervalues():
for action in actions:
if 'context' in action:
- action['context'] = self.parse_context(
+ action['context'] = parse_context(
action['context'], req.session)
if 'domain' in action:
- action['domain'] = self.parse_domain(
+ action['domain'] = parse_domain(
action['domain'], req.session)
@openerpweb.jsonrequest
self.parse_domains_and_contexts(elem, session)
return root
- def parse_domain(self, domain, session):
- """ Parses an arbitrary string containing a domain, transforms it
- to either a literal domain or a :class:`openerpweb.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, (str, unicode)):
- return domain
- try:
- return openerpweb.ast.literal_eval(domain)
- except ValueError:
- # not a literal
- return openerpweb.nonliterals.Domain(session, domain)
-
- def parse_context(self, context, session):
- """ Parses an arbitrary string containing a context, transforms it
- to either a literal context or a :class:`openerpweb.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, (str, unicode)):
- return context
- try:
- return openerpweb.ast.literal_eval(context)
- except ValueError:
- return openerpweb.nonliterals.Context(session, context)
-
def parse_domains_and_contexts(self, elem, session):
""" Converts domains and contexts from the view into Python objects,
either literals if they can be parsed by literal_eval or a special
for el in ['domain', 'filter_domain']:
domain = elem.get(el, '').strip()
if domain:
- elem.set(el, self.parse_domain(domain, session))
+ elem.set(el, parse_domain(domain, session))
+ elem.set(el + '_string', domain)
for el in ['context', 'default_get']:
context_string = elem.get(el, '').strip()
if context_string:
- elem.set(el, self.parse_context(context_string, session))
+ elem.set(el, parse_context(context_string, session))
+ elem.set(el + '_string', context_string)
@openerpweb.jsonrequest
def load(self, req, model, view_id, view_type, toolbar=False):
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:`web.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, (str, unicode)):
+ return domain
+ try:
+ return ast.literal_eval(domain)
+ except ValueError:
+ # not a literal
+ return web.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:`web.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, (str, unicode)):
+ return context
+ try:
+ return ast.literal_eval(context)
+ except ValueError:
+ return web.common.nonliterals.Context(session, context)
+
class ListView(View):
_cp_path = "/web/listview"
for field in fields.values():
# shouldn't convert the views too?
if field.get('domain'):
- field["domain"] = self.parse_domain(field["domain"], req.session)
+ field["domain"] = parse_domain(field["domain"], req.session)
if field.get('context'):
- field["context"] = self.parse_domain(field["context"], req.session)
+ field["context"] = parse_context(field["context"], req.session)
return {'fields': fields}
@openerpweb.jsonrequest
Model = req.session.model("ir.filters")
filters = Model.get_filters(model)
for filter in filters:
- filter["context"] = req.session.eval_context(self.parse_context(filter["context"], req.session))
- filter["domain"] = req.session.eval_domain(self.parse_domain(filter["domain"], req.session))
+ filter["context"] = req.session.eval_context(parse_context(filter["context"], req.session))
+ filter["domain"] = req.session.eval_domain(parse_domain(filter["domain"], req.session))
return filters
@openerpweb.jsonrequest
def save_filter(self, req, model, name, context_to_save, domain):
Model = req.session.model("ir.filters")
- ctx = openerpweb.nonliterals.CompoundContext(context_to_save)
+ ctx = web.common.nonliterals.CompoundContext(context_to_save)
ctx.session = req.session
ctx = ctx.evaluate()
- domain = openerpweb.nonliterals.CompoundDomain(domain)
+ domain = web.common.nonliterals.CompoundDomain(domain)
domain.session = req.session
domain = domain.evaluate()
uid = req.session._uid
}, context)
return to_return
+ @openerpweb.jsonrequest
+ def add_to_dashboard(self, req, menu_id, action_id, context_to_save, domain, view_mode, name=''):
+ ctx = web.common.nonliterals.CompoundContext(context_to_save)
+ ctx.session = req.session
+ ctx = ctx.evaluate()
+ domain = web.common.nonliterals.CompoundDomain(domain)
+ domain.session = req.session
+ domain = domain.evaluate()
+
+ dashboard_action = load_actions_from_ir_values(req, 'action', 'tree_but_open',
+ [('ir.ui.menu', menu_id)], False)
+ if dashboard_action:
+ action = dashboard_action[0][2]
+ if action['res_model'] == 'board.board' and action['views'][0][1] == 'form':
+ # Maybe should check the content instead of model board.board ?
+ view_id = action['views'][0][0]
+ board = req.session.model(action['res_model']).fields_view_get(view_id, 'form')
+ if board and 'arch' in board:
+ xml = ElementTree.fromstring(board['arch'])
+ column = xml.find('./board/column')
+ if column:
+ new_action = ElementTree.Element('action', {
+ 'name' : str(action_id),
+ 'string' : name,
+ 'view_mode' : view_mode,
+ 'context' : str(ctx),
+ 'domain' : str(domain)
+ })
+ column.insert(0, new_action)
+ arch = ElementTree.tostring(xml, 'utf-8')
+ return req.session.model('ir.ui.view.custom').create({
+ 'user_id': req.session._uid,
+ 'ref_id': view_id,
+ 'arch': arch
+ }, req.session.eval_context(req.context))
+
+ return False
+
class Binary(openerpweb.Controller):
_cp_path = "/web/binary"
try:
if not id:
- res = Model.default_get([field], context).get(field, '')
+ res = Model.default_get([field], context).get(field)
else:
- res = Model.read([int(id)], [field], context)[0].get(field, '')
+ res = Model.read([int(id)], [field], context)[0].get(field)
image_data = base64.b64decode(res)
except (TypeError, xmlrpclib.Fault):
image_data = self.placeholder(req)
return req.make_response(image_data, [
('Content-Type', 'image/png'), ('Content-Length', len(image_data))])
def placeholder(self, req):
- return open(os.path.join(req.addons_path, 'web', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
+ addons_path = openerpweb.addons_manifest['web']['addons_path']
+ return open(os.path.join(addons_path, 'web', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
@openerpweb.httprequest
def saveas(self, req, model, id, field, fieldname, **kw):
Model = req.session.model(model)
context = req.session.eval_context(req.context)
- res = Model.read([int(id)], [field, fieldname], context)[0]
- filecontent = res.get(field, '')
+ if id:
+ res = Model.read([int(id)], [field, fieldname], context)[0]
+ else:
+ res = Model.default_get([field, fieldname], context)
+ filecontent = base64.b64decode(res.get(field, ''))
if not filecontent:
return req.not_found()
else:
_cp_path = "/web/action"
@openerpweb.jsonrequest
- def load(self, req, action_id):
+ def load(self, req, action_id, do_not_eval=False):
Actions = req.session.model('ir.actions.actions')
value = False
context = req.session.eval_context(req.context)
ctx.update(context)
action = req.session.model(action_type[0]['type']).read([action_id], False, ctx)
if action:
- value = clean_action(req, action[0])
+ value = clean_action(req, action[0], do_not_eval)
return {'result': value}
@openerpweb.jsonrequest
@openerpweb.jsonrequest
def get_fields(self, req, model, prefix='', parent_name= '',
- import_compat=True, parent_field_type=None):
+ import_compat=True, parent_field_type=None,
+ exclude=None):
if import_compat and parent_field_type == "many2one":
fields = {}
else:
fields = self.fields_get(req, model)
- fields['.id'] = fields.pop('id') if 'id' in fields else {'string': 'ID'}
+
+ if import_compat:
+ fields.pop('id', None)
+ else:
+ fields['.id'] = fields.pop('id', {'string': 'ID'})
fields_sequence = sorted(fields.iteritems(),
key=lambda field: field[1].get('string', ''))
records = []
for field_name, field in fields_sequence:
+ if import_compat and (exclude and field_name in exclude):
+ continue
if import_compat and field.get('readonly'):
# If none of the field's states unsets readonly, skip the field
if all(dict(attrs).get('readonly', True)
record = {'id': id, 'string': name,
'value': id, 'children': False,
'field_type': field.get('type'),
- 'required': field.get('required')}
+ 'required': field.get('required'),
+ 'relation_field': field.get('relation_field')}
records.append(record)
if len(name.split('/')) < 3 and 'relation' in field:
def fields_info(self, req, model, export_fields):
info = {}
fields = self.fields_get(req, model)
- fields['.id'] = fields.pop('id') if 'id' in fields else {'string': 'ID'}
# To make fields retrieval more efficient, fetch all sub-fields of a
# given field at the same time. Because the order in the export list is
context = req.session.eval_context(req.context)
Model = req.session.model(model)
- ids = ids or Model.search(domain, context=context)
+ ids = ids or Model.search(domain, 0, False, False, context)
field_names = map(operator.itemgetter('name'), fields)
import_data = Model.export_data(ids, field_names, context).get('datas',[])
for cell_index, cell_value in enumerate(row):
if isinstance(cell_value, basestring):
cell_value = re.sub("\r", " ", cell_value)
+ if cell_value is False: cell_value = None
worksheet.write(row_index + 1, cell_index, cell_value, style)
fp = StringIO()
report_srv = req.session.proxy("report")
context = req.session.eval_context(
- openerpweb.nonliterals.CompoundContext(
+ web.common.nonliterals.CompoundContext(
req.context or {}, action[ "context"]))
- report_data = {"id": context["active_id"], "model": context["active_model"]}
+ report_data = {}
+ report_ids = context["active_ids"]
if 'report_type' in action:
report_data['report_type'] = action['report_type']
+ if 'datas' in action:
+ if 'ids' in action['datas']:
+ report_ids = action['datas'].pop('ids')
+ report_data.update(action['datas'])
+
report_id = report_srv.report(
req.session._db, req.session._uid, req.session._password,
- action["report_name"], context["active_ids"],
+ action["report_name"], report_ids,
report_data, context)
report_struct = None
('Content-Length', len(report))],
cookies={'fileToken': int(token)})
-
class Import(View):
_cp_path = "/web/import"
return fields
@openerpweb.httprequest
- def detect_data(self, req, model, csvfile, csvsep, csvdel, csvcode, csvskip,
- jsonp):
-
- _fields = {}
- _fields_invert = {}
- req_field = []
- error = None
- fields = req.session.model(model).fields_get(False, req.session.eval_context(req.context))
- fields.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}})
-
- for field in fields:
- value = fields[field]
- if value.get('required'):
- req_field.append(field)
-
- def model_populate(fields, prefix_node='', prefix=None, prefix_value='', level=2):
- def str_comp(x,y):
- if x<y: return 1
- elif x>y: return -1
- else: return 0
-
- fields_order = fields.keys()
- fields_order.sort(lambda x,y: str_comp(fields[x].get('string', ''), fields[y].get('string', '')))
- for field in fields_order:
- if (fields[field].get('type','') not in ('reference',))\
- and (not fields[field].get('readonly')\
- or not dict(fields[field].get('states', {}).get(
- 'draft', [('readonly', True)])).get('readonly',True)):
-
- st_name = prefix_value+fields[field]['string'] or field
- _fields[prefix_node+field] = st_name
- _fields_invert[st_name] = prefix_node+field
-
- if fields[field].get('type')=='one2many' and level>0:
- fields2 = self.fields_get(req, fields[field]['relation'])
- model_populate(fields2, prefix_node+field+'/', None, st_name+'/', level-1)
-
- if fields[field].get('relation',False) and level>0:
- model_populate({'/id': {'type': 'char', 'string': 'ID'}, '.id': {'type': 'char', 'string': 'Database ID'}},
- prefix_node+field, None, st_name+'/', level-1)
- fields.update({'id':{'string':'ID'},'.id':{'string':'Database ID'}})
- model_populate(fields)
- all_fields = fields.keys()
- all_fields.sort()
-
+ def detect_data(self, req, csvfile, csvsep=',', csvdel='"', csvcode='utf-8', jsonp='callback'):
try:
- data = csv.reader(csvfile, quotechar=str(csvdel), delimiter=str(csvsep))
- except:
- error={'message': 'error opening .CSV file. Input Error.'}
+ data = list(csv.reader(
+ csvfile, quotechar=str(csvdel), delimiter=str(csvsep)))
+ except csv.Error, e:
+ csvfile.seek(0)
return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'error':error}))
-
- records = []
- count = 0
- header_fields = []
- word=''
+ jsonp, simplejson.dumps({'error': {
+ 'message': 'Error parsing CSV file: %s' % e,
+ # decodes each byte to a unicode character, which may or
+ # may not be printable, but decoding will succeed.
+ # Otherwise simplejson will try to decode the `str` using
+ # utf-8, which is very likely to blow up on characters out
+ # of the ascii range (in range [128, 256))
+ 'preview': csvfile.read(200).decode('iso-8859-1')}}))
try:
- for rec in itertools.islice(data,0,4):
- records.append(rec)
-
- headers = itertools.islice(records,1)
- line = headers.next()
-
- for word in line:
- word = str(word.decode(csvcode))
- if word in _fields:
- header_fields.append((word, _fields[word]))
- elif word in _fields_invert.keys():
- header_fields.append((_fields_invert[word], word))
- else:
- count = count + 1
- header_fields.append((word, word))
-
- if len(line) == count:
- error = {'message':"File has not any column header."}
- except:
- error = {'message':('Error processing the first line of the file. Field "%s" is unknown') % (word,)}
-
- if error:
- csvfile.seek(0)
- error=dict(error, preview=csvfile.read(200))
return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'error':error}))
-
- return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'records':records[1:],'header':header_fields,'all_fields':all_fields,'req_field':req_field}))
+ jsonp, simplejson.dumps(
+ {'records': data[:10]}, encoding=csvcode))
+ except UnicodeDecodeError:
+ return '<script>window.top.%s(%s);</script>' % (
+ jsonp, simplejson.dumps({
+ 'message': u"Failed to decode CSV file using encoding %s, "
+ u"try switching to a different encoding" % csvcode
+ }))
@openerpweb.httprequest
- def import_data(self, req, model, csvfile, csvsep, csvdel, csvcode, csvskip,
- jsonp):
-
- _fields = {}
- _fields_invert = {}
- prefix_node=''
- prefix_value = ''
-
- context = req.session.eval_context(req.context)
+ def import_data(self, req, model, csvfile, csvsep, csvdel, csvcode, jsonp,
+ meta):
modle_obj = req.session.model(model)
- res = None
-
- limit = 0
- data = []
+ skip, indices, fields = operator.itemgetter('skip', 'indices', 'fields')(
+ simplejson.loads(meta))
+ error = None
if not (csvdel and len(csvdel) == 1):
- error={'message': "The CSV delimiter must be a single character"}
- return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'error':error}))
+ error = u"The CSV delimiter must be a single character"
- try:
- data_record = csv.reader(csvfile, quotechar=str(csvdel), delimiter=str(csvsep))
- for rec in itertools.islice(data_record,0,None):
- data.append(rec)
-
- headers = itertools.islice(data,1)
- fields = headers.next()
+ if not indices and fields:
+ error = u"You must select at least one field to import"
- except csv.Error, e:
- error={'message': str(e),'title': 'File Format Error'}
+ if error:
return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'error':error}))
-
- datas = []
- ctx = context
+ jsonp, simplejson.dumps({'error': {'message': error}}))
- if not isinstance(fields, list):
- fields = [fields]
+ # skip ignored records
+ data_record = itertools.islice(
+ csv.reader(csvfile, quotechar=str(csvdel), delimiter=str(csvsep)),
+ skip, None)
- flds = modle_obj.fields_get(False, req.session.eval_context(req.context))
- flds.update({'id':{'string':'ID'},'.id':{'string':'Database ID'}})
- fields_order = flds.keys()
- for field in fields_order:
- st_name = prefix_value+flds[field]['string'] or field
- _fields[prefix_node+field] = st_name
- _fields_invert[st_name] = prefix_node+field
+ # if only one index, itemgetter will return an atom rather than a tuple
+ if len(indices) == 1: mapper = lambda row: [row[indices[0]]]
+ else: mapper = operator.itemgetter(*indices)
- unmatch_field = []
- for fld in fields:
- if ((fld not in _fields) and (fld not in _fields_invert)):
- unmatch_field.append(fld)
-
- if unmatch_field:
- error = {'message':("You cannot import the fields '%s',because we cannot auto-detect it." % (unmatch_field))}
- return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'error':error}))
-
- for line in data[1:]:
- try:
- datas.append(map(lambda x:x.decode(csvcode).encode('utf-8'), line))
- except:
- datas.append(map(lambda x:x.decode('latin').encode('utf-8'), line))
+ data = None
+ error = None
+ try:
+ # decode each data row
+ data = [
+ [record.decode(csvcode) for record in row]
+ for row in itertools.imap(mapper, data_record)
+ # don't insert completely empty rows (can happen due to fields
+ # filtering in case of e.g. o2m content rows)
+ if any(row)
+ ]
+ except UnicodeDecodeError:
+ error = u"Failed to decode CSV file using encoding %s" % csvcode
+ except csv.Error, e:
+ error = u"Could not process CSV file: %s" % e
# If the file contains nothing,
- if not datas:
- error = {'message': 'The file is empty !', 'title': 'Importation !'}
+ if not data:
+ error = u"File to import is empty"
+ if error:
return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'error':error}))
+ jsonp, simplejson.dumps({'error': {'message': error}}))
- #Inverting the header into column names
try:
- res = modle_obj.import_data(fields, datas, 'init', '', False, ctx)
+ (code, record, message, _nope) = modle_obj.import_data(
+ fields, data, 'init', '', False,
+ req.session.eval_context(req.context))
except xmlrpclib.Fault, e:
- error = {"message":e.faultCode}
+ error = {"message": u"%s, %s" % (e.faultCode, e.faultString)}
return '<script>window.top.%s(%s);</script>' % (
jsonp, simplejson.dumps({'error':error}))
- if res[0]>=0:
- success={'message':'Imported %d objects' % (res[0],)}
+ if code != -1:
return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'error':success}))
+ jsonp, simplejson.dumps({'success':True}))
- d = ''
- for key,val in res[1].items():
- d+= ('%s: %s' % (str(key),str(val)))
- msg = 'Error trying to import this record:%s. ErrorMessage:%s %s' % (d,res[2],res[3])
- error = {'message':str(msg), 'title':'ImportationError'}
+ msg = u"Error during import: %s\n\nTrying to import record %r" % (
+ message, record)
return '<script>window.top.%s(%s);</script>' % (
- jsonp, simplejson.dumps({'error':error}))
+ jsonp, simplejson.dumps({'error': {'message':msg}}))