[16.0][MIG] web_m2x_options: Migration to 16.0

pull/2961/head
Diep Huu Hoang 2023-03-28 12:48:05 +07:00 committed by Siddharth Bhalgami
parent 7d050254e7
commit bf4ace489b
11 changed files with 758 additions and 567 deletions

View File

@ -14,13 +14,13 @@ web_m2x_options
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3 :alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
:target: https://github.com/OCA/web/tree/15.0/web_m2x_options :target: https://github.com/OCA/web/tree/16.0/web_m2x_options
:alt: OCA/web :alt: OCA/web
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/web-15-0/web-15-0-web_m2x_options :target: https://translation.odoo-community.org/projects/web-16-0/web-16-0-web_m2x_options
:alt: Translate me on Weblate :alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/162/15.0 :target: https://runbot.odoo-community.org/runbot/162/16.0
:alt: Try me on Runbot :alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5| |badge1| |badge2| |badge3| |badge4| |badge5|
@ -154,7 +154,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_. Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_.
In case of trouble, please check there if your issue has already been reported. In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_m2x_options%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. `feedback <https://github.com/OCA/web/issues/new?body=module:%20web_m2x_options%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues. Do not contact contributors directly about support or help with technical issues.
@ -186,6 +186,13 @@ Contributors
* Carlos Roca * Carlos Roca
* Bhavesh Odedra <bodedra@opensourceintegrators.com> * Bhavesh Odedra <bodedra@opensourceintegrators.com>
* Dhara Solanki <dhara.solanki@initos.com> (http://www.initos.com) * Dhara Solanki <dhara.solanki@initos.com> (http://www.initos.com)
* `Trobz <https://trobz.com>`_:
* Hoang Diep <hoang@trobz.com>
Other credits
~~~~~~~~~~~~~
The migration of this module from 15.0 to 16.0 was financially supported by Camptocamp
Maintainers Maintainers
~~~~~~~~~~~ ~~~~~~~~~~~
@ -200,6 +207,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and mission is to support the collaborative development of Odoo features and
promote its widespread use. promote its widespread use.
This module is part of the `OCA/web <https://github.com/OCA/web/tree/15.0/web_m2x_options>`_ project on GitHub. This module is part of the `OCA/web <https://github.com/OCA/web/tree/16.0/web_m2x_options>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -6,7 +6,7 @@
{ {
"name": "web_m2x_options", "name": "web_m2x_options",
"version": "15.0.1.1.0", "version": "16.0.1.1.0",
"category": "Web", "category": "Web",
"author": "initOS GmbH," "author": "initOS GmbH,"
"ACSONE SA/NV, " "ACSONE SA/NV, "
@ -16,12 +16,6 @@
"website": "https://github.com/OCA/web", "website": "https://github.com/OCA/web",
"license": "AGPL-3", "license": "AGPL-3",
"depends": ["web"], "depends": ["web"],
"assets": { "assets": {"web.assets_backend": ["web_m2x_options/static/src/components/*"]},
"web.assets_backend": [
"web_m2x_options/static/src/js/form.js",
"web_m2x_options/static/src/js/ir_options.js",
],
"web.assets_qweb": ["web_m2x_options/static/src/xml/base.xml"],
},
"installable": True, "installable": True,
} }

View File

@ -4,7 +4,7 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Odoo Server 15.0\n" "Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
@ -14,82 +14,80 @@ msgstr ""
"Plural-Forms: \n" "Plural-Forms: \n"
#. module: web_m2x_options #. module: web_m2x_options
#. openerp-web #. odoo-javascript
#: code:addons/web_m2x_options/static/src/xml/base.xml:0 #: code:addons/web_m2x_options/static/src/components/base.xml:0
#, python-format #, python-format
msgid "" msgid ", are you sure it does not exist yet?"
"!(widget.nodeOptions.no_open || widget.nodeOptions.no_open_edit || "
"widget.noOpen)"
msgstr "" msgstr ""
#. module: web_m2x_options #. module: web_m2x_options
#. openerp-web #. odoo-javascript
#: code:addons/web_m2x_options/static/src/js/form.js:0 #: code:addons/web_m2x_options/static/src/components/base.xml:0
#, python-format
msgid "Cancel"
msgstr ""
#. module: web_m2x_options
#. openerp-web
#: code:addons/web_m2x_options/static/src/js/form.js:0
#, python-format #, python-format
msgid "Create" msgid "Create"
msgstr "" msgstr ""
#. module: web_m2x_options #. module: web_m2x_options
#. openerp-web #. odoo-javascript
#: code:addons/web_m2x_options/static/src/js/form.js:0 #: code:addons/web_m2x_options/static/src/components/relational_utils.esm.js:0
#, python-format #, python-format
msgid "Create \"<strong>%s</strong>\"" msgid "Create \"%s\""
msgstr "" msgstr ""
#. module: web_m2x_options #. module: web_m2x_options
#. openerp-web #. odoo-javascript
#: code:addons/web_m2x_options/static/src/js/form.js:0 #: code:addons/web_m2x_options/static/src/components/base.xml:0
#, python-format #, python-format
msgid "Create a %s" msgid "Create and Edit"
msgstr "" msgstr ""
#. module: web_m2x_options #. module: web_m2x_options
#. openerp-web #. odoo-javascript
#: code:addons/web_m2x_options/static/src/js/form.js:0 #: code:addons/web_m2x_options/static/src/components/relational_utils.esm.js:0
#, python-format #, python-format
msgid "Create and Edit..." msgid "Create and edit..."
msgstr "" msgstr ""
#. module: web_m2x_options #. module: web_m2x_options
#. openerp-web #. odoo-javascript
#: code:addons/web_m2x_options/static/src/js/form.js:0 #: code:addons/web_m2x_options/static/src/components/base.xml:0
#, python-format #, python-format
msgid "Create and edit" msgid "Discard"
msgstr "" msgstr ""
#. module: web_m2x_options #. module: web_m2x_options
#. openerp-web #. odoo-javascript
#: code:addons/web_m2x_options/static/src/js/form.js:0 #: code:addons/web_m2x_options/static/src/components/form.esm.js:0
#, python-format #, python-format
msgid "No results to show..." msgid "New: %s"
msgstr "" msgstr ""
#. module: web_m2x_options #. module: web_m2x_options
#. openerp-web #. odoo-javascript
#: code:addons/web_m2x_options/static/src/js/form.js:0 #: code:addons/web_m2x_options/static/src/components/relational_utils.esm.js:0
#, python-format
msgid "No records"
msgstr ""
#. module: web_m2x_options
#. odoo-javascript
#: code:addons/web_m2x_options/static/src/components/form.esm.js:0
#, python-format #, python-format
msgid "Open: " msgid "Open: "
msgstr "" msgstr ""
#. module: web_m2x_options #. module: web_m2x_options
#. openerp-web #. odoo-javascript
#: code:addons/web_m2x_options/static/src/js/form.js:0 #: code:addons/web_m2x_options/static/src/components/relational_utils.esm.js:0
#, python-format #, python-format
msgid "Quick search: %s" msgid "Search More..."
msgstr "" msgstr ""
#. module: web_m2x_options #. module: web_m2x_options
#. openerp-web #. odoo-javascript
#: code:addons/web_m2x_options/static/src/js/form.js:0 #: code:addons/web_m2x_options/static/src/components/relational_utils.esm.js:0
#, python-format #, python-format
msgid "Search More..." msgid "Start typing..."
msgstr "" msgstr ""
#. module: web_m2x_options #. module: web_m2x_options
@ -98,8 +96,15 @@ msgid "System Parameter"
msgstr "" msgstr ""
#. module: web_m2x_options #. module: web_m2x_options
#. openerp-web #. odoo-javascript
#: code:addons/web_m2x_options/static/src/js/form.js:0 #: code:addons/web_m2x_options/static/src/components/base.xml:0
#, python-format #, python-format
msgid "You are creating a new %s, are you sure it does not exist yet?" msgid "You are creating a new"
msgstr ""
#. module: web_m2x_options
#. odoo-javascript
#: code:addons/web_m2x_options/static/src/components/base.xml:0
#, python-format
msgid "as a new"
msgstr "" msgstr ""

View File

@ -12,3 +12,5 @@
* Carlos Roca * Carlos Roca
* Bhavesh Odedra <bodedra@opensourceintegrators.com> * Bhavesh Odedra <bodedra@opensourceintegrators.com>
* Dhara Solanki <dhara.solanki@initos.com> (http://www.initos.com) * Dhara Solanki <dhara.solanki@initos.com> (http://www.initos.com)
* `Trobz <https://trobz.com>`_:
* Hoang Diep <hoang@trobz.com>

View File

@ -0,0 +1 @@
The migration of this module from 15.0 to 16.0 was financially supported by Camptocamp

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2017 Jairo Llopis <jairo.llopis@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<templates xml:space="preserve">
<t
t-name="web_m2x_options.AutoComplete"
t-inherit="web.AutoComplete"
t-inherit-mode="extension"
owl="1"
>
<xpath expr="//t[@t-foreach='source.options']/li/a" position="attributes">
<attribute name="t-attf-style">{{ option.style }}</attribute>
</xpath>
</t>
<t
t-name="web_m2x_options.Many2ManyTagsField"
t-inherit="web.Many2ManyTagsField"
t-inherit-mode="extension"
owl="1"
>
<xpath expr="//Many2XAutocomplete" position="attributes">
<attribute name="nodeOptions">props.nodeOptions</attribute>
</xpath>
</t>
<t t-name="web_m2x_options.Many2OneField.CreateConfirmationDialog" owl="1">
<Dialog title="title" size="'md'">
<div>
You are creating a new <strong t-esc="props.value" /> as a new <t
t-esc="props.name"
/>, are you sure it does not exist yet?
</div>
<t t-set-slot="footer">
<button class="btn btn-primary" t-on-click="onCreate">Create</button>
<button
class="btn btn-primary"
t-on-click="onCreateEdit"
>Create and Edit</button>
<button class="btn" t-on-click="() => props.close()">Discard</button>
</t>
</Dialog>
</t>
</templates>

View File

@ -0,0 +1,418 @@
/** @odoo-module **/
import {
Many2ManyTagsField,
Many2ManyTagsFieldColorEditable,
} from "@web/views/fields/many2many_tags/many2many_tags_field";
import {Dialog} from "@web/core/dialog/dialog";
import {FormController} from "@web/views/form/form_controller";
import {FormViewDialog} from "@web/views/view_dialogs/form_view_dialog";
import {Many2OneAvatarField} from "@web/views/fields/many2one_avatar/many2one_avatar_field";
import {Many2OneBarcodeField} from "@web/views/fields/many2one_barcode/many2one_barcode_field";
import {Many2OneField} from "@web/views/fields/many2one/many2one_field";
import {ReferenceField} from "@web/views/fields/reference/reference_field";
import {X2ManyField} from "@web/views/fields/x2many/x2many_field";
import {isX2Many} from "@web/views/utils";
import {is_option_set} from "@web_m2x_options/components/relational_utils.esm";
import {patch} from "@web/core/utils/patch";
import {sprintf} from "@web/core/utils/strings";
import {useService} from "@web/core/utils/hooks";
const {Component, onWillStart} = owl;
/**
* Patch Many2ManyTagsField
**/
patch(Many2ManyTagsField.prototype, "web_m2x_options.Many2ManyTagsField", {
setup() {
this._super(...arguments);
this.actionService = useService("action");
},
/**
* @override
*/
getTagProps(record) {
const props = this._super(...arguments);
props.onClick = (ev) => this.onMany2ManyBadgeClick(ev, record);
return props;
},
async onMany2ManyBadgeClick(event, record) {
var self = this;
if (self.props.open) {
var context = self.context;
var id = record.data.id;
if (self.props.readonly) {
event.preventDefault();
event.stopPropagation();
const action = await self.orm.call(
self.props.relation,
"get_formview_action",
[[id]],
{context: context}
);
self.actionService.doAction(action);
} else {
const view_id = await self.orm.call(
self.props.relation,
"get_formview_id",
[[id]],
{context: context}
);
const write_access = await self.orm.call(
self.props.relation,
"check_access_rights",
[],
{operation: "write", raise_exception: false}
);
var can_write = self.props.canWrite;
self.dialog.add(FormViewDialog, {
resModel: self.props.relation,
resId: id,
context: context,
title: self.env._t("Open: ") + self.string,
viewId: view_id,
mode: !can_write || !write_access ? "readonly" : "edit",
onRecordSaved: () => self.props.value.model.load(),
});
}
}
},
});
Many2ManyTagsField.props = {
...Many2ManyTagsField.props,
open: {type: Boolean, optional: true},
canWrite: {type: Boolean, optional: true},
nodeOptions: {type: Object, optional: true},
};
const Many2ManyTagsFieldExtractProps = Many2ManyTagsField.extractProps;
Many2ManyTagsField.extractProps = ({attrs, field}) => {
const canOpen = Boolean(attrs.options.open);
const canWrite = attrs.can_write && Boolean(JSON.parse(attrs.can_write));
return Object.assign(Many2ManyTagsFieldExtractProps({attrs, field}), {
open: canOpen,
canWrite: canWrite,
nodeOptions: attrs.options,
});
};
/**
* Many2ManyTagsFieldColorEditable
**/
patch(
Many2ManyTagsFieldColorEditable.prototype,
"web_m2x_options.Many2ManyTagsFieldColorEditable",
{
async onBadgeClick(event, record) {
if (this.props.canEditColor && !this.props.open) {
this._super(...arguments);
}
if (this.props.open) {
Many2ManyTagsField.prototype.onMany2ManyBadgeClick.bind(this)(
event,
record
);
}
},
}
);
Many2ManyTagsFieldColorEditable.props = {
...Many2ManyTagsFieldColorEditable.props,
open: {type: Boolean, optional: true},
canWrite: {type: Boolean, optional: true},
nodeOptions: {type: Object, optional: true},
};
/**
* CreateConfirmationDialog
* New customized component for Many2One Field
**/
class CreateConfirmationDialog extends Component {
get title() {
return sprintf(this.env._t("New: %s"), this.props.name);
}
async onCreate() {
await this.props.create();
this.props.close();
}
async onCreateEdit() {
await this.props.createEdit();
this.props.close();
}
}
CreateConfirmationDialog.components = {Dialog};
CreateConfirmationDialog.template =
"web_m2x_options.Many2OneField.CreateConfirmationDialog";
/**
* Many2OneField
**/
patch(Many2OneField.prototype, "web_m2x_options.Many2OneField", {
setup() {
this._super(...arguments);
const ormService = useService("orm");
this.user_context = Component.env.session.user_context;
onWillStart(async () => {
this.ir_options = await ormService.call(
"ir.config_parameter",
"get_web_m2x_options",
[],
{context: this.user_context}
);
});
},
/**
* @override
*/
get Many2XAutocompleteProps() {
const props = this._super(...arguments);
return {
...props,
searchLimit: this.props.searchLimit,
searchMore: this.props.searchMore,
canCreate: this.props.canCreate,
nodeOptions: this.props.nodeOptions,
};
},
async openConfirmationDialog(request) {
var m2o_dialog_opt =
is_option_set(this.props.nodeOptions.m2o_dialog) ||
(_.isUndefined(this.props.nodeOptions.m2o_dialog) &&
is_option_set(this.ir_options["web_m2x_options.m2o_dialog"])) ||
(_.isUndefined(this.props.nodeOptions.m2o_dialog) &&
_.isUndefined(this.ir_options["web_m2x_options.m2o_dialog"]));
if (this.props.canCreate && this.state.isFloating && m2o_dialog_opt) {
return new Promise((resolve, reject) => {
this.addDialog(CreateConfirmationDialog, {
value: request,
name: this.props.string,
create: async () => {
try {
await this.quickCreate(request);
resolve();
} catch (e) {
reject(e);
}
},
createEdit: async () => {
try {
await this.quickCreate(request);
await this.props.record.model.load();
this.openMany2X({
resId: this.props.value[0],
context: this.user_context,
});
resolve();
} catch (e) {
reject(e);
}
},
});
});
}
},
});
const Many2OneFieldExtractProps = Many2OneField.extractProps;
Many2OneField.extractProps = ({attrs, field}) => {
return Object.assign(Many2OneFieldExtractProps({attrs, field}), {
searchLimit: attrs.options.limit,
searchMore: attrs.options.search_more,
nodeOptions: attrs.options,
});
};
Many2OneField.props = {
...Many2OneField.props,
searchMore: {type: Boolean, optional: true},
nodeOptions: {type: Object, optional: true},
};
/**
* FIXME: find better way to extend props in Many2OneField
* Override ReferenceField
* Since extracted/added props: nodeOptions and searchMore into Many2OneField props
* and this component inherited props from Many2OneField
* So, must override props here to avoid constraint validateProps (props schema) in owl core
*/
ReferenceField.props = {
...ReferenceField.props,
searchMore: {type: Boolean, optional: true},
nodeOptions: {type: Object, optional: true},
};
/**
* FIXME: find better way to extend props in Many2OneField
* Override Many2OneBarcodeField
* Since extracted/added props: nodeOptions and searchMore into Many2OneField props
* and this component inherited props from Many2OneField
* So, must override props here to avoid constraint validateProps (props schema) in owl core
*/
Many2OneBarcodeField.props = {
...Many2OneBarcodeField.props,
searchMore: {type: Boolean, optional: true},
nodeOptions: {type: Object, optional: true},
};
/**
* FIXME: find better way to extend props in Many2OneField
* Override Many2OneAvatarField
* Since extracted/added props: nodeOptions and searchMore into Many2OneField props
* and this component inherited props from Many2OneField
* So, must override props here to avoid constraint validateProps (props schema) in owl core
*/
Many2OneAvatarField.props = {
...Many2OneAvatarField.props,
searchMore: {type: Boolean, optional: true},
nodeOptions: {type: Object, optional: true},
};
/**
* FIXME: find better way to extend props in Many2OneField
* Override mailing_m2o_filter
* Since extracted/added props: nodeOptions and searchMore into Many2OneField props
* and this component inherited props from Many2OneField
* So, must override props here to avoid constraint validateProps (props schema) in owl core
* This component is in module mass_mailing as optional module,
* So need to import dynamic way
*/
try {
(async () => {
// Make sure component mailing_m2o_filter in mass mailing module loaded
const installed_mass_mailing = await odoo.ready(
"@mass_mailing/js/mailing_m2o_filter"
);
if (installed_mass_mailing) {
var FieldMany2OneMailingFilter = await odoo.runtimeImport(
"@mass_mailing/js/mailing_m2o_filter"
);
FieldMany2OneMailingFilter.props = {
...FieldMany2OneMailingFilter.props,
searchMore: {type: Boolean, optional: true},
nodeOptions: {type: Object, optional: true},
};
}
})();
} catch {
console.log(
"Ignore overriding props of component mailing_m2o_filter since the module is not installed"
);
}
/**
* X2ManyField
**/
patch(X2ManyField.prototype, "web_m2x_options.X2ManyField", {
/**
* @override
*/
async openRecord(record) {
var self = this;
var open = this.props.open;
if (open && self.props.readonly) {
var res_id = record.data.id;
const action = await self.env.model.orm.call(
self.props.value.resModel,
"get_formview_action",
[[res_id]]
);
return self.env.model.actionService.doAction(action);
}
return this._super.apply(this, arguments);
},
});
const X2ManyFieldExtractProps = X2ManyField.extractProps;
X2ManyField.extractProps = ({attrs}) => {
const canOpen = Boolean(attrs.options.open);
return Object.assign(X2ManyFieldExtractProps({attrs}), {
open: canOpen,
});
};
X2ManyField.props = {
...X2ManyField.props,
open: {type: Boolean, optional: true},
};
/**
* FormController
**/
patch(FormController.prototype, "web_m2x_options.FormController", {
/**
* @override
*/
setup() {
var self = this;
this._super(...arguments);
/** Due to problem of 2 onWillStart in native web core
* (see: https://github.com/odoo/odoo/blob/16.0/addons/web/static/src/views/model.js#L142)
* do the trick to override beforeLoadResolver here to customize viewLimit
*/
this.superBeforeLoadResolver = this.beforeLoadResolver;
this.beforeLoadResolver = async () => {
await self._setSubViewLimit();
self.superBeforeLoadResolver();
};
},
/**
* @override
* add more method to add subview limit on formview
*/
async _setSubViewLimit() {
const ir_options = await this.model.orm.call(
"ir.config_parameter",
"get_web_m2x_options",
[],
{context: this.user_context}
);
const activeFields = this.archInfo.activeFields,
fields = this.props.fields,
isSmall = this.user;
var limit = ir_options["web_m2x_options.field_limit_entries"];
if (!_.isUndefined(limit)) {
limit = parseInt(limit, 10);
}
for (const fieldName in activeFields) {
const field = fields[fieldName];
if (!isX2Many(field)) {
// What follows only concerns x2many fields
continue;
}
const fieldInfo = activeFields[fieldName];
if (fieldInfo.modifiers.invisible === true) {
// No need to fetch the sub view if the field is always invisible
continue;
}
if (!fieldInfo.FieldComponent.useSubView) {
// The FieldComponent used to render the field doesn't need a sub view
continue;
}
let viewType = fieldInfo.viewMode || "list,kanban";
viewType = viewType.replace("tree", "list");
if (viewType.includes(",")) {
viewType = isSmall ? "kanban" : "list";
}
fieldInfo.viewMode = viewType;
if (fieldInfo.views[viewType] && limit) {
fieldInfo.views[viewType].limit = limit;
}
}
},
});

View File

@ -0,0 +1,231 @@
/** @odoo-module **/
import {Many2XAutocomplete} from "@web/views/fields/relational_utils";
import {patch} from "@web/core/utils/patch";
import {sprintf} from "@web/core/utils/strings";
import {useService} from "@web/core/utils/hooks";
const {Component, onWillStart} = owl;
export function is_option_set(option) {
if (_.isUndefined(option)) return false;
if (typeof option === "string") return option === "true" || option === "True";
if (typeof option === "boolean") return option;
return false;
}
patch(Many2XAutocomplete.prototype, "web_m2x_options.Many2XAutocomplete", {
setup() {
this._super(...arguments);
const ormService = useService("orm");
this.user_context = Component.env.session.user_context;
onWillStart(async () => {
this.ir_options = await ormService.call(
"ir.config_parameter",
"get_web_m2x_options",
[],
{context: this.user_context}
);
});
},
async loadOptionsSource(request) {
if (this.lastProm) {
this.lastProm.abort(false);
}
// Add options limit used to change number of selections record
// returned.
if (!_.isUndefined(this.ir_options["web_m2x_options.limit"])) {
this.props.searchLimit = parseInt(
this.ir_options["web_m2x_options.limit"],
10
);
this.limit = this.props.searchLimit;
}
if (typeof this.props.nodeOptions.limit === "number") {
this.props.searchLimit = this.props.nodeOptions.limit;
this.limit = this.props.searchLimit;
}
// Add options field_color and colors to color item(s) depending on field_color value
this.field_color = this.props.nodeOptions.field_color;
this.colors = this.props.nodeOptions.colors;
this.lastProm = this.orm.call(this.props.resModel, "name_search", [], {
name: request,
operator: "ilike",
args: this.props.getDomain(),
limit: this.props.searchLimit + 1,
context: this.props.context,
});
const records = await this.lastProm;
var options = records.map((result) => ({
value: result[0],
id: result[0],
label: result[1].split("\n")[0],
}));
// Limit results if there is a custom limit options
if (this.limit) {
options = options.slice(0, this.props.searchLimit);
}
// Search result value colors
if (this.colors && this.field_color) {
var value_ids = options.map((result) => result.value);
const objects = await this.orm.call(
this.props.resModel,
"search_read",
[],
{
domain: [["id", "in", value_ids]],
fields: [this.field_color],
}
);
for (var index in objects) {
for (var index_value in options) {
if (options[index_value].id === objects[index].id) {
// Find value in values by comparing ids
var option = options[index_value];
// Find color with field value as key
var color =
this.colors[objects[index][this.field_color]] || "black";
option.style = "color:" + color;
break;
}
}
}
}
// Quick create
// Note: Create should be before `search_more` (reserve native order)
// One more reason: when calling `onInputBlur`, native select the first option (activeSourceOption)
// which triggers m2o_dialog if m2o_dialog=true
var create_enabled =
this.props.quickCreate && !this.props.nodeOptions.no_create;
var raw_result = _.map(records, function (x) {
return x[1];
});
var quick_create = is_option_set(this.props.nodeOptions.create),
quick_create_undef = _.isUndefined(this.props.nodeOptions.create),
m2x_create_undef = _.isUndefined(this.ir_options["web_m2x_options.create"]),
m2x_create = is_option_set(this.ir_options["web_m2x_options.create"]);
var show_create =
(!this.props.nodeOptions && (m2x_create_undef || m2x_create)) ||
(this.props.nodeOptions &&
(quick_create ||
(quick_create_undef && (m2x_create_undef || m2x_create))));
if (
create_enabled &&
!this.props.nodeOptions.no_quick_create &&
request.length > 0 &&
!_.contains(raw_result, request) &&
show_create
) {
options.push({
label: sprintf(this.env._t(`Create "%s"`), request),
classList: "o_m2o_dropdown_option o_m2o_dropdown_option_create",
action: async (params) => {
try {
await this.props.quickCreate(request, params);
} catch {
const context = this.getCreationContext(request);
return this.openMany2X({context});
}
},
});
}
// Search more...
// Resolution order:
// 1- check if "search_more" is set locally in node's options
// 2- if set locally, apply its value
// 3- if not set locally, check if it's set globally via ir.config_parameter
// 4- if set globally, apply its value
// 5- if not set globally either, check if returned values are more than node's limit
var search_more = false;
if (!_.isUndefined(this.props.nodeOptions.search_more)) {
search_more = is_option_set(this.props.nodeOptions.search_more);
} else if (!_.isUndefined(this.ir_options["web_m2x_options.search_more"])) {
search_more = is_option_set(this.ir_options["web_m2x_options.search_more"]);
} else {
search_more =
!this.props.noSearchMore && this.props.searchLimit < records.length;
}
if (search_more) {
options.push({
label: this.env._t("Search More..."),
action: this.onSearchMore.bind(this, request),
classList: "o_m2o_dropdown_option o_m2o_dropdown_option_search_more",
});
}
// Create and Edit
const canCreateEdit =
"createEdit" in this.activeActions
? this.activeActions.createEdit
: this.activeActions.create;
if (
!request.length &&
!this.props.value &&
(this.props.quickCreate || canCreateEdit)
) {
options.push({
label: this.env._t("Start typing..."),
classList: "o_m2o_start_typing",
unselectable: true,
});
}
// Create and edit ...
var create_edit =
is_option_set(this.props.nodeOptions.create) ||
is_option_set(this.props.nodeOptions.create_edit),
create_edit_undef =
_.isUndefined(this.props.nodeOptions.create) &&
_.isUndefined(this.props.nodeOptions.create_edit),
m2x_create_edit_undef = _.isUndefined(
this.ir_options["web_m2x_options.create_edit"]
),
m2x_create_edit = is_option_set(
this.ir_options["web_m2x_options.create_edit"]
);
var show_create_edit =
(!this.props.nodeOptions && (m2x_create_edit_undef || m2x_create_edit)) ||
(this.props.nodeOptions &&
(create_edit ||
(create_edit_undef && (m2x_create_edit_undef || m2x_create_edit))));
if (
create_enabled &&
!this.props.nodeOptions.no_create_edit &&
show_create_edit &&
request.length &&
canCreateEdit
) {
const context = this.getCreationContext(request);
options.push({
label: this.env._t("Create and edit..."),
classList: "o_m2o_dropdown_option o_m2o_dropdown_option_create_edit",
action: () => this.openMany2X({context}),
});
}
// No records
if (!records.length && !this.activeActions.create) {
options.push({
label: this.env._t("No records"),
classList: "o_m2o_no_result",
unselectable: true,
});
}
return options;
},
});
Many2XAutocomplete.defaultProps = {
...Many2XAutocomplete.defaultProps,
nodeOptions: {},
};

View File

@ -1,489 +0,0 @@
/* Copyright 2016 0k.io,ACSONE SA/NV
* * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
odoo.define("web_m2x_options.web_m2x_options", function (require) {
"use strict";
var core = require("web.core"),
data = require("web.data"),
Dialog = require("web.Dialog"),
FormView = require("web.FormView"),
view_dialogs = require("web.view_dialogs"),
relational_fields = require("web.relational_fields"),
ir_options = require("web_m2x_options.ir_options");
var _t = core._t,
FieldMany2ManyTags = relational_fields.FieldMany2ManyTags,
FieldMany2One = relational_fields.FieldMany2One,
FieldOne2Many = relational_fields.FieldOne2Many,
FormFieldMany2ManyTags = relational_fields.FormFieldMany2ManyTags;
function is_option_set(option) {
if (_.isUndefined(option)) return false;
if (typeof option === "string") return option === "true" || option === "True";
if (typeof option === "boolean") return option;
return false;
}
var M2ODialog = Dialog.extend({
template: "M2ODialog",
init: function (parent, name, value) {
this.name = name;
this.value = value;
this._super(parent, {
title: _.str.sprintf(_t("Create a %s"), this.name),
size: "medium",
buttons: [
{
text: _t("Create"),
classes: "btn-primary",
click: function () {
if (this.$("input").val()) {
this.trigger_up("quick_create", {
value: this.$("input").val(),
});
this.close(true);
} else {
this.$("input").focus();
}
},
},
{
text: _t("Create and edit"),
classes: "btn-primary",
close: true,
click: function () {
this.trigger_up("search_create_popup", {
view_type: "form",
value: this.$("input").val(),
});
},
},
{
text: _t("Cancel"),
close: true,
},
],
});
},
start: function () {
this.$("p").text(
_.str.sprintf(
_t(
"You are creating a new %s, are you sure it does not exist yet?"
),
this.name
)
);
this.$("input").val(this.value);
},
/**
* @override
* @param {Boolean} isSet
*/
close: function (isSet) {
this.isSet = isSet;
this._super.apply(this, arguments);
},
/**
* @override
*/
destroy: function () {
if (!this.isSet) {
this.trigger_up("closed_unset");
}
this._super.apply(this, arguments);
},
});
FieldMany2One.include({
_onInputFocusout: function () {
var m2o_dialog_opt =
is_option_set(this.nodeOptions.m2o_dialog) ||
(_.isUndefined(this.nodeOptions.m2o_dialog) &&
is_option_set(ir_options["web_m2x_options.m2o_dialog"])) ||
(_.isUndefined(this.nodeOptions.m2o_dialog) &&
_.isUndefined(ir_options["web_m2x_options.m2o_dialog"]));
if (this.can_create && this.floating && m2o_dialog_opt) {
new M2ODialog(this, this.string, this.$input.val()).open();
}
},
_search: function (search_val) {
var self = this;
var def = new Promise((resolve) => {
// Add options limit used to change number of selections record
// returned.
if (!_.isUndefined(ir_options["web_m2x_options.limit"])) {
this.limit = parseInt(ir_options["web_m2x_options.limit"], 10);
}
if (typeof self.nodeOptions.limit === "number") {
self.limit = self.nodeOptions.limit;
}
// Add options field_color and colors to color item(s) depending on field_color value
self.field_color = self.nodeOptions.field_color;
self.colors = self.nodeOptions.colors;
var context = self.record.getContext(self.recordParams);
var domain = self.record.getDomain(self.recordParams);
var blacklisted_ids = self._getSearchBlacklist();
if (blacklisted_ids.length > 0) {
domain.push(["id", "not in", blacklisted_ids]);
}
self._rpc({
model: self.field.relation,
method: "name_search",
kwargs: {
name: search_val,
args: domain,
operator: "ilike",
limit: self.limit + 1,
context: context,
},
}).then((result) => {
// Possible selections for the m2o
var values = _.map(result, (x) => {
x[1] = self._getDisplayName(x[1]);
return {
label:
_.str.escapeHTML(x[1].trim()) || data.noDisplayContent,
value: x[1],
name: x[1],
id: x[0],
};
});
// Search result value colors
if (self.colors && self.field_color) {
var value_ids = [];
for (var val_index in values) {
value_ids.push(values[val_index].id);
}
self._rpc({
model: self.field.relation,
method: "search_read",
fields: [self.field_color],
domain: [["id", "in", value_ids]],
}).then((objects) => {
for (var index in objects) {
for (var index_value in values) {
if (values[index_value].id === objects[index].id) {
// Find value in values by comparing ids
var value = values[index_value];
// Find color with field value as key
var color =
self.colors[
objects[index][self.field_color]
] || "black";
value.label =
'<span style="color:' +
color +
'">' +
value.label +
"</span>";
break;
}
}
}
resolve(values);
});
}
// Search more...
// Resolution order:
// 1- check if "search_more" is set locally in node's options
// 2- if set locally, apply its value
// 3- if not set locally, check if it's set globally via ir.config_parameter
// 4- if set globally, apply its value
// 5- if not set globally either, check if returned values are more than node's limit
if (!_.isUndefined(self.nodeOptions.search_more)) {
var search_more = is_option_set(self.nodeOptions.search_more);
} else if (
!_.isUndefined(ir_options["web_m2x_options.search_more"])
) {
var search_more = is_option_set(
ir_options["web_m2x_options.search_more"]
);
} else {
var search_more = values.length > self.limit;
}
if (search_more) {
values = values.slice(0, self.limit);
values.push({
label: _t("Search More..."),
action: function () {
var prom = [];
if (search_val !== "") {
prom = self._rpc({
model: self.field.relation,
method: "name_search",
kwargs: {
name: search_val,
args: domain,
operator: "ilike",
limit: self.SEARCH_MORE_LIMIT,
context: context,
},
});
}
Promise.resolve(prom).then(function (results) {
var dynamicFilters = [];
if (results) {
var ids = _.map(results, function (x) {
return x[0];
});
if (search_val) {
dynamicFilters = [
{
description: _.str.sprintf(
_t("Quick search: %s"),
search_val
),
domain: [["id", "in", ids]],
},
];
} else {
dynamicFilters = [];
}
}
self._searchCreatePopup(
"search",
false,
{},
dynamicFilters
);
});
},
classname: "o_m2o_dropdown_option",
});
}
var create_enabled = self.can_create && !self.nodeOptions.no_create;
// Quick create
var raw_result = _.map(result, function (x) {
return x[1];
});
var quick_create = is_option_set(self.nodeOptions.create),
quick_create_undef = _.isUndefined(self.nodeOptions.create),
m2x_create_undef = _.isUndefined(
ir_options["web_m2x_options.create"]
),
m2x_create = is_option_set(
ir_options["web_m2x_options.create"]
);
var show_create =
(!self.nodeOptions && (m2x_create_undef || m2x_create)) ||
(self.nodeOptions &&
(quick_create ||
(quick_create_undef &&
(m2x_create_undef || m2x_create))));
if (
create_enabled &&
!self.nodeOptions.no_quick_create &&
search_val.length > 0 &&
!_.contains(raw_result, search_val) &&
show_create
) {
values.push({
label: _.str.sprintf(
_t('Create "<strong>%s</strong>"'),
$("<span />").text(search_val).html()
),
action: self._quickCreate.bind(self, search_val),
classname: "o_m2o_dropdown_option",
});
}
// Create and edit ...
var create_edit =
is_option_set(self.nodeOptions.create) ||
is_option_set(self.nodeOptions.create_edit),
create_edit_undef =
_.isUndefined(self.nodeOptions.create) &&
_.isUndefined(self.nodeOptions.create_edit),
m2x_create_edit_undef = _.isUndefined(
ir_options["web_m2x_options.create_edit"]
),
m2x_create_edit = is_option_set(
ir_options["web_m2x_options.create_edit"]
);
var show_create_edit =
(!self.nodeOptions &&
(m2x_create_edit_undef || m2x_create_edit)) ||
(self.nodeOptions &&
(create_edit ||
(create_edit_undef &&
(m2x_create_edit_undef || m2x_create_edit))));
if (
create_enabled &&
!self.nodeOptions.no_create_edit &&
show_create_edit
) {
var createAndEditAction = function () {
// Clear the value in case the user clicks on discard
self.$("input").val("");
return self._searchCreatePopup(
"form",
false,
self._createContext(search_val)
);
};
values.push({
label: _t("Create and Edit..."),
action: createAndEditAction,
classname: "o_m2o_dropdown_option",
});
} else if (values.length === 0) {
values.push({
label: _t("No results to show..."),
});
}
// Check if colors specified to wait for RPC
if (!(self.field_color && self.colors)) {
resolve(values);
}
});
});
this.orderer.add(def);
// Add options limit used to change number of selections record
// returned.
if (!_.isUndefined(ir_options["web_m2x_options.limit"])) {
this.limit = parseInt(ir_options["web_m2x_options.limit"], 10);
}
if (typeof this.nodeOptions.limit === "number") {
this.limit = this.nodeOptions.limit;
}
return def;
},
});
FieldMany2ManyTags.include({
events: _.extend({}, FieldMany2ManyTags.prototype.events, {
"click .badge": "_onOpenBadge",
}),
_onDeleteTag: function (event) {
var result = this._super.apply(this, arguments);
event.stopPropagation();
return result;
},
_onOpenBadge: function (event) {
var self = this;
var open = self.nodeOptions && is_option_set(self.nodeOptions.open);
if (open) {
var context = self.record.getContext(self.recordParams);
var id = parseInt($(event.currentTarget).data("id"), 10);
if (self.mode === "readonly") {
event.preventDefault();
event.stopPropagation();
self._rpc({
model: self.field.relation,
method: "get_formview_action",
args: [[id]],
context: context,
}).then(function (action) {
self.trigger_up("do_action", {action: action});
});
} else {
$.when(
self._rpc({
model: self.field.relation,
method: "get_formview_id",
args: [[id]],
context: context,
}),
self._rpc({
model: self.field.relation,
method: "check_access_rights",
kwargs: {operation: "write", raise_exception: false},
})
).then(function (view_id, write_access) {
var can_write =
"can_write" in self.attrs
? JSON.parse(self.attrs.can_write)
: true;
new view_dialogs.FormViewDialog(self, {
res_model: self.field.relation,
res_id: id,
context: context,
title: _t("Open: ") + self.string,
view_id: view_id,
readonly: !can_write || !write_access,
on_saved: function (record, changed) {
if (changed) {
self._setValue(self.value.data, {
forceChange: true,
});
self.trigger_up("reload", {db_id: self.value.id});
}
},
}).open();
});
}
}
},
});
FieldOne2Many.include({
_onOpenRecord: function (ev) {
var self = this;
var open = this.nodeOptions.open;
if (open && self.mode === "readonly") {
ev.stopPropagation();
var id = ev.data.id;
var res_id = self.record.data[self.name].data.filter(
(line) => line.id === id
)[0].res_id;
self._rpc({
model: self.field.relation,
method: "get_formview_action",
args: [[res_id]],
}).then(function (action) {
return self.do_action(action);
});
} else {
return this._super.apply(this, arguments);
}
},
});
FormFieldMany2ManyTags.include({
events: _.extend({}, FormFieldMany2ManyTags.prototype.events, {
"click .badge": "_onOpenBadge",
}),
_onOpenBadge: function (event) {
var open = is_option_set(this.nodeOptions.open);
var no_color_picker = is_option_set(this.nodeOptions.no_color_picker);
this._super.apply(this, arguments);
if (!open && !no_color_picker) {
this._onOpenColorPicker(event);
} else {
event.preventDefault();
event.stopPropagation();
}
},
});
// Extending class to allow change the limit of o2m registry entries using the
// system parameter "web_m2x_options.field_limit_entries".
FormView.include({
_setSubViewLimit: function (attrs) {
this._super(attrs);
var limit = ir_options["web_m2x_options.field_limit_entries"];
if (!_.isUndefined(limit)) {
attrs.limit = parseInt(limit);
}
},
});
});

View File

@ -1,12 +0,0 @@
/* Copyright 2020 Tecnativa - Carlos Roca
* * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
odoo.define("web_m2x_options.ir_options", function (require) {
"use strict";
var rpc = require("web.rpc");
return rpc.query({
model: "ir.config_parameter",
method: "get_web_m2x_options",
});
});

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2017 Jairo Llopis <jairo.llopis@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<templates xml:space="preserve">
<t t-extend="FieldMany2One">
<t t-jquery=".o_external_button" t-operation="attributes">
<attribute name="t-if">
!(widget.nodeOptions.no_open || widget.nodeOptions.no_open_edit || widget.noOpen)
</attribute>
</t>
</t>
</templates>