Merge branch 'netbox-community:develop' into script_rq_queue_name

This commit is contained in:
jchambers2012 2024-06-12 22:54:53 -04:00 committed by GitHub
commit c7970f0813
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 239 additions and 280 deletions

View File

@ -126,3 +126,13 @@ VERSION = 'v3.3.2-dev'
```
Commit this change with the comment "PRVB" (for _post-release version bump_) and push the commit upstream.
### Update the Public Documentation
After a release has been published, the public NetBox documentation needs to be updated. This is accomplished by running two actions on the [netboxlabs-docs](https://github.com/netboxlabs/netboxlabs-docs) repository.
First, run the `build-site` action, by navigating to Actions > build-site > Run workflow. This process compiles the documentation along with an overlay for integration with the documentation portal at <https://netboxlabs.com/docs>. The job should take about two minutes.
Once the documentation files have been compiled, they must be published by running the `deploy-kinsta` action. Select the desired deployment environment (staging or production) and specify `latest` as the deploy tag.
Finally, verify that the documentation at <https://netboxlabs.com/docs/netbox/en/stable/> has been updated.

View File

@ -7,7 +7,7 @@ from netbox.views import generic
from tenancy.views import ObjectContactsView
from utilities.forms import ConfirmationForm
from utilities.query import count_related
from utilities.views import register_model_view
from utilities.views import GetRelatedModelsMixin, register_model_view
from . import filtersets, forms, tables
from .models import *
@ -26,17 +26,12 @@ class ProviderListView(generic.ObjectListView):
@register_model_view(Provider)
class ProviderView(generic.ObjectView):
class ProviderView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Provider.objects.all()
def get_extra_context(self, request, instance):
related_models = (
(ProviderAccount.objects.restrict(request.user, 'view').filter(provider=instance), 'provider_id'),
(Circuit.objects.restrict(request.user, 'view').filter(provider=instance), 'provider_id'),
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, instance),
}
@ -92,16 +87,12 @@ class ProviderAccountListView(generic.ObjectListView):
@register_model_view(ProviderAccount)
class ProviderAccountView(generic.ObjectView):
class ProviderAccountView(GetRelatedModelsMixin, generic.ObjectView):
queryset = ProviderAccount.objects.all()
def get_extra_context(self, request, instance):
related_models = (
(Circuit.objects.restrict(request.user, 'view').filter(provider_account=instance), 'provider_account_id'),
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, instance),
}
@ -156,19 +147,21 @@ class ProviderNetworkListView(generic.ObjectListView):
@register_model_view(ProviderNetwork)
class ProviderNetworkView(generic.ObjectView):
class ProviderNetworkView(GetRelatedModelsMixin, generic.ObjectView):
queryset = ProviderNetwork.objects.all()
def get_extra_context(self, request, instance):
related_models = (
return {
'related_models': self.get_related_models(
request,
instance,
extra=(
(
Circuit.objects.restrict(request.user, 'view').filter(terminations__provider_network=instance),
'provider_network_id',
),
)
return {
'related_models': related_models,
),
),
}
@ -215,16 +208,12 @@ class CircuitTypeListView(generic.ObjectListView):
@register_model_view(CircuitType)
class CircuitTypeView(generic.ObjectView):
class CircuitTypeView(GetRelatedModelsMixin, generic.ObjectView):
queryset = CircuitType.objects.all()
def get_extra_context(self, request, instance):
related_models = (
(Circuit.objects.restrict(request.user, 'view').filter(type=instance), 'type_id'),
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, instance),
}

View File

@ -32,7 +32,7 @@ from netbox.views.generic.mixins import TableMixin
from utilities.forms import ConfirmationForm
from utilities.htmx import htmx_partial
from utilities.query import count_related
from utilities.views import ContentTypePermissionRequiredMixin, register_model_view
from utilities.views import ContentTypePermissionRequiredMixin, GetRelatedModelsMixin, register_model_view
from . import filtersets, forms, tables
from .models import *
@ -51,16 +51,12 @@ class DataSourceListView(generic.ObjectListView):
@register_model_view(DataSource)
class DataSourceView(generic.ObjectView):
class DataSourceView(GetRelatedModelsMixin, generic.ObjectView):
queryset = DataSource.objects.all()
def get_extra_context(self, request, instance):
related_models = (
(DataFile.objects.restrict(request.user, 'view').filter(source=instance), 'source_id'),
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, instance),
}

View File

@ -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'),
return {
'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'),
(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,
(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'),
),
),
}

View File

@ -12,7 +12,7 @@ from netbox.views import generic
from tenancy.views import ObjectContactsView
from utilities.query import count_related
from utilities.tables import get_table_ordering
from utilities.views import ViewTab, register_model_view
from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view
from virtualization.filtersets import VMInterfaceFilterSet
from virtualization.models import VMInterface
from . import filtersets, forms, tables
@ -34,15 +34,10 @@ class VRFListView(generic.ObjectListView):
@register_model_view(VRF)
class VRFView(generic.ObjectView):
class VRFView(GetRelatedModelsMixin, generic.ObjectView):
queryset = VRF.objects.all()
def get_extra_context(self, request, instance):
related_models = (
(Prefix.objects.restrict(request.user, 'view').filter(vrf=instance), 'vrf_id'),
(IPAddress.objects.restrict(request.user, 'view').filter(vrf=instance), 'vrf_id'),
)
import_targets_table = tables.RouteTargetTable(
instance.import_targets.all(),
orderable=False
@ -53,7 +48,7 @@ class VRFView(generic.ObjectView):
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, instance, omit=[Interface, VMInterface]),
'import_targets_table': import_targets_table,
'export_targets_table': export_targets_table,
}
@ -147,16 +142,12 @@ class RIRListView(generic.ObjectListView):
@register_model_view(RIR)
class RIRView(generic.ObjectView):
class RIRView(GetRelatedModelsMixin, generic.ObjectView):
queryset = RIR.objects.all()
def get_extra_context(self, request, instance):
related_models = (
(Aggregate.objects.restrict(request.user, 'view').filter(rir=instance), 'rir_id'),
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, instance),
}
@ -273,17 +264,19 @@ class ASNListView(generic.ObjectListView):
@register_model_view(ASN)
class ASNView(generic.ObjectView):
class ASNView(GetRelatedModelsMixin, generic.ObjectView):
queryset = ASN.objects.all()
def get_extra_context(self, request, instance):
related_models = (
return {
'related_models': self.get_related_models(
request,
instance,
extra=(
(Site.objects.restrict(request.user, 'view').filter(asns__in=[instance]), 'asn_id'),
(Provider.objects.restrict(request.user, 'view').filter(asns__in=[instance]), 'asn_id'),
)
return {
'related_models': related_models,
),
),
}
@ -427,18 +420,12 @@ class RoleListView(generic.ObjectListView):
@register_model_view(Role)
class RoleView(generic.ObjectView):
class RoleView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Role.objects.all()
def get_extra_context(self, request, instance):
related_models = (
(Prefix.objects.restrict(request.user, 'view').filter(role=instance), 'role_id'),
(IPRange.objects.restrict(request.user, 'view').filter(role=instance), 'role_id'),
(VLAN.objects.restrict(request.user, 'view').filter(role=instance), 'role_id'),
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, instance),
}
@ -926,16 +913,12 @@ class VLANGroupListView(generic.ObjectListView):
@register_model_view(VLANGroup)
class VLANGroupView(generic.ObjectView):
class VLANGroupView(GetRelatedModelsMixin, generic.ObjectView):
queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags')
def get_extra_context(self, request, instance):
related_models = (
(VLAN.objects.restrict(request.user, 'view').filter(group=instance), 'group_id'),
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, instance),
}

View File

@ -1,3 +1,4 @@
import zoneinfo
from dataclasses import dataclass
from typing import Optional
from urllib.parse import quote
@ -83,6 +84,8 @@ class DateTimeColumn(tables.Column):
def render(self, value):
if value:
current_tz = zoneinfo.ZoneInfo(settings.TIME_ZONE)
value = value.astimezone(current_tz)
return f"{value.date().isoformat()} {value.time().isoformat(timespec=self.timespec)}"
def value(self, value):

View File

@ -4,8 +4,7 @@ from django.utils.translation import gettext as _
from netbox.views import generic
from utilities.query import count_related
from utilities.relations import get_related_models
from utilities.views import register_model_view, ViewTab
from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view
from . import filtersets, forms, tables
from .models import *
@ -56,17 +55,14 @@ class TenantGroupListView(generic.ObjectListView):
@register_model_view(TenantGroup)
class TenantGroupView(generic.ObjectView):
class TenantGroupView(GetRelatedModelsMixin, generic.ObjectView):
queryset = TenantGroup.objects.all()
def get_extra_context(self, request, instance):
groups = instance.get_descendants(include_self=True)
related_models = (
(Tenant.objects.restrict(request.user, 'view').filter(group__in=groups), 'group_id'),
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, groups),
}
@ -123,17 +119,12 @@ class TenantListView(generic.ObjectListView):
@register_model_view(Tenant)
class TenantView(generic.ObjectView):
class TenantView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Tenant.objects.all()
def get_extra_context(self, request, instance):
related_models = [
(model.objects.restrict(request.user, 'view').filter(tenant=instance), f'{field}_id')
for model, field in get_related_models(Tenant)
]
return {
'related_models': related_models,
'related_models': self.get_related_models(request, instance),
}
@ -189,17 +180,14 @@ class ContactGroupListView(generic.ObjectListView):
@register_model_view(ContactGroup)
class ContactGroupView(generic.ObjectView):
class ContactGroupView(GetRelatedModelsMixin, generic.ObjectView):
queryset = ContactGroup.objects.all()
def get_extra_context(self, request, instance):
groups = instance.get_descendants(include_self=True)
related_models = (
(Contact.objects.restrict(request.user, 'view').filter(group__in=groups), 'group_id'),
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, groups),
}
@ -256,16 +244,12 @@ class ContactRoleListView(generic.ObjectListView):
@register_model_view(ContactRole)
class ContactRoleView(generic.ObjectView):
class ContactRoleView(GetRelatedModelsMixin, generic.ObjectView):
queryset = ContactRole.objects.all()
def get_extra_context(self, request, instance):
related_models = (
(ContactAssignment.objects.restrict(request.user, 'view').filter(role=instance), 'role_id'),
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, instance),
}

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-08 05:02+0000\n"
"POT-Creation-Date: 2024-06-12 05:01+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -29,7 +29,7 @@ msgid "Write Enabled"
msgstr ""
#: netbox/account/tables.py:35 netbox/core/tables/jobs.py:29
#: netbox/core/tables/tasks.py:79 netbox/extras/choices.py:138
#: netbox/core/tables/tasks.py:79 netbox/extras/choices.py:142
#: netbox/extras/tables/tables.py:499 netbox/templates/account/token.html:43
#: netbox/templates/core/configrevision.html:26
#: netbox/templates/core/configrevision_restore.html:12
@ -1400,7 +1400,7 @@ msgid "Syncing"
msgstr ""
#: netbox/core/choices.py:21 netbox/core/choices.py:57
#: netbox/core/tables/jobs.py:41 netbox/extras/choices.py:224
#: netbox/core/tables/jobs.py:41 netbox/extras/choices.py:228
#: netbox/templates/core/job.html:68
msgid "Completed"
msgstr ""
@ -1408,7 +1408,7 @@ msgstr ""
#: netbox/core/choices.py:22 netbox/core/choices.py:59
#: netbox/core/constants.py:20 netbox/core/tables/tasks.py:34
#: netbox/dcim/choices.py:176 netbox/dcim/choices.py:222
#: netbox/dcim/choices.py:1536 netbox/extras/choices.py:226
#: netbox/dcim/choices.py:1536 netbox/extras/choices.py:230
#: netbox/virtualization/choices.py:47
msgid "Failed"
msgstr ""
@ -1426,21 +1426,21 @@ msgstr ""
msgid "Reports"
msgstr ""
#: netbox/core/choices.py:54 netbox/extras/choices.py:221
#: netbox/core/choices.py:54 netbox/extras/choices.py:225
msgid "Pending"
msgstr ""
#: netbox/core/choices.py:55 netbox/core/constants.py:23
#: netbox/core/tables/jobs.py:32 netbox/core/tables/tasks.py:38
#: netbox/extras/choices.py:222 netbox/templates/core/job.html:55
#: netbox/extras/choices.py:226 netbox/templates/core/job.html:55
msgid "Scheduled"
msgstr ""
#: netbox/core/choices.py:56 netbox/extras/choices.py:223
#: netbox/core/choices.py:56 netbox/extras/choices.py:227
msgid "Running"
msgstr ""
#: netbox/core/choices.py:58 netbox/extras/choices.py:225
#: netbox/core/choices.py:58 netbox/extras/choices.py:229
msgid "Errored"
msgstr ""
@ -6483,71 +6483,79 @@ msgstr ""
msgid "Link"
msgstr ""
#: netbox/extras/choices.py:122
#: netbox/extras/choices.py:124
msgid "Newest"
msgstr ""
#: netbox/extras/choices.py:123
#: netbox/extras/choices.py:125
msgid "Oldest"
msgstr ""
#: netbox/extras/choices.py:139 netbox/templates/generic/object.html:61
#: netbox/extras/choices.py:126
msgid "Alphabetical (A-Z)"
msgstr ""
#: netbox/extras/choices.py:127
msgid "Alphabetical (Z-A)"
msgstr ""
#: netbox/extras/choices.py:143 netbox/templates/generic/object.html:61
msgid "Updated"
msgstr ""
#: netbox/extras/choices.py:140
#: netbox/extras/choices.py:144
msgid "Deleted"
msgstr ""
#: netbox/extras/choices.py:157 netbox/extras/choices.py:181
#: netbox/extras/choices.py:161 netbox/extras/choices.py:185
msgid "Info"
msgstr ""
#: netbox/extras/choices.py:158 netbox/extras/choices.py:180
#: netbox/extras/choices.py:162 netbox/extras/choices.py:184
msgid "Success"
msgstr ""
#: netbox/extras/choices.py:159 netbox/extras/choices.py:182
#: netbox/extras/choices.py:163 netbox/extras/choices.py:186
msgid "Warning"
msgstr ""
#: netbox/extras/choices.py:160
#: netbox/extras/choices.py:164
msgid "Danger"
msgstr ""
#: netbox/extras/choices.py:178
#: netbox/extras/choices.py:182
msgid "Debug"
msgstr ""
#: netbox/extras/choices.py:179 netbox/netbox/choices.py:104
#: netbox/extras/choices.py:183 netbox/netbox/choices.py:104
msgid "Default"
msgstr ""
#: netbox/extras/choices.py:183
#: netbox/extras/choices.py:187
msgid "Failure"
msgstr ""
#: netbox/extras/choices.py:199
#: netbox/extras/choices.py:203
msgid "Hourly"
msgstr ""
#: netbox/extras/choices.py:200
#: netbox/extras/choices.py:204
msgid "12 hours"
msgstr ""
#: netbox/extras/choices.py:201
#: netbox/extras/choices.py:205
msgid "Daily"
msgstr ""
#: netbox/extras/choices.py:202
#: netbox/extras/choices.py:206
msgid "Weekly"
msgstr ""
#: netbox/extras/choices.py:203
#: netbox/extras/choices.py:207
msgid "30 days"
msgstr ""
#: netbox/extras/choices.py:268 netbox/extras/tables/tables.py:296
#: netbox/extras/choices.py:272 netbox/extras/tables/tables.py:296
#: netbox/templates/dcim/virtualchassis_edit.html:107
#: netbox/templates/extras/eventrule.html:40
#: netbox/templates/generic/bulk_add_component.html:68
@ -6557,12 +6565,12 @@ msgstr ""
msgid "Create"
msgstr ""
#: netbox/extras/choices.py:269 netbox/extras/tables/tables.py:299
#: netbox/extras/choices.py:273 netbox/extras/tables/tables.py:299
#: netbox/templates/extras/eventrule.html:44
msgid "Update"
msgstr ""
#: netbox/extras/choices.py:270 netbox/extras/tables/tables.py:302
#: netbox/extras/choices.py:274 netbox/extras/tables/tables.py:302
#: netbox/templates/circuits/inc/circuit_termination.html:23
#: netbox/templates/dcim/inc/panels/inventory_items.html:37
#: netbox/templates/dcim/moduletype/component_templates.html:23
@ -6579,77 +6587,77 @@ msgstr ""
msgid "Delete"
msgstr ""
#: netbox/extras/choices.py:294 netbox/netbox/choices.py:57
#: netbox/extras/choices.py:298 netbox/netbox/choices.py:57
#: netbox/netbox/choices.py:105
msgid "Blue"
msgstr ""
#: netbox/extras/choices.py:295 netbox/netbox/choices.py:56
#: netbox/extras/choices.py:299 netbox/netbox/choices.py:56
#: netbox/netbox/choices.py:106
msgid "Indigo"
msgstr ""
#: netbox/extras/choices.py:296 netbox/netbox/choices.py:54
#: netbox/extras/choices.py:300 netbox/netbox/choices.py:54
#: netbox/netbox/choices.py:107
msgid "Purple"
msgstr ""
#: netbox/extras/choices.py:297 netbox/netbox/choices.py:51
#: netbox/extras/choices.py:301 netbox/netbox/choices.py:51
#: netbox/netbox/choices.py:108
msgid "Pink"
msgstr ""
#: netbox/extras/choices.py:298 netbox/netbox/choices.py:50
#: netbox/extras/choices.py:302 netbox/netbox/choices.py:50
#: netbox/netbox/choices.py:109
msgid "Red"
msgstr ""
#: netbox/extras/choices.py:299 netbox/netbox/choices.py:68
#: netbox/extras/choices.py:303 netbox/netbox/choices.py:68
#: netbox/netbox/choices.py:110
msgid "Orange"
msgstr ""
#: netbox/extras/choices.py:300 netbox/netbox/choices.py:66
#: netbox/extras/choices.py:304 netbox/netbox/choices.py:66
#: netbox/netbox/choices.py:111
msgid "Yellow"
msgstr ""
#: netbox/extras/choices.py:301 netbox/netbox/choices.py:63
#: netbox/extras/choices.py:305 netbox/netbox/choices.py:63
#: netbox/netbox/choices.py:112
msgid "Green"
msgstr ""
#: netbox/extras/choices.py:302 netbox/netbox/choices.py:60
#: netbox/extras/choices.py:306 netbox/netbox/choices.py:60
#: netbox/netbox/choices.py:113
msgid "Teal"
msgstr ""
#: netbox/extras/choices.py:303 netbox/netbox/choices.py:59
#: netbox/extras/choices.py:307 netbox/netbox/choices.py:59
#: netbox/netbox/choices.py:114
msgid "Cyan"
msgstr ""
#: netbox/extras/choices.py:304 netbox/netbox/choices.py:115
#: netbox/extras/choices.py:308 netbox/netbox/choices.py:115
msgid "Gray"
msgstr ""
#: netbox/extras/choices.py:305 netbox/netbox/choices.py:74
#: netbox/extras/choices.py:309 netbox/netbox/choices.py:74
#: netbox/netbox/choices.py:116
msgid "Black"
msgstr ""
#: netbox/extras/choices.py:306 netbox/netbox/choices.py:75
#: netbox/extras/choices.py:310 netbox/netbox/choices.py:75
#: netbox/netbox/choices.py:117
msgid "White"
msgstr ""
#: netbox/extras/choices.py:320 netbox/extras/forms/model_forms.py:242
#: netbox/extras/choices.py:324 netbox/extras/forms/model_forms.py:242
#: netbox/extras/forms/model_forms.py:324
#: netbox/templates/extras/webhook.html:10
msgid "Webhook"
msgstr ""
#: netbox/extras/choices.py:321 netbox/extras/forms/model_forms.py:312
#: netbox/extras/choices.py:325 netbox/extras/forms/model_forms.py:312
#: netbox/templates/extras/script/base.html:29
msgid "Script"
msgstr ""

View File

@ -1,3 +1,5 @@
from typing import Iterable
from django.contrib.auth.mixins import AccessMixin
from django.core.exceptions import ImproperlyConfigured
from django.urls import reverse
@ -6,10 +8,12 @@ from django.utils.translation import gettext_lazy as _
from netbox.plugins import PluginConfig
from netbox.registry import registry
from utilities.relations import get_related_models
from .permissions import resolve_permission
__all__ = (
'ContentTypePermissionRequiredMixin',
'GetRelatedModelsMixin',
'GetReturnURLMixin',
'ObjectPermissionRequiredMixin',
'ViewTab',
@ -142,6 +146,46 @@ class GetReturnURLMixin:
return reverse('home')
class GetRelatedModelsMixin:
"""
Provides logic for collecting all related models for the currently viewed model.
"""
def get_related_models(self, request, instance, omit=[], extra=[]):
"""
Get related models of the view's `queryset` model without those listed in `omit`. Will be sorted alphabetical.
Args:
request: Current request being processed.
instance: The instance related models should be looked up for. A list of instances can be passed to match
related objects in this list (e.g. to find sites of a region including child regions).
omit: Remove relationships to these models from the result. Needs to be passed, if related models don't
provide a `_list` view.
extra: Add extra models to the list of automatically determined related models. Can be used to add indirect
relationships.
"""
model = self.queryset.model
related = filter(
lambda m: m[0] is not model and m[0] not in omit,
get_related_models(model, False)
)
related_models = [
(
model.objects.restrict(request.user, 'view').filter(**(
{f'{field}__in': instance}
if isinstance(instance, Iterable)
else {field: instance}
)),
f'{field}_id'
)
for model, field in related
]
related_models.extend(extra)
return sorted(related_models, key=lambda x: x[0].model._meta.verbose_name.lower())
class ViewTab:
"""
ViewTabs are used for navigation among multiple object-specific views, such as the changelog or journal for

View File

@ -20,7 +20,7 @@ from netbox.views import generic
from tenancy.views import ObjectContactsView
from utilities.query import count_related
from utilities.query_functions import CollateAsChar
from utilities.views import ViewTab, register_model_view
from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view
from . import filtersets, forms, tables
from .models import *
@ -39,16 +39,12 @@ class ClusterTypeListView(generic.ObjectListView):
@register_model_view(ClusterType)
class ClusterTypeView(generic.ObjectView):
class ClusterTypeView(GetRelatedModelsMixin, generic.ObjectView):
queryset = ClusterType.objects.all()
def get_extra_context(self, request, instance):
related_models = (
(Cluster.objects.restrict(request.user, 'view').filter(type=instance), 'type_id'),
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, instance),
}
@ -99,16 +95,12 @@ class ClusterGroupListView(generic.ObjectListView):
@register_model_view(ClusterGroup)
class ClusterGroupView(generic.ObjectView):
class ClusterGroupView(GetRelatedModelsMixin, generic.ObjectView):
queryset = ClusterGroup.objects.all()
def get_extra_context(self, request, instance):
related_models = (
(Cluster.objects.restrict(request.user, 'view').filter(group=instance), 'group_id'),
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, instance),
}

View File

@ -2,7 +2,7 @@ from ipam.tables import RouteTargetTable
from netbox.views import generic
from tenancy.views import ObjectContactsView
from utilities.query import count_related
from utilities.views import register_model_view
from utilities.views import GetRelatedModelsMixin, register_model_view
from . import filtersets, forms, tables
from .models import *
@ -21,16 +21,12 @@ class TunnelGroupListView(generic.ObjectListView):
@register_model_view(TunnelGroup)
class TunnelGroupView(generic.ObjectView):
class TunnelGroupView(GetRelatedModelsMixin, generic.ObjectView):
queryset = TunnelGroup.objects.all()
def get_extra_context(self, request, instance):
related_models = (
(Tunnel.objects.restrict(request.user, 'view').filter(group=instance), 'group_id'),
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, instance),
}

View File

@ -1,7 +1,7 @@
from dcim.models import Interface
from netbox.views import generic
from utilities.query import count_related
from utilities.views import register_model_view
from utilities.views import GetRelatedModelsMixin, register_model_view
from . import filtersets, forms, tables
from .models import *
@ -24,17 +24,14 @@ class WirelessLANGroupListView(generic.ObjectListView):
@register_model_view(WirelessLANGroup)
class WirelessLANGroupView(generic.ObjectView):
class WirelessLANGroupView(GetRelatedModelsMixin, generic.ObjectView):
queryset = WirelessLANGroup.objects.all()
def get_extra_context(self, request, instance):
groups = instance.get_descendants(include_self=True)
related_models = (
(WirelessLAN.objects.restrict(request.user, 'view').filter(group__in=groups), 'group_id'),
)
return {
'related_models': related_models,
'related_models': self.get_related_models(request, groups),
}