Просмотр исходного кода

Merge pull request #4407 from netbox-community/4402-plugins-template-content

Closes #4402: Rework template content registration for plugins
Jeremy Stretch 5 лет назад
Родитель
Сommit
59815ea53d

+ 38 - 0
docs/plugins/development.md

@@ -106,6 +106,7 @@ class AnimalSoundsConfig(PluginConfig):
 * `max_version`: Maximum version of NetBox with which the plugin is compatible
 * `middleware`: A list of middleware classes to append after NetBox's build-in middleware.
 * `caching_config`: Plugin-specific cache configuration
+* `template_content`: The dotted path to the list of template content classes (default: `template_content.template_contnet`)
 * `menu_items`: The dotted path to the list of menu items provided by the plugin (default: `navigation.menu_items`)
 
 ### Install the Plugin for Development
@@ -305,3 +306,40 @@ A `PluginNavMenuButton` has the following attributes:
 * `color` - Button color (one of the choices provided by `ButtonColorChoices`)
 * `icon_class` - Button icon CSS class
 * `permission` - The name of the permission required to display this button (optional)
+
+## Template Content
+
+Plugins can inject custom content into certain areas of the detail views of applicable models. This is accomplished by subclassing `PluginTemplateContent`, designating a particular NetBox model, and defining the desired methods to render custom content. Four methods are available:
+
+* `left_page()` - Inject content on the left side of the page
+* `right_page()` - Inject content on the right side of the page
+* `full_width_page()` - Inject content across the entire bottom of the page
+* `buttons()` - Add buttons to the top of the page
+
+Each of these methods must return HTML content suitable for inclusion in the object template. Two instance attributes are available for context:
+
+* `self.obj` - The object being viewed
+* `self.context` - The current template context
+
+Additionally, a `render()` method is available for convenience. This method accepts the name of a template to render, and any additional context data. Its use is optional, however.
+
+Declared subclasses should be gathered into a list or tuple for integration with NetBox. By default, NetBox looks for an iterable named `template_content` within a `template_content.py` file. (This can be overridden by setting `template_content` to a custom value on the plugin's PluginConfig.) An example is below.
+
+```python
+from extras.plugins import PluginTemplateContent
+
+class AddSiteAnimal(PluginTemplateContent):
+    model = 'dcim.site'
+
+    def full_width_page(self):
+        return self.render('netbox_animal_sounds/site.html')
+
+class AddRackAnimal(PluginTemplateContent):
+    model = 'dcim.rack'
+
+    def left_page(self):
+        extra_data = {'foo': 123}
+        return self.render('netbox_animal_sounds/rack.html', extra_data)
+
+template_content_classes = [AddSiteAnimal, AddRackAnimal]
+```

+ 24 - 32
netbox/extras/plugins/__init__.py

@@ -6,10 +6,10 @@ from django.template.loader import get_template
 from django.utils.module_loading import import_string
 
 from extras.registry import registry
-from .signals import register_detail_page_content_classes
 
 
 # Initialize plugin registry stores
+registry['plugin_template_content_classes'] = collections.defaultdict(list)
 registry['plugin_nav_menu_links'] = {}
 
 
@@ -48,10 +48,18 @@ class PluginConfig(AppConfig):
 
     # Default integration paths. Plugin authors can override these to customize the paths to
     # integrated components.
+    template_content_classes = 'template_content.template_content_classes'
     menu_items = 'navigation.menu_items'
 
     def ready(self):
 
+        # Register template content
+        try:
+            class_list = import_string(f"{self.__module__}.{self.template_content_classes}")
+            register_template_content_classes(class_list)
+        except ImportError:
+            pass
+
         # Register navigation menu items (if defined)
         try:
             menu_items = import_string(f"{self.__module__}.{self.menu_items}")
@@ -67,7 +75,7 @@ class PluginConfig(AppConfig):
 class PluginTemplateContent:
     """
     This class is used to register plugin content to be injected into core NetBox templates.
-    It contains methods that are overriden by plugin authors to return template content.
+    It contains methods that are overridden by plugin authors to return template content.
 
     The `model` attribute on the class defines the which model detail page this class renders
     content for. It should be set as a string in the form '<app_label>.<model_name>'.
@@ -80,8 +88,8 @@ class PluginTemplateContent:
 
     def render(self, template, extra_context=None):
         """
-        Convenience menthod for rendering the provided template name. The detail page object is automatically
-        passed into the template context as `obj` and the origional detail page's context is available as
+        Convenience method for rendering the provided template name. The detail page object is automatically
+        passed into the template context as `obj` and the original detail page's context is available as
         `obj_context`. An additional context dictionary may be passed as `extra_context`.
         """
         context = {
@@ -123,36 +131,20 @@ class PluginTemplateContent:
         raise NotImplementedError
 
 
-def register_content_classes():
-    """
-    Helper method that populates the registry with all template content classes that have been registered by plugins
-    """
-    registry['plugin_template_content_classes'] = collections.defaultdict(list)
-
-    responses = register_detail_page_content_classes.send('registration_event')
-    for receiver, response in responses:
-        if not isinstance(response, list):
-            response = [response]
-        for template_class in response:
-            if not inspect.isclass(template_class):
-                raise TypeError('Plugin content class {} was passes as an instance!'.format(template_class))
-            if not issubclass(template_class, PluginTemplateContent):
-                raise TypeError('{} is not a subclass of extras.plugins.PluginTemplateContent!'.format(template_class))
-            if template_class.model is None:
-                raise TypeError('Plugin content class {} does not define a valid model!'.format(template_class))
-
-            registry['plugin_template_content_classes'][template_class.model].append(template_class)
-
-
-def get_content_classes(model):
+def register_template_content_classes(class_list):
     """
-    Given a model string, return the list of all registered template content classes.
-    Populate the registry if it is empty.
+    Register a list of PluginTemplateContent classes
     """
-    if 'plugin_template_content_classes' not in registry:
-        register_content_classes()
-
-    return registry['plugin_template_content_classes'].get(model, [])
+    # Validation
+    for template_content_class in class_list:
+        if not inspect.isclass(template_content_class):
+            raise TypeError('Plugin content class {} was passes as an instance!'.format(template_content_class))
+        if not issubclass(template_content_class, PluginTemplateContent):
+            raise TypeError('{} is not a subclass of extras.plugins.PluginTemplateContent!'.format(template_content_class))
+        if template_content_class.model is None:
+            raise TypeError('Plugin content class {} does not define a valid model!'.format(template_content_class))
+
+        registry['plugin_template_content_classes'][template_content_class.model].append(template_content_class)
 
 
 #

+ 3 - 9
netbox/extras/plugins/signals.py

@@ -3,7 +3,9 @@ from django.dispatch.dispatcher import NO_RECEIVERS
 
 
 class PluginSignal(django.dispatch.Signal):
-
+    """
+    FUTURE USE
+    """
     def _sorted_receivers(self, sender):
         orig_list = self._live_receivers(sender)
         sorted_list = sorted(
@@ -24,11 +26,3 @@ class PluginSignal(django.dispatch.Signal):
             response = receiver(signal=self, sender=sender, **kwargs)
             responses.append((receiver, response))
         return responses
-
-
-"""
-This signal collects template content classes which render content for object detail pages
-"""
-register_detail_page_content_classes = PluginSignal(
-    providing_args=[]
-)

+ 3 - 4
netbox/extras/templatetags/plugins.py

@@ -1,9 +1,7 @@
 from django import template as template_
-from django.template.loader import get_template
 from django.utils.safestring import mark_safe
 
-from extras.plugins import get_content_classes
-
+from extras.registry import registry
 
 register = template_.Library()
 
@@ -15,7 +13,8 @@ def _get_registered_content(obj, method, context):
     """
     html = ''
 
-    plugin_template_classes = get_content_classes(obj._meta.label_lower)
+    model_name = obj._meta.label_lower
+    plugin_template_classes = registry['plugin_template_content_classes'].get(model_name, [])
     for plugin_template_class in plugin_template_classes:
         plugin_template_renderer = plugin_template_class(obj, context)
         try: