3
0
Fork 0

[16.0][MIG] web_m2x_options: Migration to 16.0

16.0
Diep Huu Hoang 2023-03-28 12:48:05 +07:00
parent 1ddab4db69
commit b803f95772
12 changed files with 710 additions and 523 deletions

View File

@ -0,0 +1 @@
../../../../web_m2x_options

View File

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

View File

@ -23,7 +23,7 @@ web_m2x_options
:target: https://runbot.odoo-community.org/runbot/162/15.0 :target: https://runbot.odoo-community.org/runbot/162/15.0
:alt: Try me on Runbot :alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5| |badge1| |badge2| |badge3| |badge4| |badge5|
This modules modifies "many2one" and "many2manytags" form widgets so as to add some new display This modules modifies "many2one" and "many2manytags" form widgets so as to add some new display
control options. control options.
@ -85,10 +85,11 @@ in the field's options dict
Makes many2many_tags and one2many rows buttons that open the linked resource Makes many2many_tags and one2many rows buttons that open the linked resource
``no_color_picker`` *boolean* (Default: ``False``) ``no_color_picker`` *boolean* (Default: ``False``) (obselete: use native option: ``no_edit_color`` instead)
Deactivates the color picker on many2many_tags buttons to do nothing (ignored if open is set) Deactivates the color picker on many2many_tags buttons to do nothing (ignored if open is set)
ir.config_parameter options ir.config_parameter options
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

@ -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>