[IMP] *: converting custom list views adding a doAction button

In this commit, we will convert all the customisations of the list view
consist in adding an always present button in the control panel that
performs an action/calls a model's method.

To do this, we will use the new display="always" parameter applicable
to buttons in the header of list views. This allows us to define a
button that is always visible in a list view.

closes odoo/odoo#106519

Taskid: 3082303
Related: odoo/enterprise#34387
Related: odoo/documentation#3044
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
FrancoisGe
2022-12-16 18:39:56 +01:00
parent ce30c50e8d
commit 1f0e840238
19 changed files with 87 additions and 130 deletions
+11 -1
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
from odoo import fields, models, _
class Lead(models.Model):
@@ -11,3 +11,13 @@ class Lead(models.Model):
def _merge_get_fields(self):
return super(Lead, self)._merge_get_fields() + ['lead_mining_request_id']
def action_generate_leads(self):
return {
"name": _("Generate Leads"),
"type": "ir.actions.act_window",
"res_model": "crm.iap.lead.mining.request",
"target": "new",
"views": [[False, "form"]],
"context": {"is_modal": True},
}
@@ -1,24 +1,8 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { patch } from "@web/core/utils/patch";
import { useGenerateLeadsButton } from "@crm_iap_mine/views/generate_leads_hook";
import { crmKanbanView } from "@crm/views/crm_kanban/crm_kanban_view";
import { ListController } from "@web/views/list/list_controller";
import { listView } from "@web/views/list/list_view";
export class LeadMiningRequestListController extends ListController {
setup() {
super.setup();
useGenerateLeadsButton();
}
}
registry.category("views").add("crm_iap_lead_mining_request_tree", {
...listView,
Controller: LeadMiningRequestListController,
buttonTemplate: "LeadMiningRequestListView.buttons",
});
patch(crmKanbanView.Controller.prototype, "crm_iap_lead_mining_request_kanban", {
setup() {
@@ -7,13 +7,6 @@
</button>
</t>
<t t-name="LeadMiningRequestListView.buttons" t-inherit="web.ListView.Buttons" t-inherit-mode="primary" owl="1">
<!-- Before the export button -->
<xpath expr="//t[contains(@t-if, 'isExportEnable')]" position="before">
<t t-call="LeadMiningRequest.generate_leads_button"/>
</xpath>
</t>
<t t-name="LeadMiningRequestKanbanView.buttons" t-inherit="web.KanbanView.Buttons" t-inherit-mode="primary" owl="1">
<xpath expr="//div[@t-if='props.showButtons']" position="inside">
<t t-call="LeadMiningRequest.generate_leads_button"/>
+8 -4
View File
@@ -6,8 +6,9 @@
<field name="model">crm.lead</field>
<field name="inherit_id" ref="crm.crm_case_tree_view_oppor" />
<field name="arch" type="xml">
<xpath expr="//tree" position="attributes">
<attribute name="js_class">crm_iap_lead_mining_request_tree</attribute>
<xpath expr="//header" position="inside">
<button name="action_generate_leads" type="object" class="o_button_generate_leads"
string="Generate Leads" groups="sales_team.group_sale_manager" display="always"/>
</xpath>
</field>
</record>
@@ -17,8 +18,11 @@
<field name="model">crm.lead</field>
<field name="inherit_id" ref="crm.crm_case_tree_view_leads" />
<field name="arch" type="xml">
<xpath expr="//tree" position="attributes">
<attribute name="js_class">crm_iap_lead_mining_request_tree</attribute>
<xpath expr="//tree" position="inside">
<header>
<button name="action_generate_leads" type="object" class="o_button_generate_leads"
string="Generate Leads" groups="sales_team.group_sale_manager" display="always"/>
</header>
</xpath>
</field>
</record>
@@ -22,7 +22,6 @@ export class ExpenseListController extends ListController {
this.rpc = useService("rpc");
this.user = useService("user");
this.isExpenseSheet = this.model.rootParams.resModel === "hr.expense.sheet";
this.isExpense = this.model.rootParams.resModel === "hr.expense";
onWillStart(async () => {
this.is_expense_team_approver = await this.user.hasGroup("hr_expense.group_hr_expense_team_approver");
@@ -30,10 +29,6 @@ export class ExpenseListController extends ListController {
});
}
displayCreateReport() {
return this.isExpense;
}
displaySubmit() {
const records = this.model.root.selection;
return records.length && records.every(record => record.data.state === 'draft') && this.isExpenseSheet;
@@ -59,13 +54,6 @@ export class ExpenseListController extends ListController {
this.render(true);
}
async onCreateReportClick() {
const records = this.model.root.selection;
const recordIds = records.map((a) => a.resId);
const action = await this.orm.call('hr.expense', 'get_expenses_to_submit', [recordIds]);
this.actionService.doAction(action);
}
}
patch(ExpenseListController.prototype, 'expense_list_controller_upload', ExpenseDocumentUpload);
@@ -13,13 +13,6 @@
<input type="file" name="ufile" class="d-none" t-ref="fileInput" multiple="1" accept="*" t-on-change="onChangeFileInput"/>
</xpath>
<!-- hr.expense -->
<xpath expr="//button[hasclass('o_button_upload_expense')]" position="after">
<button t-if="displayCreateReport()" class="d-none d-md-block btn btn-secondary" t-on-click="onCreateReportClick">
Create Report
</button>
</xpath>
<!-- hr.expense.sheet -->
<xpath expr="//button[hasclass('o_button_upload_expense')]" position="after">
<button t-if="displaySubmit()" class="d-none d-md-block btn btn-secondary" t-on-click="() => this.onClick('action_submit_sheet')">
@@ -45,6 +45,9 @@
<field name="model">hr.expense</field>
<field name="arch" type="xml">
<tree string="Expenses" multi_edit="1" sample="1" js_class="hr_expense_tree" decoration-info="state == 'draft'">
<header>
<button name="get_expenses_to_submit" string="Create Report" type="object" display="always"/>
</header>
<field name="company_id" invisible="1"/>
<field name="company_currency_id" invisible="1"/>
<field name="attachment_number" invisible="True"/>
@@ -185,6 +185,12 @@ class MassMailingContact(models.Model):
for r in self
}
def action_import(self):
action = self.env["ir.actions.actions"]._for_xml_id("mass_mailing.mailing_contact_import_action")
action['context'] = self.env.context
return action
def action_add_to_mailing_list(self):
ctx = dict(self.env.context, default_contact_ids=self.ids)
action = self.env["ir.actions.actions"]._for_xml_id("mass_mailing.mailing_contact_to_list_action")
@@ -1,34 +0,0 @@
/** @odoo-module **/
import { ListController } from "@web/views/list/list_controller";
import { listView } from '@web/views/list/list_view';
import { registry } from '@web/core/registry';
import { useService } from "@web/core/utils/hooks";
/**
* List view for the <mailing.contact> model.
*
* Add an import button to open the wizard <mailing.contact.import>. This wizard
* allows the user to import contacts line by line.
*/
export class MailingContactListController extends ListController {
async setup() {
super.setup();
this.actionService = useService("action");
}
onImport() {
const context = this.props.context;
const actionParams = { additionalContext: context };
if (!context.default_mailing_list_ids && context.active_model === 'mailing.list' && context.active_ids) {
actionParams.additionalContext.default_mailing_list_ids = context.active_ids;
}
this.actionService.doAction('mass_mailing.mailing_contact_import_action', actionParams);
}
};
registry.category('views').add('mailing_contact_list', {
...listView,
Controller: MailingContactListController,
buttonTemplate: 'MailingContactListView.buttons',
});
@@ -1,13 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="mailing_contact_views" xml:space="preserve">
<t t-name="MailingContactListView.buttons" t-inherit="web.ListView.Buttons" t-inherit-mode="primary" owl="1">
<xpath expr="//t[@t-if='props.showButtons']" position="inside">
<button type="button" class="btn btn-secondary o_mass_mailing_import_contact" t-on-click="onImport">
Import
</button>
</xpath>
</t>
<t t-name="MailingContactKanbanView.buttons" t-inherit="web.KanbanView.Buttons" t-inherit-mode="primary" owl="1">
<xpath expr="//div[@t-if='props.showButtons']" position="inside">
<button type="button" class="btn btn-secondary o_mass_mailing_import_contact" t-on-click="onImport">
@@ -43,6 +43,7 @@
<field name="arch" type="xml">
<tree string="Mailing List Contacts" sample="1" multi_edit="1" js_class="mailing_contact_list">
<header>
<button name="action_import" string="Import" type="object" display="always"/>
<button name="action_add_to_mailing_list" string="Add to List" type="object"/>
</header>
<field name="create_date" optional="show"/>
+18
View File
@@ -430,6 +430,24 @@ class StockQuant(models.Model):
self._apply_inventory()
self.inventory_quantity_set = False
def action_inventory_at_date(self):
# Handler called when the user clicked on the 'Inventory at Date' button.
# Opens wizard to display, at choice, the products inventory or a computed
# inventory at a given date.
context = {}
if ("default_product_id" in self.env.context):
context.product_id = self.env.context.default_product_id
elif ("product_tmpl_id" in self.env.context):
context.product_tmpl_id = self.env.context.product_tmpl_id
return {
"res_model": "stock.quantity.history",
"views": [[False, "form"]],
"target": "new",
"type": "ir.actions.act_window",
"context": context,
}
def action_inventory_history(self):
self.ensure_one()
action = {
@@ -3,10 +3,6 @@
<t t-name="InventoryReport.Buttons" t-inherit="web.ListView.Buttons" t-inherit-mode="primary" owl="1">
<xpath expr="(//div/*)[last()]" position="after">
<button t-if="props.resModel == 'stock.quant' and (!props.context.inventory_mode or props.context.inventory_report_mode) and !props.context.no_at_date"
type="button" class="btn btn-primary o_button_at_date me-2" t-on-click="onClickInventoryAtDate">
Inventory at Date
</button>
<button t-if="props.context.inventory_mode and !props.context.inventory_report_mode"
class="btn btn-secondary" type="button" t-on-click="onClickApplyAll">
Apply All
@@ -12,29 +12,6 @@ export class InventoryReportListController extends ListController {
}
}
/**
* Handler called when the user clicked on the 'Valuation at Date' button.
* Opens wizard to display, at choice, the products inventory or a computed
* inventory at a given date.
*/
async onClickInventoryAtDate() {
const context = {
active_model: this.props.resModel,
};
if (this.props.context.default_product_id) {
context.product_id = this.props.context.default_product_id;
} else if (this.props.context.product_tmpl_id) {
context.product_tmpl_id = this.props.context.product_tmpl_id;
}
this.actionService.doAction({
res_model: "stock.quantity.history",
views: [[false, "form"]],
target: "new",
type: "ir.actions.act_window",
context,
});
}
get actionMenuItems() {
const actionMenus = super.actionMenuItems;
if (
+16
View File
@@ -110,6 +110,12 @@
<tree editable="bottom"
create="1" edit="1" js_class="inventory_report_list"
sample="1">
<header>
<button name="action_inventory_at_date" string="Inventory at Date" type="object"
invisible="((context.get('inventory_mode') and not context.get('inventory_report_mode')) or context.get('no_at_date'))"
class="btn-primary ms-1"
display="always"/>
</header>
<field name="create_date" invisible="1"/>
<field name="write_date" invisible="1"/>
<field name="id" invisible="1"/>
@@ -153,6 +159,12 @@
<field name="arch" type="xml">
<tree string="Inventory Valuation" js_class="inventory_report_list"
create="0" delete="0">
<header>
<button name="action_inventory_at_date" string="Inventory at Date" type="object"
invisible="((context.get('inventory_mode') and not context.get('inventory_report_mode')) or context.get('no_at_date'))"
class="btn-primary ms-1"
display="always"/>
</header>
<field name="product_id" invisible="context.get('single_product', False)"/>
<field name="location_id" invisible="context.get('hide_location', False)"/>
<field name="lot_id" groups="stock.group_production_lot"
@@ -354,6 +366,10 @@
<field name="arch" type="xml">
<tree default_order="location_id, inventory_date, product_id, package_id, lot_id, owner_id" decoration-warning='is_outdated' editable="bottom" create="1" edit="1" import="1" js_class="inventory_report_list" class="o_inventory_adjustments" sample="1">
<header>
<button name="action_inventory_at_date" string="Inventory at Date" type="object"
invisible="((context.get('inventory_mode') and not context.get('inventory_report_mode')) or context.get('no_at_date'))"
class="btn-primary ms-1"
display="always"/>
<button name="stock.action_stock_inventory_adjustement_name" groups="stock.group_stock_manager" type="action" string="Apply"/>
<button name="action_reset" type="object" string="Clear"/>
<button name="stock.action_stock_request_count" groups="stock.group_stock_manager" type="action" string="Request a Count"/>
-1
View File
@@ -44,7 +44,6 @@ Dashboard / Reports for Warehouse Management includes:
'assets': {
'web.assets_backend': [
'stock_account/static/src/js/report_stock_forecasted.js',
'stock_account/static/src/xml/inventory_report.xml',
],
},
'license': 'LGPL-3',
@@ -71,6 +71,24 @@ class StockValuationLayer(models.Model):
'res_id': self.id,
}
def action_valuation_at_date(self):
# Handler called when the user clicked on the 'Valuation at Date' button.
# Opens wizard to display, at choice, the products inventory or a computed
# inventory at a given date.
context = {}
if ("default_product_id" in self.env.context):
context.product_id = self.env.context.default_product_id
elif ("product_tmpl_id" in self.env.context):
context.product_tmpl_id = self.env.context.product_tmpl_id
return {
"res_model": "stock.quantity.history",
"views": [[False, "form"]],
"target": "new",
"type": "ir.actions.act_window",
"context": context,
}
def action_open_reference(self):
self.ensure_one()
if self.stock_move_id:
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<templates id="template" xml:space="preserve">
<t t-inherit="stock.InventoryReport.Buttons" t-inherit-mode="extension" owl="1">
<xpath expr="//button[hasclass('o_button_at_date')]" position="after">
<button t-if="props.resModel == 'stock.valuation.layer'" type="button"
class="btn btn-primary o_button_at_date me-2" t-on-click="onClickInventoryAtDate">
Valuation at Date
</button>
</xpath>
</t>
</templates>
@@ -44,6 +44,12 @@
<tree default_order="id desc" create="0"
import="0" js_class="inventory_report_list"
action="action_open_reference" type="object">
<header>
<button name="action_valuation_at_date" string="Valuation at Date" type="object"
invisible="((context.get('inventory_mode') and not context.get('inventory_report_mode')) or context.get('no_at_date'))"
class="btn-primary ms-1"
display="always"/>
</header>
<field name="create_date" string="Date" />
<field name="reference"/>
<button name="action_open_layer" icon="fa-arrow-right" title="Open Valuation Layer" type="object"/>