import os
import re
import simplejson
+import sys
import time
import urllib2
import zlib
import openerp
import openerp.modules.registry
+from openerp.addons.base.ir.ir_qweb import AssetsBundle, QWebTemplateNotFound
+from openerp.modules import get_module_resource
+from openerp.tools import topological_sort
from openerp.tools.translate import _
from openerp import http
-from openerp.http import request, serialize_exception as _serialize_exception, LazyResponse
+from openerp.http import request, serialize_exception as _serialize_exception
_logger = logging.getLogger(__name__)
-env = jinja2.Environment(
- loader=jinja2.PackageLoader('openerp.addons.web', "views"),
- autoescape=True
-)
+if hasattr(sys, 'frozen'):
+ # When running on compiled windows binary, we don't have access to package loader.
+ path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'views'))
+ loader = jinja2.FileSystemLoader(path)
+else:
+ loader = jinja2.PackageLoader('openerp.addons.web', "views")
+
+env = jinja2.Environment(loader=loader, autoescape=True)
env.filters["json"] = simplejson.dumps
+# 1 week cache for asset bundles as advised by Google Page Speed
+BUNDLE_MAXAGE = 60 * 60 * 24 * 7
+
#----------------------------------------------------------
# OpenERP Web helpers
#----------------------------------------------------------
-def rjsmin(script):
- """ Minify js with a clever regex.
- Taken from http://opensource.perlig.de/rjsmin
- Apache License, Version 2.0 """
- def subber(match):
- """ Substitution callback """
- groups = match.groups()
- return (
- groups[0] or
- groups[1] or
- groups[2] or
- groups[3] or
- (groups[4] and '\n') or
- (groups[5] and ' ') or
- (groups[6] and ' ') or
- (groups[7] and ' ') or
- ''
- )
-
- result = re.sub(
- r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?'
- r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|'
- r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?:(?<=[(,=:\[!&|?{};\r\n]'
- r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'
- r'))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*'
- r'(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*'
- r'))|(?:(?<=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\01'
- r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*((?:/(?![\r\n/*])[^/'
- r'\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]'
- r'*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*))|(?<=[^\000-!#%&(*,./'
- r':-@\[\\^`{|~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/'
- r'*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\01'
- r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#'
- r'%-\047)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-'
- r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^'
- r'\000-#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|'
- r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\0'
- r'13\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[\0'
- r'00-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:'
- r'(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*'
- r']*\*+(?:[^/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script
- ).strip()
- return result
-
db_list = http.db_list
db_monodb = http.db_monodb
se = _serialize_exception(e)
error = {
'code': 200,
- 'message': "OpenERP Server Error",
+ 'message': "Odoo Server Error",
'data': se
}
return werkzeug.exceptions.InternalServerError(simplejson.dumps(error))
"""
return http.redirect_with_hash(*args, **kw)
+def abort_and_redirect(url):
+ r = request.httprequest
+ response = werkzeug.utils.redirect(url, 302)
+ response = r.app.get_response(r, response, explicit_session=False)
+ werkzeug.exceptions.abort(response)
+
def ensure_db(redirect='/web/database/selector'):
# This helper should be used in web client auth="none" routes
# if those routes needs a db to work with.
url_redirect += '?' + r.query_string
response = werkzeug.utils.redirect(url_redirect, 302)
request.session.db = db
- response = r.app.get_response(r, response, explicit_session=False)
- werkzeug.exceptions.abort(response)
- return
+ abort_and_redirect(url_redirect)
# if db not provided, use the session one
if not db and request.session.db and http.db_filter([request.session.db]):
# always switch the session to the computed db
if db != request.session.db:
request.session.logout()
+ abort_and_redirect(request.httprequest.url)
request.session.db = db
-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():
# Candidates module the current heuristic is the /static dir
loadable = http.addons_manifest.keys()
dependencies = [i['name'] for i in deps_read]
modules[module['name']] = dependencies
- sorted_modules = module_topological_sort(modules)
+ sorted_modules = topological_sort(modules)
return sorted_modules
def module_installed_bypass_session(dbname):
modules[module['name']] = dependencies
except Exception,e:
pass
- sorted_modules = module_topological_sort(modules)
+ sorted_modules = topological_sort(modules)
return sorted_modules
def module_boot(db=None):
root.append(child)
return ElementTree.tostring(root, 'utf-8'), checksum.hexdigest()
-def concat_files(file_list, reader=None, intersperse=""):
- """ Concatenates contents of all provided files
-
- :param list(str) file_list: list of files to check
- :param function reader: reading procedure for each file
- :param str intersperse: string to intersperse between file contents
- :returns: (concatenation_result, checksum)
- :rtype: (str, str)
- """
- checksum = hashlib.new('sha1')
- if not file_list:
- return '', checksum.hexdigest()
-
- if reader is None:
- def reader(f):
- import codecs
- with codecs.open(f, 'rb', "utf-8-sig") as fp:
- return fp.read().encode("utf-8")
-
- files_content = []
- for fname in file_list:
- contents = reader(fname)
- checksum.update(contents)
- files_content.append(contents)
-
- files_concat = intersperse.join(files_content)
- return files_concat, checksum.hexdigest()
-
-concat_js_cache = {}
-
-def concat_js(file_list):
- content, checksum = concat_files(file_list, intersperse=';')
- if checksum in concat_js_cache:
- content = concat_js_cache[checksum]
- else:
- content = rjsmin(content)
- concat_js_cache[checksum] = content
- return content, checksum
-
def fs2web(path):
"""convert FS path into web path"""
return '/'.join(path.split(os.path.sep))
r.append((None, pattern))
else:
for path in glob.glob(os.path.normpath(os.path.join(addons_path, addon, pattern))):
- # Hack for IE, who limit 288Ko, 4095 rules, 31 sheets
- # http://support.microsoft.com/kb/262161/en
- if pattern == "static/lib/bootstrap/css/bootstrap.css":
- if include_remotes:
- r.insert(0, (None, fs2web(path[len(addons_path):])))
- else:
- r.append((path, fs2web(path[len(addons_path):])))
+ r.append((path, fs2web(path[len(addons_path):])))
return r
-def manifest_list(extension, mods=None, db=None, debug=False):
+def manifest_list(extension, mods=None, db=None, debug=None):
""" list ressources to load specifying either:
mods: a comma separated string listing modules
db: a database name (return all installed modules in that database)
"""
+ if debug is not None:
+ _logger.warning("openerp.addons.web.main.manifest_list(): debug parameter is deprecated")
files = manifest_glob(extension, addons=mods, db=db, include_remotes=True)
- if not debug:
- path = '/web/webclient/' + extension
- if mods is not None:
- path += '?' + werkzeug.url_encode({'mods': mods})
- elif db:
- path += '?' + werkzeug.url_encode({'db': db})
-
- remotes = [wp for fp, wp in files if fp is None]
- return [path] + remotes
return [wp for _fp, wp in files]
def get_last_modified(files):
for f in files)
return datetime.datetime(1970, 1, 1)
-def make_conditional(response, last_modified=None, etag=None):
+def make_conditional(response, last_modified=None, etag=None, max_age=0):
""" Makes the provided response conditional based upon the request,
and mandates revalidation from clients
:rtype: werkzeug.wrappers.Response
"""
response.cache_control.must_revalidate = True
- response.cache_control.max_age = 0
+ response.cache_control.max_age = max_age
if last_modified:
response.last_modified = last_modified
if etag:
#----------------------------------------------------------
# OpenERP Web web Controllers
#----------------------------------------------------------
-
-# TODO: to remove once the database manager has been migrated server side
-# and `edi` + `pos` addons has been adapted to use render_bootstrap_template()
-html_template = """<!DOCTYPE html>
-<html style="height: 100%%">
- <head>
- <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
- <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"/>
- <link rel="stylesheet" href="/web/static/src/css/full.css" />
- %(css)s
- %(js)s
- <script type="text/javascript">
- $(function() {
- var s = new openerp.init(%(modules)s);
- %(init)s
- });
- </script>
- </head>
- <body>
- <!--[if lte IE 8]>
- <script src="//ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
- <script>CFInstall.check({mode: "overlay"});</script>
- <![endif]-->
- </body>
-</html>
-"""
-
-def render_bootstrap_template(db, template, values=None, debug=False, lazy=False, **kw):
- if request and request.debug:
- debug = True
- if values is None:
- values = {}
- values.update(kw)
- values['debug'] = debug
- values['current_db'] = db
- try:
- values['databases'] = http.db_list()
- except openerp.exceptions.AccessDenied:
- values['databases'] = None
-
- for res in ['js', 'css']:
- if res not in values:
- values[res] = manifest_list(res, db=db, debug=debug)
-
- if 'modules' not in values:
- values['modules'] = module_boot(db=db)
- values['modules'] = simplejson.dumps(values['modules'])
-
- def callback(template, values):
- registry = openerp.modules.registry.RegistryManager.get(db)
- with registry.cursor() as cr:
- view_obj = registry["ir.ui.view"]
- uid = request.uid or openerp.SUPERUSER_ID
- return view_obj.render(cr, uid, template, values)
- if lazy:
- return LazyResponse(callback, template=template, values=values)
- else:
- return callback(template, values)
-
class Home(http.Controller):
@http.route('/', type='http', auth="none")
if request.session.uid:
if kw.get('redirect'):
return werkzeug.utils.redirect(kw.get('redirect'), 303)
+ if not request.uid:
+ request.uid = request.session.uid
- html = render_bootstrap_template(request.session.db, "web.webclient_bootstrap")
- return request.make_response(html, {'Cache-Control': 'no-cache', 'Content-Type': 'text/html; charset=utf-8'})
+ menu_data = request.registry['ir.ui.menu'].load_menus(request.cr, request.uid, context=request.context)
+ return request.render('web.webclient_bootstrap', qcontext={'menu_data': menu_data})
else:
return login_redirect()
if request.httprequest.method == 'GET' and redirect and request.session.uid:
return http.redirect_with_hash(redirect)
+ if not request.uid:
+ request.uid = openerp.SUPERUSER_ID
+
values = request.params.copy()
if not redirect:
redirect = '/web?' + request.httprequest.query_string
values['redirect'] = redirect
+
+ try:
+ values['databases'] = http.db_list()
+ except openerp.exceptions.AccessDenied:
+ values['databases'] = None
+
if request.httprequest.method == 'POST':
+ old_uid = request.uid
uid = request.session.authenticate(request.session.db, request.params['login'], request.params['password'])
if uid is not False:
return http.redirect_with_hash(redirect)
+ request.uid = old_uid
values['error'] = "Wrong login/password"
- return render_bootstrap_template(request.session.db, 'web.login', values, lazy=True)
+ return request.render('web.login', values)
@http.route('/login', type='http', auth="none")
def login(self, db, login, key, redirect="/web", **kw):
return werkzeug.utils.redirect('/', 303)
return login_and_redirect(db, login, key, redirect_url=redirect)
+ @http.route([
+ '/web/js/<xmlid>',
+ '/web/js/<xmlid>/<version>',
+ ], type='http', auth='public')
+ def js_bundle(self, xmlid, version=None, **kw):
+ try:
+ bundle = AssetsBundle(xmlid)
+ except QWebTemplateNotFound:
+ return request.not_found()
+
+ response = request.make_response(bundle.js(), [('Content-Type', 'application/javascript')])
+ return make_conditional(response, bundle.last_modified, max_age=BUNDLE_MAXAGE)
+
+ @http.route([
+ '/web/css/<xmlid>',
+ '/web/css/<xmlid>/<version>',
+ ], type='http', auth='public')
+ def css_bundle(self, xmlid, version=None, **kw):
+ try:
+ bundle = AssetsBundle(xmlid)
+ except QWebTemplateNotFound:
+ return request.not_found()
+
+ response = request.make_response(bundle.css(), [('Content-Type', 'text/css')])
+ return make_conditional(response, bundle.last_modified, max_age=BUNDLE_MAXAGE)
+
class WebClient(http.Controller):
@http.route('/web/webclient/csslist', type='json', auth="none")
def jslist(self, mods=None):
return manifest_list('js', mods=mods)
- @http.route('/web/webclient/qweblist', type='json', auth="none")
- def qweblist(self, mods=None):
- return manifest_list('qweb', mods=mods)
-
- @http.route('/web/webclient/css', type='http', auth="none")
- def css(self, mods=None, db=None):
- files = list(manifest_glob('css', addons=mods, db=db))
- last_modified = get_last_modified(f[0] for f in files)
- if request.httprequest.if_modified_since and request.httprequest.if_modified_since >= last_modified:
- return werkzeug.wrappers.Response(status=304)
-
- file_map = dict(files)
-
- rx_import = re.compile(r"""@import\s+('|")(?!'|"|/|https?://)""", re.U)
- rx_url = re.compile(r"""url\s*\(\s*('|"|)(?!'|"|/|https?://|data:)""", re.U)
-
- def reader(f):
- """read the a css file and absolutify all relative uris"""
- with open(f, 'rb') as fp:
- data = fp.read().decode('utf-8')
-
- path = file_map[f]
- web_dir = os.path.dirname(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.encode('utf-8')
-
- content, checksum = concat_files((f[0] for f in files), reader)
-
- # move up all @import and @charset rules to the top
- matches = []
- def push(matchobj):
- matches.append(matchobj.group(0))
- return ''
-
- content = re.sub(re.compile("(@charset.+;$)", re.M), push, content)
- content = re.sub(re.compile("(@import.+;$)", re.M), push, content)
-
- matches.append(content)
- content = '\n'.join(matches)
-
- return make_conditional(
- request.make_response(content, [('Content-Type', 'text/css')]),
- last_modified, checksum)
-
- @http.route('/web/webclient/js', type='http', auth="none")
- def js(self, mods=None, db=None):
- files = [f[0] for f in manifest_glob('js', addons=mods, db=db)]
- last_modified = get_last_modified(files)
- if request.httprequest.if_modified_since and request.httprequest.if_modified_since >= last_modified:
- return werkzeug.wrappers.Response(status=304)
-
- content, checksum = concat_js(files)
-
- return make_conditional(
- request.make_response(content, [('Content-Type', 'application/javascript')]),
- last_modified, checksum)
-
@http.route('/web/webclient/qweb', type='http', auth="none")
def qweb(self, mods=None, db=None):
files = [f[0] for f in manifest_glob('qweb', addons=mods, db=db)]
def version_info(self):
return openerp.service.common.exp_version()
+ @http.route('/web/tests', type='http', auth="none")
+ def index(self, mod=None, **kwargs):
+ return request.render('web.qunit_suite')
+
class Proxy(http.Controller):
@http.route('/web/proxy/load', type='json', auth="none")
def manager(self, **kw):
# TODO: migrate the webclient's database manager to server side views
request.session.logout()
- js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list('js', debug=request.debug))
- css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list('css', debug=request.debug))
-
- r = html_template % {
- 'js': js,
- 'css': css,
+ return env.get_template("database_manager.html").render({
'modules': simplejson.dumps(module_boot()),
- 'init': """
- var wc = new s.web.WebClient(null, { action: 'database_manager' });
- wc.appendTo($(document.body));
- """
- }
- return r
+ })
@http.route('/web/database/get_list', type='json', auth="none")
def get_list(self):
password, db = operator.itemgetter(
'drop_pwd', 'drop_db')(
dict(map(operator.itemgetter('name', 'value'), fields)))
-
+
try:
if request.session.proxy("db").drop(password, db):
return True
return simplejson.dumps([[],[{'error': openerp.tools.ustr(e), 'title': _('Backup Database')}]])
@http.route('/web/database/restore', type='http', auth="none")
- def restore(self, db_file, restore_pwd, new_db):
+ def restore(self, db_file, restore_pwd, new_db, mode):
try:
+ copy = mode == 'copy'
data = base64.b64encode(db_file.read())
- request.session.proxy("db").restore(restore_pwd, new_db, data)
+ request.session.proxy("db").restore(restore_pwd, new_db, data, copy)
return ''
except openerp.exceptions.AccessDenied, e:
raise Exception("AccessDenied")
class Menu(http.Controller):
- @http.route('/web/menu/get_user_roots', type='json', auth="user")
- def get_user_roots(self):
- """ Return all root menu ids visible for the session user.
-
- :return: the root menu ids
- :rtype: list(int)
- """
- s = request.session
- Menus = s.model('ir.ui.menu')
- # If a menu action is defined use its domain to get the root menu items
- user_menu_id = s.model('res.users').read([s.uid], ['menu_id'],
- request.context)[0]['menu_id']
-
- menu_domain = [('parent_id', '=', False)]
- if user_menu_id:
- domain_string = s.model('ir.actions.act_window').read(
- [user_menu_id[0]], ['domain'],request.context)[0]['domain']
- if domain_string:
- menu_domain = ast.literal_eval(domain_string)
-
- return Menus.search(menu_domain, 0, False, False, request.context)
-
- @http.route('/web/menu/load', type='json', auth="user")
- def load(self):
- """ Loads all menu items (all applications and their sub-menus).
-
- :return: the menu root
- :rtype: dict('children': menu_nodes)
- """
- Menus = request.session.model('ir.ui.menu')
-
- fields = ['name', 'sequence', 'parent_id', 'action']
- menu_root_ids = self.get_user_roots()
- menu_roots = Menus.read(menu_root_ids, fields, request.context) if menu_root_ids else []
- menu_root = {
- 'id': False,
- 'name': 'root',
- 'parent_id': [-1, ''],
- 'children': menu_roots,
- 'all_menu_ids': menu_root_ids,
- }
- if not menu_roots:
- return menu_root
-
- # menus are loaded fully unlike a regular tree view, cause there are a
- # limited number of items (752 when all 6.1 addons are installed)
- menu_ids = Menus.search([('id', 'child_of', menu_root_ids)], 0, False, False, request.context)
- menu_items = Menus.read(menu_ids, fields, request.context)
- # adds roots at the end of the sequence, so that they will overwrite
- # equivalent menu items from full menu read when put into id:item
- # mapping, resulting in children being correctly set on the roots.
- menu_items.extend(menu_roots)
- menu_root['all_menu_ids'] = menu_ids # includes menu_root_ids!
-
- # make a tree using parent_id
- menu_items_map = dict(
- (menu_item["id"], menu_item) for menu_item in menu_items)
- for menu_item in menu_items:
- if menu_item['parent_id']:
- parent = menu_item['parent_id'][0]
- else:
- parent = False
- if parent in menu_items_map:
- menu_items_map[parent].setdefault(
- 'children', []).append(menu_item)
-
- # sort by sequence a tree using parent_id
- for menu_item in menu_items:
- menu_item.setdefault('children', []).sort(
- key=operator.itemgetter('sequence'))
-
- return menu_root
-
@http.route('/web/menu/load_needaction', type='json', auth="user")
def load_needaction(self, menu_ids):
""" Loads needaction counters for specific menu ids.
class Binary(http.Controller):
- @http.route('/web/binary/image', type='http', auth="user")
+ @http.route('/web/binary/image', type='http', auth="public")
def image(self, model, id, field, **kw):
last_update = '__last_update'
Model = request.session.model(model)
addons_path = http.addons_manifest['web']['addons_path']
return open(os.path.join(addons_path, 'web', 'static', 'src', 'img', image), 'rb').read()
- @http.route('/web/binary/saveas', type='http', auth="user")
+ @http.route('/web/binary/saveas', type='http', auth="public")
@serialize_exception
def saveas(self, model, field, id=None, filename_field=None, **kw):
""" Download link for files stored as binary fields.
[('Content-Type', 'application/octet-stream'),
('Content-Disposition', content_disposition(filename))])
- @http.route('/web/binary/saveas_ajax', type='http', auth="user")
+ @http.route('/web/binary/saveas_ajax', type='http', auth="public")
@serialize_exception
def saveas_ajax(self, data, token):
jdata = simplejson.loads(data)
'/logo.png',
], type='http', auth="none", cors="*")
def company_logo(self, dbname=None, **kw):
- # TODO add etag, refactor to use /image code for etag
+ imgname = 'logo.png'
+ placeholder = functools.partial(get_module_resource, 'web', 'static', 'src', 'img')
uid = None
if request.session.db:
dbname = request.session.db
uid = openerp.SUPERUSER_ID
if not dbname:
- image_data = self.placeholder('logo.png')
+ response = http.send_file(placeholder(imgname))
else:
try:
# create an empty registry
registry = openerp.modules.registry.Registry(dbname)
with registry.cursor() as cr:
- cr.execute("""SELECT c.logo_web
+ cr.execute("""SELECT c.logo_web, c.write_date
FROM res_users u
LEFT JOIN res_company c
ON c.id = u.company_id
""", (uid,))
row = cr.fetchone()
if row and row[0]:
- image_data = str(row[0]).decode('base64')
+ image_data = StringIO(str(row[0]).decode('base64'))
+ response = http.send_file(image_data, filename=imgname, mtime=row[1])
else:
- image_data = self.placeholder('nologo.png')
+ response = http.send_file(placeholder('nologo.png'))
except Exception:
- image_data = self.placeholder('logo.png')
+ response = http.send_file(placeholder(imgname))
- headers = [
- ('Content-Type', 'image/png'),
- ('Content-Length', len(image_data)),
- ]
- return request.make_response(image_data, headers)
+ return response
class Action(http.Controller):
@http.route('/web/action/load', type='json', auth="user")
- def load(self, action_id, do_not_eval=False):
+ def load(self, action_id, do_not_eval=False, additional_context=None):
Actions = request.session.model('ir.actions.actions')
value = False
try:
base_action = Actions.read([action_id], ['type'], request.context)
if base_action:
- ctx = {}
+ ctx = request.context
action_type = base_action[0]['type']
if action_type == 'ir.actions.report.xml':
ctx.update({'bin_size': True})
- ctx.update(request.context)
+ if additional_context:
+ ctx.update(additional_context)
action = request.session.model(action_type).read([action_id], False, ctx)
if action:
value = clean_action(action[0])
fields['.id'] = fields.pop('id', {'string': 'ID'})
fields_sequence = sorted(fields.iteritems(),
- key=lambda field: field[1].get('string', ''))
+ key=lambda field: openerp.tools.ustr(field[1].get('string', '')))
records = []
for field_name, field in fields_sequence:
for k, v in self.fields_info(model, export_fields).iteritems())
class ExportFormat(object):
+ raw_data = False
+
@property
def content_type(self):
""" Provides the format's content type """
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',[])
+ import_data = Model.export_data(ids, field_names, self.raw_data, context=context).get('datas',[])
if import_compat:
columns_headers = field_names
return data
class ExcelExport(ExportFormat, http.Controller):
+ # Excel needs raw data to correctly handle numbers and date values
+ raw_data = True
@http.route('/web/export/xls', type='http', auth="user")
@serialize_exception
worksheet.write(0, i, fieldname)
worksheet.col(i).width = 8000 # around 220 pixels
- style = xlwt.easyxf('align: wrap yes')
+ base_style = xlwt.easyxf('align: wrap yes')
+ date_style = xlwt.easyxf('align: wrap yes', num_format_str='YYYY-MM-DD')
+ datetime_style = xlwt.easyxf('align: wrap yes', num_format_str='YYYY-MM-DD HH:mm:SS')
for row_index, row in enumerate(rows):
for cell_index, cell_value in enumerate(row):
+ cell_style = base_style
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)
+ elif isinstance(cell_value, datetime.datetime):
+ cell_style = datetime_style
+ elif isinstance(cell_value, datetime.date):
+ cell_style = date_style
+ worksheet.write(row_index + 1, cell_index, cell_value, cell_style)
fp = StringIO()
workbook.save(fp)
sakey = Session().save_session_action(action)
debug = '?debug' if req.debug else ''
return werkzeug.utils.redirect('/web{0}#sa={1}'.format(debug, sakey))
-
+
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: