[FIX] domain and context generation from fields, also doc

bzr revid: xmo@openerp.com-20120503154311-4pg762pcgt83416c
This commit is contained in:
Xavier Morel 2012-05-03 17:43:11 +02:00
parent 6d12c155a4
commit 0c99ac1333
3 changed files with 213 additions and 39 deletions

View File

@ -1009,20 +1009,26 @@ instance.web.search.Field = instance.web.search.Input.extend( /** @lends instanc
values: [{label: String(value), value: value}]
});
},
get_value: function (facet) {
return facet.value();
value_from: function (facetValue) {
return facetValue.get('value');
},
get_context: function (facet) {
// A field needs a value to be "active", and a context to send when
// active
var self = this;
// A field needs a context to send when active
var context = this.attrs.context;
if (!context) {
if (!context || !facet.values.length) {
return;
}
var val = this.get_value(facet);
var has_value = (val !== null && val !== '');
return new instance.web.CompoundContext(context)
.set_eval_context({self: val});
var contexts = facet.values.map(function (facetValue) {
return new instance.web.CompoundContext(context)
.set_eval_context({self: self.value_from(facetValue)});
});
if (contexts.length === 1) { return contexts[0]; }
return _.extend(instance.web.CompoundContext, {
__contexts: contexts
});
},
get_groupby: function () { },
/**
@ -1037,23 +1043,37 @@ instance.web.search.Field = instance.web.search.Input.extend( /** @lends instanc
* @returns {Array<Array>} domain to include in the resulting search
*/
make_domain: function (name, operator, facet) {
return [[name, operator, this.get_value(facet)]];
return [[name, operator, this.value_from(facet)]];
},
get_domain: function (facet) {
var val = this.get_value(facet);
if (val === null || val === '') {
return;
if (!facet.values.length) { return; }
var value_to_domain;
var self = this;
var domain = this.attrs['filter_domain'];
if (domain) {
value_to_domain = function (facetValue) {
return new instance.web.CompoundDomain(domain)
.set_eval_context({self: self.value_from(facetValue)});
};
} else {
value_to_domain = function (facetValue) {
return self.make_domain(
self.attrs.name,
self.attrs.operator || self.default_operator,
facetValue);
};
}
var domains = facet.values.map(value_to_domain);
if (domains.length === 1) { return domains[0]; }
for (var i = domains.length; --i;) {
domains.unshift(['|']);
}
var domain = this.attrs['filter_domain'];
if (!domain) {
return this.make_domain(
this.attrs.name,
this.attrs.operator || this.default_operator,
facet);
}
return new instance.web.CompoundDomain(domain)
.set_eval_context({self: val});
return _.extend(new instance.web.CompoundDomain, {
__domains: domains
});
}
});
/**
@ -1085,7 +1105,7 @@ instance.web.search.CharField = instance.web.search.Field.extend( /** @lends ins
}
});
instance.web.search.NumberField = instance.web.search.Field.extend(/** @lends instance.web.search.NumberField# */{
get_value: function () {
value_from: function () {
if (!this.$element.val()) {
return null;
}
@ -1193,7 +1213,7 @@ instance.web.search.SelectionField = instance.web.search.Field.extend(/** @lends
if (!match) { return $.when(null); }
return $.when(facet_from(this, match));
},
get_value: function (facet) {
value_from: function (facet) {
return facet.get('values');
}
});
@ -1209,7 +1229,7 @@ instance.web.search.BooleanField = instance.web.search.SelectionField.extend(/**
['false', _t("No")]
];
},
get_value: function (facet) {
value_from: function (facet) {
switch (this._super(facet)) {
case 'false': return false;
case 'true': return true;
@ -1222,7 +1242,7 @@ instance.web.search.BooleanField = instance.web.search.SelectionField.extend(/**
* @extends instance.web.search.DateField
*/
instance.web.search.DateField = instance.web.search.Field.extend(/** @lends instance.web.search.DateField# */{
get_value: function (facet) {
value_from: function (facet) {
return openerp.web.date_to_str(facet.get('values'));
},
complete: function (needle) {
@ -1255,7 +1275,7 @@ instance.web.search.DateField = instance.web.search.Field.extend(/** @lends inst
* @extends instance.web.DateField
*/
instance.web.search.DateTimeField = instance.web.search.DateField.extend(/** @lends instance.web.search.DateTimeField# */{
get_value: function (facet) {
value_from: function (facet) {
return openerp.web.datetime_to_str(facet.get('values'));
}
});

View File

@ -770,6 +770,70 @@ $(document).ready(function () {
});
});
test('Field single value, default domain & context', function () {
var f = new instance.web.search.Field({}, {name: 'foo'}, {inputs: []});
var facet = new instance.web.search.Facet({
field: f,
values: [{value: 42}]
});
deepEqual(f.get_domain(facet), [['foo', '=', 42]],
"default field domain is a strict equality of name to facet's value");
equal(f.get_context(facet), null,
"default field context is null");
});
test('Field multiple values, default domain & context', function () {
var f = new instance.web.search.Field({}, {name: 'foo'}, {inputs: []});
var facet = new instance.web.search.Facet({
field: f,
values: [{value: 42}, {value: 68}, {value: 999}]
});
var actual_domain = f.get_domain(facet);
equal(actual_domain.__ref, "compound_domain",
"multiple value should yield compound domain");
deepEqual(actual_domain.__domains, [
['|'],
['|'],
[['foo', '=', 42]],
[['foo', '=', 68]],
[['foo', '=', 999]]
],
"domain should OR a default domain for each value");
equal(f.get_context(facet), null,
"default field context is null");
});
test('Field single value, custom domain & context', function () {
var f = new instance.web.search.Field({attrs:{
context: "{'bob': self}",
filter_domain: "[['edmund', 'is', self]]"
}}, {name: 'foo'}, {inputs: []});
var facet = new instance.web.search.Facet({
field: f,
values: [{value: "great"}]
});
var actual_domain = f.get_domain(facet);
equal(actual_domain.__ref, "compound_domain",
"@filter_domain should yield compound domain");
deepEqual(actual_domain.__domains, [
"[['edmund', 'is', self]]"
], 'should hold unevaluated custom domain');
deepEqual(actual_domain.get_eval_context(), {
self: "great"
}, "evaluation context should hold facet value as self");
var actual_context = f.get_context(facet);
equal(actual_context.__ref, "compound_context",
"@context should yield compound context");
deepEqual(actual_context.__contexts, [
"{'bob': self}"
], 'should hold unevaluated custom context');
deepEqual(actual_context.get_eval_context(), {
self: "great"
}, "evaluation context should hold facet value as self");
});
module('drawer', {
setup: function () {
instance = window.openerp.init([]);

View File

@ -277,6 +277,92 @@ exception if the value is not valid at all for the field.
``Array`` of groupby domains rather than a single context. At this
point, it is only implemented on (and used by) filters.
Field services
++++++++++++++
:js:class:`~openerp.web.search.Field` provides a default
implementation of :js:func:`~openerp.web.search.Input.get_domain` and
:js:func:`~openerp.web.search.Input.get_context` taking care of most
of the peculiarities pertaining to OpenERP's handling of fields in
search views. It also provides finer hooks to let developers of new
fields and widgets customize the behavior they want without
necessarily having to reimplement all of
:js:func:`~openerp.web.search.Input.get_domain` or
:js:func:`~openerp.web.search.Input.get_context`:
.. js:function:: openerp.web.search.Field.get_context(facet)
If the field has no ``@context``, simply returns
``null``. Otherwise, calls
:js:func:`~openerp.web.search.Field.value_from` once for each
:js:class:`~openerp.web.search.FacetValue` of the current
:js:class:`~openerp.web.search.Facet` (in order to extract the
basic javascript object from the
:js:class:`~openerp.web.search.FacetValue` then evaluates
``@context`` with each of these values set as ``self``, and
returns the union of all these contexts.
:param facet:
:type facet: openerp.web.search.Facet
:returns: a context (literal or compound)
.. js:function:: openerp.web.search.Field.get_domain(facet)
If the field has no ``@filter_domain``, calls
:js:func:`~openerp.web.search.Field.make_domain` once with each
:js:class:`~openerp.web.search.FacetValue` of the current
:js:class:`~openerp.web.search.Facet` as well as the field's
``@name`` and either its ``@operator`` or
:js:attr:`~openerp.web.search.Field.default_operator`.
If the field has an ``@filter_value``, calls
:js:func:`~openerp.web.search.Field.value_from` once per
:js:class:`~openerp.web.search.FacetValue` and evaluates
``@filter_value`` with each of these values set as ``self``.
In either case, "ors" all of the resulting domains (using ``|``)
if there is more than one
:js:class:`~openerp.web.search.FacetValue` and returns the union
of the result.
:param facet:
:type facet: openerp.web.search.Facet
:returns: a domain (literal or compound)
.. js:function:: openerp.web.search.Field.make_domain(name, operator, facetValue)
Builds a literal domain from the provided data. Calls
:js:func:`~openerp.web.search.Field.value_from` on the
:js:class:`~openerp.web.search.FacetValue` and evaluates and sets
it as the domain's third value, uses the other two parameters as
the first two values.
Can be overridden to build more complex default domains.
:param String name: the field's name
:param String operator: the operator to use in the field's domain
:param facetValue:
:type facetValue: openerp.web.search.FacetValue
:returns: Array<(String, String, Object)>
.. js:function:: openerp.web.search.Field.value_from(facetValue)
Extracts a "bare" javascript value from the provided
:js:class:`~openerp.web.search.FacetValue`, and returns it.
The default implementation will simply return the ``value``
backbone property of the argument.
:param facetValue:
:type facetValue: openerp.web.search.FacetValue
:returns: Object
.. js:attribute:: openerp.web.search.Field.default_operator
Operator used to build a domain when a field has no ``@operator``
or ``@filter_domain``. ``"="`` for
:js:class:`~openerp.web.search.Field`
Converting to facet objects
---------------------------
@ -314,7 +400,7 @@ Widgets API
* :js:func:`~openerp.web.search.Input.get_domain` and
:js:func:`~openerp.web.search.Input.get_context` now take a
:js:class:`~VS.model.SearchFacet` as parameter, from which it's
:js:class:`~openerp.web.search.Facet` as parameter, from which it's
their job to get whatever value they want
* :js:func:`~openerp.web.search.Input.get_groupby` has been added. It returns
@ -334,19 +420,16 @@ Filters
Fields
++++++
* ``get_value`` now takes a :js:class:`~VS.model.SearchFacet` (instead
of taking no argument).
A default implementation is provided as
:js:func:`openerp.web.search.Field.get_value` and simply calls
:js:func:`VS.model.SearchFacet.value`.
* ``get_value`` has been replaced by
:js:func:`~openerp.web.search.Field.value_from` as it now takes a
:js:class:`~openerp.web.search.FacetValue` argument (instead of no
argument). It provides a default implementation returning the
``value`` property of its argument.
* The third argument to
:js:func:`~openerp.web.search.Field.make_domain` is now the
:js:class:`~VS.model.SearchFacet` received by
:js:func:`~openerp.web.search.Field.get_domain`, so child classes
have all the information they need to derive the "right" resulting
domain.
:js:func:`~openerp.web.search.Field.make_domain` is now a
:js:class:`~openerp.web.search.FacetValue` so child classes have all
the information they need to derive the "right" resulting domain.
Custom filters
++++++++++++++
@ -363,6 +446,13 @@ Many To One
:js:func:`openerp.web.search.ManyToOneField.setup_autocomplete` has
been removed.
Advanced Search
+++++++++++++++
The advanced search is now a more standard
:js:class:`~openerp.web.search.Input` configured to be rendered in the
drawer.
.. [#] the original view was implemented on top of a monkey-patched
VisualSearch, but as our needs diverged from VisualSearch's goal this
made less and less sense ultimately leading to a clean-room