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
|
.idea
|
||||||
.coverage
|
.coverage
|
||||||
.vscode
|
.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.
|
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)
|
## 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)
|
## 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
|
# Ensure the user has a UserConfig defined. (This should normally be handled by
|
||||||
# create_userconfig() on user creation.)
|
# create_userconfig() on user creation.)
|
||||||
if not hasattr(request.user, 'config'):
|
if not hasattr(request.user, 'config'):
|
||||||
config = get_config()
|
request.user.config = get_config()
|
||||||
UserConfig(user=request.user, data=config.DEFAULT_USER_PREFERENCES).save()
|
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:
|
else:
|
||||||
logger.debug(f"Login form validation failed for username: {form['username'].value()}")
|
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")
|
logger.info(f"User {username} has logged out")
|
||||||
messages.info(request, "You have 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 = HttpResponseRedirect(resolve_url(settings.LOGOUT_REDIRECT_URL))
|
||||||
response.delete_cookie('session_key')
|
response.delete_cookie('session_key')
|
||||||
|
response.delete_cookie(settings.LANGUAGE_COOKIE_NAME)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -63,10 +63,12 @@ class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
|||||||
status = columns.ChoiceFieldColumn()
|
status = columns.ChoiceFieldColumn()
|
||||||
termination_a = tables.TemplateColumn(
|
termination_a = tables.TemplateColumn(
|
||||||
template_code=CIRCUITTERMINATION_LINK,
|
template_code=CIRCUITTERMINATION_LINK,
|
||||||
|
orderable=False,
|
||||||
verbose_name=_('Side A')
|
verbose_name=_('Side A')
|
||||||
)
|
)
|
||||||
termination_z = tables.TemplateColumn(
|
termination_z = tables.TemplateColumn(
|
||||||
template_code=CIRCUITTERMINATION_LINK,
|
template_code=CIRCUITTERMINATION_LINK,
|
||||||
|
orderable=False,
|
||||||
verbose_name=_('Side Z')
|
verbose_name=_('Side Z')
|
||||||
)
|
)
|
||||||
commit_rate = CommitRateColumn(
|
commit_rate = CommitRateColumn(
|
||||||
|
@ -7,7 +7,7 @@ from netbox.views import generic
|
|||||||
from tenancy.views import ObjectContactsView
|
from tenancy.views import ObjectContactsView
|
||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.query import count_related
|
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 . import filtersets, forms, tables
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
@ -26,17 +26,12 @@ class ProviderListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(Provider)
|
@register_model_view(Provider)
|
||||||
class ProviderView(generic.ObjectView):
|
class ProviderView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = Provider.objects.all()
|
queryset = Provider.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
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 {
|
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)
|
@register_model_view(ProviderAccount)
|
||||||
class ProviderAccountView(generic.ObjectView):
|
class ProviderAccountView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = ProviderAccount.objects.all()
|
queryset = ProviderAccount.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(Circuit.objects.restrict(request.user, 'view').filter(provider_account=instance), 'provider_account_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
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)
|
@register_model_view(ProviderNetwork)
|
||||||
class ProviderNetworkView(generic.ObjectView):
|
class ProviderNetworkView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = ProviderNetwork.objects.all()
|
queryset = ProviderNetwork.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(
|
|
||||||
Circuit.objects.restrict(request.user, 'view').filter(terminations__provider_network=instance),
|
|
||||||
'provider_network_id',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'related_models': related_models,
|
'related_models': self.get_related_models(
|
||||||
|
request,
|
||||||
|
instance,
|
||||||
|
extra=(
|
||||||
|
(
|
||||||
|
Circuit.objects.restrict(request.user, 'view').filter(terminations__provider_network=instance),
|
||||||
|
'provider_network_id',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -215,16 +208,12 @@ class CircuitTypeListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(CircuitType)
|
@register_model_view(CircuitType)
|
||||||
class CircuitTypeView(generic.ObjectView):
|
class CircuitTypeView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = CircuitType.objects.all()
|
queryset = CircuitType.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(Circuit.objects.restrict(request.user, 'view').filter(type=instance), 'type_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
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.forms import ConfirmationForm
|
||||||
from utilities.htmx import htmx_partial
|
from utilities.htmx import htmx_partial
|
||||||
from utilities.query import count_related
|
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 . import filtersets, forms, tables
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
@ -51,16 +51,12 @@ class DataSourceListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(DataSource)
|
@register_model_view(DataSource)
|
||||||
class DataSourceView(generic.ObjectView):
|
class DataSourceView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = DataSource.objects.all()
|
queryset = DataSource.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(DataFile.objects.restrict(request.user, 'view').filter(source=instance), 'source_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
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
|
# Enable filtering rack units by ID
|
||||||
q = data['q']
|
if q := data['q']:
|
||||||
if q:
|
q = q.lower()
|
||||||
elevation = [u for u in elevation if q in str(u['id']) or q in str(u['name'])]
|
elevation = [u for u in elevation if q in str(u['id']) or q in str(u['name']).lower()]
|
||||||
|
|
||||||
page = self.paginate_queryset(elevation)
|
page = self.paginate_queryset(elevation)
|
||||||
if page is not None:
|
if page is not None:
|
||||||
|
@ -17,7 +17,7 @@ from jinja2.exceptions import TemplateError
|
|||||||
|
|
||||||
from circuits.models import Circuit, CircuitTermination
|
from circuits.models import Circuit, CircuitTermination
|
||||||
from extras.views import ObjectConfigContextView
|
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 ipam.tables import InterfaceVLANTable
|
||||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
||||||
from netbox.views import generic
|
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.permissions import get_permission_for_model
|
||||||
from utilities.query import count_related
|
from utilities.query import count_related
|
||||||
from utilities.query_functions import CollateAsChar
|
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.filtersets import VirtualMachineFilterSet
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from virtualization.tables import VirtualMachineTable
|
from virtualization.tables import VirtualMachineTable
|
||||||
@ -226,19 +228,21 @@ class RegionListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(Region)
|
@register_model_view(Region)
|
||||||
class RegionView(generic.ObjectView):
|
class RegionView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = Region.objects.all()
|
queryset = Region.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
regions = instance.get_descendants(include_self=True)
|
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 {
|
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)
|
@register_model_view(SiteGroup)
|
||||||
class SiteGroupView(generic.ObjectView):
|
class SiteGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = SiteGroup.objects.all()
|
queryset = SiteGroup.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
groups = instance.get_descendants(include_self=True)
|
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 {
|
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)
|
@register_model_view(Site)
|
||||||
class SiteView(generic.ObjectView):
|
class SiteView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = Site.objects.prefetch_related('tenant__group')
|
queryset = Site.objects.prefetch_related('tenant__group')
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
# DCIM
|
|
||||||
(Location.objects.restrict(request.user, 'view').filter(site=instance), 'site_id'),
|
|
||||||
(Rack.objects.restrict(request.user, 'view').filter(site=instance), 'site_id'),
|
|
||||||
(Device.objects.restrict(request.user, 'view').filter(site=instance), 'site_id'),
|
|
||||||
# Virtualization
|
|
||||||
(VirtualMachine.objects.restrict(request.user, 'view').filter(cluster__site=instance), 'site_id'),
|
|
||||||
# IPAM
|
|
||||||
(Prefix.objects.restrict(request.user, 'view').filter(site=instance), 'site_id'),
|
|
||||||
(ASN.objects.restrict(request.user, 'view').filter(sites=instance), 'site_id'),
|
|
||||||
(VLANGroup.objects.restrict(request.user, 'view').filter(
|
|
||||||
scope_type=ContentType.objects.get_for_model(Site),
|
|
||||||
scope_id=instance.pk
|
|
||||||
), 'site'),
|
|
||||||
(VLAN.objects.restrict(request.user, 'view').filter(site=instance), 'site_id'),
|
|
||||||
# Circuits
|
|
||||||
(Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).distinct(), 'site_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'related_models': related_models,
|
'related_models': self.get_related_models(
|
||||||
|
request,
|
||||||
|
instance,
|
||||||
|
[CableTermination, CircuitTermination],
|
||||||
|
(
|
||||||
|
(VLANGroup.objects.restrict(request.user, 'view').filter(
|
||||||
|
scope_type=ContentType.objects.get_for_model(Site),
|
||||||
|
scope_id=instance.pk
|
||||||
|
), 'site'),
|
||||||
|
(ASN.objects.restrict(request.user, 'view').filter(sites=instance), 'site_id'),
|
||||||
|
(Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).distinct(),
|
||||||
|
'site_id'),
|
||||||
|
),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -466,18 +466,13 @@ class LocationListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(Location)
|
@register_model_view(Location)
|
||||||
class LocationView(generic.ObjectView):
|
class LocationView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = Location.objects.all()
|
queryset = Location.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
locations = instance.get_descendants(include_self=True)
|
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 {
|
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)
|
@register_model_view(RackRole)
|
||||||
class RackRoleView(generic.ObjectView):
|
class RackRoleView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = RackRole.objects.all()
|
queryset = RackRole.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(Rack.objects.restrict(request.user, 'view').filter(role=instance), 'role_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
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)
|
@register_model_view(Rack)
|
||||||
class RackView(generic.ObjectView):
|
class RackView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'location', 'role')
|
queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'location', 'role')
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
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)
|
peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=instance.site)
|
||||||
|
|
||||||
if instance.location:
|
if instance.location:
|
||||||
@ -679,7 +665,7 @@ class RackView(generic.ObjectView):
|
|||||||
])
|
])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'related_models': related_models,
|
'related_models': self.get_related_models(request, instance, [CableTermination]),
|
||||||
'next_rack': next_rack,
|
'next_rack': next_rack,
|
||||||
'prev_rack': prev_rack,
|
'prev_rack': prev_rack,
|
||||||
'svg_extra': svg_extra,
|
'svg_extra': svg_extra,
|
||||||
@ -838,19 +824,12 @@ class ManufacturerListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(Manufacturer)
|
@register_model_view(Manufacturer)
|
||||||
class ManufacturerView(generic.ObjectView):
|
class ManufacturerView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = Manufacturer.objects.all()
|
queryset = Manufacturer.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
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 {
|
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)
|
@register_model_view(DeviceType)
|
||||||
class DeviceTypeView(generic.ObjectView):
|
class DeviceTypeView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = DeviceType.objects.all()
|
queryset = DeviceType.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(Device.objects.restrict(request.user).filter(device_type=instance), 'device_type_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
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)
|
@register_model_view(ModuleType)
|
||||||
class ModuleTypeView(generic.ObjectView):
|
class ModuleTypeView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = ModuleType.objects.all()
|
queryset = ModuleType.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(Module.objects.restrict(request.user).filter(module_type=instance), 'module_type_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
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)
|
@register_model_view(DeviceRole)
|
||||||
class DeviceRoleView(generic.ObjectView):
|
class DeviceRoleView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = DeviceRole.objects.all()
|
queryset = DeviceRole.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
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 {
|
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)
|
@register_model_view(Platform)
|
||||||
class PlatformView(generic.ObjectView):
|
class PlatformView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = Platform.objects.all()
|
queryset = Platform.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
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 {
|
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)
|
@register_model_view(Module)
|
||||||
class ModuleView(generic.ObjectView):
|
class ModuleView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = Module.objects.all()
|
queryset = Module.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
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 {
|
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)
|
@register_model_view(PowerPanel)
|
||||||
class PowerPanelView(generic.ObjectView):
|
class PowerPanelView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = PowerPanel.objects.all()
|
queryset = PowerPanel.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(PowerFeed.objects.restrict(request.user).filter(power_panel=instance), 'power_panel_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
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)
|
@register_model_view(VirtualDeviceContext)
|
||||||
class VirtualDeviceContextView(generic.ObjectView):
|
class VirtualDeviceContextView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = VirtualDeviceContext.objects.all()
|
queryset = VirtualDeviceContext.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(Interface.objects.restrict(request.user, 'view').filter(vdcs__in=[instance]), 'vdc_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
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_NEWEST = '-created'
|
||||||
ORDERING_OLDEST = 'created'
|
ORDERING_OLDEST = 'created'
|
||||||
|
ORDERING_ALPHABETICAL_AZ = 'name'
|
||||||
|
ORDERING_ALPHABETICAL_ZA = '-name'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(ORDERING_NEWEST, _('Newest')),
|
(ORDERING_NEWEST, _('Newest')),
|
||||||
(ORDERING_OLDEST, _('Oldest')),
|
(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:
|
if request.user.is_anonymous:
|
||||||
bookmarks = list()
|
bookmarks = list()
|
||||||
else:
|
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'):
|
if object_types := self.config.get('object_types'):
|
||||||
models = get_models_from_content_types(object_types)
|
models = get_models_from_content_types(object_types)
|
||||||
conent_types = ObjectType.objects.get_for_models(*models).values()
|
content_types = ObjectType.objects.get_for_models(*models).values()
|
||||||
bookmarks = bookmarks.filter(object_type__in=conent_types)
|
bookmarks = bookmarks.filter(object_type__in=content_types)
|
||||||
if max_items := self.config.get('max_items'):
|
if max_items := self.config.get('max_items'):
|
||||||
bookmarks = bookmarks[:max_items]
|
bookmarks = bookmarks[:max_items]
|
||||||
|
|
||||||
|
@ -660,6 +660,10 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
|||||||
# Validate date & time
|
# Validate date & time
|
||||||
elif self.type == CustomFieldTypeChoices.TYPE_DATETIME:
|
elif self.type == CustomFieldTypeChoices.TYPE_DATETIME:
|
||||||
if type(value) is not 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:
|
try:
|
||||||
datetime.fromisoformat(value)
|
datetime.fromisoformat(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -12,7 +12,7 @@ from netbox.views import generic
|
|||||||
from tenancy.views import ObjectContactsView
|
from tenancy.views import ObjectContactsView
|
||||||
from utilities.query import count_related
|
from utilities.query import count_related
|
||||||
from utilities.tables import get_table_ordering
|
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.filtersets import VMInterfaceFilterSet
|
||||||
from virtualization.models import VMInterface
|
from virtualization.models import VMInterface
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
@ -34,15 +34,10 @@ class VRFListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(VRF)
|
@register_model_view(VRF)
|
||||||
class VRFView(generic.ObjectView):
|
class VRFView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = VRF.objects.all()
|
queryset = VRF.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
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(
|
import_targets_table = tables.RouteTargetTable(
|
||||||
instance.import_targets.all(),
|
instance.import_targets.all(),
|
||||||
orderable=False
|
orderable=False
|
||||||
@ -53,7 +48,7 @@ class VRFView(generic.ObjectView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'related_models': related_models,
|
'related_models': self.get_related_models(request, instance, omit=[Interface, VMInterface]),
|
||||||
'import_targets_table': import_targets_table,
|
'import_targets_table': import_targets_table,
|
||||||
'export_targets_table': export_targets_table,
|
'export_targets_table': export_targets_table,
|
||||||
}
|
}
|
||||||
@ -147,16 +142,12 @@ class RIRListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(RIR)
|
@register_model_view(RIR)
|
||||||
class RIRView(generic.ObjectView):
|
class RIRView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = RIR.objects.all()
|
queryset = RIR.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(Aggregate.objects.restrict(request.user, 'view').filter(rir=instance), 'rir_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
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)
|
@register_model_view(ASN)
|
||||||
class ASNView(generic.ObjectView):
|
class ASNView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = ASN.objects.all()
|
queryset = ASN.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(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 {
|
return {
|
||||||
'related_models': related_models,
|
'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'),
|
||||||
|
),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -427,18 +420,12 @@ class RoleListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(Role)
|
@register_model_view(Role)
|
||||||
class RoleView(generic.ObjectView):
|
class RoleView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = Role.objects.all()
|
queryset = Role.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
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 {
|
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)
|
@register_model_view(VLANGroup)
|
||||||
class VLANGroupView(generic.ObjectView):
|
class VLANGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags')
|
queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags')
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(VLAN.objects.restrict(request.user, 'view').filter(group=instance), 'group_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'related_models': related_models,
|
'related_models': self.get_related_models(request, instance),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import zoneinfo
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
@ -83,6 +84,8 @@ class DateTimeColumn(tables.Column):
|
|||||||
|
|
||||||
def render(self, value):
|
def render(self, value):
|
||||||
if 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)}"
|
return f"{value.date().isoformat()} {value.time().isoformat(timespec=self.timespec)}"
|
||||||
|
|
||||||
def value(self, value):
|
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 { initDashboard } from './dashboard';
|
||||||
import { initRackElevation } from './racks';
|
import { initRackElevation } from './racks';
|
||||||
import { initHtmx } from './htmx';
|
import { initHtmx } from './htmx';
|
||||||
|
import { initSavedFilterSelect } from './forms/savedFiltersSelect';
|
||||||
|
|
||||||
function initDocument(): void {
|
function initDocument(): void {
|
||||||
for (const init of [
|
for (const init of [
|
||||||
@ -31,6 +32,7 @@ function initDocument(): void {
|
|||||||
initDashboard,
|
initDashboard,
|
||||||
initRackElevation,
|
initRackElevation,
|
||||||
initHtmx,
|
initHtmx,
|
||||||
|
initSavedFilterSelect,
|
||||||
]) {
|
]) {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
// Overrides of external libraries
|
// Overrides of external libraries
|
||||||
@import 'overrides/bootstrap';
|
@import 'overrides/bootstrap';
|
||||||
@import 'overrides/tabler';
|
@import 'overrides/tabler';
|
||||||
|
@import 'overrides/tomselect';
|
||||||
|
|
||||||
// Transitional styling to ease migration of templates from NetBox v3.x
|
// Transitional styling to ease migration of templates from NetBox v3.x
|
||||||
@import 'transitional/badges';
|
@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) #}
|
{# User menu (mobile view) #}
|
||||||
<div class="navbar-nav flex-row d-lg-none">
|
<div class="navbar-nav flex-row d-lg-none">
|
||||||
|
{% include 'inc/light_toggle.html' %}
|
||||||
{% include 'inc/user_menu.html' %}
|
{% include 'inc/user_menu.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -52,14 +53,7 @@ Blocks:
|
|||||||
|
|
||||||
<div class="navbar-nav flex-row align-items-center order-md-last">
|
<div class="navbar-nav flex-row align-items-center order-md-last">
|
||||||
{# Dark/light mode toggle #}
|
{# Dark/light mode toggle #}
|
||||||
<div class="d-none d-md-flex">
|
{% include 'inc/light_toggle.html' %}
|
||||||
<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>
|
|
||||||
|
|
||||||
{# User menu #}
|
{# User menu #}
|
||||||
{% include 'inc/user_menu.html' %}
|
{% include 'inc/user_menu.html' %}
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Rack" %}</th>
|
<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 %}
|
{% if object.rack %}
|
||||||
{{ object.rack|linkify }}
|
{{ 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" %}">
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Physical Address" %}</th>
|
<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 %}
|
{% if object.physical_address %}
|
||||||
<span>{{ object.physical_address|linebreaksbr }}</span>
|
<span>{{ object.physical_address|linebreaksbr }}</span>
|
||||||
{% if config.MAPS_URL %}
|
{% 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,25 +1,37 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3" id="results">
|
||||||
<div class="col-auto d-print-none">
|
<div class="col-auto d-print-none">
|
||||||
<div class="input-group input-group-flat me-2 quicksearch" hx-disinherit="hx-select hx-swap">
|
<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"
|
<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" />
|
hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search"/>
|
||||||
<span class="input-group-text py-1">
|
<span class="input-group-text py-1">
|
||||||
<a href="#" id="quicksearch_clear" class="invisible text-secondary"><i class="mdi mdi-close-circle"></i></a>
|
<a href="#" id="quicksearch_clear" class="invisible text-secondary"><i class="mdi mdi-close-circle"></i></a>
|
||||||
</span>
|
</span>
|
||||||
{% block extra_table_controls %}{% endblock %}
|
{% block extra_table_controls %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="col-auto ms-auto d-print-none">
|
||||||
{% if request.user.is_authenticated and table_modal %}
|
{% if request.user.is_authenticated and table_modal %}
|
||||||
<div class="table-configure input-group">
|
<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" %}"
|
||||||
class="btn">
|
data-bs-target="#{{ table_modal }}"
|
||||||
|
class="btn">
|
||||||
<i class="mdi mdi-cog"></i> {% trans "Configure Table" %}
|
<i class="mdi mdi-cog"></i> {% trans "Configure Table" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -4,8 +4,7 @@ from django.utils.translation import gettext as _
|
|||||||
|
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.query import count_related
|
from utilities.query import count_related
|
||||||
from utilities.relations import get_related_models
|
from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view
|
||||||
from utilities.views import register_model_view, ViewTab
|
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
@ -56,17 +55,14 @@ class TenantGroupListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(TenantGroup)
|
@register_model_view(TenantGroup)
|
||||||
class TenantGroupView(generic.ObjectView):
|
class TenantGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = TenantGroup.objects.all()
|
queryset = TenantGroup.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
groups = instance.get_descendants(include_self=True)
|
groups = instance.get_descendants(include_self=True)
|
||||||
related_models = (
|
|
||||||
(Tenant.objects.restrict(request.user, 'view').filter(group__in=groups), 'group_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
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)
|
@register_model_view(Tenant)
|
||||||
class TenantView(generic.ObjectView):
|
class TenantView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = Tenant.objects.all()
|
queryset = Tenant.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
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 {
|
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)
|
@register_model_view(ContactGroup)
|
||||||
class ContactGroupView(generic.ObjectView):
|
class ContactGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = ContactGroup.objects.all()
|
queryset = ContactGroup.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
groups = instance.get_descendants(include_self=True)
|
groups = instance.get_descendants(include_self=True)
|
||||||
related_models = (
|
|
||||||
(Contact.objects.restrict(request.user, 'view').filter(group__in=groups), 'group_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
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)
|
@register_model_view(ContactRole)
|
||||||
class ContactRoleView(generic.ObjectView):
|
class ContactRoleView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = ContactRole.objects.all()
|
queryset = ContactRole.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(ContactAssignment.objects.restrict(request.user, 'view').filter(role=instance), 'role_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'related_models': related_models,
|
'related_models': self.get_related_models(request, instance),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \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"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -29,7 +29,7 @@ msgid "Write Enabled"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/account/tables.py:35 netbox/core/tables/jobs.py:29
|
#: 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/extras/tables/tables.py:499 netbox/templates/account/token.html:43
|
||||||
#: netbox/templates/core/configrevision.html:26
|
#: netbox/templates/core/configrevision.html:26
|
||||||
#: netbox/templates/core/configrevision_restore.html:12
|
#: netbox/templates/core/configrevision_restore.html:12
|
||||||
@ -58,7 +58,7 @@ msgstr ""
|
|||||||
msgid "Allowed IPs"
|
msgid "Allowed IPs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/account/views.py:197
|
#: netbox/account/views.py:204
|
||||||
msgid "Your preferences have been updated."
|
msgid "Your preferences have been updated."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ msgstr ""
|
|||||||
#: netbox/circuits/forms/filtersets.py:207
|
#: netbox/circuits/forms/filtersets.py:207
|
||||||
#: netbox/circuits/forms/model_forms.py:136
|
#: netbox/circuits/forms/model_forms.py:136
|
||||||
#: netbox/circuits/forms/model_forms.py:152
|
#: 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: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_edit.py:771 netbox/dcim/forms/bulk_import.py:130
|
||||||
#: netbox/dcim/forms/bulk_import.py:184 netbox/dcim/forms/bulk_import.py:257
|
#: 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/filtersets.py:212
|
||||||
#: netbox/circuits/forms/model_forms.py:109
|
#: netbox/circuits/forms/model_forms.py:109
|
||||||
#: netbox/circuits/forms/model_forms.py:131
|
#: 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/circuit.html:15
|
||||||
#: netbox/templates/circuits/circuittermination.html:19
|
#: netbox/templates/circuits/circuittermination.html:19
|
||||||
#: netbox/templates/dcim/inc/cable_termination.html:55
|
#: 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/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/forms/filtersets.py:188 netbox/dcim/forms/model_forms.py:122
|
||||||
#: netbox/dcim/tables/sites.py:94 netbox/ipam/models/asns.py:126
|
#: 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/netbox/navigation/menu.py:159 netbox/netbox/navigation/menu.py:162
|
||||||
#: netbox/templates/circuits/provider.html:23
|
#: netbox/templates/circuits/provider.html:23
|
||||||
msgid "ASNs"
|
msgid "ASNs"
|
||||||
@ -469,7 +469,7 @@ msgstr ""
|
|||||||
#: netbox/circuits/forms/model_forms.py:45
|
#: netbox/circuits/forms/model_forms.py:45
|
||||||
#: netbox/circuits/forms/model_forms.py:59
|
#: netbox/circuits/forms/model_forms.py:59
|
||||||
#: netbox/circuits/forms/model_forms.py:91
|
#: 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:72
|
||||||
#: netbox/circuits/tables/providers.py:103
|
#: netbox/circuits/tables/providers.py:103
|
||||||
#: netbox/templates/circuits/circuit.html:18
|
#: netbox/templates/circuits/circuit.html:18
|
||||||
@ -748,7 +748,7 @@ msgstr ""
|
|||||||
#: netbox/circuits/forms/bulk_edit.py:191
|
#: netbox/circuits/forms/bulk_edit.py:191
|
||||||
#: netbox/circuits/forms/bulk_edit.py:215
|
#: netbox/circuits/forms/bulk_edit.py:215
|
||||||
#: netbox/circuits/forms/model_forms.py:153
|
#: 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/inc/circuit_termination_fields.html:62
|
||||||
#: netbox/templates/circuits/providernetwork.html:17
|
#: netbox/templates/circuits/providernetwork.html:17
|
||||||
msgid "Provider Network"
|
msgid "Provider Network"
|
||||||
@ -895,7 +895,7 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/filtersets.py:653 netbox/dcim/forms/filtersets.py:1010
|
#: 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/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/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:37
|
||||||
#: netbox/virtualization/forms/filtersets.py:48
|
#: netbox/virtualization/forms/filtersets.py:48
|
||||||
#: netbox/virtualization/forms/filtersets.py:106
|
#: netbox/virtualization/forms/filtersets.py:106
|
||||||
@ -1328,21 +1328,21 @@ msgstr ""
|
|||||||
msgid "Circuit ID"
|
msgid "Circuit ID"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/circuits/tables/circuits.py:66
|
#: netbox/circuits/tables/circuits.py:67
|
||||||
#: netbox/wireless/forms/model_forms.py:160
|
#: netbox/wireless/forms/model_forms.py:160
|
||||||
msgid "Side A"
|
msgid "Side A"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/circuits/tables/circuits.py:70
|
#: netbox/circuits/tables/circuits.py:72
|
||||||
msgid "Side Z"
|
msgid "Side Z"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/circuits/tables/circuits.py:73
|
#: netbox/circuits/tables/circuits.py:75
|
||||||
#: netbox/templates/circuits/circuit.html:55
|
#: netbox/templates/circuits/circuit.html:55
|
||||||
msgid "Commit Rate"
|
msgid "Commit Rate"
|
||||||
msgstr ""
|
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:82
|
||||||
#: netbox/circuits/tables/providers.py:107 netbox/dcim/tables/devices.py:1001
|
#: netbox/circuits/tables/providers.py:107 netbox/dcim/tables/devices.py:1001
|
||||||
#: netbox/dcim/tables/devicetypes.py:92 netbox/dcim/tables/modules.py:29
|
#: netbox/dcim/tables/devicetypes.py:92 netbox/dcim/tables/modules.py:29
|
||||||
@ -1400,7 +1400,7 @@ msgid "Syncing"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/choices.py:21 netbox/core/choices.py:57
|
#: 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
|
#: netbox/templates/core/job.html:68
|
||||||
msgid "Completed"
|
msgid "Completed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -1408,7 +1408,7 @@ msgstr ""
|
|||||||
#: netbox/core/choices.py:22 netbox/core/choices.py:59
|
#: netbox/core/choices.py:22 netbox/core/choices.py:59
|
||||||
#: netbox/core/constants.py:20 netbox/core/tables/tasks.py:34
|
#: 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: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
|
#: netbox/virtualization/choices.py:47
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -1426,21 +1426,21 @@ msgstr ""
|
|||||||
msgid "Reports"
|
msgid "Reports"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/choices.py:54 netbox/extras/choices.py:221
|
#: netbox/core/choices.py:54 netbox/extras/choices.py:225
|
||||||
msgid "Pending"
|
msgid "Pending"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/choices.py:55 netbox/core/constants.py:23
|
#: netbox/core/choices.py:55 netbox/core/constants.py:23
|
||||||
#: netbox/core/tables/jobs.py:32 netbox/core/tables/tasks.py:38
|
#: 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"
|
msgid "Scheduled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/choices.py:56 netbox/extras/choices.py:223
|
#: netbox/core/choices.py:56 netbox/extras/choices.py:227
|
||||||
msgid "Running"
|
msgid "Running"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/choices.py:58 netbox/extras/choices.py:225
|
#: netbox/core/choices.py:58 netbox/extras/choices.py:229
|
||||||
msgid "Errored"
|
msgid "Errored"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -2067,8 +2067,8 @@ msgstr ""
|
|||||||
msgid "No workers found"
|
msgid "No workers found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/views.py:335 netbox/core/views.py:378 netbox/core/views.py:401
|
#: netbox/core/views.py:331 netbox/core/views.py:374 netbox/core/views.py:397
|
||||||
#: netbox/core/views.py:419 netbox/core/views.py:454
|
#: netbox/core/views.py:415 netbox/core/views.py:450
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Job {job_id} not found"
|
msgid "Job {job_id} not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2946,7 +2946,7 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/bulk_create.py:40 netbox/extras/forms/filtersets.py:410
|
#: 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:443
|
||||||
#: netbox/extras/forms/model_forms.py:495 netbox/netbox/forms/base.py:84
|
#: 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/circuits/inc/circuit_termination.html:32
|
||||||
#: netbox/templates/generic/bulk_edit.html:65
|
#: netbox/templates/generic/bulk_edit.html:65
|
||||||
#: netbox/templates/inc/panels/tags.html:5
|
#: 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/netbox/navigation/menu.py:60 netbox/netbox/navigation/menu.py:62
|
||||||
#: netbox/virtualization/forms/model_forms.py:122
|
#: netbox/virtualization/forms/model_forms.py:122
|
||||||
#: netbox/virtualization/tables/clusters.py:83
|
#: netbox/virtualization/tables/clusters.py:83
|
||||||
#: netbox/virtualization/views.py:210
|
#: netbox/virtualization/views.py:202
|
||||||
msgid "Devices"
|
msgid "Devices"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -6054,8 +6054,8 @@ msgid "Power outlets"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devices.py:243 netbox/dcim/tables/devices.py:1046
|
#: 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/tables/devicetypes.py:125 netbox/dcim/views.py:985
|
||||||
#: netbox/dcim/views.py:1245 netbox/dcim/views.py:1931
|
#: netbox/dcim/views.py:1224 netbox/dcim/views.py:1900
|
||||||
#: netbox/netbox/navigation/menu.py:81 netbox/netbox/navigation/menu.py:237
|
#: netbox/netbox/navigation/menu.py:81 netbox/netbox/navigation/menu.py:237
|
||||||
#: netbox/templates/dcim/device/base.html:37
|
#: netbox/templates/dcim/device/base.html:37
|
||||||
#: netbox/templates/dcim/device_list.html:43
|
#: netbox/templates/dcim/device_list.html:43
|
||||||
@ -6067,7 +6067,7 @@ msgstr ""
|
|||||||
#: netbox/templates/virtualization/virtualmachine/base.html:27
|
#: netbox/templates/virtualization/virtualmachine/base.html:27
|
||||||
#: netbox/templates/virtualization/virtualmachine_list.html:14
|
#: netbox/templates/virtualization/virtualmachine_list.html:14
|
||||||
#: netbox/virtualization/tables/virtualmachines.py:100
|
#: 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"
|
msgid "Interfaces"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -6093,8 +6093,8 @@ msgid "Module Bay"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devices.py:310 netbox/dcim/tables/devicetypes.py:48
|
#: 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/tables/devicetypes.py:140 netbox/dcim/views.py:1060
|
||||||
#: netbox/dcim/views.py:2024 netbox/netbox/navigation/menu.py:90
|
#: netbox/dcim/views.py:1993 netbox/netbox/navigation/menu.py:90
|
||||||
#: netbox/templates/dcim/device/base.html:52
|
#: netbox/templates/dcim/device/base.html:52
|
||||||
#: netbox/templates/dcim/device_list.html:71
|
#: netbox/templates/dcim/device_list.html:71
|
||||||
#: netbox/templates/dcim/devicetype/base.html:49
|
#: netbox/templates/dcim/devicetype/base.html:49
|
||||||
@ -6124,8 +6124,8 @@ msgid "Allocated draw (W)"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devices.py:546 netbox/ipam/forms/model_forms.py:747
|
#: 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/tables/fhrp.py:28 netbox/ipam/views.py:589
|
||||||
#: netbox/ipam/views.py:701 netbox/netbox/navigation/menu.py:145
|
#: netbox/ipam/views.py:688 netbox/netbox/navigation/menu.py:145
|
||||||
#: netbox/netbox/navigation/menu.py:147
|
#: netbox/netbox/navigation/menu.py:147
|
||||||
#: netbox/templates/dcim/interface.html:339
|
#: netbox/templates/dcim/interface.html:339
|
||||||
#: netbox/templates/ipam/ipaddress_bulk_add.html:15
|
#: netbox/templates/ipam/ipaddress_bulk_add.html:15
|
||||||
@ -6218,8 +6218,8 @@ msgstr ""
|
|||||||
msgid "Instances"
|
msgid "Instances"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:113 netbox/dcim/views.py:946
|
#: netbox/dcim/tables/devicetypes.py:113 netbox/dcim/views.py:925
|
||||||
#: netbox/dcim/views.py:1185 netbox/dcim/views.py:1871
|
#: netbox/dcim/views.py:1164 netbox/dcim/views.py:1840
|
||||||
#: netbox/netbox/navigation/menu.py:84
|
#: netbox/netbox/navigation/menu.py:84
|
||||||
#: netbox/templates/dcim/device/base.html:25
|
#: netbox/templates/dcim/device/base.html:25
|
||||||
#: netbox/templates/dcim/device_list.html:15
|
#: netbox/templates/dcim/device_list.html:15
|
||||||
@ -6229,8 +6229,8 @@ msgstr ""
|
|||||||
msgid "Console Ports"
|
msgid "Console Ports"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:116 netbox/dcim/views.py:961
|
#: netbox/dcim/tables/devicetypes.py:116 netbox/dcim/views.py:940
|
||||||
#: netbox/dcim/views.py:1200 netbox/dcim/views.py:1886
|
#: netbox/dcim/views.py:1179 netbox/dcim/views.py:1855
|
||||||
#: netbox/netbox/navigation/menu.py:85
|
#: netbox/netbox/navigation/menu.py:85
|
||||||
#: netbox/templates/dcim/device/base.html:28
|
#: netbox/templates/dcim/device/base.html:28
|
||||||
#: netbox/templates/dcim/device_list.html:22
|
#: netbox/templates/dcim/device_list.html:22
|
||||||
@ -6240,8 +6240,8 @@ msgstr ""
|
|||||||
msgid "Console Server Ports"
|
msgid "Console Server Ports"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:119 netbox/dcim/views.py:976
|
#: netbox/dcim/tables/devicetypes.py:119 netbox/dcim/views.py:955
|
||||||
#: netbox/dcim/views.py:1215 netbox/dcim/views.py:1901
|
#: netbox/dcim/views.py:1194 netbox/dcim/views.py:1870
|
||||||
#: netbox/netbox/navigation/menu.py:86
|
#: netbox/netbox/navigation/menu.py:86
|
||||||
#: netbox/templates/dcim/device/base.html:31
|
#: netbox/templates/dcim/device/base.html:31
|
||||||
#: netbox/templates/dcim/device_list.html:29
|
#: netbox/templates/dcim/device_list.html:29
|
||||||
@ -6251,8 +6251,8 @@ msgstr ""
|
|||||||
msgid "Power Ports"
|
msgid "Power Ports"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:122 netbox/dcim/views.py:991
|
#: netbox/dcim/tables/devicetypes.py:122 netbox/dcim/views.py:970
|
||||||
#: netbox/dcim/views.py:1230 netbox/dcim/views.py:1916
|
#: netbox/dcim/views.py:1209 netbox/dcim/views.py:1885
|
||||||
#: netbox/netbox/navigation/menu.py:87
|
#: netbox/netbox/navigation/menu.py:87
|
||||||
#: netbox/templates/dcim/device/base.html:34
|
#: netbox/templates/dcim/device/base.html:34
|
||||||
#: netbox/templates/dcim/device_list.html:36
|
#: netbox/templates/dcim/device_list.html:36
|
||||||
@ -6262,8 +6262,8 @@ msgstr ""
|
|||||||
msgid "Power Outlets"
|
msgid "Power Outlets"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:128 netbox/dcim/views.py:1021
|
#: netbox/dcim/tables/devicetypes.py:128 netbox/dcim/views.py:1000
|
||||||
#: netbox/dcim/views.py:1260 netbox/dcim/views.py:1952
|
#: netbox/dcim/views.py:1239 netbox/dcim/views.py:1921
|
||||||
#: netbox/netbox/navigation/menu.py:82
|
#: netbox/netbox/navigation/menu.py:82
|
||||||
#: netbox/templates/dcim/device/base.html:40
|
#: netbox/templates/dcim/device/base.html:40
|
||||||
#: netbox/templates/dcim/devicetype/base.html:37
|
#: netbox/templates/dcim/devicetype/base.html:37
|
||||||
@ -6272,8 +6272,8 @@ msgstr ""
|
|||||||
msgid "Front Ports"
|
msgid "Front Ports"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:131 netbox/dcim/views.py:1036
|
#: netbox/dcim/tables/devicetypes.py:131 netbox/dcim/views.py:1015
|
||||||
#: netbox/dcim/views.py:1275 netbox/dcim/views.py:1967
|
#: netbox/dcim/views.py:1254 netbox/dcim/views.py:1936
|
||||||
#: netbox/netbox/navigation/menu.py:83
|
#: netbox/netbox/navigation/menu.py:83
|
||||||
#: netbox/templates/dcim/device/base.html:43
|
#: netbox/templates/dcim/device/base.html:43
|
||||||
#: netbox/templates/dcim/device_list.html:50
|
#: netbox/templates/dcim/device_list.html:50
|
||||||
@ -6283,16 +6283,16 @@ msgstr ""
|
|||||||
msgid "Rear Ports"
|
msgid "Rear Ports"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:134 netbox/dcim/views.py:1066
|
#: netbox/dcim/tables/devicetypes.py:134 netbox/dcim/views.py:1045
|
||||||
#: netbox/dcim/views.py:2005 netbox/netbox/navigation/menu.py:89
|
#: netbox/dcim/views.py:1974 netbox/netbox/navigation/menu.py:89
|
||||||
#: netbox/templates/dcim/device/base.html:49
|
#: netbox/templates/dcim/device/base.html:49
|
||||||
#: netbox/templates/dcim/device_list.html:57
|
#: netbox/templates/dcim/device_list.html:57
|
||||||
#: netbox/templates/dcim/devicetype/base.html:46
|
#: netbox/templates/dcim/devicetype/base.html:46
|
||||||
msgid "Device Bays"
|
msgid "Device Bays"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:137 netbox/dcim/views.py:1051
|
#: netbox/dcim/tables/devicetypes.py:137 netbox/dcim/views.py:1030
|
||||||
#: netbox/dcim/views.py:1986 netbox/netbox/navigation/menu.py:88
|
#: netbox/dcim/views.py:1955 netbox/netbox/navigation/menu.py:88
|
||||||
#: netbox/templates/dcim/device/base.html:46
|
#: netbox/templates/dcim/device/base.html:46
|
||||||
#: netbox/templates/dcim/device_list.html:64
|
#: netbox/templates/dcim/device_list.html:64
|
||||||
#: netbox/templates/dcim/devicetype/base.html:43
|
#: netbox/templates/dcim/devicetype/base.html:43
|
||||||
@ -6350,38 +6350,38 @@ msgstr ""
|
|||||||
msgid "Test case must set peer_termination_type"
|
msgid "Test case must set peer_termination_type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/views.py:137
|
#: netbox/dcim/views.py:139
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Disconnected {count} {type}"
|
msgid "Disconnected {count} {type}"
|
||||||
msgstr ""
|
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"
|
msgid "Reservations"
|
||||||
msgstr ""
|
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
|
#: netbox/templates/dcim/site.html:140
|
||||||
msgid "Non-Racked Devices"
|
msgid "Non-Racked Devices"
|
||||||
msgstr ""
|
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/templates/extras/configcontext.html:10
|
||||||
#: netbox/virtualization/forms/model_forms.py:225
|
#: netbox/virtualization/forms/model_forms.py:225
|
||||||
#: netbox/virtualization/views.py:407
|
#: netbox/virtualization/views.py:399
|
||||||
msgid "Config Context"
|
msgid "Config Context"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/views.py:2047 netbox/virtualization/views.py:417
|
#: netbox/dcim/views.py:2016 netbox/virtualization/views.py:409
|
||||||
msgid "Render Config"
|
msgid "Render Config"
|
||||||
msgstr ""
|
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/netbox/navigation/menu.py:234 netbox/netbox/navigation/menu.py:236
|
||||||
#: netbox/virtualization/views.py:185
|
#: netbox/virtualization/views.py:177
|
||||||
msgid "Virtual Machines"
|
msgid "Virtual Machines"
|
||||||
msgstr ""
|
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"
|
msgid "Children"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -6483,71 +6483,79 @@ msgstr ""
|
|||||||
msgid "Link"
|
msgid "Link"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:122
|
#: netbox/extras/choices.py:124
|
||||||
msgid "Newest"
|
msgid "Newest"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:123
|
#: netbox/extras/choices.py:125
|
||||||
msgid "Oldest"
|
msgid "Oldest"
|
||||||
msgstr ""
|
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"
|
msgid "Updated"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:140
|
#: netbox/extras/choices.py:144
|
||||||
msgid "Deleted"
|
msgid "Deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:157 netbox/extras/choices.py:181
|
#: netbox/extras/choices.py:161 netbox/extras/choices.py:185
|
||||||
msgid "Info"
|
msgid "Info"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:158 netbox/extras/choices.py:180
|
#: netbox/extras/choices.py:162 netbox/extras/choices.py:184
|
||||||
msgid "Success"
|
msgid "Success"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:159 netbox/extras/choices.py:182
|
#: netbox/extras/choices.py:163 netbox/extras/choices.py:186
|
||||||
msgid "Warning"
|
msgid "Warning"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:160
|
#: netbox/extras/choices.py:164
|
||||||
msgid "Danger"
|
msgid "Danger"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:178
|
#: netbox/extras/choices.py:182
|
||||||
msgid "Debug"
|
msgid "Debug"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:179 netbox/netbox/choices.py:104
|
#: netbox/extras/choices.py:183 netbox/netbox/choices.py:104
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:183
|
#: netbox/extras/choices.py:187
|
||||||
msgid "Failure"
|
msgid "Failure"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:199
|
#: netbox/extras/choices.py:203
|
||||||
msgid "Hourly"
|
msgid "Hourly"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:200
|
#: netbox/extras/choices.py:204
|
||||||
msgid "12 hours"
|
msgid "12 hours"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:201
|
#: netbox/extras/choices.py:205
|
||||||
msgid "Daily"
|
msgid "Daily"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:202
|
#: netbox/extras/choices.py:206
|
||||||
msgid "Weekly"
|
msgid "Weekly"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:203
|
#: netbox/extras/choices.py:207
|
||||||
msgid "30 days"
|
msgid "30 days"
|
||||||
msgstr ""
|
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/dcim/virtualchassis_edit.html:107
|
||||||
#: netbox/templates/extras/eventrule.html:40
|
#: netbox/templates/extras/eventrule.html:40
|
||||||
#: netbox/templates/generic/bulk_add_component.html:68
|
#: netbox/templates/generic/bulk_add_component.html:68
|
||||||
@ -6557,12 +6565,12 @@ msgstr ""
|
|||||||
msgid "Create"
|
msgid "Create"
|
||||||
msgstr ""
|
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
|
#: netbox/templates/extras/eventrule.html:44
|
||||||
msgid "Update"
|
msgid "Update"
|
||||||
msgstr ""
|
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/circuits/inc/circuit_termination.html:23
|
||||||
#: netbox/templates/dcim/inc/panels/inventory_items.html:37
|
#: netbox/templates/dcim/inc/panels/inventory_items.html:37
|
||||||
#: netbox/templates/dcim/moduletype/component_templates.html:23
|
#: netbox/templates/dcim/moduletype/component_templates.html:23
|
||||||
@ -6579,77 +6587,77 @@ msgstr ""
|
|||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr ""
|
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
|
#: netbox/netbox/choices.py:105
|
||||||
msgid "Blue"
|
msgid "Blue"
|
||||||
msgstr ""
|
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
|
#: netbox/netbox/choices.py:106
|
||||||
msgid "Indigo"
|
msgid "Indigo"
|
||||||
msgstr ""
|
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
|
#: netbox/netbox/choices.py:107
|
||||||
msgid "Purple"
|
msgid "Purple"
|
||||||
msgstr ""
|
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
|
#: netbox/netbox/choices.py:108
|
||||||
msgid "Pink"
|
msgid "Pink"
|
||||||
msgstr ""
|
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
|
#: netbox/netbox/choices.py:109
|
||||||
msgid "Red"
|
msgid "Red"
|
||||||
msgstr ""
|
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
|
#: netbox/netbox/choices.py:110
|
||||||
msgid "Orange"
|
msgid "Orange"
|
||||||
msgstr ""
|
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
|
#: netbox/netbox/choices.py:111
|
||||||
msgid "Yellow"
|
msgid "Yellow"
|
||||||
msgstr ""
|
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
|
#: netbox/netbox/choices.py:112
|
||||||
msgid "Green"
|
msgid "Green"
|
||||||
msgstr ""
|
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
|
#: netbox/netbox/choices.py:113
|
||||||
msgid "Teal"
|
msgid "Teal"
|
||||||
msgstr ""
|
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
|
#: netbox/netbox/choices.py:114
|
||||||
msgid "Cyan"
|
msgid "Cyan"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/choices.py:304 netbox/netbox/choices.py:115
|
#: netbox/extras/choices.py:308 netbox/netbox/choices.py:115
|
||||||
msgid "Gray"
|
msgid "Gray"
|
||||||
msgstr ""
|
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
|
#: netbox/netbox/choices.py:116
|
||||||
msgid "Black"
|
msgid "Black"
|
||||||
msgstr ""
|
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
|
#: netbox/netbox/choices.py:117
|
||||||
msgid "White"
|
msgid "White"
|
||||||
msgstr ""
|
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/extras/forms/model_forms.py:324
|
||||||
#: netbox/templates/extras/webhook.html:10
|
#: netbox/templates/extras/webhook.html:10
|
||||||
msgid "Webhook"
|
msgid "Webhook"
|
||||||
msgstr ""
|
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
|
#: netbox/templates/extras/script/base.html:29
|
||||||
msgid "Script"
|
msgid "Script"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -7678,56 +7686,56 @@ msgstr ""
|
|||||||
msgid "Date values must be in ISO 8601 format (YYYY-MM-DD)."
|
msgid "Date values must be in ISO 8601 format (YYYY-MM-DD)."
|
||||||
msgstr ""
|
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)."
|
msgid "Date and time values must be in ISO 8601 format (YYYY-MM-DD HH:MM:SS)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/models/customfields.py:674
|
#: netbox/extras/models/customfields.py:678
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid choice ({value}) for choice set {choiceset}."
|
msgid "Invalid choice ({value}) for choice set {choiceset}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/models/customfields.py:684
|
#: netbox/extras/models/customfields.py:688
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid choice(s) ({value}) for choice set {choiceset}."
|
msgid "Invalid choice(s) ({value}) for choice set {choiceset}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/models/customfields.py:693
|
#: netbox/extras/models/customfields.py:697
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Value must be an object ID, not {type}"
|
msgid "Value must be an object ID, not {type}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/models/customfields.py:699
|
#: netbox/extras/models/customfields.py:703
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Value must be a list of object IDs, not {type}"
|
msgid "Value must be a list of object IDs, not {type}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/models/customfields.py:703
|
#: netbox/extras/models/customfields.py:707
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Found invalid object ID: {id}"
|
msgid "Found invalid object ID: {id}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/models/customfields.py:706
|
#: netbox/extras/models/customfields.py:710
|
||||||
msgid "Required field cannot be empty."
|
msgid "Required field cannot be empty."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/models/customfields.py:725
|
#: netbox/extras/models/customfields.py:729
|
||||||
msgid "Base set of predefined choices (optional)"
|
msgid "Base set of predefined choices (optional)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/models/customfields.py:737
|
#: netbox/extras/models/customfields.py:741
|
||||||
msgid "Choices are automatically ordered alphabetically"
|
msgid "Choices are automatically ordered alphabetically"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/models/customfields.py:744
|
#: netbox/extras/models/customfields.py:748
|
||||||
msgid "custom field choice set"
|
msgid "custom field choice set"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/models/customfields.py:745
|
#: netbox/extras/models/customfields.py:749
|
||||||
msgid "custom field choice sets"
|
msgid "custom field choice sets"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/extras/models/customfields.py:781
|
#: netbox/extras/models/customfields.py:785
|
||||||
msgid "Must define base or extra choices."
|
msgid "Must define base or extra choices."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -9407,7 +9415,7 @@ msgid "The primary function of this VLAN"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/ipam/models/vlans.py:215 netbox/ipam/tables/ip.py:175
|
#: 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
|
#: netbox/netbox/navigation/menu.py:180 netbox/netbox/navigation/menu.py:182
|
||||||
msgid "VLANs"
|
msgid "VLANs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -9479,7 +9487,7 @@ msgid "Added"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/ipam/tables/ip.py:127 netbox/ipam/tables/ip.py:165
|
#: 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/netbox/navigation/menu.py:152 netbox/netbox/navigation/menu.py:154
|
||||||
#: netbox/templates/ipam/vlan.html:84
|
#: netbox/templates/ipam/vlan.html:84
|
||||||
msgid "Prefixes"
|
msgid "Prefixes"
|
||||||
@ -9580,23 +9588,23 @@ msgid ""
|
|||||||
"are allowed in DNS names"
|
"are allowed in DNS names"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/ipam/views.py:541
|
#: netbox/ipam/views.py:528
|
||||||
msgid "Child Prefixes"
|
msgid "Child Prefixes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/ipam/views.py:576
|
#: netbox/ipam/views.py:563
|
||||||
msgid "Child Ranges"
|
msgid "Child Ranges"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/ipam/views.py:902
|
#: netbox/ipam/views.py:889
|
||||||
msgid "Related IPs"
|
msgid "Related IPs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/ipam/views.py:1133
|
#: netbox/ipam/views.py:1116
|
||||||
msgid "Device Interfaces"
|
msgid "Device Interfaces"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/ipam/views.py:1150
|
#: netbox/ipam/views.py:1133
|
||||||
msgid "VM Interfaces"
|
msgid "VM Interfaces"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -10151,7 +10159,7 @@ msgstr ""
|
|||||||
#: netbox/templates/virtualization/virtualmachine/base.html:32
|
#: netbox/templates/virtualization/virtualmachine/base.html:32
|
||||||
#: netbox/templates/virtualization/virtualmachine_list.html:21
|
#: netbox/templates/virtualization/virtualmachine_list.html:21
|
||||||
#: netbox/virtualization/tables/virtualmachines.py:103
|
#: netbox/virtualization/tables/virtualmachines.py:103
|
||||||
#: netbox/virtualization/views.py:388
|
#: netbox/virtualization/views.py:380
|
||||||
msgid "Virtual Disks"
|
msgid "Virtual Disks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -10490,15 +10498,15 @@ msgstr ""
|
|||||||
msgid "Chinese"
|
msgid "Chinese"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/netbox/tables/columns.py:185
|
#: netbox/netbox/tables/columns.py:188
|
||||||
msgid "Toggle all"
|
msgid "Toggle all"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/netbox/tables/columns.py:287
|
#: netbox/netbox/tables/columns.py:290
|
||||||
msgid "Toggle Dropdown"
|
msgid "Toggle Dropdown"
|
||||||
msgstr ""
|
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"
|
msgid "Error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -10782,36 +10790,28 @@ msgstr ""
|
|||||||
msgid "NetBox Logo"
|
msgid "NetBox Logo"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/templates/base/layout.html:56
|
#: netbox/templates/base/layout.html:139
|
||||||
msgid "Enable dark mode"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: netbox/templates/base/layout.html:59
|
|
||||||
msgid "Enable light mode"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: netbox/templates/base/layout.html:145
|
|
||||||
msgid "Docs"
|
msgid "Docs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/templates/base/layout.html:151
|
#: netbox/templates/base/layout.html:145
|
||||||
#: netbox/templates/rest_framework/api.html:10
|
#: netbox/templates/rest_framework/api.html:10
|
||||||
msgid "REST API"
|
msgid "REST API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/templates/base/layout.html:157
|
#: netbox/templates/base/layout.html:151
|
||||||
msgid "REST API documentation"
|
msgid "REST API documentation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/templates/base/layout.html:164
|
#: netbox/templates/base/layout.html:158
|
||||||
msgid "GraphQL API"
|
msgid "GraphQL API"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/templates/base/layout.html:171
|
#: netbox/templates/base/layout.html:165
|
||||||
msgid "Source Code"
|
msgid "Source Code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/templates/base/layout.html:177
|
#: netbox/templates/base/layout.html:171
|
||||||
msgid "Community"
|
msgid "Community"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -11104,8 +11104,8 @@ msgstr ""
|
|||||||
#: netbox/templates/core/rq_worker_list.html:45
|
#: netbox/templates/core/rq_worker_list.html:45
|
||||||
#: netbox/templates/extras/script_result.html:49
|
#: netbox/templates/extras/script_result.html:49
|
||||||
#: netbox/templates/extras/script_result.html:51
|
#: netbox/templates/extras/script_result.html:51
|
||||||
#: netbox/templates/inc/table_controls_htmx.html:18
|
#: netbox/templates/inc/table_controls_htmx.html:28
|
||||||
#: netbox/templates/inc/table_controls_htmx.html:20
|
#: netbox/templates/inc/table_controls_htmx.html:31
|
||||||
msgid "Configure Table"
|
msgid "Configure Table"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -12681,6 +12681,14 @@ msgstr ""
|
|||||||
msgid "Reset"
|
msgid "Reset"
|
||||||
msgstr ""
|
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
|
#: netbox/templates/inc/missing_prerequisites.html:8
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -12721,6 +12729,14 @@ msgstr ""
|
|||||||
msgid "Data is out of sync with upstream file"
|
msgid "Data is out of sync with upstream file"
|
||||||
msgstr ""
|
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
|
#: netbox/templates/inc/user_menu.html:23
|
||||||
msgid "Django Admin"
|
msgid "Django Admin"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -14064,17 +14080,17 @@ msgstr ""
|
|||||||
msgid "{value} is not a valid regular expression."
|
msgid "{value} is not a valid regular expression."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/utilities/views.py:40
|
#: netbox/utilities/views.py:44
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{self.__class__.__name__} must implement get_required_permission()"
|
msgid "{self.__class__.__name__} must implement get_required_permission()"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/utilities/views.py:76
|
#: netbox/utilities/views.py:80
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{class_name} must implement get_required_permission()"
|
msgid "{class_name} must implement get_required_permission()"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/utilities/views.py:100
|
#: netbox/utilities/views.py:104
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"{class_name} has no queryset defined. ObjectPermissionRequiredMixin may only "
|
"{class_name} has no queryset defined. ObjectPermissionRequiredMixin may only "
|
||||||
|
@ -29,7 +29,7 @@ def linkify_phone(value):
|
|||||||
"""
|
"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
return f"tel:{value}"
|
return f"tel:{value.replace(' ', '')}"
|
||||||
|
|
||||||
|
|
||||||
def register_table_column(column, name, *tables):
|
def register_table_column(column, name, *tables):
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
{% elif customfield.type == 'date' and value %}
|
{% elif customfield.type == 'date' and value %}
|
||||||
{{ value|isodate }}
|
{{ value|isodate }}
|
||||||
{% elif customfield.type == 'datetime' and value %}
|
{% elif customfield.type == 'datetime' and value %}
|
||||||
{{ value|isodate }} {{ value|isodatetime }}
|
{{ value|isodatetime }}
|
||||||
{% elif customfield.type == 'url' and value %}
|
{% elif customfield.type == 'url' and value %}
|
||||||
<a href="{{ value }}">{{ value|truncatechars:70 }}</a>
|
<a href="{{ value }}">{{ value|truncatechars:70 }}</a>
|
||||||
{% elif customfield.type == 'json' and value %}
|
{% elif customfield.type == 'json' and value %}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django import template
|
from django import template
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from extras.choices import CustomFieldTypeChoices
|
from extras.choices import CustomFieldTypeChoices
|
||||||
from utilities.querydict import dict_to_querydict
|
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 HTMX navigation is enabled (per the user's preferences).
|
||||||
"""
|
"""
|
||||||
if context.get('htmx_navigation', False):
|
if context.get('htmx_navigation', False):
|
||||||
return 'hx-push-url="true" hx-post'
|
return mark_safe('hx-push-url="true" hx-post')
|
||||||
return 'formaction'
|
return 'formaction'
|
||||||
|
@ -281,6 +281,10 @@ def applied_filters(context, model, form, query_params):
|
|||||||
if filter_name not in querydict:
|
if filter_name not in querydict:
|
||||||
continue
|
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)
|
bound_field = form.fields[filter_name].get_bound_field(form, filter_name)
|
||||||
querydict.pop(filter_name)
|
querydict.pop(filter_name)
|
||||||
display_value = ', '.join([str(v) for v in get_selected_values(form, 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.contrib.auth.mixins import AccessMixin
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -6,10 +8,12 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from netbox.plugins import PluginConfig
|
from netbox.plugins import PluginConfig
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
|
from utilities.relations import get_related_models
|
||||||
from .permissions import resolve_permission
|
from .permissions import resolve_permission
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ContentTypePermissionRequiredMixin',
|
'ContentTypePermissionRequiredMixin',
|
||||||
|
'GetRelatedModelsMixin',
|
||||||
'GetReturnURLMixin',
|
'GetReturnURLMixin',
|
||||||
'ObjectPermissionRequiredMixin',
|
'ObjectPermissionRequiredMixin',
|
||||||
'ViewTab',
|
'ViewTab',
|
||||||
@ -142,6 +146,46 @@ class GetReturnURLMixin:
|
|||||||
return reverse('home')
|
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:
|
class ViewTab:
|
||||||
"""
|
"""
|
||||||
ViewTabs are used for navigation among multiple object-specific views, such as the changelog or journal for
|
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 tenancy.views import ObjectContactsView
|
||||||
from utilities.query import count_related
|
from utilities.query import count_related
|
||||||
from utilities.query_functions import CollateAsChar
|
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 . import filtersets, forms, tables
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
@ -39,16 +39,12 @@ class ClusterTypeListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(ClusterType)
|
@register_model_view(ClusterType)
|
||||||
class ClusterTypeView(generic.ObjectView):
|
class ClusterTypeView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = ClusterType.objects.all()
|
queryset = ClusterType.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(Cluster.objects.restrict(request.user, 'view').filter(type=instance), 'type_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
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)
|
@register_model_view(ClusterGroup)
|
||||||
class ClusterGroupView(generic.ObjectView):
|
class ClusterGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = ClusterGroup.objects.all()
|
queryset = ClusterGroup.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(Cluster.objects.restrict(request.user, 'view').filter(group=instance), 'group_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
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 netbox.views import generic
|
||||||
from tenancy.views import ObjectContactsView
|
from tenancy.views import ObjectContactsView
|
||||||
from utilities.query import count_related
|
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 . import filtersets, forms, tables
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
@ -21,16 +21,12 @@ class TunnelGroupListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(TunnelGroup)
|
@register_model_view(TunnelGroup)
|
||||||
class TunnelGroupView(generic.ObjectView):
|
class TunnelGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = TunnelGroup.objects.all()
|
queryset = TunnelGroup.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = (
|
|
||||||
(Tunnel.objects.restrict(request.user, 'view').filter(group=instance), 'group_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'related_models': related_models,
|
'related_models': self.get_related_models(request, instance),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.query import count_related
|
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 . import filtersets, forms, tables
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
@ -24,17 +24,14 @@ class WirelessLANGroupListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(WirelessLANGroup)
|
@register_model_view(WirelessLANGroup)
|
||||||
class WirelessLANGroupView(generic.ObjectView):
|
class WirelessLANGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
queryset = WirelessLANGroup.objects.all()
|
queryset = WirelessLANGroup.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
groups = instance.get_descendants(include_self=True)
|
groups = instance.get_descendants(include_self=True)
|
||||||
related_models = (
|
|
||||||
(WirelessLAN.objects.restrict(request.user, 'view').filter(group__in=groups), 'group_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'related_models': related_models,
|
'related_models': self.get_related_models(request, groups),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user