feat: implement DashboardWidgetProvider pattern for modular dashboard widgets
Add protocol-based widget system following the MetricsProvider pattern: - Create DashboardWidgetProviderProtocol in contracts/widgets.py - Add WidgetAggregatorService in core to discover and aggregate widgets - Implement MarketplaceWidgetProvider for recent_imports widget - Implement TenancyWidgetProvider for recent_vendors widget - Update admin dashboard to use widget_aggregator - Add widget_provider field to ModuleDefinition Architecture documentation: - Add widget-provider-pattern.md with implementation guide - Add cross-module-import-rules.md enforcing core/optional separation - Update module-system.md with widget_provider and import rules This enables modules to provide rich dashboard widgets without core modules importing from optional modules, maintaining true module independence. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,7 @@ if TYPE_CHECKING:
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.modules.contracts.metrics import MetricsProviderProtocol
|
||||
from app.modules.contracts.widgets import DashboardWidgetProviderProtocol
|
||||
|
||||
from app.modules.enums import FrontendType
|
||||
|
||||
@@ -413,6 +414,26 @@ class ModuleDefinition:
|
||||
# The provider will be discovered by core's StatsAggregator service.
|
||||
metrics_provider: "Callable[[], MetricsProviderProtocol] | None" = None
|
||||
|
||||
# =========================================================================
|
||||
# Widget Provider (Module-Driven Dashboard Widgets)
|
||||
# =========================================================================
|
||||
# Callable that returns a DashboardWidgetProviderProtocol implementation.
|
||||
# Use a callable (factory function) to enable lazy loading and avoid
|
||||
# circular imports. Each module can provide its own widgets for dashboards.
|
||||
#
|
||||
# Example:
|
||||
# def _get_widget_provider():
|
||||
# from app.modules.orders.services.order_widgets import order_widget_provider
|
||||
# return order_widget_provider
|
||||
#
|
||||
# orders_module = ModuleDefinition(
|
||||
# code="orders",
|
||||
# widget_provider=_get_widget_provider,
|
||||
# )
|
||||
#
|
||||
# The provider will be discovered by core's WidgetAggregator service.
|
||||
widget_provider: "Callable[[], DashboardWidgetProviderProtocol] | None" = None
|
||||
|
||||
# =========================================================================
|
||||
# Menu Item Methods (Legacy - uses menu_items dict of IDs)
|
||||
# =========================================================================
|
||||
@@ -799,6 +820,28 @@ class ModuleDefinition:
|
||||
return None
|
||||
return self.metrics_provider()
|
||||
|
||||
# =========================================================================
|
||||
# Widget Provider Methods
|
||||
# =========================================================================
|
||||
|
||||
def has_widget_provider(self) -> bool:
|
||||
"""Check if this module has a widget provider."""
|
||||
return self.widget_provider is not None
|
||||
|
||||
def get_widget_provider_instance(self) -> "DashboardWidgetProviderProtocol | None":
|
||||
"""
|
||||
Get the widget provider instance for this module.
|
||||
|
||||
Calls the widget_provider factory function to get the provider.
|
||||
Returns None if no provider is configured.
|
||||
|
||||
Returns:
|
||||
DashboardWidgetProviderProtocol instance, or None
|
||||
"""
|
||||
if self.widget_provider is None:
|
||||
return None
|
||||
return self.widget_provider()
|
||||
|
||||
# =========================================================================
|
||||
# Magic Methods
|
||||
# =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user