mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-15 00:02:17 -06:00
15794 Make "related objects" dynamic (#15876)
* Closes #15794: Make "related objects" dynamic Instead of hardcoding relationships between models for the detail view, they are now dynamically generated. * Fix related models call * Remove extra related models hook Instead of providing a rarely used hook method, additional related models can now be passed directly to the lookup method. * Fix relations view for ASNs ASNs have ManyToMany relationships and therefore can't used automatic resolving. Explicit relations have been restored as before. * Add method call keywords for clarification * Cleanup related models --------- Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
@@ -17,7 +17,7 @@ from jinja2.exceptions import TemplateError
|
||||
|
||||
from circuits.models import Circuit, CircuitTermination
|
||||
from extras.views import ObjectConfigContextView
|
||||
from ipam.models import ASN, IPAddress, Prefix, VLAN, VLANGroup
|
||||
from ipam.models import ASN, IPAddress, VLANGroup
|
||||
from ipam.tables import InterfaceVLANTable
|
||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
||||
from netbox.views import generic
|
||||
@@ -27,7 +27,9 @@ from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||
from utilities.permissions import get_permission_for_model
|
||||
from utilities.query import count_related
|
||||
from utilities.query_functions import CollateAsChar
|
||||
from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin, ViewTab, register_model_view
|
||||
from utilities.views import (
|
||||
GetRelatedModelsMixin, GetReturnURLMixin, ObjectPermissionRequiredMixin, ViewTab, register_model_view
|
||||
)
|
||||
from virtualization.filtersets import VirtualMachineFilterSet
|
||||
from virtualization.models import VirtualMachine
|
||||
from virtualization.tables import VirtualMachineTable
|
||||
@@ -226,19 +228,21 @@ class RegionListView(generic.ObjectListView):
|
||||
|
||||
|
||||
@register_model_view(Region)
|
||||
class RegionView(generic.ObjectView):
|
||||
class RegionView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = Region.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
regions = instance.get_descendants(include_self=True)
|
||||
related_models = (
|
||||
(Site.objects.restrict(request.user, 'view').filter(region__in=regions), 'region_id'),
|
||||
(Location.objects.restrict(request.user, 'view').filter(site__region__in=regions), 'region_id'),
|
||||
(Rack.objects.restrict(request.user, 'view').filter(site__region__in=regions), 'region_id'),
|
||||
)
|
||||
|
||||
return {
|
||||
'related_models': related_models,
|
||||
'related_models': self.get_related_models(
|
||||
request,
|
||||
regions,
|
||||
extra=(
|
||||
(Location.objects.restrict(request.user, 'view').filter(site__region__in=regions), 'region_id'),
|
||||
(Rack.objects.restrict(request.user, 'view').filter(site__region__in=regions), 'region_id'),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -306,19 +310,21 @@ class SiteGroupListView(generic.ObjectListView):
|
||||
|
||||
|
||||
@register_model_view(SiteGroup)
|
||||
class SiteGroupView(generic.ObjectView):
|
||||
class SiteGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = SiteGroup.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
groups = instance.get_descendants(include_self=True)
|
||||
related_models = (
|
||||
(Site.objects.restrict(request.user, 'view').filter(group__in=groups), 'group_id'),
|
||||
(Location.objects.restrict(request.user, 'view').filter(site__group__in=groups), 'site_group_id'),
|
||||
(Rack.objects.restrict(request.user, 'view').filter(site__group__in=groups), 'site_group_id'),
|
||||
)
|
||||
|
||||
return {
|
||||
'related_models': related_models,
|
||||
'related_models': self.get_related_models(
|
||||
request,
|
||||
groups,
|
||||
extra=(
|
||||
(Location.objects.restrict(request.user, 'view').filter(site__group__in=groups), 'site_group_id'),
|
||||
(Rack.objects.restrict(request.user, 'view').filter(site__group__in=groups), 'site_group_id'),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -380,31 +386,25 @@ class SiteListView(generic.ObjectListView):
|
||||
|
||||
|
||||
@register_model_view(Site)
|
||||
class SiteView(generic.ObjectView):
|
||||
class SiteView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = Site.objects.prefetch_related('tenant__group')
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
related_models = (
|
||||
# DCIM
|
||||
(Location.objects.restrict(request.user, 'view').filter(site=instance), 'site_id'),
|
||||
(Rack.objects.restrict(request.user, 'view').filter(site=instance), 'site_id'),
|
||||
(Device.objects.restrict(request.user, 'view').filter(site=instance), 'site_id'),
|
||||
# Virtualization
|
||||
(VirtualMachine.objects.restrict(request.user, 'view').filter(cluster__site=instance), 'site_id'),
|
||||
# IPAM
|
||||
(Prefix.objects.restrict(request.user, 'view').filter(site=instance), 'site_id'),
|
||||
(ASN.objects.restrict(request.user, 'view').filter(sites=instance), 'site_id'),
|
||||
(VLANGroup.objects.restrict(request.user, 'view').filter(
|
||||
scope_type=ContentType.objects.get_for_model(Site),
|
||||
scope_id=instance.pk
|
||||
), 'site'),
|
||||
(VLAN.objects.restrict(request.user, 'view').filter(site=instance), 'site_id'),
|
||||
# Circuits
|
||||
(Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).distinct(), 'site_id'),
|
||||
)
|
||||
|
||||
return {
|
||||
'related_models': related_models,
|
||||
'related_models': self.get_related_models(
|
||||
request,
|
||||
instance,
|
||||
[CableTermination, CircuitTermination],
|
||||
(
|
||||
(VLANGroup.objects.restrict(request.user, 'view').filter(
|
||||
scope_type=ContentType.objects.get_for_model(Site),
|
||||
scope_id=instance.pk
|
||||
), 'site'),
|
||||
(ASN.objects.restrict(request.user, 'view').filter(sites=instance), 'site_id'),
|
||||
(Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).distinct(),
|
||||
'site_id'),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -466,18 +466,13 @@ class LocationListView(generic.ObjectListView):
|
||||
|
||||
|
||||
@register_model_view(Location)
|
||||
class LocationView(generic.ObjectView):
|
||||
class LocationView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = Location.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
locations = instance.get_descendants(include_self=True)
|
||||
related_models = (
|
||||
(Rack.objects.restrict(request.user, 'view').filter(location__in=locations), 'location_id'),
|
||||
(Device.objects.restrict(request.user, 'view').filter(location__in=locations), 'location_id'),
|
||||
)
|
||||
|
||||
return {
|
||||
'related_models': related_models,
|
||||
'related_models': self.get_related_models(request, locations, [CableTermination]),
|
||||
}
|
||||
|
||||
|
||||
@@ -541,16 +536,12 @@ class RackRoleListView(generic.ObjectListView):
|
||||
|
||||
|
||||
@register_model_view(RackRole)
|
||||
class RackRoleView(generic.ObjectView):
|
||||
class RackRoleView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = RackRole.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
related_models = (
|
||||
(Rack.objects.restrict(request.user, 'view').filter(role=instance), 'role_id'),
|
||||
)
|
||||
|
||||
return {
|
||||
'related_models': related_models,
|
||||
'related_models': self.get_related_models(request, instance),
|
||||
}
|
||||
|
||||
|
||||
@@ -655,15 +646,10 @@ class RackElevationListView(generic.ObjectListView):
|
||||
|
||||
|
||||
@register_model_view(Rack)
|
||||
class RackView(generic.ObjectView):
|
||||
class RackView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'location', 'role')
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
related_models = (
|
||||
(Device.objects.restrict(request.user, 'view').filter(rack=instance), 'rack_id'),
|
||||
(PowerFeed.objects.restrict(request.user).filter(rack=instance), 'rack_id'),
|
||||
)
|
||||
|
||||
peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=instance.site)
|
||||
|
||||
if instance.location:
|
||||
@@ -679,7 +665,7 @@ class RackView(generic.ObjectView):
|
||||
])
|
||||
|
||||
return {
|
||||
'related_models': related_models,
|
||||
'related_models': self.get_related_models(request, instance, [CableTermination]),
|
||||
'next_rack': next_rack,
|
||||
'prev_rack': prev_rack,
|
||||
'svg_extra': svg_extra,
|
||||
@@ -838,19 +824,12 @@ class ManufacturerListView(generic.ObjectListView):
|
||||
|
||||
|
||||
@register_model_view(Manufacturer)
|
||||
class ManufacturerView(generic.ObjectView):
|
||||
class ManufacturerView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = Manufacturer.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
related_models = (
|
||||
(DeviceType.objects.restrict(request.user, 'view').filter(manufacturer=instance), 'manufacturer_id'),
|
||||
(ModuleType.objects.restrict(request.user, 'view').filter(manufacturer=instance), 'manufacturer_id'),
|
||||
(InventoryItem.objects.restrict(request.user, 'view').filter(manufacturer=instance), 'manufacturer_id'),
|
||||
(Platform.objects.restrict(request.user, 'view').filter(manufacturer=instance), 'manufacturer_id'),
|
||||
)
|
||||
|
||||
return {
|
||||
'related_models': related_models,
|
||||
'related_models': self.get_related_models(request, instance, [InventoryItemTemplate]),
|
||||
}
|
||||
|
||||
|
||||
@@ -912,16 +891,16 @@ class DeviceTypeListView(generic.ObjectListView):
|
||||
|
||||
|
||||
@register_model_view(DeviceType)
|
||||
class DeviceTypeView(generic.ObjectView):
|
||||
class DeviceTypeView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = DeviceType.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
related_models = (
|
||||
(Device.objects.restrict(request.user).filter(device_type=instance), 'device_type_id'),
|
||||
)
|
||||
|
||||
return {
|
||||
'related_models': related_models,
|
||||
'related_models': self.get_related_models(request, instance, omit=[
|
||||
ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, FrontPortTemplate,
|
||||
InventoryItemTemplate, InterfaceTemplate, ModuleBayTemplate, PowerOutletTemplate, PowerPortTemplate,
|
||||
RearPortTemplate,
|
||||
]),
|
||||
}
|
||||
|
||||
|
||||
@@ -1151,16 +1130,16 @@ class ModuleTypeListView(generic.ObjectListView):
|
||||
|
||||
|
||||
@register_model_view(ModuleType)
|
||||
class ModuleTypeView(generic.ObjectView):
|
||||
class ModuleTypeView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = ModuleType.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
related_models = (
|
||||
(Module.objects.restrict(request.user).filter(module_type=instance), 'module_type_id'),
|
||||
)
|
||||
|
||||
return {
|
||||
'related_models': related_models,
|
||||
'related_models': self.get_related_models(request, instance, omit=[
|
||||
ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, FrontPortTemplate,
|
||||
InventoryItemTemplate, InterfaceTemplate, ModuleBayTemplate, PowerOutletTemplate, PowerPortTemplate,
|
||||
RearPortTemplate,
|
||||
]),
|
||||
}
|
||||
|
||||
|
||||
@@ -1711,17 +1690,12 @@ class DeviceRoleListView(generic.ObjectListView):
|
||||
|
||||
|
||||
@register_model_view(DeviceRole)
|
||||
class DeviceRoleView(generic.ObjectView):
|
||||
class DeviceRoleView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = DeviceRole.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
related_models = (
|
||||
(Device.objects.restrict(request.user, 'view').filter(role=instance), 'role_id'),
|
||||
(VirtualMachine.objects.restrict(request.user, 'view').filter(role=instance), 'role_id'),
|
||||
)
|
||||
|
||||
return {
|
||||
'related_models': related_models,
|
||||
'related_models': self.get_related_models(request, instance),
|
||||
}
|
||||
|
||||
|
||||
@@ -1775,17 +1749,12 @@ class PlatformListView(generic.ObjectListView):
|
||||
|
||||
|
||||
@register_model_view(Platform)
|
||||
class PlatformView(generic.ObjectView):
|
||||
class PlatformView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = Platform.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
related_models = (
|
||||
(Device.objects.restrict(request.user, 'view').filter(platform=instance), 'platform_id'),
|
||||
(VirtualMachine.objects.restrict(request.user, 'view').filter(platform=instance), 'platform_id'),
|
||||
)
|
||||
|
||||
return {
|
||||
'related_models': related_models,
|
||||
'related_models': self.get_related_models(request, instance),
|
||||
}
|
||||
|
||||
|
||||
@@ -2157,22 +2126,12 @@ class ModuleListView(generic.ObjectListView):
|
||||
|
||||
|
||||
@register_model_view(Module)
|
||||
class ModuleView(generic.ObjectView):
|
||||
class ModuleView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = Module.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
related_models = (
|
||||
(Interface.objects.restrict(request.user, 'view').filter(module=instance), 'module_id'),
|
||||
(ConsolePort.objects.restrict(request.user, 'view').filter(module=instance), 'module_id'),
|
||||
(ConsoleServerPort.objects.restrict(request.user, 'view').filter(module=instance), 'module_id'),
|
||||
(PowerPort.objects.restrict(request.user, 'view').filter(module=instance), 'module_id'),
|
||||
(PowerOutlet.objects.restrict(request.user, 'view').filter(module=instance), 'module_id'),
|
||||
(FrontPort.objects.restrict(request.user, 'view').filter(module=instance), 'module_id'),
|
||||
(RearPort.objects.restrict(request.user, 'view').filter(module=instance), 'module_id'),
|
||||
)
|
||||
|
||||
return {
|
||||
'related_models': related_models,
|
||||
'related_models': self.get_related_models(request, instance),
|
||||
}
|
||||
|
||||
|
||||
@@ -3552,16 +3511,12 @@ class PowerPanelListView(generic.ObjectListView):
|
||||
|
||||
|
||||
@register_model_view(PowerPanel)
|
||||
class PowerPanelView(generic.ObjectView):
|
||||
class PowerPanelView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = PowerPanel.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
related_models = (
|
||||
(PowerFeed.objects.restrict(request.user).filter(power_panel=instance), 'power_panel_id'),
|
||||
)
|
||||
|
||||
return {
|
||||
'related_models': related_models,
|
||||
'related_models': self.get_related_models(request, instance),
|
||||
}
|
||||
|
||||
|
||||
@@ -3665,16 +3620,18 @@ class VirtualDeviceContextListView(generic.ObjectListView):
|
||||
|
||||
|
||||
@register_model_view(VirtualDeviceContext)
|
||||
class VirtualDeviceContextView(generic.ObjectView):
|
||||
class VirtualDeviceContextView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = VirtualDeviceContext.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
related_models = (
|
||||
(Interface.objects.restrict(request.user, 'view').filter(vdcs__in=[instance]), 'vdc_id'),
|
||||
)
|
||||
|
||||
return {
|
||||
'related_models': related_models,
|
||||
'related_models': self.get_related_models(
|
||||
request,
|
||||
instance,
|
||||
extra=(
|
||||
(Interface.objects.restrict(request.user, 'view').filter(vdcs__in=[instance]), 'vdc_id'),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user