New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow plugins to replace core views #1466
Comments
Interesting idea! This would introduce a new way that plugins can potentially conflict with one another, i.e. what should happen if two different plugins both attempt to override the same core view? |
This is certainly an advanced feature and I envision it really only being used by an org's internally developed plugins. I would be fine with the order within |
It was excitingly easy to get this off the ground... diff --git a/nautobot/extras/plugins/__init__.py b/nautobot/extras/plugins/__init__.py
index 3f78020dc..180ab94de 100644
--- a/nautobot/extras/plugins/__init__.py
+++ b/nautobot/extras/plugins/__init__.py
@@ -7,7 +7,7 @@ from packaging import version
from django.core.exceptions import ValidationError
from django.template.loader import get_template
-from django.urls import URLPattern
+from django.urls import get_resolver, URLPattern
from nautobot.core.apps import (
NautobotConfig,
@@ -94,6 +94,7 @@ class PluginConfig(NautobotConfig):
menu_items = "navigation.menu_items"
secrets_providers = "secrets.secrets_providers"
template_extensions = "template_content.template_extensions"
+ override_views = "views.override_views"
def ready(self):
"""Callback after plugin app is loaded."""
@@ -190,6 +191,11 @@ class PluginConfig(NautobotConfig):
f"{filter_extension.model} -> {filterform_field_name}"
)
+ # Register override view (if any)
+ override_views = import_object(f"{self.__module__}.{self.override_views}")
+ if override_views is not None:
+ register_override_views(override_views, self.name)
+
@classmethod
def validate(cls, user_config, nautobot_version):
"""Validate the user_config for baseline correctness."""
@@ -603,3 +609,29 @@ def register_custom_validators(class_list):
raise TypeError(f"PluginCustomValidator class {custom_validator} does not define a valid model!")
registry["plugin_custom_validators"][custom_validator.model].append(custom_validator)
+
+
+#
+# Override views
+#
+
+def register_override_views(override_views, plugin):
+ resolver = get_resolver()
+
+ for qualified_view_name, view in override_views.items():
+ try:
+ app_name, view_name = qualified_view_name.split(":")
+ except ValueError:
+ raise ValidationError(
+ f"Plugin {plugin} tried to override view {qualified_view_name} but only top level namespace views are supported (e.g. `dcim:device`)."
+ )
+
+ app_resolver = resolver.namespace_dict.get(app_name)
+ if not app_resolver:
+ raise ValidationError(
+ f"Plugin {plugin} tried to override view {qualified_view_name} but {app_name} is not a valid core app."
+ )
+
+ for pattern in app_resolver[1].url_patterns:
+ if isinstance(pattern, URLPattern) and hasattr(pattern, "name") and pattern.name == view_name:
+ pattern.callback = view Plugin from django.http import HttpResponse
from django.views import View
class DeviceOverrideView(View):
def get(self, request, *args, **kwargs):
return HttpResponse("Hello world! I'm a view provided by a plugin to override the `dcim:device` view.")
override_views = {
"dcim:device": DeviceOverrideView.as_view()
} |
Because of the way we have namespacing currently setup (for better or worse), this will work for DRF API routes too.
|
Completed in #1957. |
As ...
P.D. - Plugin Developer
I want ...
A plugin to define a view that can functionally replace a core view by changing which view is routed to in the URL path from
urlpatterns
So that ...
I can provide functionality in a plugin that entirely replaces the implementation of a core view. For example, I may want to drastically change the look and feel of the IPAM prefix-list or detail views by completely suppressing certain model fields from table columns or filter fields. I may also want to provide my own filterset and filter form.
Plugin developers are doing this today by having a plugin provide a new view under its own URL namespace and using hacks in the permission framework to "hide" the existing views from navigation. This is very cumbersome as it requires the usage of custom permission actions and the unnecessary implementation of other related model views to override things like return URLs. Even with this hacky implementation, consider that a user may still be able to access the core view by navigating through other areas of the UI. For example, accessing the existing Prefix view by navigating through IP Addresses. This creates very bad UX.
I know this is done when...
Optional - Feature groups this request pertains to.
Database Changes
No response
External Dependencies
No response
The text was updated successfully, but these errors were encountered: