[Merge] With trunk upto revision 584.

bzr revid: noz@tinyerp.com-20110711072314-kas966zook3joe8p
This commit is contained in:
noz (OpenERP) 2011-07-11 12:53:14 +05:30
commit 84fd866212
15 changed files with 446 additions and 211 deletions

View File

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
import base64
import glob, os
import pprint
from xml.etree import ElementTree
from cStringIO import StringIO
@ -179,14 +178,14 @@ class Session(openerpweb.Controller):
def jslist(self, req, mods='base'):
return {'files': self.manifest_glob(mods.split(','), 'js')}
def css(self, req, mods='base,base_hello'):
def css(self, req, mods='base'):
files = self.manifest_glob(mods.split(','), 'css')
concat = self.concat_files(files)[0]
# TODO request set the Date of last modif and Etag
return concat
css.exposed = True
def js(self, req, mods='base,base_hello'):
def js(self, req, mods='base'):
files = self.manifest_glob(mods.split(','), 'js')
concat = self.concat_files(files)[0]
# TODO request set the Date of last modif and Etag
@ -446,9 +445,10 @@ class DataSet(openerpweb.Controller):
req.session.eval_context(req.context))}
@openerpweb.jsonrequest
def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
return self.do_search_read(request, model, fields, offset, limit, domain, context, sort)
def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, context=None, sort=None):
def search_read(self, request, model, fields=False, offset=0, limit=False, domain=None, sort=None):
return self.do_search_read(request, model, fields, offset, limit, domain, sort)
def do_search_read(self, request, model, fields=False, offset=0, limit=False, domain=None
, sort=None):
""" Performs a search() followed by a read() (if needed) using the
provided search criteria
@ -461,22 +461,33 @@ class DataSet(openerpweb.Controller):
:param int limit: the maximum number of records to return
:param list domain: the search domain for the query
:param list sort: sorting directives
:returns: a list of result records
:returns: A structure (dict) with two keys: ids (all the ids matching
the (domain, context) pair) and records (paginated records
matching fields selection set)
:rtype: list
"""
Model = request.session.model(model)
context, domain = eval_context_and_domain(request.session, request.context, domain)
ids = Model.search(domain, offset or 0, limit or False,
sort or False, context)
context, domain = eval_context_and_domain(
request.session, request.context, domain)
ids = Model.search(domain, 0, False, sort or False, context)
# need to fill the dataset with all ids for the (domain, context) pair,
# so search un-paginated and paginate manually before reading
paginated_ids = ids[offset:(offset + limit if limit else None)]
if fields and fields == ['id']:
# shortcut read if we only want the ids
return map(lambda id: {'id': id}, ids)
return {
'ids': ids,
'records': map(lambda id: {'id': id}, paginated_ids)
}
records = Model.read(paginated_ids, fields or False, context)
records.sort(key=lambda obj: ids.index(obj['id']))
return {
'ids': ids,
'records': records
}
reads = Model.read(ids, fields or False, context)
reads.sort(key=lambda obj: ids.index(obj['id']))
return reads
@openerpweb.jsonrequest
def get(self, request, model, ids, fields=False):
@ -637,36 +648,6 @@ class View(openerpweb.Controller):
return {'result': True}
return {'result': False}
def normalize_attrs(self, elem, context):
""" Normalize @attrs, @invisible, @required, @readonly and @states, so
the client only has to deal with @attrs.
See `the discoveries pad <http://pad.openerp.com/discoveries>`_ for
the rationale.
:param elem: the current view node (Python object)
:type elem: xml.etree.ElementTree.Element
:param dict context: evaluation context
"""
# If @attrs is normalized in json by server, the eval should be replaced by simplejson.loads
attrs = openerpweb.ast.literal_eval(elem.get('attrs', '{}'))
if 'states' in elem.attrib:
attrs.setdefault('invisible', [])\
.append(('state', 'not in', elem.attrib.pop('states').split(',')))
if attrs:
elem.set('attrs', simplejson.dumps(attrs))
for a in ['invisible', 'readonly', 'required']:
if a in elem.attrib:
# In the XML we trust
avalue = bool(eval(elem.get(a, 'False'),
{'context': context or {}}))
if not avalue:
del elem.attrib[a]
else:
elem.attrib[a] = '1'
if a == 'invisible' and 'attrs' in elem.attrib:
del elem.attrib['attrs']
def transform_view(self, view_string, session, context=None):
# transform nodes on the fly via iterparse, instead of
# doing it statically on the parsing result
@ -676,7 +657,6 @@ class View(openerpweb.Controller):
if event == "start":
if root is None:
root = elem
self.normalize_attrs(elem, context)
self.parse_domains_and_contexts(elem, session)
return root

View File

@ -680,6 +680,9 @@ background: linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* W3C */
vertical-align: middle;
text-align: left;
}
.openerp .oe-listview td.oe-record-delete {
text-align: right;
}
.openerp .oe-listview th.oe-sortable,
.openerp .oe-listview th.oe-sortable .ui-icon {
cursor: pointer;
@ -710,6 +713,17 @@ background: linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* W3C */
.openerp .oe-listview th.oe-list-pager {
text-align: right;
}
.openerp .oe-list-pager .oe-pager-state {
cursor: pointer;
}
.openerp .oe-listview .oe-group-name {
padding-right: 1em;
}
.openerp .oe-listview .oe-group-name,
.openerp .oe-listview .oe-group-pagination {
white-space: nowrap;
}
.openerp .oe-listview tfoot td {
padding: 3px 3px 0;

View File

@ -350,7 +350,6 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b
* @param {Object} params call parameters
* @param {Function} success_callback function to execute on RPC call success
* @param {Function} error_callback function to execute on RPC call failure
* one
* @returns {jQuery.Deferred} jquery-provided ajax deferred
*/
rpc: function(url, params, success_callback, error_callback) {

View File

@ -135,7 +135,7 @@ openerp.base.ContainerDataGroup = openerp.base.DataGroup.extend(
domain: this.domain,
group_by_fields: this.group_by
}, function () { }).then(function (response) {
var data_groups = _(response.result).map(
var data_groups = _(response).map(
_.bind(self.transform_group, self));
self.groups = data_groups;
d.resolveWith(self, [data_groups]);
@ -196,7 +196,9 @@ openerp.base.GrouplessDataGroup = openerp.base.DataGroup.extend(
this._super(session, model, domain, context, null, level);
},
list: function (ifGroups, ifRecords) {
ifRecords(new openerp.base.DataSetSearch(this.session, this.model, this.context, this.domain));
ifRecords(_.extend(
new openerp.base.DataSetSearch(this.session, this.model),
{domain: this.domain, context: this.context}));
}
});
@ -421,13 +423,10 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({
sort: this.sort(),
offset: offset,
limit: limit
}, function (records) {
self.ids.splice(0, self.ids.length);
}, function (result) {
self.ids = result.ids;
self.offset = offset;
for (var i=0; i < records.length; i++ ) {
self.ids.push(records[i].id);
}
callback(records);
callback(result.records);
});
},
/**

View File

@ -132,7 +132,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
on_form_changed: function() {
for (var w in this.widgets) {
w = this.widgets[w];
w.process_attrs();
w.process_modifiers();
w.update_dom();
}
},
@ -166,7 +166,6 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
this.ready = false;
var onchange = _.trim(widget.node.attrs.on_change);
var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/);
console.log("Onchange triggered for field '%s' -> %s", widget.name, onchange);
if (call) {
var method = call[1], args = [];
var context_index = null;
@ -226,14 +225,12 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
on_processed_onchange: function(response, processed) {
var result = response.result;
if (result.value) {
console.log(" |-> Onchange Response :", result.value);
for (var f in result.value) {
var field = this.fields[f];
if (field) {
var value = result.value[f];
processed.push(field.name);
if (field.get_value() != value) {
console.log(" |-> Onchange Action : change '%s' value from '%s' to '%s'", field.name, field.get_value(), value);
field.set_value(value);
field.touched = true;
if (_.indexOf(processed, field.name) < 0) {
@ -242,12 +239,11 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
}
} else {
// this is a common case, the normal behavior should be to ignore it
console.debug("on_processed_onchange can't find field " + f, result);
}
}
this.on_form_changed();
}
if (result.warning && !_.isEmpty(result.warning)) {
if (!_.isEmpty(result.warning)) {
$(QWeb.render("DialogWarning", result.warning)).dialog({
modal: true,
buttons: {
@ -396,12 +392,13 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
this.on_attachments_loaded([]);
} else {
// TODO fme: modify this so it doesn't try to load attachments when there is not sidebar
/*this.rpc('/base/dataset/search_read', {
model: 'ir.attachment',
fields: ['name', 'url', 'type'],
domain: [['res_model', '=', this.dataset.model], ['res_id', '=', this.datarecord.id], ['type', 'in', ['binary', 'url']]],
context: this.dataset.get_context()
}, this.on_attachments_loaded);*/
/*(new openerp.base.DataSetSearch(
this.session, 'ir.attachment', this.dataset.get_context(),
[['res_model', '=', this.dataset.model],
['res_id', '=', this.datarecord.id],
['type', 'in', ['binary', 'url']]])).read_slice(
['name', 'url', 'type'], false, false,
this.on_attachments_loaded);*/
}
},
on_attachments_loaded: function(attachments) {
@ -506,7 +503,7 @@ openerp.base.form.compute_domain = function(expr, fields) {
stack.push(!_(val).contains(field));
break;
default:
this.log("Unsupported operator in attrs :", op);
this.log("Unsupported operator in modifiers :", op);
}
}
return _.all(stack);
@ -517,7 +514,7 @@ openerp.base.form.Widget = openerp.base.Controller.extend({
init: function(view, node) {
this.view = view;
this.node = node;
this.attrs = JSON.parse(this.node.attrs.attrs || '{}');
this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}');
this.type = this.type || node.tag;
this.element_name = this.element_name || this.type;
this.element_id = [this.view.element_id, this.element_name, this.view.widgets_counter++].join("_");
@ -530,15 +527,15 @@ openerp.base.form.Widget = openerp.base.Controller.extend({
this.string = this.string || node.attrs.string;
this.help = this.help || node.attrs.help;
this.invisible = (node.attrs.invisible == '1');
this.invisible = this.modifiers['invisible'] === true;
},
start: function() {
this.$element = $('#' + this.element_id);
},
process_attrs: function() {
process_modifiers: function() {
var compute_domain = openerp.base.form.compute_domain;
for (var a in this.attrs) {
this[a] = compute_domain(this.attrs[a], this.view.fields);
for (var a in this.modifiers) {
this[a] = compute_domain(this.modifiers[a], this.view.fields);
}
},
update_dom: function() {
@ -739,10 +736,9 @@ openerp.base.form.Field = openerp.base.form.Widget.extend({
this.field = view.fields_view.fields[node.attrs.name] || {};
this.string = node.attrs.string || this.field.string;
this.help = node.attrs.help || this.field.help;
this.invisible = (this.invisible || this.field.invisible == '1');
this.nolabel = (this.field.nolabel || node.attrs.nolabel) == '1';
this.readonly = (this.field.readonly || node.attrs.readonly) == '1';
this.required = (this.field.required || node.attrs.required) == '1';
this.nolabel = (this.field.nolabel || node.attrs.nolabel) === '1';
this.readonly = this.modifiers['readonly'] === true;
this.required = this.modifiers['required'] === true;
this.invalid = false;
this.touched = false;
},
@ -801,7 +797,7 @@ openerp.base.form.Field = openerp.base.form.Widget.extend({
var v_context1 = this.node.attrs.default_get || {};
var v_context2 = this.node.attrs.context || {};
var v_context = new openerp.base.CompoundContext(v_context1, v_context2);
if (v_context1.__ref || v_context2.__ref) {
if (v_context1.__ref || v_context2.__ref || true) { //TODO niv: remove || true
var fields_values = this._build_view_fields_values();
v_context.set_eval_context(fields_values);
}
@ -811,7 +807,7 @@ openerp.base.form.Field = openerp.base.form.Widget.extend({
build_domain: function() {
var f_domain = this.field.domain || [];
var v_domain = this.node.attrs.domain || [];
if (!(v_domain instanceof Array)) {
if (!(v_domain instanceof Array) || true) { //TODO niv: remove || true
var fields_values = this._build_view_fields_values();
v_domain = new openerp.base.CompoundDomain(v_domain).set_eval_context(fields_values);
}
@ -915,6 +911,25 @@ openerp.base.form.FieldFloat = openerp.base.form.FieldChar.extend({
}
});
openerp.base.form.FieldInteger = openerp.base.form.FieldFloat.extend({
init: function(view, node) {
this._super(view, node);
this.validation_regex = /^-?\d+$/;
},
set_value: function(value) {
this._super.apply(this, [value]);
if (value === false || value === undefined) {
// TODO fme: check if GTK client default integers to 0 (like it does with floats)
value = 0;
}
var show_value = parseInt(value, 10);
this.$element.find('input').val(show_value);
},
set_value_from_ui: function() {
this.value = Number(this.$element.find('input').val());
}
});
openerp.base.form.FieldDatetime = openerp.base.form.Field.extend({
init: function(view, node) {
this._super(view, node);
@ -935,7 +950,7 @@ openerp.base.form.FieldDatetime = openerp.base.form.Field.extend({
},
set_value: function(value) {
this._super.apply(this, arguments);
if (value == null || value == false) {
if (!value) {
this.$element.find('input').val('');
} else {
this.$element.find('input').unbind('change');
@ -1086,7 +1101,7 @@ openerp.base.form.FieldProgressBar = openerp.base.form.Field.extend({
set_value: function(value) {
this._super.apply(this, arguments);
var show_value = Number(value);
if (show_value === NaN) {
if (isNaN(show_value)) {
show_value = 0;
}
this.$element.find('div').progressbar('option', 'value', show_value).find('span').html(show_value + '%');
@ -1101,28 +1116,54 @@ openerp.base.form.FieldSelection = openerp.base.form.Field.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldSelection";
this.field_index = _.map(this.field.selection, function(x, index) {
return {"ikey": "" + index, "ekey": x[0], "label": x[1]};
});
},
start: function() {
// Flag indicating whether we're in an event chain containing a change
// event on the select, in order to know what to do on keyup[RETURN]:
// * If the user presses [RETURN] as part of changing the value of a
// selection, we should just let the value change and not let the
// event broadcast further (e.g. to validating the current state of
// the form in editable list view, which would lead to saving the
// current row or switching to the next one)
// * If the user presses [RETURN] with a select closed (side-effect:
// also if the user opened the select and pressed [RETURN] without
// changing the selected value), takes the action as validating the
// row
var ischanging = false;
this._super.apply(this, arguments);
this.$element.find('select').change(this.on_ui_change);
this.$element.find('select')
.change(this.on_ui_change)
.change(function () { ischanging = true; })
.click(function () { ischanging = false; })
.keyup(function (e) {
if (e.which !== 13 || !ischanging) { return; }
e.stopPropagation();
ischanging = false;
});
},
set_value: function(value) {
this._super.apply(this, arguments);
if (value != null && value !== false) {
this.$element.find('select').val(value);
} else {
this.$element.find('select').val('false');
}
value = value === null ? false : value;
value = value instanceof Array ? value[0] : value;
this._super(value);
var option = _.detect(this.field_index, function(x) {return x.ekey === value;});
this.$element.find('select').val(option === undefined ? '' : option.ikey);
},
set_value_from_ui: function() {
this.value = this.$element.find('select').val();
var ikey = this.$element.find('select').val();
var option = _.detect(this.field_index, function(x) {return x.ikey === ikey;});
this.value = option === undefined ? false : option.ekey;
},
update_dom: function() {
this._super.apply(this, arguments);
this.$element.find('select').attr('disabled', this.readonly);
},
validate: function() {
this.invalid = this.required && this.$element.find('select').val() === "";
var ikey = this.$element.find('select').val();
var option = _.detect(this.field_index, function(x) {return x.ikey === ikey;});
this.invalid = this.required && (option === undefined || option.ekey === false);
},
focus: function() {
this.$element.find('select').focus();
@ -1493,7 +1534,9 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
}
self.is_started.resolve();
});
this.viewmanager.start();
setTimeout(function () {
self.viewmanager.start();
}, 0);
},
reload_current_view: function() {
var self = this;
@ -1646,7 +1689,9 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({
this.list_view.on_loaded.add_last(function() {
self.is_started.resolve();
});
this.list_view.start();
setTimeout(function () {
self.list_view.start();
}, 0);
},
set_value: function(value) {
value = value || [];
@ -2021,7 +2066,7 @@ openerp.base.form.widgets = new openerp.base.Registry({
'reference' : 'openerp.base.form.FieldReference',
'boolean' : 'openerp.base.form.FieldBoolean',
'float' : 'openerp.base.form.FieldFloat',
'integer': 'openerp.base.form.FieldFloat',
'integer': 'openerp.base.form.FieldInteger',
'progressbar': 'openerp.base.form.FieldProgressBar',
'float_time': 'openerp.base.form.FieldFloatTime',
'image': 'openerp.base.form.FieldBinaryImage',

View File

@ -116,6 +116,23 @@ openerp.base.list.editable = function (openerp) {
delete this.edition_index;
delete this.edition;
},
/**
* Adapts this list's view description to be suitable to the inner form view of a row being edited.
*
* @returns {Object} fields_view_get's view section suitable for putting into form view of editable rows.
*/
get_form_fields_view: function () {
// deep copy of view
var view = $.extend(true, {}, this.group.view.fields_view);
_(view.arch.children).each(function (widget) {
widget.attrs.nolabel = true;
if (widget.tag === 'button') {
delete widget.attrs.string;
}
});
view.arch.attrs.col = 2 * view.arch.children.length;
return view;
},
render_row_as_form: function (row) {
this.cancel_pending_edition();
@ -161,7 +178,7 @@ openerp.base.list.editable = function (openerp) {
template: 'ListView.row.form',
registry: openerp.base.list.form.widgets
});
$.when(this.edition_form.on_loaded({fields_view: this.get_fields_view()})).then(function () {
$.when(this.edition_form.on_loaded({fields_view: this.get_form_fields_view()})).then(function () {
// put in $.when just in case FormView.on_loaded becomes asynchronous
$new_row.find('td')
.addClass('oe-field-cell')
@ -251,10 +268,12 @@ openerp.base.list.editable = function (openerp) {
openerp.base.list.form[key] = (form_widgets.get_object(key)).extend({
update_dom: function () {
this.$element.children().css('visibility', '');
if (this.invisible && this.node.attrs.invisible !== '1') {
if (this.invisible) {
this.$element.children().css('visibility', 'hidden');
} else {
this.invisible = !!this.modifiers.tree_invisible;
this._super();
this.invisible = false;
}
}
});

View File

@ -59,6 +59,24 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
if (this.dataset instanceof openerp.base.DataSetStatic) {
this.groups.datagroup = new openerp.base.StaticDataGroup(this.dataset);
}
this.page = 0;
},
/**
* Retrieves the view's number of records per page (|| section)
*
* options > defaults > view_manager.action.limit > indefinite
*
* @returns {Number|null}
*/
limit: function () {
if (this._limit === undefined) {
this._limit = (this.options.limit
|| this.defaults.limit
|| (this.view_manager.action || {}).limit
|| null);
}
return this._limit;
},
/**
* Set a custom Group construct as the root of the List View.
@ -134,10 +152,10 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
this.$element.html(QWeb.render("ListView", this));
// Head hook
this.$element.find('#oe-list-add')
this.$element.find('.oe-list-add')
.click(this.do_add_record)
.attr('disabled', grouped && this.options.editable);
this.$element.find('#oe-list-delete')
this.$element.find('.oe-list-delete')
.attr('disabled', true)
.click(this.do_delete_selected);
this.$element.find('thead').delegate('th[data-id]', 'click', function (e) {
@ -149,8 +167,75 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
self.reload_view();
});
this.$element.find('.oe-list-pager')
.delegate('button', 'click', function () {
var $this = $(this);
switch ($this.data('pager-action')) {
case 'first':
self.page = 0; break;
case 'last':
self.page = Math.floor(
self.dataset.ids.length / self.limit());
break;
case 'next':
self.page += 1; break;
case 'previous':
self.page -= 1; break;
}
self.reload_content();
}).find('.oe-pager-state')
.click(function (e) {
e.stopPropagation();
var $this = $(this);
var $select = $('<select>')
.appendTo($this.empty())
.click(function (e) {e.stopPropagation();})
.append('<option value="80">80</option>' +
'<option value="100">100</option>' +
'<option value="200">200</option>' +
'<option value="500">500</option>' +
'<option value="NaN">Unlimited</option>')
.change(function () {
var val = parseInt($select.val(), 10);
self._limit = (isNaN(val) ? null : val);
self.page = 0;
self.reload_content();
})
.val(self._limit || 'NaN');
});
this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar);
},
/**
* Configures the ListView pager based on the provided dataset's information
*
* Horrifying side-effect: sets the dataset's data on this.dataset?
*
* @param {openerp.base.DataSet} dataset
*/
configure_pager: function (dataset) {
this.dataset.ids = dataset.ids;
var limit = this.limit(),
total = dataset.ids.length,
first = (this.page * limit),
last;
if (!limit || (total - first) < limit) {
last = total;
} else {
last = first + limit;
}
this.$element.find('span.oe-pager-state').empty().text(_.sprintf(
"[%d to %d] of %d", first + 1, last, total));
this.$element
.find('button[data-pager-action=first], button[data-pager-action=previous]')
.attr('disabled', this.page === 0)
.end()
.find('button[data-pager-action=last], button[data-pager-action=next]')
.attr('disabled', last === total);
},
/**
* Sets up the listview's columns: merges view and fields data, move
* grouped-by columns to the front of the columns list and make them all
@ -167,22 +252,22 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
var name = field.attrs.name;
var column = _.extend({id: name, tag: field.tag},
field.attrs, fields[name]);
// attrs computer
if (column.attrs) {
var attrs = JSON.parse(column.attrs);
column.attrs_for = function (fields) {
// modifiers computer
if (column.modifiers) {
var modifiers = JSON.parse(column.modifiers);
column.modifiers_for = function (fields) {
var result = {};
for (var attr in attrs) {
result[attr] = domain_computer(attrs[attr], fields);
for (var modifier in modifiers) {
result[modifier] = domain_computer(modifiers[modifier], fields);
}
return result;
};
} else {
column.attrs_for = noop;
column.modifiers_for = noop;
}
return column;
};
this.columns.splice(0, this.columns.length);
this.columns.push.apply(
this.columns,
@ -190,10 +275,10 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
if (grouped) {
this.columns.unshift({
id: '_group', tag: '', string: "Group", meta: true,
attrs_for: function () { return {}; }
modifiers_for: function () { return {}; }
}, {
id: '_count', tag: '', string: '#', meta: true,
attrs_for: function () { return {}; }
modifiers_for: function () { return {}; }
});
}
@ -264,10 +349,8 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
*/
reload_view: function (grouped) {
var self = this;
this.dataset.offset = 0;
this.dataset.limit = false;
var callback = function (field_view_get) {
self.on_loaded(field_view_get, grouped);
self.on_loaded(field_view_get, grouped);
};
if (this.embedded_view) {
return $.Deferred().then(callback).resolve({fields_view: this.embedded_view});
@ -326,9 +409,9 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
$.proxy(this, 'reload_content'));
},
/**
* Handles the signal to delete a line from the DOM
* Handles the signal to delete lines from the records list
*
* @param {Array} ids the id of the object to delete
* @param {Array} ids the ids of the records to delete
*/
do_delete: function (ids) {
if (!ids.length) {
@ -336,7 +419,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
}
var self = this;
return $.when(this.dataset.unlink(ids)).then(function () {
self.reload_content();
self.groups.drop_records(ids);
});
},
/**
@ -346,7 +429,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
* @param {Array} records selected record values
*/
do_select: function (ids, records) {
this.$element.find('#oe-list-delete')
this.$element.find('.oe-list-delete')
.attr('disabled', !ids.length);
if (!records.length) {
@ -505,6 +588,7 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
init: function (group, opts) {
var self = this;
this.group = group;
this.view = group.view;
this.options = opts.options;
this.columns = opts.columns;
@ -553,19 +637,9 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
this.$current.remove();
}
this.$current = this.$_element.clone(true);
this.$current.empty().append(QWeb.render('ListView.rows', this));
},
get_fields_view: function () {
// deep copy of view
var view = $.extend(true, {}, this.group.view.fields_view);
_(view.arch.children).each(function (widget) {
widget.attrs.nolabel = true;
if (widget.tag === 'button') {
delete widget.attrs.string;
}
});
view.arch.attrs.col = 2 * view.arch.children.length;
return view;
this.$current.empty().append(
QWeb.render('ListView.rows', _.extend({
render_cell: this.render_cell}, this)));
},
/**
* Gets the ids of all currently selected records, if any
@ -695,8 +769,67 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
options: this.options,
row: this.rows[record_index],
row_parity: (record_index % 2 === 0) ? 'even' : 'odd',
row_index: record_index
row_index: record_index,
render_cell: this.render_cell
});
},
/**
* Formats the rendring of a given value based on its field type
*
* @param {Object} row_data record whose values should be displayed in the cell
* @param {Object} column column descriptor
* @param {"button"|"field"} column.tag base control type
* @param {String} column.type widget type for a field control
* @param {String} [column.string] button label
* @param {String} [column.icon] button icon
*/
render_cell: function (row_data, column) {
var attrs = column.modifiers_for(row_data);
if (attrs.invisible) { return ''; }
if (column.tag === 'button') {
return [
'<button type="button" title="', column.string || '', '">',
'<img src="/base/static/src/img/icons/', column.icon, '.png"',
' alt="', column.string || '', '"/>',
'</button>'
].join('')
}
var record = row_data[column.id];
if (record.value === false) { return ''; }
switch (column.widget || column.type) {
case 'many2one':
// name_get value format
return record.value[1];
case 'float_time':
return _.sprintf("%02d:%02d",
Math.floor(record.value),
Math.round((record.value % 1) * 60));
case 'progressbar':
return _.sprintf(
'<progress value="%.2f" max="100.0">%.2f%%</progress>',
record.value, record.value);
default:
return record.value;
}
},
/**
* Stops displaying the records matching the provided ids.
*
* @param {Array} ids identifiers of the records to remove
*/
drop_records: function (ids) {
var self = this;
_(this.rows).chain()
.map(function (record, index) {
return {index: index, id: record.data.id.value};
}).filter(function (record) {
return _(ids).contains(record.id);
}).reverse()
.each(function (record) {
self.$current.find('tr:eq(' + record.index + ')').remove();
self.rows.splice(record.index, 1);
})
}
// drag and drop
});
@ -715,13 +848,10 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
this.columns = view.columns;
this.datagroup = null;
this.sections = [];
this.$row = null;
this.children = {};
},
pad: function ($row) {
if (this.options.selectable) {
$row.append('<td>');
}
this.page = 0;
},
make_fragment: function () {
return document.createDocumentFragment();
@ -753,10 +883,35 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
}
return red_letter_tboday;
},
make_paginator: function () {
var self = this;
var $prev = $('<button type="button" data-pager-action="previous">&lt;</button>')
.click(function (e) {
e.stopPropagation();
self.page -= 1;
self.$row.closest('tbody').next()
.replaceWith(self.render());
});
var $next = $('<button type="button" data-pager-action="next">&gt;</button>')
.click(function (e) {
e.stopPropagation();
self.page += 1;
self.$row.closest('tbody').next()
.replaceWith(self.render());
});
this.$row.children().last()
.append($prev)
.append('<span class="oe-pager-state"></span>')
.append($next);
},
open: function (point_insertion) {
this.render().insertAfter(point_insertion);
this.make_paginator();
},
close: function () {
this.$row.children().last().empty();
this.apoptosis();
},
/**
@ -788,7 +943,7 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
self.bind_child_events(child);
child.datagroup = group;
var $row = $('<tr>');
var $row = child.$row = $('<tr>');
if (group.openable) {
$row.click(function (e) {
if (!$row.data('open')) {
@ -808,7 +963,7 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
}
placeholder.appendChild($row[0]);
var $group_column = $('<th>').appendTo($row);
var $group_column = $('<th class="oe-group-name">').appendTo($row);
if (group.grouped_on) {
// Don't fill this if group_by_no_leaf but no group_by
$group_column
@ -822,8 +977,10 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
self.indent($group_column, group.level);
// count column
$('<td>').text(group.length).appendTo($row);
self.pad($row);
if (self.options.selectable) {
$row.append('<td>');
}
_(self.columns).chain()
.filter(function (column) {return !column.invisible;})
.each(function (column) {
@ -844,6 +1001,9 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
$row.append('<td>');
}
});
if (self.options.deletable) {
$row.append('<td class="oe-group-pagination">');
}
});
return placeholder;
},
@ -867,6 +1027,7 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
},
render_dataset: function (dataset) {
var rows = [],
self = this,
list = new openerp.base.ListView.List(this, {
options: this.options,
columns: this.columns,
@ -875,11 +1036,30 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
});
this.bind_child_events(list);
var d = new $.Deferred();
var view = this.view,
limit = view.limit(),
d = new $.Deferred(),
page = this.datagroup.openable ? this.page : view.page;
dataset.read_slice(
_.filter(_.pluck(_.select(this.columns, function(x) {return x.tag == "field";}), 'name'), _.identity),
0, false,
page * limit, limit,
function (records) {
if (!self.datagroup.openable) {
view.configure_pager(dataset);
} else {
var pages = Math.ceil(dataset.ids.length / limit);
self.$row
.find('.oe-pager-state')
.text(_.sprintf('%d/%d', page + 1, pages))
.end()
.find('button[data-pager-action=previous]')
.attr('disabled', page === 0)
.end()
.find('button[data-pager-action=next]')
.attr('disabled', page === pages - 1);
}
var form_records = _(records).map(
$.proxy(list, 'transform_record'));
@ -943,6 +1123,19 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
.map(function (child) {
return child.get_records();
}).flatten().value();
},
/**
* Stops displaying the records with the linked ids, assumes these records
* were deleted from the DB.
*
* This is the up-signal from the `deleted` event on groups and lists.
*
* @param {Array} ids list of identifier of the records to remove.
*/
drop_records: function (ids) {
_.each(this.children, function (child) {
child.drop_records(ids);
});
}
});
};

View File

@ -736,6 +736,9 @@ openerp.base.search.ExtendedSearch = openerp.base.BaseWidget.extend({
},
start: function () {
this._super();
if (!this.$element) {
return; // not a logical state but sometimes it happens
}
this.$element.closest("table.oe-searchview-render-line").css("display", "none");
var self = this;
this.rpc("/base/searchview/fields_get",
@ -751,6 +754,9 @@ openerp.base.search.ExtendedSearch = openerp.base.BaseWidget.extend({
return null;
},
get_domain: function() {
if (!this.$element) {
return null; // not a logical state but sometimes it happens
}
if(this.$element.closest("table.oe-searchview-render-line").css("display") == "none") {
return null;
}

View File

@ -449,7 +449,8 @@ openerp.base.View = openerp.base.Controller.extend({
search_view : false,
views_switcher : false,
action_buttons : false,
pager : false
pager : false,
new_window: true
};
action_manager.do_action(action, on_closed);
if (self.view_manager.is_dialog && action.type != 'ir.actions.act_window_close') {

View File

@ -428,11 +428,11 @@
<th t-if="actions_span" t-att-colspan="actions_span"
class="oe-actions">
<t t-if="flags.action_buttons !== false">
<button type="button" id="oe-list-add"
<button type="button" class="oe-list-add"
t-if="options.addable">
<t t-esc="options.addable"/>
</button>
<button type="button" id="oe-list-delete"
<button type="button" class="oe-list-delete"
t-if="options.selectable and options.deletable">
Delete
</button>
@ -441,16 +441,19 @@
<th t-att-colspan="columns_count - actions_span"
class="oe-list-pager">
<t t-if="flags.pager !== false">
<button type="button" data-pager-action="first">First</button>
<button type="button" data-pager-action="previous"
<button type="button" disabled="disabled"
data-pager-action="first">First</button>
<button type="button" disabled="disabled"
data-pager-action="previous"
>&lt;&lt;</button>
<span class="oe-pager-first">1</span>
to <span class="oe-pager-last">1</span>
of <span class="oe-pager-total">1</span>
<span class="oe-pager-state">
</span>
<button type="button" data-pager-action="next">&gt;&gt;</button>
<button type="button" data-pager-action="last">Last</button>
<button type="button" disabled="disabled"
data-pager-action="next">&gt;&gt;</button>
<button type="button" disabled="disabled"
data-pager-action="last">Last</button>
</t>
</th>
</tr>
@ -506,20 +509,7 @@
<td t-if="!column.meta and column.invisible !== '1'" t-att-title="column.help"
t-att-class="'oe-field-cell' + (align ? ' oe-number' : '')"
t-att-data-field="column.id">
<t t-set="attrs" t-value="column.attrs_for(row.data)"/>
<t t-if="!attrs.invisible">
<t t-set="is_button" t-value="column.tag === 'button'"/>
<!-- TODO: get correct widget from form -->
<t t-if="!is_button and row['data'][column.id].value !== false">
<t t-set="value" t-value="row['data'][column.id].value"/>
<t t-esc="value instanceof Array ? value[1] : value"/>
</t>
<button type="button" t-att-title="column.string"
t-if="is_button">
<img t-att-src="'/base/static/src/img/icons/' + column.icon + '.png'"
t-att-alt="column.string"/>
</button>
</t>
<t t-raw="render_cell(row.data, column)"/>
</td>
</t>
<td t-if="options.deletable" class='oe-record-delete'>
@ -699,9 +689,9 @@
t-att-id="widget.element_id + '_field'"
t-att-class="'field_' + widget.type"
style="width: 100%">
<t t-foreach="widget.field.selection" t-as="options">
<option t-att-value="options[0]">
<t t-esc="options[1]"/>
<t t-foreach="widget.field_index" t-as="options">
<option t-att-value="options.ikey">
<t t-esc="options.label"/>
</option>
</t>
</select>

View File

@ -149,7 +149,7 @@ $(document).ready(function () {
listview.$element.find('tbody th input:eq(1)')
.attr('checked', true);
listview.$element.find('#oe-list-delete').click();
listview.$element.find('.oe-list-delete').click();
deepEqual(deleted, [2, 3]);
});
});

View File

@ -262,21 +262,13 @@ init: function(view_manager, session, element_id, dataset, view_id) {
throw "Unrecognized date/time format";
},
reload_gantt: function(domain) {
reload_gantt: function() {
var self = this;
var ajax = {
url: '/base/dataset/search_read',
async: false
};
this.rpc(ajax, {
model: this.dataset.model,
domain: self.dataset.domain,
context :self.dataset.get_context()
}, function(response) {
ganttChartControl.clearAll();
jQuery("#GanttDiv").children().remove();
self.load_event(response);
});
this.dataset.read_slice(false, false, false, function(response) {
ganttChartControl.clearAll();
jQuery("#GanttDiv").children().remove();
self.load_event(response);
});
},
do_search: function (domains, contexts, groupbys) {
@ -290,7 +282,7 @@ init: function(view_manager, session, element_id, dataset, view_id) {
}, function (results) {
self.dataset.context = results.context;
self.dataset.domain = results.domain;
return self.reload_gantt(self.dataset.domain);
self.reload_gantt();
});
}

View File

@ -4,4 +4,5 @@
"depends": [],
"js": ["static/*/*.js", "static/*/js/*.js"],
"css": [],
'active': False,
}

View File

@ -90,11 +90,8 @@ openerp.web_mobile.ListView = openerp.base.Controller.extend({
on_action: function(action) {
var self = this;
var view_id = action.views[0][0];
var model = action.res_model;
self.rpc('/base/dataset/search_read', {
model: model
},function(result){
(new openerp.base.DataSetSearch(this.session, action.res_model, null, null))
.read_slice(false, false, false, function(result){
this.listview = new openerp.web_mobile.ListView(this.session, "oe_app");
self.$element.html(QWeb.render("ListView", {'records' : result}));
});

View File

@ -74,8 +74,7 @@ class OpenERPSession(object):
Used to store references to non-literal domains which need to be
round-tripped to the client browser.
"""
def __init__(self, server='127.0.0.1', port=8069,
model_factory=OpenERPModel):
def __init__(self, server='127.0.0.1', port=8069, model_factory=OpenERPModel):
self._server = server
self._port = port
self._db = False
@ -234,11 +233,7 @@ class OpenERPSession(object):
# OpenERP Web RequestHandler
#----------------------------------------------------------
class JsonRequest(object):
""" JSON-RPC2 over HTTP POST using non standard POST encoding.
Difference with the standard:
* the json string is passed as a form parameter named "request"
* method is currently ignored
""" JSON-RPC2 over HTTP.
Sucessful request::
@ -278,7 +273,9 @@ class JsonRequest(object):
self.httpsession_id = "cookieid"
self.httpsession = cherrypy.session
self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex
self.session = self.httpsession.setdefault(self.session_id, OpenERPSession())
host = cherrypy.config['openerp.server.host']
port = cherrypy.config['openerp.server.port']
self.session = self.httpsession.setdefault(self.session_id, OpenERPSession(host, port))
self.context = self.params.pop('context', None)
return self.params
@ -380,7 +377,9 @@ class HttpRequest(object):
self.httpsession_id = "cookieid"
self.httpsession = cherrypy.session
self.context = kw.get('context', {})
self.session = self.httpsession.setdefault(kw.get('session_id', None), OpenERPSession())
host = cherrypy.config['openerp.server.host']
port = cherrypy.config['openerp.server.port']
self.session = self.httpsession.setdefault(kw.get('session_id', None), OpenERPSession(host, port))
self.result = ""
if request.method == 'GET':
print "GET --> %s.%s %s %r" % (controller.__class__.__name__, f.__name__, request, kw)
@ -465,8 +464,10 @@ class Root(object):
def main(argv):
# change the timezone of the program to the OpenERP server's assumed timezone
os.environ["TZ"] = "UTC"
DEFAULT_CONFIG = {
'openerp.server.host': '127.0.0.1',
'openerp.server.port': 8069,
'server.socket_port': 8002,
'server.socket_host': '0.0.0.0',
'tools.sessions.on': True,
@ -474,13 +475,13 @@ def main(argv):
'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")
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")
op.add_option("", "--server-host", dest="openerp.server.host", help="OpenERP server hostname", metavar="HOST")
op.add_option("", "--server-port", dest="openerp.server.port", help="OpenERP server port", type="int", metavar="NUMBER")
(o, args) = op.parse_args(argv[1:])
o = vars(o)
for k in o.keys():
@ -489,19 +490,17 @@ def main(argv):
# Setup and run cherrypy
cherrypy.tree.mount(Root())
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.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)
os.makedirs(cherrypy.config['tools.sessions.storage_path'], 0700)
cherrypy.server.subscribe()
cherrypy.engine.start()
cherrypy.engine.block()