[IMP] web: prepare calendar view to ease modifications

The calendar renderer used a template for events.
Now moved as a configurable template to ease futur calendar changes.

This commit adds also a custom event to the calendar renderer so that custom
implementations have a way to re-render the event items within the calendar
after making internal changes to the event records.

We introduce also a new option for a field "filter_field" which allows to specify
the field of the model in which we will save the status of a filter.

This is preliminary changes in order to make the calendar view able to modify
the attendance status of a meeting and refresh the events to visually display
if the user is attending or not.

Task ID 2196775
COM PR: odoo/odoo#55190
ENT PR: odoo/enterprise#12196
UPG PR: odoo/upgrade#1532

Co-authored-by: Simon Genin (ges) <ges@odoo.com>
Co-authored-by: jeh-odoo <jeh@odoo.com>
This commit is contained in:
Simon Genin (ges)
2021-04-15 14:05:10 +00:00
committed by Jérémy Hennecart
co-authored by jeh-odoo
parent 55eb75d949
commit 0774459f5d
7 changed files with 69 additions and 9 deletions
@@ -246,7 +246,22 @@ var CalendarController = AbstractController.extend({
* @private
* @param {OdooEvent} event
*/
_onChangeFilter: function (event) {
_onChangeFilter: async function (event) {
if (event.data.value !== "all" && event.target.filter_field) {
const domain = [['user_id', '=', this.getSession().uid], [event.target.write_field, '=', parseInt(event.data.value)]];
const contact = await this._rpc({
model: event.target.write_model,
method: 'search',
args: [domain],
});
let val = {};
val[event.target.filter_field] = event.data.active;
await this._rpc({
model: event.target.write_model,
method: 'write',
args: [contact, val],
});
}
if (this.model.changeFilter(event.data) && !event.data.no_reload) {
this.reload();
}
@@ -537,11 +537,15 @@ return AbstractModel.extend({
}
var field = this.fields[filter.fieldName];
var fields = [filter.write_field];
if (filter.filter_field) {
fields.push(filter.filter_field);
}
return this._rpc({
model: filter.write_model,
method: 'search_read',
domain: [["user_id", "=", session.uid]],
fields: [filter.write_field],
fields: fields,
})
.then(function (res) {
var records = _.map(res, function (record) {
@@ -554,7 +558,7 @@ return AbstractModel.extend({
'id': record.id,
'value': value,
'label': formater(_value, field),
'active': f && f.active,
'active': (f && f.active) || (filter.filter_field && record[filter.filter_field]),
};
});
records.sort(function (f1,f2) {
@@ -46,6 +46,7 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, {
this.fieldName = options.fieldName;
this.write_model = options.write_model;
this.write_field = options.write_field;
this.filter_field = options.filter_field;
this.avatar_field = options.avatar_field;
this.avatar_model = options.avatar_model;
this.filters = options.filters;
@@ -161,10 +162,12 @@ return AbstractRenderer.extend({
template: "CalendarView",
config: {
CalendarPopover: CalendarPopover,
eventTemplate: 'calendar-box',
},
custom_events: _.extend({}, AbstractRenderer.prototype.custom_events || {}, {
edit_event: '_onEditEvent',
delete_event: '_onDeleteEvent',
render_event: '_renderEvents',
}),
/**
@@ -356,7 +359,7 @@ return AbstractRenderer.extend({
if (_.isEmpty(qweb_context.record)) {
return '';
} else {
return qweb.render("calendar-box", qweb_context);
return qweb.render(this.config.eventTemplate, qweb_context);
}
},
/**
@@ -112,6 +112,7 @@ var CalendarView = AbstractView.extend({
};
filters[fieldName].write_model = child.attrs.write_model;
filters[fieldName].write_field = child.attrs.write_field; // can't use a x2many fields
filters[fieldName].filter_field = child.attrs.filter_field;
modelFilters.push(fields[fieldName].relation);
}
@@ -116,11 +116,12 @@ QUnit.module('Views', {
id: {string: "ID", type: "integer"},
user_id: {string: "user", type: "many2one", relation: 'user'},
partner_id: {string: "partner", type: "many2one", relation: 'partner'},
partner_checked: {string: "checked", type: "boolean"}
},
records: [
{id: 1, user_id: session.uid, partner_id: 1},
{id: 2, user_id: session.uid, partner_id: 2},
{id: 3, user_id: 4, partner_id: 3}
{id: 1, user_id: session.uid, partner_id: 1, partner_checked: true},
{id: 2, user_id: session.uid, partner_id: 2, partner_checked: true},
{id: 3, user_id: 4, partner_id: 3, partner_checked: true}
]
},
};
@@ -2140,6 +2141,40 @@ QUnit.module('Views', {
assert.strictEqual($('#ui-datepicker-div:empty').length, 0, "should have a clean body");
});
QUnit.test('check filters with filter_field specified', async function (assert) {
assert.expect(5);
const calendar = await createCalendarView({
View: CalendarView,
model: 'event',
data: this.data,
arch: `
<calendar class="o_calendar_test"
event_open_popup="true"
date_start="start"
date_stop="stop"
all_day="allday"
mode="week"
color="partner_id">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" filter_field="partner_checked"/>
</calendar>`,
});
assert.containsOnce(calendar, '.o_calendar_filter_item[data-id="2"] input:checked',
"checkbox should be checked");
await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-id="2"] input'));
assert.containsNone(calendar, '.o_calendar_filter_item[data-id="2"] input:checked',
"checkbox should not be checked");
assert.strictEqual(this.data.filter_partner.records.find(r => r.id === 2).partner_checked, false,
"the status of this filter should now be false");
await calendar.reload();
assert.containsNone(calendar, '.o_calendar_filter_item[data-id="2"] input:checked',
"checkbox should not be checked after the reload");
assert.strictEqual(this.data.filter_partner.records.find(r => r.id === 2).partner_checked, false,
"the status of this filter should still be false after the reload");
calendar.destroy();
});
QUnit.test('"all" filter', async function (assert) {
assert.expect(8);
+3 -2
View File
@@ -454,10 +454,11 @@ calendar view are:
* ``avatar_field``
only for x2many field, to display the avatar instead of the display_name
in the cards
* ``write_model`` and ``write_field``
* ``write_model`` and ``write_field`` and ``filter_field``
you can add a filter and save the result in the defined model, the
filter is added in the sidebar
filter is added in the sidebar. The ``filter_field`` is optionnal and allows
you to specify the field that will hold the status of the filter.
* ``filter`` and ``color``
use "True" to add this field in filter in the sidebar. You can specify
+1
View File
@@ -265,6 +265,7 @@
<rng:optional><rng:attribute name="avatar_field" /></rng:optional>
<rng:optional><rng:attribute name="write_model" /></rng:optional>
<rng:optional><rng:attribute name="write_field" /></rng:optional>
<rng:optional><rng:attribute name="filter_field" /></rng:optional>
<rng:optional><rng:attribute name="text" /></rng:optional>
<rng:optional><rng:attribute name="optional" /></rng:optional>
<rng:optional><rng:attribute name="decoration-bf"/></rng:optional>