[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", "version" : "2.0",
"depends" : [], "depends" : [],
'active': True, 'active': True,

View File

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

View File

@ -1,22 +1,9 @@
#!/usr/bin/python #!/usr/bin/python
import datetime import datetime
import urllib
import dateutil.relativedelta import dateutil.relativedelta
import functools
import logging
import optparse
import os
import sys
import tempfile
import time import time
import traceback
import uuid
import xmlrpclib import xmlrpclib
import cherrypy
import cherrypy.lib.static
import simplejson
import nonliterals import nonliterals
#---------------------------------------------------------- #----------------------------------------------------------
# OpenERPSession RPC openerp backend access # 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 binascii
import hashlib import hashlib
import simplejson.encoder import simplejson.encoder
import time
import datetime
__all__ = ['Domain', 'Context', 'NonLiteralEncoder, non_literal_decoder', 'CompoundDomain', 'CompoundContext'] __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 base64
import csv import csv
import glob import glob
import itertools
import operator import operator
import os import os
import re import re
@ -10,16 +11,15 @@ import simplejson
import textwrap import textwrap
import xmlrpclib import xmlrpclib
import time import time
import webrelease
from xml.etree import ElementTree from xml.etree import ElementTree
from cStringIO import StringIO from cStringIO import StringIO
import cherrypy import web.common.dispatch as openerpweb
import web.common.ast
import base.common as openerpweb import web.common.nonliterals
import base.common.ast openerpweb.ast = web.common.ast
import base.common.nonliterals openerpweb.nonliterals = web.common.nonliterals
openerpweb.ast = base.common.ast
openerpweb.nonliterals = base.common.nonliterals
from babel.messages.pofile import read_po from babel.messages.pofile import read_po
@ -65,19 +65,19 @@ class Xml2Json:
return res return res
#---------------------------------------------------------- #----------------------------------------------------------
# OpenERP Web base Controllers # OpenERP Web web Controllers
#---------------------------------------------------------- #----------------------------------------------------------
def manifest_glob(addons, key): def manifest_glob(addons_path, addons, key):
files = [] files = []
for addon in addons: for addon in addons:
globlist = openerpweb.addons_manifest.get(addon, {}).get(key, []) globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
for pattern in globlist: for pattern in globlist:
for path in glob.glob(os.path.join(openerpweb.path_addons, addon, pattern)): for path in glob.glob(os.path.join(addons_path, addon, pattern)):
files.append(path[len(openerpweb.path_addons):]) files.append(path[len(addons_path):])
return files return files
def concat_files(file_list): def concat_files(addons_path, file_list):
""" Concatenate file content """ Concatenate file content
return (concat,timestamp) return (concat,timestamp)
concat: concatenation of file content concat: concatenation of file content
@ -86,30 +86,27 @@ def concat_files(file_list):
files_content = [] files_content = []
files_timestamp = 0 files_timestamp = 0
for i in file_list: 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) ftime = os.path.getmtime(fname)
if ftime > files_timestamp: if ftime > files_timestamp:
files_timestamp = ftime files_timestamp = ftime
files_content.append(open(fname).read()) files_content.append(open(fname).read())
files_concat = "".join(files_content) files_concat = "".join(files_content)
return (files_concat,files_timestamp) return files_concat,files_timestamp
home_template = textwrap.dedent("""<!DOCTYPE html> home_template = textwrap.dedent("""<!DOCTYPE html>
<html style="height: 100%%"> <html style="height: 100%%">
<head> <head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>OpenERP</title> <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 %(css)s
<!--[if lte IE 7]>
<link rel="stylesheet" href="/base/static/src/css/base-ie7.css" type="text/css"/>
<![endif]-->
%(javascript)s %(javascript)s
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
QWeb = new QWeb2.Engine(); QWeb = new QWeb2.Engine();
var c = new openerp.init(); var c = new openerp.init();
var wc = new c.base.WebClient("oe"); var wc = new c.web.WebClient("oe");
wc.start(); wc.start();
}); });
</script> </script>
@ -118,44 +115,42 @@ home_template = textwrap.dedent("""<!DOCTYPE html>
</html> </html>
""") """)
class WebClient(openerpweb.Controller): class WebClient(openerpweb.Controller):
_cp_path = "/base/webclient" _cp_path = "/web/webclient"
@openerpweb.jsonrequest @openerpweb.jsonrequest
def csslist(self, req, mods='base'): def csslist(self, req, mods='web'):
return manifest_glob(mods.split(','), 'css') return manifest_glob(req.config.addons_path, mods.split(','), 'css')
@openerpweb.jsonrequest @openerpweb.jsonrequest
def jslist(self, req, mods='base'): def jslist(self, req, mods='web'):
return manifest_glob(mods.split(','), 'js') return manifest_glob(req.config.addons_path, mods.split(','), 'js')
@openerpweb.httprequest @openerpweb.httprequest
def css(self, req, mods='base'): def css(self, req, mods='web'):
req.httpresponse.headers['Content-Type'] = 'text/css' files = manifest_glob(req.config.addons_path, mods.split(','), 'css')
files = manifest_glob(mods.split(','), 'css') content,timestamp = concat_files(req.config.addons_path, files)
content,timestamp = concat_files(files)
# TODO request set the Date of last modif and Etag # TODO request set the Date of last modif and Etag
return content return req.make_response(content, [('Content-Type', 'text/css')])
@openerpweb.httprequest @openerpweb.httprequest
def js(self, req, mods='base'): def js(self, req, mods='web'):
req.httpresponse.headers['Content-Type'] = 'application/javascript' files = manifest_glob(req.config.addons_path, mods.split(','), 'js')
files = manifest_glob(mods.split(','), 'js') content,timestamp = concat_files(req.config.addons_path, files)
content,timestamp = concat_files(files)
# TODO request set the Date of last modif and Etag # TODO request set the Date of last modif and Etag
return content return req.make_response(content, [('Content-Type', 'application/javascript')])
@openerpweb.httprequest @openerpweb.httprequest
def home(self, req, s_action=None, **kw): def home(self, req, s_action=None, **kw):
# script tags # script tags
jslist = ['/base/webclient/js'] jslist = ['/web/webclient/js']
if req.debug: 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]) js = "\n ".join(['<script type="text/javascript" src="%s"></script>'%i for i in jslist])
# css tags # css tags
csslist = ['/base/webclient/css'] csslist = ['/web/webclient/css']
if req.debug: 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]) css = "\n ".join(['<link rel="stylesheet" href="%s">'%i for i in csslist])
r = home_template % { r = home_template % {
'javascript': js, 'javascript': js,
@ -185,7 +180,7 @@ class WebClient(openerpweb.Controller):
transl = {"messages":[]} transl = {"messages":[]}
transs[addon_name] = transl transs[addon_name] = transl
for l in langs: 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): if not os.path.exists(f_name):
continue continue
try: try:
@ -199,8 +194,14 @@ class WebClient(openerpweb.Controller):
return {"modules": transs, return {"modules": transs,
"lang_parameters": lang_obj} "lang_parameters": lang_obj}
@openerpweb.jsonrequest
def version_info(self, req):
return {
"version": webrelease.version
}
class Database(openerpweb.Controller): class Database(openerpweb.Controller):
_cp_path = "/base/database" _cp_path = "/web/database"
@openerpweb.jsonrequest @openerpweb.jsonrequest
def get_list(self, req): def get_list(self, req):
@ -208,7 +209,7 @@ class Database(openerpweb.Controller):
dbs = proxy.list() dbs = proxy.list()
h = req.httprequest.headers['Host'].split(':')[0] h = req.httprequest.headers['Host'].split(':')[0]
d = h.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)] dbs = [i for i in dbs if re.match(r, i)]
return {"db_list": dbs} return {"db_list": dbs}
@ -251,13 +252,13 @@ class Database(openerpweb.Controller):
@openerpweb.httprequest @openerpweb.httprequest
def backup(self, req, backup_db, backup_pwd, token): def backup(self, req, backup_db, backup_pwd, token):
try: try:
db_dump = base64.decodestring( db_dump = base64.b64decode(
req.session.proxy("db").dump(backup_pwd, backup_db)) req.session.proxy("db").dump(backup_pwd, backup_db))
req.httpresponse.headers['Content-Type'] = "application/octet-stream; charset=binary" return req.make_response(db_dump,
req.httpresponse.headers['Content-Disposition'] = 'attachment; filename="' + backup_db + '.dump"' [('Content-Type', 'application/octet-stream; charset=binary'),
req.httpresponse.cookie['fileToken'] = token ('Content-Disposition', 'attachment; filename="' + backup_db + '.dump"')],
req.httpresponse.cookie['fileToken']['path'] = '/' {'fileToken': int(token)}
return db_dump )
except xmlrpclib.Fault, e: except xmlrpclib.Fault, e:
if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied': if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
return 'Backup Database|' + e.faultCode return 'Backup Database|' + e.faultCode
@ -266,7 +267,7 @@ class Database(openerpweb.Controller):
@openerpweb.httprequest @openerpweb.httprequest
def restore(self, req, db_file, restore_pwd, new_db): def restore(self, req, db_file, restore_pwd, new_db):
try: 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) req.session.proxy("db").restore(restore_pwd, new_db, data)
return '' return ''
except xmlrpclib.Fault, e: except xmlrpclib.Fault, e:
@ -286,7 +287,7 @@ class Database(openerpweb.Controller):
return {'error': 'Error, password not changed !', 'title': 'Change Password'} return {'error': 'Error, password not changed !', 'title': 'Change Password'}
class Session(openerpweb.Controller): class Session(openerpweb.Controller):
_cp_path = "/base/session" _cp_path = "/web/session"
@openerpweb.jsonrequest @openerpweb.jsonrequest
def login(self, req, db, login, password): def login(self, req, db, login, password):
@ -333,7 +334,7 @@ class Session(openerpweb.Controller):
# TODO query server for installed web modules # TODO query server for installed web modules
mods = [] mods = []
for name, manifest in openerpweb.addons_manifest.items(): 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) mods.append(name)
return mods return mods
@ -537,7 +538,7 @@ def fix_view_modes(action):
return action return action
class Menu(openerpweb.Controller): class Menu(openerpweb.Controller):
_cp_path = "/base/menu" _cp_path = "/web/menu"
@openerpweb.jsonrequest @openerpweb.jsonrequest
def load(self, req): def load(self, req):
@ -585,7 +586,7 @@ class Menu(openerpweb.Controller):
return {"action": actions} return {"action": actions}
class DataSet(openerpweb.Controller): class DataSet(openerpweb.Controller):
_cp_path = "/base/dataset" _cp_path = "/web/dataset"
@openerpweb.jsonrequest @openerpweb.jsonrequest
def fields(self, req, model): def fields(self, req, model):
@ -707,6 +708,12 @@ class DataSet(openerpweb.Controller):
if context_id and len(args) - 1 >= context_id: if context_id and len(args) - 1 >= context_id:
args[context_id] = c 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) return getattr(req.session.model(model), method)(*args)
@openerpweb.jsonrequest @openerpweb.jsonrequest
@ -737,7 +744,7 @@ class DataSet(openerpweb.Controller):
return {'result': r} return {'result': r}
class DataGroup(openerpweb.Controller): class DataGroup(openerpweb.Controller):
_cp_path = "/base/group" _cp_path = "/web/group"
@openerpweb.jsonrequest @openerpweb.jsonrequest
def read(self, req, model, fields, group_by_fields, domain=None, sort=None): def read(self, req, model, fields, group_by_fields, domain=None, sort=None):
Model = req.session.model(model) Model = req.session.model(model)
@ -748,7 +755,7 @@ class DataGroup(openerpweb.Controller):
dict(context, group_by=group_by_fields), sort or False) dict(context, group_by=group_by_fields), sort or False)
class View(openerpweb.Controller): class View(openerpweb.Controller):
_cp_path = "/base/view" _cp_path = "/web/view"
def fields_view_get(self, req, model, view_id, view_type, def fields_view_get(self, req, model, view_id, view_type,
transform=True, toolbar=False, submenu=False): transform=True, toolbar=False, submenu=False):
@ -878,7 +885,7 @@ class View(openerpweb.Controller):
elem.set(el, self.parse_context(context_string, session)) elem.set(el, self.parse_context(context_string, session))
class FormView(View): class FormView(View):
_cp_path = "/base/formview" _cp_path = "/web/formview"
@openerpweb.jsonrequest @openerpweb.jsonrequest
def load(self, req, model, view_id, toolbar=False): def load(self, req, model, view_id, toolbar=False):
@ -886,7 +893,7 @@ class FormView(View):
return {'fields_view': fields_view} return {'fields_view': fields_view}
class ListView(View): class ListView(View):
_cp_path = "/base/listview" _cp_path = "/web/listview"
@openerpweb.jsonrequest @openerpweb.jsonrequest
def load(self, req, model, view_id, toolbar=False): def load(self, req, model, view_id, toolbar=False):
@ -912,7 +919,7 @@ class ListView(View):
return 'maroon' return 'maroon'
class SearchView(View): class SearchView(View):
_cp_path = "/base/searchview" _cp_path = "/web/searchview"
@openerpweb.jsonrequest @openerpweb.jsonrequest
def load(self, req, model, view_id): def load(self, req, model, view_id):
@ -960,23 +967,25 @@ class SearchView(View):
return to_return return to_return
class Binary(openerpweb.Controller): class Binary(openerpweb.Controller):
_cp_path = "/base/binary" _cp_path = "/web/binary"
@openerpweb.httprequest @openerpweb.httprequest
def image(self, req, model, id, field, **kw): def image(self, req, model, id, field, **kw):
req.httpresponse.headers['Content-Type'] = 'image/png'
Model = req.session.model(model) Model = req.session.model(model)
context = req.session.eval_context(req.context) context = req.session.eval_context(req.context)
try: try:
if not id: if not id:
res = Model.default_get([field], context).get(field, '') res = Model.default_get([field], context).get(field, '')
else: else:
res = Model.read([int(id)], [field], context)[0].get(field, '') res = Model.read([int(id)], [field], context)[0].get(field, '')
return base64.decodestring(res) image_data = base64.b64decode(res)
except: # TODO: what's the exception here? except (TypeError, xmlrpclib.Fault):
return self.placeholder() image_data = self.placeholder(req)
def placeholder(self): return req.make_response(image_data, [
return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read() ('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 @openerpweb.httprequest
def saveas(self, req, model, id, field, fieldname, **kw): 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] res = Model.read([int(id)], [field, fieldname], context)[0]
filecontent = res.get(field, '') filecontent = res.get(field, '')
if not filecontent: if not filecontent:
raise cherrypy.NotFound return req.not_found()
else: else:
req.httpresponse.headers['Content-Type'] = 'application/octet-stream'
filename = '%s_%s' % (model.replace('.', '_'), id) filename = '%s_%s' % (model.replace('.', '_'), id)
if fieldname: if fieldname:
filename = res.get(fieldname, '') or filename filename = res.get(fieldname, '') or filename
req.httpresponse.headers['Content-Disposition'] = 'attachment; filename=' + filename return req.make_response(filecontent,
return base64.decodestring(filecontent) [('Content-Type', 'application/octet-stream'),
('Content-Disposition', 'attachment; filename=' + filename)])
@openerpweb.httprequest @openerpweb.httprequest
def upload(self, req, callback, ufile=None): def upload(self, req, callback, ufile):
cherrypy.response.timeout = 500
headers = {}
for key, val in req.httprequest.headers.iteritems():
headers[key.lower()] = val
size = int(headers.get('content-length', 0))
# TODO: might be useful to have a configuration flag for max-length file uploads # TODO: might be useful to have a configuration flag for max-length file uploads
try: try:
out = """<script language="javascript" type="text/javascript"> out = """<script language="javascript" type="text/javascript">
@ -1015,15 +1019,15 @@ class Binary(openerpweb.Controller):
}); });
} }
</script>""" </script>"""
data = ufile.file.read() data = ufile.read()
args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)] args = [ufile.content_length, ufile.filename,
ufile.content_type, base64.b64encode(data)]
except Exception, e: except Exception, e:
args = [False, e.message] args = [False, e.message]
return out % (simplejson.dumps(callback), simplejson.dumps(args)) return out % (simplejson.dumps(callback), simplejson.dumps(args))
@openerpweb.httprequest @openerpweb.httprequest
def upload_attachment(self, req, callback, model, id, ufile=None): def upload_attachment(self, req, callback, model, id, ufile):
cherrypy.response.timeout = 500
context = req.session.eval_context(req.context) context = req.session.eval_context(req.context)
Model = req.session.model('ir.attachment') Model = req.session.model('ir.attachment')
try: try:
@ -1036,7 +1040,7 @@ class Binary(openerpweb.Controller):
</script>""" </script>"""
attachment_id = Model.create({ attachment_id = Model.create({
'name': ufile.filename, 'name': ufile.filename,
'datas': base64.encodestring(ufile.file.read()), 'datas': base64.encodestring(ufile.read()),
'res_model': model, 'res_model': model,
'res_id': int(id) 'res_id': int(id)
}, context) }, context)
@ -1049,7 +1053,7 @@ class Binary(openerpweb.Controller):
return out % (simplejson.dumps(callback), simplejson.dumps(args)) return out % (simplejson.dumps(callback), simplejson.dumps(args))
class Action(openerpweb.Controller): class Action(openerpweb.Controller):
_cp_path = "/base/action" _cp_path = "/web/action"
@openerpweb.jsonrequest @openerpweb.jsonrequest
def load(self, req, action_id): def load(self, req, action_id):
@ -1070,7 +1074,7 @@ class Action(openerpweb.Controller):
[action_id], req.session.eval_context(req.context))) [action_id], req.session.eval_context(req.context)))
class TreeView(View): class TreeView(View):
_cp_path = "/base/treeview" _cp_path = "/web/treeview"
@openerpweb.jsonrequest @openerpweb.jsonrequest
def load(self, req, model, view_id, toolbar=False): def load(self, req, model, view_id, toolbar=False):
@ -1082,62 +1086,22 @@ class TreeView(View):
req,'action', 'tree_but_open',[(model, id)], req,'action', 'tree_but_open',[(model, id)],
False) 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): 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): def fields_get(self, req, model):
Model = req.session.model(model) Model = req.session.model(model)
@ -1145,159 +1109,236 @@ class Export(View):
return fields return fields
@openerpweb.jsonrequest @openerpweb.jsonrequest
def get_fields(self, req, model, prefix='', name= '', field_parent=None, params={}): def get_fields(self, req, model, prefix='', parent_name= '',
import_compat = params.get("import_compat", False) import_compat=True, parent_field_type=None):
fields = self.fields_get(req, model) if import_compat and parent_field_type == "many2one":
field_parent_type = params.get("parent_field_type",False)
if import_compat and field_parent_type and field_parent_type == "many2one":
fields = {} 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 = [] records = []
fields_order = fields.keys() for field_name, field in fields_sequence:
fields_order.sort(lambda x,y: -cmp(fields[x].get('string', ''), fields[y].get('string', ''))) 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): id = prefix + (prefix and '/'or '') + field_name
value = fields[field] name = parent_name + (parent_name and '/' or '') + field['string']
record = {} record = {'id': id, 'string': name,
if import_compat and value.get('readonly', False): 'value': id, 'children': False,
ok = False 'field_type': field.get('type'),
for sl in value.get('states', {}).values(): 'required': field.get('required')}
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))
records.append(record) records.append(record)
if len(nm.split('/')) < 3 and value.get('relation', False): if len(name.split('/')) < 3 and 'relation' in field:
if import_compat: ref = field.pop('relation')
ref = value.pop('relation') record['value'] += '/id'
cfields = self.fields_get(req, ref) record['params'] = {'model': ref, 'prefix': id, 'name': name}
if (value['type'] == 'many2many'):
record['children'] = []
record['params'] = {'model': ref, 'prefix': id, 'name': nm}
elif value['type'] == 'many2one': if not import_compat or field['type'] == 'one2many':
record['children'] = [id + '/id', id + '/.id'] # m2m field in import_compat is childless
record['params'] = {'model': ref, 'prefix': id, 'name': nm} 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 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 @openerpweb.jsonrequest
def namelist(self,req, model, export_id): 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)) fields_data = self.fields_info(
ir_export_obj = req.session.model("ir.exports") req, model, map(operator.itemgetter('name'), export_fields_list))
ir_export_line_obj = req.session.model("ir.exports.line")
field = ir_export_obj.read(export_id) return [
fields = ir_export_line_obj.read(field['export_fields']) {'name': field['name'], 'label': fields_data[field['name']]}
for field in export_fields_list
]
name_list = {} def fields_info(self, req, model, export_fields):
[name_list.update({field['name']: result.get(field['name'])}) for field in fields] info = {}
return name_list
def get_data(self, req, model, context=None):
ids = []
context = context or {}
fields_data = {}
proxy = req.session.model(model)
fields = self.fields_get(req, model) fields = self.fields_get(req, model)
if not ids: fields['.id'] = fields.pop('id') if 'id' in fields else {'string': 'ID'}
f1 = proxy.fields_view_get(False, 'tree', context)['fields']
f2 = proxy.fields_view_get(False, 'form', context)['fields']
fields = dict(f1) # To make fields retrieval more efficient, fetch all sub-fields of a
fields.update(f2) # given field at the same time. Because the order in the export list is
fields.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}}) # 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): return info
_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', '')))
for field in fields_order: def graft_subfields(self, req, model, prefix, prefix_string, fields):
fields_data[prefix_node+field] = fields[field] export_fields = [field.split('/', 1)[1] for field in fields]
if prefix_node: return (
fields_data[prefix_node + field]['string'] = '%s%s' % (prefix_value, fields_data[prefix_node + field]['string']) (prefix + '/' + k, prefix_string + '/' + v)
st_name = fields[field]['string'] or field for k, v in self.fields_info(req, model, export_fields).iteritems())
_fields[prefix_node+field] = st_name
if fields[field].get('relation', False) and level>0: #noinspection PyPropertyDefinition
fields2 = self.fields_get(req, fields[field]['relation']) @property
fields2.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}}) def content_type(self):
model_populate(fields2, prefix_node+field+'/', None, st_name+'/', level-1) """ Provides the format's content type """
model_populate(fields) raise NotImplementedError()
return _fields
return rec(fields) 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) 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() if import_compat:
result = modle_obj.export_data(ids, field , context).get('datas',[]) columns_headers = field_names
if not import_compat:
field = [val.strip() for val in fields.values()]
if export_format == 'xls':
return export_xls(field, result)
else: 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 @openerpweb.jsonrequest
def get_report(self, req, action): def get_report(self, req, action):
@ -1315,6 +1356,7 @@ class Export(View):
break break
time.sleep(_REPORT_POLLER_DELAY) time.sleep(_REPORT_POLLER_DELAY)
#TODO: ok now we've got the report, and so what? #TODO: ok now we've got the report, and so what?
return False 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