From 566c1fd36ef71909bca8d1f12af14c75c2c99eff Mon Sep 17 00:00:00 2001 From: Vladimir Sidorenko Date: Sat, 24 May 2025 17:35:42 +0100 Subject: [PATCH] feat: automatically convert standard model admins for unfold --- src/unfold/contrib/convert/__init__.py | 0 src/unfold/contrib/convert/apps.py | 16 ++++ .../contrib/convert/convert_model_admin.py | 84 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 src/unfold/contrib/convert/__init__.py create mode 100644 src/unfold/contrib/convert/apps.py create mode 100644 src/unfold/contrib/convert/convert_model_admin.py diff --git a/src/unfold/contrib/convert/__init__.py b/src/unfold/contrib/convert/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/unfold/contrib/convert/apps.py b/src/unfold/contrib/convert/apps.py new file mode 100644 index 00000000..fc888374 --- /dev/null +++ b/src/unfold/contrib/convert/apps.py @@ -0,0 +1,16 @@ +from typing import override + +from django.apps import AppConfig + + +class DefaultAppConfig(AppConfig): + name = "unfold.contrib.convert" + label = "unfold_convert" + + @override + def ready(self) -> None: + from unfold.contrib.convert.convert_model_admin import ( + convert_model_admin_to_unfold, + ) + + convert_model_admin_to_unfold() diff --git a/src/unfold/contrib/convert/convert_model_admin.py b/src/unfold/contrib/convert/convert_model_admin.py new file mode 100644 index 00000000..e4545835 --- /dev/null +++ b/src/unfold/contrib/convert/convert_model_admin.py @@ -0,0 +1,84 @@ +from django.contrib import admin + +from unfold.admin import ModelAdmin, StackedInline, TabularInline + + +def convert_model_admin_to_unfold(admin_site: admin.AdminSite | None = None) -> None: + """ + Automatically convert all registered admin classes to work with django-unfold. + + This function: + 1. Finds all admin classes that are not subclasses of unfold.admin.ModelAdmin + 2. Unregisters them + 3. Creates new classes that inherit from both the original admin and unfold.admin.ModelAdmin + 4. Fixes any inlines by creating new classes that inherit from unfold inline classes + 5. Registers the new classes + + Args: + admin_site: The admin site to convert. Defaults to django.contrib.admin.site + + """ + + admin_site = admin_site or admin.site + + # Get a copy of the registry to avoid modification during iteration + registry_items = list(admin_site._registry.items()) + + for model, model_admin in registry_items: + # Skip if already using unfold.admin.ModelAdmin + if isinstance(model_admin, ModelAdmin): + continue + + # Get the original admin class + original_admin_class = model_admin.__class__ + + # Create a new admin class that inherits from both original and unfold ModelAdmin + new_admin_attrs = {} + + # Handle inlines if they exist + if getattr(model_admin, "inlines", None): + fixed_inlines = [ + _make_inline(inline_class) for inline_class in model_admin.inlines + ] + new_admin_attrs["inlines"] = tuple(fixed_inlines) + + # Create the new admin class + new_admin_name = f"Unfold{original_admin_class.__name__}" + new_admin_class = type( + new_admin_name, + (ModelAdmin, original_admin_class), + new_admin_attrs, + ) + + # Unregister the old admin and register the new one + admin_site.unregister(model) + admin_site.register(model, new_admin_class) + + +def _make_inline(inline_class: type) -> type: + if issubclass(inline_class, (TabularInline, StackedInline)): + return inline_class + + # Determine which unfold inline base to use + unfold_base = None + if hasattr(inline_class, "__bases__"): + for base in inline_class.__bases__: + if hasattr(base, "__name__"): + if "TabularInline" in base.__name__: + unfold_base = TabularInline + break + if "StackedInline" in base.__name__: + unfold_base = StackedInline + break + + # Default to TabularInline if we can't determine the type + if unfold_base is None: + unfold_base = TabularInline + + # Create new inline class + new_inline_name = f"Unfold{inline_class.__name__}" + return type( + new_inline_name, + (inline_class, unfold_base), + {}, + )