Skip to content

fix(js): resolve create button conflict between Programs and Change Request#28

Merged
gonzalesedwin1123 merged 1 commit into19.0from
fix/custom-list-create-button-conflict
Feb 11, 2026
Merged

fix(js): resolve create button conflict between Programs and Change Request#28
gonzalesedwin1123 merged 1 commit into19.0from
fix/custom-list-create-button-conflict

Conversation

@emjay0921
Copy link
Contributor

Why is this change needed?

Both spp_programs and spp_change_request_v2 used position="replace" on the same ListView control-panel-create-button slot. 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?

  • Moved the generic extensible create button pattern to spp_base_common (shared dependency of both modules)
  • custom_list_create_template.xml: Single slot replacement that checks customListCreateButton config object; also provides hideFormCreateButton flag for FormView
  • custom_list_create.js: Base no-op onCustomListCreate() handler on ListController
  • Both spp_programs and spp_change_request_v2 now set customListCreateButton config and override onCustomListCreate() via patch for their respective models
  • Each module also patches FormController to set hideFormCreateButton = true for its model
  • Templates in both modules emptied (no more competing slot replacements)

New unit tests

Unit tests executed by the author

How to test manually

  1. Install both spp_programs and spp_change_request_v2
  2. Navigate to Programs list — should show "Create Program" button (not "New")
  3. Navigate to Change Requests list — should show "New Request" button (not "New")
  4. Navigate to any other list view — should show default "New" button
  5. Install only spp_change_request_v2 without spp_programs — "New Request" should still work

Related links

…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.
@gemini-code-assist
Copy link

Summary of Changes

Hello @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

  • Conflict Resolution: Resolved a UI conflict where spp_programs and spp_change_request_v2 modules were overwriting each other's custom create buttons due to both using position="replace" on the same ListView slot.
  • Centralized Custom Button Pattern: Introduced a generic, extensible pattern for custom create buttons and form view create button hiding, moving the core logic to the spp_base_common module.
  • Module Adaptation: Updated spp_programs and spp_change_request_v2 to utilize the new centralized pattern by configuring customListCreateButton and overriding onCustomListCreate() via patches, and patching FormController to hide the default create button.
  • Template Simplification: Emptied the conflicting ListView and FormView template extensions in spp_programs and spp_change_request_v2, as their functionality is now handled by the common base module.
Changelog
  • spp_base_common/manifest.py
    • Added spp_base_common/static/src/js/custom_list_create.js to assets.
    • Added spp_base_common/static/src/xml/custom_list_create_template.xml to assets.
  • spp_base_common/static/src/js/custom_list_create.js
    • Added a new JavaScript file to define a base onCustomListCreate method for ListController.
  • spp_base_common/static/src/xml/custom_list_create_template.xml
    • Added a new XML file defining a generic extensible create button for ListView.
    • Added a mechanism to hide the FormView create button based on a hideFormCreateButton flag.
  • spp_change_request_v2/static/src/js/create_change_request.js
    • Imported FormController.
    • Modified ListController patch to set customListCreateButton configuration and override onCustomListCreate for spp.change.request model.
    • Added a new patch for FormController to set hideFormCreateButton = true for spp.change.request model.
  • spp_change_request_v2/static/src/xml/create_change_request_template.xml
    • Removed the previous ListView slot replacement for the custom create button.
    • Removed the previous FormView attribute modification to hide the create button.
  • spp_programs/static/src/js/create_program.js
    • Imported FormController and registry.
    • Added a new client action openProgramCloseModal to handle closing modals and opening program forms.
    • Modified ListController patch to set customListCreateButton configuration and override onCustomListCreate for spp.program model.
    • Added a new patch for FormController to set hideFormCreateButton = true for spp.program model.
  • spp_programs/static/src/xml/create_program_template.xml
    • Removed the previous ListView slot replacement for the custom create button.
    • Removed the previous FormView attribute modification to hide the create button.
Activity
  • The pull request was initiated to resolve a conflict in custom create button rendering between spp_programs and spp_change_request_v2 modules.
  • The author provided a detailed explanation of the problem, the implementation strategy, and comprehensive manual testing instructions.
  • The solution involves centralizing the custom button logic in a shared base module (spp_base_common) to prevent future conflicts and promote reusability.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gonzalesedwin1123 gonzalesedwin1123 merged commit d9d9b66 into 19.0 Feb 11, 2026
26 of 27 checks passed
@gonzalesedwin1123 gonzalesedwin1123 deleted the fix/custom-list-create-button-conflict branch February 11, 2026 03:04
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +21 to +25
patch(ListController.prototype, {
async onCustomListCreate() {
// Base no-op — overridden by consuming modules
},
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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);
        }
    },
});

Comment on lines 38 to +77
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);
},
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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",
                };
            }
        });
    },
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants