[MERGE] xmo listview, bug in the form pager
bzr revid: al@openerp.com-20110413013711-vp63eigz5aadm6wc
This commit is contained in:
commit
3ac70cbecf
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import glob, os
|
||||
import pprint
|
||||
from xml.etree import ElementTree
|
||||
from cStringIO import StringIO
|
||||
|
||||
|
@ -334,18 +335,17 @@ class DataSet(openerpweb.Controller):
|
|||
:param int offset: from which index should the results start being returned
|
||||
:param int limit: the maximum number of records to return
|
||||
:param list domain: the search domain for the query
|
||||
:param dict context: the context in which the search should be executed
|
||||
:param list sort: sorting directives
|
||||
:returns: a list of result records
|
||||
:rtype: list
|
||||
"""
|
||||
Model = request.session.model(model)
|
||||
ids = Model.search(domain or [], offset or 0, limit or False,
|
||||
sort or False, context or False)
|
||||
sort or False, request.context)
|
||||
if fields and fields == ['id']:
|
||||
# shortcut read if we only want the ids
|
||||
return map(lambda id: {'id': id}, ids)
|
||||
return Model.read(ids, fields or False)
|
||||
return Model.read(ids, fields or False, request.context)
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def get(self, request, model, ids, fields=False):
|
||||
|
@ -363,6 +363,9 @@ class DataSet(openerpweb.Controller):
|
|||
:type model: str
|
||||
:param ids: a list of identifiers
|
||||
:type ids: list
|
||||
:param fields: a list of fields to fetch, ``False`` or empty to fetch
|
||||
all fields in the model
|
||||
:type fields: list | False
|
||||
:returns: a list of records, in the same order as the list of ids
|
||||
:rtype: list
|
||||
"""
|
||||
|
@ -407,16 +410,20 @@ class DataSet(openerpweb.Controller):
|
|||
return {'result': r}
|
||||
|
||||
class View(openerpweb.Controller):
|
||||
def fields_view_get(self, session, model, view_id, view_type, transform=True, toolbar=False, submenu=False):
|
||||
Model = session.model(model)
|
||||
r = Model.fields_view_get(view_id, view_type, {}, toolbar, submenu)
|
||||
def fields_view_get(self, request, model, view_id, view_type,
|
||||
transform=True, toolbar=False, submenu=False):
|
||||
Model = request.session.model(model)
|
||||
fvg = Model.fields_view_get(view_id, view_type, request.context,
|
||||
toolbar, submenu)
|
||||
if transform:
|
||||
context = {} # TODO: dict(ctx_sesssion, **ctx_action)
|
||||
xml = self.transform_view(r['arch'], session, context)
|
||||
evaluation_context = request.session.evaluation_context(
|
||||
request.context or {})
|
||||
xml = self.transform_view(
|
||||
fvg['arch'], request.session, evaluation_context)
|
||||
else:
|
||||
xml = ElementTree.fromstring(r['arch'])
|
||||
r['arch'] = Xml2Json.convert_element(xml)
|
||||
return r
|
||||
xml = ElementTree.fromstring(fvg['arch'])
|
||||
fvg['arch'] = Xml2Json.convert_element(xml)
|
||||
return fvg
|
||||
|
||||
def normalize_attrs(self, elem, context):
|
||||
""" Normalize @attrs, @invisible, @required, @readonly and @states, so
|
||||
|
@ -430,19 +437,16 @@ class View(openerpweb.Controller):
|
|||
:param dict context: evaluation context
|
||||
"""
|
||||
# If @attrs is normalized in json by server, the eval should be replaced by simplejson.loads
|
||||
attrs = eval(elem.attrib.get('attrs', '{}'))
|
||||
attrs = openerpweb.ast.literal_eval(elem.get('attrs', '{}'))
|
||||
if 'states' in elem.attrib:
|
||||
if 'invisible' not in attrs:
|
||||
attrs['invisible'] = []
|
||||
# This should be done by the server
|
||||
attrs['invisible'].append(('state', 'not in', elem.attrib['states'].split(',')))
|
||||
del(elem.attrib['states'])
|
||||
attrs.setdefault('invisible', [])\
|
||||
.append(('state', 'not in', elem.attrib.pop('states').split(',')))
|
||||
if attrs:
|
||||
elem.attrib['attrs'] = simplejson.dumps(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.attrib.get(a, 'False'),
|
||||
avalue = bool(eval(elem.get(a, 'False'),
|
||||
{'context': context or {}}))
|
||||
if not avalue:
|
||||
del elem.attrib[a]
|
||||
|
@ -514,7 +518,7 @@ class FormView(View):
|
|||
|
||||
@openerpweb.jsonrequest
|
||||
def load(self, req, model, view_id, toolbar=False):
|
||||
fields_view = self.fields_view_get(req.session, model, view_id, 'form', toolbar=toolbar)
|
||||
fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
|
||||
return {'fields_view': fields_view}
|
||||
|
||||
class ListView(View):
|
||||
|
@ -522,15 +526,86 @@ class ListView(View):
|
|||
|
||||
@openerpweb.jsonrequest
|
||||
def load(self, req, model, view_id, toolbar=False):
|
||||
fields_view = self.fields_view_get(req.session, model, view_id, 'tree', toolbar=toolbar)
|
||||
fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
|
||||
return {'fields_view': fields_view}
|
||||
|
||||
def fields_view_get(self, request, model, view_id, view_type="tree",
|
||||
transform=True, toolbar=False, submenu=False):
|
||||
""" Sets @editable on the view's arch if it isn't already set and
|
||||
``set_editable`` is present in the request context
|
||||
"""
|
||||
view = super(ListView, self).fields_view_get(
|
||||
request, model, view_id, view_type, transform, toolbar, submenu)
|
||||
|
||||
view_attributes = view['arch']['attrs']
|
||||
if request.context.get('set_editable')\
|
||||
and 'editable' not in view_attributes:
|
||||
view_attributes['editable'] = 'bottom'
|
||||
return view
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def fill(self, request, model, id, domain,
|
||||
offset=0, limit=False):
|
||||
return self.do_fill(request, model, id, domain, offset, limit)
|
||||
|
||||
def do_fill(self, request, model, id, domain,
|
||||
offset=0, limit=False):
|
||||
""" Returns all information needed to fill a table:
|
||||
|
||||
* view with processed ``editable`` flag
|
||||
* fields (columns) with processed ``invisible`` flag
|
||||
* rows with processed ``attrs`` and ``colors``
|
||||
|
||||
.. note:: context is passed through ``request`` parameter
|
||||
|
||||
:param request: OpenERP request
|
||||
:type request: openerpweb.openerpweb.JsonRequest
|
||||
:type str model: OpenERP model for this list view
|
||||
:type int id: view_id, or False if none provided
|
||||
:param list domain: the search domain to search for
|
||||
:param int offset: search offset, for pagination
|
||||
:param int limit: search limit, for pagination
|
||||
:returns: hell if I have any idea yet
|
||||
"""
|
||||
view = self.fields_view_get(request, model, id)
|
||||
|
||||
rows = DataSet().do_search_read(request, model,
|
||||
offset=offset, limit=limit,
|
||||
domain=domain)
|
||||
eval_context = request.session.evaluation_context(
|
||||
request.context)
|
||||
return [
|
||||
{'data': dict((key, {'value': value})
|
||||
for key, value in row.iteritems()),
|
||||
'color': self.process_colors(view, row, eval_context)}
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def process_colors(self, view, row, context):
|
||||
colors = view['arch']['attrs'].get('colors')
|
||||
|
||||
if not colors:
|
||||
return None
|
||||
|
||||
color = [
|
||||
pair.split(':')[0]
|
||||
for pair in colors.split(';')
|
||||
if eval(pair.split(':')[1], dict(context, **row))
|
||||
]
|
||||
|
||||
if not color:
|
||||
return None
|
||||
elif len(color) == 1:
|
||||
return color[0]
|
||||
return 'maroon'
|
||||
|
||||
|
||||
class SearchView(View):
|
||||
_cp_path = "/base/searchview"
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def load(self, req, model, view_id):
|
||||
fields_view = self.fields_view_get(req.session, model, view_id, 'search')
|
||||
fields_view = self.fields_view_get(req, model, view_id, 'search')
|
||||
return {'fields_view': fields_view}
|
||||
|
||||
class Action(openerpweb.Controller):
|
||||
|
|
|
@ -443,6 +443,7 @@ body.openerp {
|
|||
padding-left: 10px;
|
||||
}
|
||||
|
||||
|
||||
.openerp .oe-searchview-render-line {
|
||||
width:100%;
|
||||
}
|
||||
|
@ -462,6 +463,75 @@ body.openerp {
|
|||
background: url(../img/icons/gtk-remove.png) repeat-y;
|
||||
padding-left: 18px;
|
||||
}
|
||||
/* List */
|
||||
.openerp .oe-listview table {
|
||||
clear: right;
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
border: 1px solid silver;
|
||||
}
|
||||
|
||||
.openerp .oe-listview tr.odd {
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
.openerp .oe-listview tbody tr:hover {
|
||||
background-color: #ecebf2;
|
||||
}
|
||||
.openerp .oe-listview tbody tr:hover {
|
||||
background-color: #eae9f0;
|
||||
}
|
||||
|
||||
.openerp .oe-listview td,
|
||||
.openerp .oe-listview th {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.openerp .oe-listview .oe-field-cell button {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
.openerp .oe-listview .oe-field-cell button:active {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.openerp .oe-listview th.oe-actions {
|
||||
text-align: left;
|
||||
}
|
||||
.openerp .oe-listview th.oe-list-pager {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/** list rounded corners
|
||||
|
||||
rounded corners are a pain on tables: need to round not only table, but
|
||||
also on the first and last children of the first and last row
|
||||
*/
|
||||
.openerp .oe-listview table {
|
||||
-webkit-border-radius: 7px;
|
||||
-moz-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
.openerp .oe-listview table thead tr:first-child th:first-child {
|
||||
-webkit-border-top-left-radius: 7px;
|
||||
-moz-border-radius-topleft: 7px;
|
||||
border-top-left-radius: 7px;
|
||||
}
|
||||
.openerp .oe-listview table thead tr:first-child th:last-child {
|
||||
-webkit-border-top-right-radius: 7px;
|
||||
-moz-border-radius-topright: 7px;
|
||||
border-top-right-radius: 7px;
|
||||
}
|
||||
.openerp .oe-listview table tbody tr:last-child th:first-child {
|
||||
-webkit-border-bottom-left-radius: 7px;
|
||||
-moz-border-radius-bottomleft: 7px;
|
||||
border-bottom-left-radius: 7px;
|
||||
}
|
||||
.openerp .oe-listview table tbody tr:last-child td:last-child {
|
||||
-webkit-border-bottom-right-radius: 7px;
|
||||
-moz-border-radius-bottomright: 7px;
|
||||
border-bottom-right-radius: 7px;
|
||||
}
|
||||
|
||||
/* Notebook */
|
||||
.openerp .oe_form_notebook {
|
||||
|
@ -489,7 +559,6 @@ body.openerp {
|
|||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
|
||||
/* Form */
|
||||
.openerp table.oe_frame td {
|
||||
color: #4c4c4c;
|
||||
|
|
|
@ -108,6 +108,8 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.
|
|||
this.notification.notify("Unlink", ids);
|
||||
},
|
||||
call: function (method, ids, args, callback) {
|
||||
this.notification.notify(
|
||||
"Calling", this.model + '#' + method + '(' + ids + ')');
|
||||
ids = ids || [];
|
||||
args = args || [];
|
||||
return this.rpc('/base/dataset/call', {
|
||||
|
|
|
@ -311,6 +311,64 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base
|
|||
/** @namespace */
|
||||
openerp.base.form = {};
|
||||
|
||||
openerp.base.form.compute_domain = function(expr, fields) {
|
||||
var stack = [];
|
||||
for (var i = expr.length - 1; i >= 0; i--) {
|
||||
var ex = expr[i];
|
||||
if (ex.length == 1) {
|
||||
var top = stack.pop();
|
||||
switch (ex[0]) {
|
||||
case '|':
|
||||
stack.push(stack.pop() || top);
|
||||
continue;
|
||||
case '&':
|
||||
stack.push(stack.pop() && top);
|
||||
continue;
|
||||
case '!':
|
||||
stack.push(!top);
|
||||
continue;
|
||||
default:
|
||||
throw new Error('Unknown domain operator ' + ex[0]);
|
||||
}
|
||||
}
|
||||
|
||||
var field = fields[ex[0]].value;
|
||||
var op = ex[1];
|
||||
var val = ex[2];
|
||||
|
||||
switch (op.toLowerCase()) {
|
||||
case '=':
|
||||
case '==':
|
||||
stack.push(field == val);
|
||||
break;
|
||||
case '!=':
|
||||
case '<>':
|
||||
stack.push(field != val);
|
||||
break;
|
||||
case '<':
|
||||
stack.push(field < val);
|
||||
break;
|
||||
case '>':
|
||||
stack.push(field > val);
|
||||
break;
|
||||
case '<=':
|
||||
stack.push(field <= val);
|
||||
break;
|
||||
case '>=':
|
||||
stack.push(field >= val);
|
||||
break;
|
||||
case 'in':
|
||||
stack.push(_.indexOf(val, field) > -1);
|
||||
break;
|
||||
case 'not in':
|
||||
stack.push(_.indexOf(val, field) == -1);
|
||||
break;
|
||||
default:
|
||||
this.log("Unsupported operator in attrs :", op);
|
||||
}
|
||||
}
|
||||
return _.indexOf(stack, false) == -1;
|
||||
},
|
||||
openerp.base.form.Widget = openerp.base.Controller.extend({
|
||||
init: function(view, node) {
|
||||
this.view = view;
|
||||
|
@ -335,69 +393,11 @@ openerp.base.form.Widget = openerp.base.Controller.extend({
|
|||
this.$element = $('#' + this.element_id);
|
||||
},
|
||||
process_attrs: function() {
|
||||
var compute_domain = openerp.base.form.compute_domain;
|
||||
for (var a in this.attrs) {
|
||||
this[a] = this.eval_attrs(this.attrs[a]);
|
||||
this[a] = compute_domain(this.attrs[a], this.view.fields);
|
||||
}
|
||||
},
|
||||
eval_attrs: function(expr) {
|
||||
var stack = [];
|
||||
for (var i = 0; i < expr.length; i++) {
|
||||
var ex = expr[i];
|
||||
if (ex.length == 1) {
|
||||
stack.push(ex[0]);
|
||||
continue;
|
||||
}
|
||||
|
||||
var field = this.view.fields[ex[0]].value;
|
||||
var op = ex[1];
|
||||
var val = ex[2];
|
||||
|
||||
switch (op.toLowerCase()) {
|
||||
case '=':
|
||||
case '==':
|
||||
stack.push(field == val);
|
||||
break;
|
||||
case '!=':
|
||||
case '<>':
|
||||
stack.push(field != val);
|
||||
break;
|
||||
case '<':
|
||||
stack.push(field < val);
|
||||
break;
|
||||
case '>':
|
||||
stack.push(field > val);
|
||||
break;
|
||||
case '<=':
|
||||
stack.push(field <= val);
|
||||
break;
|
||||
case '>=':
|
||||
stack.push(field >= val);
|
||||
break;
|
||||
case 'in':
|
||||
stack.push(_.indexOf(val, field) > -1);
|
||||
break;
|
||||
case 'not in':
|
||||
stack.push(_.indexOf(val, field) == -1);
|
||||
break;
|
||||
default:
|
||||
this.log("Unsupported operator in attrs :", op);
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = stack.length-1; j >- 1; j--) {
|
||||
switch (stack[j]) {
|
||||
case '|':
|
||||
var result = stack[j + 1] || stack[j + 2];
|
||||
stack.splice(j, 3, result);
|
||||
break;
|
||||
case '&':
|
||||
var result = stack[j + 1] && stack[j + 2];
|
||||
stack.splice(j, 3, result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return _.indexOf(stack, false) == -1;
|
||||
},
|
||||
update_dom: function() {
|
||||
this.$element.toggle(!this.invisible);
|
||||
},
|
||||
|
|
|
@ -8,7 +8,9 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
// list rows can be deleted
|
||||
'deletable': true,
|
||||
// whether the column headers should be displayed
|
||||
'header': true
|
||||
'header': true,
|
||||
// display addition button, with that label
|
||||
'addable': "New"
|
||||
},
|
||||
/**
|
||||
* @constructs
|
||||
|
@ -21,6 +23,7 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
* @param {Boolean} [options.selectable=true] determines whether view rows are selectable (e.g. via a checkbox)
|
||||
* @param {Boolean} [options.header=true] should the list's header be displayed
|
||||
* @param {Boolean} [options.deletable=true] are the list rows deletable
|
||||
* @param {null|String} [options.addable="New"] should the new-record button be displayed, and what should its label be. Use ``null`` to hide the button.
|
||||
*/
|
||||
init: function(view_manager, session, element_id, dataset, view_id, options) {
|
||||
this._super(session, element_id);
|
||||
|
@ -35,20 +38,37 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
this.options = _.extend({}, this.defaults, options || {});
|
||||
},
|
||||
start: function() {
|
||||
//this.log('Starting ListView '+this.model+this.view_id)
|
||||
this.$element.addClass('oe-listview');
|
||||
return this.rpc("/base/listview/load", {"model": this.model, "view_id":this.view_id,
|
||||
toolbar:!!this.view_manager.sidebar}, this.on_loaded);
|
||||
},
|
||||
on_loaded: function(data) {
|
||||
var self = this;
|
||||
this.fields_view = data.fields_view;
|
||||
//this.log(this.fields_view);
|
||||
this.name = "" + this.fields_view.arch.attrs.string;
|
||||
|
||||
var fields = this.fields_view.fields;
|
||||
var domain_computer = openerp.base.form.compute_domain;
|
||||
this.columns = _(this.fields_view.arch.children).chain()
|
||||
.map(function (field) {
|
||||
var name = field.attrs.name;
|
||||
return _.extend({id: name, tag: field.tag}, field.attrs, fields[name]);
|
||||
var column = _.extend({id: name, tag: field.tag},
|
||||
field.attrs, fields[name]);
|
||||
// attrs computer
|
||||
if (column.attrs) {
|
||||
var attrs = eval('(' + column.attrs + ')');
|
||||
column.attrs_for = function (fields) {
|
||||
var result = {};
|
||||
for (var attr in attrs) {
|
||||
result[attr] = domain_computer(attrs[attr], fields);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
} else {
|
||||
column.attrs_for = function () { return {}; };
|
||||
}
|
||||
return column;
|
||||
}).value();
|
||||
|
||||
this.visible_columns = _.filter(this.columns, function (column) {
|
||||
|
@ -57,20 +77,46 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
this.$element.html(QWeb.render("ListView", this));
|
||||
|
||||
// Head hook
|
||||
this.$element.find('#oe-list-delete').click(this.do_delete_selected);
|
||||
this.$element.find('#oe-list-add').click(this.do_add_record);
|
||||
this.$element.find('#oe-list-delete')
|
||||
.hide()
|
||||
.click(this.do_delete_selected);
|
||||
|
||||
var $table = this.$element.find('table');
|
||||
// Cell events
|
||||
this.$element.find('table').delegate(
|
||||
'th.oe-record-selector', 'click', function (e) {
|
||||
// A click in the selection cell should not activate the
|
||||
// linking feature
|
||||
e.stopImmediatePropagation();
|
||||
$table.delegate(
|
||||
'th.oe-record-selector', 'click', function (e) {
|
||||
// TODO: ~linear performances, would a simple counter work?
|
||||
if ($table.find('th.oe-record-selector input:checked').length) {
|
||||
$table.find('#oe-list-delete').show();
|
||||
} else {
|
||||
$table.find('#oe-list-delete').hide();
|
||||
}
|
||||
// A click in the selection cell should not activate the
|
||||
// linking feature
|
||||
e.stopImmediatePropagation();
|
||||
});
|
||||
this.$element.find('table').delegate(
|
||||
$table.delegate(
|
||||
'td.oe-field-cell button', 'click', function (e) {
|
||||
var $cell = $(e.currentTarget).closest('td');
|
||||
var col_index = $cell.prevAll('td').length;
|
||||
var field = self.visible_columns[col_index];
|
||||
var action = field.name;
|
||||
|
||||
var $row = $cell.parent('tr');
|
||||
var row = self.rows[$row.prevAll().length];
|
||||
|
||||
var context = _.extend(
|
||||
{}, self.dataset.context, field.context || {});
|
||||
self.dataset.call(action, [row.data.id.value], [context],
|
||||
self.do_reload);
|
||||
e.stopImmediatePropagation();
|
||||
});
|
||||
$table.delegate(
|
||||
'td.oe-record-delete button', 'click', this.do_delete);
|
||||
|
||||
// Global rows handlers
|
||||
this.$element.find('table').delegate(
|
||||
$table.delegate(
|
||||
'tr', 'click', this.on_select_row);
|
||||
|
||||
// sidebar stuff
|
||||
|
@ -85,11 +131,27 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
* @returns {Promise} promise to the end of view rendering (list views are asynchronously filled for improved responsiveness)
|
||||
*/
|
||||
do_fill_table: function(records) {
|
||||
var $table = this.$element.find('table');
|
||||
this.rows = records;
|
||||
|
||||
var $table = this.$element.find('table');
|
||||
// Keep current selected record, if it's still in our new search
|
||||
var current_record_id = this.dataset.ids[this.dataset.index];
|
||||
this.dataset.ids = _(records).chain().map(function (record) {
|
||||
return record.data.id.value;
|
||||
}).value();
|
||||
this.dataset.index = _.indexOf(this.dataset.ids, current_record_id);
|
||||
if (this.dataset.index === -1) {
|
||||
this.dataset.index = 0;
|
||||
}
|
||||
|
||||
// TODO: offset, length, count
|
||||
var results = this.rows.length;
|
||||
$table.find('.oe-pager-last').text(results);
|
||||
$table.find('.oe-pager-total').text(results);
|
||||
|
||||
|
||||
// remove all data lines
|
||||
$table.find('tbody').remove();
|
||||
var $old_body = $table.find('tbody');
|
||||
|
||||
// add new content
|
||||
var columns = this.columns,
|
||||
|
@ -100,10 +162,10 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
var PAGE_SIZE = 50,
|
||||
bodies_count = Math.ceil(this.rows.length / PAGE_SIZE),
|
||||
body = 0,
|
||||
$body = $('<tbody>').appendTo($table);
|
||||
$body = $('<tbody class="ui-widget-content">').appendTo($table);
|
||||
|
||||
var rendered = $.Deferred();
|
||||
var render_body = function () {
|
||||
var rendered = $.Deferred();
|
||||
setTimeout(function () {
|
||||
$body.append(
|
||||
QWeb.render("ListView.rows", {
|
||||
|
@ -118,9 +180,29 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
rendered.resolve();
|
||||
}
|
||||
}, 0);
|
||||
return rendered.promise();
|
||||
};
|
||||
return render_body();
|
||||
render_body();
|
||||
|
||||
return rendered.promise().then(function () {
|
||||
$old_body.remove();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Asks the view manager to switch to a different view, using the provided
|
||||
* record index (within the current dataset).
|
||||
*
|
||||
* If the index is null, ``switch_to_record`` asks for the creation of a
|
||||
* new record.
|
||||
*
|
||||
* @param {Number|null} index the record index (in the current dataset) to switch to
|
||||
* @param {String} [view="form"] the view to switch to
|
||||
*/
|
||||
switch_to_record:function (index, view) {
|
||||
view = view || 'form';
|
||||
this.dataset.index = index;
|
||||
_.delay(_.bind(function () {
|
||||
this.view_manager.on_mode_switch(view);
|
||||
}, this));
|
||||
},
|
||||
on_select_row: function (event) {
|
||||
var $target = $(event.currentTarget);
|
||||
|
@ -130,26 +212,41 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
// count number of preceding siblings to line clicked
|
||||
var row = this.rows[$target.prevAll().length];
|
||||
|
||||
var index = _.indexOf(this.dataset.ids, row.id);
|
||||
var index = _.indexOf(this.dataset.ids, row.data.id.value);
|
||||
if (index == undefined || index === -1) {
|
||||
return;
|
||||
}
|
||||
this.dataset.index = index;
|
||||
_.delay(_.bind(function () {
|
||||
this.view_manager.on_mode_switch('form');
|
||||
}, this));
|
||||
|
||||
},
|
||||
this.switch_to_record(index);
|
||||
},
|
||||
do_show: function () {
|
||||
// TODO: re-trigger search
|
||||
this.$element.show();
|
||||
if (this.hidden) {
|
||||
this.do_reload();
|
||||
this.hidden = false;
|
||||
}
|
||||
},
|
||||
do_hide: function () {
|
||||
this.$element.hide();
|
||||
this.hidden = true;
|
||||
},
|
||||
/**
|
||||
* Reloads the search view based on the current settings (dataset & al)
|
||||
*/
|
||||
do_reload: function () {
|
||||
// TODO: need to do 5 billion tons of pre-processing, bypass
|
||||
// DataSet for now
|
||||
//self.dataset.read_slice(self.dataset.fields, 0, self.limit,
|
||||
// self.do_fill_table);
|
||||
return this.rpc('/base/listview/fill', {
|
||||
'model': this.dataset.model,
|
||||
'id': this.view_id,
|
||||
'context': this.dataset.context,
|
||||
'domain': this.dataset.domain
|
||||
}, this.do_fill_table);
|
||||
},
|
||||
do_search: function (domains, contexts, groupbys) {
|
||||
var self = this;
|
||||
this.rpc('/base/session/eval_domain_and_context', {
|
||||
return this.rpc('/base/session/eval_domain_and_context', {
|
||||
domains: domains,
|
||||
contexts: contexts,
|
||||
group_by_seq: groupbys
|
||||
|
@ -157,12 +254,12 @@ 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;
|
||||
self.dataset.read_slice(self.dataset.fields, 0, self.limit, self.do_fill_table);
|
||||
return self.do_reload();
|
||||
});
|
||||
},
|
||||
do_update: function () {
|
||||
var self = this;
|
||||
self.dataset.read_ids(self.dataset.ids, self.dataset.fields, self.do_fill_table);
|
||||
//self.dataset.read_ids(self.dataset.ids, self.dataset.fields, self.do_fill_table);
|
||||
},
|
||||
/**
|
||||
* Handles the signal to delete a line from the DOM
|
||||
|
@ -173,7 +270,17 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
// don't link to forms
|
||||
e.stopImmediatePropagation();
|
||||
this.dataset.unlink(
|
||||
[this.rows[$(e.currentTarget).closest('tr').prevAll().length].id]);
|
||||
[this.rows[$(e.currentTarget).closest('tr').prevAll().length].data.id.value]);
|
||||
},
|
||||
/**
|
||||
* Handles signal for the addition of a new record (can be a creation,
|
||||
* can be the addition from a remote source, ...)
|
||||
*
|
||||
* The default implementation is to switch to a new record on the form view
|
||||
*/
|
||||
do_add_record: function () {
|
||||
this.notification.notify('Add', "New record");
|
||||
this.switch_to_record(null);
|
||||
},
|
||||
/**
|
||||
* Handles deletion of all selected lines
|
||||
|
@ -195,7 +302,7 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
|||
var rows = this.rows;
|
||||
return this.$element.find('th.oe-record-selector input:checked')
|
||||
.closest('tr').map(function () {
|
||||
return rows[$(this).prevAll().length].id;
|
||||
return rows[$(this).prevAll().length].data.id.value;
|
||||
}).get();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -144,8 +144,10 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
|
|||
this.searchview.stop();
|
||||
}
|
||||
this.searchview = new openerp.base.SearchView(this, this.session, this.element_id + "_search", this.dataset, view_id, search_defaults);
|
||||
this.searchview.on_search.add(function() {
|
||||
self.views[self.active_view].controller.do_search.apply(self, arguments);
|
||||
this.searchview.on_search.add(function(domains, contexts, groupbys) {
|
||||
self.views[self.active_view].controller.do_search.call(
|
||||
self, domains.concat(self.domains()),
|
||||
contexts.concat(self.contexts()), groupbys);
|
||||
});
|
||||
return this.searchview.start();
|
||||
},
|
||||
|
@ -159,6 +161,23 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
|
|||
on_remove: function() {
|
||||
},
|
||||
on_edit: function() {
|
||||
},
|
||||
/**
|
||||
* Domains added on searches by the view manager, to override in subsequent
|
||||
* view manager in order to add new pieces of domains to searches
|
||||
*
|
||||
* @returns an empty list
|
||||
*/
|
||||
domains: function () {
|
||||
return [];
|
||||
},
|
||||
/**
|
||||
* Contexts added on searches by the view manager.
|
||||
*
|
||||
* @returns an empty list
|
||||
*/
|
||||
contexts: function () {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -180,8 +199,6 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
|
|||
this.sidebar.start();
|
||||
}
|
||||
|
||||
// init search view
|
||||
var view_id = this.action.search_view_id ? this.action.search_view_id[0] || false : false;
|
||||
var search_defaults = {};
|
||||
_.each(this.action.context, function (value, key) {
|
||||
var match = /^search_default_(.*)$/.exec(key);
|
||||
|
@ -189,10 +206,12 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
|
|||
search_defaults[match[1]] = value;
|
||||
}
|
||||
});
|
||||
var searchview_loaded = null;
|
||||
if (view_id) {
|
||||
searchview_loaded = this.setup_search_view(view_id,search_defaults);
|
||||
}
|
||||
|
||||
// init search view
|
||||
var searchview_id = this.action.search_view_id && this.action.search_view_id[0];
|
||||
|
||||
var searchview_loaded = this.setup_search_view(
|
||||
searchview_id || false, search_defaults);
|
||||
|
||||
// schedule auto_search
|
||||
if (searchview_loaded != null && this.action['auto_search']) {
|
||||
|
@ -206,6 +225,28 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
|
|||
this.sidebar.stop();
|
||||
}
|
||||
this._super();
|
||||
},
|
||||
/**
|
||||
* adds action domain to the search domains
|
||||
*
|
||||
* @returns the action's domain
|
||||
*/
|
||||
domains: function () {
|
||||
if (!this.action.domain) {
|
||||
return [];
|
||||
}
|
||||
return [this.action.domain];
|
||||
},
|
||||
/**
|
||||
* adds action context to the search contexts
|
||||
*
|
||||
* @returns the action's context
|
||||
*/
|
||||
contexts: function () {
|
||||
if (!this.action.context) {
|
||||
return [];
|
||||
}
|
||||
return [this.action.context];
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -154,38 +154,37 @@
|
|||
</tr>
|
||||
</table>
|
||||
</t>
|
||||
<t t-name="ListView">
|
||||
<!--
|
||||
<h3><t t-esc="fields_view.arch['@string']"/></h3>
|
||||
-->
|
||||
<div class="oe_list_header">
|
||||
<div class="oe_list_buttons">
|
||||
<!--<button type="button" class="oe_list_button_new">New</button>-->
|
||||
</div>
|
||||
<div class="oe_list_pager">
|
||||
<button type="button" data-pager-action="first">First</button>
|
||||
<button type="button" data-pager-action="previous"><<</button>
|
||||
|
||||
<span class="oe_pager_index">0</span> / <span class="oe_pager_count">0</span>
|
||||
|
||||
<button type="button" data-pager-action="next">>></button>
|
||||
<button type="button" data-pager-action="last">Last</button>
|
||||
</div>
|
||||
</div>
|
||||
<table>
|
||||
<t t-call="ListView.header"/>
|
||||
</table>
|
||||
</t>
|
||||
<t t-name="ListView.header">
|
||||
<thead t-if="options.header">
|
||||
<tr t-if="options.selectable and options.deletable">
|
||||
<th t-att-colspan="visible_columns.length + 2">
|
||||
<button type="button" id="oe-list-delete">
|
||||
Delete Selected
|
||||
<table t-name="ListView">
|
||||
<t t-set="columns_count" t-value="visible_columns.length + (options.selectable ? 1 : 0) + (options.deletable ? 1 : 0)"/>
|
||||
<t t-set="actions_span" t-value="Math.floor((options.deletable or options.addable) ? columns_count/2 : 0)"/>
|
||||
<thead class="ui-widget-header">
|
||||
<tr t-if="options.selectable">
|
||||
<th t-if="actions_span" t-att-colspan="actions_span"
|
||||
class="oe-actions">
|
||||
<button type="button" id="oe-list-add"
|
||||
t-if="options.addable">
|
||||
<t t-esc="options.addable"/>
|
||||
</button>
|
||||
<button type="button" id="oe-list-delete"
|
||||
t-if="options.deletable">
|
||||
Delete
|
||||
</button>
|
||||
</th>
|
||||
<th t-att-colspan="columns_count - actions_span"
|
||||
class="oe-list-pager">
|
||||
<button type="button" data-pager-action="first">First</button>
|
||||
<button type="button" data-pager-action="previous"
|
||||
><<</button>
|
||||
|
||||
<span class="oe-pager-first">1</span>
|
||||
to <span class="oe-pager-last">1</span>
|
||||
of <span class="oe-pager-total">1</span>
|
||||
|
||||
<button type="button" data-pager-action="next">>></button>
|
||||
<button type="button" data-pager-action="last">Last</button>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr t-if="options.header">
|
||||
<th t-if="options.selectable"/>
|
||||
<t t-foreach="columns" t-as="column">
|
||||
<th t-if="column.invisible !== '1'">
|
||||
|
@ -197,27 +196,36 @@
|
|||
<th t-if="options.deletable"/>
|
||||
</tr>
|
||||
</thead>
|
||||
</t>
|
||||
</table>
|
||||
<t t-name="ListView.rows" t-foreach="rows" t-as="row">
|
||||
<t t-call="ListView.row"/>
|
||||
<t t-call="ListView.row">
|
||||
<t t-set="style" t-value="null"/>
|
||||
<t-if test="row.color">
|
||||
<t t-set="style" t-value="'color: ' + row.color"/>
|
||||
</t-if>
|
||||
</t>
|
||||
</t>
|
||||
<tr t-name="ListView.row">
|
||||
<tr t-name="ListView.row" t-att-style="style"
|
||||
t-att-class="row_parity">
|
||||
<th t-if="options.selectable" class="oe-record-selector">
|
||||
<input type="checkbox"/>
|
||||
</th>
|
||||
<t t-foreach="columns" t-as="column">
|
||||
<!-- TODO: handle attrs -->
|
||||
<td t-if="column.invisible !== '1'" t-att-title="column.help">
|
||||
<t t-set="is_button" t-value="column.tag === 'button'"/>
|
||||
<!-- TODO: get correct widget from form -->
|
||||
<t t-if="!is_button and row[column.id]">
|
||||
<t t-esc="row[column.id]"/>
|
||||
<t t-set="attrs" t-value="column.attrs_for(row.data)"/>
|
||||
<td t-if="column.invisible !== '1'" t-att-title="column.help"
|
||||
class="oe-field-cell">
|
||||
<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">
|
||||
<t t-esc="row['data'][column.id].value"/>
|
||||
</t>
|
||||
<button type="button" t-att-title="column.help"
|
||||
t-if="is_button">
|
||||
<img t-att-src="'/base/static/src/img/icons/' + column.icon + '.png'"
|
||||
t-att-alt="column.string"/>
|
||||
</button>
|
||||
</t>
|
||||
<button type="button" t-att-title="column.help"
|
||||
t-if="is_button">
|
||||
<img t-att-src="'/base/static/src/img/icons/' + column.icon + '.png'"
|
||||
t-att-alt="column.string"/>
|
||||
</button>
|
||||
</td>
|
||||
</t>
|
||||
<td t-if="options.deletable" class='oe-record-delete'>
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
$(document).ready(function () {
|
||||
var openerp;
|
||||
module("form.widget", {
|
||||
setup: function () {
|
||||
openerp = window.openerp.init(true);
|
||||
window.openerp.base.chrome(openerp);
|
||||
// views loader stuff
|
||||
window.openerp.base.data(openerp);
|
||||
window.openerp.base.views(openerp);
|
||||
window.openerp.base.form(openerp);
|
||||
}
|
||||
});
|
||||
test("compute_domain", function () {
|
||||
var fields = {
|
||||
'a': {value: 3},
|
||||
'group_method': {value: 'line'},
|
||||
'select1': {value: 'day'},
|
||||
'rrule_type': {value: 'monthly'}
|
||||
};
|
||||
ok(openerp.base.form.compute_domain(
|
||||
[['a', '=', 3]], fields));
|
||||
ok(openerp.base.form.compute_domain(
|
||||
[['group_method','!=','count']], fields));
|
||||
ok(openerp.base.form.compute_domain(
|
||||
[['select1','=','day'], ['rrule_type','=','monthly']], fields));
|
||||
});
|
||||
test("compute_domain or", function () {
|
||||
var base = {
|
||||
'section_id': {value: null},
|
||||
'user_id': {value: null},
|
||||
'member_ids': {value: null}
|
||||
};
|
||||
|
||||
var domain = ['|', ['section_id', '=', 42],
|
||||
'|', ['user_id','=',3],
|
||||
['member_ids', 'in', [3]]];
|
||||
|
||||
ok(openerp.base.form.compute_domain(domain, _.extend(
|
||||
{}, base, {'section_id': {value: 42}})));
|
||||
ok(openerp.base.form.compute_domain(domain, _.extend(
|
||||
{}, base, {'user_id': {value: 3}})));
|
||||
|
||||
ok(openerp.base.form.compute_domain(domain, _.extend(
|
||||
{}, base, {'member_ids': {value: 3}})));
|
||||
});
|
||||
test("compute_domain not", function () {
|
||||
var fields = {
|
||||
'a': {value: 5},
|
||||
'group_method': {value: 'line'}
|
||||
};
|
||||
ok(openerp.base.form.compute_domain(
|
||||
['!', ['a', '=', 3]], fields));
|
||||
ok(openerp.base.form.compute_domain(
|
||||
['!', ['group_method','=','count']], fields));
|
||||
});
|
||||
});
|
|
@ -24,7 +24,9 @@ $(document).ready(function () {
|
|||
openerp = window.openerp.init(true);
|
||||
window.openerp.base.chrome(openerp);
|
||||
// views loader stuff
|
||||
window.openerp.base.data(openerp);
|
||||
window.openerp.base.views(openerp);
|
||||
window.openerp.base.form(openerp);
|
||||
window.openerp.base.list(openerp);
|
||||
}
|
||||
});
|
||||
|
@ -36,7 +38,11 @@ $(document).ready(function () {
|
|||
|
||||
listview.on_loaded(fvg);
|
||||
|
||||
listview.do_fill_table([{}, {}, {}]).then(function () {
|
||||
listview.do_fill_table([
|
||||
{data: {id: {value: null}}},
|
||||
{data: {id: {value: null}}},
|
||||
{data: {id: {value: null}}}
|
||||
]).then(function () {
|
||||
ok(are(listview.$element.find('tbody th'),
|
||||
'.oe-record-selector'));
|
||||
ok(are(listview.$element.find('tbody th input'),
|
||||
|
@ -52,7 +58,11 @@ $(document).ready(function () {
|
|||
|
||||
listview.on_loaded(fvg);
|
||||
|
||||
listview.do_fill_table([{}, {}, {}]).then(function () {
|
||||
listview.do_fill_table([
|
||||
{data: {id: {value: null}}},
|
||||
{data: {id: {value: null}}},
|
||||
{data: {id: {value: null}}}
|
||||
]).then(function () {
|
||||
equal(listview.$element.find('tbody th').length, 0);
|
||||
start();
|
||||
});
|
||||
|
@ -62,7 +72,11 @@ $(document).ready(function () {
|
|||
{}, null, 'qunit-fixture', {model: null});
|
||||
listview.on_loaded(fvg);
|
||||
|
||||
listview.do_fill_table([{id: 1}, {id: 2}, {id: 3}]).then(function () {
|
||||
listview.do_fill_table([
|
||||
{data: {id: {value: 1}}},
|
||||
{data: {id: {value: 2}}},
|
||||
{data: {id: {value: 3}}}
|
||||
]).then(function () {
|
||||
listview.$element.find('tbody th input:eq(2)')
|
||||
.attr('checked', true);
|
||||
deepEqual(listview.get_selection(), [3]);
|
||||
|
@ -78,7 +92,11 @@ $(document).ready(function () {
|
|||
|
||||
listview.on_loaded(fvg);
|
||||
|
||||
listview.do_fill_table([{id: 1}, {id: 2}, {id: 3}]).then(function () {
|
||||
listview.do_fill_table([
|
||||
{data: {id: {value: null}}},
|
||||
{data: {id: {value: null}}},
|
||||
{data: {id: {value: null}}}
|
||||
]).then(function () {
|
||||
equal(
|
||||
listview.$element.find('tbody tr td.oe-record-delete button').length,
|
||||
3);
|
||||
|
@ -95,7 +113,11 @@ $(document).ready(function () {
|
|||
|
||||
listview.on_loaded(fvg);
|
||||
|
||||
listview.do_fill_table([{id: 1}, {id: 2}, {id: 3}]).then(function () {
|
||||
listview.do_fill_table([
|
||||
{data: {id: {value: 1}}},
|
||||
{data: {id: {value: 2}}},
|
||||
{data: {id: {value: 3}}}
|
||||
]).then(function () {
|
||||
listview.$element.find('tbody td.oe-record-delete:eq(2) button').click();
|
||||
deepEqual(deleted, [3]);
|
||||
listview.$element.find('tbody td.oe-record-delete:eq(0) button').click();
|
||||
|
@ -112,7 +134,11 @@ $(document).ready(function () {
|
|||
|
||||
listview.on_loaded(fvg);
|
||||
|
||||
listview.do_fill_table([{id: 1}, {id: 2}, {id: 3}]).then(function () {
|
||||
listview.do_fill_table([
|
||||
{data: {id: {value: 1}}},
|
||||
{data: {id: {value: 2}}},
|
||||
{data: {id: {value: 3}}}
|
||||
]).then(function () {
|
||||
listview.$element.find('tbody th input:eq(2)')
|
||||
.attr('checked', true);
|
||||
listview.$element.find('tbody th input:eq(1)')
|
||||
|
|
|
@ -21,6 +21,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/form.js"></script>
|
||||
<script src="/base/static/src/js/list.js"></script>
|
||||
<script type="text/javascript">
|
||||
QWeb.add_template('/base/static/src/xml/base.xml');
|
||||
|
@ -36,5 +37,6 @@
|
|||
</body>
|
||||
<script type="text/javascript" src="/base/static/test/registry.js"></script>
|
||||
<script type="text/javascript" src="/base/static/test/search-date.js"></script>
|
||||
<script type="text/javascript" src="/base/static/test/form.js"></script>
|
||||
<script type="text/javascript" src="/base/static/test/list.js"></script>
|
||||
</html>
|
||||
|
|
|
@ -14,14 +14,15 @@ class TestDataSetController(unittest2.TestCase):
|
|||
self.search.return_value = []
|
||||
self.read.return_value = []
|
||||
|
||||
self.assertFalse(self.dataset.do_find(self.request, 'fake.model'))
|
||||
self.read.assert_called_once_with([], False)
|
||||
self.assertFalse(self.dataset.do_search_read(self.request, 'fake.model'))
|
||||
self.read.assert_called_once_with([], False, self.request.context)
|
||||
|
||||
def test_regular_find(self):
|
||||
self.search.return_value = [1, 2, 3]
|
||||
|
||||
self.dataset.do_find(self.request, 'fake.model')
|
||||
self.read.assert_called_once_with([1, 2, 3], False)
|
||||
self.dataset.do_search_read(self.request, 'fake.model')
|
||||
self.read.assert_called_once_with([1, 2, 3], False,
|
||||
self.request.context)
|
||||
|
||||
def test_ids_shortcut(self):
|
||||
self.search.return_value = [1, 2, 3]
|
||||
|
@ -32,7 +33,7 @@ class TestDataSetController(unittest2.TestCase):
|
|||
]
|
||||
|
||||
self.assertEqual(
|
||||
self.dataset.do_find(self.request, 'fake.model', ['id']),
|
||||
self.dataset.do_search_read(self.request, 'fake.model', ['id']),
|
||||
[{'id': 1}, {'id': 2}, {'id': 3}])
|
||||
self.assertFalse(self.read.called)
|
||||
|
||||
|
@ -46,7 +47,7 @@ class TestDataSetController(unittest2.TestCase):
|
|||
result = self.dataset.do_get(
|
||||
self.request, 'fake.model', [3, 2, 1])
|
||||
self.read.assert_called_once_with(
|
||||
[3, 2, 1])
|
||||
[3, 2, 1], False)
|
||||
self.assertFalse(self.search.called)
|
||||
|
||||
self.assertEqual(
|
||||
|
|
|
@ -1,34 +1,23 @@
|
|||
import copy
|
||||
import xml.etree.ElementTree
|
||||
import mock
|
||||
|
||||
import unittest2
|
||||
import simplejson
|
||||
|
||||
import base.controllers.main
|
||||
import openerpweb.nonliterals
|
||||
import openerpweb.openerpweb
|
||||
|
||||
def field_attrs(fields_view_get, fieldname):
|
||||
(field,) = filter(lambda f: f['attrs'].get('name') == fieldname,
|
||||
fields_view_get['arch']['children'])
|
||||
return field['attrs']
|
||||
|
||||
#noinspection PyCompatibility
|
||||
class ViewTest(unittest2.TestCase):
|
||||
class DomainsAndContextsTest(unittest2.TestCase):
|
||||
def setUp(self):
|
||||
self.view = base.controllers.main.View()
|
||||
def test_identity(self):
|
||||
base_view = """
|
||||
<form string="Title">
|
||||
<group>
|
||||
<field name="some_field"/>
|
||||
<field name="some_other_field"/>
|
||||
</group>
|
||||
<field name="stuff"/>
|
||||
</form>
|
||||
"""
|
||||
|
||||
pristine = xml.etree.ElementTree.fromstring(base_view)
|
||||
transformed = self.view.transform_view(base_view, None)
|
||||
|
||||
self.assertEqual(
|
||||
xml.etree.ElementTree.tostring(transformed),
|
||||
xml.etree.ElementTree.tostring(pristine)
|
||||
)
|
||||
|
||||
def test_convert_literal_domain(self):
|
||||
e = xml.etree.ElementTree.Element(
|
||||
|
@ -115,3 +104,137 @@ class ViewTest(unittest2.TestCase):
|
|||
openerpweb.nonliterals.Context(
|
||||
session, key=e.get('context').key).get_context_string(),
|
||||
context_string)
|
||||
|
||||
class AttrsNormalizationTest(unittest2.TestCase):
|
||||
def setUp(self):
|
||||
self.view = base.controllers.main.View()
|
||||
|
||||
def test_identity(self):
|
||||
base_view = """
|
||||
<form string="Title">
|
||||
<group>
|
||||
<field name="some_field"/>
|
||||
<field name="some_other_field"/>
|
||||
</group>
|
||||
<field name="stuff"/>
|
||||
</form>
|
||||
"""
|
||||
|
||||
pristine = xml.etree.ElementTree.fromstring(base_view)
|
||||
transformed = self.view.transform_view(base_view, None)
|
||||
|
||||
self.assertEqual(
|
||||
xml.etree.ElementTree.tostring(transformed),
|
||||
xml.etree.ElementTree.tostring(pristine)
|
||||
)
|
||||
def test_transform_states(self):
|
||||
element = xml.etree.ElementTree.Element(
|
||||
'field', states="open,closed")
|
||||
self.view.normalize_attrs(element, {})
|
||||
|
||||
self.assertIsNone(element.get('states'))
|
||||
self.assertEqual(
|
||||
simplejson.loads(element.get('attrs')),
|
||||
{'invisible': [['state', 'not in', ['open', 'closed']]]})
|
||||
|
||||
def test_transform_invisible(self):
|
||||
element = xml.etree.ElementTree.Element(
|
||||
'field', invisible="context.get('invisible_country', False)")
|
||||
|
||||
empty_context = copy.deepcopy(element)
|
||||
self.view.normalize_attrs(empty_context, {})
|
||||
self.assertEqual(empty_context.get('invisible'), None)
|
||||
|
||||
full_context = copy.deepcopy(element)
|
||||
self.view.normalize_attrs(full_context, {'invisible_country': True})
|
||||
self.assertEqual(full_context.get('invisible'), '1')
|
||||
|
||||
def test_transform_invisible_list_column(self):
|
||||
req = mock.Mock()
|
||||
req.context = {'set_editable':True, 'set_visible':True,
|
||||
'gtd_visible':True, 'user_invisible':True}
|
||||
req.session.evaluation_context = \
|
||||
openerpweb.openerpweb.OpenERPSession().evaluation_context
|
||||
req.session.model('project.task').fields_view_get.return_value = {
|
||||
'arch': '''
|
||||
<tree colors="grey:state in ('cancelled','done');blue:state == 'pending';red:date_deadline and (date_deadline<current_date) and (state in ('draft','pending','open'))" string="Tasks">
|
||||
<field name="sequence" invisible="not context.get('seq_visible', False)"/>
|
||||
<field name="user_id" invisible="context.get('user_invisible', False)"/>
|
||||
<field name="delegated_user_id" invisible="context.get('show_delegated', True)"/>
|
||||
<field name="total_hours" invisible="1"/>
|
||||
<field name="date_deadline" invisible="context.get('deadline_visible',True)"/>
|
||||
<field name="type_id" invisible="context.get('set_visible',False)"/>
|
||||
</tree>
|
||||
'''}
|
||||
parsed_view = base.controllers.main.View().fields_view_get(
|
||||
req, 'project.task', 42, 'tree')
|
||||
|
||||
self.assertTrue(field_attrs(parsed_view, 'sequence')['invisible'])
|
||||
self.assertTrue(field_attrs(parsed_view, 'user_id')['invisible'])
|
||||
self.assertTrue(
|
||||
field_attrs(parsed_view, 'delegated_user_id')['invisible'])
|
||||
self.assertTrue(field_attrs(parsed_view, 'total_hours')['invisible'])
|
||||
self.assertTrue(
|
||||
field_attrs(parsed_view, 'date_deadline')['invisible'])
|
||||
self.assertTrue(field_attrs(parsed_view, 'type_id')['invisible'])
|
||||
|
||||
class ListViewTest(unittest2.TestCase):
|
||||
def setUp(self):
|
||||
self.view = base.controllers.main.ListView()
|
||||
self.request = mock.Mock()
|
||||
self.request.context = {'set_editable': True}
|
||||
def test_no_editable_editable_context(self):
|
||||
self.request.session.model('fake').fields_view_get.return_value = \
|
||||
{'arch': '<tree><field name="foo"/></tree>'}
|
||||
view = self.view.fields_view_get(self.request, 'fake', False)
|
||||
|
||||
self.assertEqual(view['arch']['attrs']['editable'],
|
||||
'bottom')
|
||||
def test_editable_top_editable_context(self):
|
||||
self.request.session.model('fake').fields_view_get.return_value = \
|
||||
{'arch': '<tree editable="top"><field name="foo"/></tree>'}
|
||||
view = self.view.fields_view_get(self.request, 'fake', False)
|
||||
|
||||
self.assertEqual(view['arch']['attrs']['editable'],
|
||||
'top')
|
||||
|
||||
def test_editable_bottom_editable_context(self):
|
||||
self.request.session.model('fake').fields_view_get.return_value = \
|
||||
{'arch': '<tree editable="bottom"><field name="foo"/></tree>'}
|
||||
view = self.view.fields_view_get(self.request, 'fake', False)
|
||||
|
||||
self.assertEqual(view['arch']['attrs']['editable'],
|
||||
'bottom')
|
||||
|
||||
def test_color_nocolor(self):
|
||||
self.assertEqual(
|
||||
self.view.process_colors(
|
||||
{'arch': {'attrs': {}, 'children': []}}, {}, {}),
|
||||
None)
|
||||
def test_color_literal(self):
|
||||
self.assertEqual(
|
||||
self.view.process_colors(
|
||||
{'arch': {'attrs': {'colors': 'black:1'}}, 'children': []},
|
||||
{}, {}),
|
||||
'black')
|
||||
def test_color_miss(self):
|
||||
self.assertEqual(
|
||||
self.view.process_colors(
|
||||
{'arch': {'attrs': {'colors': "grey:state in ('cancelled','done');blue:state in ('pending')"}},
|
||||
'children': []
|
||||
}, {'state': 'open'}, {}),
|
||||
None)
|
||||
def test_color_compute(self):
|
||||
self.assertEqual(
|
||||
self.view.process_colors(
|
||||
{'arch': {'attrs': {'colors': "grey:state in ('cancelled','done');blue:state in ('pending')"}},
|
||||
'children': []
|
||||
}, {'state': 'done'}, {}),
|
||||
'grey')
|
||||
def test_color_multiple(self):
|
||||
self.assertEqual(
|
||||
self.view.process_colors(
|
||||
{'arch': {'attrs': {'colors': "grey:state in ('cancelled','done');blue:state in ('done')"}},
|
||||
'children': []
|
||||
}, {'state': 'done'}, {}),
|
||||
'maroon')
|
||||
|
|
|
@ -57,7 +57,7 @@ class CalendarView(View):
|
|||
|
||||
@openerpweb.jsonrequest
|
||||
def load(self, req, model, view_id):
|
||||
fields_view = self.fields_view_get(req.session, model, view_id, 'calendar')
|
||||
fields_view = self.fields_view_get(req, model, view_id, 'calendar')
|
||||
return {'fields_view':fields_view}
|
||||
|
||||
def convert(self, event):
|
||||
|
@ -354,4 +354,3 @@ class CalendarView(View):
|
|||
title = title.strip()
|
||||
description = ', '.join(description).strip()
|
||||
return {'id': event['id'], 'start_date': str(DT.datetime(*starts[:6])), 'end_date': str(DT.datetime(*ends[:6])), 'text': title, 'title': description, 'color': self.colors[event[self.color_field]][-1]}
|
||||
|
|
@ -196,7 +196,7 @@ class OpenERPSession(object):
|
|||
if isinstance(context_to_eval, dict):
|
||||
return context_to_eval
|
||||
|
||||
ctx = context or {}
|
||||
ctx = dict(context or {})
|
||||
ctx['context'] = ctx
|
||||
|
||||
# if the domain was unpacked from JSON, it needs the current
|
||||
|
@ -252,7 +252,7 @@ class OpenERPSession(object):
|
|||
if isinstance(domain, list):
|
||||
return domain
|
||||
|
||||
ctx = context or {}
|
||||
ctx = dict(context or {})
|
||||
ctx['context'] = ctx
|
||||
|
||||
# if the domain was unpacked from JSON, it needs the current
|
||||
|
|
Loading…
Reference in New Issue