mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-09 00:58:16 -06:00
Merge branch 'develop' into 16424-allow_filtering_of_devices_by_cluster_and_cluster_group
This commit is contained in:
commit
40c4b17d2a
1
.gitignore
vendored
1
.gitignore
vendored
@ -28,3 +28,4 @@ netbox.pid
|
||||
.idea
|
||||
.coverage
|
||||
.vscode
|
||||
.python-version
|
||||
|
@ -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.
|
||||
|
@ -2,6 +2,26 @@
|
||||
|
||||
## v4.0.6 (FUTURE)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#15348](https://github.com/netbox-community/netbox/issues/15348) - Show saved filters alongside quick search on object list views
|
||||
* [#15794](https://github.com/netbox-community/netbox/issues/15794) - Dynamically populate related objects in UI views
|
||||
* [#16256](https://github.com/netbox-community/netbox/issues/16256) - Enable alphabetical ordering of bookmarks on dashboard
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#13925](https://github.com/netbox-community/netbox/issues/13925) - Fix support for "zulu" (UTC) timestamps for custom fields
|
||||
* [#14829](https://github.com/netbox-community/netbox/issues/14829) - Fix support for simple conditions (without AND/OR) in event rules
|
||||
* [#16143](https://github.com/netbox-community/netbox/issues/16143) - Display timestamps in tables in the configured timezone
|
||||
* [#16416](https://github.com/netbox-community/netbox/issues/16416) - Retain dark/light mode toggle on mobile view
|
||||
* [#16444](https://github.com/netbox-community/netbox/issues/16444) - Disable ordering circuits list by A/Z termination
|
||||
* [#16450](https://github.com/netbox-community/netbox/issues/16450) - Searching for rack unit in form dropdown should be case-insensitive
|
||||
* [#16452](https://github.com/netbox-community/netbox/issues/16452) - Fix sizing of buttons within object attribute panels
|
||||
* [#16454](https://github.com/netbox-community/netbox/issues/16454) - Address DNS lookup bug in `django-debug-toolbar
|
||||
* [#16460](https://github.com/netbox-community/netbox/issues/16460) - Omit spaces from telephone number URLs
|
||||
* [#16512](https://github.com/netbox-community/netbox/issues/16512) - Restore a user's preferred language (if any) on login
|
||||
* [#16542](https://github.com/netbox-community/netbox/issues/16542) - Fix bulk form operations when HTMX is enabled
|
||||
|
||||
---
|
||||
|
||||
## v4.0.5 (2024-06-06)
|
||||
|
@ -104,10 +104,16 @@ class LoginView(View):
|
||||
# Ensure the user has a UserConfig defined. (This should normally be handled by
|
||||
# create_userconfig() on user creation.)
|
||||
if not hasattr(request.user, 'config'):
|
||||
config = get_config()
|
||||
UserConfig(user=request.user, data=config.DEFAULT_USER_PREFERENCES).save()
|
||||
request.user.config = get_config()
|
||||
UserConfig(user=request.user, data=request.user.config.DEFAULT_USER_PREFERENCES).save()
|
||||
|
||||
return self.redirect_to_next(request, logger)
|
||||
response = self.redirect_to_next(request, logger)
|
||||
|
||||
# Set the user's preferred language (if any)
|
||||
if language := request.user.config.get('locale.language'):
|
||||
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language)
|
||||
|
||||
return response
|
||||
|
||||
else:
|
||||
logger.debug(f"Login form validation failed for username: {form['username'].value()}")
|
||||
@ -145,9 +151,10 @@ class LogoutView(View):
|
||||
logger.info(f"User {username} has logged out")
|
||||
messages.info(request, "You have logged out.")
|
||||
|
||||
# Delete session key cookie (if set) upon logout
|
||||
# Delete session key & language cookies (if set) upon logout
|
||||
response = HttpResponseRedirect(resolve_url(settings.LOGOUT_REDIRECT_URL))
|
||||
response.delete_cookie('session_key')
|
||||
response.delete_cookie(settings.LANGUAGE_COOKIE_NAME)
|
||||
|
||||
return response
|
||||
|
||||
|
@ -63,10 +63,12 @@ class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
status = columns.ChoiceFieldColumn()
|
||||
termination_a = tables.TemplateColumn(
|
||||
template_code=CIRCUITTERMINATION_LINK,
|
||||
orderable=False,
|
||||
verbose_name=_('Side A')
|
||||
)
|
||||
termination_z = tables.TemplateColumn(
|
||||
template_code=CIRCUITTERMINATION_LINK,
|
||||
orderable=False,
|
||||
verbose_name=_('Side Z')
|
||||
)
|
||||
commit_rate = CommitRateColumn(
|
||||
|
@ -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),
|
||||
}
|
||||
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
|
||||
|
||||
|
@ -219,9 +219,9 @@ class RackViewSet(NetBoxModelViewSet):
|
||||
)
|
||||
|
||||
# Enable filtering rack units by ID
|
||||
q = data['q']
|
||||
if q:
|
||||
elevation = [u for u in elevation if q in str(u['id']) or q in str(u['name'])]
|
||||
if q := data['q']:
|
||||
q = q.lower()
|
||||
elevation = [u for u in elevation if q in str(u['id']) or q in str(u['name']).lower()]
|
||||
|
||||
page = self.paginate_queryset(elevation)
|
||||
if page is not None:
|
||||
|
@ -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'),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
@ -117,10 +117,14 @@ class BookmarkOrderingChoices(ChoiceSet):
|
||||
|
||||
ORDERING_NEWEST = '-created'
|
||||
ORDERING_OLDEST = 'created'
|
||||
ORDERING_ALPHABETICAL_AZ = 'name'
|
||||
ORDERING_ALPHABETICAL_ZA = '-name'
|
||||
|
||||
CHOICES = (
|
||||
(ORDERING_NEWEST, _('Newest')),
|
||||
(ORDERING_OLDEST, _('Oldest')),
|
||||
(ORDERING_ALPHABETICAL_AZ, _('Alphabetical (A-Z)')),
|
||||
(ORDERING_ALPHABETICAL_ZA, _('Alphabetical (Z-A)')),
|
||||
)
|
||||
|
||||
#
|
||||
|
@ -381,11 +381,17 @@ class BookmarksWidget(DashboardWidget):
|
||||
if request.user.is_anonymous:
|
||||
bookmarks = list()
|
||||
else:
|
||||
bookmarks = Bookmark.objects.filter(user=request.user).order_by(self.config['order_by'])
|
||||
user_bookmarks = Bookmark.objects.filter(user=request.user)
|
||||
if self.config['order_by'] == BookmarkOrderingChoices.ORDERING_ALPHABETICAL_AZ:
|
||||
bookmarks = sorted(user_bookmarks, key=lambda bookmark: bookmark.__str__().lower())
|
||||
elif self.config['order_by'] == BookmarkOrderingChoices.ORDERING_ALPHABETICAL_ZA:
|
||||
bookmarks = sorted(user_bookmarks, key=lambda bookmark: bookmark.__str__().lower(), reverse=True)
|
||||
else:
|
||||
bookmarks = user_bookmarks.order_by(self.config['order_by'])
|
||||
if object_types := self.config.get('object_types'):
|
||||
models = get_models_from_content_types(object_types)
|
||||
conent_types = ObjectType.objects.get_for_models(*models).values()
|
||||
bookmarks = bookmarks.filter(object_type__in=conent_types)
|
||||
content_types = ObjectType.objects.get_for_models(*models).values()
|
||||
bookmarks = bookmarks.filter(object_type__in=content_types)
|
||||
if max_items := self.config.get('max_items'):
|
||||
bookmarks = bookmarks[:max_items]
|
||||
|
||||
|
@ -660,6 +660,10 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||
# Validate date & time
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_DATETIME:
|
||||
if type(value) is not datetime:
|
||||
# Work around UTC issue for Python < 3.11; see
|
||||
# https://docs.python.org/3/library/datetime.html#datetime.datetime.fromisoformat
|
||||
if type(value) is str and value.endswith('Z'):
|
||||
value = f'{value[:-1]}+00:00'
|
||||
try:
|
||||
datetime.fromisoformat(value)
|
||||
except ValueError:
|
||||
|
@ -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),
|
||||
}
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
BIN
netbox/project-static/dist/netbox.css
vendored
BIN
netbox/project-static/dist/netbox.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js
vendored
BIN
netbox/project-static/dist/netbox.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js.map
vendored
BIN
netbox/project-static/dist/netbox.js.map
vendored
Binary file not shown.
30
netbox/project-static/src/forms/savedFiltersSelect.ts
Normal file
30
netbox/project-static/src/forms/savedFiltersSelect.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { isTruthy } from '../util';
|
||||
|
||||
/**
|
||||
* Handle saved filter change event.
|
||||
*
|
||||
* @param event "change" event for the saved filter select
|
||||
*/
|
||||
function handleSavedFilterChange(event: Event): void {
|
||||
const savedFilter = event.currentTarget as HTMLSelectElement;
|
||||
let baseUrl = savedFilter.baseURI.split('?')[0];
|
||||
const preFilter = '?';
|
||||
|
||||
const selectedOptions = Array.from(savedFilter.options)
|
||||
.filter(option => option.selected)
|
||||
.map(option => `filter_id=${option.value}`)
|
||||
.join('&');
|
||||
|
||||
baseUrl += `${preFilter}${selectedOptions}`;
|
||||
document.location.href = baseUrl;
|
||||
}
|
||||
|
||||
export function initSavedFilterSelect(): void {
|
||||
const divResults = document.getElementById('results');
|
||||
if (isTruthy(divResults)) {
|
||||
const savedFilterSelect = document.getElementById('id_filter_id');
|
||||
if (isTruthy(savedFilterSelect)) {
|
||||
savedFilterSelect.addEventListener('change', handleSavedFilterChange);
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import { initSideNav } from './sidenav';
|
||||
import { initDashboard } from './dashboard';
|
||||
import { initRackElevation } from './racks';
|
||||
import { initHtmx } from './htmx';
|
||||
import { initSavedFilterSelect } from './forms/savedFiltersSelect';
|
||||
|
||||
function initDocument(): void {
|
||||
for (const init of [
|
||||
@ -31,6 +32,7 @@ function initDocument(): void {
|
||||
initDashboard,
|
||||
initRackElevation,
|
||||
initHtmx,
|
||||
initSavedFilterSelect,
|
||||
]) {
|
||||
init();
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
// Overrides of external libraries
|
||||
@import 'overrides/bootstrap';
|
||||
@import 'overrides/tabler';
|
||||
@import 'overrides/tomselect';
|
||||
|
||||
// Transitional styling to ease migration of templates from NetBox v3.x
|
||||
@import 'transitional/badges';
|
||||
|
8
netbox/project-static/styles/overrides/_tomselect.scss
Normal file
8
netbox/project-static/styles/overrides/_tomselect.scss
Normal file
@ -0,0 +1,8 @@
|
||||
.ts-wrapper.multi {
|
||||
.ts-control {
|
||||
padding: 7px 7px 3px 7px;
|
||||
div {
|
||||
margin: 0 4px 4px 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@ Blocks:
|
||||
|
||||
{# User menu (mobile view) #}
|
||||
<div class="navbar-nav flex-row d-lg-none">
|
||||
{% include 'inc/light_toggle.html' %}
|
||||
{% include 'inc/user_menu.html' %}
|
||||
</div>
|
||||
|
||||
@ -52,14 +53,7 @@ Blocks:
|
||||
|
||||
<div class="navbar-nav flex-row align-items-center order-md-last">
|
||||
{# Dark/light mode toggle #}
|
||||
<div class="d-none d-md-flex">
|
||||
<button class="btn color-mode-toggle hide-theme-dark" title="{% trans "Enable dark mode" %}" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<i class="mdi mdi-lightbulb"></i>
|
||||
</button>
|
||||
<button class="btn color-mode-toggle hide-theme-light" title="{% trans "Enable light mode" %}" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<i class="mdi mdi-lightbulb-on"></i>
|
||||
</button>
|
||||
</div>
|
||||
{% include 'inc/light_toggle.html' %}
|
||||
|
||||
{# User menu #}
|
||||
{% include 'inc/user_menu.html' %}
|
||||
|
@ -28,7 +28,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Rack" %}</th>
|
||||
<td class="d-flex justify-content-between">
|
||||
<td class="d-flex justify-content-between align-items-start">
|
||||
{% if object.rack %}
|
||||
{{ object.rack|linkify }}
|
||||
<a href="{{ object.rack.get_absolute_url }}?device={{ object.pk }}" class="btn btn-primary btn-sm d-print-none" title="{% trans "Highlight device in rack" %}">
|
||||
|
@ -73,7 +73,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Physical Address" %}</th>
|
||||
<td class="d-flex justify-content-between">
|
||||
<td class="d-flex justify-content-between align-items-start">
|
||||
{% if object.physical_address %}
|
||||
<span>{{ object.physical_address|linebreaksbr }}</span>
|
||||
{% if config.MAPS_URL %}
|
||||
|
10
netbox/templates/inc/light_toggle.html
Normal file
10
netbox/templates/inc/light_toggle.html
Normal file
@ -0,0 +1,10 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="d-flex">
|
||||
<button class="btn color-mode-toggle hide-theme-dark" title="{% trans "Enable dark mode" %}" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<i class="mdi mdi-lightbulb"></i>
|
||||
</button>
|
||||
<button class="btn color-mode-toggle hide-theme-light" title="{% trans "Enable light mode" %}" data-bs-toggle="tooltip" data-bs-placement="bottom">
|
||||
<i class="mdi mdi-lightbulb-on"></i>
|
||||
</button>
|
||||
</div>
|
@ -1,21 +1,32 @@
|
||||
{% load helpers %}
|
||||
{% load i18n %}
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="row mb-3" id="results">
|
||||
<div class="col-auto d-print-none">
|
||||
<div class="input-group input-group-flat me-2 quicksearch" hx-disinherit="hx-select hx-swap">
|
||||
<input type="search" results="5" name="q" id="quicksearch" class="form-control px-2 py-1" placeholder="Quick search"
|
||||
hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search" />
|
||||
<input type="search" results="5" name="q" id="quicksearch" class="form-control" placeholder="{% trans "Quick search" %}"
|
||||
hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search"/>
|
||||
<span class="input-group-text py-1">
|
||||
<a href="#" id="quicksearch_clear" class="invisible text-secondary"><i class="mdi mdi-close-circle"></i></a>
|
||||
</span>
|
||||
{% block extra_table_controls %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto d-print-none">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
<i class="mdi mdi-filter" title="{% trans "Saved filter" %}"></i>
|
||||
</div>
|
||||
{{ filter_form.filter_id }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto ms-auto d-print-none">
|
||||
{% if request.user.is_authenticated and table_modal %}
|
||||
<div class="table-configure input-group">
|
||||
<button type="button" data-bs-toggle="modal" title="{% trans "Configure Table" %}" data-bs-target="#{{ table_modal }}"
|
||||
<button type="button" data-bs-toggle="modal" title="{% trans "Configure Table" %}"
|
||||
data-bs-target="#{{ table_modal }}"
|
||||
class="btn">
|
||||
<i class="mdi mdi-cog"></i> {% trans "Configure Table" %}
|
||||
</button>
|
||||
@ -23,3 +34,4 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
|
||||
|
||||
|
@ -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-19 05:02+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
|
||||
@ -58,7 +58,7 @@ msgstr ""
|
||||
msgid "Allowed IPs"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/account/views.py:197
|
||||
#: netbox/account/views.py:204
|
||||
msgid "Your preferences have been updated."
|
||||
msgstr ""
|
||||
|
||||
@ -158,7 +158,7 @@ msgstr ""
|
||||
#: netbox/circuits/forms/filtersets.py:207
|
||||
#: netbox/circuits/forms/model_forms.py:136
|
||||
#: netbox/circuits/forms/model_forms.py:152
|
||||
#: netbox/circuits/tables/circuits.py:105 netbox/dcim/forms/bulk_edit.py:167
|
||||
#: netbox/circuits/tables/circuits.py:107 netbox/dcim/forms/bulk_edit.py:167
|
||||
#: netbox/dcim/forms/bulk_edit.py:239 netbox/dcim/forms/bulk_edit.py:575
|
||||
#: netbox/dcim/forms/bulk_edit.py:771 netbox/dcim/forms/bulk_import.py:130
|
||||
#: netbox/dcim/forms/bulk_import.py:184 netbox/dcim/forms/bulk_import.py:257
|
||||
@ -308,7 +308,7 @@ msgstr ""
|
||||
#: netbox/circuits/forms/filtersets.py:212
|
||||
#: netbox/circuits/forms/model_forms.py:109
|
||||
#: netbox/circuits/forms/model_forms.py:131
|
||||
#: netbox/circuits/tables/circuits.py:96 netbox/dcim/forms/connections.py:71
|
||||
#: netbox/circuits/tables/circuits.py:98 netbox/dcim/forms/connections.py:71
|
||||
#: netbox/templates/circuits/circuit.html:15
|
||||
#: netbox/templates/circuits/circuittermination.html:19
|
||||
#: netbox/templates/dcim/inc/cable_termination.html:55
|
||||
@ -325,7 +325,7 @@ msgstr ""
|
||||
#: netbox/circuits/tables/providers.py:33 netbox/dcim/forms/bulk_edit.py:127
|
||||
#: netbox/dcim/forms/filtersets.py:188 netbox/dcim/forms/model_forms.py:122
|
||||
#: netbox/dcim/tables/sites.py:94 netbox/ipam/models/asns.py:126
|
||||
#: netbox/ipam/tables/asn.py:27 netbox/ipam/views.py:219
|
||||
#: netbox/ipam/tables/asn.py:27 netbox/ipam/views.py:210
|
||||
#: netbox/netbox/navigation/menu.py:159 netbox/netbox/navigation/menu.py:162
|
||||
#: netbox/templates/circuits/provider.html:23
|
||||
msgid "ASNs"
|
||||
@ -469,7 +469,7 @@ msgstr ""
|
||||
#: netbox/circuits/forms/model_forms.py:45
|
||||
#: netbox/circuits/forms/model_forms.py:59
|
||||
#: netbox/circuits/forms/model_forms.py:91
|
||||
#: netbox/circuits/tables/circuits.py:56 netbox/circuits/tables/circuits.py:100
|
||||
#: netbox/circuits/tables/circuits.py:56 netbox/circuits/tables/circuits.py:102
|
||||
#: netbox/circuits/tables/providers.py:72
|
||||
#: netbox/circuits/tables/providers.py:103
|
||||
#: netbox/templates/circuits/circuit.html:18
|
||||
@ -748,7 +748,7 @@ msgstr ""
|
||||
#: netbox/circuits/forms/bulk_edit.py:191
|
||||
#: netbox/circuits/forms/bulk_edit.py:215
|
||||
#: netbox/circuits/forms/model_forms.py:153
|
||||
#: netbox/circuits/tables/circuits.py:109
|
||||
#: netbox/circuits/tables/circuits.py:111
|
||||
#: netbox/templates/circuits/inc/circuit_termination_fields.html:62
|
||||
#: netbox/templates/circuits/providernetwork.html:17
|
||||
msgid "Provider Network"
|
||||
@ -895,7 +895,7 @@ msgstr ""
|
||||
#: netbox/dcim/forms/filtersets.py:653 netbox/dcim/forms/filtersets.py:1010
|
||||
#: netbox/netbox/navigation/menu.py:44 netbox/netbox/navigation/menu.py:46
|
||||
#: netbox/tenancy/forms/filtersets.py:42 netbox/tenancy/tables/columns.py:70
|
||||
#: netbox/tenancy/tables/contacts.py:25 netbox/tenancy/views.py:19
|
||||
#: netbox/tenancy/tables/contacts.py:25 netbox/tenancy/views.py:18
|
||||
#: netbox/virtualization/forms/filtersets.py:37
|
||||
#: netbox/virtualization/forms/filtersets.py:48
|
||||
#: netbox/virtualization/forms/filtersets.py:106
|
||||
@ -1328,21 +1328,21 @@ msgstr ""
|
||||
msgid "Circuit ID"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/tables/circuits.py:66
|
||||
#: netbox/circuits/tables/circuits.py:67
|
||||
#: netbox/wireless/forms/model_forms.py:160
|
||||
msgid "Side A"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/tables/circuits.py:70
|
||||
#: netbox/circuits/tables/circuits.py:72
|
||||
msgid "Side Z"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/tables/circuits.py:73
|
||||
#: netbox/circuits/tables/circuits.py:75
|
||||
#: netbox/templates/circuits/circuit.html:55
|
||||
msgid "Commit Rate"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/tables/circuits.py:76 netbox/circuits/tables/providers.py:48
|
||||
#: netbox/circuits/tables/circuits.py:78 netbox/circuits/tables/providers.py:48
|
||||
#: netbox/circuits/tables/providers.py:82
|
||||
#: netbox/circuits/tables/providers.py:107 netbox/dcim/tables/devices.py:1001
|
||||
#: netbox/dcim/tables/devicetypes.py:92 netbox/dcim/tables/modules.py:29
|
||||
@ -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 ""
|
||||
|
||||
@ -2067,8 +2067,8 @@ msgstr ""
|
||||
msgid "No workers found"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/views.py:335 netbox/core/views.py:378 netbox/core/views.py:401
|
||||
#: netbox/core/views.py:419 netbox/core/views.py:454
|
||||
#: netbox/core/views.py:331 netbox/core/views.py:374 netbox/core/views.py:397
|
||||
#: netbox/core/views.py:415 netbox/core/views.py:450
|
||||
#, python-brace-format
|
||||
msgid "Job {job_id} not found"
|
||||
msgstr ""
|
||||
@ -2946,7 +2946,7 @@ msgstr ""
|
||||
#: netbox/dcim/forms/bulk_create.py:40 netbox/extras/forms/filtersets.py:410
|
||||
#: netbox/extras/forms/model_forms.py:443
|
||||
#: netbox/extras/forms/model_forms.py:495 netbox/netbox/forms/base.py:84
|
||||
#: netbox/netbox/forms/mixins.py:81 netbox/netbox/tables/columns.py:458
|
||||
#: netbox/netbox/forms/mixins.py:81 netbox/netbox/tables/columns.py:461
|
||||
#: netbox/templates/circuits/inc/circuit_termination.html:32
|
||||
#: netbox/templates/generic/bulk_edit.html:65
|
||||
#: netbox/templates/inc/panels/tags.html:5
|
||||
@ -5974,7 +5974,7 @@ msgstr ""
|
||||
#: netbox/netbox/navigation/menu.py:60 netbox/netbox/navigation/menu.py:62
|
||||
#: netbox/virtualization/forms/model_forms.py:122
|
||||
#: netbox/virtualization/tables/clusters.py:83
|
||||
#: netbox/virtualization/views.py:210
|
||||
#: netbox/virtualization/views.py:202
|
||||
msgid "Devices"
|
||||
msgstr ""
|
||||
|
||||
@ -6054,8 +6054,8 @@ msgid "Power outlets"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devices.py:243 netbox/dcim/tables/devices.py:1046
|
||||
#: netbox/dcim/tables/devicetypes.py:125 netbox/dcim/views.py:1006
|
||||
#: netbox/dcim/views.py:1245 netbox/dcim/views.py:1931
|
||||
#: netbox/dcim/tables/devicetypes.py:125 netbox/dcim/views.py:985
|
||||
#: netbox/dcim/views.py:1224 netbox/dcim/views.py:1900
|
||||
#: netbox/netbox/navigation/menu.py:81 netbox/netbox/navigation/menu.py:237
|
||||
#: netbox/templates/dcim/device/base.html:37
|
||||
#: netbox/templates/dcim/device_list.html:43
|
||||
@ -6067,7 +6067,7 @@ msgstr ""
|
||||
#: netbox/templates/virtualization/virtualmachine/base.html:27
|
||||
#: netbox/templates/virtualization/virtualmachine_list.html:14
|
||||
#: netbox/virtualization/tables/virtualmachines.py:100
|
||||
#: netbox/virtualization/views.py:367 netbox/wireless/tables/wirelesslan.py:55
|
||||
#: netbox/virtualization/views.py:359 netbox/wireless/tables/wirelesslan.py:55
|
||||
msgid "Interfaces"
|
||||
msgstr ""
|
||||
|
||||
@ -6093,8 +6093,8 @@ msgid "Module Bay"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devices.py:310 netbox/dcim/tables/devicetypes.py:48
|
||||
#: netbox/dcim/tables/devicetypes.py:140 netbox/dcim/views.py:1081
|
||||
#: netbox/dcim/views.py:2024 netbox/netbox/navigation/menu.py:90
|
||||
#: netbox/dcim/tables/devicetypes.py:140 netbox/dcim/views.py:1060
|
||||
#: netbox/dcim/views.py:1993 netbox/netbox/navigation/menu.py:90
|
||||
#: netbox/templates/dcim/device/base.html:52
|
||||
#: netbox/templates/dcim/device_list.html:71
|
||||
#: netbox/templates/dcim/devicetype/base.html:49
|
||||
@ -6124,8 +6124,8 @@ msgid "Allocated draw (W)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devices.py:546 netbox/ipam/forms/model_forms.py:747
|
||||
#: netbox/ipam/tables/fhrp.py:28 netbox/ipam/views.py:602
|
||||
#: netbox/ipam/views.py:701 netbox/netbox/navigation/menu.py:145
|
||||
#: netbox/ipam/tables/fhrp.py:28 netbox/ipam/views.py:589
|
||||
#: netbox/ipam/views.py:688 netbox/netbox/navigation/menu.py:145
|
||||
#: netbox/netbox/navigation/menu.py:147
|
||||
#: netbox/templates/dcim/interface.html:339
|
||||
#: netbox/templates/ipam/ipaddress_bulk_add.html:15
|
||||
@ -6218,8 +6218,8 @@ msgstr ""
|
||||
msgid "Instances"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:113 netbox/dcim/views.py:946
|
||||
#: netbox/dcim/views.py:1185 netbox/dcim/views.py:1871
|
||||
#: netbox/dcim/tables/devicetypes.py:113 netbox/dcim/views.py:925
|
||||
#: netbox/dcim/views.py:1164 netbox/dcim/views.py:1840
|
||||
#: netbox/netbox/navigation/menu.py:84
|
||||
#: netbox/templates/dcim/device/base.html:25
|
||||
#: netbox/templates/dcim/device_list.html:15
|
||||
@ -6229,8 +6229,8 @@ msgstr ""
|
||||
msgid "Console Ports"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:116 netbox/dcim/views.py:961
|
||||
#: netbox/dcim/views.py:1200 netbox/dcim/views.py:1886
|
||||
#: netbox/dcim/tables/devicetypes.py:116 netbox/dcim/views.py:940
|
||||
#: netbox/dcim/views.py:1179 netbox/dcim/views.py:1855
|
||||
#: netbox/netbox/navigation/menu.py:85
|
||||
#: netbox/templates/dcim/device/base.html:28
|
||||
#: netbox/templates/dcim/device_list.html:22
|
||||
@ -6240,8 +6240,8 @@ msgstr ""
|
||||
msgid "Console Server Ports"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:119 netbox/dcim/views.py:976
|
||||
#: netbox/dcim/views.py:1215 netbox/dcim/views.py:1901
|
||||
#: netbox/dcim/tables/devicetypes.py:119 netbox/dcim/views.py:955
|
||||
#: netbox/dcim/views.py:1194 netbox/dcim/views.py:1870
|
||||
#: netbox/netbox/navigation/menu.py:86
|
||||
#: netbox/templates/dcim/device/base.html:31
|
||||
#: netbox/templates/dcim/device_list.html:29
|
||||
@ -6251,8 +6251,8 @@ msgstr ""
|
||||
msgid "Power Ports"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:122 netbox/dcim/views.py:991
|
||||
#: netbox/dcim/views.py:1230 netbox/dcim/views.py:1916
|
||||
#: netbox/dcim/tables/devicetypes.py:122 netbox/dcim/views.py:970
|
||||
#: netbox/dcim/views.py:1209 netbox/dcim/views.py:1885
|
||||
#: netbox/netbox/navigation/menu.py:87
|
||||
#: netbox/templates/dcim/device/base.html:34
|
||||
#: netbox/templates/dcim/device_list.html:36
|
||||
@ -6262,8 +6262,8 @@ msgstr ""
|
||||
msgid "Power Outlets"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:128 netbox/dcim/views.py:1021
|
||||
#: netbox/dcim/views.py:1260 netbox/dcim/views.py:1952
|
||||
#: netbox/dcim/tables/devicetypes.py:128 netbox/dcim/views.py:1000
|
||||
#: netbox/dcim/views.py:1239 netbox/dcim/views.py:1921
|
||||
#: netbox/netbox/navigation/menu.py:82
|
||||
#: netbox/templates/dcim/device/base.html:40
|
||||
#: netbox/templates/dcim/devicetype/base.html:37
|
||||
@ -6272,8 +6272,8 @@ msgstr ""
|
||||
msgid "Front Ports"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:131 netbox/dcim/views.py:1036
|
||||
#: netbox/dcim/views.py:1275 netbox/dcim/views.py:1967
|
||||
#: netbox/dcim/tables/devicetypes.py:131 netbox/dcim/views.py:1015
|
||||
#: netbox/dcim/views.py:1254 netbox/dcim/views.py:1936
|
||||
#: netbox/netbox/navigation/menu.py:83
|
||||
#: netbox/templates/dcim/device/base.html:43
|
||||
#: netbox/templates/dcim/device_list.html:50
|
||||
@ -6283,16 +6283,16 @@ msgstr ""
|
||||
msgid "Rear Ports"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:134 netbox/dcim/views.py:1066
|
||||
#: netbox/dcim/views.py:2005 netbox/netbox/navigation/menu.py:89
|
||||
#: netbox/dcim/tables/devicetypes.py:134 netbox/dcim/views.py:1045
|
||||
#: netbox/dcim/views.py:1974 netbox/netbox/navigation/menu.py:89
|
||||
#: netbox/templates/dcim/device/base.html:49
|
||||
#: netbox/templates/dcim/device_list.html:57
|
||||
#: netbox/templates/dcim/devicetype/base.html:46
|
||||
msgid "Device Bays"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/tables/devicetypes.py:137 netbox/dcim/views.py:1051
|
||||
#: netbox/dcim/views.py:1986 netbox/netbox/navigation/menu.py:88
|
||||
#: netbox/dcim/tables/devicetypes.py:137 netbox/dcim/views.py:1030
|
||||
#: netbox/dcim/views.py:1955 netbox/netbox/navigation/menu.py:88
|
||||
#: netbox/templates/dcim/device/base.html:46
|
||||
#: netbox/templates/dcim/device_list.html:64
|
||||
#: netbox/templates/dcim/devicetype/base.html:43
|
||||
@ -6350,38 +6350,38 @@ msgstr ""
|
||||
msgid "Test case must set peer_termination_type"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:137
|
||||
#: netbox/dcim/views.py:139
|
||||
#, python-brace-format
|
||||
msgid "Disconnected {count} {type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:698 netbox/netbox/navigation/menu.py:28
|
||||
#: netbox/dcim/views.py:684 netbox/netbox/navigation/menu.py:28
|
||||
msgid "Reservations"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:716 netbox/templates/dcim/location.html:90
|
||||
#: netbox/dcim/views.py:702 netbox/templates/dcim/location.html:90
|
||||
#: netbox/templates/dcim/site.html:140
|
||||
msgid "Non-Racked Devices"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:2037 netbox/extras/forms/model_forms.py:453
|
||||
#: netbox/dcim/views.py:2006 netbox/extras/forms/model_forms.py:453
|
||||
#: netbox/templates/extras/configcontext.html:10
|
||||
#: netbox/virtualization/forms/model_forms.py:225
|
||||
#: netbox/virtualization/views.py:407
|
||||
#: netbox/virtualization/views.py:399
|
||||
msgid "Config Context"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:2047 netbox/virtualization/views.py:417
|
||||
#: netbox/dcim/views.py:2016 netbox/virtualization/views.py:409
|
||||
msgid "Render Config"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:2097 netbox/extras/tables/tables.py:440
|
||||
#: netbox/dcim/views.py:2066 netbox/extras/tables/tables.py:440
|
||||
#: netbox/netbox/navigation/menu.py:234 netbox/netbox/navigation/menu.py:236
|
||||
#: netbox/virtualization/views.py:185
|
||||
#: netbox/virtualization/views.py:177
|
||||
msgid "Virtual Machines"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/views.py:2989 netbox/ipam/tables/ip.py:233
|
||||
#: netbox/dcim/views.py:2948 netbox/ipam/tables/ip.py:233
|
||||
msgid "Children"
|
||||
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 ""
|
||||
@ -7678,56 +7686,56 @@ msgstr ""
|
||||
msgid "Date values must be in ISO 8601 format (YYYY-MM-DD)."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:667
|
||||
#: netbox/extras/models/customfields.py:671
|
||||
msgid "Date and time values must be in ISO 8601 format (YYYY-MM-DD HH:MM:SS)."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:674
|
||||
#: netbox/extras/models/customfields.py:678
|
||||
#, python-brace-format
|
||||
msgid "Invalid choice ({value}) for choice set {choiceset}."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:684
|
||||
#: netbox/extras/models/customfields.py:688
|
||||
#, python-brace-format
|
||||
msgid "Invalid choice(s) ({value}) for choice set {choiceset}."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:693
|
||||
#: netbox/extras/models/customfields.py:697
|
||||
#, python-brace-format
|
||||
msgid "Value must be an object ID, not {type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:699
|
||||
#: netbox/extras/models/customfields.py:703
|
||||
#, python-brace-format
|
||||
msgid "Value must be a list of object IDs, not {type}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:703
|
||||
#: netbox/extras/models/customfields.py:707
|
||||
#, python-brace-format
|
||||
msgid "Found invalid object ID: {id}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:706
|
||||
#: netbox/extras/models/customfields.py:710
|
||||
msgid "Required field cannot be empty."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:725
|
||||
#: netbox/extras/models/customfields.py:729
|
||||
msgid "Base set of predefined choices (optional)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:737
|
||||
#: netbox/extras/models/customfields.py:741
|
||||
msgid "Choices are automatically ordered alphabetically"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:744
|
||||
#: netbox/extras/models/customfields.py:748
|
||||
msgid "custom field choice set"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:745
|
||||
#: netbox/extras/models/customfields.py:749
|
||||
msgid "custom field choice sets"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:781
|
||||
#: netbox/extras/models/customfields.py:785
|
||||
msgid "Must define base or extra choices."
|
||||
msgstr ""
|
||||
|
||||
@ -9407,7 +9415,7 @@ msgid "The primary function of this VLAN"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/models/vlans.py:215 netbox/ipam/tables/ip.py:175
|
||||
#: netbox/ipam/tables/vlans.py:78 netbox/ipam/views.py:978
|
||||
#: netbox/ipam/tables/vlans.py:78 netbox/ipam/views.py:961
|
||||
#: netbox/netbox/navigation/menu.py:180 netbox/netbox/navigation/menu.py:182
|
||||
msgid "VLANs"
|
||||
msgstr ""
|
||||
@ -9479,7 +9487,7 @@ msgid "Added"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/tables/ip.py:127 netbox/ipam/tables/ip.py:165
|
||||
#: netbox/ipam/tables/vlans.py:138 netbox/ipam/views.py:349
|
||||
#: netbox/ipam/tables/vlans.py:138 netbox/ipam/views.py:342
|
||||
#: netbox/netbox/navigation/menu.py:152 netbox/netbox/navigation/menu.py:154
|
||||
#: netbox/templates/ipam/vlan.html:84
|
||||
msgid "Prefixes"
|
||||
@ -9580,23 +9588,23 @@ msgid ""
|
||||
"are allowed in DNS names"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/views.py:541
|
||||
#: netbox/ipam/views.py:528
|
||||
msgid "Child Prefixes"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/views.py:576
|
||||
#: netbox/ipam/views.py:563
|
||||
msgid "Child Ranges"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/views.py:902
|
||||
#: netbox/ipam/views.py:889
|
||||
msgid "Related IPs"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/views.py:1133
|
||||
#: netbox/ipam/views.py:1116
|
||||
msgid "Device Interfaces"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/ipam/views.py:1150
|
||||
#: netbox/ipam/views.py:1133
|
||||
msgid "VM Interfaces"
|
||||
msgstr ""
|
||||
|
||||
@ -10151,7 +10159,7 @@ msgstr ""
|
||||
#: netbox/templates/virtualization/virtualmachine/base.html:32
|
||||
#: netbox/templates/virtualization/virtualmachine_list.html:21
|
||||
#: netbox/virtualization/tables/virtualmachines.py:103
|
||||
#: netbox/virtualization/views.py:388
|
||||
#: netbox/virtualization/views.py:380
|
||||
msgid "Virtual Disks"
|
||||
msgstr ""
|
||||
|
||||
@ -10490,15 +10498,15 @@ msgstr ""
|
||||
msgid "Chinese"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/tables/columns.py:185
|
||||
#: netbox/netbox/tables/columns.py:188
|
||||
msgid "Toggle all"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/tables/columns.py:287
|
||||
#: netbox/netbox/tables/columns.py:290
|
||||
msgid "Toggle Dropdown"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/netbox/tables/columns.py:552 netbox/templates/core/job.html:35
|
||||
#: netbox/netbox/tables/columns.py:555 netbox/templates/core/job.html:35
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
@ -10782,36 +10790,28 @@ msgstr ""
|
||||
msgid "NetBox Logo"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:56
|
||||
msgid "Enable dark mode"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:59
|
||||
msgid "Enable light mode"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:145
|
||||
#: netbox/templates/base/layout.html:139
|
||||
msgid "Docs"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:151
|
||||
#: netbox/templates/base/layout.html:145
|
||||
#: netbox/templates/rest_framework/api.html:10
|
||||
msgid "REST API"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:157
|
||||
#: netbox/templates/base/layout.html:151
|
||||
msgid "REST API documentation"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:164
|
||||
#: netbox/templates/base/layout.html:158
|
||||
msgid "GraphQL API"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:171
|
||||
#: netbox/templates/base/layout.html:165
|
||||
msgid "Source Code"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/base/layout.html:177
|
||||
#: netbox/templates/base/layout.html:171
|
||||
msgid "Community"
|
||||
msgstr ""
|
||||
|
||||
@ -11104,8 +11104,8 @@ msgstr ""
|
||||
#: netbox/templates/core/rq_worker_list.html:45
|
||||
#: netbox/templates/extras/script_result.html:49
|
||||
#: netbox/templates/extras/script_result.html:51
|
||||
#: netbox/templates/inc/table_controls_htmx.html:18
|
||||
#: netbox/templates/inc/table_controls_htmx.html:20
|
||||
#: netbox/templates/inc/table_controls_htmx.html:28
|
||||
#: netbox/templates/inc/table_controls_htmx.html:31
|
||||
msgid "Configure Table"
|
||||
msgstr ""
|
||||
|
||||
@ -12681,6 +12681,14 @@ msgstr ""
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/inc/light_toggle.html:4
|
||||
msgid "Enable dark mode"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/inc/light_toggle.html:7
|
||||
msgid "Enable light mode"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/inc/missing_prerequisites.html:8
|
||||
#, python-format
|
||||
msgid ""
|
||||
@ -12721,6 +12729,14 @@ msgstr ""
|
||||
msgid "Data is out of sync with upstream file"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/inc/table_controls_htmx.html:7
|
||||
msgid "Quick search"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/inc/table_controls_htmx.html:19
|
||||
msgid "Saved filter"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/inc/user_menu.html:23
|
||||
msgid "Django Admin"
|
||||
msgstr ""
|
||||
@ -14064,17 +14080,17 @@ msgstr ""
|
||||
msgid "{value} is not a valid regular expression."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/views.py:40
|
||||
#: netbox/utilities/views.py:44
|
||||
#, python-brace-format
|
||||
msgid "{self.__class__.__name__} must implement get_required_permission()"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/views.py:76
|
||||
#: netbox/utilities/views.py:80
|
||||
#, python-brace-format
|
||||
msgid "{class_name} must implement get_required_permission()"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/views.py:100
|
||||
#: netbox/utilities/views.py:104
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"{class_name} has no queryset defined. ObjectPermissionRequiredMixin may only "
|
||||
|
@ -29,7 +29,7 @@ def linkify_phone(value):
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
return f"tel:{value}"
|
||||
return f"tel:{value.replace(' ', '')}"
|
||||
|
||||
|
||||
def register_table_column(column, name, *tables):
|
||||
|
@ -11,7 +11,7 @@
|
||||
{% elif customfield.type == 'date' and value %}
|
||||
{{ value|isodate }}
|
||||
{% elif customfield.type == 'datetime' and value %}
|
||||
{{ value|isodate }} {{ value|isodatetime }}
|
||||
{{ value|isodatetime }}
|
||||
{% elif customfield.type == 'url' and value %}
|
||||
<a href="{{ value }}">{{ value|truncatechars:70 }}</a>
|
||||
{% elif customfield.type == 'json' and value %}
|
||||
|
@ -1,4 +1,5 @@
|
||||
from django import template
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from extras.choices import CustomFieldTypeChoices
|
||||
from utilities.querydict import dict_to_querydict
|
||||
@ -124,5 +125,5 @@ def formaction(context):
|
||||
if HTMX navigation is enabled (per the user's preferences).
|
||||
"""
|
||||
if context.get('htmx_navigation', False):
|
||||
return 'hx-push-url="true" hx-post'
|
||||
return mark_safe('hx-push-url="true" hx-post')
|
||||
return 'formaction'
|
||||
|
@ -281,6 +281,10 @@ def applied_filters(context, model, form, query_params):
|
||||
if filter_name not in querydict:
|
||||
continue
|
||||
|
||||
# Skip saved filters, as they're displayed alongside the quick search widget
|
||||
if filter_name == 'filter_id':
|
||||
continue
|
||||
|
||||
bound_field = form.fields[filter_name].get_bound_field(form, filter_name)
|
||||
querydict.pop(filter_name)
|
||||
display_value = ', '.join([str(v) for v in get_selected_values(form, filter_name)])
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
}
|
||||
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user