[MERGE]Merge with trunk upto revision no 931.

bzr revid: kch@tinyerp.com-20110906125440-turixz49exf0po30
This commit is contained in:
Kunal Chavda (OpenERP) 2011-09-06 18:24:40 +05:30
commit a9446c53f9
873 changed files with 3774 additions and 2520 deletions

View File

@ -1,273 +0,0 @@
#!/usr/bin/python
import datetime
import urllib
import dateutil.relativedelta
import functools
import logging
import optparse
import os
import sys
import tempfile
import time
import traceback
import uuid
import xmlrpclib
import cherrypy
import cherrypy.lib.static
import simplejson
import nonliterals
# TODO if from openerpserver use backendlocal
# from backendlocal import *
from backendrpc import *
#-----------------------------------------------------------
# Globals
#-----------------------------------------------------------
import __main__
path_root = __main__.path_root
path_addons = __main__.path_addons
cherrypy_root = None
#-----------------------------------------------------------
# Globals (wont move into a pool)
#-----------------------------------------------------------
applicationsession = {}
addons_module = {}
addons_manifest = {}
controllers_class = {}
controllers_object = {}
controllers_path = {}
#----------------------------------------------------------
# OpenERP Web RequestHandler
#----------------------------------------------------------
class CherryPyRequest(object):
""" CherryPy request handling
"""
def init(self,params):
self.params = params
# Move cherrypy thread local objects to attributes
self.applicationsession = applicationsession
self.httprequest = cherrypy.request
self.httpresponse = cherrypy.response
self.httpsession = cherrypy.session
self.httpsession_id = "cookieid"
# OpenERP session setup
self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex
host = cherrypy.config['openerp.server.host']
port = cherrypy.config['openerp.server.port']
self.session = self.httpsession.setdefault(self.session_id, OpenERPSession(host, port))
# Request attributes
self.context = self.params.pop('context', None)
self.debug = self.params.pop('debug',False) != False
class JsonRequest(CherryPyRequest):
""" JSON-RPC2 over HTTP.
Sucessful request::
--> {"jsonrpc": "2.0",
"method": "call",
"params": {"session_id": "SID",
"context": {},
"arg1": "val1" },
"id": null}
<-- {"jsonrpc": "2.0",
"result": { "res1": "val1" },
"id": null}
Request producing a error::
--> {"jsonrpc": "2.0",
"method": "call",
"params": {"session_id": "SID",
"context": {},
"arg1": "val1" },
"id": null}
<-- {"jsonrpc": "2.0",
"error": {"code": 1,
"message": "End user error message.",
"data": {"code": "codestring",
"debug": "traceback" } },
"id": null}
"""
def dispatch(self, controller, method, requestf=None, request=None):
""" Calls the method asked for by the JSON-RPC2 request
:param controller: the instance of the controller which received the request
:param method: the method which received the request
:param requestf: a file-like object containing an encoded JSON-RPC2 request
:param request: a JSON-RPC2 request
:returns: an utf8 encoded JSON-RPC2 reply
"""
response = {"jsonrpc": "2.0" }
error = None
try:
# Read POST content or POST Form Data named "request"
if requestf:
self.jsonrequest = simplejson.load(requestf, object_hook=nonliterals.non_literal_decoder)
else:
self.jsonrequest = simplejson.loads(request, object_hook=nonliterals.non_literal_decoder)
self.init(self.jsonrequest.get("params", {}))
if self.debug or 1:
print "--> %s.%s %s" % (controller.__class__.__name__, method.__name__, self.jsonrequest)
response['id'] = self.jsonrequest.get('id')
response["result"] = method(controller, self, **self.params)
except OpenERPUnboundException:
error = {
'code': 100,
'message': "OpenERP Session Invalid",
'data': {
'type': 'session_invalid',
'debug': traceback.format_exc()
}
}
except xmlrpclib.Fault, e:
error = {
'code': 200,
'message': "OpenERP Server Error",
'data': {
'type': 'server_exception',
'fault_code': e.faultCode,
'debug': "Client %s\nServer %s" % (
"".join(traceback.format_exception("", None, sys.exc_traceback)), e.faultString)
}
}
except Exception:
cherrypy.log("An error occured while handling a json request",
severity=logging.ERROR, traceback=True)
error = {
'code': 300,
'message': "OpenERP WebClient Error",
'data': {
'type': 'client_exception',
'debug': "Client %s" % traceback.format_exc()
}
}
if error:
response["error"] = error
if self.debug or 1:
print "<--", response
print
content = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder)
cherrypy.response.headers['Content-Type'] = 'application/json'
cherrypy.response.headers['Content-Length'] = len(content)
return content
def jsonrequest(f):
@cherrypy.expose
@functools.wraps(f)
def json_handler(controller):
return JsonRequest().dispatch(controller, f, requestf=cherrypy.request.body)
return json_handler
class HttpRequest(CherryPyRequest):
""" Regular GET/POST request
"""
def dispatch(self, controller, method, **kw):
self.init(kw)
akw = {}
for key in kw.keys():
if isinstance(kw[key], basestring) and len(kw[key]) < 1024:
akw[key] = kw[key]
else:
akw[key] = type(kw[key])
if self.debug or 1:
print "%s --> %s.%s %r" % (self.httprequest.method, controller.__class__.__name__, method.__name__, akw)
r = method(controller, self, **kw)
if self.debug or 1:
print "<--", 'size:', len(r)
print
return r
def httprequest(f):
# check cleaner wrapping:
# functools.wraps(f)(lambda x: JsonRequest().dispatch(x, f))
def http_handler(controller,*l, **kw):
return HttpRequest().dispatch(controller, f, **kw)
http_handler.exposed = 1
return http_handler
#-----------------------------------------------------------
# Cherrypy stuff
#-----------------------------------------------------------
class ControllerType(type):
def __init__(cls, name, bases, attrs):
super(ControllerType, cls).__init__(name, bases, attrs)
controllers_class["%s.%s" % (cls.__module__, cls.__name__)] = cls
class Controller(object):
__metaclass__ = ControllerType
class Root(object):
def __init__(self):
self.addons = {}
self._load_addons()
def _load_addons(self):
if path_addons not in sys.path:
sys.path.insert(0, path_addons)
for i in os.listdir(path_addons):
if i not in addons_module:
manifest_path = os.path.join(path_addons, i, '__openerp__.py')
if os.path.isfile(manifest_path):
manifest = eval(open(manifest_path).read())
print "Loading", i
m = __import__(i)
addons_module[i] = m
addons_manifest[i] = manifest
for k, v in controllers_class.items():
if k not in controllers_object:
o = v()
controllers_object[k] = o
if hasattr(o, '_cp_path'):
controllers_path[o._cp_path] = o
def default(self, *l, **kw):
print "default",l,kw
# handle static files
if len(l) > 2 and l[1] == 'static':
# sanitize path
p = os.path.normpath(os.path.join(*l))
p2 = os.path.join(path_addons, p)
print "p",p
print "p2",p2
return cherrypy.lib.static.serve_file(p2)
elif len(l) > 1:
for i in range(len(l), 1, -1):
ps = "/" + "/".join(l[0:i])
if ps in controllers_path:
c = controllers_path[ps]
rest = l[i:] or ['index']
meth = rest[0]
m = getattr(c, meth)
if getattr(m, 'exposed', 0):
print "Calling", ps, c, meth, m
return m(**kw)
raise cherrypy.NotFound('/' + '/'.join(l))
elif l and l[0] == 'mobile':
#for the mobile web client we are supposed to use a different url to just add '/mobile'
raise cherrypy.HTTPRedirect('/web_mobile/static/src/web_mobile.html', 301)
else:
if kw:
qs = '?' + urllib.urlencode(kw)
else:
qs = ''
raise cherrypy.HTTPRedirect('/base/webclient/home' + qs, 301)
default.exposed = True
#

View File

@ -1,402 +0,0 @@
openerp.base.data_export = function(openerp) {
openerp.base.DataExport = openerp.base.Dialog.extend({
init: function(parent, dataset) {
this._super(parent);
this.dataset = dataset;
},
start: function() {
var self = this;
self._super(false);
self.template = 'ExportTreeView';
self.dialog_title = "Export Data";
self.open({
modal: true,
width: '55%',
height: 'auto',
position: 'top',
buttons : {
"Close" : function() {
self.close();
},
"Export To File" : function() {
self.on_click_export_data();
}
},
close: function(event, ui){ self.close();}
});
self.on_show_exists_export_list();
self.$element.removeClass('ui-dialog-content ui-widget-content');
self.$element.find('#add_field').click(function() {
if ($('#field-tree-structure tr.ui-selected')) {
var fld = self.$element.find('#field-tree-structure tr.ui-selected').find('a');
for (var i = 0; i < fld.length; i++) {
var id = $(fld[i]).attr('id').split('-')[1];
var string = $(fld[i]).attr('string');
self.add_field(id, string);
}
self.$element.find('#field-tree-structure tr').removeClass('ui-selected');
}
});
self.$element.find('#remove_field').click(function() {
self.$element.find('#fields_list option:selected').remove();
});
self.$element.find('#remove_all_field').click(function() {
self.$element.find('#fields_list option').remove();
});
self.$element.find('#export_new_list').click(function() {
self.on_show_save_list();
});
var import_comp = self.$element.find('#import_compat option:selected').val(),
params = {
import_compat: parseInt(import_comp)
};
self.rpc('/base/export/get_fields', { model: self.dataset.model, params: params }, self.on_show_data);
self.$element.find('#import_compat').change(function() {
self.$element.find('#fields_list option').remove();
self.$element.find('#field-tree-structure').remove();
var import_comp = self.$element.find("#import_compat option:selected").val();
if (import_comp) {
var params = {
import_compat: parseInt(import_comp)
}
self.rpc("/base/export/get_fields", { model: self.dataset.model, params: params}, self.on_show_data);
}
});
},
on_show_exists_export_list: function() {
var self = this;
if (self.$element.find('#saved_export_list').is(':hidden')) {
self.$element.find('#ExistsExportList').show();
} else {
this.rpc('/base/export/exist_export_lists', { 'model': this.dataset.model}, function(export_list) {
if (export_list.length) {
self.$element.find('#ExistsExportList').append(QWeb.render('Exists.ExportList', {'existing_exports': export_list}));
self.$element.find('#saved_export_list').change(function() {
self.$element.find('#fields_list option').remove();
var export_id = self.$element.find('#saved_export_list option:selected').val();
if (export_id) {
self.rpc('/base/export/namelist', {'model': self.dataset.model, export_id: parseInt(export_id)}, self.do_load_export_field);
}
});
self.$element.find('#delete_export_list').click(function() {
var select_exp = self.$element.find('#saved_export_list option:selected');
if (select_exp.val()) {
self.rpc('/base/export/delete_export', { export_id: parseInt(select_exp.val())}, {});
select_exp.remove();
if (self.$element.find('#saved_export_list option').length <= 1) {
self.$element.find('#ExistsExportList').hide();
}
}
});
}
});
}
},
do_load_export_field: function(field_list) {
var export_node = this.$element.find("#fields_list");
for (var key in field_list) {
export_node.append(new Option(field_list[key], key));
}
},
on_show_save_list: function() {
var self = this;
var current_node = self.$element.find("#savenewlist");
if (!(current_node.find("label")).length) {
current_node.append(QWeb.render('ExportNewList'));
current_node.find("#add_export_list").click(function() {
var value = current_node.find("#savelist_name").val();
if (value) {
self.do_save_export_list(value);
} else {
alert("Pleae Enter Save Field List Name");
}
});
} else {
if (current_node.is(':hidden')) {
current_node.show();
current_node.find("#savelist_name").val("");
} else {
current_node.hide();
}
}
},
do_save_export_list: function(value) {
var self = this;
var export_field = self.get_fields();
if (export_field.length) {
self.rpc("/base/export/save_export_lists", {"model": self.dataset.model, "name":value, "field_list":export_field}, function(exp_id) {
if (exp_id) {
if (self.$element.find("#saved_export_list").length > 0) {
self.$element.find("#saved_export_list").append(new Option(value, exp_id));
} else {
self.on_show_exists_export_list();
}
if (self.$element.find("#saved_export_list").is(":hidden")) {
self.on_show_exists_export_list();
}
}
});
self.on_show_save_list();
self.$element.find("#fields_list option").remove();
}
},
on_click: function(id, result) {
var self = this;
self.field_id = id.split("-")[1];
var is_loaded = 0;
_.each(result, function(record) {
if (record['id'] == self.field_id && (record['children']).length >= 1) {
var model = record['params']['model'],
prefix = record['params']['prefix'],
name = record['params']['name'];
$(record['children']).each(function(e, childid) {
if (self.$element.find("tr[id='treerow-" + childid + "']").length > 0) {
if (self.$element.find("tr[id='treerow-" + childid + "']").is(':hidden')) {
is_loaded = -1;
} else {
is_loaded++;
}
}
});
if (is_loaded == 0) {
if (self.$element.find("tr[id='treerow-" + self.field_id +"']").find('img').attr('src') === '/base/static/src/img/expand.gif') {
if (model) {
var import_comp = self.$element.find("#import_compat option:selected").val();
var params = {
import_compat: parseInt(import_comp),
parent_field_type : record['field_type']
}
self.rpc("/base/export/get_fields", {
model: model,
prefix: prefix,
name: name,
field_parent : self.field_id,
params: params
}, function(results) {
self.on_show_data(results);
});
}
}
} else if (is_loaded > 0) {
self.showcontent(self.field_id, true);
} else {
self.showcontent(self.field_id, false);
}
}
});
},
on_show_data: function(result) {
var self = this;
var imp_cmpt = parseInt(self.$element.find("#import_compat option:selected").val());
var current_tr = self.$element.find("tr[id='treerow-" + self.field_id + "']");
if (current_tr.length >= 1) {
current_tr.find('img').attr('src','/base/static/src/img/collapse.gif');
current_tr.after(QWeb.render('ExportTreeView-Secondary.children', {'fields': result}));
} else {
self.$element.find('#left_field_panel').append(QWeb.render('ExportTreeView-Secondary', {'fields': result}));
}
_.each(result, function(record) {
if ((record.field_type == "one2many") && imp_cmpt) {
var o2m_fld = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
o2m_fld.addClass("oe_export_readonlyfield");
}
if ((record.required == true) || record.required == "True") {
var required_fld = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
required_fld.addClass("oe_export_requiredfield");
}
self.$element.find("img[id='parentimg-" + record.id +"']").click(function() {
self.on_click(this.id, result);
});
self.$element.find("tr[id='treerow-" + record.id + "']").click(function(e) {
if (e.shiftKey == true) {
var frst_click, scnd_click = '';
if (self.row_index == 0) {
self.row_index = this.rowIndex;
frst_click = self.$element.find("tr[id^='treerow-']")[self.row_index-1];
$(frst_click).addClass("ui-selected");
} else {
if (this.rowIndex >=self.row_index) {
for (i = (self.row_index-1); i < this.rowIndex; i++) {
scnd_click = self.$element.find("tr[id^='treerow-']")[i];
if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
$(scnd_click).addClass("ui-selected");
}
}
} else {
for (i = (self.row_index-1); i >= (this.rowIndex-1); i--) {
scnd_click = self.$element.find("tr[id^='treerow-']")[i];
if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
$(scnd_click).addClass("ui-selected");
}
}
}
}
}
self.row_index = this.rowIndex;
self.$element.find("tr[id='treerow-" + record.id + "']").keyup(function(e) {
self.row_index = 0;
});
var o2m_selection = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
if ($(o2m_selection).hasClass("oe_export_readonlyfield")) {
return false;
}
var selected = self.$element.find("tr.ui-selected");
if ($(this).hasClass("ui-selected") && (e.ctrlKey == true)) {
$(this).find('a').blur();
$(this).removeClass("ui-selected");
} else if ($(this).hasClass("ui-selected") && (e.ctrlKey == false) && (e.shiftKey == false)) {
selected.find('a').blur();
selected.removeClass("ui-selected");
$(this).find('a').focus();
$(this).addClass("ui-selected");
} else if (!$(this).hasClass("ui-selected") && (e.ctrlKey == false) && (e.shiftKey == false)) {
selected.find('a').blur();
selected.removeClass("ui-selected");
$(this).find('a').focus();
$(this).addClass("ui-selected");
} else if (!$(this).hasClass("ui-selected") && (e.ctrlKey == true)) {
$(this).find('a').focus();
$(this).addClass("ui-selected");
}
return false;
});
self.$element.find("tr[id='treerow-" + record.id + "']").keydown(function(e) {
var keyCode = e.keyCode || e.which;
arrow = {left: 37, up: 38, right: 39, down: 40 };
switch (keyCode) {
case arrow.left:
if ($(this).find('img').attr('src') === '/base/static/src/img/collapse.gif') {
self.on_click(this.id, result);
}
break;
case arrow.up:
var elem = this;
$(elem).removeClass("ui-selected");
while ($(elem).prev().is(":visible") == false) {
elem = $(elem).prev();
}
if (!$(elem).prev().find('#tree-column').hasClass("oe_export_readonlyfield")) {
$(elem).prev().addClass("ui-selected");
}
$(elem).prev().find('a').focus();
break;
case arrow.right:
if ($(this).find('img').attr('src') == '/base/static/src/img/expand.gif') {
self.on_click(this.id, result);
}
break;
case arrow.down:
var elem = this;
$(elem).removeClass("ui-selected");
while($(elem).next().is(":visible") == false) {
elem = $(elem).next();
}
if (!$(elem).next().find('#tree-column').hasClass("oe_export_readonlyfield")) {
$(elem).next().addClass("ui-selected");
}
$(elem).next().find('a').focus();
break;
}
});
self.$element.find("tr[id='treerow-" + record.id + "']").dblclick(function(e) {
var $o2m_selection = self.$element.find("tr[id^='treerow-" + record.id + "']").find('#tree-column');
if (!$o2m_selection.hasClass("oe_export_readonlyfield")) {
var field_id = $(this).find("a").attr("id");
if (field_id) {
self.add_field(field_id.split('-')[1], $(this).find("a").attr("string"));
}
}
});
});
self.$element.find('#fields_list').mouseover(function(event) {
if (event.relatedTarget) {
if (event.relatedTarget.attributes['id'] && event.relatedTarget.attributes['string']) {
var field_id = event.relatedTarget.attributes["id"]["value"];
if (field_id && field_id.split("-")[0] === 'export') {
if (!self.$element.find("tr[id='treerow-" + field_id.split("-")[1] + "']").find('#tree-column').hasClass("oe_export_readonlyfield")) {
self.add_field(field_id.split("-")[1], event.relatedTarget.attributes["string"]["value"]);
}
}
}
}
});
},
showcontent: function(id, flag) {
// show & hide the contents
var first_child = this.$element.find("tr[id='treerow-" + id + "']").find('img');
if (flag) {
first_child.attr('src', '/base/static/src/img/expand.gif');
}
else {
first_child.attr('src', '/base/static/src/img/collapse.gif');
}
var child_field = this.$element.find("tr[id^='treerow-" + id +"/']");
var child_len = (id.split("/")).length + 1;
for (var i = 0; i < child_field.length; i++) {
if (flag) {
$(child_field[i]).hide();
} else {
if (child_len == (child_field[i].id.split("/")).length) {
if ($(child_field[i]).find('img').attr('src') == '/base/static/src/img/collapse.gif') {
$(child_field[i]).find('img').attr('src', '/base/static/src/img/expand.gif');
}
$(child_field[i]).show();
}
}
}
},
add_field: function(field_id, string) {
var field_list = this.$element.find('#fields_list');
if (this.$element.find("#fields_list option[value='" + field_id + "']") && !this.$element.find("#fields_list option[value='" + field_id + "']").length) {
field_list.append(new Option(string, field_id));
}
},
get_fields: function() {
var export_field = [];
this.$element.find("#fields_list option").each(function() {
export_field.push($(this).val());
});
if (!export_field.length) {
alert('Please select fields to save export list...');
}
return export_field;
},
on_click_export_data: function() {
var self = this;
var export_field = {};
var flag = true;
self.$element.find("#fields_list option").each(function() {
export_field[$(this).val()] = $(this).text();
flag = false;
});
if (flag) {
alert('Please select fields to export...');
return;
}
var import_comp = self.$element.find("#import_compat option:selected").val(),
export_format = self.$element.find("#export_format").val();
self.rpc("/base/export/export_data", {
model: self.dataset.model,
fields: export_field,
ids: self.dataset.ids,
domain: self.dataset.domain,
import_compat: parseInt(import_comp),
export_format: export_format
}, function(data) {
window.location = "data:text/csv/excel;charset=utf8," + data;
self.close();
});
},
close: function() {
$(this.$dialog).remove();
this._super();
}
});
};

View File

@ -1,54 +0,0 @@
$(document).ready(function () {
var openerp;
module('base-formats', {
setup: function () {
openerp = window.openerp.init();
window.openerp.base.core(openerp);
window.openerp.base.dates(openerp);
window.openerp.base.formats(openerp);
}
});
test("format_datetime", function () {
var date = openerp.base.str_to_datetime("2009-05-04 12:34:23");
var str = openerp.base.format_value(date, {type:"datetime"});
equal(str, date.toString("M/d/yyyy h:mm:ss tt"));
});
test("format_date", function () {
var date = openerp.base.str_to_datetime("2009-05-04 12:34:23");
var str = openerp.base.format_value(date, {type:"date"});
equal(str, date.toString("M/d/yyyy"));
});
test("format_time", function () {
var date = openerp.base.str_to_datetime("2009-05-04 12:34:23");
var str = openerp.base.format_value(date, {type:"time"});
equal(str, date.toString("h:mm:ss tt"));
});
test("format_float", function () {
var fl = 12.1234;
var str = openerp.base.format_value(fl, {type:"float"});
equal(str, "12.12");
});
test("parse_datetime", function () {
var val = openerp.base.str_to_datetime("2009-05-04 12:34:23");
var res = openerp.base.parse_value(val.toString("M/d/yyyy h:mm:ss tt"), {type:"datetime"});
equal(val.toString("M/d/yyyy h:mm:ss tt"), res.toString("M/d/yyyy h:mm:ss tt"));
});
test("parse_date", function () {
var val = openerp.base.str_to_date("2009-05-04");
var res = openerp.base.parse_value(val.toString("M/d/yyyy"), {type:"date"});
equal(val.toString("M/d/yyyy"), res.toString("M/d/yyyy"));
});
test("parse_time", function () {
var val = openerp.base.str_to_time("12:34:23");
var res = openerp.base.parse_value(val.toString("h:mm:ss tt"), {type:"time"});
equal(val.toString("h:mm:ss tt"), res.toString("h:mm:ss tt"));
});
test("parse_float", function () {
var str = "134,112.1234";
var val = openerp.base.parse_value(str, {type:"float"});
equal(val, 134112.1234);
var str = "-134,112.1234";
var val = openerp.base.parse_value(str, {type:"float"});
equal(val, -134112.1234);
});
});

View File

@ -1,33 +0,0 @@
$(document).ready(function () {
var openerp;
module('Registry', {
setup: function () {
openerp = window.openerp.init(true);
window.openerp.base.core(openerp);
openerp.base.Foo = {};
openerp.base.Bar = {};
}
});
test('key fetch', function () {
var reg = new openerp.base.Registry({
foo: 'openerp.base.Foo',
bar: 'openerp.base.Bar',
quux: 'openerp.base.Quux'
});
strictEqual(reg.get_object('foo'), openerp.base.Foo);
raises(function () { reg.get_object('qux'); },
openerp.base.KeyNotFound,
"Unknown keys should raise KeyNotFound");
raises(function () { reg.get_object('quux'); },
openerp.base.ObjectNotFound,
"Incorrect file paths should raise ObjectNotFound");
});
test('key set', function () {
var reg = new openerp.base.Registry();
reg.add('foo', 'openerp.base.Foo')
.add('bar', 'openerp.base.Bar');
strictEqual(reg.get_object('bar'), openerp.base.Bar);
});
});

View File

@ -1,53 +0,0 @@
<!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="/base/static/src/img/favicon.ico" type="image/x-icon"/>
<link rel="stylesheet" href="/base/static/lib/qunit/qunit-2011-23-22.css">
<script src="/base/static/lib/qunit/qunit-2011-23-22.js" type="text/javascript"></script>
<script src="/base/static/lib/underscore/underscore.js" type="text/javascript"></script>
<script src="/base/static/lib/underscore/underscore.string.js" type="text/javascript"></script>
<!-- jquery -->
<script src="/base/static/lib/jquery/jquery-1.6.2.js"></script>
<script src="/base/static/lib/jquery.ui/js/jquery-ui-1.8.9.custom.min.js"></script>
<script src="/base/static/lib/datejs/globalization/en-US.js"></script>
<script src="/base/static/lib/datejs/core.js"></script>
<script src="/base/static/lib/datejs/parser.js"></script>
<script src="/base/static/lib/datejs/sugarpak.js"></script>
<script src="/base/static/lib/datejs/extras.js"></script>
<script src="/base/static/lib/qweb/qweb.js"></script>
<script src="/base/static/src/js/boot.js"></script>
<script src="/base/static/src/js/core.js"></script>
<script src="/base/static/src/js/dates.js"></script>
<script src="/base/static/src/js/formats.js"></script>
<script src="/base/static/src/js/chrome.js"></script>
<script src="/base/static/src/js/data.js"></script>
<script src="/base/static/src/js/views.js"></script>
<script src="/base/static/src/js/search.js"></script>
<script src="/base/static/src/js/form.js"></script>
<script src="/base/static/src/js/list.js"></script>
<script type="text/javascript">
QWeb.add_template('/base/static/src/xml/base.xml');
</script>
</head>
<body id="oe" class="openerp">
<h1 id="qunit-header">OpenERP Base Test Suite</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture"></div>
</body>
<script type="text/javascript" src="/base/static/test/class.js"></script>
<script type="text/javascript" src="/base/static/test/registry.js"></script>
<script type="text/javascript" src="/base/static/test/form.js"></script>
<script type="text/javascript" src="/base/static/test/list-utils.js"></script>
<script type="text/javascript" src="/base/static/test/formats.js"></script>
</html>

View File

@ -1,6 +0,0 @@
{
"name" : "OpenERP Web base Diagram",
"version" : "2.0",
"depends" : [],
'active': False,
}

View File

@ -1,183 +0,0 @@
/*---------------------------------------------------------
* OpenERP base library
*---------------------------------------------------------*/
openerp.base.diagram = function (openerp) {
openerp.base.views.add('diagram', 'openerp.base.DiagramView');
openerp.base.DiagramView = openerp.base.Widget.extend({
init: function(view_manager, session, element_id, dataset, view_id){
this._super(session, element_id);
this.view_manager = view_manager;
this.dataset = dataset;
this.model = dataset.model;
this.view_id = view_id;
this.name = "";
this.domain = this.dataset._domain ? this.dataset._domain: [];
this.context = {};
this.ids = this.dataset.ids;
console.log('data set>>',this.dataset)
},
start: function() {
this.rpc("/base_diagram/diagram/load", {"model": this.model, "view_id": this.view_id}, this.on_loaded);
},
toTitleCase: function(str) {
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
},
on_loaded: function(result) {
var self = this;
if(this.ids && this.ids.length) {
this.id = this.ids[0];
}
this.fields_view = result.fields_view;
this.view_id = this.fields_view.view_id;
this.name = this.fields_view.name;
this.fields = this.fields_view.fields;
var children = this.fields_view.arch.children;
/*
* For Nodes (Fields)
*/
this.node = '';
this.bgcolor = '';
this.shape = '';
this.visible_fields_nodes = [];
this.invisible_fields_nodes = [];
this.fields_nodes_string = [];
/*
* For Arraows(Connector)
*/
this.connector = '';
this.src_node = '';
this.des_node = '';
this.connector_fields = [];
this.fields_connector_string = [];
for(ch in children) {
if(children[ch]['tag'] == 'node') {
this.node = children[ch]['attrs']['object'];
this.bgcolor = children[ch]['attrs']['bgcolor'] || '';
this.shape = children[ch]['attrs']['shape'] || '';
for(node_chld in children[ch]['children']) {
if (children[ch]['children'][node_chld]['tag'] = 'field') {
var ch_name = children[ch]['children'][node_chld]['attrs']['name'];
if (children[ch]['children'][node_chld]['attrs']['invisible']) {
if (children[ch]['children'][node_chld]['attrs']['invisible'] == 1 && children[ch]['children'][node_chld]['attrs']['invisible'] == '1') {
this.invisible_fields_nodes.push(ch_name)
}
}
else {
this.visible_fields_nodes.push(ch_name);
var ch_node_string = this.fields[ch_name]['string'] || this.toTitleCase(ch_name);
this.fields_nodes_string.push(ch_node_string)
}
}
}
} else if(children[ch]['tag'] == 'arrow') {
this.connector = children[ch]['attrs']['object'];
this.src_node = children[ch]['attrs']['source'];
this.des_node = children[ch]['attrs']['destination'];
for (arrow_chld in children[ch]['children']) {
if (children[ch]['children'][arrow_chld]['tag'] = 'field') {
var arr_ch_name = children[ch]['children'][arrow_chld]['attrs']['name'];
var ch_node_string = this.fields[arr_ch_name]['string'] || this.toTitleCase(arr_ch_name);
this.fields_connector_string.push(ch_node_string);
this.connector_fields.push(arr_ch_name);
}
}
}
}
this.$element.html(QWeb.render("DiagramView", {"fields_view": this.fields_view}));
if(this.id) {
this.rpc(
'/base_diagram/diagram/get_diagram_info',
{
'id': this.id,
'model': this.model,
'bgcolor': this.bgcolor,
'shape': this.shape,
'node': this.node,
'connector': this.connector,
'src_node': this.src_node,
'des_node': this.des_node,
'visible_node_fields': this.visible_fields_nodes,
'invisible_node_fields': this.invisible_fields_nodes,
'node_fields_string': this.fields_nodes_string,
'connector_fields': this.connector_fields,
'connector_fields_string': this.fields_connector_string
},
function(result) {
self.draw_diagram(result);
}
)
}
},
draw_diagram: function(result) {
console.log('this>>>',this)
var g = new Graph();
// var raphel = new
this.in_transition_field = result['in_transition_field'];
this.out_transition_field = result['out_transition_field'];
var res_nodes = result['nodes'];
var res_connectors = result['conn'];
var render = function(r, n) {
var set;
if (n.node.shape == 'ellipse') {
set = r.set().push(
r.ellipse(n.node.x - 30, n.node.y - 13, 40, 40).attr({
"fill": n.node.color,
r: "12px",
"stroke-width": n.distance == 0 ? "3px" : "1px"
})).push(r.text(n.node.x - 30, n.node.y - 10, (n.label || n.id)));
} else {
set = r.set().push(
r.rect(n.node.x-30, n.node.y-13, 60, 44).attr({"fill": n.node.color, r : "12px", "stroke-width" : n.distance == 0 ? "3px" : "1px" })).push(
r.text(n.point[0], n.point[1] + 10, (n.label || n.id) + "\n(" + (n.distance == undefined ? "Infinity" : n.distance) + ")"));
}
return set;
};
for(nd in res_nodes) {
var res_node = res_nodes[nd];
g.addNode(res_node['name'],
{
node: res_node,
render: render
});
}
for(cr in res_connectors) {
var res_connector = res_connectors[cr];
g.addEdge(res_connector['source'], res_connector['destination']);
}
var layouter = new Graph.Layout.Spring(g);
layouter.layout();
var renderer = new Graph.Renderer.Raphael('dia-canvas', g, 800, 800);
renderer.draw();
},
do_show: function () {
this.$element.show();
},
do_hide: function () {
this.$element.hide();
}
});
};
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:

View File

@ -1,5 +1,5 @@
{
"name" : "OpenERP Web base",
"name" : "web",
"version" : "2.0",
"depends" : [],
'active': True,

View File

@ -1,5 +1,4 @@
#!/usr/bin/python
# TODO if from openerpserver use backendlocal
# from backendlocal import *
from backendrpc import *
from dispatch import *

View File

@ -1,22 +1,9 @@
#!/usr/bin/python
import datetime
import urllib
import dateutil.relativedelta
import functools
import logging
import optparse
import os
import sys
import tempfile
import time
import traceback
import uuid
import xmlrpclib
import cherrypy
import cherrypy.lib.static
import simplejson
import nonliterals
#----------------------------------------------------------
# OpenERPSession RPC openerp backend access

View File

@ -0,0 +1,429 @@
#!/usr/bin/python
from __future__ import with_statement
import functools
import logging
import os
import sys
import traceback
import uuid
import xmlrpclib
import simplejson
import werkzeug.datastructures
import werkzeug.exceptions
import werkzeug.urls
import werkzeug.utils
import werkzeug.wrappers
import werkzeug.wsgi
import ast
import nonliterals
import http
# import backendlocal as backend
import backendrpc as backend
__all__ = ['Root', 'jsonrequest', 'httprequest', 'Controller',
'WebRequest', 'JsonRequest', 'HttpRequest']
#-----------------------------------------------------------
# Globals (wont move into a pool)
#-----------------------------------------------------------
applicationsession = {}
addons_module = {}
addons_manifest = {}
controllers_class = {}
controllers_object = {}
controllers_path = {}
#----------------------------------------------------------
# OpenERP Web RequestHandler
#----------------------------------------------------------
class WebRequest(object):
""" Parent class for all OpenERP Web request types, mostly deals with
initialization and setup of the request object (the dispatching itself has
to be handled by the subclasses)
:param request: a wrapped werkzeug Request object
:type request: :class:`werkzeug.wrappers.BaseRequest`
:param config: configuration object
.. attribute:: applicationsession
an application-wide :class:`~collections.Mapping`
.. attribute:: httprequest
the original :class:`werkzeug.wrappers.Request` object provided to the
request
.. attribute:: httpsession
a :class:`~collections.Mapping` holding the HTTP session data for the
current http session
.. attribute:: config
config parameter provided to the request object
.. attribute:: params
:class:`~collections.Mapping` of request parameters, not generally
useful as they're provided directly to the handler method as keyword
arguments
.. attribute:: session_id
opaque identifier for the :class:`backend.OpenERPSession` instance of
the current request
.. attribute:: session
:class:`~backend.OpenERPSession` instance for the current request
.. attribute:: context
:class:`~collections.Mapping` of context values for the current request
.. attribute:: debug
``bool``, indicates whether the debug mode is active on the client
"""
def __init__(self, request, config):
self.applicationsession = applicationsession
self.httprequest = request
self.httpresponse = None
self.httpsession = request.session
self.config = config
def init(self, params):
self.params = dict(params)
# OpenERP session setup
self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex
self.session = self.httpsession.setdefault(
self.session_id, backend.OpenERPSession(
self.config.server_host, self.config.server_port))
self.context = self.params.pop('context', None)
self.debug = self.params.pop('debug', False) != False
class JsonRequest(WebRequest):
""" JSON-RPC2 over HTTP.
Sucessful request::
--> {"jsonrpc": "2.0",
"method": "call",
"params": {"session_id": "SID",
"context": {},
"arg1": "val1" },
"id": null}
<-- {"jsonrpc": "2.0",
"result": { "res1": "val1" },
"id": null}
Request producing a error::
--> {"jsonrpc": "2.0",
"method": "call",
"params": {"session_id": "SID",
"context": {},
"arg1": "val1" },
"id": null}
<-- {"jsonrpc": "2.0",
"error": {"code": 1,
"message": "End user error message.",
"data": {"code": "codestring",
"debug": "traceback" } },
"id": null}
"""
def dispatch(self, controller, method, requestf=None, request=None):
""" Calls the method asked for by the JSON-RPC2 request
:param controller: the instance of the controller which received the request
:param method: the method which received the request
:param requestf: a file-like object containing an encoded JSON-RPC2 request
:param request: a JSON-RPC2 request
:returns: an utf8 encoded JSON-RPC2 reply
"""
response = {"jsonrpc": "2.0" }
error = None
try:
# Read POST content or POST Form Data named "request"
if requestf:
self.jsonrequest = simplejson.load(requestf, object_hook=nonliterals.non_literal_decoder)
else:
self.jsonrequest = simplejson.loads(request, object_hook=nonliterals.non_literal_decoder)
self.init(self.jsonrequest.get("params", {}))
if self.debug or 1:
print "--> %s.%s %s" % (controller.__class__.__name__, method.__name__, self.jsonrequest)
response['id'] = self.jsonrequest.get('id')
response["result"] = method(controller, self, **self.params)
except backend.OpenERPUnboundException:
error = {
'code': 100,
'message': "OpenERP Session Invalid",
'data': {
'type': 'session_invalid',
'debug': traceback.format_exc()
}
}
except xmlrpclib.Fault, e:
error = {
'code': 200,
'message': "OpenERP Server Error",
'data': {
'type': 'server_exception',
'fault_code': e.faultCode,
'debug': "Client %s\nServer %s" % (
"".join(traceback.format_exception("", None, sys.exc_traceback)), e.faultString)
}
}
except Exception:
logging.getLogger('openerp.JSONRequest.dispatch').exception\
("An error occured while handling a json request")
error = {
'code': 300,
'message': "OpenERP WebClient Error",
'data': {
'type': 'client_exception',
'debug': "Client %s" % traceback.format_exc()
}
}
if error:
response["error"] = error
if self.debug or 1:
print "<--", response
print
content = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder)
return werkzeug.wrappers.Response(
content, headers=[('Content-Type', 'application/json'),
('Content-Length', len(content))])
def jsonrequest(f):
""" Decorator marking the decorated method as being a handler for a
JSON-RPC request (the exact request path is specified via the
``$(Controller._cp_path)/$methodname`` combination.
If the method is called, it will be provided with a :class:`JsonRequest`
instance and all ``params`` sent during the JSON-RPC request, apart from
the ``session_id``, ``context`` and ``debug`` keys (which are stripped out
beforehand)
"""
@functools.wraps(f)
def json_handler(controller, request, config):
return JsonRequest(request, config).dispatch(
controller, f, requestf=request.stream)
json_handler.exposed = True
return json_handler
class HttpRequest(WebRequest):
""" Regular GET/POST request
"""
def dispatch(self, controller, method):
params = dict(self.httprequest.args)
params.update(self.httprequest.form)
params.update(self.httprequest.files)
self.init(params)
akw = {}
for key, value in self.httprequest.args.iteritems():
if isinstance(value, basestring) and len(value) < 1024:
akw[key] = value
else:
akw[key] = type(value)
if self.debug or 1:
print "%s --> %s.%s %r" % (self.httprequest.method, controller.__class__.__name__, method.__name__, akw)
r = method(controller, self, **self.params)
if self.debug or 1:
if isinstance(r, werkzeug.wrappers.BaseResponse):
print '<--', r
else:
print "<--", 'size:', len(r)
print
return r
def make_response(self, data, headers=None, cookies=None):
""" Helper for non-HTML responses, or HTML responses with custom
response headers or cookies.
While handlers can just return the HTML markup of a page they want to
send as a string if non-HTML data is returned they need to create a
complete response object, or the returned data will not be correctly
interpreted by the clients.
:param basestring data: response body
:param headers: HTTP headers to set on the response
:type headers: ``[(name, value)]``
:param collections.Mapping cookies: cookies to set on the client
"""
response = werkzeug.wrappers.Response(data, headers=headers)
if cookies:
for k, v in cookies.iteritems():
response.set_cookie(k, v)
return response
def not_found(self, description=None):
""" Helper for 404 response, return its result from the method
"""
return werkzeug.exceptions.NotFound(description)
def httprequest(f):
""" Decorator marking the decorated method as being a handler for a
normal HTTP request (the exact request path is specified via the
``$(Controller._cp_path)/$methodname`` combination.
If the method is called, it will be provided with a :class:`HttpRequest`
instance and all ``params`` sent during the request (``GET`` and ``POST``
merged in the same dictionary), apart from the ``session_id``, ``context``
and ``debug`` keys (which are stripped out beforehand)
"""
@functools.wraps(f)
def http_handler(controller, request, config):
return HttpRequest(request, config).dispatch(controller, f)
http_handler.exposed = True
return http_handler
class ControllerType(type):
def __init__(cls, name, bases, attrs):
super(ControllerType, cls).__init__(name, bases, attrs)
controllers_class["%s.%s" % (cls.__module__, cls.__name__)] = cls
class Controller(object):
__metaclass__ = ControllerType
class Root(object):
"""Root WSGI application for the OpenERP Web Client.
:param options: mandatory initialization options object, must provide
the following attributes:
``server_host`` (``str``)
hostname of the OpenERP server to dispatch RPC to
``server_port`` (``int``)
RPC port of the OpenERP server
``serve_static`` (``bool | None``)
whether this application should serve the various
addons's static files
``storage_path`` (``str``)
filesystem path where HTTP session data will be stored
``dbfilter`` (``str``)
only used in case the list of databases is requested
by the server, will be filtered by this pattern
"""
def __init__(self, options):
self.root = werkzeug.urls.Href('/web/webclient/home')
self.config = options
self.session_cookie = 'sessionid'
self.addons = {}
static_dirs = self._load_addons()
if options.serve_static:
self.dispatch = werkzeug.wsgi.SharedDataMiddleware(
self.dispatch, static_dirs)
if options.session_storage:
if not os.path.exists(options.session_storage):
os.mkdir(options.session_storage, 0700)
self.session_storage = options.session_storage
def __call__(self, environ, start_response):
""" Handle a WSGI request
"""
return self.dispatch(environ, start_response)
def dispatch(self, environ, start_response):
"""
Performs the actual WSGI dispatching for the application, may be
wrapped during the initialization of the object.
Call the object directly.
"""
request = werkzeug.wrappers.Request(environ)
request.parameter_storage_class = werkzeug.datastructures.ImmutableDict
if request.path == '/':
return werkzeug.utils.redirect(
self.root(request.args), 301)(
environ, start_response)
elif request.path == '/mobile':
return werkzeug.utils.redirect(
'/web_mobile/static/src/web_mobile.html', 301)(
environ, start_response)
handler = self.find_handler(*(request.path.split('/')[1:]))
if not handler:
response = werkzeug.exceptions.NotFound()
else:
with http.session(request, self.session_storage, self.session_cookie) as session:
result = handler(
request, self.config)
if isinstance(result, basestring):
response = werkzeug.wrappers.Response(
result, headers=[('Content-Type', 'text/html; charset=utf-8'),
('Content-Length', len(result))])
else:
response = result
response.set_cookie(self.session_cookie, session.sid)
return response(environ, start_response)
def _load_addons(self):
"""
Loads all addons at the specified addons path, returns a mapping of
static URLs to the corresponding directories
"""
statics = {}
addons_path = self.config.addons_path
if addons_path not in sys.path:
sys.path.insert(0, addons_path)
for module in os.listdir(addons_path):
if module not in addons_module:
manifest_path = os.path.join(addons_path, module, '__openerp__.py')
if os.path.isfile(manifest_path):
manifest = ast.literal_eval(open(manifest_path).read())
print "Loading", module
m = __import__(module)
addons_module[module] = m
addons_manifest[module] = manifest
statics['/%s/static' % module] = \
os.path.join(addons_path, module, 'static')
for k, v in controllers_class.items():
if k not in controllers_object:
o = v()
controllers_object[k] = o
if hasattr(o, '_cp_path'):
controllers_path[o._cp_path] = o
return statics
def find_handler(self, *l):
"""
Tries to discover the controller handling the request for the path
specified by the provided parameters
:param l: path sections to a controller or controller method
:returns: a callable matching the path sections, or ``None``
:rtype: ``Controller | None``
"""
if len(l) > 1:
for i in range(len(l), 1, -1):
ps = "/" + "/".join(l[0:i])
if ps in controllers_path:
c = controllers_path[ps]
rest = l[i:] or ['index']
meth = rest[0]
m = getattr(c, meth)
if getattr(m, 'exposed', False):
print "Dispatching to", ps, c, meth, m
return m
return None

25
addons/web/common/http.py Normal file
View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
import contextlib
import werkzeug.contrib.sessions
STORES = {}
@contextlib.contextmanager
def session(request, storage_path, session_cookie='sessionid'):
session_store = STORES.get(storage_path)
if not session_store:
session_store = werkzeug.contrib.sessions.FilesystemSessionStore(
storage_path)
STORES[storage_path] = session_store
sid = request.cookies.get(session_cookie)
if sid:
request.session = session_store.get(sid)
else:
request.session = session_store.new()
yield request.session
session_store.save(request.session)

View File

@ -7,8 +7,6 @@ can't be sent there themselves).
import binascii
import hashlib
import simplejson.encoder
import time
import datetime
__all__ = ['Domain', 'Context', 'NonLiteralEncoder, non_literal_decoder', 'CompoundDomain', 'CompoundContext']

View File

@ -0,0 +1 @@
import main

View File

@ -3,6 +3,7 @@
import base64
import csv
import glob
import itertools
import operator
import os
import re
@ -10,16 +11,15 @@ import simplejson
import textwrap
import xmlrpclib
import time
import webrelease
from xml.etree import ElementTree
from cStringIO import StringIO
import cherrypy
import base.common as openerpweb
import base.common.ast
import base.common.nonliterals
openerpweb.ast = base.common.ast
openerpweb.nonliterals = base.common.nonliterals
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
@ -65,19 +65,19 @@ class Xml2Json:
return res
#----------------------------------------------------------
# OpenERP Web base Controllers
# OpenERP Web web Controllers
#----------------------------------------------------------
def manifest_glob(addons, key):
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(openerpweb.path_addons, addon, pattern)):
files.append(path[len(openerpweb.path_addons):])
for path in glob.glob(os.path.join(addons_path, addon, pattern)):
files.append(path[len(addons_path):])
return files
def concat_files(file_list):
def concat_files(addons_path, file_list):
""" Concatenate file content
return (concat,timestamp)
concat: concatenation of file content
@ -86,30 +86,27 @@ def concat_files(file_list):
files_content = []
files_timestamp = 0
for i in file_list:
fname = os.path.join(openerpweb.path_addons, i[1:])
fname = os.path.join(addons_path, i[1:])
ftime = os.path.getmtime(fname)
if ftime > files_timestamp:
files_timestamp = ftime
files_content.append(open(fname).read())
files_concat = "".join(files_content)
return (files_concat,files_timestamp)
return files_concat,files_timestamp
home_template = textwrap.dedent("""<!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="/base/static/src/img/favicon.ico" type="image/x-icon"/>
<link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/>
%(css)s
<!--[if lte IE 7]>
<link rel="stylesheet" href="/base/static/src/css/base-ie7.css" type="text/css"/>
<![endif]-->
%(javascript)s
<script type="text/javascript">
$(function() {
QWeb = new QWeb2.Engine();
var c = new openerp.init();
var wc = new c.base.WebClient("oe");
var wc = new c.web.WebClient("oe");
wc.start();
});
</script>
@ -118,44 +115,42 @@ home_template = textwrap.dedent("""<!DOCTYPE html>
</html>
""")
class WebClient(openerpweb.Controller):
_cp_path = "/base/webclient"
_cp_path = "/web/webclient"
@openerpweb.jsonrequest
def csslist(self, req, mods='base'):
return manifest_glob(mods.split(','), 'css')
def csslist(self, req, mods='web'):
return manifest_glob(req.config.addons_path, mods.split(','), 'css')
@openerpweb.jsonrequest
def jslist(self, req, mods='base'):
return manifest_glob(mods.split(','), 'js')
def jslist(self, req, mods='web'):
return manifest_glob(req.config.addons_path, mods.split(','), 'js')
@openerpweb.httprequest
def css(self, req, mods='base'):
req.httpresponse.headers['Content-Type'] = 'text/css'
files = manifest_glob(mods.split(','), 'css')
content,timestamp = concat_files(files)
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
return content
return req.make_response(content, [('Content-Type', 'text/css')])
@openerpweb.httprequest
def js(self, req, mods='base'):
req.httpresponse.headers['Content-Type'] = 'application/javascript'
files = manifest_glob(mods.split(','), 'js')
content,timestamp = concat_files(files)
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
return content
return req.make_response(content, [('Content-Type', 'application/javascript')])
@openerpweb.httprequest
def home(self, req, s_action=None, **kw):
# script tags
jslist = ['/base/webclient/js']
jslist = ['/web/webclient/js']
if req.debug:
jslist = manifest_glob(['base'], 'js')
jslist = 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 = ['/base/webclient/css']
csslist = ['/web/webclient/css']
if req.debug:
csslist = manifest_glob(['base'], 'css')
csslist = 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,
@ -185,7 +180,7 @@ class WebClient(openerpweb.Controller):
transl = {"messages":[]}
transs[addon_name] = transl
for l in langs:
f_name = os.path.join(openerpweb.path_addons, addon_name, "po", l + ".po")
f_name = os.path.join(req.config.addons_path, addon_name, "po", l + ".po")
if not os.path.exists(f_name):
continue
try:
@ -199,8 +194,14 @@ class WebClient(openerpweb.Controller):
return {"modules": transs,
"lang_parameters": lang_obj}
@openerpweb.jsonrequest
def version_info(self, req):
return {
"version": webrelease.version
}
class Database(openerpweb.Controller):
_cp_path = "/base/database"
_cp_path = "/web/database"
@openerpweb.jsonrequest
def get_list(self, req):
@ -208,7 +209,7 @@ class Database(openerpweb.Controller):
dbs = proxy.list()
h = req.httprequest.headers['Host'].split(':')[0]
d = h.split('.')[0]
r = cherrypy.config['openerp.dbfilter'].replace('%h', h).replace('%d', d)
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}
@ -251,13 +252,13 @@ class Database(openerpweb.Controller):
@openerpweb.httprequest
def backup(self, req, backup_db, backup_pwd, token):
try:
db_dump = base64.decodestring(
db_dump = base64.b64decode(
req.session.proxy("db").dump(backup_pwd, backup_db))
req.httpresponse.headers['Content-Type'] = "application/octet-stream; charset=binary"
req.httpresponse.headers['Content-Disposition'] = 'attachment; filename="' + backup_db + '.dump"'
req.httpresponse.cookie['fileToken'] = token
req.httpresponse.cookie['fileToken']['path'] = '/'
return db_dump
return req.make_response(db_dump,
[('Content-Type', 'application/octet-stream; charset=binary'),
('Content-Disposition', 'attachment; filename="' + backup_db + '.dump"')],
{'fileToken': int(token)}
)
except xmlrpclib.Fault, e:
if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
return 'Backup Database|' + e.faultCode
@ -266,7 +267,7 @@ class Database(openerpweb.Controller):
@openerpweb.httprequest
def restore(self, req, db_file, restore_pwd, new_db):
try:
data = base64.encodestring(db_file.file.read())
data = base64.b64encode(db_file.file.read())
req.session.proxy("db").restore(restore_pwd, new_db, data)
return ''
except xmlrpclib.Fault, e:
@ -286,7 +287,7 @@ class Database(openerpweb.Controller):
return {'error': 'Error, password not changed !', 'title': 'Change Password'}
class Session(openerpweb.Controller):
_cp_path = "/base/session"
_cp_path = "/web/session"
@openerpweb.jsonrequest
def login(self, req, db, login, password):
@ -333,7 +334,7 @@ class Session(openerpweb.Controller):
# TODO query server for installed web modules
mods = []
for name, manifest in openerpweb.addons_manifest.items():
if name != 'base' and manifest.get('active', True):
if name != 'web' and manifest.get('active', True):
mods.append(name)
return mods
@ -537,7 +538,7 @@ def fix_view_modes(action):
return action
class Menu(openerpweb.Controller):
_cp_path = "/base/menu"
_cp_path = "/web/menu"
@openerpweb.jsonrequest
def load(self, req):
@ -585,7 +586,7 @@ class Menu(openerpweb.Controller):
return {"action": actions}
class DataSet(openerpweb.Controller):
_cp_path = "/base/dataset"
_cp_path = "/web/dataset"
@openerpweb.jsonrequest
def fields(self, req, model):
@ -707,6 +708,12 @@ class DataSet(openerpweb.Controller):
if context_id and len(args) - 1 >= context_id:
args[context_id] = c
for i in xrange(len(args)):
if isinstance(args[i], web.common.nonliterals.BaseContext):
args[i] = req.session.eval_context(args[i])
if isinstance(args[i], web.common.nonliterals.BaseDomain):
args[i] = req.session.eval_domain(args[i])
return getattr(req.session.model(model), method)(*args)
@openerpweb.jsonrequest
@ -737,7 +744,7 @@ class DataSet(openerpweb.Controller):
return {'result': r}
class DataGroup(openerpweb.Controller):
_cp_path = "/base/group"
_cp_path = "/web/group"
@openerpweb.jsonrequest
def read(self, req, model, fields, group_by_fields, domain=None, sort=None):
Model = req.session.model(model)
@ -748,7 +755,7 @@ class DataGroup(openerpweb.Controller):
dict(context, group_by=group_by_fields), sort or False)
class View(openerpweb.Controller):
_cp_path = "/base/view"
_cp_path = "/web/view"
def fields_view_get(self, req, model, view_id, view_type,
transform=True, toolbar=False, submenu=False):
@ -878,7 +885,7 @@ class View(openerpweb.Controller):
elem.set(el, self.parse_context(context_string, session))
class FormView(View):
_cp_path = "/base/formview"
_cp_path = "/web/formview"
@openerpweb.jsonrequest
def load(self, req, model, view_id, toolbar=False):
@ -886,7 +893,7 @@ class FormView(View):
return {'fields_view': fields_view}
class ListView(View):
_cp_path = "/base/listview"
_cp_path = "/web/listview"
@openerpweb.jsonrequest
def load(self, req, model, view_id, toolbar=False):
@ -912,7 +919,7 @@ class ListView(View):
return 'maroon'
class SearchView(View):
_cp_path = "/base/searchview"
_cp_path = "/web/searchview"
@openerpweb.jsonrequest
def load(self, req, model, view_id):
@ -960,23 +967,25 @@ class SearchView(View):
return to_return
class Binary(openerpweb.Controller):
_cp_path = "/base/binary"
_cp_path = "/web/binary"
@openerpweb.httprequest
def image(self, req, model, id, field, **kw):
req.httpresponse.headers['Content-Type'] = 'image/png'
Model = req.session.model(model)
context = req.session.eval_context(req.context)
try:
if not id:
res = Model.default_get([field], context).get(field, '')
else:
res = Model.read([int(id)], [field], context)[0].get(field, '')
return base64.decodestring(res)
except: # TODO: what's the exception here?
return self.placeholder()
def placeholder(self):
return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
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()
@openerpweb.httprequest
def saveas(self, req, model, id, field, fieldname, **kw):
@ -985,22 +994,17 @@ class Binary(openerpweb.Controller):
res = Model.read([int(id)], [field, fieldname], context)[0]
filecontent = res.get(field, '')
if not filecontent:
raise cherrypy.NotFound
return req.not_found()
else:
req.httpresponse.headers['Content-Type'] = 'application/octet-stream'
filename = '%s_%s' % (model.replace('.', '_'), id)
if fieldname:
filename = res.get(fieldname, '') or filename
req.httpresponse.headers['Content-Disposition'] = 'attachment; filename=' + filename
return base64.decodestring(filecontent)
return req.make_response(filecontent,
[('Content-Type', 'application/octet-stream'),
('Content-Disposition', 'attachment; filename=' + filename)])
@openerpweb.httprequest
def upload(self, req, callback, ufile=None):
cherrypy.response.timeout = 500
headers = {}
for key, val in req.httprequest.headers.iteritems():
headers[key.lower()] = val
size = int(headers.get('content-length', 0))
def upload(self, req, callback, ufile):
# TODO: might be useful to have a configuration flag for max-length file uploads
try:
out = """<script language="javascript" type="text/javascript">
@ -1015,15 +1019,15 @@ class Binary(openerpweb.Controller):
});
}
</script>"""
data = ufile.file.read()
args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
data = ufile.read()
args = [ufile.content_length, ufile.filename,
ufile.content_type, base64.b64encode(data)]
except Exception, e:
args = [False, e.message]
return out % (simplejson.dumps(callback), simplejson.dumps(args))
@openerpweb.httprequest
def upload_attachment(self, req, callback, model, id, ufile=None):
cherrypy.response.timeout = 500
def upload_attachment(self, req, callback, model, id, ufile):
context = req.session.eval_context(req.context)
Model = req.session.model('ir.attachment')
try:
@ -1036,7 +1040,7 @@ class Binary(openerpweb.Controller):
</script>"""
attachment_id = Model.create({
'name': ufile.filename,
'datas': base64.encodestring(ufile.file.read()),
'datas': base64.encodestring(ufile.read()),
'res_model': model,
'res_id': int(id)
}, context)
@ -1049,7 +1053,7 @@ class Binary(openerpweb.Controller):
return out % (simplejson.dumps(callback), simplejson.dumps(args))
class Action(openerpweb.Controller):
_cp_path = "/base/action"
_cp_path = "/web/action"
@openerpweb.jsonrequest
def load(self, req, action_id):
@ -1070,7 +1074,7 @@ class Action(openerpweb.Controller):
[action_id], req.session.eval_context(req.context)))
class TreeView(View):
_cp_path = "/base/treeview"
_cp_path = "/web/treeview"
@openerpweb.jsonrequest
def load(self, req, model, view_id, toolbar=False):
@ -1082,62 +1086,22 @@ class TreeView(View):
req,'action', 'tree_but_open',[(model, id)],
False)
def export_csv(fields, result):
fp = StringIO()
writer = csv.writer(fp, quoting=csv.QUOTE_ALL)
writer.writerow(fields)
for data in result:
row = []
for d in data:
if isinstance(d, basestring):
d = d.replace('\n',' ').replace('\t',' ')
try:
d = d.encode('utf-8')
except:
pass
if d is False: d = None
row.append(d)
writer.writerow(row)
fp.seek(0)
data = fp.read()
fp.close()
return data
def export_xls(fieldnames, table):
try:
import xlwt
except ImportError:
common.error(_('Import Error.'), _('Please install xlwt library to export to MS Excel.'))
workbook = xlwt.Workbook()
worksheet = workbook.add_sheet('Sheet 1')
for i, fieldname in enumerate(fieldnames):
worksheet.write(0, i, str(fieldname))
worksheet.col(i).width = 8000 # around 220 pixels
style = xlwt.easyxf('align: wrap yes')
for row_index, row in enumerate(table):
for cell_index, cell_value in enumerate(row):
cell_value = str(cell_value)
cell_value = re.sub("\r", " ", cell_value)
worksheet.write(row_index + 1, cell_index, cell_value, style)
fp = StringIO()
workbook.save(fp)
fp.seek(0)
data = fp.read()
fp.close()
#return data.decode('ISO-8859-1')
return unicode(data, 'utf-8', 'replace')
class Export(View):
_cp_path = "/base/export"
_cp_path = "/web/export"
@openerpweb.jsonrequest
def formats(self, req):
""" Returns all valid export formats
:returns: for each export format, a pair of identifier and printable name
:rtype: [(str, str)]
"""
return sorted([
controller.fmt
for path, controller in openerpweb.controllers_path.iteritems()
if path.startswith(self._cp_path)
if hasattr(controller, 'fmt')
], key=operator.itemgetter(1))
def fields_get(self, req, model):
Model = req.session.model(model)
@ -1145,159 +1109,236 @@ class Export(View):
return fields
@openerpweb.jsonrequest
def get_fields(self, req, model, prefix='', name= '', field_parent=None, params={}):
import_compat = params.get("import_compat", False)
def get_fields(self, req, model, prefix='', parent_name= '',
import_compat=True, parent_field_type=None):
fields = self.fields_get(req, model)
field_parent_type = params.get("parent_field_type",False)
if import_compat and field_parent_type and field_parent_type == "many2one":
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'}
fields_sequence = sorted(fields.iteritems(),
key=lambda field: field[1].get('string', ''))
fields.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}})
records = []
fields_order = fields.keys()
fields_order.sort(lambda x,y: -cmp(fields[x].get('string', ''), fields[y].get('string', '')))
for field_name, field in fields_sequence:
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)
for attrs in field.get('states', {}).values()):
continue
for index, field in enumerate(fields_order):
value = fields[field]
record = {}
if import_compat and value.get('readonly', False):
ok = False
for sl in value.get('states', {}).values():
for s in sl:
ok = ok or (s==['readonly',False])
if not ok: continue
id = prefix + (prefix and '/'or '') + field
nm = name + (name and '/' or '') + value['string']
record.update(id=id, string= nm, action='javascript: void(0)',
target=None, icon=None, children=[], field_type=value.get('type',False), required=value.get('required', False))
id = prefix + (prefix and '/'or '') + field_name
name = parent_name + (parent_name and '/' or '') + field['string']
record = {'id': id, 'string': name,
'value': id, 'children': False,
'field_type': field.get('type'),
'required': field.get('required')}
records.append(record)
if len(nm.split('/')) < 3 and value.get('relation', False):
if import_compat:
ref = value.pop('relation')
cfields = self.fields_get(req, ref)
if (value['type'] == 'many2many'):
record['children'] = []
record['params'] = {'model': ref, 'prefix': id, 'name': nm}
if len(name.split('/')) < 3 and 'relation' in field:
ref = field.pop('relation')
record['value'] += '/id'
record['params'] = {'model': ref, 'prefix': id, 'name': name}
elif value['type'] == 'many2one':
record['children'] = [id + '/id', id + '/.id']
record['params'] = {'model': ref, 'prefix': id, 'name': nm}
if not import_compat or field['type'] == 'one2many':
# m2m field in import_compat is childless
record['children'] = True
else:
cfields_order = cfields.keys()
cfields_order.sort(lambda x,y: -cmp(cfields[x].get('string', ''), cfields[y].get('string', '')))
children = []
for j, fld in enumerate(cfields_order):
cid = id + '/' + fld
cid = cid.replace(' ', '_')
children.append(cid)
record['children'] = children or []
record['params'] = {'model': ref, 'prefix': id, 'name': nm}
else:
ref = value.pop('relation')
cfields = self.fields_get(req, ref)
cfields_order = cfields.keys()
cfields_order.sort(lambda x,y: -cmp(cfields[x].get('string', ''), cfields[y].get('string', '')))
children = []
for j, fld in enumerate(cfields_order):
cid = id + '/' + fld
cid = cid.replace(' ', '_')
children.append(cid)
record['children'] = children or []
record['params'] = {'model': ref, 'prefix': id, 'name': nm}
records.reverse()
return records
@openerpweb.jsonrequest
def save_export_lists(self, req, name, model, field_list):
result = {'resource':model, 'name':name, 'export_fields': []}
for field in field_list:
result['export_fields'].append((0, 0, {'name': field}))
return req.session.model("ir.exports").create(result, req.session.eval_context(req.context))
@openerpweb.jsonrequest
def exist_export_lists(self, req, model):
export_model = req.session.model("ir.exports")
return export_model.read(export_model.search([('resource', '=', model)]), ['name'])
@openerpweb.jsonrequest
def delete_export(self, req, export_id):
req.session.model("ir.exports").unlink(export_id, req.session.eval_context(req.context))
return True
@openerpweb.jsonrequest
def namelist(self,req, model, export_id):
# TODO: namelist really has no reason to be in Python (although itertools.groupby helps)
export = req.session.model("ir.exports").read([export_id])[0]
export_fields_list = req.session.model("ir.exports.line").read(
export['export_fields'])
result = self.get_data(req, model, req.session.eval_context(req.context))
ir_export_obj = req.session.model("ir.exports")
ir_export_line_obj = req.session.model("ir.exports.line")
fields_data = self.fields_info(
req, model, map(operator.itemgetter('name'), export_fields_list))
field = ir_export_obj.read(export_id)
fields = ir_export_line_obj.read(field['export_fields'])
return [
{'name': field['name'], 'label': fields_data[field['name']]}
for field in export_fields_list
]
name_list = {}
[name_list.update({field['name']: result.get(field['name'])}) for field in fields]
return name_list
def get_data(self, req, model, context=None):
ids = []
context = context or {}
fields_data = {}
proxy = req.session.model(model)
def fields_info(self, req, model, export_fields):
info = {}
fields = self.fields_get(req, model)
if not ids:
f1 = proxy.fields_view_get(False, 'tree', context)['fields']
f2 = proxy.fields_view_get(False, 'form', context)['fields']
fields['.id'] = fields.pop('id') if 'id' in fields else {'string': 'ID'}
fields = dict(f1)
fields.update(f2)
fields.update({'id': {'string': 'ID'}, '.id': {'string': 'Database 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
# arbitrary, this requires ordering all sub-fields of a given field
# together so they can be fetched at the same time
#
# Works the following way:
# * sort the list of fields to export, the default sorting order will
# put the field itself (if present, for xmlid) and all of its
# sub-fields right after it
# * then, group on: the first field of the path (which is the same for
# a field and for its subfields and the length of splitting on the
# first '/', which basically means grouping the field on one side and
# all of the subfields on the other. This way, we have the field (for
# the xmlid) with length 1, and all of the subfields with the same
# base but a length "flag" of 2
# * if we have a normal field (length 1), just add it to the info
# mapping (with its string) as-is
# * otherwise, recursively call fields_info via graft_subfields.
# all graft_subfields does is take the result of fields_info (on the
# field's model) and prepend the current base (current field), which
# rebuilds the whole sub-tree for the field
#
# result: because we're not fetching the fields_get for half the
# database models, fetching a namelist with a dozen fields (including
# relational data) falls from ~6s to ~300ms (on the leads model).
# export lists with no sub-fields (e.g. import_compatible lists with
# no o2m) are even more efficient (from the same 6s to ~170ms, as
# there's a single fields_get to execute)
for (base, length), subfields in itertools.groupby(
sorted(export_fields),
lambda field: (field.split('/', 1)[0], len(field.split('/', 1)))):
subfields = list(subfields)
if length == 2:
# subfields is a seq of $base/*rest, and not loaded yet
info.update(self.graft_subfields(
req, fields[base]['relation'], base, fields[base]['string'],
subfields
))
else:
info[base] = fields[base]['string']
def rec(fields):
_fields = {'id': 'ID' , '.id': 'Database ID' }
def model_populate(fields, prefix_node='', prefix=None, prefix_value='', level=2):
fields_order = fields.keys()
fields_order.sort(lambda x,y: -cmp(fields[x].get('string', ''), fields[y].get('string', '')))
return info
for field in fields_order:
fields_data[prefix_node+field] = fields[field]
if prefix_node:
fields_data[prefix_node + field]['string'] = '%s%s' % (prefix_value, fields_data[prefix_node + field]['string'])
st_name = fields[field]['string'] or field
_fields[prefix_node+field] = st_name
if fields[field].get('relation', False) and level>0:
fields2 = self.fields_get(req, fields[field]['relation'])
fields2.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}})
model_populate(fields2, prefix_node+field+'/', None, st_name+'/', level-1)
model_populate(fields)
return _fields
return rec(fields)
def graft_subfields(self, req, model, prefix, prefix_string, fields):
export_fields = [field.split('/', 1)[1] for field in fields]
return (
(prefix + '/' + k, prefix_string + '/' + v)
for k, v in self.fields_info(req, model, export_fields).iteritems())
#noinspection PyPropertyDefinition
@property
def content_type(self):
""" Provides the format's content type """
raise NotImplementedError()
def filename(self, base):
""" Creates a valid filename for the format (with extension) from the
provided base name (exension-less)
"""
raise NotImplementedError()
def from_data(self, fields, rows):
""" Conversion method from OpenERP's export data to whatever the
current export class outputs
:params list fields: a list of fields to export
:params list rows: a list of records to export
:returns:
:rtype: bytes
"""
raise NotImplementedError()
@openerpweb.httprequest
def index(self, req, data, token):
model, fields, ids, domain, import_compat = \
operator.itemgetter('model', 'fields', 'ids', 'domain',
'import_compat')(
simplejson.loads(data))
@openerpweb.jsonrequest
def export_data(self, req, model, fields, ids, domain, import_compat=False, export_format="csv", context=None):
context = req.session.eval_context(req.context)
modle_obj = req.session.model(model)
Model = req.session.model(model)
ids = ids or Model.search(domain, context=context)
ids = ids or modle_obj.search(domain, context=context)
field_names = map(operator.itemgetter('name'), fields)
import_data = Model.export_data(ids, field_names, context).get('datas',[])
field = fields.keys()
result = modle_obj.export_data(ids, field , context).get('datas',[])
if not import_compat:
field = [val.strip() for val in fields.values()]
if export_format == 'xls':
return export_xls(field, result)
if import_compat:
columns_headers = field_names
else:
return export_csv(field, result)
columns_headers = [val['label'].strip() for val in fields]
class Export(View):
_cp_path = "/base/report"
return req.make_response(self.from_data(columns_headers, import_data),
headers=[('Content-Disposition', 'attachment; filename="%s"' % self.filename(model)),
('Content-Type', self.content_type)],
cookies={'fileToken': int(token)})
class CSVExport(Export):
_cp_path = '/web/export/csv'
fmt = ('csv', 'CSV')
@property
def content_type(self):
return 'text/csv;charset=utf8'
def filename(self, base):
return base + '.csv'
def from_data(self, fields, rows):
fp = StringIO()
writer = csv.writer(fp, quoting=csv.QUOTE_ALL)
writer.writerow(fields)
for data in rows:
row = []
for d in data:
if isinstance(d, basestring):
d = d.replace('\n',' ').replace('\t',' ')
try:
d = d.encode('utf-8')
except:
pass
if d is False: d = None
row.append(d)
writer.writerow(row)
fp.seek(0)
data = fp.read()
fp.close()
return data
class ExcelExport(Export):
_cp_path = '/web/export/xls'
fmt = ('xls', 'Excel')
@property
def content_type(self):
return 'application/vnd.ms-excel'
def filename(self, base):
return base + '.xls'
def from_data(self, fields, rows):
import xlwt
workbook = xlwt.Workbook()
worksheet = workbook.add_sheet('Sheet 1')
for i, fieldname in enumerate(fields):
worksheet.write(0, i, str(fieldname))
worksheet.col(i).width = 8000 # around 220 pixels
style = xlwt.easyxf('align: wrap yes')
for row_index, row in enumerate(rows):
for cell_index, cell_value in enumerate(row):
if isinstance(cell_value, basestring):
cell_value = re.sub("\r", " ", cell_value)
worksheet.write(row_index + 1, cell_index, cell_value, style)
fp = StringIO()
workbook.save(fp)
fp.seek(0)
data = fp.read()
fp.close()
return data
class Reports(View):
_cp_path = "/web/report"
@openerpweb.jsonrequest
def get_report(self, req, action):
@ -1315,6 +1356,7 @@ class Export(View):
break
time.sleep(_REPORT_POLLER_DELAY)
#TODO: ok now we've got the report, and so what?
return False

574
addons/web/po/web.pot Normal file
View File

@ -0,0 +1,574 @@
# Translations template for PROJECT.
# Copyright (C) 2011 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2011-09-06 12:02+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"
#: addons/web/static/src/js/form.js:1473
msgid "<em>   Search More...</em>"
msgstr ""
#: addons/web/static/src/js/form.js:1486
#, python-format
msgid "<em>   Create \"<strong>%s</strong>\"</em>"
msgstr ""
#: addons/web/static/src/js/form.js:1492
msgid "<em>   Create and Edit...</em>"
msgstr ""
#: addons/web/static/src/js/views.js:484
msgid "Translations"
msgstr ""
#: addons/web/static/src/js/views.js:489 addons/web/static/src/xml/base.xml:0
msgid "Save"
msgstr ""
#: addons/web/static/src/js/views.js:490
msgid "Close"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "x"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "#{title}"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "#{text}"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Powered by"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "openerp.com"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Loading..."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Create"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Drop"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Backup"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Restore"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Password"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Back to Login"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "CREATE DATABASE"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Master password:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "New database name:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Load Demonstration data:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Default language:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Admin password:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Confirm password:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "DROP DATABASE"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Database:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Master Password:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "BACKUP DATABASE"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "RESTORE DATABASE"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "File:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "CHANGE MASTER PASSWORD"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "New master password:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Confirm new master password:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "User:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Password:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Database"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Login"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Bad username or password"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid ""
"We think that daily job activities can be more intuitive, efficient, "
"automated, .. and even fun."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "OpenERP's vision to be:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Full featured"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid ""
"Today's enterprise challenges are multiple. We provide one module for "
"each need."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Open Source"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid ""
"To Build a great product, we rely on the knowledge of thousands of "
"contributors."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "User Friendly"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "In order to be productive, people need clean and easy to use interface."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "("
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid ")"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "LOGOUT"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "h3"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "<"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid ">"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "</"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "h4"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Fields"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "View labels"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Sidebar Relates"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Field"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid ":"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Translate view"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Translate sidebar"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Delete"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "First"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Last"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "♻"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "View#"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Save & Edit"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Create & Edit"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "New"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "<<"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "0"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "/"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid ">>"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Add"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Unhandled widget"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "?"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Open..."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Create..."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Search..."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "..."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Uploading ..."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Select"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Save As"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Clear"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Advanced Filter"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "-- Filters --"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "-- Actions --"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Save Filter"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Manage Filters"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Filter Name:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "(Any existing filter with the same name will be replaced)"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Any of the following conditions must match"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "All the following conditions must match"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "None of the following conditions must match"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Add condition"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "and"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Cancel"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Save & New"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Save & Close"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Export"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid ""
"This wizard will export all data that matches the current search criteria"
" to a CSV file.\n"
" You can export all data or only the fields that can be "
"reimported after modification."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Export Type:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Import Compatible Export"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Export all Data"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Export Formats"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Available fields"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Fields to export"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Save fields list"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Remove"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Remove All"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Name"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "&nbsp;"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Save as:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Ok"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Saved exports:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Old Password:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "New Password:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Confirm Password:"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "OpenERP Web"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Version"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Copyright © 2011-TODAY OpenERP SA. All Rights Reserved."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "OpenERP is a trademark of the"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "OpenERP SA Company"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "Licenced under the terms of"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "GNU Affero General Public License"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "About OpenERP"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid "OpenERP"
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid ""
"is a free enterprise-scale software system that is designed to boost\n"
" productivity and profit through data integration. It "
"connects, improves and\n"
" manages business processes in areas such as sales, finance, "
"supply chain,\n"
" project management, production, services, CRM, etc..."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid ""
"The system is platform-independent, and can be installed on Windows, Mac "
"OS X,\n"
" and various Linux and other Unix-based distributions. Its "
"architecture enables\n"
" new functionality to be rapidly created, modifications to be "
"made to a\n"
" production system and migration to a new version to be "
"straightforward."
msgstr ""
#: addons/web/static/src/xml/base.xml:0
msgid ""
"Depending on your needs, OpenERP is available through a web or "
"application client."
msgstr ""

Some files were not shown because too many files have changed in this diff Show More