From 3750659c37b68f23cd882da8152a77d0a5452927 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 10 Oct 2022 12:25:53 -0400 Subject: [PATCH] Update docs & improve plugin support --- docs/plugins/development/search.md | 30 ++++++++++++++---------------- netbox/extras/plugins/__init__.py | 4 ++-- netbox/netbox/search/__init__.py | 8 +++++++- netbox/netbox/search/backends.py | 22 +++++++++++++++++----- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/docs/plugins/development/search.md b/docs/plugins/development/search.md index 73d515485..13edd4527 100644 --- a/docs/plugins/development/search.md +++ b/docs/plugins/development/search.md @@ -1,31 +1,29 @@ # Search -If you want your plugin models to appear in search results, you will need to create a search index for the models. Search indexes must be in defined in a search_indexes.py file. - -As an example, lets define a search_index for the MyModel object defined before: +Plugins can define and register their own models to extend NetBox's core search functionality. Typically, a plugin will include a file named `search.py`, which holds all search indexes for its models (see the example below). ```python -# search_indexes.py -from .filters import MyFilterSet +# search.py +from netbox.search import SearchMixin +from .filters import MyModelFilterSet from .tables import MyModelTable from .models import MyModel -from search.models import SearchMixin - class MyModelIndex(SearchMixin): model = MyModel queryset = MyModel.objects.all() filterset = MyModelFilterSet table = MyModelTable - url = 'plugins:netbox_mymodel:mymodel_list' - choice_header = 'MyModel' + url = 'plugins:myplugin:mymodel_list' ``` -All the fields must be defined as follows: +To register one or more indexes with NetBox, define a list named `indexes` at the end of this file: -* `model` - The model that will be searched (see: [Models](./models.md)). -* `queryset` - The queryset on the model that will be passed to the filterset. -* `filterset` - The filterset for the model that contains the search method (see: [Filters & Filter Sets](./filtersets.md)). -* `table` - Table that is used in the list view (see: (see: [Tables](./tables.md)). -* `url` - URL to the list view to show search results. -* `choice_header` - The header that will appear in the search drop-down to group menu items together. +```python +indexes = [MyModelIndex] +``` + +!!! tip + The path to the list of search indexes can be modified by setting `search_indexes` in the PluginConfig instance. + +::: netbox.search.SearchIndex diff --git a/netbox/extras/plugins/__init__.py b/netbox/extras/plugins/__init__.py index acab4f966..78a056216 100644 --- a/netbox/extras/plugins/__init__.py +++ b/netbox/extras/plugins/__init__.py @@ -61,7 +61,7 @@ class PluginConfig(AppConfig): # Default integration paths. Plugin authors can override these to customize the paths to # integrated components. - search = 'search.indexes' + search_indexes = 'search.indexes' graphql_schema = 'graphql.schema' menu = 'navigation.menu' menu_items = 'navigation.menu_items' @@ -72,7 +72,7 @@ class PluginConfig(AppConfig): plugin_name = self.name.rsplit('.', 1)[-1] # Search extensions - search_indexes = import_object(f"{self.__module__}.{self.search}") or [] + search_indexes = import_object(f"{self.__module__}.{self.search_indexes}") or [] for idx in search_indexes: register_search()(idx) diff --git a/netbox/netbox/search/__init__.py b/netbox/netbox/search/__init__.py index 2fcf533d5..0664dc6ca 100644 --- a/netbox/netbox/search/__init__.py +++ b/netbox/netbox/search/__init__.py @@ -4,11 +4,17 @@ from extras.registry import registry class SearchIndex: """ Base class for building search indexes. + + Attrs: + model: The model class for which this index is used. """ - search_index = True + model = None @classmethod def get_category(cls): + """ + Return the title of the search category under which this model is registered. + """ if hasattr(cls, 'category'): return cls.category return cls.model._meta.app_config.verbose_name diff --git a/netbox/netbox/search/backends.py b/netbox/netbox/search/backends.py index 0a87f8be1..fa1d06e96 100644 --- a/netbox/netbox/search/backends.py +++ b/netbox/netbox/search/backends.py @@ -71,7 +71,7 @@ class SearchBackend(object): raise NotImplementedError -class PostgresIcontainsSearchBackend(SearchBackend): +class FilterSetSearchBackend(SearchBackend): def search(self, request, search_text): results = [] @@ -79,11 +79,23 @@ class PostgresIcontainsSearchBackend(SearchBackend): search_registry = self.get_registry() for obj_type in search_registry.keys(): - queryset = search_registry[obj_type].queryset.restrict(request.user, 'view') - filterset = search_registry[obj_type].filterset - table = search_registry[obj_type].table + queryset = search_registry[obj_type].queryset url = search_registry[obj_type].url + # Restrict the queryset for the current user + if hasattr(queryset, 'restrict'): + queryset = queryset.restrict(request.user, 'view') + + filterset = getattr(search_registry[obj_type], 'filterset', None) + if not filterset: + # This backend requires a FilterSet class for the model + continue + + table = getattr(search_registry[obj_type], 'table', None) + if not table: + # This backend requires a Table class for the model + continue + # Construct the results table for this object type filtered_queryset = filterset({'q': search_text}, queryset=queryset).qs table = table(filtered_queryset, orderable=False) @@ -103,7 +115,7 @@ def get_backend(backend_name=None): """Initializes and returns the search backend.""" global _backends_cache if not backend_name: - backend_name = getattr(settings, "SEARCH_BACKEND", "netbox.search.backends.PostgresIcontainsSearchBackend") + backend_name = getattr(settings, "SEARCH_BACKEND", "netbox.search.backends.FilterSetSearchBackend") # Try to use the cached backend. if backend_name in _backends_cache: