fix(js): resolve create button conflict between Programs and Change Request#28
Conversation
…equest Both spp_programs and spp_change_request_v2 used position="replace" on the same ListView control-panel-create-button slot. Whichever loaded last overwrote the other, causing Programs to lose its "Create Program" button. Move the generic extensible create button pattern to spp_base_common so any module can set customListCreateButton config and override onCustomListCreate() without conflicting. Same fix applied for FormView hideFormCreateButton.
Summary of ChangesHello @emjay0921, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a long-standing UI conflict in Odoo's frontend where multiple modules attempting to customize the 'create' button in list and form views would inadvertently override each other. By refactoring the custom button logic into a shared base module, it establishes a robust and extensible pattern for module-specific create actions. This change ensures that each module can define its unique create button behavior without interfering with others, leading to a more stable and predictable user interface across the application. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request effectively resolves the button conflict between spp_programs and spp_change_request_v2 by introducing a generic, extensible pattern in spp_base_common. The solution is well-designed, centralizing the logic and making it reusable for other modules. The use of Odoo's patching mechanism is correct and robust. I've added a few suggestions to further refine the new generic pattern, making it more data-driven and reducing boilerplate code in the consuming modules. Overall, this is a great improvement to the codebase.
| patch(ListController.prototype, { | ||
| async onCustomListCreate() { | ||
| // Base no-op — overridden by consuming modules | ||
| }, | ||
| }); |
There was a problem hiding this comment.
The base onCustomListCreate is a no-op, which requires every consuming module to patch it with very similar logic (check model, call action). This pattern can be improved to be more data-driven, which would reduce boilerplate code in the modules that use it.
Consider moving the action-calling logic into this base method. The specific action to be called can then be defined as a property of the customListCreateButton configuration object. This would make the pattern more declarative and easier to use.
patch(ListController.prototype, {
setup() {
super.setup(...arguments);
// Ensure actionService is available for onCustomListCreate and other potential patches.
if (!this.actionService) {
this.actionService = useService("action");
}
},
async onCustomListCreate() {
if (this.customListCreateButton?.action) {
await this.actionService.doAction(this.customListCreateButton.action, {
onClose: async () => {
await this.model.root.load();
},
});
} else if (this.customListCreateButton) {
// Fallback for modules that still override this method and don't provide an action.
// This maintains backward compatibility with the original proposed pattern.
return super.onCustomListCreate(...arguments);
}
},
});| patch(ListController.prototype, { | ||
| setup() { | ||
| super.setup(); | ||
| this.actionService = useService("action"); | ||
| onWillStart(async () => { | ||
| // Check if user has permission to create programs | ||
| // Groups: spp_security.group_spp_admin OR spp_programs.group_programs_manager | ||
| if (this.model.root.resModel !== "spp.program") { | ||
| return; | ||
| } | ||
| const is_admin = await user.hasGroup("spp_security.group_spp_admin"); | ||
| const is_program_manager = await user.hasGroup("spp_programs.group_programs_manager"); | ||
| this.canCreateProgram = is_admin || is_program_manager; | ||
| const is_program_manager = await user.hasGroup( | ||
| "spp_programs.group_programs_manager" | ||
| ); | ||
| if (is_admin || is_program_manager) { | ||
| this.customListCreateButton = { | ||
| label: "Create Program", | ||
| title: "Create a New Program", | ||
| className: "o_list_button_add_program", | ||
| }; | ||
| } | ||
| }); | ||
| }, | ||
|
|
||
| /** | ||
| * Opens the Create Program wizard when the "Create Program" button is clicked. | ||
| * This is called from the custom button in the Programs list view. | ||
| * Opens the Create Program wizard when the custom button is clicked. | ||
| */ | ||
| async load_wizard() { | ||
| // Only proceed if we're on the spp.program model | ||
| if (this.model.root.resModel !== "spp.program") { | ||
| async onCustomListCreate() { | ||
| if (this.model.root.resModel === "spp.program") { | ||
| await this.actionService.doAction( | ||
| "spp_programs.action_create_program_wizard", | ||
| { | ||
| onClose: async () => { | ||
| await this.model.root.load(); | ||
| }, | ||
| } | ||
| ); | ||
| return; | ||
| } | ||
| return super.onCustomListCreate(...arguments); | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Following the suggestion to make the onCustomListCreate handler in spp_base_common data-driven, this patch can be greatly simplified. You can remove the onCustomListCreate override and instead provide the wizard's action name within the customListCreateButton configuration object. This change reduces boilerplate and centralizes the action-handling logic in the base module, making it easier to maintain.
patch(ListController.prototype, {
setup() {
super.setup(...arguments);
this.actionService = useService("action");
onWillStart(async () => {
if (this.model.root.resModel !== "spp.program") {
return;
}
const is_admin = await user.hasGroup("spp_security.group_spp_admin");
const is_program_manager = await user.hasGroup(
"spp_programs.group_programs_manager"
);
if (is_admin || is_program_manager) {
this.customListCreateButton = {
label: "Create Program",
title: "Create a New Program",
className: "o_list_button_add_program",
action: "spp_programs.action_create_program_wizard",
};
}
});
},
});
Why is this change needed?
Both
spp_programsandspp_change_request_v2usedposition="replace"on the same ListViewcontrol-panel-create-buttonslot. Whichever OWL template loaded last overwrote the other, causing Programs to lose its "Create Program" button (showing generic "New" instead). Same conflict existed for the FormView create button hide.How was the change implemented?
spp_base_common(shared dependency of both modules)custom_list_create_template.xml: Single slot replacement that checkscustomListCreateButtonconfig object; also provideshideFormCreateButtonflag for FormViewcustom_list_create.js: Base no-oponCustomListCreate()handler on ListControllerspp_programsandspp_change_request_v2now setcustomListCreateButtonconfig and overrideonCustomListCreate()via patch for their respective modelshideFormCreateButton = truefor its modelNew unit tests
Unit tests executed by the author
How to test manually
spp_programsandspp_change_request_v2spp_change_request_v2withoutspp_programs— "New Request" should still workRelated links