[IMP] web,calendar: display attendance status widget in calendar popover

before this commit, widgets which has specialData attribute
e.g.many2manyattendee were not supported in calendar popover, as widgets with
specialData attribute are meant to be supported in form view.

after this commit, widgets with specialData attribute can be added in calendar
popover, specialData method is called explicitly from calendar_popover to fetch
special data required to render such widgets.

with this commit we also did miscellaneous improvement in many2manyattendee

- many2many_attendee status widget bullet not aligned with attendee name, with
this commit bullet is aligned with attendee name.

- if many2many_attendee does not have status value then do not display status
else blank space is displayed before attendee name.

- in community tentative status color in many2many attendee tags widget has
o-brand-secondary color which is almost light grey, so due to badge light grey
color and tentative status light grey color status is not displayed. To fix
this issue, set tentative status color to simple grey which is bit dark then
badge background color.

task-2058767

closes odoo/odoo#46396

Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Mohammed Shekha
2020-09-07 13:06:41 +00:00
committed by Aaron Bohy
parent 0cde1189c6
commit 47b0c0d2d4
7 changed files with 95 additions and 11 deletions
@@ -8,7 +8,7 @@ var relationalFields = require('web.relational_fields');
var session = require('web.session');
var WebClient = require('web.WebClient');
var FieldMany2ManyTags = relationalFields.FieldMany2ManyTags;
const FieldMany2ManyTagsAvatar = relationalFields.FieldMany2ManyTagsAvatar;
var CalendarNotification = Notification.extend({
template: "CalendarNotification",
@@ -137,7 +137,7 @@ BasicModel.include({
},
});
var Many2ManyAttendee = FieldMany2ManyTags.extend({
const Many2ManyAttendee = FieldMany2ManyTagsAvatar.extend({
// as this widget is model dependant (rpc on res.partner), use it in another
// context probably won't work
// supportedFieldTypes: ['many2many'],
@@ -3,13 +3,14 @@
width: 10px;
height: 10px;
border-radius: 5px;
vertical-align: middle;
&.accepted {
background-color: theme-color('success');
}
&.needsAction {
background-color: $o-brand-secondary;
&.tentative {
background-color: $o-main-color-muted;
}
&.declined {
@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<template>
<t t-name="Many2ManyAttendeeTag" t-extend="FieldMany2ManyTag">
<t t-name="Many2ManyAttendeeTag" t-extend="FieldMany2ManyTagAvatar">
<t t-jquery="span:first" t-operation="prepend">
<span t-if="attendeesData[el_index]" t-attf-class="o_calendar_invitation #{attendeesData[el_index].status}"/>
<span t-if="attendeesData[el_index] and attendeesData[el_index].status and attendeesData[el_index].status !== 'needsAction'"
t-attf-class="o_calendar_invitation #{attendeesData[el_index].status}"/>
</t>
</t>
+11 -3
View File
@@ -34,7 +34,7 @@ QUnit.module('calendar', {
},
}, function () {
QUnit.test("many2manyattendee widget: basic rendering", async function (assert) {
assert.expect(9);
assert.expect(12);
var form = await createView({
View: FormView,
@@ -55,7 +55,7 @@ QUnit.module('calendar', {
"the event id should be passed as argument");
return Promise.resolve([
[1, "Jesus", "accepted", 0],
[2, "Mahomet", "needsAction", 0],
[2, "Mahomet", "tentative", 0],
]);
}
return this._super.apply(this, arguments);
@@ -71,8 +71,16 @@ QUnit.module('calendar', {
"Jesus should attend the meeting");
assert.strictEqual(form.$('.o_field_widget[name="partner_ids"] .badge[data-id="2"]').text().trim(), "Mahomet",
"the tag should be correctly named");
assert.hasClass(form.$('.o_field_widget[name="partner_ids"] .badge[data-id="2"] .o_calendar_invitation'),'needsAction',
assert.hasClass(form.el.querySelector('.o_field_widget[name="partner_ids"] .badge[data-id="2"] .o_calendar_invitation'), 'tentative',
"Mohamet should still confirm his attendance to the meeting");
assert.hasClass(form.el.querySelector('.o_field_many2manytags'), 'avatar',
"should have avatar class");
assert.containsOnce(form, '.o_field_many2manytags.avatar.o_field_widget .badge:first img',
"should have img tag");
assert.hasAttrValue(form.$('.o_field_many2manytags.avatar.o_field_widget .badge:first img'),
'data-src',
'/web/image/partner/1/image_128',
"should have correct avatar image");
form.destroy();
});
+1 -1
View File
@@ -257,7 +257,7 @@
color="user_id">
<field name="attendee_status"/>
<field name="user_id" string="Responsible" filters="1" widget="many2one_avatar_user"/>
<field name="partner_ids" widget="many2many_tags_avatar" write_model="calendar.contacts" write_field="partner_id" avatar_field="image_128"/>
<field name="partner_ids" widget="many2manyattendee" write_model="calendar.contacts" write_field="partner_id" avatar_field="image_128"/>
<field name="is_highlighted" invisible="1"/>
<field name="description"/>
<field name="privacy"/>
@@ -95,6 +95,27 @@ var CalendarPopover = Widget.extend(WidgetAdapterMixin, StandaloneFieldManagerMi
// Private
//--------------------------------------------------------------------------
/**
* Returns the AbstractField specialization that should be used for the
* given field informations. If there is no mentioned specific widget to
* use, determines one according the field type.
*
* @private
* @param {Object} field
* @param {Object} attrs
* @returns {function|null} AbstractField specialization Class
*/
_getFieldWidgetClass(field, attrs) {
let FieldWidget;
if (attrs.widget) {
FieldWidget = fieldRegistry.getAny(['form' + "." + attrs.widget, attrs.widget]);
if (!FieldWidget) {
console.warn("Missing widget: ", attrs.widget, " for field", attrs.name, "of type", field.type);
}
}
return FieldWidget || fieldRegistry.getAny(['form' + "." + field.type, field.type, "abstract"]);
},
/**
* Generate fields to render into popover
*
@@ -104,11 +125,15 @@ var CalendarPopover = Widget.extend(WidgetAdapterMixin, StandaloneFieldManagerMi
_processFields: function () {
var self = this;
var fieldsToGenerate = [];
const fieldInformation = {};
var fields = _.keys(this.displayFields);
for (var i=0; i<fields.length; i++) {
var fieldName = fields[i];
var displayFieldInfo = self.displayFields[fieldName] || {attrs: {invisible: 1}};
var fieldInfo = self.fields[fieldName];
fieldInformation[fieldName] = {
Widget: self._getFieldWidgetClass(fieldInfo, displayFieldInfo.attrs),
};
var field = {
name: fieldName,
string: displayFieldInfo.attrs.string || fieldInfo.string,
@@ -145,9 +170,12 @@ var CalendarPopover = Widget.extend(WidgetAdapterMixin, StandaloneFieldManagerMi
};
this.$fieldsList = [];
return this.model.makeRecord(this.modelName, fieldsToGenerate).then(function (recordID) {
return this.model.makeRecord(this.modelName, fieldsToGenerate, fieldInformation).then(async function (recordID) {
var defs = [];
const recordDataPoint = self.model.localData[recordID];
recordDataPoint.res_id = self.event.extendedProps.record.id;
await self.model._fetchSpecialData(recordDataPoint);
var record = self.model.get(recordID);
_.each(fieldsToGenerate, function (field) {
if (field.invisible) return;
@@ -2,15 +2,18 @@ odoo.define('web.calendar_tests', function (require) {
"use strict";
var AbstractStorageService = require('web.AbstractStorageService');
const BasicModel = require('web.BasicModel');
var CalendarView = require('web.CalendarView');
var CalendarRenderer = require('web.CalendarRenderer');
var Dialog = require('web.Dialog');
const fieldRegistry = require('web.field_registry');
var ViewDialogs = require('web.view_dialogs');
var fieldUtils = require('web.field_utils');
var mixins = require('web.mixins');
var RamStorage = require('web.RamStorage');
var testUtils = require('web.test_utils');
var session = require('web.session');
const Widget = require('web.Widget');
var createActionManager = testUtils.createActionManager;
@@ -912,6 +915,49 @@ QUnit.module('Views', {
calendar.destroy();
});
QUnit.test('render popover with widget which has specialData attribute', async function (assert) {
assert.expect(3);
await testUtils.mock.patch(BasicModel, {
_fetchSpecialDataForMyWidget() {
assert.step("_fetchSpecialDataForMyWidget");
return Promise.resolve();
},
});
const MyWidget = Widget.extend({
specialData: "_fetchSpecialDataForMyWidget",
});
fieldRegistry.add('specialWidget', MyWidget);
const calendar = await createCalendarView({
View: CalendarView,
model: 'event',
data: this.data,
arch: `<calendar class="o_calendar_test"
date_start="start"
date_stop="stop"
all_day="allday"
mode="week">
<field name="name" string="Custom Name" widget="specialWidget"/>
<field name="partner_id"/>
</calendar>`,
archs,
viewOptions: {
initialDate,
},
});
const event4 = document.querySelectorAll(".fc-event")[0];
await testUtils.dom.click(event4);
assert.containsOnce(calendar, '.o_cw_popover', "should open a popover clicking on event");
assert.verifySteps(["_fetchSpecialDataForMyWidget"]);
calendar.destroy();
await testUtils.mock.unpatch(BasicModel);
});
QUnit.test('attributes hide_date and hide_time', async function (assert) {
assert.expect(1);