[IMP] mail: add activity view
A simple new view to easily display all activity on a model grouped by res_id and activity_type_id. Some action are possible like sending a mail template for all record having an activity with this activity type and present in the view (depends on the filter). The view use the Kanban activity view on cells. It could be a good idea to rename this widget to something like "DropdownActivityView" (maybe after the freeze) Task: #1870662 PR: #26272
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">res.partner</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="view_mode">kanban,tree,form,activity</field>
|
||||
<field name="search_view_id" ref="base.view_res_partner_filter"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
'application': True,
|
||||
'qweb': [
|
||||
'static/src/xml/activity.xml',
|
||||
'static/src/xml/activity_view.xml',
|
||||
'static/src/xml/composer.xml',
|
||||
'static/src/xml/chatter.xml',
|
||||
'static/src/xml/discuss.xml',
|
||||
|
||||
@@ -147,6 +147,42 @@ class MailActivity(models.Model):
|
||||
help='Technical field for UX purpose')
|
||||
mail_template_ids = fields.Many2many(related='activity_type_id.mail_template_ids')
|
||||
|
||||
@api.model
|
||||
def get_activity_data(self, res_model, domain):
|
||||
res = self.env[res_model].search(domain)
|
||||
activity_domain = [('res_id', 'in', res.ids), ('res_model', '=', res_model)]
|
||||
grouped_activities = self.env['mail.activity'].read_group(activity_domain, ['res_id', 'activity_type_id', 'res_name:max(res_name)', 'ids:array_agg(id)', 'date_deadline:min(date_deadline)'], ['res_id', 'activity_type_id'], lazy=False)
|
||||
activity_type_ids = self.env['mail.activity.type']
|
||||
res_list = set()
|
||||
activity_data = defaultdict(dict)
|
||||
for group in grouped_activities:
|
||||
res_id = group['res_id']
|
||||
res_name = group['res_name']
|
||||
activity_type_id = group['activity_type_id'][0]
|
||||
activity_type_ids |= self.env['mail.activity.type'].browse(activity_type_id) # we will get the name when reading mail_template_ids
|
||||
res_list.add((res_id, res_name))
|
||||
state = self._compute_state_from_date(group['date_deadline'], self.user_id.sudo().tz)
|
||||
activity_data[res_id][activity_type_id] = {
|
||||
'count': group['__count'],
|
||||
'domain': group['__domain'],
|
||||
'ids': group['ids'],
|
||||
'state': state,
|
||||
'o_closest_deadline': group['date_deadline'],
|
||||
}
|
||||
activity_type_infos = []
|
||||
for elem in activity_type_ids:
|
||||
mail_template_info = []
|
||||
for mail_template_id in elem.mail_template_ids:
|
||||
mail_template_info.append({"id": mail_template_id.id, "name": mail_template_id.name})
|
||||
activity_type_infos.append([elem.id, elem.name, mail_template_info])
|
||||
|
||||
return {
|
||||
'activity_types': activity_type_infos,
|
||||
'res_ids': list(res_list),
|
||||
'grouped_activities': activity_data,
|
||||
'model': res_model,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
@api.onchange('previous_activity_type_id')
|
||||
def _compute_has_recommended_activities(self):
|
||||
@@ -167,24 +203,27 @@ class MailActivity(models.Model):
|
||||
|
||||
@api.depends('date_deadline')
|
||||
def _compute_state(self):
|
||||
today_default = date.today()
|
||||
|
||||
for record in self.filtered(lambda activity: activity.date_deadline):
|
||||
today = today_default
|
||||
tz = record.user_id.sudo().tz
|
||||
if tz:
|
||||
today_utc = pytz.UTC.localize(datetime.utcnow())
|
||||
today_tz = today_utc.astimezone(pytz.timezone(tz))
|
||||
today = date(year=today_tz.year, month=today_tz.month, day=today_tz.day)
|
||||
date_deadline = record.date_deadline
|
||||
record.state = self._compute_state_from_date(date_deadline, tz)
|
||||
|
||||
date_deadline = fields.Date.from_string(record.date_deadline)
|
||||
diff = (date_deadline - today)
|
||||
if diff.days == 0:
|
||||
record.state = 'today'
|
||||
elif diff.days < 0:
|
||||
record.state = 'overdue'
|
||||
else:
|
||||
record.state = 'planned'
|
||||
@api.model
|
||||
def _compute_state_from_date(self, date_deadline, tz=False):
|
||||
date_deadline = fields.Date.from_string(date_deadline)
|
||||
today_default = date.today()
|
||||
today = today_default
|
||||
if tz:
|
||||
today_utc = pytz.UTC.localize(datetime.utcnow())
|
||||
today_tz = today_utc.astimezone(pytz.timezone(tz))
|
||||
today = date(year=today_tz.year, month=today_tz.month, day=today_tz.day)
|
||||
diff = (date_deadline - today)
|
||||
if diff.days == 0:
|
||||
return 'today'
|
||||
elif diff.days < 0:
|
||||
return 'overdue'
|
||||
else:
|
||||
return 'planned'
|
||||
|
||||
@api.onchange('activity_type_id')
|
||||
def _onchange_activity_type_id(self):
|
||||
|
||||
@@ -146,6 +146,7 @@ var AbstractActivityField = AbstractField.extend({
|
||||
context: this.record.getContext(),
|
||||
}).then(
|
||||
function(rslt_action) {
|
||||
console.log(rslt_action);
|
||||
self.do_action(rslt_action, {
|
||||
on_close: function () {
|
||||
self.trigger_up('reload');
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
odoo.define('mail.ActivityController', function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractController = require('web.AbstractController');
|
||||
|
||||
var ActivityController = AbstractController.extend({
|
||||
custom_events: _.extend({}, AbstractController.prototype.custom_events, {
|
||||
send_mail_template: '_onSendMailTemplate',
|
||||
open_view_form: '_onOpenViewForm',
|
||||
reload: '_onReload',
|
||||
}),
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {OdooEvent} event
|
||||
* @param {string} event.name
|
||||
* @param {Object} event.data
|
||||
* @param {boolean} [event.data.activity]
|
||||
* @param {boolean} [event.data.followers]
|
||||
* @param {boolean} [event.data.thread]
|
||||
*/
|
||||
_onReload: function (event) {
|
||||
event.stopPropagation();
|
||||
var self = this;
|
||||
this.model.reload().then(self.reload());
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {OdooEvent} ev
|
||||
*/
|
||||
_onSendMailTemplate: function (ev) {
|
||||
var templateID = ev.data.templateID;
|
||||
var activityTypeID = ev.data.activityTypeID;
|
||||
var state = this.model.get();
|
||||
var groupedActivities = state.data.grouped_activities;
|
||||
var resIDS = [];
|
||||
Object.keys(groupedActivities).forEach(function (resID) {
|
||||
var activityByType = groupedActivities[resID];
|
||||
var activity = activityByType[activityTypeID];
|
||||
if (activity) {
|
||||
resIDS.push(parseInt(resID));
|
||||
}
|
||||
});
|
||||
this._rpc({
|
||||
model: this.model.modelName,
|
||||
method: 'activity_send_mail',
|
||||
args: [resIDS, templateID],
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onOpenViewForm: function (ev) {
|
||||
var resID = ev.data.resID;
|
||||
this.do_action({
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: this.model.modelName,
|
||||
res_id: resID,
|
||||
views: [[false, 'form']],
|
||||
target: 'current'
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return ActivityController;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
odoo.define('mail.ActivityModel', function (require) {
|
||||
'use strict';
|
||||
|
||||
var AbstractModel = require('web.AbstractModel');
|
||||
|
||||
var ActivityModel = AbstractModel.extend({
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
get: function () {
|
||||
return this.data;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @param {Object} params
|
||||
* @param {Array[]} params.domain
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
load: function (params) {
|
||||
this.domain = params.domain;
|
||||
this.modelName = params.modelName;
|
||||
this.data = {};
|
||||
return this._fetchData();
|
||||
},
|
||||
/**
|
||||
* @param {any} handle
|
||||
* @param {Object} params
|
||||
* @param {Array[]} params.domain
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
reload: function (handle, params) {
|
||||
if (params && 'domain' in params) {
|
||||
this.domain = params.domain;
|
||||
}
|
||||
return this._fetchData();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch activity data.
|
||||
*
|
||||
* @private
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
_fetchData: function () {
|
||||
var self = this;
|
||||
return this._rpc({
|
||||
model: "mail.activity",
|
||||
method: 'get_activity_data',
|
||||
kwargs: {
|
||||
res_model: this.modelName,
|
||||
domain: this.domain,
|
||||
}
|
||||
}).then(function (result) {
|
||||
self.data.data = result;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return ActivityModel;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,181 @@
|
||||
odoo.define('mail.ActivityRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractRenderer = require('web.AbstractRenderer');
|
||||
var core = require('web.core');
|
||||
var field_registry = require('web.field_registry');
|
||||
|
||||
var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
var ActivityRenderer = AbstractRenderer.extend({
|
||||
className: 'o_activity_view',
|
||||
events: {
|
||||
'click .o_res_name_cell': '_onResNameClicked',
|
||||
'click .o_send_mail_template': '_onSenMailTemplateClicked',
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_render: function () {
|
||||
this.$el
|
||||
.removeClass('table-responsive')
|
||||
.empty();
|
||||
|
||||
if (this.state.data.activity_types.length === 0) {
|
||||
this.$el.append($("<div>").text(_t("No schedulded activity")));
|
||||
} else {
|
||||
var $table = $('<table>')
|
||||
.addClass('table-bordered')
|
||||
.append(this._renderHeader())
|
||||
.append(this._renderBody());
|
||||
this.$el
|
||||
.addClass('table-responsive')
|
||||
.append($table);
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {jQueryElement} a jquery element <tbody>
|
||||
*/
|
||||
_renderBody: function () {
|
||||
var $rows = _.map(this.state.data.res_ids, this._renderRow.bind(this));
|
||||
return $('<tbody>').append($rows);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {jQueryElement} a jquery element <thead>
|
||||
*/
|
||||
_renderHeader: function () {
|
||||
var $tr = $('<tr>')
|
||||
.append($('<th>')) //empty cell for name
|
||||
.append(_.map(this.state.data.activity_types, this._renderHeaderCell.bind(this)));
|
||||
return $('<thead>').append($tr);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} activity_type
|
||||
* @returns {jQueryElement} a <th> element
|
||||
*/
|
||||
_renderHeaderCell: function (activity_type) {
|
||||
return QWeb.render('mail.ActivityViewHeaderCell', {
|
||||
id: activity_type[0],
|
||||
name: activity_type[1],
|
||||
template_list: activity_type[2] || [],
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} data
|
||||
* @returns {jQueryElement} a <tr> element
|
||||
*/
|
||||
_renderRow: function (data) {
|
||||
var self = this;
|
||||
var res_id = data[0];
|
||||
var name = data[1];
|
||||
var $nameTD = $('<td>')
|
||||
.addClass("o_res_name_cell")
|
||||
.html(name)
|
||||
.data('res-id', res_id);
|
||||
var $cells = _.map(this.state.data.activity_types, function (node) {
|
||||
var $td = $('<td>').addClass("o_activity_summary_cell");
|
||||
var activity_type_id = node[0];
|
||||
var activity_group = self.state.data.grouped_activities[res_id][activity_type_id];
|
||||
activity_group = activity_group || {count: 0, ids: [], state: false};
|
||||
if (activity_group.state) {
|
||||
$td.addClass(activity_group.state);
|
||||
}
|
||||
// we need to create a fake record in order to instanciate the KanbanActivity
|
||||
// this is the minimal information in order to make it work
|
||||
// AAB: move this to a function
|
||||
var record = {
|
||||
data: {
|
||||
activity_ids: {
|
||||
model: 'mail.activity',
|
||||
res_ids: activity_group.ids,
|
||||
},
|
||||
activity_state: activity_group.state,
|
||||
},
|
||||
fields: {
|
||||
activity_ids: {},
|
||||
activity_state: {
|
||||
selection: [
|
||||
['overdue', "Overdue"],
|
||||
['today', "Today"],
|
||||
['planned', "Planned"],
|
||||
],
|
||||
},
|
||||
},
|
||||
fieldsInfo: {},
|
||||
model: self.state.data.model,
|
||||
ref: res_id, // not necessary, i think
|
||||
type: 'record',
|
||||
res_id: res_id,
|
||||
getContext: function () {
|
||||
return {}; // session.user_context
|
||||
},
|
||||
//todo intercept event or changes on record to update view
|
||||
};
|
||||
var KanbanActivity = field_registry.get('kanban_activity');
|
||||
var widget = new KanbanActivity(self, "activity_ids", record, {});
|
||||
widget.appendTo($td);
|
||||
// replace clock by closest deadline
|
||||
var $date = $('<div>');
|
||||
var formated_date = moment(activity_group.o_closest_deadline).format('ll');
|
||||
var current_year = (new Date()).getFullYear();
|
||||
if (formated_date.endsWith(current_year)) { // Dummy logic to remove year (only if current year), we will maybe need to improve it
|
||||
formated_date = formated_date.slice(0, -4);
|
||||
formated_date = formated_date.replace(/( |,)*$/g, "");
|
||||
}
|
||||
$date
|
||||
.text(formated_date)
|
||||
.addClass('o_closest_deadline');
|
||||
$td.find('a')
|
||||
.empty()
|
||||
.append($date);
|
||||
return $td;
|
||||
});
|
||||
var $tr = $('<tr/>', {class: 'o_data_row'})
|
||||
.append($nameTD)
|
||||
.append($cells);
|
||||
return $tr;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onSenMailTemplateClicked: function (ev) {
|
||||
var $target = $(ev.currentTarget);
|
||||
var templateID = $target.data('template-id');
|
||||
var activityTypeID = $target.closest('th').data('activity-type-id');
|
||||
this.trigger_up('send_mail_template', {
|
||||
activityTypeID: activityTypeID,
|
||||
templateID: templateID,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onResNameClicked: function (ev) {
|
||||
var resID = $(ev.currentTarget).data('res-id');
|
||||
this.trigger_up('open_view_form', {resID: resID});
|
||||
},
|
||||
});
|
||||
|
||||
return ActivityRenderer;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
odoo.define('mail.ActivityView', function (require) {
|
||||
"use strict";
|
||||
|
||||
var ActivityController = require('mail.ActivityController');
|
||||
var ActivityModel = require('mail.ActivityModel');
|
||||
var ActivityRenderer = require('mail.ActivityRenderer');
|
||||
var AbstractView = require('web.AbstractView');
|
||||
var core = require('web.core');
|
||||
var view_registry = require('web.view_registry');
|
||||
|
||||
var _lt = core._lt;
|
||||
|
||||
var ActivityView = AbstractView.extend({
|
||||
accesskey: "a",
|
||||
display_name: _lt('Activity'),
|
||||
icon: 'fa-clock-o',
|
||||
config: _.extend({}, AbstractView.prototype.config, {
|
||||
Controller: ActivityController,
|
||||
Model: ActivityModel,
|
||||
Renderer: ActivityRenderer,
|
||||
}),
|
||||
viewType: 'activity',
|
||||
});
|
||||
|
||||
view_registry.add('activity', ActivityView);
|
||||
|
||||
return ActivityView;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
.o_activity_view {
|
||||
height: 100%;
|
||||
> table {
|
||||
background-color: white;
|
||||
}
|
||||
.o_activity_summary_cell {
|
||||
background-color: #FFF;
|
||||
&.planned {
|
||||
background-color: theme-color('success');
|
||||
}
|
||||
&.overdue {
|
||||
background-color: theme-color('danger');
|
||||
}
|
||||
&.today {
|
||||
background-color: theme-color('warning');
|
||||
}
|
||||
.o_kanban_inline_block {
|
||||
min-height: 42px;
|
||||
}
|
||||
.dropdown-toggle {
|
||||
cursor: pointer;
|
||||
.o_closest_deadline {
|
||||
height: 42px;
|
||||
width: 100%;
|
||||
color: #FFF;
|
||||
text-align: center;
|
||||
line-height: 42px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_res_name_cell {
|
||||
padding: 0 10px;
|
||||
color: #008784;
|
||||
&:hover {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
.o_activity_type_cell {
|
||||
padding:10px;
|
||||
min-width:100px;
|
||||
.fa-ellipsis-v {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.o_template_element {
|
||||
white-space: nowrap;
|
||||
padding:5px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: theme-color('success');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,10 +144,13 @@
|
||||
}
|
||||
|
||||
/* Kanban View */
|
||||
.o_kanban_record {
|
||||
.o_kanban_record{
|
||||
.o_kanban_inline_block {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.o_kanban_record, .o_view_controller{
|
||||
.o_mail_activity {
|
||||
.o_activity_btn {
|
||||
span.fa {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.ActivityViewHeaderCell">
|
||||
<th class="o_activity_type_cell" t-att-data-activity-type-id="id">
|
||||
<span><t t-esc="name"/></span>
|
||||
<span t-if="template_list.length > 0" class="dropdown pull-right">
|
||||
<i class="fa fa-ellipsis-v fa-fw" data-toggle="dropdown"/>
|
||||
<div class="dropdown-menu">
|
||||
<div t-foreach="template_list" t-as="template" title="This action will send an email." class="o_template_element o_send_mail_template" t-att-data-template-id="template.id">
|
||||
<i class="fa fa-envelope fa-fw"/> <t t-esc="template.name"/>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</th>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -447,7 +447,7 @@ QUnit.test('kanban activity widget popover test', function (assert) {
|
||||
'</t></templates>' +
|
||||
'</kanban>',
|
||||
mockRPC: function (route, args) {
|
||||
if (route === '/web/dataset/call_kw/mail.activity/action_feedback') {
|
||||
if (route === '/web/dataset/call_kw/mail.activity/action_done_schedule_next') {
|
||||
rpcCount++;
|
||||
|
||||
var current_ids = this.data.partner.records[0].activity_ids;
|
||||
@@ -1195,7 +1195,7 @@ QUnit.test('form activity widget with another x2many field in view', function (a
|
||||
});
|
||||
|
||||
QUnit.test('form activity widget: schedule next activity', function (assert) {
|
||||
assert.expect(5);
|
||||
assert.expect(4);
|
||||
this.data.partner.records[0].activity_ids = [1];
|
||||
this.data.partner.records[0].activity_state = 'today';
|
||||
this.data['mail.activity'].records = [{
|
||||
@@ -1208,7 +1208,6 @@ QUnit.test('form activity widget: schedule next activity', function (assert) {
|
||||
activity_type_id: 2,
|
||||
}];
|
||||
|
||||
var checkReadArgs = false;
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
@@ -1225,35 +1224,17 @@ QUnit.test('form activity widget: schedule next activity', function (assert) {
|
||||
'</form>',
|
||||
res_id: 2,
|
||||
mockRPC: function (route, args) {
|
||||
if (route === '/web/dataset/call_kw/mail.activity/action_feedback') {
|
||||
assert.ok(_.isEqual(args.args[0], [1]), "should call 'action_feedback' for id 1");
|
||||
if (route === '/web/dataset/call_kw/mail.activity/action_done_schedule_next') {
|
||||
assert.ok(_.isEqual(args.args[0], [1]), "should call 'action_done_schedule_next' for id 1");
|
||||
assert.strictEqual(args.kwargs.feedback, 'everything is ok',
|
||||
"the feedback should be sent correctly");
|
||||
return $.when();
|
||||
}
|
||||
if (args.method === 'read' && args.model === 'partner' && checkReadArgs) {
|
||||
assert.deepEqual(args.args[1], ['activity_ids', 'message_ids', 'display_name'],
|
||||
"should only read the mail fields");
|
||||
return $.when('test_result');
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
intercepts: {
|
||||
do_action: function (event) {
|
||||
assert.deepEqual(event.data.action, {
|
||||
context: {
|
||||
default_res_id: 2,
|
||||
default_res_model: "partner",
|
||||
default_previous_activity_type_id: 2,
|
||||
},
|
||||
res_id: false,
|
||||
res_model: 'mail.activity',
|
||||
type: 'ir.actions.act_window',
|
||||
target: "new",
|
||||
view_mode: "form",
|
||||
view_type: "form",
|
||||
views: [[false, "form"]],
|
||||
}, "should do a do_action with correct parameters");
|
||||
checkReadArgs = true; // should re-read the activities when closing the dialog
|
||||
assert.strictEqual(event.data.action,'test_result' , "should do a do_action with correct parameters");
|
||||
event.data.options.on_close();
|
||||
},
|
||||
},
|
||||
|
||||
@@ -57,6 +57,11 @@
|
||||
<script type="text/javascript" src="/mail/static/src/js/tour.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/js/utils.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/js/activity.js"></script>
|
||||
<!-- Activity view type -->
|
||||
<script type="text/javascript" src="/mail/static/src/js/views/activity/activity_view.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/js/views/activity/activity_model.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/js/views/activity/activity_controller.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/js/views/activity/activity_renderer.js"></script>
|
||||
<!-- Others -->
|
||||
<script type="text/javascript" src="/mail/static/src/js/emojis.js"></script>
|
||||
|
||||
@@ -70,6 +75,7 @@
|
||||
<link rel="stylesheet" type="text/scss" href="/mail/static/src/scss/thread.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/mail/static/src/scss/systray.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/mail/static/src/scss/mail_activity.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/mail/static/src/scss/activity_view.scss"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<record id="act_project_project_2_project_task_all" model="ir.actions.act_window">
|
||||
<field name="name">Tasks</field>
|
||||
<field name="res_model">project.task</field>
|
||||
<field name="view_mode">kanban,tree,form,calendar,pivot,graph</field>
|
||||
<field name="view_mode">kanban,tree,form,calendar,pivot,graph,activity</field>
|
||||
<field name="context">{
|
||||
'search_default_project_id': [active_id],
|
||||
'default_project_id': active_id,
|
||||
|
||||
@@ -1252,6 +1252,15 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
|
||||
element = E.field(name=self._rec_name_fallback())
|
||||
return E.tree(element, string=self._description)
|
||||
|
||||
@api.model
|
||||
def _get_default_activity_view(self):
|
||||
""" Generates an empty activity view.
|
||||
|
||||
:returns: a activity view as an lxml document
|
||||
:rtype: etree._Element
|
||||
"""
|
||||
return E.activity(string=self._description)
|
||||
|
||||
@api.model
|
||||
def _get_default_pivot_view(self):
|
||||
""" Generates an empty pivot view.
|
||||
|
||||
Reference in New Issue
Block a user