[Merge] upto revno 1024.

bzr revid: vme@tinyerp.com-20110914053746-ri6d5s341mm6tc7n
This commit is contained in:
Vidhin Mehta (OpenERP) 2011-09-14 11:07:46 +05:30
commit b6076c3cc3
947 changed files with 5056 additions and 2707 deletions

View File

@ -1,9 +1,8 @@
.PHONY: all release clean .PHONY: all doc release clean
HOST = 127.0.0.1 HOST = 127.0.0.1
PORT = 8080 PORT = 8080
all: run all: run
run: run:
@ -24,6 +23,9 @@ clean:
@rm -rf dist @rm -rf dist
@rm -rf *.egg-info @rm -rf *.egg-info
cloc: doc:
cloc openerpweb/*.py addons/*/controllers/*.py addons/*/static/src/*.js addons/*/static/src/js/*.js addons/*/static/src/css/*.css addons/*/static/src/xml/*.xml make -C doc html
cloc:
cloc addons/*/common/*.py addons/*/controllers/*.py addons/*/static/src/*.js addons/*/static/src/js/*.js addons/*/static/src/css/*.css addons/*/static/src/xml/*.xml

View File

@ -1,42 +1,9 @@
Coding style OpenERP Web
------------
Javascript
http://javascript.crockford.com/code.html with the following amendements:
- Line Length should be 160 (132?), dont split at 80, rationale in 2011 we all use 16/9 screens
http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml ?
Modules Conventions
-------------------
addons/<modulename>/controllers/ python controller
addons/<modulename>/controllers/main.py main python controller is there is only one (or should it be <modulename>.py ?)
addons/<modulename>/static/ static directory (directly served by the web server)
addons/<modulename>/static/<external_library>/ directory containing an external javascrip library respect the directory structure of upstream is this directory
addons/<modulename>/static/openerp/ module specfic static files
addons/<modulename>/static/openerp/js/ module specific javscript files
addons/<modulename>/static/openerp/css/ module specific css files
addons/<modulename>/static/openerp/img/ module specific images files
addons/<modulename>/static/openerp/ other files
addons/<modulename>/__openerp__.py module manifest referencing js and css files
Dependecies
----------- -----------
Depends on
install "CherryPy>=3.1.2" "-d cherrypy" To build the documentation use:
install "simplejson>=2.0.9" "-d simplejson"
Maybe soon on $ make doc
#install "Babel>=0.9.4" "-d babel" then look at doc/build/html/index.html
#install "pytz>=2009j" "-d pytz"
Probably not anymore on:
#install "Mako>=0.2.4" "-d mako"
#install "formencode>=1.2.2" "-d formencode"
#install "pyparsing>=1.5.2" "-f pyparsing.py"
#install "xlwt>=0.7" "-d xlwt"

View File

@ -1,6 +0,0 @@
import common
import controllers
# TODO
# if we detect that we are imported from the openerp server register common.Root() as a wsgi entry point

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,2 +0,0 @@
#!/usr/bin/python
import controllers

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:

32
addons/web/__init__.py Normal file
View File

@ -0,0 +1,32 @@
import common
import controllers
import common.dispatch
import logging
_logger = logging.getLogger(__name__)
try:
import openerp.wsgi
import os
import tempfile
_logger.info("embedded mode")
class Options(object):
pass
o = Options()
o.dbfilter = '.*'
o.session_storage = os.path.join(tempfile.gettempdir(), "oe-sessions")
o.addons_path = os.path.dirname(os.path.dirname(__file__))
o.serve_static = True
o.server_host = '127.0.0.1'
o.server_port = 8069
app = common.dispatch.Root(o)
#import openerp.wsgi
openerp.wsgi.register_wsgi_handler(app)
except ImportError:
_logger.info("standalone mode")
# TODO
# if we detect that we are imported from the openerp server register common.Root() as a wsgi entry point

View File

@ -1,6 +1,5 @@
{ {
"name" : "OpenERP Web base", "name" : "web",
"version" : "2.0",
"depends" : [], "depends" : [],
'active': True, 'active': True,
'js' : [ 'js' : [
@ -47,4 +46,5 @@
"static/src/css/base.css", "static/src/css/base.css",
"static/src/css/data_export.css", "static/src/css/data_export.css",
], ],
'wsgi' : 'app',
} }

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

@ -4,6 +4,7 @@ from __future__ import with_statement
import functools import functools
import logging import logging
import os import os
import pprint
import sys import sys
import traceback import traceback
import uuid import uuid
@ -26,6 +27,8 @@ import backendrpc as backend
__all__ = ['Root', 'jsonrequest', 'httprequest', 'Controller', __all__ = ['Root', 'jsonrequest', 'httprequest', 'Controller',
'WebRequest', 'JsonRequest', 'HttpRequest'] 'WebRequest', 'JsonRequest', 'HttpRequest']
_logger = logging.getLogger(__name__)
#----------------------------------------------------------- #-----------------------------------------------------------
# Globals (wont move into a pool) # Globals (wont move into a pool)
#----------------------------------------------------------- #-----------------------------------------------------------
@ -159,8 +162,8 @@ class JsonRequest(WebRequest):
else: else:
self.jsonrequest = simplejson.loads(request, object_hook=nonliterals.non_literal_decoder) self.jsonrequest = simplejson.loads(request, object_hook=nonliterals.non_literal_decoder)
self.init(self.jsonrequest.get("params", {})) self.init(self.jsonrequest.get("params", {}))
if self.debug or 1: if _logger.isEnabledFor(logging.DEBUG):
print "--> %s.%s %s" % (controller.__class__.__name__, method.__name__, self.jsonrequest) _logger.debug("--> %s.%s\n%s", controller.__class__.__name__, method.__name__, pprint.pformat(self.jsonrequest))
response['id'] = self.jsonrequest.get('id') response['id'] = self.jsonrequest.get('id')
response["result"] = method(controller, self, **self.params) response["result"] = method(controller, self, **self.params)
except backend.OpenERPUnboundException: except backend.OpenERPUnboundException:
@ -184,7 +187,7 @@ class JsonRequest(WebRequest):
} }
} }
except Exception: except Exception:
logging.getLogger('openerp.JSONRequest.dispatch').exception\ logging.getLogger(__name__ + '.JSONRequest.dispatch').exception\
("An error occured while handling a json request") ("An error occured while handling a json request")
error = { error = {
'code': 300, 'code': 300,
@ -197,10 +200,8 @@ class JsonRequest(WebRequest):
if error: if error:
response["error"] = error response["error"] = error
if self.debug or 1: if _logger.isEnabledFor(logging.DEBUG):
print "<--", response _logger.debug("<--\n%s", pprint.pformat(response))
print
content = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder) content = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder)
return werkzeug.wrappers.Response( return werkzeug.wrappers.Response(
content, headers=[('Content-Type', 'application/json'), content, headers=[('Content-Type', 'application/json'),
@ -227,22 +228,23 @@ class HttpRequest(WebRequest):
""" Regular GET/POST request """ Regular GET/POST request
""" """
def dispatch(self, controller, method): def dispatch(self, controller, method):
self.init(dict(self.httprequest.args, **self.httprequest.form)) params = dict(self.httprequest.args)
params.update(self.httprequest.form)
params.update(self.httprequest.files)
self.init(params)
akw = {} akw = {}
for key, value in self.httprequest.args.iteritems(): for key, value in self.httprequest.args.iteritems():
if isinstance(value, basestring) and len(value) < 1024: if isinstance(value, basestring) and len(value) < 1024:
akw[key] = value akw[key] = value
else: else:
akw[key] = type(value) akw[key] = type(value)
if self.debug or 1: _logger.debug("%s --> %s.%s %r", self.httprequest.method, controller.__class__.__name__, method.__name__, akw)
print "%s --> %s.%s %r" % (self.httprequest.method, controller.__class__.__name__, method.__name__, akw)
r = method(controller, self, **self.params) r = method(controller, self, **self.params)
if self.debug or 1: if self.debug or 1:
if isinstance(r, werkzeug.wrappers.BaseResponse): if isinstance(r, werkzeug.wrappers.BaseResponse):
print '<--', r _logger.debug('<-- %s', r)
else: else:
print "<--", 'size:', len(r) _logger.debug("<-- size: %s", len(r))
print
return r return r
def make_response(self, data, headers=None, cookies=None): def make_response(self, data, headers=None, cookies=None):
@ -314,7 +316,7 @@ class Root(object):
by the server, will be filtered by this pattern by the server, will be filtered by this pattern
""" """
def __init__(self, options): def __init__(self, options):
self.root = werkzeug.urls.Href('/base/webclient/home') self.root = werkzeug.urls.Href('/web/webclient/home')
self.config = options self.config = options
self.session_cookie = 'sessionid' self.session_cookie = 'sessionid'
@ -347,7 +349,7 @@ class Root(object):
if request.path == '/': if request.path == '/':
return werkzeug.utils.redirect( return werkzeug.utils.redirect(
self.root(request.args), 301)( self.root(dict(request.args, debug='')), 301)(
environ, start_response) environ, start_response)
elif request.path == '/mobile': elif request.path == '/mobile':
return werkzeug.utils.redirect( return werkzeug.utils.redirect(
@ -388,7 +390,7 @@ class Root(object):
manifest_path = os.path.join(addons_path, module, '__openerp__.py') manifest_path = os.path.join(addons_path, module, '__openerp__.py')
if os.path.isfile(manifest_path): if os.path.isfile(manifest_path):
manifest = ast.literal_eval(open(manifest_path).read()) manifest = ast.literal_eval(open(manifest_path).read())
print "Loading", module _logger.info("Loading %s", module)
m = __import__(module) m = __import__(module)
addons_module[module] = m addons_module[module] = m
addons_manifest[module] = manifest addons_manifest[module] = manifest
@ -421,6 +423,6 @@ class Root(object):
meth = rest[0] meth = rest[0]
m = getattr(c, meth) m = getattr(c, meth)
if getattr(m, 'exposed', False): if getattr(m, 'exposed', False):
print "Dispatching to", ps, c, meth, m _logger.debug("Dispatching to %s %s %s", ps, c, meth)
return m return m
return None return None

View File

@ -0,0 +1,10 @@
name = 'openerp-web'
version = '6.1.0 alpha'
description = "Web Client of OpenERP, the Enterprise Management Software"
long_description = "OpenERP Web is the web client of the OpenERP, a free enterprise management software"
author = "OpenERP SA"
author_email = "info@openerp.com"
support_email = 'support@openerp.com'
url = "http://www.openerp.com/"
download_url = ''
license = "AGPL"

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,18 +11,19 @@ import simplejson
import textwrap import textwrap
import xmlrpclib import xmlrpclib
import time import time
import zlib
from xml.etree import ElementTree from xml.etree import ElementTree
from cStringIO import StringIO from cStringIO import StringIO
import base.common.dispatch as openerpweb
import base.common.ast
import base.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
_REPORT_POLLER_DELAY = 0.05 import web.common.dispatch as openerpweb
import web.common.ast
import web.common.nonliterals
import web.common.release
openerpweb.ast = web.common.ast
openerpweb.nonliterals = web.common.nonliterals
# Should move to openerpweb.Xml2Json # Should move to openerpweb.Xml2Json
class Xml2Json: class Xml2Json:
@ -63,7 +65,7 @@ class Xml2Json:
return res return res
#---------------------------------------------------------- #----------------------------------------------------------
# OpenERP Web base Controllers # OpenERP Web web Controllers
#---------------------------------------------------------- #----------------------------------------------------------
def manifest_glob(addons_path, addons, key): def manifest_glob(addons_path, addons, key):
@ -97,17 +99,13 @@ home_template = textwrap.dedent("""<!DOCTYPE html>
<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();
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>
@ -116,25 +114,25 @@ 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(req.config.addons_path, 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(req.config.addons_path, 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'):
files = manifest_glob(req.config.addons_path, mods.split(','), 'css') files = manifest_glob(req.config.addons_path, mods.split(','), 'css')
content,timestamp = concat_files(req.config.addons_path, files) content,timestamp = concat_files(req.config.addons_path, files)
# TODO request set the Date of last modif and Etag # TODO request set the Date of last modif and Etag
return req.make_response(content, [('Content-Type', 'text/css')]) 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'):
files = manifest_glob(req.config.addons_path, mods.split(','), 'js') files = manifest_glob(req.config.addons_path, mods.split(','), 'js')
content,timestamp = concat_files(req.config.addons_path, files) content,timestamp = concat_files(req.config.addons_path, files)
# TODO request set the Date of last modif and Etag # TODO request set the Date of last modif and Etag
@ -143,15 +141,15 @@ class WebClient(openerpweb.Controller):
@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(req.config.addons_path, ['base'], 'js') jslist = [i + '?debug=' + str(time.time()) for i in manifest_glob(req.config.addons_path, ['web'], 'js')]
js = "\n ".join(['<script type="text/javascript" src="%s"></script>'%i for i in jslist]) 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(req.config.addons_path, ['base'], 'css') csslist = [i + '?debug=' + str(time.time()) for i in manifest_glob(req.config.addons_path, ['web'], 'css')]
css = "\n ".join(['<link rel="stylesheet" href="%s">'%i for i in csslist]) css = "\n ".join(['<link rel="stylesheet" href="%s">'%i for i in csslist])
r = home_template % { r = home_template % {
'javascript': js, 'javascript': js,
@ -195,8 +193,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": web.common.release.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):
@ -247,12 +251,12 @@ 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))
return req.make_response(db_dump, return req.make_response(db_dump,
[('Content-Type', 'application/octet-stream; charset=binary'), [('Content-Type', 'application/octet-stream; charset=binary'),
('Content-Disposition', 'attachment; filename="' + backup_db + '.dump"')], ('Content-Disposition', 'attachment; filename="' + backup_db + '.dump"')],
{'fileToken': token} {'fileToken': int(token)}
) )
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':
@ -262,7 +266,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:
@ -282,7 +286,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):
@ -292,7 +296,15 @@ class Session(openerpweb.Controller):
return { return {
"session_id": req.session_id, "session_id": req.session_id,
"uid": req.session._uid, "uid": req.session._uid,
"context": ctx "context": ctx,
"db": req.session._db
}
@openerpweb.jsonrequest
def get_session_info(self, req):
return {
"uid": req.session._uid,
"context": req.session.get_context() if req.session._uid else False,
"db": req.session._db
} }
@openerpweb.jsonrequest @openerpweb.jsonrequest
def change_password (self,req,fields): def change_password (self,req,fields):
@ -329,7 +341,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
@ -448,8 +460,6 @@ def load_actions_from_ir_values(req, key, key2, models, meta):
def clean_action(req, action): def clean_action(req, action):
action.setdefault('flags', {}) action.setdefault('flags', {})
if action['type'] != 'ir.actions.act_window':
return action
context = req.session.eval_context(req.context) context = req.session.eval_context(req.context)
eval_ctx = req.session.evaluation_context(context) eval_ctx = req.session.evaluation_context(context)
@ -461,7 +471,9 @@ def clean_action(req, action):
if isinstance(action.get('domain'), basestring): if isinstance(action.get('domain'), basestring):
action['domain'] = eval( action['domain'], eval_ctx ) or [] action['domain'] = eval( action['domain'], eval_ctx ) or []
return fix_view_modes(action) if action['type'] == 'ir.actions.act_window':
return fix_view_modes(action)
return action
# I think generate_views,fix_view_modes should go into js ActionManager # I think generate_views,fix_view_modes should go into js ActionManager
def generate_views(action): def generate_views(action):
@ -533,7 +545,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):
@ -581,7 +593,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):
@ -702,6 +714,12 @@ class DataSet(openerpweb.Controller):
args[domain_id] = d args[domain_id] = d
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)
@ -733,7 +751,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)
@ -744,7 +762,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):
@ -753,6 +771,8 @@ class View(openerpweb.Controller):
fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu) fvg = Model.fields_view_get(view_id, view_type, context, toolbar, submenu)
# todo fme?: check that we should pass the evaluated context here # todo fme?: check that we should pass the evaluated context here
self.process_view(req.session, fvg, context, transform) self.process_view(req.session, fvg, context, transform)
if toolbar and transform:
self.process_toolbar(req, fvg['toolbar'])
return fvg return fvg
def process_view(self, session, fvg, context, transform): def process_view(self, session, fvg, context, transform):
@ -784,6 +804,22 @@ class View(openerpweb.Controller):
if field.get('context'): if field.get('context'):
field["context"] = self.parse_context(field["context"], session) field["context"] = self.parse_context(field["context"], session)
def process_toolbar(self, req, toolbar):
"""
The toolbar is a mapping of section_key: [action_descriptor]
We need to clean all those actions in order to ensure correct
round-tripping
"""
for actions in toolbar.itervalues():
for action in actions:
if 'context' in action:
action['context'] = self.parse_context(
action['context'], req.session)
if 'domain' in action:
action['domain'] = self.parse_domain(
action['domain'], req.session)
@openerpweb.jsonrequest @openerpweb.jsonrequest
def add_custom(self, req, view_id, arch): def add_custom(self, req, view_id, arch):
CustomView = req.session.model('ir.ui.view.custom') CustomView = req.session.model('ir.ui.view.custom')
@ -873,21 +909,12 @@ class View(openerpweb.Controller):
if context_string: if context_string:
elem.set(el, self.parse_context(context_string, session)) elem.set(el, self.parse_context(context_string, session))
class FormView(View):
_cp_path = "/base/formview"
@openerpweb.jsonrequest @openerpweb.jsonrequest
def load(self, req, model, view_id, toolbar=False): def load(self, req, model, view_id, view_type, toolbar=False):
fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar) return self.fields_view_get(req, model, view_id, view_type, toolbar=toolbar)
return {'fields_view': fields_view}
class ListView(View): class ListView(View):
_cp_path = "/base/listview" _cp_path = "/web/listview"
@openerpweb.jsonrequest
def load(self, req, model, view_id, toolbar=False):
fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
return {'fields_view': fields_view}
def process_colors(self, view, row, context): def process_colors(self, view, row, context):
colors = view['arch']['attrs'].get('colors') colors = view['arch']['attrs'].get('colors')
@ -907,8 +934,17 @@ class ListView(View):
return color[0] return color[0]
return 'maroon' return 'maroon'
class TreeView(View):
_cp_path = "/web/treeview"
@openerpweb.jsonrequest
def action(self, req, model, id):
return load_actions_from_ir_values(
req,'action', 'tree_but_open',[(model, id)],
False)
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):
@ -956,23 +992,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(req) image_data = self.placeholder(req)
return req.make_response(image_data, [
('Content-Type', 'image/png'), ('Content-Length', len(image_data))])
def placeholder(self, req): def placeholder(self, req):
return open(os.path.join(req.addons_path, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read() 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):
@ -991,11 +1029,7 @@ class Binary(openerpweb.Controller):
('Content-Disposition', 'attachment; filename=' + filename)]) ('Content-Disposition', 'attachment; filename=' + filename)])
@openerpweb.httprequest @openerpweb.httprequest
def upload(self, req, callback, ufile=None): def upload(self, req, callback, ufile):
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">
@ -1010,14 +1044,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):
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:
@ -1030,7 +1065,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)
@ -1043,7 +1078,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):
@ -1052,8 +1087,11 @@ class Action(openerpweb.Controller):
context = req.session.eval_context(req.context) context = req.session.eval_context(req.context)
action_type = Actions.read([action_id], ['type'], context) action_type = Actions.read([action_id], ['type'], context)
if action_type: if action_type:
action = req.session.model(action_type[0]['type']).read([action_id], False, ctx = {}
context) if action_type[0]['type'] == 'ir.actions.report.xml':
ctx.update({'bin_size': True})
ctx.update(context)
action = req.session.model(action_type[0]['type']).read([action_id], False, ctx)
if action: if action:
value = clean_action(req, action[0]) value = clean_action(req, action[0])
return {'result': value} return {'result': value}
@ -1063,75 +1101,22 @@ class Action(openerpweb.Controller):
return clean_action(req, req.session.model('ir.actions.server').run( return clean_action(req, req.session.model('ir.actions.server').run(
[action_id], req.session.eval_context(req.context))) [action_id], req.session.eval_context(req.context)))
class TreeView(View):
_cp_path = "/base/treeview"
@openerpweb.jsonrequest
def load(self, req, model, view_id, toolbar=False):
return self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
@openerpweb.jsonrequest
def action(self, req, model, id):
return load_actions_from_ir_values(
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): 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)
@ -1139,174 +1124,285 @@ 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 modle_obj.search(domain, context=context) ids = ids or Model.search(domain, context=context)
field = fields.keys() field_names = map(operator.itemgetter('name'), fields)
result = modle_obj.export_data(ids, field , context).get('datas',[]) import_data = Model.export_data(ids, field_names, context).get('datas',[])
if not import_compat: if import_compat:
field = [val.strip() for val in fields.values()] columns_headers = field_names
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"
@openerpweb.jsonrequest return req.make_response(self.from_data(columns_headers, import_data),
def get_report(self, req, action): 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"
POLLING_DELAY = 0.25
TYPES_MAPPING = {
'doc': 'application/vnd.ms-word',
'html': 'text/html',
'odt': 'application/vnd.oasis.opendocument.text',
'pdf': 'application/pdf',
'sxw': 'application/vnd.sun.xml.writer',
'xls': 'application/vnd.ms-excel',
}
@openerpweb.httprequest
def index(self, req, action, token):
action = simplejson.loads(action)
report_srv = req.session.proxy("report") report_srv = req.session.proxy("report")
context = req.session.eval_context(openerpweb.nonliterals.CompoundContext(req.context, \ context = req.session.eval_context(
action["context"])) openerpweb.nonliterals.CompoundContext(
req.context or {}, action[ "context"]))
args = [req.session._db, req.session._uid, req.session._password, action["report_name"], context["active_ids"], {"id": context["active_id"], "model": context["active_model"], "report_type": action["report_type"]}, context] report_data = {}
report_id = report_srv.report(*args) report_ids = context["active_ids"]
report = None if 'report_type' in action:
while True: report_data['report_type'] = action['report_type']
args2 = [req.session._db, req.session._uid, req.session._password, report_id] if 'datas' in action:
report = report_srv.report_get(*args2) if 'form' in action['datas']:
if report["state"]: report_data['form'] = action['datas']['form']
break if 'ids' in action['datas']:
time.sleep(_REPORT_POLLER_DELAY) report_ids = action['datas']['ids']
#TODO: ok now we've got the report, and so what? report_id = report_srv.report(
return False req.session._db, req.session._uid, req.session._password,
action["report_name"], report_ids,
report_data, context)
report_struct = None
while True:
report_struct = report_srv.report_get(
req.session._db, req.session._uid, req.session._password, report_id)
if report_struct["state"]:
break
time.sleep(self.POLLING_DELAY)
report = base64.b64decode(report_struct['result'])
if report_struct.get('code') == 'zlib':
report = zlib.decompress(report)
report_mimetype = self.TYPES_MAPPING.get(
report_struct['format'], 'octet-stream')
return req.make_response(report,
headers=[
('Content-Disposition', 'attachment; filename="%s.%s"' % (action['report_name'], report_struct['format'])),
('Content-Type', report_mimetype),
('Content-Length', len(report))],
cookies={'fileToken': int(token)})

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