-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
397 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
import dcim.filtersets | ||
import dcim.tables | ||
from dcim.models import ( | ||
Cable, | ||
Device, | ||
DeviceType, | ||
Interface, | ||
Location, | ||
Module, | ||
ModuleType, | ||
PowerFeed, | ||
Rack, | ||
RackReservation, | ||
Site, | ||
VirtualChassis, | ||
) | ||
from django.db import models | ||
from search.models import SearchMixin | ||
|
||
|
||
class SiteIndex(SearchMixin): | ||
model = Site | ||
queryset = Site.objects.prefetch_related('region', 'tenant', 'tenant__group') | ||
filterset = dcim.filtersets.SiteFilterSet | ||
table = dcim.tables.SiteTable | ||
url = 'dcim:site_list' | ||
|
||
|
||
class RackIndex(SearchMixin): | ||
model = Rack | ||
queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'tenant__group', 'role').annotate( | ||
device_count=count_related(Device, 'rack') | ||
) | ||
filterset = dcim.filtersets.RackFilterSet | ||
table = dcim.tables.RackTable | ||
url = 'dcim:rack_list' | ||
|
||
|
||
class RackReservationIndex(SearchMixin): | ||
model = RackReservation | ||
queryset = RackReservation.objects.prefetch_related('rack', 'user') | ||
filterset = dcim.filtersets.RackReservationFilterSet | ||
table = dcim.tables.RackReservationTable | ||
url = 'dcim:rackreservation_list' | ||
|
||
|
||
class LocationIndex(SearchMixin): | ||
model = Site | ||
queryset = Location.objects.add_related_count( | ||
Location.objects.add_related_count(Location.objects.all(), Device, 'location', 'device_count', cumulative=True), | ||
Rack, | ||
'location', | ||
'rack_count', | ||
cumulative=True, | ||
).prefetch_related('site') | ||
filterset = dcim.filtersets.LocationFilterSet | ||
table = dcim.tables.LocationTable | ||
url = 'dcim:location_list' | ||
|
||
|
||
class DeviceTypeIndex(SearchMixin): | ||
model = DeviceType | ||
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate( | ||
instance_count=count_related(Device, 'device_type') | ||
) | ||
filterset = dcim.filtersets.DeviceTypeFilterSet | ||
table = dcim.tables.DeviceTypeTable | ||
url = 'dcim:devicetype_list' | ||
|
||
|
||
class DeviceIndex(SearchMixin): | ||
model = DeviceIndex | ||
queryset = Device.objects.prefetch_related( | ||
'device_type__manufacturer', | ||
'device_role', | ||
'tenant', | ||
'tenant__group', | ||
'site', | ||
'rack', | ||
'primary_ip4', | ||
'primary_ip6', | ||
) | ||
filterset = dcim.filtersets.DeviceFilterSet | ||
table = dcim.tables.DeviceTable | ||
url = 'dcim:device_list' | ||
|
||
|
||
class ModuleTypeIndex(SearchMixin): | ||
model = ModuleType | ||
queryset = ModuleType.objects.prefetch_related('manufacturer').annotate( | ||
instance_count=count_related(Module, 'module_type') | ||
) | ||
filterset = dcim.filtersets.ModuleTypeFilterSet | ||
table = dcim.tables.ModuleTypeTable | ||
url = 'dcim:moduletype_list' | ||
|
||
|
||
class ModuleIndex(SearchMixin): | ||
model = Module | ||
queryset = Module.objects.prefetch_related( | ||
'module_type__manufacturer', | ||
'device', | ||
'module_bay', | ||
) | ||
filterset = dcim.filtersets.ModuleFilterSet | ||
table = dcim.tables.ModuleTable | ||
url = 'dcim:module_list' | ||
|
||
|
||
class VirtualChassisIndex(SearchMixin): | ||
model = VirtualChassis | ||
queryset = VirtualChassis.objects.prefetch_related('master').annotate( | ||
member_count=count_related(Device, 'virtual_chassis') | ||
) | ||
filterset = dcim.filtersets.VirtualChassisFilterSet | ||
table = dcim.tables.VirtualChassisTable | ||
url = 'dcim:virtualchassis_list' | ||
|
||
|
||
class CableIndex(SearchMixin): | ||
model = Cable | ||
queryset = Cable.objects.all() | ||
filterset = dcim.filtersets.CableFilterSet | ||
table = dcim.tables.CableTable | ||
url = 'dcim:cable_list' | ||
|
||
|
||
class PowerFeedIndex(SearchMixin): | ||
model = PowerFeed | ||
queryset = PowerFeed.objects.all() | ||
filterset = dcim.filtersets.PowerFeedFilterSet | ||
table = dcim.tables.PowerFeedTable | ||
url = 'dcim:powerfeed_list' | ||
|
||
|
||
DCIM_SEARCH_ORDERING = [ | ||
SiteIndex, | ||
RackIndex, | ||
RackReservationIndex, | ||
LocationIndex, | ||
DeviceTypeIndex, | ||
DeviceIndex, | ||
ModuleTypeIndex, | ||
ModuleIndex, | ||
VirtualChassisIndex, | ||
CableIndex, | ||
PowerFeedIndex, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from django.apps import AppConfig | ||
|
||
from django.apps import apps | ||
from django.utils.module_loading import module_has_submodule | ||
from netbox import denormalized | ||
|
||
|
||
def get_app_modules(): | ||
""" | ||
Returns all app modules (installed apps) - yields tuples of (app_name, module) | ||
""" | ||
for app in apps.get_app_configs(): | ||
yield app.name, app.module | ||
|
||
|
||
def get_app_submodules(submodule_name): | ||
""" | ||
Searches each app module for the specified submodule - yields tuples of (app_name, module) | ||
""" | ||
for name, module in get_app_modules(): | ||
if module_has_submodule(module, submodule_name): | ||
yield name, import_module(f"{name}.{submodule_name}") | ||
|
||
|
||
class SearchConfig(AppConfig): | ||
name = "search" | ||
verbose_name = "search" | ||
|
||
def ready(self): | ||
for name, module in get_app_modules(): | ||
submodule_name = "search_indexes" | ||
if module_has_submodule(module, submodule_name): | ||
print(f"{name}.{submodule_name}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
from abc import ABC | ||
from importlib import import_module | ||
|
||
from django.conf import settings | ||
from django.core.exceptions import ImproperlyConfigured | ||
from django.db import models | ||
from django.db.models.signals import post_save, pre_delete | ||
|
||
# The cache for the initialized backend. | ||
_backends_cache = {} | ||
|
||
|
||
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", "search.backends.PostgresIcontainsSearchBackend") | ||
|
||
# Try to use the cached backend. | ||
if backend_name in _backends_cache: | ||
return _backends_cache[backend_name] | ||
|
||
# Load the backend class. | ||
backend_module_name, backend_cls_name = backend_name.rsplit(".", 1) | ||
backend_module = import_module(backend_module_name) | ||
try: | ||
backend_cls = getattr(backend_module, backend_cls_name) | ||
except AttributeError: | ||
raise ImproperlyConfigured(f"Could not find a class named {backend_module_name} in {backend_cls_name}") | ||
|
||
# Initialize the backend. | ||
backend = backend_cls() | ||
_backends_cache[backend_name] = backend | ||
return backend | ||
|
||
|
||
class SearchEngineError(Exception): | ||
|
||
"""Something went wrong with a search engine.""" | ||
|
||
|
||
class SearchBackend(object): | ||
|
||
"""A search engine capable of performing multi-table searches.""" | ||
|
||
_created_engines: dict = dict() | ||
|
||
@classmethod | ||
def get_created_engines(cls): | ||
"""Returns all created search engines.""" | ||
return list(cls._created_engines.items()) | ||
|
||
def __init__(self, engine_slug: str): | ||
"""Initializes the search engine.""" | ||
# Check the slug is unique for this project. | ||
if engine_slug in SearchBackend._created_engines: | ||
raise SearchEngineError(f"A search engine has already been created with the slug {engine_slug}") | ||
|
||
# Initialize this engine. | ||
self._registered_models = {} | ||
self._engine_slug = engine_slug | ||
|
||
# Store a reference to this engine. | ||
self.__class__._created_engines[engine_slug] = self | ||
|
||
def is_registered(self, model): | ||
"""Checks whether the given model is registered with this search engine.""" | ||
return model in self._registered_models | ||
|
||
def register(self, model): | ||
""" | ||
Registers the given model with this search engine. | ||
If the given model is already registered with this search engine, a | ||
RegistrationError will be raised. | ||
""" | ||
# Check for existing registration. | ||
if self.is_registered(model): | ||
raise RegistrationError(f"{model} is already registered with this search engine") | ||
|
||
# Connect to the signalling framework. | ||
if self._use_hooks(): | ||
post_save.connect(self._post_save_receiver, model) | ||
pre_delete.connect(self._pre_delete_receiver, model) | ||
|
||
# Signalling hooks. | ||
|
||
def _use_hooks(self): | ||
raise NotImplementedError | ||
|
||
def _post_save_receiver(self, instance, **kwargs): | ||
"""Signal handler for when a registered model has been saved.""" | ||
raise NotImplementedError | ||
|
||
def _pre_delete_receiver(self, instance, **kwargs): | ||
"""Signal handler for when a registered model has been deleted.""" | ||
raise NotImplementedError | ||
|
||
# Searching. | ||
|
||
def search(self, search_text, models=(), exclude=(), ranking=True, backend_name=None): | ||
"""Performs a search using the given text, returning a queryset of SearchEntry.""" | ||
raise NotImplementedError | ||
|
||
|
||
class PostgresIcontainsSearchBackend(SearchBackend): | ||
def _use_hooks(self): | ||
return False | ||
|
||
|
||
# The main search methods. | ||
default_search_engine = SearchBackend("default") | ||
search = default_search_engine.search |
Oops, something went wrong.