summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
authorAaron Bohy <aab@odoo.com>2020-04-29 07:21:38 +0000
committerAaron Bohy <aab@odoo.com>2020-05-05 10:05:12 +0000
commitdf4693e7ffe8082335355b9a7c573d1038547f89 (patch)
treed611e954c8b1961107f5e644545d6429348c106d /addons/web/static
parent4f8860e5038711c0da5b26584c4b72663908ddbc (diff)
[IMP] web: add Many2OneAvatar field widget
In readonly, this widget displays the image of the related record next to its display_name. In edit, it behaves exactly like the regular many2one. Part of task 2195254
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/src/js/fields/field_registry.js1
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js41
-rw-r--r--addons/web/static/src/scss/fields.scss11
-rw-r--r--addons/web/static/src/scss/kanban_view.scss6
-rw-r--r--addons/web/static/src/xml/base.xml6
-rw-r--r--addons/web/static/tests/fields/relational_fields/field_many2one_tests.js102
6 files changed, 166 insertions, 1 deletions
diff --git a/addons/web/static/src/js/fields/field_registry.js b/addons/web/static/src/js/fields/field_registry.js
index 3cadf6cf6fa..925b405051e 100644
--- a/addons/web/static/src/js/fields/field_registry.js
+++ b/addons/web/static/src/js/fields/field_registry.js
@@ -84,6 +84,7 @@ registry
.add('many2one_barcode', relational_fields.Many2oneBarcode)
.add('list.many2one', relational_fields.ListFieldMany2One)
.add('kanban.many2one', relational_fields.KanbanFieldMany2One)
+ .add('many2one_avatar', relational_fields.Many2OneAvatar)
.add('many2many', relational_fields.FieldMany2Many)
.add('many2many_binary', relational_fields.FieldMany2ManyBinaryMultiFiles)
.add('many2many_tags', relational_fields.FieldMany2ManyTags)
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index 72476d74c92..b5b6d7252e5 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -959,6 +959,44 @@ var KanbanFieldMany2One = AbstractField.extend({
},
});
+/**
+ * Widget Many2OneAvatar is only supported on many2one fields pointing to a
+ * model which inherits from 'image.mixin'. In readonly, it displays the
+ * record's image next to the display_name. In edit, it behaves exactly like a
+ * regular many2one widget.
+ */
+const Many2OneAvatar = FieldMany2One.extend({
+ _template: 'web.Many2OneAvatar',
+
+ init() {
+ this._super.apply(this, arguments);
+ if (this.mode === 'readonly') {
+ this.template = null;
+ this.tagName = 'div';
+ this.className = 'o_field_many2one_avatar';
+ // disable the redirection to the related record on click, in readonly
+ this.noOpen = true;
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _renderReadonly() {
+ this.$el.empty();
+ if (this.value) {
+ this.$el.html(qweb.render(this._template, {
+ url: `/web/image/${this.field.relation}/${this.value.res_id}/image_128`,
+ value: this.m2o_value,
+ }));
+ }
+ },
+});
+
//------------------------------------------------------------------------------
// X2Many widgets
//------------------------------------------------------------------------------
@@ -3308,8 +3346,9 @@ return {
Many2oneBarcode: Many2oneBarcode,
KanbanFieldMany2One: KanbanFieldMany2One,
ListFieldMany2One: ListFieldMany2One,
+ Many2OneAvatar: Many2OneAvatar,
- FieldX2Many : FieldX2Many,
+ FieldX2Many: FieldX2Many,
FieldOne2Many: FieldOne2Many,
FieldMany2Many: FieldMany2Many,
diff --git a/addons/web/static/src/scss/fields.scss b/addons/web/static/src/scss/fields.scss
index f0d15adbd52..8152389c32c 100644
--- a/addons/web/static/src/scss/fields.scss
+++ b/addons/web/static/src/scss/fields.scss
@@ -83,6 +83,17 @@
}
}
+ // Many2OneAvatar
+ &.o_field_many2one_avatar {
+ > img.o_m2o_avatar {
+ border-radius: 50%;
+ width: 19px;
+ height: 19px;
+ object-fit: cover;
+ margin-right: 4px;
+ }
+ }
+
// Many2many tags
&.o_field_many2manytags {
flex-flow: row wrap;
diff --git a/addons/web/static/src/scss/kanban_view.scss b/addons/web/static/src/scss/kanban_view.scss
index 29cfade3857..d24a928536e 100644
--- a/addons/web/static/src/scss/kanban_view.scss
+++ b/addons/web/static/src/scss/kanban_view.scss
@@ -280,6 +280,12 @@
}
}
+ .o_field_many2one_avatar {
+ img.o_m2o_avatar {
+ margin-right: 0;
+ }
+ }
+
// Commonly used to place an image beside the text
// (e.g. Fleet, Employees, ...)
.o_kanban_image {
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index b06b8402620..3abde74ac51 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -1346,6 +1346,12 @@
<button type="button" t-if="!widget.noOpen" class="fa fa-external-link btn btn-secondary o_external_button" tabindex="-1" draggable="false" aria-label="External link" title="External link"/>
</div>
</t>
+
+<t t-name="web.Many2OneAvatar">
+ <img t-att-src="url" t-att-alt="value" class="o_m2o_avatar"/>
+ <span t-esc="value"/>
+</t>
+
<t t-name="FieldReference" t-extend="FieldMany2One">
<t t-jquery=".o_input_dropdown" t-operation="before">
<select t-att-class="'o_input o_field_widget' + (widget.nodeOptions.hide_model and ' d-none' or '')">
diff --git a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
index 04c934290a1..c2c4942381a 100644
--- a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
+++ b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
@@ -3204,6 +3204,108 @@ QUnit.module('fields', {}, function () {
"should be 1 column after the value change");
form.destroy();
});
+
+ QUnit.module('Many2OneAvatar');
+
+ QUnit.test('many2one_avatar widget in form view', async function (assert) {
+ assert.expect(10);
+
+ const form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form><field name="user_id" widget="many2one_avatar"/></form>',
+ res_id: 1,
+ });
+
+ assert.hasClass(form.$('.o_form_view'), 'o_form_readonly');
+ assert.strictEqual(form.$('.o_field_widget[name=user_id]').text().trim(), 'Aline');
+ assert.containsOnce(form, 'img.o_m2o_avatar[data-src="/web/image/user/17/image_128"]');
+
+ await testUtils.form.clickEdit(form);
+
+ assert.hasClass(form.$('.o_form_view'), 'o_form_editable');
+ assert.containsOnce(form, '.o_input_dropdown');
+ assert.strictEqual(form.$('.o_input_dropdown input').val(), 'Aline');
+ assert.containsOnce(form, '.o_external_button');
+
+ await testUtils.fields.many2one.clickOpenDropdown("user_id");
+ await testUtils.fields.many2one.clickItem("user_id", "Christine");
+ await testUtils.form.clickSave(form);
+
+ assert.hasClass(form.$('.o_form_view'), 'o_form_readonly');
+ assert.strictEqual(form.$('.o_field_widget[name=user_id]').text().trim(), 'Christine');
+ assert.containsOnce(form, 'img.o_m2o_avatar[data-src="/web/image/user/19/image_128"]');
+
+ form.destroy();
+ });
+
+ QUnit.test('many2one_avatar widget in form view, with onchange', async function (assert) {
+ assert.expect(7);
+
+ this.data.partner.onchanges = {
+ int_field: function (obj) {
+ if (obj.int_field === 1) {
+ obj.user_id = [19, 'Christine'];
+ } else if (obj.int_field === 2) {
+ obj.user_id = false;
+ } else {
+ obj.user_id = [17, 'Aline']; // default value
+ }
+ },
+ };
+ const form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: `
+ <form>
+ <field name="int_field"/>
+ <field name="user_id" widget="many2one_avatar" readonly="1"/>
+ </form>`,
+ });
+
+ assert.hasClass(form.$('.o_form_view'), 'o_form_editable');
+ assert.strictEqual(form.$('.o_field_widget[name=user_id]').text().trim(), 'Aline');
+ assert.containsOnce(form, 'img.o_m2o_avatar[data-src="/web/image/user/17/image_128"]');
+
+ await testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 1);
+
+ assert.strictEqual(form.$('.o_field_widget[name=user_id]').text().trim(), 'Christine');
+ assert.containsOnce(form, 'img.o_m2o_avatar[data-src="/web/image/user/19/image_128"]');
+
+ await testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 2);
+
+ assert.strictEqual(form.$('.o_field_widget[name=user_id]').text().trim(), '');
+ assert.containsNone(form, 'img.o_m2o_avatar');
+
+ form.destroy();
+ });
+
+ QUnit.test('many2one_avatar widget in list view', async function (assert) {
+ assert.expect(5);
+
+ this.data.partner.records = [
+ { id: 1, user_id: 17, },
+ { id: 2, user_id: 19, },
+ { id: 3, user_id: 17, },
+ { id: 3, user_id: false, },
+ ];
+ const list = await createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '<tree><field name="user_id" widget="many2one_avatar"/></tree>',
+ });
+
+ assert.strictEqual(list.$('.o_data_cell span').text(), 'AlineChristineAline');
+ assert.containsOnce(list.$('.o_data_cell:nth(0)'), 'img.o_m2o_avatar[data-src="/web/image/user/17/image_128"]');
+ assert.containsOnce(list.$('.o_data_cell:nth(1)'), 'img.o_m2o_avatar[data-src="/web/image/user/19/image_128"]');
+ assert.containsOnce(list.$('.o_data_cell:nth(2)'), 'img.o_m2o_avatar[data-src="/web/image/user/17/image_128"]');
+ assert.containsNone(list.$('.o_data_cell:nth(3)'), 'img.o_m2o_avatar');
+
+ list.destroy();
+ });
});
});
});