From ea4cdf68f4f592bb0a58748857b601d707bffa5c Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Sat, 25 Oct 2025 14:38:31 +0200 Subject: [PATCH] feat: warn on multiple class overrides (#34169) --- frappe/installer.py | 8 ++++++++ frappe/migrate.py | 16 +++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/frappe/installer.py b/frappe/installer.py index fdfae9daf6..f0827d5315 100644 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -296,6 +296,14 @@ def install_app(name, verbose=False, set_as_patched=True, force=False): print(f"\nInstalling {name}...") + other_class_overrides = frappe.get_hooks("override_doctype_class") + if ( + other_class_overrides + and app_hooks.override_doctype_class + and any(dt in app_hooks.override_doctype_class for dt in other_class_overrides) + ): + click.secho(f"App {name} overrides a doctype that is already overridden by another app.", fg="yellow") + if name != "frappe": frappe.only_for("System Manager") diff --git a/frappe/migrate.py b/frappe/migrate.py index 9a7a4adc2d..623fa1bcbe 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -7,8 +7,11 @@ import json import os import threading import time +from collections import defaultdict from textwrap import dedent +import click + import frappe import frappe.model.sync import frappe.modules.patch_handler @@ -24,7 +27,7 @@ from frappe.modules.utils import sync_customizations from frappe.search.website_search import build_index_for_all_routes from frappe.utils.connections import check_connection from frappe.utils.dashboard import sync_dashboards -from frappe.utils.data import cint +from frappe.utils.data import cint, comma_and from frappe.utils.fixtures import sync_fixtures from frappe.website.utils import clear_website_cache @@ -115,10 +118,21 @@ class SiteMigration: @atomic def pre_schema_updates(self): """Executes `before_migrate` hooks""" + overrides = defaultdict(list) for app in frappe.get_installed_apps(): for fn in frappe.get_hooks("before_migrate", app_name=app): frappe.get_attr(fn)() + for doctype in frappe.get_hooks("override_doctype_class", {}, app_name=app).keys(): + overrides[doctype].append(app) + + for doctype, app_names in overrides.items(): + if len(app_names) > 1: + click.secho( + f"The controller for {doctype} is overridden by multiple apps: {comma_and(app_names, add_quotes=False)}.", + fg="yellow", + ) + @atomic def run_schema_updates(self): """Run patches as defined in patches.txt, sync schema changes as defined in the {doctype}.json files"""