bzr revid: nicolas.vanhoren@openerp.com-20110513145314-q71g21r1o4trx6v8
This commit is contained in:
niv-openerp 2011-05-13 16:53:14 +02:00
commit 348a3386b3
23 changed files with 952 additions and 301 deletions

View File

@ -357,6 +357,28 @@ class DataSet(openerpweb.Controller):
reads = Model.read(ids, fields or False, request.context)
reads.sort(key=lambda obj: ids.index(obj['id']))
return reads
@openerpweb.jsonrequest
def read(self, request, model, ids, fields=False):
return self.do_search_read(request, model, ids, fields)
def search_read(self, request, model, ids, fields=False):
""" Performs a read()
:param request: a JSON-RPC request object
:type request: openerpweb.JsonRequest
:param str model: the name of the model to search on
:param ids: the ids of the records
:type ids: [?]
:param fields: a list of the fields to return in the result records
:type fields: [str]
:returns: a list of result records
:rtype: list
"""
Model = request.session.model(model)
reads = Model.read(ids, fields or False, request.context)
reads.sort(key=lambda obj: ids.index(obj['id']))
return reads
@openerpweb.jsonrequest
def get(self, request, model, ids, fields=False):
@ -424,6 +446,19 @@ class DataSet(openerpweb.Controller):
m = req.session.model(model)
r = m.default_get(fields, context)
return {'result': r}
@openerpweb.jsonrequest
def name_search(self, req, model, search_str, domain=[], context={}):
m = req.session.model(model)
r = m.name_search(search_str+'%', domain, '=ilike', context)
return {'result': r}
class DataGroup(openerpweb.Controller):
_cp_path = "/base/group"
@openerpweb.jsonrequest
def read(self, request, model, group_by_fields, domain=None, context=None):
Model = request.session.model(model)
return Model.read_group(domain or False, False, group_by_fields, 0, False, context or False)
class View(openerpweb.Controller):
def fields_view_get(self, request, model, view_id, view_type,

View File

@ -116,7 +116,7 @@ var QWeb2 = {
var new_dict = this.extend({}, old_dict);
new_dict['__caller__'] = old_dict['__template__'];
if (callback) {
new_dict[0] = callback(context, new_dict);
new_dict['__content__'] = callback(context, new_dict);
}
var r = context.engine._render(template, new_dict);
if (_import) {
@ -485,9 +485,6 @@ QWeb2.Element = (function() {
format_expression : function(e) {
/* Naive format expression builder. Replace reserved words and variables to dict[variable]
* Does not handle spaces before dot yet, and causes problems for anonymous functions. Use t-js="" for that */
if (e === '0') {
return "dict['0']";
}
if (QWeb2.expressions_cache[e]) {
return QWeb2.expressions_cache[e];
}

View File

@ -24,10 +24,11 @@
<script type="text/javascript" src="/base/static/src/js/dates.js"></script>
<script type="text/javascript" src="/base/static/src/js/chrome.js"></script>
<script type="text/javascript" src="/base/static/src/js/data.js"></script>
<script type="text/javascript" src="/base/static/src/js/views.js"></script>
<script type="text/javascript" src="/base/static/src/js/form.js"></script>
<script type="text/javascript" src="/base/static/src/js/list.js"></script>
<script type="text/javascript" src="/base/static/src/js/search.js"></script>
<script type="text/javascript" src="/base/static/src/js/views.js"></script>
<script type="text/javascript" src="/base/static/src/js/m2o.js"></script>
<link rel="stylesheet" type="text/css" media="screen" href="/base/static/lib/jquery.ui/css/smoothness/jquery-ui-1.8.9.custom.css" />
<link rel="stylesheet" type="text/css" media="screen" href="/base/static/lib/jquery.ui.notify/css/ui.notify.css" />

View File

@ -221,8 +221,6 @@ body.openerp {
height: 63px;
width: 200px;
margin-right: 10px;
line-height: 63px;
text-align: center;
border: 1px solid white;
border-right-color: black;
border-bottom-color: black;
@ -232,7 +230,12 @@ body.openerp {
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#CECECE',GradientType=0 );
}
.openerp .company_logo {
vertical-align: middle
margin-top: 7px;
margin-left: 10px;
display: block;
background: url(/base/static/src/img/logo.png);
width:180px;
height:46px;
}
.openerp .header_title {
float: left;
@ -440,6 +443,9 @@ body.openerp {
.openerp .searchview_group_content {
padding-left: 10px;
}
.openerp .searchview_group_content .oe-searchview-render-line {
width:0;
}
.openerp .oe-searchview-render-line {
@ -452,12 +458,12 @@ body.openerp {
margin: 2px;
}
.openerp .searchview_extended_add_proposition, .openerp .searchview_extended_add_group {
.openerp .searchview_extended_add_proposition span, .openerp .searchview_extended_add_group span {
background: url(../img/icons/gtk-add.png) repeat-y;
padding-left: 18px;
}
.openerp .searchview_extended_delete_group, .openerp .searchview_extended_delete_prop {
.openerp .searchview_extended_delete_group span, .openerp .searchview_extended_delete_prop span {
background: url(../img/icons/gtk-remove.png) repeat-y;
padding-left: 18px;
}
@ -770,15 +776,61 @@ body.openerp {
background-position: 21px 0;
}
.openerp .kitten-mode-activated {
.openerp.kitten-mode-activated .main_table {
background: url(http://placekitten.com/g/1500/800) repeat;
}
.openerp .kitten-mode-activated .header {
.openerp.kitten-mode-activated .header {
background: url(http://placekitten.com/g/211/65) repeat;
}
.openerp .kitten-mode-activated .secondary_menu {
.openerp.kitten-mode-activated .secondary_menu {
background: url(http://placekitten.com/g/212/100) repeat;
}
.openerp.kitten-mode-activated .menu {
background: #828282;
background: -moz-linear-gradient(top, #828282 0%, #4D4D4D 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#828282), color-stop(100%,#4D4D4D));
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#828282', endColorstr='#4D4D4D',GradientType=0 );
}
.openerp.kitten-mode-activated .menu a {
background: none;
}
.openerp.kitten-mode-activated .menu span {
background: none;
}
.openerp.kitten-mode-activated .sidebar-displaying-div li a,
.openerp.kitten-mode-activated .oe-application .view-manager-main-content h2.oe_view_title,
.openerp.kitten-mode-activated .oe-application .view-manager-main-content a.searchview_group_string,
.openerp.kitten-mode-activated .oe-application .view-manager-main-content label {
color: white;
}
.openerp.kitten-mode-activated .menu,
.openerp.kitten-mode-activated .header_corner,
.openerp.kitten-mode-activated .header_title,
.openerp.kitten-mode-activated .secondary_menu div,
.openerp.kitten-mode-activated .oe-application,
.openerp.kitten-mode-activated .oe_footer,
.openerp.kitten-mode-activated .loading,
.openerp.kitten-mode-activated .ui-dialog {
opacity:0.8;
filter:alpha(opacity=80);
}
.openerp.kitten-mode-activated .header .company_logo {
background: url(http://placekitten.com/g/180/46);
}
.openerp.kitten-mode-activated .loading {
background: #828282;
border-color: #828282;
}
/* Many2one Autosearch */
.openerp .ui_combo {
height: 20px;
margin-right: 1px;
top: 4px;
width: 22px;
}
/* ------------- End autocomplete ------- */

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

View File

@ -126,6 +126,7 @@ openerp.base = function(instance) {
openerp.base.views(instance);
openerp.base.search(instance);
openerp.base.list(instance);
openerp.base.m2o(instance);
openerp.base.form(instance);
};

View File

@ -790,10 +790,6 @@ openerp.base.Header = openerp.base.Controller.extend({
this.do_update();
},
do_update: function() {
if(jQuery.param != undefined &&
jQuery.deparam(jQuery.param.querystring()).kitten != undefined) {
this.kitten = 1;
}
this.$element.html(QWeb.render("Header", this));
}
});
@ -897,7 +893,7 @@ openerp.base.WebClient = openerp.base.Controller.extend({
var params = {};
if(jQuery.param != undefined &&
jQuery.deparam(jQuery.param.querystring()).kitten != undefined) {
params = {kitten:1};
this.$element.addClass("kitten-mode-activated");
}
this.$element.html(QWeb.render("Interface", params));

View File

@ -3,15 +3,139 @@ openerp.base.data = function(openerp) {
openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.base.DataGroup# */{
/**
* Management interface between views and the collection of selected OpenERP
* records (represents the view's state?)
* Management interface between views and grouped collections of OpenERP
* records.
*
* The root DataGroup is instantiated with the relevant information
* (a session, a model, a domain, a context and a group_by sequence), the
* domain and context may be empty. It is then interacted with via
* :js:func:`~openerp.base.DataGroup.list`, which is used to read the
* content of the current grouping level, and
* :js:func:`~openerp.base.DataGroup.get`, which is used to fetch an item
* of the current grouping level.
*
* @constructs
* @extends openerp.base.Controller
*
* @param {openerp.base.Session} session Current OpenERP session
* @param {String} model name of the model managed by this DataGroup
* @param {Array} domain search domain for this DataGroup
* @param {Object} context context of the DataGroup's searches
* @param {Array} group_by sequence of fields by which to group
*/
init: function(session) {
init: function(session, model, domain, context, group_by) {
this._super(session, null);
this.model = model;
this.context = context;
this.domain = domain;
this.group_by = group_by;
this.groups = null;
},
fetch: function () {
// internal method
var d = new $.Deferred();
var self = this;
if (this.groups) {
d.resolveWith(this, [this.groups]);
} else {
this.rpc('/base/group/read', {
model: this.model,
context: this.context,
domain: this.domain,
group_by_fields: this.group_by
}, function () { }).then(function (response) {
self.groups = response.result;
// read_group results are annoying: they use the name of the
// field grouped on to hold the value and the count, so no
// generic access to those values is possible.
// Alias them to `value` and `length`.
d.resolveWith(self, [_(response.result).map(function (group) {
var field_name = self.group_by[0];
return _.extend({}, group, {
// provide field used for grouping
grouped_on: field_name,
length: group[field_name + '_count'],
value: group[field_name]
});
})]);
}, function () {
d.rejectWith.apply(d, self, [arguments]);
});
}
return d.promise();
},
/**
* Retrieves the content of an item in the DataGroup, which results in
* either a DataSet or new DataGroup instance.
*
* Calling :js:func:`~openerp.base.DataGroup.get` without having called
* :js:func:`~openerp.base.DataGroup.list` beforehand will likely result
* in an error.
*
* The resulting :js:class:`~openerp.base.DataGroup` or
* :js:class:`~openerp.base.DataSet` will be provided through the relevant
* callback function. In both functions, the current DataGroup will be
* provided as context (``this``)
*
* @param {Number} index the index of the group to open in the datagroup's collection
* @param {Function} ifDataSet executed if the item results in a DataSet, provided with the new dataset as parameter
* @param {Function} ifDataGroup executed if the item results in a DataSet, provided with the new datagroup as parameter
*/
get: function (index, ifDataSet, ifDataGroup) {
var group = this.groups[index];
if (!group) {
throw new Error("No group at index " + index);
}
var child_context = _.extend({}, this.context, group.__context);
if (group.__context.group_by.length) {
var datagroup = new openerp.base.DataGroup(
this.session, this.model, group.__domain, child_context,
group.__context.group_by);
ifDataGroup.call(this, datagroup);
} else {
var dataset = new openerp.base.DataSetSearch(this.session, this.model);
dataset.domain = group.__domain;
dataset.context = child_context;
ifDataSet.call(this, dataset);
}
},
/**
* Gathers the content of the current data group (the current grouping
* level), and provides it via a promise object to which callbacks should
* be added::
*
* datagroup.list().then(function (list) {
* // manipulate list here
* });
*
* The argument to the callback is the list of elements fetched, the
* context (``this``) is the datagroup itself.
*
* The items of a list are the standard objects returned by ``read_group``
* with three properties added:
*
* ``length``
* the number of records contained in the group (and all of its
* sub-groups). This does *not* provide the size of the "next level"
* of the group, unless the group is terminal (no more groups within
* it).
* ``grouped_on``
* the name of the field this level was grouped on, this is mostly
* used for display purposes, in order to know the name of the current
* level of grouping. The ``grouped_on`` should be the same for all
* objects of the list.
* ``value``
* the value which led to this group (this is the value all contained
* records have for the current ``grouped_on`` field name).
*
* @returns {$.Deferred}
*/
list: function () {
return this.fetch();
}
});
@ -89,12 +213,12 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.
context: this.context
}, callback);
},
create: function(data, callback) {
create: function(data, callback, error_callback) {
return this.rpc('/base/dataset/create', {
model: this.model,
data: data,
context: this.context
}, callback);
}, callback, error_callback);
},
write: function (id, data, callback) {
return this.rpc('/base/dataset/save', {
@ -119,6 +243,15 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.
args: args
}, callback);
},
name_search: function (search_str, callback) {
search_str = search_str || '';
return this.rpc('/base/dataset/name_search', {
model: this.model,
search_str: search_str,
domain: this.domain || [],
context: this.context
}, callback);
},
exec_workflow: function (id, signal, callback) {
return this.rpc('/base/dataset/exec_workflow', {
model: this.model,

View File

@ -1,6 +1,16 @@
openerp.base.dates = function(openerp) {
/**
* Converts a string to a Date javascript object using OpenERP's
* datetime string format (exemple: '2011-12-01 15:12:35').
*
* The timezone is assumed to be UTC (standard for OpenERP 6.1)
* and will be converted to the browser's timezone.
*
* @param {String} str A string representing a datetime.
* @returns {Date}
*/
openerp.base.parse_datetime = function(str) {
if(!str) {
return str;
@ -17,6 +27,13 @@ openerp.base.parse_datetime = function(str) {
return obj;
};
/**
* Converts a string to a Date javascript object using OpenERP's
* date string format (exemple: '2011-12-01').
*
* @param {String} str A string representing a date.
* @returns {Date}
*/
openerp.base.parse_date = function(str) {
if(!str) {
return str;
@ -33,6 +50,13 @@ openerp.base.parse_date = function(str) {
return obj;
};
/**
* Converts a string to a Date javascript object using OpenERP's
* time string format (exemple: '15:12:35').
*
* @param {String} str A string representing a time.
* @returns {Date}
*/
openerp.base.parse_time = function(str) {
if(!str) {
return str;
@ -49,7 +73,7 @@ openerp.base.parse_time = function(str) {
return obj;
};
/**
/*
* Just a simple function to add some '0' if an integer it too small.
*/
var fts = function(str, size) {
@ -61,8 +85,18 @@ var fts = function(str, size) {
return to_add + str;
};
/**
* Converts a Date javascript object to a string using OpenERP's
* datetime string format (exemple: '2011-12-01 15:12:35').
*
* The timezone of the Date object is assumed to be the one of the
* browser and it will be converted to UTC (standard for OpenERP 6.1).
*
* @param {Date} obj
* @returns {String} A string representing a datetime.
*/
openerp.base.format_datetime = function(obj) {
if(! obj) {
if (!obj) {
return false;
}
return fts(obj.getUTCFullYear(),4) + "-" + fts(obj.getUTCMonth() + 1,2) + "-"
@ -70,20 +104,35 @@ openerp.base.format_datetime = function(obj) {
+ fts(obj.getUTCMinutes(),2) + ":" + fts(obj.getUTCSeconds(),2);
};
/**
* Converts a Date javascript object to a string using OpenERP's
* date string format (exemple: '2011-12-01').
*
* @param {Date} obj
* @returns {String} A string representing a date.
*/
openerp.base.format_date = function(obj) {
if(! obj) {
if (!obj) {
return false;
}
return fts(obj.getFullYear(),4) + "-" + fts(obj.getMonth() + 1,2) + "-"
+ fts(obj.getDate(),2);
};
/**
* Converts a Date javascript object to a string using OpenERP's
* time string format (exemple: '15:12:35').
*
* @param {Date} obj
* @returns {String} A string representing a time.
*/
openerp.base.format_time = function(obj) {
if(! obj) {
if (!obj) {
return false;
}
return fts(obj.getHours(),2) + ":" + fts(obj.getMinutes(),2) + ":"
+ fts(obj.getSeconds(),2);
};
};
};

View File

@ -2,7 +2,7 @@
openerp.base.form = function (openerp) {
openerp.base.views.add('form', 'openerp.base.FormView');
openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base.FormView# */{
openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormView# */{
/**
* Indicates that this view is not searchable, and thus that no search
* view should be displayed (if there is one active).
@ -143,14 +143,21 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base
var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/);
if (call) {
var method = call[1], args = [];
var argument_replacement = {
'False' : false,
'True' : true,
'None' : null
}
_.each(call[2].split(','), function(a) {
var field = _.trim(a);
if (self.fields[field]) {
if (field in argument_replacement) {
args.push(argument_replacement[field]);
} else if (self.fields[field]) {
var value = self.fields[field].value;
args.push(value == null ? false : value);
} else {
args.push(false);
this.log("warning : on_change can't find field " + field, onchange);
self.log("warning : on_change can't find field " + field, onchange);
}
});
var ajax = {
@ -365,7 +372,8 @@ openerp.base.form.compute_domain = function(expr, fields) {
}
}
return _.indexOf(stack, false) == -1;
},
}
openerp.base.form.Widget = openerp.base.Controller.extend({
init: function(view, node) {
this.view = view;
@ -532,7 +540,7 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({
on_confirmed: function() {
var self = this;
this.execute_action(
this.view.execute_action(
this.node.attrs, this.view.dataset, this.session.action_manager,
this.view.datarecord.id, function (result) {
self.log("Button returned", result);
@ -540,9 +548,6 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({
});
}
});
// let WidgetButton execute actions
_.extend(openerp.base.form.WidgetButton.prototype,
openerp.base.ActionExecutor);
openerp.base.form.WidgetLabel = openerp.base.form.Widget.extend({
init: function(view, node) {
@ -649,7 +654,24 @@ openerp.base.form.FieldChar = openerp.base.form.Field.extend({
openerp.base.form.FieldEmail = openerp.base.form.FieldChar.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldEmail";
this.validation_regex = /@/;
},
start: function() {
this._super.apply(this, arguments);
this.$element.find('button').click(this.on_button_clicked);
},
on_button_clicked: function() {
if (!this.value || this.invalid) {
this.notification.warn("E-mail error", "Can't send email to invalid e-mail address");
} else {
location.href = 'mailto:' + this.value;
}
},
set_value: function(value) {
this._super.apply(this, arguments);
var show_value = (value != null && value !== false) ? value : '';
this.$element.find('a').attr('href', 'mailto:' + show_value);
}
});
@ -659,44 +681,91 @@ openerp.base.form.FieldUrl = openerp.base.form.FieldChar.extend({
openerp.base.form.FieldFloat = openerp.base.form.FieldChar.extend({
init: function(view, node) {
this._super(view, node);
this.validation_regex = /^\d+(\.\d+)?$/;
this.validation_regex = /^-?\d+(\.\d+)?$/;
},
set_value: function(value) {
this._super.apply(this, arguments);
var show_value = (value != null && value !== false) ? value.toFixed(2) : '';
if (!value) {
// As in GTK client, floats default to 0
value = 0;
}
this._super.apply(this, [value]);
var show_value = value.toFixed(2);
this.$element.find('input').val(show_value);
},
set_value_from_ui: function() {
this.value = this.$element.find('input').val().replace(/,/g, '.');
},
validate: function() {
this._super.apply(this, arguments);
if (!this.invalid) {
this.value = Number(this.value);
}
}
});
openerp.base.form.FieldDate = openerp.base.form.FieldChar.extend({
openerp.base.form.FieldDatetime = openerp.base.form.Field.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldDate";
this.validation_regex = /^\d+-\d+-\d+$/;
this.jqueryui_object = 'datetimepicker';
},
start: function() {
this._super.apply(this, arguments);
this.$element.find('input').change(this.on_ui_change).datepicker({
dateFormat: 'yy-mm-dd'
});
}
});
openerp.base.form.FieldDatetime = openerp.base.form.FieldChar.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldDatetime";
this.validation_regex = /^\d+-\d+-\d+( \d+:\d+(:\d+)?)?$/;
},
start: function() {
this._super.apply(this, arguments);
this.$element.find('input').change(this.on_ui_change).datetimepicker({
this.$element.find('input').change(this.on_ui_change)[this.jqueryui_object]({
dateFormat: 'yy-mm-dd',
timeFormat: 'hh:mm:ss'
});
},
set_value: function(value) {
this._super.apply(this, arguments);
if (value == null || value == false) {
this.$element.find('input').val('');
} else {
this.value = this.parse(value);
this.$element.find('input')[this.jqueryui_object]('setDate', this.value);
}
},
set_value_from_ui: function() {
this.value = this.$element.find('input')[this.jqueryui_object]('getDate') || false;
if (this.value) {
this.value = this.format(this.value);
}
},
validate: function() {
this.invalid = this.required && this.value === false;
},
parse: openerp.base.parse_datetime,
format: openerp.base.format_datetime
});
openerp.base.form.FieldDate = openerp.base.form.FieldDatetime.extend({
init: function(view, node) {
this._super(view, node);
this.jqueryui_object = 'datepicker';
},
parse: openerp.base.parse_date,
format: openerp.base.format_date
});
openerp.base.form.FieldFloatTime = openerp.base.form.FieldChar.extend({
init: function(view, node) {
this._super(view, node);
this.validation_regex = /^\d+:\d+$/;
},
set_value: function(value) {
value = value || 0;
this._super.apply(this, [value]);
var show_value = _.sprintf("%02d:%02d", Math.floor(value), Math.round((value % 1) * 60));
this.$element.find('input').val(show_value);
},
validate: function() {
if (typeof(this.value) == "string") {
this._super.apply(this, arguments);
if (!this.invalid) {
var time = this.value.split(':');
this.value = parseInt(time[0], 10) + parseInt(time[1], 10) / 60;
}
}
}
});
@ -817,10 +886,33 @@ openerp.base.form.FieldSelection = openerp.base.form.Field.extend({
}
});
openerp.base.form.FieldMany2OneDatasSet = openerp.base.DataSetStatic.extend({
start: function() {
},
write: function (id, data, callback) {
this._super(id, data, callback);
},
});
openerp.base.form.FieldMany2OneViewManager = openerp.base.ViewManager.extend({
init: function(session, element_id, dataset, views) {
this._super(session, element_id, dataset, views);
}
});
openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldMany2One";
this.is_field_m2o = true;
},
start: function() {
this.$element = $('#' + this.element_id);
this.dataset = new openerp.base.form.FieldMany2OneDatasSet(this.session, this.field.relation);
var views = [ [false,"list"], [false,"form"] ];
this.viewmanager = new openerp.base.form.FieldMany2OneViewManager(this.view.session, this.element_id, this.dataset, views);
new openerp.base.m2o(this.viewmanager, this.$element, this.field.relation, this.dataset, this.session)
this.$element.find('input').change(this.on_ui_change);
},
set_value: function(value) {
this._super.apply(this, arguments);
@ -830,6 +922,16 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
this.value = value[0];
}
this.$element.find('input').val(show_value);
this.$element.find('input').attr('m2o_id', this.value);
},
get_value: function() {
var val = this.$element.find('input').attr('m2o_id') || this.value
return val;
},
on_ui_change: function() {
this.touched = this.view.touched = true;
}
});
@ -890,6 +992,8 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({
this._super(view, node);
this.template = "FieldMany2Many";
this.list_id = _.uniqueId("many2many");
this.is_started = false;
this.is_setted = false;
},
start: function() {
this._super.apply(this, arguments);
@ -899,23 +1003,35 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({
var self = this;
this.list_view.m2m_field = this;
this.list_view.start();
var hack = {loaded: false};
this.list_view.on_loaded.add_last(function() {
if (! hack.loaded) {
self.is_started = true;
self.check_load();
hack.loaded = true;
}
});
},
set_value: function(value) {
if (value != false) {
this.dataset.ids = value;
this.dataset.count = value.length;
this.list_view.do_reload();
this.is_setted = true;
this.check_load();
}
},
get_value: function() {
return [[6,false,this.dataset.ids]];
},
check_load: function() {
if(this.is_started && this.is_setted) {
this.list_view.do_reload();
}
}
});
openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({
do_delete: function (e) {
e.stopImmediatePropagation();
var ids = [this.rows[$(e.currentTarget).closest('tr').prevAll().length].data.id.value];
do_delete: function (ids) {
this.dataset.ids = _.without.apply(null, [this.dataset.ids].concat(ids));
this.dataset.count = this.dataset.ids.length;
// there may be a faster way
@ -937,12 +1053,20 @@ openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({
},
do_add_record: function (e) {
e.stopImmediatePropagation();
// TODO: need to open a popup with search view
var pop = new openerp.base.form.Many2XSelectPopup(null, this.m2m_field.view.session);
pop.select_element(this.model);
var self = this;
pop.on_select_element.add(function(element_id) {
if(! _.detect(self.dataset.ids, function(x) {return x == element_id;})) {
self.dataset.ids.push(element_id);
self.dataset.count = self.dataset.ids.length;
self.do_reload();
}
pop.stop();
});
},
on_select_row: function(event) {
var $target = $(event.currentTarget);
var row = this.rows[$target.prevAll().length];
var id = row.data.id.value;
select_record: function(index) {
var id = this.rows[index].data.id.value;
if(! id) {
return;
}
@ -959,6 +1083,97 @@ openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({
}
});
openerp.base.form.Many2XSelectPopup = openerp.base.BaseWidget.extend({
identifier_prefix: "many2xselectpopup",
template: "Many2XSelectPopup",
select_element: function(model) {
this.model = model;
var html = this.render();
jQuery(html).dialog({title: '',
modal: true,
minWidth: 800});
this.start();
},
start: function() {
this._super();
this.dataset = new openerp.base.DataSetSearch(this.session, this.model);
this.setup_search_view();
},
setup_search_view: function() {
var self = this;
if (this.searchview) {
this.searchview.stop();
}
this.searchview = new openerp.base.SearchView(this, this.session, this.element_id + "_search",
this.dataset, false, {});
this.searchview.on_search.add(function(domains, contexts, groupbys) {
self.view_list.do_search.call(
self, domains, contexts, groupbys);
});
this.searchview.on_loaded.add_last(function () {
var $buttons = self.searchview.$element.find(".oe_search-view-buttons");
$buttons.append(QWeb.render("Many2XSelectPopup.search.buttons"));
var $nbutton = $buttons.find(".oe_many2xselectpopup-search-new");
$nbutton.click(function() {
self.new_object();
});
var $cbutton = $buttons.find(".oe_many2xselectpopup-search-close");
$cbutton.click(function() {
self.stop();
});
self.view_list = new openerp.base.form.Many2XPopupListView( null, self.session,
self.element_id + "_view_list", self.dataset, false);
self.view_list.popup = self;
self.view_list.do_show();
self.view_list.start();
var tmphack = {"loaded": false};
self.view_list.on_loaded.add_last(function() {
if ( !tmphack.loaded ) {
self.view_list.do_reload();
tmphack.loaded = true;
};
});
});
this.searchview.start();
},
on_select_element: function(element_id) {
},
new_object: function() {
var self = this;
this.searchview.hide();
this.view_list.$element.hide();
this.dataset.index = null;
this.view_form = new openerp.base.FormView({}, this.session,
this.element_id + "_view_form", this.dataset, false);
this.view_form.start();
this.view_form.on_loaded.add_last(function() {
var $buttons = self.view_form.$element.find(".oe_form_buttons");
$buttons.html(QWeb.render("Many2XSelectPopup.form.buttons"));
var $nbutton = $buttons.find(".oe_many2xselectpopup-form-save");
$nbutton.click(function() {
self.view_form.do_save();
});
var $cbutton = $buttons.find(".oe_many2xselectpopup-form-close");
$cbutton.click(function() {
self.stop();
});
});
this.view_form.on_created.add_last(function(r, success) {
if (r.result) {
var id = arguments[0].result;
self.on_select_element(id);
}
});
this.view_form.do_show();
}
});
openerp.base.form.Many2XPopupListView = openerp.base.ListView.extend({
switch_to_record: function(index) {
this.popup.on_select_element(this.dataset.ids[index]);
}
});
openerp.base.form.FieldReference = openerp.base.form.Field.extend({
init: function(view, node) {
this._super(view, node);
@ -966,6 +1181,17 @@ openerp.base.form.FieldReference = openerp.base.form.Field.extend({
}
});
openerp.base.form.FieldImage = openerp.base.form.Field.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldImage";
},
set_value: function(value) {
this._super.apply(this, arguments);
this.$element.find('img').show().attr('src', 'data:image/png;base64,' + this.value);
}
});
/**
* Registry of form widgets, called by :js:`openerp.base.FormView`
*/
@ -992,7 +1218,8 @@ openerp.base.form.widgets = new openerp.base.Registry({
'float' : 'openerp.base.form.FieldFloat',
'integer': 'openerp.base.form.FieldFloat',
'progressbar': 'openerp.base.form.FieldProgressBar',
'float_time': 'openerp.base.form.FieldFloat'
'float_time': 'openerp.base.form.FieldFloatTime',
'image': 'openerp.base.form.FieldImage'
});
};

View File

@ -1,7 +1,6 @@
openerp.base.list = function (openerp) {
openerp.base.views.add('list', 'openerp.base.ListView');
openerp.base.ListView = openerp.base.Controller.extend(
/** @lends openerp.base.ListView# */ {
openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListView# */ {
defaults: {
// records can be selected one by one
'selectable': true,
@ -61,7 +60,11 @@ openerp.base.ListView = openerp.base.Controller.extend(
columns: this.columns,
rows: this.rows
});
$(this.list).bind({
this.groups = new openerp.base.ListView.Groups({
options: this.options,
columns: this.columns
});
$([this.list, this.groups]).bind({
'selected': function (e, selection) {
self.$element.find('#oe-list-delete')
.toggle(!!selection.length);
@ -80,7 +83,7 @@ openerp.base.ListView = openerp.base.Controller.extend(
id, self.do_reload);
},
'row_link': function (e, index) {
self.switch_to_record(index);
self.select_record(index);
}
});
@ -233,7 +236,7 @@ openerp.base.ListView = openerp.base.Controller.extend(
* @param {Number|null} index the record index (in the current dataset) to switch to
* @param {String} [view="form"] the view type to switch to
*/
switch_to_record:function (index, view) {
select_record:function (index, view) {
view = view || 'form';
this.dataset.index = index;
_.delay(_.bind(function () {
@ -290,6 +293,14 @@ openerp.base.ListView = openerp.base.Controller.extend(
// TODO: handle non-empty results.group_by with read_group
self.dataset.context = results.context;
self.dataset.domain = results.domain;
if (results.group_by.length) {
self.groups.datagroup = new openerp.base.DataGroup(
self.session, self.dataset.model,
results.domain, results.context,
results.group_by);
self.$element.html(self.groups.render());
return;
}
return self.do_reload();
});
},
@ -338,7 +349,7 @@ openerp.base.ListView = openerp.base.Controller.extend(
*/
do_add_record: function () {
this.notification.notify('Add', "New record");
this.switch_to_record(null);
this.select_record(null);
},
/**
* Handles deletion of all selected lines
@ -349,10 +360,7 @@ openerp.base.ListView = openerp.base.Controller.extend(
}
// TODO: implement reorder (drag and drop rows)
});
_.extend(openerp.base.ListView.prototype, openerp.base.ActionExecutor);
openerp.base.ListView.List = Class.extend(
/** @lends openerp.base.ListView.List# */{
openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List# */{
/**
* List display for the ListView, handles basic DOM events and transforms
* them in the relevant higher-level events, to which the list view (or
@ -464,10 +472,54 @@ openerp.base.ListView.List = Class.extend(
// drag and drop
// editable?
});
openerp.base.TreeView = openerp.base.Controller.extend({
openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Groups# */{
/**
* Grouped display for the ListView. Handles basic DOM events and interacts
* with the :js:class:`~openerp.base.DataGroup` bound to it.
*
* Provides events similar to those of
* :js:class:`~openerp.base.ListView.List`
*/
init: function (opts) {
this.options = opts.options;
this.columns = opts.columns;
this.datagroup = {};
},
make_level: function (datagroup) {
var self = this, $root = $('<dl>');
datagroup.list().then(function (list) {
_(list).each(function (group, index) {
var $title = $('<dt>')
.text(group.grouped_on + ': ' + group.value + ' (' + group.length + ')')
.appendTo($root);
$title.click(function () {
datagroup.get(index, function (new_dataset) {
var $content = $('<ul>').appendTo(
$('<dd>').insertAfter($title));
new_dataset.read_slice([], null, null, function (records) {
_(records).each(function (record) {
$('<li>')
.appendTo($content)
.text(_(record).map(function (value, key) {
return key + ': ' + value;
}).join(', '));
});
});
}, function (new_datagroup) {
console.log(new_datagroup);
$('<dd>')
.insertAfter($title)
.append(self.make_level(new_datagroup));
});
});
});
});
return $root;
},
render: function () {
return this.make_level(this.datagroup);
}
});
};
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:

View File

@ -0,0 +1,121 @@
openerp.base.m2o = function(openerp){
openerp.base.m2o = openerp.base.Controller.extend({
init: function(view_manager, element_id, model, dataset, session){
this._super(element_id, model, dataset, session);
this.view_manager = view_manager
this.session = session;
this.element = element_id.find('input');
this.dataset = dataset;
var cache = {};
var lastXhr;
this.relation = model;
this.result_ids = []
var self = this
var $input = this.element.autocomplete({
source: function(request, response){
var search_val = request.term;
if (search_val in cache) {
response(cache[search_val]);
return;
}
//pass request to server
lastXhr = self.dataset.name_search(search_val, function(obj, status, xhr){
var result = obj.result
var values = [];
if (!result.length) {
values.push({'value': 'Create...', id: 'create'})
}
$.each(result, function(i, val){
values.push({
value: val[1],
id: val[0],
orig_val: val[1]
});
self.result_ids.push(result[i][0])
});
if (values.length > 10) {
values = values.slice(0, 10);
values.push({'value': 'More...', id: 'more'})
}
//process response
cache[search_val] = values;
response(values);
});
return;
},
select: function(event, ui){
ui.item.value = ui.item.orig_val? ui.item.orig_val : self.element.data( "autocomplete" ).term
if (ui.item.id == 'more') {
self.dataset.ids = self.result_ids;
self.dataset.count = self.dataset.ids.length;
self.dataset.domain = []
self.element.val('')
var pop = new openerp.base.form.Many2XSelectPopup(null, self.session);
pop.select_element(self.relation, self.dataset);
return;
}
if (ui.item.id == 'create') {
var val = self.element.val()
self.dataset.create({'name': ui.item.value},
function(r){}, function(r){
var element_id = _.uniqueId("act_window_dialog");
var dialog = jQuery('<div>',
{'id': element_id
}).dialog({
modal: true,
minWidth: 800
});
self.element.val('')
var event_form = new openerp.base.FormView(self.view_manager, self.session, element_id, self.dataset, false);
event_form.start();
});
$input.val(self.element.data( "autocomplete" ).term);
return true;
}
self.element.attr('m2o_id', ui.item.id);
},
minLength: 0,
focus: function(event, ui) {
if (ui.item.id == ('create')) {
return true;
}
ui.item.value = self.element.data("autocomplete").term.length ? self.element.val() + '[' + ui.item.orig_val.substring(self.element.data("autocomplete").term.length) + ']' : this.lastSearch
},
});
$("<div type='button' class='ui_combo'>&nbsp;</div>")
.attr("tabIndex", -1)
.attr("title", "Show All Items")
.insertAfter($input)
.button({
icons: {
primary: "ui-icon-triangle-1-s"
},
text: false
})
.removeClass("ui-corner-all")
.addClass("ui-corner-right ui-button-icon")
.click(function() {
// close if already visible
if ($input.autocomplete("widget").is(":visible")) {
$input.autocomplete( "close" );
return;
}
$(this).blur();
$input.autocomplete("search", "" );
$input.focus();
});
}
});
}

View File

@ -121,7 +121,10 @@ openerp.base.SearchView = openerp.base.Controller.extend({
'lines': lines,
'defaults': this.defaults
});
this.$element.html(render);
// We don't understand why the following commented line does not work in Chrome but
// the non-commented line does. As far as we investigated, only God knows.
//this.$element.html(render);
jQuery(render).appendTo(this.$element);
var f = this.$element.find('form');
this.$element.find('form')

View File

@ -0,0 +1,26 @@
/*---------------------------------------------------------
* OpenERP base library
*---------------------------------------------------------*/
openerp.base.view_tree = function(openerp) {
openerp.base.views.add('tree', 'openerp.base.TreeView');
openerp.base.TreeView = openerp.base.Controller.extend({
/**
* Genuine tree view (the one displayed as a tree, not the list)
*/
start: function () {
this._super();
this.$element.append('Tree view');
},
do_show: function () {
this.$element.show();
},
do_hide: function () {
this.$element.hide();
}
});
};
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:

View File

@ -17,17 +17,22 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
/**
* Process an action
* Supported actions: act_window
*
* If the action contains a 'no_sidebar' key, the resulting view will never contain a sidebar.
*/
do_action: function(action) {
var self = this;
action.flags = _.extend({
sidebar : true,
search_view : true,
new_window : false,
toolbar : true,
pager : true
}, action.flags || {});
// instantiate the right controllers by understanding the action
switch (action.type) {
case 'ir.actions.act_window':
if (action.target == 'new') {
var element_id = _.uniqueId("act_window_dialog");
var dialog = $('<div id="'+element_id+'"></div>');
var dialog = $('<div id="' + element_id + '"></div>');
dialog.dialog({
title: action.name,
modal: true,
@ -37,18 +42,20 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
// When dialog is closed with ESC key or close manually, branch to act_window_close logic
self.do_action({ type: 'ir.actions.act_window_close' });
});
var viewmanager = new openerp.base.ViewManagerAction(this.session, element_id, action, false);
var viewmanager = new openerp.base.ViewManagerAction(this.session, element_id, action);
viewmanager.start();
this.dialog_stack.push(viewmanager);
} else if (action.flags.new_window) {
this.rpc("/base/session/save_session_action", { the_action : action}, function(key) {
var url = window.location.protocol + "//" + window.location.host +
window.location.pathname + "?" + jQuery.param({ s_action : "" + key });
window.open(url);
});
} else {
if (this.viewmanager) {
this.viewmanager.stop();
}
var sidebar = true;
if(action.no_sidebar) {
sidebar = false;
}
this.viewmanager = new openerp.base.ViewManagerAction(this.session, this.element_id, action, sidebar);
this.viewmanager = new openerp.base.ViewManagerAction(this.session, this.element_id, action);
this.viewmanager.start();
}
break;
@ -63,63 +70,6 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
}
});
/**
* Mixin for action-executing objects, provides handling of OpenERP actions to
* all clients.
*
* Mix into existing classes via ``_.extend`` of the class's prototype.
*
* @class
*/
openerp.base.ActionExecutor =
/**
* @lends openerp.base.ActionExecutor#
*/ {
/**
* Fetches and executes the action identified by ``action_data``.
*
* @param {Object} action_data the action descriptor data
* @param {String} action_data.name the action name, used to uniquely identify the action to find and execute it
* @param {String} [action_data.special=null] special action handlers (currently: only ``'cancel'``)
* @param {String} [action_data.type='workflow'] the action type, if present, one of ``'object'``, ``'action'`` or ``'workflow'``
* @param {Object} [action_data.context=null] additional action context, to add to the current context
* @param {openerp.base.DataSet} dataset a dataset object used to communicate with the server
* @param {openerp.base.ActionManager} action_manager object able to actually execute the action, if any is fetched
* @param {Number} [record_id] the identifier of the object on which the action is to be applied
* @param {Function} on_no_action callback to execute if the action does not generate any result (no new action)
*/
execute_action: function (action_data, dataset, action_manager, record_id, on_no_action) {
var handler = function (r) {
if (r.result && r.result.constructor == Object) {
action_manager.do_action(r.result);
} else {
on_no_action(r.result);
}
};
if (action_data.special) {
handler({
result : { type: 'ir.actions.act_window_close' }
});
} else {
var context = _.extend({}, dataset.context, action_data.context || {});
switch(action_data.type) {
case 'object':
return dataset.call(action_data.name, [record_id], [context], handler);
case 'action':
return this.rpc('/base/action/load', { action_id: parseInt(action_data.name, 10) }, handler);
default:
return dataset.exec_workflow(record_id, action_data.name, handler);
}
}
}
};
/**
* Registry for all the main views
*/
openerp.base.views = new openerp.base.Registry();
openerp.base.ViewManager = openerp.base.Controller.extend({
init: function(session, element_id, dataset, views) {
this._super(session, element_id);
@ -240,7 +190,7 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
});
openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
init: function(session, element_id, action, sidebar) {
init: function(session, element_id, action) {
var dataset;
if(!action.res_id) {
dataset = new openerp.base.DataSetSearch(session, action.res_model);
@ -251,9 +201,9 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
}
this._super(session, element_id, dataset, action.views);
this.action = action;
this.sidebar = sidebar;
if (sidebar)
if (action.flags.sidebar) {
this.sidebar = new openerp.base.Sidebar(null, this);
}
},
start: function() {
var inital_view_loaded = this._super();
@ -275,17 +225,14 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
// init search view
var searchview_id = this.action.search_view_id && this.action.search_view_id[0];
if (searchview_id) {
var searchview_loaded = this.setup_search_view(
searchview_id, search_defaults);
var searchview_loaded = this.setup_search_view(
searchview_id || false, search_defaults);
// schedule auto_search
if (this.action['auto_search']) {
$.when(searchview_loaded, inital_view_loaded)
.then(this.searchview.do_search);
}
// schedule auto_search
if (searchview_loaded != null && this.action['auto_search']) {
$.when(searchview_loaded, inital_view_loaded)
.then(this.searchview.do_search);
}
},
stop: function() {
// should be replaced by automatic destruction implemented in BaseWidget
@ -349,7 +296,10 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({
var i = $this.attr("data-i");
var j = $this.attr("data-j");
var action = self.sections[i].elements[j];
(new openerp.base.ExternalActionManager(self.view_manager.session, null)) .handle_action(action);
action.flags = {
new_window : true
}
self.session.action_manager.do_action(action);
e.stopPropagation();
e.preventDefault();
});
@ -360,89 +310,51 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({
}
});
openerp.base.ExternalActionManager = openerp.base.Controller.extend({
handle_action: function(action) {
if(action.type=="ir.actions.act_window") {
if(action.target=="new") {
var element_id = _.uniqueId("act_window_dialog");
var dialog = $('<div id="'+element_id+'"></div>');
dialog.dialog({
title: action.name
});
var viewmanager = new openerp.base.ViewManagerAction(this.session ,element_id, action, false);
viewmanager.start();
} else if (action.target == "current") {
this.rpc("/base/session/save_session_action", {the_action:action}, function(key) {
var url = window.location.protocol + "//" + window.location.host +
window.location.pathname + "?" + jQuery.param({s_action:""+key});
window.open(url);
});
openerp.base.View = openerp.base.Controller.extend({
/**
* Fetches and executes the action identified by ``action_data``.
*
* @param {Object} action_data the action descriptor data
* @param {String} action_data.name the action name, used to uniquely identify the action to find and execute it
* @param {String} [action_data.special=null] special action handlers (currently: only ``'cancel'``)
* @param {String} [action_data.type='workflow'] the action type, if present, one of ``'object'``, ``'action'`` or ``'workflow'``
* @param {Object} [action_data.context=null] additional action context, to add to the current context
* @param {openerp.base.DataSet} dataset a dataset object used to communicate with the server
* @param {openerp.base.ActionManager} action_manager object able to actually execute the action, if any is fetched
* @param {Number} [record_id] the identifier of the object on which the action is to be applied
* @param {Function} on_no_action callback to execute if the action does not generate any result (no new action)
*/
execute_action: function (action_data, dataset, action_manager, record_id, on_no_action) {
var handler = function (r) {
if (r.result && r.result.constructor == Object) {
action_manager.do_action(r.result);
} else {
on_no_action(r.result);
}
};
if (action_data.special) {
handler({
result : { type: 'ir.actions.act_window_close' }
});
} else {
var context = _.extend({}, dataset.context, action_data.context || {});
switch(action_data.type) {
case 'object':
return dataset.call(action_data.name, [record_id], [context], handler);
case 'action':
return this.rpc('/base/action/load', { action_id: parseInt(action_data.name, 10) }, handler);
default:
return dataset.exec_workflow(record_id, action_data.name, handler);
}
}
// TODO: show an error like "not implemented" here
// since we don't currently have any way to handle errors do you have any better idea
// than using todos?
}
});
openerp.base.views.add('calendar', 'openerp.base.CalendarView');
openerp.base.CalendarView = openerp.base.Controller.extend({
start: function () {
this._super();
this.$element.append('Calendar view');
},
do_show: function () {
this.$element.show();
},
do_hide: function () {
this.$element.hide();
}
});
openerp.base.views.add('gantt', 'openerp.base.GanttView');
openerp.base.GanttView = openerp.base.Controller.extend({
start: function () {
this._super();
this.$element.append('Gantt view');
},
do_show: function () {
this.$element.show();
},
do_hide: function () {
this.$element.hide();
}
});
openerp.base.views.add('tree', 'openerp.base.TreeView');
openerp.base.TreeView = openerp.base.Controller.extend({
/**
* Genuine tree view (the one displayed as a tree, not the list)
* Registry for all the main views
*/
start: function () {
this._super();
this.$element.append('Tree view');
},
do_show: function () {
this.$element.show();
},
do_hide: function () {
this.$element.hide();
}
});
openerp.base.views.add('graph', 'openerp.base.GraphView');
openerp.base.GraphView = openerp.base.Controller.extend({
start: function () {
this._super();
this.$element.append('Graph view');
},
do_show: function () {
this.$element.show();
},
do_hide: function () {
this.$element.hide();
}
});
openerp.base.views = new openerp.base.Registry();
openerp.base.ProcessView = openerp.base.Controller.extend({
});

View File

@ -18,8 +18,7 @@
</div>
</div>
<div id="oe_login" class="login"></div>
<table border="0" cellpadding="0" cellspacing="0" width="100%" height="100%"
t-att-class="'main_table' + (typeof kitten == 'undefined' ? '' : ' kitten-mode-activated')">
<table border="0" cellpadding="0" cellspacing="0" width="100%" height="100%" class="main_table">
<tr>
<td colspan="2">
<div id="oe_header" class="header"></div>
@ -55,8 +54,7 @@
</t>
<t t-name="Header">
<a href="/" class="company_logo_link">
<img t-att-src="typeof kitten == 'undefined' ? '/base/static/src/img/logo.png' :
'http://placekitten.com/g/179/46'" border="0" class="company_logo"/>
<div class="company_logo" />
</a>
<h1 class="header_title" t-if="session.session_is_valid()">
<span class="company">$company</span> - (<span class="database">$database</span>)<br/>
@ -259,7 +257,7 @@
<t t-foreach="row" t-as="td">
<td t-att-colspan="td.colspan gt 1 ? td.colspan : undefined"
t-att-width="td.width ? td.width : undefined"
t-att-nowrap="td.is_field_label ? 'true' : undefined"
t-att-nowrap="td.is_field_label or td.is_field_m2o? 'true' : undefined"
t-att-valign="td.table ? 'top' : undefined"
t-att-id="td.element_id"
t-att-class="'oe_form_' + (td.is_field_label ? 'label' : (td.field ? 'field_' + td.type : td.type))"
@ -306,6 +304,20 @@
t-att-class="'field_' + widget.type" style="width: 100%"
/>
</t>
<t t-name="FieldEmail">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tr>
<td width="100%">
<t t-call="FieldChar"/>
</td>
<td width="16">
<button class="button" title="Send an e-mail with your default e-mail client">
<img src="/base/static/src/img/icons/terp-mail-message-new.png"/>
</button>
</td>
</tr>
</table>
</t>
<t t-name="FieldText">
<textarea rows="6" style="width: 100%;"
t-att-name="widget.name"
@ -320,13 +332,6 @@
t-att-class="'field_' + widget.type"
/>
</t>
<t t-name="FieldDatetime">
<input type="text" style="width: 100%"
t-att-name="widget.name"
t-att-id="widget.element_id + '_field'"
t-att-class="'field_' + widget.type"
/>
</t>
<t t-name="FieldSelection">
<select
t-att-name="widget.name"
@ -345,7 +350,9 @@
t-att-name="widget.name"
t-att-id="widget.element_id + '_field'"
t-att-class="'field_' + widget.type"
t-att-type="widget.type"
style="width: 100%;"/>
</t>
<t t-name="FieldOne2Many">
<div t-att-id="widget.element_id">
@ -369,6 +376,15 @@
<span></span>
</div>
</t>
<t t-name="FieldImage">
<img align="left" border="1" style="display: none"
t-att-id="widget.element_id + '_field'"
t-att-name="widget.name"
t-att-class="'field_' + widget.type"
t-att-width="widget.node.attrs.img_width || widget.node.attrs.width"
t-att-height="widget.node.attrs.img_height || widget.node.attrs.height"
/>
</t>
<t t-name="WidgetButton">
<button type="button"
t-att-id="widget.element_id + '_button'"
@ -382,7 +398,7 @@
<h2 class="oe_view_title"><t t-esc="view.attrs['string']"/></h2>
<form>
<t t-call="SearchView.render_lines"/>
<div style="text-align:right;">
<div class="oe_search-view-buttons" style="text-align:right;">
<input type="submit" value="Search"/>
<input type="reset" value="Clear"/>
</div>
@ -418,7 +434,7 @@
<span t-if="attrs.help">(?)</span>
</label>
<div style="white-space: nowrap;">
<input type="text" t-att-name="attrs.name"
<input type="text" size="15" t-att-name="attrs.name"
t-att-autofocus="attrs.default_focus === '1' ? 'autofocus' : undefined"
t-att-id="element_id"
t-att-value="defaults[attrs.name] || ''"/>
@ -490,9 +506,10 @@
<t t-set="expand" t-value="false"/>
<t t-set="label" t-value="'Custom Filters'"/>
<t t-set="content">
<div class="searchview_extended_groups_list"/>
<div class="searchview_extended_groups_list">
</div>
<button class="searchview_extended_add_group"
type="button">Add group of conditions</button>
type="button"><span>Add group of conditions</span></button>
</t>
</t>
</t>
@ -504,10 +521,10 @@
<option value="none">None of the following conditions must match</option>
</select>
<button class="searchview_extended_delete_group"
type="button">Delete this group of conditions</button>
type="button"><span>Delete this group of conditions</span></button>
<div class="searchview_extended_propositions_list">
</div>
<button class="searchview_extended_add_proposition" type="button">Add condition</button>
<button class="searchview_extended_add_proposition" type="button"><span>Add condition</span></button>
</div>
</t>
<t t-name="SearchView.extended_search.proposition">
@ -523,7 +540,7 @@
<select class="searchview_extended_prop_op"/>
<span class="searchview_extended_prop_value"/>
<button class="searchview_extended_delete_prop"
type="button">Delete this condition</button>
type="button"><span>Delete this condition</span></button>
</div>
</t>
<t t-name="SearchView.extended_search.proposition.char">
@ -544,12 +561,12 @@
<a class="toggle-sidebar"></a>
<div t-att-id="element_id" class="sidebar-sub-div">
<div class="sidebar-displaying-div">
<t t-set="i" t-value="0"/>
<t t-set="i" t-value="1-1"/> <!-- al do stupid things -->
<t t-foreach="sections" t-as="section">
<t t-if="section.elements.length &gt; 0">
<h2><t t-esc="section.label"/></h2>
<ul>
<t t-set="j" t-value="0"/>
<t t-set="j" t-value="1-1"/>
<t t-foreach="section.elements" t-as="element">
<li><a t-att-data-i="i" t-att-data-j="j" href="#"><t t-esc="element.name"/></a></li>
<t t-set="j" t-value="j+1"/>
@ -570,4 +587,19 @@
</p>
</div>
</t>
<t t-name="Many2XSelectPopup">
<div t-att-id="element_id">
<div t-att-id="element_id + '_search'"></div>
<div t-att-id="element_id + '_view_list'"></div>
<div t-att-id="element_id + '_view_form'"></div>
</div>
</t>
<t t-name="Many2XSelectPopup.search.buttons">
<button type="button" class="oe_many2xselectpopup-search-new">New</button>
<button type="button" class="oe_many2xselectpopup-search-close">Close</button>
</t>
<t t-name="Many2XSelectPopup.form.buttons">
<button type="button" class="oe_many2xselectpopup-form-save">Save</button>
<button type="button" class="oe_many2xselectpopup-form-close">Close</button>
</t>
</templates>

View File

@ -22,6 +22,7 @@
<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/m2o.js"></script>
<script src="/base/static/src/js/form.js"></script>
<script src="/base/static/src/js/list.js"></script>
<script type="text/javascript">

View File

@ -10,5 +10,6 @@
'static/src/js/web_chat.js'
],
"css": [],
# 'active': True,
'active': False,
}

View File

@ -307,8 +307,6 @@ Javascript
:returns: the dataset's current active id
.. js:class:: openerp.base.DataRecord(session, model, fields, values)
Ad-hoc objects and structural types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1,27 +1,3 @@
[global]
server.environment = "development"
# Some server parameters that you may want to tweak
server.socket_host = "0.0.0.0"
server.socket_port = 8080
# Sets the number of threads the server uses
server.thread_pool = 10
tools.sessions.on = True
tools.sessions.persistent = False
# logging
#log.access_file = "/var/log/openerp-web/access.log"
#log.error_file = "/var/log/openerp-web/error.log"
log.access_level = "INFO"
log.error_level = "INFO"
# OpenERP Server
openerp.server.host = 'localhost'
openerp.server.port = '8070'
openerp.server.protocol = 'socket'
openerp.server.timeout = 450
# Regex for dbname %{subdomain}s for the subdomain
#openerp.web.database.list = "%{subdomain}s_.*"
#openerp.web.database.manager = False

View File

@ -20,9 +20,6 @@
#
##############################################################################
""" We assume that, just like the OpenERP server, the python part of the client
doesn't need to handle timezone-aware datetimes, this could be changed in the future. """
import datetime
DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d"
@ -32,31 +29,60 @@ DEFAULT_SERVER_DATETIME_FORMAT = "%s %s" % (
DEFAULT_SERVER_TIME_FORMAT)
def parse_datetime(str):
"""
Converts a string to a datetime object using OpenERP's
datetime string format (exemple: '2011-12-01 15:12:35').
No timezone information is added, the datetime is a naive instance, but
according to OpenERP 6.1 specification the timezone is always UTC.
"""
if not str:
return str
return datetime.datetime.strptime(str, DEFAULT_SERVER_DATETIME_FORMAT)
def parse_date(str):
"""
Converts a string to a date object using OpenERP's
date string format (exemple: '2011-12-01').
"""
if not str:
return str
return datetime.datetime.strptime(str, DEFAULT_SERVER_DATE_FORMAT).date()
def parse_time(str):
"""
Converts a string to a time object using OpenERP's
time string format (exemple: '15:12:35').
"""
if not str:
return str
return datetime.datetime.strptime(str, DEFAULT_SERVER_TIME_FORMAT).time()
def format_datetime(obj):
"""
Converts a datetime object to a string using OpenERP's
datetime string format (exemple: '2011-12-01 15:12:35').
The datetime instance should not have an attached timezone and be in UTC.
"""
if not obj:
return False
return obj.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
def format_date(obj):
"""
Converts a date object to a string using OpenERP's
date string format (exemple: '2011-12-01').
"""
if not obj:
return False
return obj.strftime(DEFAULT_SERVER_DATE_FORMAT)
def format_time(obj):
"""
Converts a time object to a string using OpenERP's
time string format (exemple: '15:12:35').
"""
if not obj:
return False
return obj.strftime(DEFAULT_SERVER_TIME_FORMAT)

View File

@ -500,30 +500,42 @@ def main(argv):
# change the timezone of the program to the OpenERP server's assumed timezone
os.environ["TZ"] = "UTC"
# Parse config
op = optparse.OptionParser()
op.add_option("-p", "--port", dest="socket_port", help="listening port", metavar="NUMBER", default=8002)
op.add_option("-s", "--session-path", dest="storage_path",
help="directory used for session storage", metavar="DIR",
default=os.path.join(tempfile.gettempdir(), "cpsessions"))
(o, args) = op.parse_args(argv[1:])
# Prepare cherrypy config from options
if not os.path.exists(o.storage_path):
os.mkdir(o.storage_path, 0700)
config = {
'server.socket_port': int(o.socket_port),
DEFAULT_CONFIG = {
'server.socket_port': 8002,
'server.socket_host': '0.0.0.0',
#'server.thread_pool' = 10,
'tools.sessions.on': True,
'tools.sessions.storage_type': 'file',
'tools.sessions.storage_path': o.storage_path,
'tools.sessions.storage_path': os.path.join(tempfile.gettempdir(), "cpsessions"),
'tools.sessions.timeout': 60
}
# Parse config
op = optparse.OptionParser()
op.add_option("-p", "--port", dest="server.socket_port", help="listening port",
type="int", metavar="NUMBER")
op.add_option("-s", "--session-path", dest="tools.sessions.storage_path",
help="directory used for session storage", metavar="DIR")
(o, args) = op.parse_args(argv[1:])
o = vars(o)
for k in o.keys():
if o[k] == None:
del(o[k])
# Setup and run cherrypy
cherrypy.tree.mount(Root())
cherrypy.config.update(config)
cherrypy.config.update(config=DEFAULT_CONFIG)
if os.path.exists(os.path.join(os.path.dirname(
os.path.dirname(__file__)),'openerp-web.cfg')):
cherrypy.config.update(os.path.join(os.path.dirname(
os.path.dirname(__file__)),'openerp-web.cfg'))
if os.path.exists(os.path.expanduser('~/.openerp_webrc')):
cherrypy.config.update(os.path.expanduser('~/.openerp_webrc'))
cherrypy.config.update(o)
if not os.path.exists(cherrypy.config['tools.sessions.storage_path']):
os.mkdir(cherrypy.config['tools.sessions.storage_path'], 0700)
cherrypy.server.subscribe()
cherrypy.engine.start()
cherrypy.engine.block()